在上一篇文章中我们手写了一个极简的redux和中间件,并且在react项目中演示实际使用效果。其实redux是一个js库,为了在react上方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux。
今天我们仿照React-Redux做一个简版的实现。
React-Redux
使用React-Redux
首先我们创建一个react项目,然后加入Redux和React-Redux
create-react-app lreact-redux
yarn add redux react-redux
创建一个常规的redux页面,
src
| store
| index.js
| pages
| reduxPage.jsx
创建一个store,store/index.js:
import { combineReducers, createStore } from "redux";
function countReducer(state = 0, action) {
switch (action.type) {
case 'ADD':
return state + action.payload;
case 'MINUS':
return state - action.payload;
default:
return state;
}
}
const store = createStore(combineReducers({ count: countReducer }));
export default store;
交互页面,pages/reduxPage.jsx:
import React from "react";
import { connect } from "react-redux";
function ReduxPage(props) {
const { count, add } = props;
return (
<div>
<div>{count}</div>
<button onClick={() => add(2)}>add</button>
</div>
);
}
export default connect(({ count }) => ({ count }), {
add: (payload) => ({ type: "ADD", payload }),
})(ReduxPage);
修改src/App.js,提供Provider
import { Provider } from 'react-redux';
import './App.css';
import ReduxPage from './pages/reduxPage';
import store from "./store";
function App() {
return (
<div className="App">
<Provider store={store}>
<ReduxPage />
</Provider>
</div>
);
}
export default App;
运行项目,可以看到如下画面:
每点击一次ADD可以看到数字增加2。这是基本的React-Redux用法。
可以看到,React-Redux首先是通过Provider将store传入子组件,而组件中再通过connect这个高阶组件获取state和dispatch,并且在store变更的时候自动重新渲染视图。这便是我们的实现思路。
实现React-Redux
在src下创建lReactRedux.js,在这里实现我们的redux:
export const Provider = (params) => {
}
export const connect = (params) => {
}
Provider的原理是Context,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
import React from 'react';
const Context = React.createContext();
export const Provider = ({ children, store }) => {
return <Context.Provider value={store}> {children} </Context.Provider>
}
再来看这个connect,这是个柯里化函数,先后接受了3次参数,第1次是mapStateToProps, mapDispatchToProps,第二次是需要被包含的组件WrappedComponent,再之后是这个WrappedComponent组件本身需要的参数。
我们先实现mapStateToProps处理state:
export const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => props => {
// 首先获取store 和 state
const store = useContext(Context);
const state = store.getState();
// 执行mapStateToProps
let stateProps = mapStateToProps(state);
return <WrappedComponent {...stateProps} {...props} />
}
更改项目中Provider和connect的引用,再次运行项目,可以看到state正常显示,说明我们的实现起作用了,但是此时add还没有效果,还会报错,是因为我们还没有实现mapDispatchToProps的处理。
mapDispatchToProps如果没有传递的话,会默认给一个dispatch方法;如果传递的是一个函数,则会传入dispatch执行;如果是一个对象,那么会遍历对象,每个value都返回一个action,我们需要给他当作dispatch的参数执行,并返回一个新的函数:
export const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => props => {
// 首先获取store 和 state
const store = useContext(Context);
const state = store.getState();
// 执行mapStateToProps
let stateProps = mapStateToProps(state);
// 默认传递dispatch
let dispatchProps = store.dispatch;
// 如果是函数,则会传入dispatch执行
// 如果是对象,则遍历,给action包一层dispatch
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(store.dispatch);
} else if (typeof mapDispatchToProps === 'object' && mapDispatchToProps !== null) {
dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch);
}
return <WrappedComponent {...stateProps} {...dispatchProps} {...props} />
}
function bindActionCreators(creators, dispatch) {
let obj = {};
for (const key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch);
}
return obj;
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args));
}
这个时候再运行项目,点击add按钮发现虽然不再报错,但是毫无反应,这是因为我们没有监听state的改变去同步视图重新渲染。只需在connect里将强制更新视图的函数加入redux的subscribe列表中即可。
强制更新视图在类组件我们可以使用forceUpdate,但是在函数组件我们只能用useState或者useReducer做一个计数器来更新视图,因为如果前后两次的值相同,useState和useReducer Hook 都会放弃更新。
export const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => props => {
// 首先获取store 和 state
const store = useContext(Context);
const state = store.getState();
// 执行mapStateToProps
let stateProps = mapStateToProps(state);
// 默认传递dispatch
let dispatchProps = store.dispatch;
// 如果是函数,则会传入dispatch执行
// 如果是对象,则遍历,给action包一层dispatch
if (typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(store.dispatch);
} else if (typeof mapDispatchToProps === 'object' && mapDispatchToProps !== null) {
dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch);
}
const forceUpdate = useForceUpdate();
useLayoutEffect(() => {
const unSubscribe = store.subscribe(forceUpdate);
return () => {
unSubscribe();
};
}, [store, forceUpdate]);
return <WrappedComponent {...stateProps} {...dispatchProps} {...props} />
}
// 用useState或者useReducer做一个计数器来更新视图
function useForceUpdate() {
const [, update] = useReducer((x) => x + 1, 0);
const forceUpdate = useCallback(
() => {
update();
},
[],
);
return forceUpdate;
}
再次运行项目,功能一切正常。
到这里我们就实现了一个建议版本的React-Redux了,接下来我们可以再实现React-Redux的一些Hooks。
实现React-Redux的Hooks
useDispatch
用于获取dispatch的hook。
export function useDispatch() {
const store = useContext(Context);
return store.dispatch;
}
useSelector
useSelector和connect中的mapStateToProps的概念差不多,它能让我们获取store中的state,并自动订阅state的更新。
export function useSelector(Selector) {
const store = useContext(Context);
const selectedState = Selector(store.state);
const forceUpdate = useForceUpdate();
useLayoutEffect(() => {
const unSubscribe = store.subscribe(forceUpdate);
return () => {
unSubscribe();
};
}, [store, forceUpdate]);
return selectedState;
}
最后
以上就是全部内容了,手写了一个简易的React-Redux 以及 2个Hooks。
有任何问题都欢迎交流~
参考: