Skip to content

Commit 528d1d5

Browse files
author
boquanfu
committed
refactor: add computed manager
1 parent 3684dd6 commit 528d1d5

File tree

3 files changed

+190
-160
lines changed

3 files changed

+190
-160
lines changed

src/behavior.ts

Lines changed: 178 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import rfdc from 'rfdc'
22
import deepEqual from 'fast-deep-equal'
33
import * as dataPath from './data-path'
44
import * as dataTracer from './data-tracer'
5+
import type { DataPathWithOptions } from './data-path'
56

67
const deepClone = rfdc({ proto: true })
78

@@ -24,8 +25,8 @@ interface ObserversItem {
2425

2526
interface ComputedWatchInfo {
2627
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>
2930
_triggerFromComputedAttached: Record<string, boolean>
3031
}
3132

@@ -46,197 +47,221 @@ function equal(a: unknown, b: unknown) {
4647
}
4748
}
4849

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[]> = []
6255

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({
6961
fields: '_computedWatchInit',
7062
observer(this: BehaviorExtend) {
7163
const status = this.data._computedWatchInit
7264
if (status === ComputedWatchInitStatus.CREATED) {
7365
// init data fields
7466
const computedWatchInfo = {
7567
computedUpdaters: [],
76-
computedRelatedPathValues: {},
77-
watchCurVal: {},
68+
computedRelatedPathValues: new Array(computedList.length),
69+
watchCurVal: new Array(watchList.length),
7870
_triggerFromComputedAttached: Object.create(null),
7971
}
8072
if (!this._computedWatchInfo) this._computedWatchInfo = {}
8173
this._computedWatchInfo[computedWatchDefId] = computedWatchInfo
8274
// handling watch
8375
// 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
9381
})
94-
}
82+
computedWatchInfo.watchCurVal[index] = curVal
83+
})
9584
} else if (status === ComputedWatchInitStatus.ATTACHED) {
9685
// handling computed
9786
// 1. push to initFuncs
9887
// 2. push to computedUpdaters
9988
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
11299

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]) {
138117
needUpdate = true
139118
break
140119
}
141120
}
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+
}
142128
}
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
152129
}
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+
})
156142
}
157143
},
158144
})
145+
}
159146

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
167157

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+
}
180170

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
204194
}
205195
}
206-
const oldVal = computedWatchInfo.watchCurVal[watchPath]
196+
}
197+
}
198+
const oldVal = computedWatchInfo.watchCurVal[index]
207199

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
217209

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+
}
228220

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)
238262
})
239263
}
264+
const observersItems = builder.observersItems
240265

241266
if (typeof defFields.observers !== 'object') {
242267
defFields.observers = {}

0 commit comments

Comments
 (0)