背景
重构代码时看到老代码出现了一些疑惑点。
脱敏图片:useEffect,监听A,setB(A.name)
如上图的代码,将一个 State Hook 对象(代号 A)中的一个值单独定义为了一个新的 Hook(代号 B)。
脱敏图片:useEffect,监听B, 发请求。
这个 B 被 Effect Hook 监听,在变化时更新网络内容。
- 关于猜测为什么这么写
-
- 1.原来取到这个值太长,但这个值很多地方用到,需要定义新的变量来简化代码。
- 2.直接监听 A 来控制何时重新请求资源,当 A 很庞大时会产生资源消耗。
问题
\
关于简化代码
没问题,一定是要取的。
关于减少直接监听庞大的 A 的产生的资源消耗。
- 首先可以排除采用上述写法来避免资源消耗,因为虽然是通过判断 B 来控制重新请求,但是 B 的改变又监听了 A。
\
- 其次可以考虑如何实现
\
上述代码采用 State Hook 来保存 B 的状态,并通过 Effect Hook 来监听 A 的变化更新 B。
现在改为采用直接定义变量的方式定义 B,也就是 const B = A.?.?.?.?。
- 疑问一:重新寻址
\
没有吧 B 的状态保存,导致即使 A 没有改变的情况下每次重新渲染此组件也都需要对 B 进行一次寻址。但是要通过 State Hook 保存状态重新渲染需要把状态值从 current 树上取下来并做拷贝,也需要寻址一次并且消耗额外的内存空间。
- 疑问二:监听普通变量
\
被提出了新的问题,State Hook 发生 Set 事件可以让组件重新渲染,且 Effct 可以监听到更新,但是普通变量是否也能被监听到更新?
首先可以明确的是,这个变量取值于 A,而 A 是 State Hook,A 被 Set 更新时引发函数组件重新执行,函数内作用于被刷新,变量被重新定义和赋值,B 的值是新的。因此,无论 Effect 是采用浅拷贝还是深拷贝来储存上一次的依赖值,一定可以确定当 B 的值更新后可以触发对依赖值变化的更新。
新的问题
\
其实思考后 Effect 本身就可以拿任何值来作为依赖值,重点在于 Effect 对于新旧依赖值的对比是如何实现的。
首先明确:检查源码后,Effect 对于第二个值的对比是通过遍历数组的每一项使用 Object.is() 对比。
- 当值为简单类型值
\
因为简单类型是值传递,值变化一定触发对比失败。
- 当值为引用类型值 —— 且引用值是在函数组件运行过程中生成的新对象
如果值变化,不管是指针地址还是值都不相同,所以一定会触发对比失败。
- 当值为引用类型值 —— 且引用值来自与上级组件的状态或父级作用域
\
当这类引用值改变,对象的地址指针不会变仅仅是值变,如果 Effect 是把依赖值浅拷贝到 current 树上储存旧依赖值,那么会引起依赖值改变但对比成功。
以下代码中如果窗口大小改变并不会触发 Effect。
- 当值为引用类型值 —— 且值来自于 State
整个Hook分支树都是替换实现的。所以无论set是新对象替换还是改变原来对象,旧Hook树都会被替换,其实对这个问题就没有影响了
Vue 的计算属性呢
watcher 函数只捕获 data 中的对象。此贴结。