前言
前几天打算使用React开发一个博客系统,由于有许多数据都是要共用的,比如Tags,Categories等,而React相比Vue会组件嵌套的情况会更严重,如果将数据一个个转发势必会造成代码逻辑过于复杂,耦合度过高,所以,我们需要全局状态管理。React有一个好伙伴Redux,Redux是一个用于应用程序状态管理的开源JavaScript库。但是Redux相对复杂,需要编写太多模板代码,而博客系统这种轻应用并没必要使用Redux。这时就需要自制一个简单的Store来管理全局状态。
分析
在正式编写代码之前我们需要先分析一下我们的Store需要什么功能,以及结构。
首先Store需要有State来存储数据,Action来触发全局的事件,以及对State的更改,我们并不需要Reduce,有Reduce可以很好的分辨State的变动,但是我们的应用并没有这么复杂,所以并不需要多一步Reduce来改变State。
由于我使用过Redux和Vuex,通过对比可以发现Vuex的Action调用相对简单灵活,但是并不能很好的应对同步的场景,所以为了后期的开心编码,我们需要结合二者的一些优点。
React在16.x添加了Context,使得我们在组件树中传递数据能够简单的实现,我们只需要将root组件的状态作为全局状态通过Context传递下去即可。
实现
首先,我们需要初始化State和Action,或许你会对下方的this感到疑惑,耐心看完下面的代码就能理解了。
// initialStore.js
export const initialState = {
count: 1
}
export const initialActions = {
addCount() {
this.setState({ ...this.state, count: this.state.count + 1 });
}
}
然后我们需要导入initialStore并创建Context,同时导出Context,用于子组件
import React from 'react';
import { initialActions, initialState } from './initialStore';
export const StoreContext = React.createContext({
state: initialState,
actions: initialActions
});
这时我们就能创建一个Store组件来存储和转发Context
export default class StoreProvider extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
this.actions = initialActions;
for (const key in this.actions) {
this.actions[key] = this.actions[key].bind(this);
}
}
render() {
return (
<StoreContext.Provider
value={{
state: this.state,
actions: this.actions
}}
>
{this.props.children}
</StoreContext.Provider>
);
}
}
使用Class组件的原因是为了将该组件的this绑定到Action上,这样Action就能通过this.state和this.setState操作读取State了,你也可以使用Hook来达到相同的目的,只是需要利用useRef来防止函数读取到旧的状态,使用起来比Class复杂,所以这里直接使用Class了。
改进
使用全局状态管理在调试的时候不太容易跟踪状态的变化,所以我们需要在状态变化的时候打印变动信息,让调试更方便。
export default class StoreProvider extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
if (process.env.NODE_ENV === 'development') {
this.defSetState = this.setState;
this.setState = this.debugSetState;
}
this.actions = initialActions;
for (const key in this.actions) {
this.actions[key] = this.actions[key].bind(this);
}
}
debugSetState(newState) {
try {
throw new Error('[!] - 检查到未被移除的Log调用:');
} catch (e) {
console.log({
oldState: this.state,
newState: newState,
dispatchAction: e.stack
});
}
if (window.__REACT_DISPATCH_SHOW_TRACE__) {
console.trace();
}
this.defSetState(newState);
}
render() {
return (
<StoreContext.Provider
value={{
state: this.state,
actions: this.actions
}}
>
{this.props.children}
</StoreContext.Provider>
);
}
}
在setState注入输出日志,我们就能在控制台清晰的看到状态的改变,以及执行栈。
那么,如何使用呢?
使用其实就是从Context提取出State和Action,把它们当成变量和函数执行即可
const store = useContext(StoreContext);
const { count } = store.state;
const { addCount } = store.actions;
//...
<button onClick={() => addCount()}>{count}</button>
本文地址: 为React添加简单的Store