问题描述
在React当中,state 推荐使用原子类型,例如 const [num, setNum] = useState(0),其中的 num 为 一个 Number 类型。
当然,稍微复杂一些的对象,也是可以通过 useState 来实现,例如
const [obj, setObj] = useState(()=>({
a: 1,
b: 2
}))
但是当更新对象中的其中一个元素的时候,就会出现这样的代码
setObj({
...obj,
a:2
})
react 会将 obj 看作一个整体,只有当整体替换的时候,才会触发更新。
而如果obj 变得更加的复杂, 如
const [obj, setObj] = useState(()=>({
a: 10,
setting: {
num: {
value: 10,
},
},
}))
// 当更新 value 值当时候,就会有如下代码
setObj({
...obj,
setting:{
...obj.setting,
num:{
...obj.setting.num,
value: obj.setting.num.value + 1
}
}
})
此时,代码将变得难以阅读和理解,并且很容易出现错误。
简单的一种处理方式是,当 obj 当中的某一项变更的时候,先对 obj 进行深度拷贝,然后进行 对 obj 的拷贝对象进行修改,然后再进行赋值
// 错误写法,对 obj 修改并不会出发 rerender
obj.setting.num.value = obj.setting.num.value + 1;
setObj(obj);
// 正确写法
import * as lodash from 'lodash';
// 对obj进行深度拷贝
const catchObj = lodash.cloneDeep(obj);
catchObj.setting.num.value = catchObj.setting.num.value + 1;
setObj(catchObj);
这里对 obj 进行了深度拷贝,可以看出写法上虽然比 第一种简便了不少,但深拷贝增大了内存开销。
useComplexState
这里借用 vue 的响应式思路,通过 Proxy 实现一个 当 obj 对象的某一个属性发生变化时,即刷新界面的 hook
import type { Dispatch, SetStateAction } from 'react';
import { useRef, useState } from 'react';
export function createProxy(initialState: any, callback: Function) {
// 代理缓存
const proxyCache = new WeakMap();
const createProxy_ = (initialState: any, callback: Function) => {
if (proxyCache.has(initialState)) {
// 如果对象已经被代理过,直接返回缓存的代理对象
return proxyCache.get(initialState);
}
const handle = {
set: function (target, propKey, value, receiver) {
Reflect.set(target, propKey, value, receiver);
callback?.();
return target;
},
get(target, propKey, receiver) {
const value = Reflect.get(target, propKey, receiver);
if (typeof value === 'object' && value !== null) {
return createProxy_(value, callback);
}
return value;
},
};
const p = new Proxy(initialState, handle);
proxyCache.set(initialState, p);
return p;
};
return createProxy_(initialState, callback);
}
/**
* 复杂对象的 state
* @param initData
*/
export default function useComplexState<S>(
initialState: S | (() => S)
): React.MutableRefObject<S> {
const stateProxy = useRef(
createProxy(initialState, () => {
setState(new Date().getTime() + Math.random());
})
);
const [, setState] = useState(() => {
return new Date().getTime() + Math.random();
});
return stateProxy.current;
}
这段代码定义了一个自定义的 React Hook useComplexState,用于管理复杂对象的状态。它利用 JavaScript 的 Proxy 对象来监听状态的变化,并在状态改变时触发重新渲染。以下是对代码的详细解析:
createProxy 函数
createProxy 函数用于创建一个代理对象,该代理对象可以在属性被修改时触发回调函数。
参数
initialState:初始状态,可以是任何类型的对象。callback:当代理对象的属性被修改时调用的回调函数。
内部逻辑
-
代理缓存 (
proxyCache) :使用WeakMap存储已经创建的代理对象,以避免重复创建代理。 -
createProxy_函数:实际创建代理对象的函数。-
如果
initialState已经被代理过,直接返回缓存的代理对象。 -
定义
handle对象,包含set和get两个捕获器:set捕获器:在设置属性时调用Reflect.set来设置属性值,并调用回调函数callback。get捕获器:在获取属性时调用Reflect.get来获取属性值。如果属性值是对象,则递归调用createProxy_来代理该对象。
-
创建代理对象
p并存储在proxyCache中。
-
返回值
- 返回创建的代理对象。
useComplexState Hook
useComplexState 是一个自定义的 React Hook,用于管理复杂对象的状态。
参数
initialState:初始状态,可以是对象或返回对象的函数。
内部逻辑
stateProxy:使用useRef创建一个引用,引用的初始值是通过createProxy创建的代理对象。代理对象的回调函数会触发组件重新渲染。setState:使用useState创建一个状态变量和更新函数。状态的初始值是一个随机数(通过new Date().getTime() + Math.random()生成)。- 返回值:返回
stateProxy.current,即代理后的初始状态。
代码示例
以下是一个使用 useComplexState 的示例:
import React from 'react';
import useComplexState from './useComplexState';
function App() {
const state = useComplexState({ count: 0, nested: { value: 1 } });
const increment = () => {
state.count += 1;
};
const incrementNested = () => {
state.nested.value += 1;
};
return (
<div>
<p>Count: {state.count}</p>
<p>Nested Value: {state.nested.value}</p>
<button onClick={increment}>Increment Count</button>
<button onClick={incrementNested}>Increment Nested Value</button>
</div>
);
}
export default App;
在这个示例中,useComplexState 返回的 state 对象是一个代理对象。当 state.count 或 state.nested.value 被修改时,组件会重新渲染。
总结
createProxy函数创建一个代理对象,用于监听对象属性的修改。useComplexStateHook 使用createProxy生成的代理对象来管理复杂对象的状态,并在状态变化时触发组件重新渲染。- 通过这种方式,可以方便地管理复杂嵌套对象的状态变化,并自动触发 React 组件的重新渲染。