大家好,我麦当。
用过 ahooks 的小伙伴会发现,里面有个神奇的 hooks 叫做 useReactive,它神奇的地方在于,打破了你对 react 的传统印象,即永远不要直接更改 state。
tsx
import React from 'react';
import { useReactive } from 'ahooks';
export default () => {
const state = useReactive({
count: 0,
inputVal: '',
obj: {
value: '',
},
});
return (
<div>
<p> state.count:{state.count}</p>
<button style={{ marginRight: 8 }} onClick={() => state.count++}>
state.count++
</button>
<button onClick={() => state.count--}>state.count--</button>
<p style={{ marginTop: 20 }}> state.inputVal: {state.inputVal}</p>
<input onChange={(e) => (state.inputVal = e.target.value)} />
<p style={{ marginTop: 20 }}> state.obj.value: {state.obj.value}</p>
<input onChange={(e) => (state.obj.value = e.target.value)} />
</div>
);
};
那么 useReactive 是如何做到的?今天我们就来研究下它的实现原理。
Proxy 的使用
useReactive 的实现依赖于 JavaScript 的 Proxy
对象。Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 在 useReactive 中,我们使用 Proxy 来拦截对象的 get 和 set 操作。当我们尝试获取对象的属性值时,get 操作会被触发。当我们尝试设置对象的属性值时,set 操作会被触发。
tsx
const observer = <T extends Record<string, any>>(
initialVal: T,
cb: () => void
): T => {
return new Proxy<T>(initialVal, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
return typeof res === "object"
? observer(res, cb)
: Reflect.get(target, key);
},
set(target, key, val) {
const ret = Reflect.set(target, key, val);
cb();
return ret;
},
});
};
在 get 操作中,我们首先使用 Reflect.get
获取属性值。如果属性值是一个对象,我们会递归地对这个对象进行代理。这样,我们就可以实现对嵌套对象的响应式操作。
在 set 操作中,我们首先使用 Reflect.set
设置属性值,然后调用回调函数 cb。这个回调函数的作用是通知 React 进行重新渲染。
tsx
const useReactive = <T extends Record<string, any>>(initialState: T): T => {
const ref = useLatest<T>(initialState);
const update = useUpdate();
return useCreation(() => {
// useCreation 不了解的话,先不用关注,把它当作 useMemo 即可
return observer(ref.current, () => {
update();
});
}, []);
};
当我们修改响应式对象的属性值时,Proxy 的 set 操作会被触发,然后调用 update 函数。update 函数会强制组件重新渲染,从而实现 state 的更新。
例子
它能解决 hooks 的闭包问题
tsx
const App = () => {
const state = useReactive({count: 0})
useEffect(() => {
console.log('eff')
setInterval(() => {
state.count++;
console.log("=>(index.tsx:24) state.count", state.count);
}, 1000)
}, []
return (
<div>{state.count}</div>
)
}
总结
useReactive Hook 通过使用 Proxy 对象和直接修改 state,实现了对对象的响应式操作。