You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
5.1 KiB
160 lines
5.1 KiB
import dataPath from './data-path';
|
|
import dataTracer from './data-tracer';
|
|
|
|
const getDataOnPath = function (data, path) {
|
|
let ret = data
|
|
path.forEach((s) => {
|
|
if (typeof ret !== 'object' || ret === null) ret = undefined
|
|
else ret = ret[s]
|
|
})
|
|
return ret
|
|
}
|
|
|
|
const setDataOnPath = function (data, path, value) {
|
|
let cur = data
|
|
let index = 0
|
|
while (index < path.length - 1) {
|
|
const s = path[index++]
|
|
if (typeof s === 'number') {
|
|
if (!(cur[s] instanceof Array)) {
|
|
cur[s] = []
|
|
}
|
|
} else if (typeof cur[s] !== 'object' || cur[s] === null) {
|
|
cur[s] = {}
|
|
}
|
|
cur = cur[s]
|
|
}
|
|
cur[path[index]] = value
|
|
}
|
|
|
|
export default Behavior({
|
|
lifetimes: {
|
|
created() {
|
|
this._initComputedWatchInfo()
|
|
}
|
|
},
|
|
definitionFilter(defFields) {
|
|
const computedDef = defFields.computed || {}
|
|
const watchDef = defFields.watch || {}
|
|
|
|
const observersItems = []
|
|
if (!defFields.methods) {
|
|
defFields.methods = {}
|
|
}
|
|
if (!defFields.data) {
|
|
defFields.data = {}
|
|
}
|
|
if (defFields.methods._initComputedWatchInfo) {
|
|
throw new Error('Please do not use this behavior more than once in a single component')
|
|
}
|
|
|
|
// initialize status, executed on created
|
|
const initFuncs = []
|
|
defFields.methods._initComputedWatchInfo = function () {
|
|
if (this._computedWatchInfo) return
|
|
this._computedWatchInfo = {
|
|
computedRelatedPathValues: {},
|
|
watchCurVal: {},
|
|
}
|
|
initFuncs.forEach((func) => func.call(this))
|
|
}
|
|
|
|
// handling computed
|
|
const computedUpdaters = []
|
|
Object.keys(computedDef).forEach((targetField) => {
|
|
const targetPath = dataPath.parseSingleDataPath(targetField)
|
|
const updateMethod = computedDef[targetField]
|
|
// update value and calculate related paths
|
|
const updateValueAndRelatedPaths = function () {
|
|
const oldPathValues = this._computedWatchInfo.computedRelatedPathValues[targetField]
|
|
let needUpdate = false
|
|
for (let i = 0; i < oldPathValues.length; i++) {
|
|
const {path, value: oldVal} = oldPathValues[i]
|
|
const curVal = getDataOnPath(this.data, path)
|
|
if (oldVal !== curVal) {
|
|
needUpdate = true
|
|
break
|
|
}
|
|
}
|
|
if (!needUpdate) return false
|
|
const relatedPathValues = []
|
|
const val = updateMethod(dataTracer.create(this.data, relatedPathValues))
|
|
this.setData({
|
|
[targetField]: val
|
|
})
|
|
this._computedWatchInfo.computedRelatedPathValues[targetField] = relatedPathValues
|
|
return true
|
|
}
|
|
// calculate value on registration
|
|
const relatedPathValuesOnDef = []
|
|
const val = updateMethod(dataTracer.create(defFields.data, relatedPathValuesOnDef))
|
|
setDataOnPath(defFields.data, targetPath, val)
|
|
initFuncs.push(function () {
|
|
const pathValues = relatedPathValuesOnDef.map(({path}) => ({
|
|
path,
|
|
value: getDataOnPath(this.data, path)
|
|
}))
|
|
this._computedWatchInfo.computedRelatedPathValues[targetField] = pathValues
|
|
})
|
|
// calculate value on setData
|
|
computedUpdaters.push(updateValueAndRelatedPaths)
|
|
})
|
|
if (computedUpdaters.length) {
|
|
// add a single observer for all computed fields
|
|
observersItems.push({
|
|
fields: '**',
|
|
observer() {
|
|
if (!this._computedWatchInfo) return
|
|
let changed
|
|
do {
|
|
changed = false
|
|
// eslint-disable-next-line no-loop-func
|
|
computedUpdaters.forEach((func) => {
|
|
if (func.call(this)) changed = true
|
|
})
|
|
} while (changed)
|
|
}
|
|
})
|
|
}
|
|
|
|
// handling watch
|
|
Object.keys(watchDef).forEach((watchPath) => {
|
|
const paths = dataPath.parseMultiDataPaths(watchPath)
|
|
// record the original value of watch targets
|
|
initFuncs.push(function () {
|
|
const curVal = paths.map((path) => getDataOnPath(this.data, path))
|
|
this._computedWatchInfo.watchCurVal[watchPath] = curVal
|
|
})
|
|
// add watch observer
|
|
observersItems.push({
|
|
fields: watchPath,
|
|
observer() {
|
|
if (!this._computedWatchInfo) return
|
|
const oldVal = this._computedWatchInfo.watchCurVal[watchPath]
|
|
const curVal = paths.map((path) => getDataOnPath(this.data, path))
|
|
this._computedWatchInfo.watchCurVal[watchPath] = curVal
|
|
let changed = false
|
|
for (let i = 0; i < curVal.length; i++) {
|
|
if (oldVal[i] !== curVal[i]) {
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
if (changed) watchDef[watchPath].apply(this, curVal)
|
|
}
|
|
})
|
|
})
|
|
|
|
// register to observers
|
|
if (typeof defFields.observers !== 'object') {
|
|
defFields.observers = {}
|
|
}
|
|
if (defFields.observers instanceof Array) {
|
|
defFields.observers.push(...observersItems)
|
|
} else {
|
|
observersItems.forEach((item) => {
|
|
defFields.observers[item.fields] = item.observer
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|