如何实现一个简单版的react-redux
在现代的Web开发中,构建复杂的用户界面变得越来越普遍,而React已经成为了许多开发人员的首选框架之一。但随着应用程序的规模和复杂性的增加, 有效地管理应用程序的状态变得至关重要。这就是Redux和React Redux的威力所在。React Redux是一个为React应用程序提供状态管理的库,它建立在Redux之上, 为React组件提供了一种优雅的方式来连接到Redux存储,并在组件之间传递数据。本文将介绍如何实现一个简单版的react-redux。
react-redux的核心方法是Provider
与connect
方法
我们先看一下Provider组件,
Provider
Provider组件的作用是把store对象通过react context传到下级组件, 先定义一个react context
import React from 'react';
const ReactReduxContext = React.createContext();
export default ReactReduxContext;
再实现Provider组件
import ReactReduxContext from './Context';
function Provider(props) {
const {children, store} = props;
const contextValue = { store };
return (
<ReactReduxContext.Provider value={contextValue}>
{children}
</ReactReduxContext.Provider>
)
}
逻辑很简单,就是通过Context把redux的store对象传到下级组件
connect
conncet实现的功能主要有2点 1.react store的state和dispatch方法通过props传给包裹的下层组件 2.监听store的改变,比较包裹组件的props是否改变,如果改变则触发一次渲染
connect的用法如下
const mapStateToProps = (state) => {
const counter = state?.counter;
return {
count: counter && counter.count? counter.count: 0
}
}
const mapDispatchToProps = (dispatch) => {
return {
incrementHandler: () => dispatch("INCREMENT"),
decrementHandler: () => dispatch("DECREMENT"),
resetHandler: () => dispatch("RESET"),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
先来实现一个高阶组件,接收mapStateToProps和mapDispatchToProps并传到下层组件
function connect(mapStateToProps, mapDispatchToProps) {
return function HocConnect(WrapperComponent) {
return function WrappedComponent(props) {
//通过useContext拿到ReactReduxContext上存的store对象
const { store } = useContext(ReactReduxContext);
const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
const mergedProps = { ...stateProps, ...dispatchProps, ...props };
const { store } = useContext(ReactReduxContext);
return <WrapperComponent {...mergedProps}></WrapperComponent>;
};
};
}
监听store的改变,比较props是否改变,这里要比较props是否改变,就是记录上一次渲染的props, 我们可以把上一次渲染的props存在ref里面,然后通过 shallowEqual比较props是否改变,shallowEqual可以找一个现成的实现
function connect(mapStateToProps, mapDispatchToProps) {
return function HocConnect(WrapperComponent) {
return function WrappedComponent(props) {
//通过useContext拿到ReactReduxContext上存的store对象
const { store } = useContext(ReactReduxContext);
const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
const mergedProps = { ...stateProps, ...dispatchProps, ...props };
const lastChildProps = useRef();
useEffect(() => {
store.subscribe(() => {
const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
const newProps = { ...stateProps, ...dispatchProps, ...props };
if(shallowEqual(newProps, lastChildProps.current)) {
}
})
}, []);
useEffect(() => {
lastChildProps.current = mergedProps;
})
return <WrapperComponent {...mergedProps}></WrapperComponent>;
};
};
}
// shallowEqual.js
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
if (is(objA, objB)) return true
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false
}
}
return true
}
这里我们把代码优化一下,把获取props的逻辑抽一个方法出来
function connect(mapStateToProps, mapDispatchToProps) {
return function HocConnect(WrapperComponent) {
const mergeChildProps = (store, props) => {
const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
const mergedProps = { ...stateProps, ...dispatchProps, ...props };
return mergedProps;
}
return function WrappedComponent(props) {
//通过useContext拿到ReactReduxContext上存的store对象
const { store } = useContext(ReactReduxContext);
const mergedProps = mergeChildProps(store, props);
const lastChildProps = useRef();
useEffect(() => {
store.subscribe(() => {
const newProps = mergeChildProps(store, props);
if(shallowEqual(newProps, lastChildProps.current)) {
//执行强制更新
}
})
}, []);
useEffect(() => {
lastChildProps.current = mergedProps;
})
return <WrapperComponent {...mergedProps}></WrapperComponent>;
};
};
}
接下来实现强制更新的逻辑,通过下面的方法,我们可以创建一个forceUpdate方法,逻辑很简单,通过每次给useState返回的updateState方法设置一个空对象,来触发更新
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
这我们就已经实现了react-redux的基本功能,完整代码如下
function connect(mapStateToProps, mapDispatchToProps) {
return function HocConnect(WrapperComponent) {
const mergeChildProps = (store, props) => {
const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
const mergedProps = { ...stateProps, ...dispatchProps, ...props };
return mergedProps;
}
return function WrappedComponent(props) {
//通过useContext拿到ReactReduxContext上存的store对象
const { store } = useContext(ReactReduxContext);
const mergedProps = mergeChildProps(store, props);
const lastChildProps = useRef();
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
useEffect(() => {
store.subscribe(() => {
const newProps = mergeChildProps(store, props);
if(shallowEqual(newProps, lastChildProps.current)) {
//执行强制更新
forceUpdate();
}
})
}, []);
useEffect(() => {
lastChildProps.current = mergedProps;
})
return <WrapperComponent {...mergedProps}></WrapperComponent>;
};
};
}
当前的实现方式存在一个问题,思考下面的场景,
我们有一个TaskList
组件,TaskList
的子组件是TaskItem
,
TaskList
和TaskItem
都connect了redux store,TaskList
根据store里面的存的task list循环渲染TaskItem
TaskItem里面会通过store拿task list里的item的值。
这时候如果我们删除了一个item,因为TaskList
和TaskItem
都监听了store改变,所以TaskList
和被删除TaskItem
会同时触发渲染,TaskItem
会从store里面里面拿一个已经删除的值,这时候就会报错。
其实按照正常的渲染逻辑,删除了一个item只要TaskList
触发渲染,然后等TaskList
触发TaskItem
的渲染,这样就不会有报错的情况,因为被删除的TaskItem
在TaskLIst
循环渲染的时候直接被销毁了。
所以react-redux官方在实现的时候是只在Provider监听store的改变,其他子组件通过自己实现一套Subscription
来通知更新
但是Subscription的实现依赖于connect,如果是通过hook方式来使用react-redux,这个方案也是行不通的。
对于hook的方式,官方的建议是从写法上来约束,避免报错,感兴趣的可以看看下面的文章
react-redux.js.org/api/hooks#s…
转载自:https://juejin.cn/post/7350874162011979776