@@ -2,6 +2,7 @@ import rfdc from 'rfdc'
2
2
import deepEqual from 'fast-deep-equal'
3
3
import * as dataPath from './data-path'
4
4
import * as dataTracer from './data-tracer'
5
+ import type { DataPathWithOptions } from './data-path'
5
6
6
7
const deepClone = rfdc ( { proto : true } )
7
8
@@ -24,8 +25,8 @@ interface ObserversItem {
24
25
25
26
interface ComputedWatchInfo {
26
27
computedUpdaters : Array < ( ...args : unknown [ ] ) => boolean >
27
- computedRelatedPathValues : Record < string , Array < dataTracer . RelatedPathValue > >
28
- watchCurVal : Record < string , any >
28
+ computedRelatedPathValues : Array < Array < dataTracer . RelatedPathValue > >
29
+ watchCurVal : Array < unknown >
29
30
_triggerFromComputedAttached : Record < string , boolean >
30
31
}
31
32
@@ -46,197 +47,221 @@ function equal(a: unknown, b: unknown) {
46
47
}
47
48
}
48
49
49
- export const behavior = Behavior ( {
50
- lifetimes : {
51
- attached ( this : BehaviorExtend ) {
52
- this . setData ( {
53
- _computedWatchInit : ComputedWatchInitStatus . ATTACHED ,
54
- } )
55
- } ,
56
- created ( this : BehaviorExtend ) {
57
- this . setData ( {
58
- _computedWatchInit : ComputedWatchInitStatus . CREATED ,
59
- } )
60
- } ,
61
- } ,
50
+ class ComputedBuilder {
51
+ observersItems : ObserversItem [ ] = [ ]
52
+ private computedWatchDefId = computedWatchDefIdInc ++
53
+ private computedList : Array < [ string , ( data : Record < string , unknown > ) => unknown ] > = [ ]
54
+ private watchList : Array < DataPathWithOptions [ ] > = [ ]
62
55
63
- definitionFilter ( defFields : any & BehaviorExtend ) {
64
- const computedDef = defFields . computed
65
- const watchDef = defFields . watch
66
- const observersItems : ObserversItem [ ] = [ ]
67
- const computedWatchDefId = computedWatchDefIdInc ++
68
- observersItems . push ( {
56
+ constructor ( ) {
57
+ const computedWatchDefId = this . computedWatchDefId
58
+ const computedList = this . computedList
59
+ const watchList = this . watchList
60
+ this . observersItems . push ( {
69
61
fields : '_computedWatchInit' ,
70
62
observer ( this : BehaviorExtend ) {
71
63
const status = this . data . _computedWatchInit
72
64
if ( status === ComputedWatchInitStatus . CREATED ) {
73
65
// init data fields
74
66
const computedWatchInfo = {
75
67
computedUpdaters : [ ] ,
76
- computedRelatedPathValues : { } ,
77
- watchCurVal : { } ,
68
+ computedRelatedPathValues : new Array ( computedList . length ) ,
69
+ watchCurVal : new Array ( watchList . length ) ,
78
70
_triggerFromComputedAttached : Object . create ( null ) ,
79
71
}
80
72
if ( ! this . _computedWatchInfo ) this . _computedWatchInfo = { }
81
73
this . _computedWatchInfo [ computedWatchDefId ] = computedWatchInfo
82
74
// handling watch
83
75
// 1. push to initFuncs
84
- if ( watchDef ) {
85
- Object . keys ( watchDef ) . forEach ( ( watchPath ) => {
86
- const paths = dataPath . parseMultiDataPaths ( watchPath )
87
- // record the original value of watch targets
88
- const curVal = paths . map ( ( { path, options } ) => {
89
- const val = dataPath . getDataOnPath ( this . data , path )
90
- return options . deepCmp ? deepClone ( val ) : val
91
- } )
92
- computedWatchInfo . watchCurVal [ watchPath ] = curVal
76
+ watchList . forEach ( ( paths , index ) => {
77
+ // record the original value of watch targets
78
+ const curVal = paths . map ( ( { path, options } ) => {
79
+ const val = dataPath . getDataOnPath ( this . data , path )
80
+ return options . deepCmp ? deepClone ( val ) : val
93
81
} )
94
- }
82
+ computedWatchInfo . watchCurVal [ index ] = curVal
83
+ } )
95
84
} else if ( status === ComputedWatchInitStatus . ATTACHED ) {
96
85
// handling computed
97
86
// 1. push to initFuncs
98
87
// 2. push to computedUpdaters
99
88
const computedWatchInfo = this . _computedWatchInfo [ computedWatchDefId ]
100
- if ( computedDef ) {
101
- Object . keys ( computedDef ) . forEach ( ( targetField ) => {
102
- const updateMethod = computedDef [ targetField ]
103
- const relatedPathValuesOnDef = [ ] as Array < dataTracer . RelatedPathValue >
104
- const val = updateMethod ( dataTracer . create ( this . data , relatedPathValuesOnDef ) )
105
- // here we can do small setDatas
106
- // because observer handlers will force grouping small setDatas together
107
- this . setData ( {
108
- [ targetField ] : dataTracer . unwrap ( val ) ,
109
- } )
110
- computedWatchInfo . _triggerFromComputedAttached [ targetField ] = true
111
- computedWatchInfo . computedRelatedPathValues [ targetField ] = relatedPathValuesOnDef
89
+ computedList . forEach ( ( [ targetField , updateMethod ] , index ) => {
90
+ const relatedPathValuesOnDef = [ ] as Array < dataTracer . RelatedPathValue >
91
+ const val = updateMethod ( dataTracer . create ( this . data , relatedPathValuesOnDef ) )
92
+ // here we can do small setDatas
93
+ // because observer handlers will force grouping small setDatas together
94
+ this . setData ( {
95
+ [ targetField ] : dataTracer . unwrap ( val ) ,
96
+ } )
97
+ computedWatchInfo . _triggerFromComputedAttached [ targetField ] = true
98
+ computedWatchInfo . computedRelatedPathValues [ index ] = relatedPathValuesOnDef
112
99
113
- // will be invoked when setData is called
114
- const updateValueAndRelatedPaths = ( ) => {
115
- const oldPathValues = computedWatchInfo . computedRelatedPathValues [ targetField ]
116
- let needUpdate = false
117
- // check whether its dependency updated
118
- for ( let i = 0 ; i < oldPathValues . length ; i ++ ) {
119
- const item = oldPathValues [ i ]
120
- if ( item . kind === 'keys' ) {
121
- const { path, keys : oldKeys } = item
122
- const curVal = dataPath . getDataOnPath ( this . data , path )
123
- const keys = Object . keys ( curVal ) . sort ( )
124
- if ( keys . length !== oldKeys . length ) {
125
- needUpdate = true
126
- break
127
- }
128
- for ( let j = 0 ; j < keys . length ; j += 1 ) {
129
- if ( keys [ j ] !== oldKeys [ j ] ) {
130
- needUpdate = true
131
- break
132
- }
133
- }
134
- } else {
135
- const { path, value : oldVal } = item
136
- const curVal = dataPath . getDataOnPath ( this . data , path )
137
- if ( ! equal ( oldVal , curVal ) ) {
100
+ // will be invoked when setData is called
101
+ const updateValueAndRelatedPaths = ( ) => {
102
+ const oldPathValues = computedWatchInfo . computedRelatedPathValues [ index ]
103
+ let needUpdate = false
104
+ // check whether its dependency updated
105
+ for ( let i = 0 ; i < oldPathValues . length ; i ++ ) {
106
+ const item = oldPathValues [ i ]
107
+ if ( item . kind === 'keys' ) {
108
+ const { path, keys : oldKeys } = item
109
+ const curVal = dataPath . getDataOnPath ( this . data , path )
110
+ const keys = Object . keys ( curVal ) . sort ( )
111
+ if ( keys . length !== oldKeys . length ) {
112
+ needUpdate = true
113
+ break
114
+ }
115
+ for ( let j = 0 ; j < keys . length ; j += 1 ) {
116
+ if ( keys [ j ] !== oldKeys [ j ] ) {
138
117
needUpdate = true
139
118
break
140
119
}
141
120
}
121
+ } else {
122
+ const { path, value : oldVal } = item
123
+ const curVal = dataPath . getDataOnPath ( this . data , path )
124
+ if ( ! equal ( oldVal , curVal ) ) {
125
+ needUpdate = true
126
+ break
127
+ }
142
128
}
143
- if ( ! needUpdate ) return false
144
-
145
- const relatedPathValues = [ ] as Array < dataTracer . RelatedPathValue >
146
- const val = updateMethod ( dataTracer . create ( this . data , relatedPathValues ) )
147
- this . setData ( {
148
- [ targetField ] : dataTracer . unwrap ( val ) ,
149
- } )
150
- computedWatchInfo . computedRelatedPathValues [ targetField ] = relatedPathValues
151
- return true
152
129
}
153
- computedWatchInfo . computedUpdaters . push ( updateValueAndRelatedPaths )
154
- } )
155
- }
130
+ if ( ! needUpdate ) return false
131
+
132
+ const relatedPathValues = [ ] as Array < dataTracer . RelatedPathValue >
133
+ const val = updateMethod ( dataTracer . create ( this . data , relatedPathValues ) )
134
+ this . setData ( {
135
+ [ targetField ] : dataTracer . unwrap ( val ) ,
136
+ } )
137
+ computedWatchInfo . computedRelatedPathValues [ index ] = relatedPathValues
138
+ return true
139
+ }
140
+ computedWatchInfo . computedUpdaters . push ( updateValueAndRelatedPaths )
141
+ } )
156
142
}
157
143
} ,
158
144
} )
145
+ }
159
146
160
- if ( computedDef ) {
161
- observersItems . push ( {
162
- fields : '**' ,
163
- observer ( this : BehaviorExtend ) {
164
- if ( ! this . _computedWatchInfo ) return
165
- const computedWatchInfo = this . _computedWatchInfo [ computedWatchDefId ]
166
- if ( ! computedWatchInfo ) return
147
+ addComputed ( targetField : string , updateMethod : ( data : Record < string , unknown > ) => unknown ) {
148
+ this . computedList . push ( [ targetField , updateMethod ] )
149
+ if ( this . computedList . length !== 1 ) return
150
+ const computedWatchDefId = this . computedWatchDefId
151
+ this . observersItems . push ( {
152
+ fields : '**' ,
153
+ observer ( this : BehaviorExtend ) {
154
+ if ( ! this . _computedWatchInfo ) return
155
+ const computedWatchInfo = this . _computedWatchInfo [ computedWatchDefId ]
156
+ if ( ! computedWatchInfo ) return
167
157
168
- let changed : boolean
169
- do {
170
- try {
171
- changed = computedWatchInfo . computedUpdaters . some ( ( func ) => func . call ( this ) )
172
- } catch ( err ) {
173
- console . error ( err . stack )
174
- break
175
- }
176
- } while ( changed )
177
- } ,
178
- } )
179
- }
158
+ let changed : boolean
159
+ do {
160
+ try {
161
+ changed = computedWatchInfo . computedUpdaters . some ( ( func ) => func . call ( this ) )
162
+ } catch ( err ) {
163
+ console . error ( err . stack )
164
+ break
165
+ }
166
+ } while ( changed )
167
+ } ,
168
+ } )
169
+ }
180
170
181
- if ( watchDef ) {
182
- Object . keys ( watchDef ) . forEach ( ( watchPath ) => {
183
- const paths = dataPath . parseMultiDataPaths ( watchPath )
184
- observersItems . push ( {
185
- fields : watchPath ,
186
- observer ( this : BehaviorExtend ) {
187
- if ( ! this . _computedWatchInfo ) return
188
- const computedWatchInfo = this . _computedWatchInfo [ computedWatchDefId ]
189
- if ( ! computedWatchInfo ) return
190
- // (issue #58) ignore watch func when trigger by computed attached
191
- if ( Object . keys ( computedWatchInfo . _triggerFromComputedAttached ) . length ) {
192
- const pathsMap : Record < string , boolean > = { }
193
- paths . forEach ( ( path ) => ( pathsMap [ path . path [ 0 ] ] = true ) )
194
- for ( const computedVal in computedWatchInfo . _triggerFromComputedAttached ) {
195
- if ( computedWatchInfo . _triggerFromComputedAttached [ computedVal ] ) {
196
- if (
197
- pathsMap [ computedVal ] &&
198
- computedWatchInfo . _triggerFromComputedAttached [ computedVal ]
199
- ) {
200
- computedWatchInfo . _triggerFromComputedAttached [ computedVal ] = false
201
- return
202
- }
203
- }
171
+ addWatch ( watchPath : string , listener : ( args : any ) => void ) {
172
+ const paths = dataPath . parseMultiDataPaths ( watchPath )
173
+ const index = this . watchList . length
174
+ this . watchList . push ( paths )
175
+ const computedWatchDefId = this . computedWatchDefId
176
+ this . observersItems . push ( {
177
+ fields : watchPath ,
178
+ observer ( this : BehaviorExtend ) {
179
+ if ( ! this . _computedWatchInfo ) return
180
+ const computedWatchInfo = this . _computedWatchInfo [ computedWatchDefId ]
181
+ if ( ! computedWatchInfo ) return
182
+ // (issue #58) ignore watch func when trigger by computed attached
183
+ if ( Object . keys ( computedWatchInfo . _triggerFromComputedAttached ) . length ) {
184
+ const pathsMap : Record < string , boolean > = { }
185
+ paths . forEach ( ( path ) => ( pathsMap [ path . path [ 0 ] ] = true ) )
186
+ for ( const computedVal in computedWatchInfo . _triggerFromComputedAttached ) {
187
+ if ( computedWatchInfo . _triggerFromComputedAttached [ computedVal ] ) {
188
+ if (
189
+ pathsMap [ computedVal ] &&
190
+ computedWatchInfo . _triggerFromComputedAttached [ computedVal ]
191
+ ) {
192
+ computedWatchInfo . _triggerFromComputedAttached [ computedVal ] = false
193
+ return
204
194
}
205
195
}
206
- const oldVal = computedWatchInfo . watchCurVal [ watchPath ]
196
+ }
197
+ }
198
+ const oldVal = computedWatchInfo . watchCurVal [ index ]
207
199
208
- // get new watching field value
209
- const originalCurValWithOptions = paths . map ( ( { path, options } ) => {
210
- const val = dataPath . getDataOnPath ( this . data , path )
211
- return { val, options }
212
- } )
213
- const curVal = originalCurValWithOptions . map ( ( { val, options } ) =>
214
- options . deepCmp ? deepClone ( val ) : val ,
215
- )
216
- computedWatchInfo . watchCurVal [ watchPath ] = curVal
200
+ // get new watching field value
201
+ const originalCurValWithOptions = paths . map ( ( { path, options } ) => {
202
+ const val = dataPath . getDataOnPath ( this . data , path )
203
+ return { val, options }
204
+ } )
205
+ const curVal = originalCurValWithOptions . map ( ( { val, options } ) =>
206
+ options . deepCmp ? deepClone ( val ) : val ,
207
+ )
208
+ computedWatchInfo . watchCurVal [ index ] = curVal
217
209
218
- // compare
219
- let changed = false
220
- for ( let i = 0 ; i < curVal . length ; i ++ ) {
221
- const options = paths [ i ] . options
222
- const deepCmp = options . deepCmp
223
- if ( deepCmp ? ! deepEqual ( oldVal [ i ] , curVal [ i ] ) : ! equal ( oldVal [ i ] , curVal [ i ] ) ) {
224
- changed = true
225
- break
226
- }
227
- }
210
+ // compare
211
+ let changed = false
212
+ for ( let i = 0 ; i < curVal . length ; i ++ ) {
213
+ const options = paths [ i ] . options
214
+ const deepCmp = options . deepCmp
215
+ if ( deepCmp ? ! deepEqual ( oldVal [ i ] , curVal [ i ] ) : ! equal ( oldVal [ i ] , curVal [ i ] ) ) {
216
+ changed = true
217
+ break
218
+ }
219
+ }
228
220
229
- // if changed, update
230
- if ( changed ) {
231
- watchDef [ watchPath ] . apply (
232
- this ,
233
- originalCurValWithOptions . map ( ( { val } ) => val ) ,
234
- )
235
- }
236
- } ,
237
- } )
221
+ // if changed, update
222
+ if ( changed ) {
223
+ listener . apply (
224
+ this ,
225
+ originalCurValWithOptions . map ( ( { val } ) => val ) ,
226
+ )
227
+ }
228
+ } ,
229
+ } )
230
+ }
231
+ }
232
+
233
+ export const behavior = Behavior ( {
234
+ lifetimes : {
235
+ attached ( this : BehaviorExtend ) {
236
+ this . setData ( {
237
+ _computedWatchInit : ComputedWatchInitStatus . ATTACHED ,
238
+ } )
239
+ } ,
240
+ created ( this : BehaviorExtend ) {
241
+ this . setData ( {
242
+ _computedWatchInit : ComputedWatchInitStatus . CREATED ,
243
+ } )
244
+ } ,
245
+ } ,
246
+
247
+ definitionFilter ( defFields : any & BehaviorExtend ) {
248
+ const computedDef = defFields . computed
249
+ const watchDef = defFields . watch
250
+
251
+ const builder = new ComputedBuilder ( )
252
+ if ( computedDef ) {
253
+ Object . keys ( computedDef ) . forEach ( ( targetField ) => {
254
+ const updateMethod = computedDef [ targetField ]
255
+ builder . addComputed ( targetField , updateMethod )
256
+ } )
257
+ }
258
+ if ( watchDef ) {
259
+ Object . keys ( watchDef ) . forEach ( ( watchPath ) => {
260
+ const listener = watchDef [ watchPath ]
261
+ builder . addWatch ( watchPath , listener )
238
262
} )
239
263
}
264
+ const observersItems = builder . observersItems
240
265
241
266
if ( typeof defFields . observers !== 'object' ) {
242
267
defFields . observers = { }
0 commit comments