在react项目中,redux常常用来管理应用的数据,react-redux用来绑定redux, 这样你的组件能够从store中读取数据,而且能够dispatch actions更新store, redux主要思想让系统中的数据按照统一的规则流动,即单项数据流:javascript
如图: 对于组件的修改,经过action被dispatch到store, store根据action和当前的state计算出下一次的state, component拿到更新后的state从新渲染组件;能够看到数据只是单项流动java
动手实现一个简单的redux, react-redux, 中间件,而后对比一下咱们的实现有哪些潜在的问题,实际上怎么解决的react
如何使用redux和react-redux, redux.createStore方法建立一个store, store注入到Provider组件上,而后经过connect方法拿到store中的state和dispatcher,而后经过组件的props拿到这些方法和属性redux
createStore建立store,store对象具备dispatch方法,subscribe方法,getState方法, replaceReducer方法,那么经过观察者模式能够实现数组
function createStore(reducer, preloadedState) { let listeners = [] let state = preloadedState let currentReducer = reducer let subscribe = (fn) => { listeners.push(fn) return () => { const index = nextListeners.indexOf(listener) listeners.splice(index, 1) } } let dispatch = (action) => { state = currentReducer(state, action) for (let i = 0; i< listeners.length; i++) { const listener = listeners[i] listener() } } let getState = () => { return state } let repalceReducer = (nextReducer) => { currentReducer = nextReducer dispatch({type:'@@redux/REPLACE'}) } return { dispatch subscribe, getState, replaceReducer } }
redux实际实现要比这个复杂,好比说listeners存在两个变量中, currentListeners和nextListeners,dispatch执行的老是currentListeners中的函数,subscribe和unsubscibe老是在nextListeners中增长或者移除listener,这样能够避免在dispatching过程当中,listeners数组发生改变,上面还能够看到replaceReducer其实更新了state,触发了订阅, replaceReducer用在须要按需加载的reducer场景中,看下combiceReducers的实现,你会发现一次combine几百个reducer并非一件好事,replaceReducer动态替换reducer提高效率app
接下来的问题如何在组件中订阅state的更新,而且能够dispatch action,以更新state;redux-redux使用了Context, redux中的Provider组件就是对Context.Provider作了封装ide
export const ReactReduxContext = React.createContext(null) function Provider({store, children, context}) { const [provider, setProvider] = useState({ state: store.getState(), dispatch: store.dispatch }) useEffect(() => { store.subcribe(() => { setProvider({ state: store.getState(), dispatch: store.dispatch }) }) }, [store]) const Context = context || ReactReduxContext return <Context>{props.children}</Context> } export default Provider
有了Provider组件,咱们还须要一个connect函数,connect函数接收mapStateToProps和mapDispatchToProps,返回一个高阶组件,这个高阶组件接收React组件,返回一个PureComponent;函数
import ReactReduxContext from './Provider' export default function connect (mapStateToProps, mapDispatchToProps) { return function (WrappedComment) { return function (props) { return ( <ReactReduxContext> { ({state, dispatch}) => { <WrappedComment {...mapStateToProps(state, props)} {...mapDispatchToProps(dispatch, props)} /> } } </ReactReduxContext> ) } } }
其实这个返回的组件,react-redux又用React.memo进行包装,保证只在props发生变化的时候才会从新渲染。react-redux对于connect的实现比这里要复杂得多,如开头提出的问题: react-redux须要保证父组件的更新在子组件以前,react的connect方法实际上是对connectAdvanced方法的封装(参见官网),connectAdvanced方法放回一个高阶组件,如上面所封装的connect方法,返回的组件又使用React.memo变为PureComponent, react-redux如何保证父组件订阅store的更新,发生在子组件以前呢? 也是经过subscirpe方法;每个connect方法返回的高阶组件内部都用了一个Context.Provider包装WrappedComponent, 她的value为store和Subscription对象,这样每个子组件的Subscription对象能够拿到离它最近的父组件的Subscription对象,这样造成了一个Subscription对象树,经过Subvcription控制更新顺序, 直接上图spa
什么是中间件?中间件就是对dispatch方法作了封装,好比说每次dispatch一个action前,我须要发送一条日志,若是不使用中间件,你的作法是日志