formily
formily copied to clipboard
[Bug Report] reactiveField toJS函数会deep clone对象,导致组件watch effect重复执行
- [x] I have searched the issues of this repository and believe that this is not a duplicate.
Reproduction link
Steps to reproduce
如link中的源码。 组件在watch中触发emit,导致parent渲染的reactiveField重新渲染,并构造了一个新的props。 再次渲染子组件的过程中,导致了watch effect函数重复执行。
What is expected?
watch函数只触发一次。
What is actually happening?
watch函数触发了n次
Package
@formily/[email protected]
Reproduction link 显示 devBox not found, 看看链接是不是有问题?
看标题描述应该与 #2794 这个问题相关。 可以通过 useField
获取 field 中获取到组件 props, 这个引用不会变
Reproduction link 显示 devBox not found, 看看链接是不是有问题?
原链接是私有的,现在开放了
看标题描述应该与 #2794 这个问题相关。 可以通过
useField
获取 field 中获取到组件 props, 这个引用不会变
对但vue的响应式系统下,props的引用地址变更也会触发watch effect。react我理解useEffect的dep也是简单的shallow diff。我觉得props地址是不能变更的,虽然两个对象打印后是一样。
源码是在packages/vue/src/components/ReactiveField.ts originData对象里的value都是deep clone的,经过这个toJs这个函数处理
export const toJS = <T>(values: T): T => {
const visited = new WeakSet<any>()
const _toJS: typeof toJS = (values: any) => {
if (visited.has(values)) {
return values
}
if (values && values[RAW_TYPE]) return values
if (isArr(values)) {
if (isObservable(values)) {
visited.add(values)
const res: any = []
values.forEach((item: any) => {
res.push(_toJS(item))
})
visited.delete(values)
return res
}
} else if (isPlainObj(values)) {
if (isObservable(values)) {
visited.add(values)
const res: any = {}
for (const key in values) {
if (hasOwnProperty.call(values, key)) {
res[key] = _toJS(values[key])
}
}
visited.delete(values)
return res
}
}
return values
}
return _toJS(values)
}
看标题描述应该与 #2794 这个问题相关。 可以通过
useField
获取 field 中获取到组件 props, 这个引用不会变对但vue的响应式系统下,props的引用地址变更也会触发watch effect。react我理解useEffect的dep也是简单的shallow diff。我觉得props地址是不能变更的,虽然值一样。
源码是在packages/vue/src/components/ReactiveField.ts originData对象里的value都是deep clone的,经过这个toJs这个函数处理
export const toJS = <T>(values: T): T => { const visited = new WeakSet<any>() const _toJS: typeof toJS = (values: any) => { if (visited.has(values)) { return values } if (values && values[RAW_TYPE]) return values if (isArr(values)) { if (isObservable(values)) { visited.add(values) const res: any = [] values.forEach((item: any) => { res.push(_toJS(item)) }) visited.delete(values) return res } } else if (isPlainObj(values)) { if (isObservable(values)) { visited.add(values) const res: any = {} for (const key in values) { if (hasOwnProperty.call(values, key)) { res[key] = _toJS(values[key]) } } visited.delete(values) return res } } return values } return _toJS(values) }
是的, vue是自带响应式的。 出现这个问题的原因是当前版本的 formily 在设计的时候,将响应式放在@formily/reactive 这一层了,没有利用 vue自身的能力。 目前 @formily/vue 中的 ReactiveField
实际上还是参考了 @formily/react 中的实现,vue 组件更像一个 functional Component, 是否重新渲染是由 reactive 层去控制的。 在你不脱离@formily/vue
去开发情况下,通过 provide/inject 可以规避这个问题。
看标题描述应该与 #2794 这个问题相关。 可以通过
useField
获取 field 中获取到组件 props, 这个引用不会变对但vue的响应式系统下,props的引用地址变更也会触发watch effect。react我理解useEffect的dep也是简单的shallow diff。我觉得props地址是不能变更的,虽然值一样。 源码是在packages/vue/src/components/ReactiveField.ts originData对象里的value都是deep clone的,经过这个toJs这个函数处理
export const toJS = <T>(values: T): T => { const visited = new WeakSet<any>() const _toJS: typeof toJS = (values: any) => { if (visited.has(values)) { return values } if (values && values[RAW_TYPE]) return values if (isArr(values)) { if (isObservable(values)) { visited.add(values) const res: any = [] values.forEach((item: any) => { res.push(_toJS(item)) }) visited.delete(values) return res } } else if (isPlainObj(values)) { if (isObservable(values)) { visited.add(values) const res: any = {} for (const key in values) { if (hasOwnProperty.call(values, key)) { res[key] = _toJS(values[key]) } } visited.delete(values) return res } } return values } return _toJS(values) }
是的, vue是自带响应式的。 出现这个问题的原因是当前版本的 formily 在设计的时候,将响应式放在@formily/reactive 这一层了,没有利用 vue自身的能力。 目前 @formily/vue 中的
ReactiveField
实际上还是参考了 @formily/react 中的实现,vue 组件更像一个 functional Component, 是否重新渲染是由 reactive 层去控制的。 在你不脱离@formily/vue
去开发情况下,通过 provide/inject 可以规避这个问题。
后续这个问题会修复吗?我这边可以先规避下
同问后续这个问题会修复吗? 我这边的场景是表单其它字段的 value 会通过props 传入,一样会导致 useEffect 重复执行