在 Vue 3.0 中使用 Proxy 实现了数据的双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。
在 React 中是单向数据流,数据只能从父组件通过属性的方式传给其子组件,那么能否使用 Proxy 做状态管理呢?答案当然是可以的,本文以计数器组件的开发为例子,使用 Proxy 实现一个简单的 React 全局状态管理。
Proxy 简单介绍
Proxy 是 ES6 中的一个特性,在目标对象之前架设一层 “拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。使用方法如下:
const p = new Proxy(target, handler)
参数解释:
-
target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
-
handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
下面是一个具体示例:
const state = {
count: 0,
};
const store = new Proxy(state, {
set(target, propertyKey, value, receiver) {
Reflect.set(target, propertyKey, value, receiver);
console.log(propertyKey, value)
return true;
},
get(target, propertyKey, receiver) {
return Reflect.get(target, propertyKey, receiver);
},
});
store.count = 1; // count 1
console.log(store.count); // 1
console.log(state.count); // 1
在上面的例子中,store 是对 state 做了拦截,通过 store 可以读写 state 的属性值,store.count = 1;
会触发 console.log(propertyKey, value)
打印 count 1
。在 Proxy 中,使用 handler.get() 捕捉器来拦截读操作,使用 handler.set() 捕捉器来拦截写操作。在捕捉器中我们自定义实现一些额外的功能,当 Proxy 对象发生读写操作时会触发这些操作,再接下来的状态管理中就会使用到这个特性。
简单的状态管理
对上面的代码做一些改造,声明一个自定义 React Hooks:
const useProxyStore = (initialValue) => {
const [state, setState] = useState(initialValue);
const store = new Proxy(state, {
set(target, propertyKey, value, receiver) {
Reflect.set(target, propertyKey, value, receiver);
setState(Object.assign({}, state, { [propertyKey]: value }));
return true;
},
get(target, propertyKey, receiver) {
return Reflect.get(target, propertyKey, receiver);
},
});
return store;
};
与上面的例子一样,也是 store 对 state 做了拦截,当使用 store 修改 state 的值时,调用 setState 函数更新组件的状态:
const Counter = ({ text }) => {
const store = useProxyStore({ count: 0 });
return (
<div
onClick={() => {
store.count += 1;
}}
>
{text}: {store.count}
</div>
);
};
在 Counter 组件的onClick
中,只需要通过 store.count += 1;
就进行状态的更新,不需要再调用 setState,如果感兴趣可以了解一下 use-state-proxy。当然,为了实现全局状态管理,我们需要进一步改造。
全局的状态管理
全局状态管理的思路,无非就是当状态的发生变化时,要将所有使用该状态的组件都重新渲染。首先,要记录哪些组件使用了该全局状态:
const listeners = new Set();
const useStore = () => {
const [, setState] = useState();
useEffect(() => {
const fn = () => setState({});
listeners.add(fn);
return () => listeners.delete(fn);
}, []);
};
useStore 是一个自定义 Hooks,在组件使用了 useStore 之后,将函数 fn 注册到 listeners;不难发现函数 fn 只是重新设置了组件状态,因此只要执行 fn 函数,该组件一定会被重新渲染。接下来,我们将实现全局状态,全局状态发生变化时,重新渲染各个组件:
const store = new Proxy(state, {
set(target, propertyKey, value, receiver) {
Reflect.set(target, propertyKey, value, receiver);
// 状态发生变化时,重新渲染各个组件
for (const listener of listeners) {
listener();
}
return true;
},
get(target, propertyKey, receiver) {
return Reflect.get(target, propertyKey, receiver);
},
});
最后,我们将上面的代码重新整理,实现一个简单的全局状态管理工具:
const createGlobalStore = (initialValue) => {
// 记录哪些组件使用了全局状态
const listeners = new Set();
const store = new Proxy(state, {
set(target, propertyKey, value, receiver) {
Reflect.set(target, propertyKey, value, receiver);
// 状态发生变化时,重新渲染各个组件
for (const listener of listeners) {
listener();
}
return true;
},
get(target, propertyKey, receiver) {
return Reflect.get(target, propertyKey, receiver);
},
});
const useStore = () => {
const [, setState] = useState();
useEffect(() => {
const fn = () => setState({});
listeners.add(fn);
return () => listeners.delete(fn);
}, []);
return store;
};
return [store, useStore];
};
那么我们如何使用它呢?代码如下:
const [store, useStore] = createGlobalStore({ count: 0 });
// 在组件外部更新全局状态
setTimeout(() => {
store.count = 100;
}, 5000)
const Counter = () => {
const store = useStore();
return (
<div
onClick={() => {
store.count += 1;
}}
>
{store.count}
</div>
);
};
const App = () => {
const store = useStore();
return (
<div className="App">
<Counter></Counter>
{store.count}
</div>
);
}
执行效果如下:
至此,已经使用 Proxy 实现 React 全局状态管理,如果您想了解更多的实现细节,可以参照 @slimlib/store 这个库的源码,本文也参考了它的实现思路。
关注我们的公众号,阅读更多前端技术文章
原文始发于微信公众号(KooFE前端团队):使用 Proxy 实现 React 实现全局状态管理
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/54289.html