forked from mobxjs/mobx.dart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcomputed.dart
200 lines (165 loc) · 5.41 KB
/
computed.dart
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
part of '../core.dart';
class Computed<T> extends Atom implements Derivation, ObservableValue<T> {
/// Creates a computed value with an optional [name].
///
/// The passed in function: [fn], is used to give back the computed value.
/// Computed values can depend on other observables and computed values!
/// This makes them both an *observable* and an *observer*.
/// Computed values are also referred to as _derived-values_ because they inherently _derive_ their
/// value from other observables. Don't underestimate the power of the **computed**.
/// They are possibly the most powerful observables in your application.
///
/// A computed's value is read with the `value` property.
///
/// It is possible to override equality comparison (when deciding whether to notify observers)
/// by providing an [equals] comparator.
///
/// [keepAlive]
/// This avoids suspending computed values when they are not being observed by anything.
/// Can potentially create memory leaks.
///
/// ```
/// var x = Observable(10);
/// var y = Observable(10);
/// var total = Computed((){
/// return x.value + y.value;
/// });
///
/// x.value = 100; // recomputes total
/// y.value = 100; // recomputes total again
///
/// print('total = ${total.value}'); // prints "total = 200"
/// ```
///
/// A computed value is _cached_ and it recomputes only when the dependent observables actually
/// change. This makes them fast and you are free to use them throughout your application. Internally
/// MobX uses a 2-phase change propagation that ensures no unnecessary computations are performed.
factory Computed(T Function() fn,
{String? name,
ReactiveContext? context,
EqualityComparer<T>? equals,
bool? keepAlive}) =>
Computed._(context ?? mainContext, fn,
name: name, equals: equals, keepAlive: keepAlive);
Computed._(ReactiveContext context, this._fn,
{String? name, this.equals, bool? keepAlive})
: _keepAlive = keepAlive ?? false,
super._(context, name: name ?? context.nameFor('Computed'));
final EqualityComparer<T>? equals;
final bool _keepAlive;
@override
MobXCaughtException? _errorValue;
@override
MobXCaughtException? get errorValue => _errorValue;
@override
// ignore: prefer_final_fields
Set<Atom> _observables = {};
@override
Set<Atom>? _newObservables;
T Function() _fn;
@override
// ignore: prefer_final_fields
DerivationState _dependenciesState = DerivationState.notTracking;
T? _value;
bool _isComputing = false;
@override
T get value {
if (_isComputing) {
throw MobXCyclicReactionException(
'Cycle detected in computation $name: $_fn');
}
if (!_context.isWithinBatch && _observers.isEmpty && !_keepAlive) {
if (_context._shouldCompute(this)) {
_context.startBatch();
_value = computeValue(track: false);
_context.endBatch();
}
} else {
reportObserved();
if (_context._shouldCompute(this)) {
if (_trackAndCompute()) {
_context._propagateChangeConfirmed(this);
}
}
}
if (_context._hasCaughtException(this)) {
Error.throwWithStackTrace(_errorValue!, _errorValue!._stackTrace);
}
return _value as T;
}
T? computeValue({required bool track}) {
_isComputing = true;
_context.pushComputation();
T? value;
if (track) {
value = _context.trackDerivation(this, _fn);
} else {
if (_context.config.disableErrorBoundaries == true) {
value = _fn();
} else {
try {
value = _fn();
_errorValue = null;
} on Object catch (e, s) {
_errorValue = MobXCaughtException(e, stackTrace: s);
}
}
}
_context.popComputation();
_isComputing = false;
return value;
}
@override
void _suspend() {
if (!this._keepAlive) {
_context.clearObservables(this);
_value = null;
}
}
@override
void _onBecomeStale() {
_context._propagatePossiblyChanged(this);
}
bool _trackAndCompute() {
if (_context.isSpyEnabled) {
_context.spyReport(ComputedValueSpyEvent(this, name: name));
}
final oldValue = _value;
final wasSuspended = _dependenciesState == DerivationState.notTracking;
final hadCaughtException = _context._hasCaughtException(this);
final newValue = computeValue(track: true);
final changedException =
hadCaughtException != _context._hasCaughtException(this);
final changed =
wasSuspended || changedException || !_isEqual(oldValue, newValue);
if (changed) {
_value = newValue;
}
return changed;
}
bool _isEqual(T? x, T? y) => equals == null ? x == y : equals!(x, y);
void Function() observe(void Function(ChangeNotification<T>) handler,
{@Deprecated(
'fireImmediately has no effect anymore. It is on by default.')
bool? fireImmediately}) {
T? prevValue;
void notifyChange() {
_context.untracked(() {
handler(ChangeNotification(
type: OperationType.update,
object: this,
oldValue: prevValue,
newValue: value));
});
}
return autorun((_) {
final newValue = value;
notifyChange();
prevValue = newValue;
}, context: _context)
.call;
}
@override
String toString() =>
'Computed<$T>(name: $name, identity: ${identityHashCode(this)})';
}