由于公司项目的技术栈是 React,与之配套的公共状态管理的库是 Redux,最近也研究了其中的原理。由于我之前是硬背 Redux 的用法,时间搁久了总是忘记如何使用。每次要用的时候,就去翻文档,不仅效率低下,用起来也感觉到恶心自己了。搞清楚了背后的机制,写起来就很顺手。相信大家弄懂的基本的实现细节,用起来也会得心应手。
首先思考 Redux 解决了什么问题,为什么要使用它。每当有一些数据要在很多组件中使用的时候,而这些组件跨层级时,props 就不再适合。就需要把这些数据提取出来放在一个模块,我们把它称为公共状态,每当数据改变时,每当数据改变时,它会通知订阅它的说有组件。
要想实现这样的共享的数据,首先想到的必是建一个全局模块,里面存放很多公共的数据。就像这样
const obj = { count: 0 } 复制代码
当状态需要改变的时候,直接显示的赋值,当然,这里要通过观察者模式数据已经改变。不过这样肯定不好
既然是共享数据,外部又不能直接改变它,当然就是一个闭包啦。没错,Redux 内部这样的一个机制,通过 store 的一些方法,我们可以搭出大致的骨架
function createStore() { // 公共的状态 let currentState = {} // getter function getState() { return currentState } // setter function dispatch() { } function subscribe() { } return { getState, dispatch, subscribe } } 复制代码
毫无疑问,每次调用 getState 就可以拿到最新的 currentState 的值
在 Redux 里,每当要改变数据,都是调用 dispatch 一个 action,根据 actionType 的值来返回不同的 state,好了,我们就可以这样写
function createStore() { // 公共的状态 let currentState = {} // getter function getState() { return currentState } // setter function dispatch(action) { swicth(action.type) { case "increament": return { ...currentState, count: currentState.count + 1 } default: return currentState } } function subscribe() { } return { getState, dispatch, subscribe } } 复制代码
这样就实现了一个 dispatch,但这样写不好,我们把 switch 这样的逻辑可以提取成一个函数,这也是所说的 reducer,那么,代码就成了这样
const initialState = { count: 1 } function reducer(state = initialState, {type, payload}) { switch (type) { case 'increament': return { ...state, count: count + payload } case 'decrement': return { ...state, count: count - payload } default: return initialState } } function createStore() { // 公共的状态 let currentState = {} // getter function getState() { return currentState } // setter function dispatch(action) { currentState = reducer(currentState, action) } function subscribe() { } return { getState, dispatch, subscribe } } 复制代码
在我们创建 store 的时候,会初始化一次 state,也就是内部会 dispatch 一次,因而
function createStore() { // 公共的状态 let currentState = {} // getter function getState() { return currentState } // setter function dispatch(action) { currentState = reducer(currentState, action) } dispatch({ type: '@@REDUX_INIT' }) function subscribe() { } return { getState, dispatch, subscribe } } 复制代码
到此,即完成了获取和改变状态的功能,验证一下代码
const store = createStore(reducer) console.log(store.getState().count) // 1 store.dispatch({type: 'increament', payload: 5}) console.log(store.getState().count) // 6 复制代码
尽管如此,当数据改变了,还是不能通知我视图更新。这里需要监听数据的变化,用到了观察者模式,也有人说是发布订阅模式,这两种模式基本的思想一致,但还是有一些区别的,这里观察者模式更加准确。状态数据相当于被观察者,在 createStore 中,我们要维护一个观察者的队列,当执行 subscribe时,把他们放入队列,dispatch的时候,执行队列里的回调
function createStore() { // 公共的状态 let currentState = {} // 存放观察者的队列 let observers = [] // getter function getState() { return currentState } // setter function dispatch(action) { currentState = reducer(currentState, action) observers.foreach(fn => fn()) } dispatch({ type: '@@REDUX_INIT' }) function subscribe(fn) { observers.push(fn) } return { getState, dispatch, subscribe } } 复制代码
如此以来,就完成了数据变化,通知组件改变视图的功能,模拟组件运行代码
const store = createStore(reducer) store.subscribe(() => console.log('组件1收到通知', store.getState())) store.subscribe(() => console.log('组件2收到通知', store.getState())) store.dispatch({ type: 'increament', payload: 5 }) 复制代码
一个最简单的 redux 的应用已经完成,这里还缺饭了大量的参数类型判断,异常处理,异步action、中间件等等。但基本的原理大致相同
在 react 的项目中,尽管 redux 能实现我们的需求,但写法太过冗余,和 react 的组件写法也不太契合,于是,就有了 react-redux。简单来说,它提供了两大功能:
Provider原理是把 store 绑定在 context 上,至于 context,可以为子孙组件的上下文提供 store 对象
class Provider extends Component { static childContextTypes = { store: PropTypes.shape({ subscribe: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired, getState: PropTypes.func.isRequired }).isRequired } constructor(props) { super(props); this.store = props.store; } getChildContext() { return { store: this.store } } render() { return this.props.children } } 复制代码
connect 是一个高阶函数,接收 mapStateToProps, mapDispatchToProps 为参数,并且它返回一个高阶组件。其中,这两个参数都是函数,mapStateToProps 接收 state 返回一个对象,这搞清楚了,mapDispatchToProps 接收一个 dispatch 返回一个对象,我们再来实现它:
// 形如 mapTostate、mapDispatchToProps const mapStateToProps = state => {} const mapDispatchToProps = dispatch => {} function connect(mapStateToProps, mapDispatchToProps) { return function(WrappedComponent) { class Connect extends Component{ constructor(props) { super(props) this.state = mapStateToProps(store.getState()) this.mapDispatch = mapDispatchToProps(store.dispacth) } ComponentDidMount() { } <WrappedComponent {...this.props} {this.state} {this.mapDispatch} /> } return Connect } } 复制代码
知道这里,我们通过高阶函数把一些属性挂载到了高阶组件的 props 上,接下来就可以通过 this.props.xxx 调用。此时,我们被 connect 包裹的新组件的 props 上虽然有了值,但是还不具备自动更新的功能,继续改进 connect
function connect(mapStateToProps, mapDispatchToProps) { return function(WrappedComponent) { class Connect extends Component{ constructor(props) { super(props) this.state = mapStateToProps(store.getState()) this.mapDispatch = mapDispatchToProps(store.dispacth) } ComponentDidMount() { // 如果 store 中的状态改变会执行回调函数,此时新获取的 mappedState 和旧的做对比,如若有变化,就setState this.unsub = store.subscribe(() => { const mappedState = mapStateToProps(store.getState()); if(shallowEqual(this.state, mappedState)) { return; } this.setState(mappedState); }); } <WrappedComponent {...this.props} {this.state} {this.mapDispatch} /> } return Connect } } 复制代码
如此,一个基本功能的 react-redux 已经实现。