d-render
Version:
数据渲染库 - form、table、search-form
188 lines (183 loc) • 7.98 kB
text/typescript
import { computed, ComputedRef, ShallowRef, shallowRef } from 'vue'
// cloneDeep,
import { getFieldValue, IAnyObject, IRenderConfig, TFormConfig } from '@d-render/shared'
import { EffectExecutor, IEffectExecutorConfigs, IExecutor, IEffects } from '../effect-executor'
import { useFieldChange } from './use-field-change'
import { secureNewFn, cloneDeepConfig, IKey, IObjectKey, getValuesByKeys } from '../util'
import type { FormItemProps } from '../index'
export const useWatchFieldDepend = (
props: FormItemProps,
{ updateModelValue, updateOtherValue, clearValues }: {
updateModelValue: (val:unknown) => Promise<void>
updateOtherValue: (val:unknown) => Promise<void>
clearValues: () => void
}
) => {
const securityConfig: ComputedRef<TFormConfig> = computed(() => props.config ?? {})
// 运行中的config
// [PERF]: 6.1.x 使用shallowRef优化对config的相应
const runningConfig: ShallowRef<TFormConfig|undefined> = shallowRef() // 运行时的config
// 各类型自己的执行器
// [PERF]: 6.1.x初步优化cloneDeep的性能不在对一些特殊的属性进行深度复制
// [FEAT]: 6.0.x changeConfig、changeValue支持流式处理
// [REFACTOR]: 执行条件提升到执行器配置中
const changeConfigExecutor = async (
values: Parameters<IExecutor>['0'],
outValues: Parameters<IExecutor>['1'],
effects: Parameters<IExecutor>['2']
) => {
// [PERF]: cloneDeep较为消耗性能如何优化
let config = cloneDeepConfig(securityConfig.value)
for (let [changeConfigCb] of effects) {
if (typeof changeConfigCb === 'string') {
changeConfigCb = secureNewFn('config', 'values', 'outValues', changeConfigCb) as ()=> Promise<TFormConfig>
}
if (typeof changeConfigCb === 'boolean') {
continue
}
config = await changeConfigCb(config, values, outValues) as TFormConfig
}
runningConfig.value = config
}
const changeValueExecutor = async (
values: Parameters<IExecutor>['0'],
outValues: Parameters<IExecutor>['1'],
effects: Parameters<IExecutor>['2']
) => {
const otherKey = ([] as string[]).concat(props.config?.otherKey as string)
let data = { value: getFieldValue(props.model, props.fieldKey), otherValue: getValuesByKeys(props.model, otherKey) }
for (let [changeValueCb] of effects) {
if (typeof changeValueCb === 'string') {
changeValueCb = secureNewFn('values', 'outValues', changeValueCb) as ()=> Promise<{ value: unknown, otherValue?: unknown } | undefined>
}
if (typeof changeValueCb === 'boolean') {
continue
}
data = await changeValueCb(values, outValues, data) as { value: unknown, otherValue: {} }
}
if (typeof data === 'object') {
const { value, otherValue } = data
updateModelValue(value)
updateOtherValue(otherValue)
}
}
const resetValueExecutor = (
values: Parameters<IExecutor>['0'],
outValues: Parameters<IExecutor>['1'],
effects: Parameters<IExecutor>['2']
) => {
// 获取判断结果
for (let [resetValueCb, { oldValues, keys }] of effects) {
keys = ([] as Array<string>).concat(keys)
oldValues = ([]as Array<unknown>).concat(oldValues)
if (
!(keys.length === 1 && keys[0] === props.fieldKey) && // 不能只有自己变了变了
oldValues.some(val => val !== undefined) // 存在变更的依赖原始值不为undefined [1,undefined] true [undefined,undefined] false
// executeChangeValueEffect // 此条件提升至执行器配置中
) {
if ((resetValueCb === true) ||
(typeof resetValueCb === 'function' &&
resetValueCb(getFieldValue(props.model, props.fieldKey!), values, outValues)
)
) {
// 清空值只需要执行一次, 由于异步执行的关系,其执行顺序有可能在changeValue后导致他们同时运行时可能出现不一致的问题
clearValues()
return
}
}
}
}
const changeValueByOldExecutor = async (
values: Parameters<IExecutor>['0'],
outValues: Parameters<IExecutor>['1'],
effects: Parameters<IExecutor>['2']
) => {
// 与changeValue一样,首次不触发
let data
for (let [changeValueByOldCb, { keys, oldValues }] of effects) {
keys = ([] as Array<string>).concat(keys)
oldValues = ([]as Array<unknown>).concat(oldValues)
if (typeof changeValueByOldCb === 'boolean') {
continue
}
for (const [idx, key] of keys.entries()) {
// 全局多源监听时会执行多次,不建议设置为全局副作用 [兼容老代码]
data = await changeValueByOldCb({ key, oldValue: oldValues[idx] }, values, outValues, data) as {value: unknown, otherValue: unknown} | undefined
}
}
if (data !== undefined) {
const { value, otherValue } = data
updateModelValue(value)
updateOtherValue(otherValue)
}
}
const effectExecutorConfig: IEffectExecutorConfigs = {
// 允许effect为字符串,配置转换函数
changeConfig: {
executor: changeConfigExecutor,
transformStrEffect: (effectStr: string) =>
secureNewFn('config', 'dependOnValues', 'outDependOnValues', effectStr)
},
changeValue: {
condition: (effects: IEffects, executeChangeValueEffect: boolean) => !props.readonly && executeChangeValueEffect,
executor: changeValueExecutor,
transformStrEffect: (effectStr: string) =>
secureNewFn('dependOnValues', 'outDependOnValues', 'data', effectStr)
},
resetValue: {
condition: (effects: IEffects, executeChangeValueEffect: boolean) => !props.readonly && executeChangeValueEffect,
executor: resetValueExecutor
}, // 此方法全局和局部只要执行一个就好了, 存在effect.resetValue不需要执行global.resetValue
changeValueByOld: { // 此方法不存在global
condition: (effects: IEffects, executeChangeValueEffect: boolean) => !props.readonly && executeChangeValueEffect,
executor: changeValueByOldExecutor
}
}
const effectExecutor = new EffectExecutor(effectExecutorConfig)
const dependOnWatchCb = (
{ changeKeys, changeOldValues }: {changeKeys: Array<IKey>, changeOldValues: Array<unknown> },
{ values, outValues, executeChangeValueEffect }: {values: IAnyObject, outValues: IAnyObject, executeChangeValueEffect: boolean}
) => {
// 获取局部effect的key
const privateEffectKeys = changeKeys
.map((key, index) => {
if (typeof key === 'object') return { ...key, _index: index }
return key
}) // 塞入初始的index
.filter(key => typeof key === 'object') as Array<IObjectKey & {_index: number }>
// 获取全局effect的key
const hasGlobalEffectKey = changeKeys.find(key => typeof key === 'string')
const cbParams = []
// 执行全局effect的回调
if (hasGlobalEffectKey) {
cbParams.push({}) // 只要有个无effect的标记即可,analysisEffects会使用globalParam中的配置
}
// 执行局部effect的回调
privateEffectKeys.forEach(object => {
const privateEffect = object.effect || {}
// @ts-ignore
cbParams.push({
keys: object.key,
oldValues: changeOldValues[object._index],
effect: privateEffect
})
})
// [BROKEN]: 6.0.x 除resetValue外,其他函数在执行私有的副作用时会优先执行全局的副作用
effectExecutor.analysisEffects(cbParams, securityConfig.value, {
values,
outValues,
keys: changeKeys,
oldValues: changeOldValues,
executeChangeValueEffect
})
effectExecutor.executeAll(props.fieldKey as string)
}
// 收集变化的依赖
const { changeCount, dependOnValues, outDependOnValues } = useFieldChange(props, securityConfig, dependOnWatchCb)
return {
changeCount, // model变化总计
dependOnValues,
outDependOnValues,
runningConfig
}
}