const descriptors = (obj) => {
return Object.keys(obj).reduce((handles, key) => {
let value = obj[key];
// 如果 value 是个对象,那就递归其属性进行 `setter/getter`
if (Object.prototype.toString.call(obj) === "[object Object]") {
value = Object.defineProperty({}, descriptors(value));
}
return {
...handles,
[key]: {
get() {
return value
},
set(newVal) {
setState({ ...state, [key]: newVal })
}
}
}
}, {})
}
如果你仔细观察了这段代码,会发现有个非常致命的问题。那就是在做递归的时候,set(newVal) 里面的代码并不对,state 是个深层对象,不能这么简单地对其外层进行赋值。
这意味着,我们需要将访问这个对象深层属性的一整条路径保存下来,以便于 set 到正确的值,可以用一个数组来收集路径上的 key 值。
这里用使用 lodash 的 set 和 get 来做一下演示。
1. 前言
前几天在知乎看到了一个问题,React 的 Hooks 是否可以改为用类似 vue3 composition api 的方式实现?
关于 React Hooks 和 Vue3 Composition API 的热烈讨论一直都存在,虽然两者本质上都是实现状态逻辑复用,但在实现上却代表了两个社区的不同发展方向。
我想说,小孩子才分好坏,成年人表示我全都要。
2. 你不知道的 Object.defineProperty
那今天我们来讨论一下怎么用 React Hooks 来实现 Vue3 Composition 的效果。
先来看一下我们最终要实现的效果。
看到这个 API 的用法你会联想到什么?没错,很明显这里借用了 Proxy 或者
Object.defineProperty
。在《你不知道的 Proxy:ES6 Proxy 能做哪些有意思的事情?》一文中,我们已经对比过两者的用法了。
其实这里还有一个不为人知的区别,那就是可以通过
Object.defineProperty
给对象添加一个新属性。打印出来的效果是这样的:
这就很有意思了,意味着我们可以把某个对象 A 上所有属性都挂载到对象 B 上,这样我们不必对 A 进行任何监听,即不会污染 A。
3. React Hooks + Object.defineProperty = ?
如果将上面的代码结合 React Hooks,那会出现什么效果呢?没错,我们的 Hooks 变得更加
reactive
了。将这段代码进一步封装,可以得到一个 Custom Hook,也就是我们今天要说的 Composition API。
当然,这段代码还存在很多问题,依赖了对象的结构、不支持更深层的
getter/setter
等等,我们接下来就一起来优化一下。4. 实现 Composition
4.1 递归劫持属性
对于有多个属性的对象来说,我们可以遍历,配合
Object.defineProperties
来劫持它的所有属性。而对于更深层的对象来说,不仅要做递归,还要考虑 setState 这里应该根据访问路径来设置。 首先,我们来对深层对象做一次递归。
如果你仔细观察了这段代码,会发现有个非常致命的问题。那就是在做递归的时候,
set(newVal)
里面的代码并不对,state
是个深层对象,不能这么简单地对其外层进行赋值。 这意味着,我们需要将访问这个对象深层属性的一整条路径保存下来,以便于set
到正确的值,可以用一个数组来收集路径上的 key 值。 这里用使用 lodash 的 set 和 get 来做一下演示。但是,如果传入的是个数组,这里就会有问题了。因为我们只是对 Object 进行了拦截,没有对 Array 进行处理。
5. 完整版
这样,我们就实现了一个完整版的
ref
,我将代码和示例都放到了codesandbox
上面:Compostion API❤️ 看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙: