Redux 是我们经常接触的一个状态管理工具,将整个应用状态存储到到一个地方进行集中的管理,而本文实现一个简单的 Redux。
首先简单介绍一下 Redux 的三个核心概念:
整个应用的 state 被储存于唯一一个 store 中
唯一改变 state 的方法就是触发 action
需要用 reducers 来描述 action 如何改变 state
然后,再介绍一下 Redux 的 Store 提供的方法:
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器。
因此,我们可以构造一下 createStore 函数:
const createStore = (reducer, preloadedState) => { let state = preloadedState; const listeners = new Set(); // 提供 dispatch(action) 方法更新 state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
}; // 提供 getState() 方法获取 state
const getState = () => { return state;
}; // 通过 subscribe(listener) 注册监听器
const subscribe = (listener) => {
listeners.add(listener); return unsubscribe(listener);
}; // 过 subscribe(listener) 返回的函数注销监听器
const unsubscribe = (listener) => { return () => {
listeners.delete(listener);
};
}; return {
dispatch,
subscribe,
getState,
};
};
使用方法如下:
// 需要用 reducers 来描述 action 如何改变 stateconst reducer = (state = { count: 0 }, action) => { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; default: throw new Error();
}
};const store = createStore(reducer);const unsubscribe = store.subscribe(() => { // 回调中用 getState 获取最新 state
const state = store.getState(); console.log(state.count);
});// 唯一改变 state 的方法就是触发 actionstore.dispatch({ type: "increment" })
unsubscribe();
上面代码定义的 store 可在 React 组件中使用,当 store 发生变化,强制组件重新渲染:
const Counter = () => { const [, forceUpdate] = React.useState();
useEffect(() => { const unsubscribe = store.subscribe(() => { // store 发生变化,组件强制重新渲染
forceUpdate({});
}); return () => {
unsubscribe();
};
}, []); return ( <div onClick={() => store.dispatch({ type: "increment" })}>
{store.getState()?.count || 0} </div>
);
};
上面已经实现了一个简单的 Redux,为了使用起来更方便,进一步实现一个简单的 React-Redux。首先定义 Provider 组件:
const ReduxContext = React.createContext();const Provider = ({ store, children }) => { const contextValue = useMemo(() => ({ store }), [store]);
useEffect(() => {
}) return ( <ReduxContext.Provider value={contextValue}>
{children} </ReduxContext.Provider>
);
};
在 Provider 中使用 Context 实现各个子组件之间共享 store 数据;connect 消费 Context 并且在 store 的值变化时,强制重新渲染组件:
const connect = (mapStateToProps, mapDispatchToProps) => { return (Component) => { return (props) => { const [, forceUpdate] = useState({}); const { store } = useContext(ReduxContext); const stateToProps = mapStateToProps(store.getState()); const dispachToProps = mapDispatchToProps(store.dispatch, props);
useEffect(() => {
store.subscribe(() => { // store 的值变化时,强制重新渲染组件
forceUpdate({});
});
}, [store]); return <Component {...props} {...stateToProps} {...dispachToProps} />;
};
};
};
connect 作为一个高阶组件,将 store 中的数据和方法映射到组件的 props 上,而且当 store 变化时更新组件。
使用方法如下:
const Counter = (props) => { return <div onClick={props.onClick}>{props.count}</div>;
};const CounterWrap = connect( (state) => ({ count: state.count }), (dispatch) => ({ onClick: () => {
dispatch({ type: "increment" });
},
})
)(Counter);const App = () => { return ( <Provider store={store}>
<CounterWrap />
</Provider>
);
};
之前 3 篇关于状态管理的文章:
使用 Context + useReducer 实现 React 全局状态管理
关注我们的公众号,阅读更多前端技术文章