1 | import {
|
2 | IDerivation,
|
3 | endBatch,
|
4 | globalState,
|
5 | isSpyEnabled,
|
6 | spyReportEnd,
|
7 | spyReportStart,
|
8 | startBatch,
|
9 | untrackedEnd,
|
10 | untrackedStart,
|
11 | isFunction,
|
12 | allowStateReadsStart,
|
13 | allowStateReadsEnd,
|
14 | ACTION,
|
15 | EMPTY_ARRAY,
|
16 | die,
|
17 | getDescriptor
|
18 | } from "../internal"
|
19 |
|
20 |
|
21 |
|
22 | let currentActionId = 0
|
23 | let nextActionId = 1
|
24 | const isFunctionNameConfigurable = getDescriptor(() => {}, "name")?.configurable ?? false
|
25 |
|
26 |
|
27 | const tmpNameDescriptor: PropertyDescriptor = {
|
28 | value: "action",
|
29 | configurable: true,
|
30 | writable: false,
|
31 | enumerable: false
|
32 | }
|
33 |
|
34 | export function createAction(
|
35 | actionName: string,
|
36 | fn: Function,
|
37 | autoAction: boolean = false,
|
38 | ref?: Object
|
39 | ): Function {
|
40 | if (__DEV__) {
|
41 | if (!isFunction(fn)) die("`action` can only be invoked on functions")
|
42 | if (typeof actionName !== "string" || !actionName)
|
43 | die(`actions should have valid names, got: '${actionName}'`)
|
44 | }
|
45 | function res() {
|
46 | return executeAction(actionName, autoAction, fn, ref || this, arguments)
|
47 | }
|
48 | res.isMobxAction = true
|
49 | if (isFunctionNameConfigurable) {
|
50 | tmpNameDescriptor.value = actionName
|
51 | Object.defineProperty(res, "name", tmpNameDescriptor)
|
52 | }
|
53 | return res
|
54 | }
|
55 |
|
56 | export function executeAction(
|
57 | actionName: string,
|
58 | canRunAsDerivation: boolean,
|
59 | fn: Function,
|
60 | scope?: any,
|
61 | args?: IArguments
|
62 | ) {
|
63 | const runInfo = _startAction(actionName, canRunAsDerivation, scope, args)
|
64 | try {
|
65 | return fn.apply(scope, args)
|
66 | } catch (err) {
|
67 | runInfo.error_ = err
|
68 | throw err
|
69 | } finally {
|
70 | _endAction(runInfo)
|
71 | }
|
72 | }
|
73 |
|
74 | export interface IActionRunInfo {
|
75 | prevDerivation_: IDerivation | null
|
76 | prevAllowStateChanges_: boolean
|
77 | prevAllowStateReads_: boolean
|
78 | notifySpy_: boolean
|
79 | startTime_: number
|
80 | error_?: any
|
81 | parentActionId_: number
|
82 | actionId_: number
|
83 | runAsAction_?: boolean
|
84 | }
|
85 |
|
86 | export function _startAction(
|
87 | actionName: string,
|
88 | canRunAsDerivation: boolean,
|
89 | scope: any,
|
90 | args?: IArguments
|
91 | ): IActionRunInfo {
|
92 | const notifySpy_ = __DEV__ && isSpyEnabled() && !!actionName
|
93 | let startTime_: number = 0
|
94 | if (__DEV__ && notifySpy_) {
|
95 | startTime_ = Date.now()
|
96 | const flattenedArgs = args ? Array.from(args) : EMPTY_ARRAY
|
97 | spyReportStart({
|
98 | type: ACTION,
|
99 | name: actionName,
|
100 | object: scope,
|
101 | arguments: flattenedArgs
|
102 | })
|
103 | }
|
104 | const prevDerivation_ = globalState.trackingDerivation
|
105 | const runAsAction = !canRunAsDerivation || !prevDerivation_
|
106 | startBatch()
|
107 | let prevAllowStateChanges_ = globalState.allowStateChanges
|
108 | if (runAsAction) {
|
109 | untrackedStart()
|
110 | prevAllowStateChanges_ = allowStateChangesStart(true)
|
111 | }
|
112 | const prevAllowStateReads_ = allowStateReadsStart(true)
|
113 | const runInfo = {
|
114 | runAsAction_: runAsAction,
|
115 | prevDerivation_,
|
116 | prevAllowStateChanges_,
|
117 | prevAllowStateReads_,
|
118 | notifySpy_,
|
119 | startTime_,
|
120 | actionId_: nextActionId++,
|
121 | parentActionId_: currentActionId
|
122 | }
|
123 | currentActionId = runInfo.actionId_
|
124 | return runInfo
|
125 | }
|
126 |
|
127 | export function _endAction(runInfo: IActionRunInfo) {
|
128 | if (currentActionId !== runInfo.actionId_) {
|
129 | die(30)
|
130 | }
|
131 | currentActionId = runInfo.parentActionId_
|
132 |
|
133 | if (runInfo.error_ !== undefined) {
|
134 | globalState.suppressReactionErrors = true
|
135 | }
|
136 | allowStateChangesEnd(runInfo.prevAllowStateChanges_)
|
137 | allowStateReadsEnd(runInfo.prevAllowStateReads_)
|
138 | endBatch()
|
139 | if (runInfo.runAsAction_) untrackedEnd(runInfo.prevDerivation_)
|
140 | if (__DEV__ && runInfo.notifySpy_) {
|
141 | spyReportEnd({ time: Date.now() - runInfo.startTime_ })
|
142 | }
|
143 | globalState.suppressReactionErrors = false
|
144 | }
|
145 |
|
146 | export function allowStateChanges<T>(allowStateChanges: boolean, func: () => T): T {
|
147 | const prev = allowStateChangesStart(allowStateChanges)
|
148 | try {
|
149 | return func()
|
150 | } finally {
|
151 | allowStateChangesEnd(prev)
|
152 | }
|
153 | }
|
154 |
|
155 | export function allowStateChangesStart(allowStateChanges: boolean) {
|
156 | const prev = globalState.allowStateChanges
|
157 | globalState.allowStateChanges = allowStateChanges
|
158 | return prev
|
159 | }
|
160 |
|
161 | export function allowStateChangesEnd(prev: boolean) {
|
162 | globalState.allowStateChanges = prev
|
163 | }
|