手写一个React-Redux,玩转React的Context API (5)

然后在我们前面自己实现的React-Redux里面,我们的根组件始终是Provider,所以Provider需要实例化一个Subscription并放到context上,而且每次state更新的时候需要手动调用子组件回调,代码改造如下:

import React, { useMemo, useEffect } from 'react'; import ReactReduxContext from './Context'; import Subscription from './Subscription'; function Provider(props) { const {store, children} = props; // 这是要传递的context // 里面放入store和subscription实例 const contextValue = useMemo(() => { const subscription = new Subscription(store) // 注册回调为通知子组件,这样就可以开始层级通知了 subscription.onStateChange = subscription.notifyNestedSubs return { store, subscription } }, [store]) // 拿到之前的state值 const previousState = useMemo(() => store.getState(), [store]) // 每次contextValue或者previousState变化的时候 // 用notifyNestedSubs通知子组件 useEffect(() => { const { subscription } = contextValue; subscription.trySubscribe() if (previousState !== store.getState()) { subscription.notifyNestedSubs() } }, [contextValue, previousState]) // 返回ReactReduxContext包裹的组件,传入contextValue // 里面的内容就直接是children,我们不动他 return ( <ReactReduxContext.Provider value={contextValue}> {children} </ReactReduxContext.Provider> ) } export default Provider; 改造connect

有了Subscription类,connect就不能直接注册到store了,而是应该注册到父级subscription上,更新的时候除了更新自己还要通知子组件更新。在渲染包裹的组件时,也不能直接渲染了,而是应该再次使用Context.Provider包裹下,传入修改过的contextValue,这个contextValue里面的subscription应该替换为自己的。改造后代码如下:

import React, { useContext, useRef, useLayoutEffect, useReducer } from 'react'; import ReactReduxContext from './Context'; import shallowEqual from './shallowEqual'; import Subscription from './Subscription'; function storeStateUpdatesReducer(count) { return count + 1; } function connect( mapStateToProps = () => {}, mapDispatchToProps = () => {} ) { function childPropsSelector(store, wrapperProps) { const state = store.getState(); // 拿到state // 执行mapStateToProps和mapDispatchToProps const stateProps = mapStateToProps(state); const dispatchProps = mapDispatchToProps(store.dispatch); return Object.assign({}, stateProps, dispatchProps, wrapperProps); } return function connectHOC(WrappedComponent) { function ConnectFunction(props) { const { ...wrapperProps } = props; const contextValue = useContext(ReactReduxContext); const { store, subscription: parentSub } = contextValue; // 解构出store和parentSub const actualChildProps = childPropsSelector(store, wrapperProps); const lastChildProps = useRef(); useLayoutEffect(() => { lastChildProps.current = actualChildProps; }, [actualChildProps]); const [ , forceComponentUpdateDispatch ] = useReducer(storeStateUpdatesReducer, 0) // 新建一个subscription实例 const subscription = new Subscription(store, parentSub); // state回调抽出来成为一个方法 const checkForUpdates = () => { const newChildProps = childPropsSelector(store, wrapperProps); // 如果参数变了,记录新的值到lastChildProps上 // 并且强制更新当前组件 if(!shallowEqual(newChildProps, lastChildProps.current)) { lastChildProps.current = newChildProps; // 需要一个API来强制更新当前组件 forceComponentUpdateDispatch(); // 然后通知子级更新 subscription.notifyNestedSubs(); } }; // 使用subscription注册回调 subscription.onStateChange = checkForUpdates; subscription.trySubscribe(); // 修改传给子级的context // 将subscription替换为自己的 const overriddenContextValue = { ...contextValue, subscription } // 渲染WrappedComponent // 再次使用ReactReduxContext包裹,传入修改过的context return ( <ReactReduxContext.Provider value={overriddenContextValue}> <WrappedComponent {...actualChildProps} /> </ReactReduxContext.Provider> ) } return ConnectFunction; } } export default connect;

到这里我们的React-Redux就完成了,跑起来的效果跟官方的效果一样,完整代码已经上传GitHub了:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux

下面我们再来总结下React-Redux的核心原理。

总结

React-Redux是连接React和Redux的库,同时使用了React和Redux的API。

React-Redux主要是使用了React的context api来传递Redux的store。

Provider的作用是接收Redux store并将它放到context上传递下去。

connect的作用是从Redux store中选取需要的属性传递给包裹的组件。

connect会自己判断是否需要更新,判断的依据是需要的state是否已经变化了。

connect在判断是否变化的时候使用的是浅比较,也就是只比较一层,所以在mapStateToProps和mapDispatchToProps中不要反回多层嵌套的对象。

为了解决父组件和子组件各自独立依赖Redux,破坏了React的父级->子级的更新流程,React-Redux使用Subscription类自己管理了一套通知流程。

只有连接到Redux最顶级的组件才会直接注册到Redux store,其他子组件都会注册到最近父组件的subscription实例上。

通知的时候从根组件开始依次通知自己的子组件,子组件接收到通知的时候,先更新自己再通知自己的子组件。

参考资料

官方文档:https://react-redux.js.org/

GitHub源码:https://github.com/reduxjs/react-redux/

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzfxp.html