Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug Report] reactiveField toJS函数会deep clone对象,导致组件watch effect重复执行 #4097

Open
1 task done
workkk98 opened this issue Feb 27, 2024 · 6 comments

Comments

@workkk98
Copy link

workkk98 commented Feb 27, 2024

  • I have searched the issues of this repository and believe that this is not a duplicate.

Reproduction link

!sandbox

Steps to reproduce

如link中的源码。
组件在watch中触发emit,导致parent渲染的reactiveField重新渲染,并构造了一个新的props。
再次渲染子组件的过程中,导致了watch effect函数重复执行。

image
image
image

What is expected?

watch函数只触发一次。

What is actually happening?

watch函数触发了n次

Package

@formily/vue@2.1.11


@lumdzeehol
Copy link
Contributor

Reproduction link 显示 devBox not found, 看看链接是不是有问题?

@lumdzeehol
Copy link
Contributor

看标题描述应该与 #2794 这个问题相关。 可以通过 useField 获取 field 中获取到组件 props, 这个引用不会变

@workkk98
Copy link
Author

Reproduction link 显示 devBox not found, 看看链接是不是有问题?

原链接是私有的,现在开放了

@workkk98
Copy link
Author

workkk98 commented Feb 27, 2024

看标题描述应该与 #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)
}

@lumdzeehol
Copy link
Contributor

看标题描述应该与 #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 可以规避这个问题。

@workkk98
Copy link
Author

看标题描述应该与 #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 可以规避这个问题。

后续这个问题会修复吗?我这边可以先规避下

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants