forked from mobxjs/mobx
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautorun.ts
More file actions
163 lines (146 loc) · 4.47 KB
/
Copy pathautorun.ts
File metadata and controls
163 lines (146 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import {
EMPTY_OBJECT,
IEqualsComparer,
IReactionDisposer,
IReactionPublic,
Lambda,
Reaction,
action,
comparer,
getNextId,
invariant,
isAction
} from "../internal"
export interface IAutorunOptions {
delay?: number
name?: string
scheduler?: (callback: () => void) => any
onError?: (error: any) => void
}
/**
* Creates a named reactive view and keeps it alive, so that the view is always
* updated if one of the dependencies changes, even when the view is not further used by something else.
* @param view The reactive view
* @returns disposer function, which can be used to stop the view from being updated in the future.
*/
export function autorun(
view: (r: IReactionPublic) => any,
opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
if (process.env.NODE_ENV !== "production") {
invariant(typeof view === "function", "Autorun expects a function as first argument")
invariant(
isAction(view) === false,
"Autorun does not accept actions since actions are untrackable"
)
}
const name: string = (opts && opts.name) || (view as any).name || "Autorun@" + getNextId()
const runSync = !opts.scheduler && !opts.delay
let reaction: Reaction
if (runSync) {
// normal autorun
reaction = new Reaction(
name,
function(this: Reaction) {
this.track(reactionRunner)
},
opts.onError
)
} else {
const scheduler = createSchedulerFromOptions(opts)
// debounced autorun
let isScheduled = false
reaction = new Reaction(
name,
() => {
if (!isScheduled) {
isScheduled = true
scheduler(() => {
isScheduled = false
if (!reaction.isDisposed) reaction.track(reactionRunner)
})
}
},
opts.onError
)
}
function reactionRunner() {
view(reaction)
}
reaction.schedule()
return reaction.getDisposer()
}
export type IReactionOptions = IAutorunOptions & {
fireImmediately?: boolean
equals?: IEqualsComparer<any>
}
const run = (f: Lambda) => f()
function createSchedulerFromOptions(opts: IReactionOptions) {
return opts.scheduler
? opts.scheduler
: opts.delay
? (f: Lambda) => setTimeout(f, opts.delay!)
: run
}
export function reaction<T>(
expression: (r: IReactionPublic) => T,
effect: (arg: T, r: IReactionPublic) => void,
opts: IReactionOptions = EMPTY_OBJECT
): IReactionDisposer {
if (process.env.NODE_ENV !== "production") {
invariant(
typeof expression === "function",
"First argument to reaction should be a function"
)
invariant(typeof opts === "object", "Third argument of reactions should be an object")
}
const name = opts.name || "Reaction@" + getNextId()
const effectAction = action(
name,
opts.onError ? wrapErrorHandler(opts.onError, effect) : effect
)
const runSync = !opts.scheduler && !opts.delay
const scheduler = createSchedulerFromOptions(opts)
let firstTime = true
let isScheduled = false
let value: T
const equals = (opts as any).compareStructural
? comparer.structural
: opts.equals || comparer.default
const r = new Reaction(
name,
() => {
if (firstTime || runSync) {
reactionRunner()
} else if (!isScheduled) {
isScheduled = true
scheduler!(reactionRunner)
}
},
opts.onError
)
function reactionRunner() {
isScheduled = false // Q: move into reaction runner?
if (r.isDisposed) return
let changed = false
r.track(() => {
const nextValue = expression(r)
changed = firstTime || !equals(value, nextValue)
value = nextValue
})
if (firstTime && opts.fireImmediately!) effectAction(value, r)
if (!firstTime && (changed as boolean) === true) effectAction(value, r)
if (firstTime) firstTime = false
}
r.schedule()
return r.getDisposer()
}
function wrapErrorHandler(errorHandler, baseFn) {
return function() {
try {
return baseFn.apply(this, arguments)
} catch (e) {
errorHandler.call(this, e)
}
}
}