redux 原理浅析

在对 redux 的使用过程中,了解到 redux 中的一些核心概念和方法,为了达到 “知其然,也知其所以然” 的学习目标,尝试从应用层面出发,剖析原理,手撸 redux 和 react-redux 中的核心方法。 一. redux 中的基本概念 整个工作流如图所示,涉及到以下核心概念: Store:状态树,存储对象状态的地一个容器 Action :操作 store 的行为载荷,通过 store.dispatch 传递到 store Reducers:真正操作 store 的方法,可以查看之前的状态,也可以响应接收到的 action 并返回一个新的状态 以下概念为 React-redux 中才具备的: Provider:一个外层容器,配合 connect 实现父子层级组件的通信 Connect:连接 React 组件与 redux store 的一个方法,接收 Provider 组件提供的 store,返回一个高阶组件,将响应的 state 和 dispatch 作为属性参数传给内部组件 redux 工作流 二. redux 核心方法实现 在手撸核心方法之前,先回忆一下使用 redux 的流程: 首先需要声明一个 reducer,然后调用 createStore 来实例化一个 store 紧接着如果要获取对象的状态,就需要调用 store.getState 方法来获取 如果要修改对象的状态,就需要调用 store.dispatch 方法来修改 最后不要忘了使用 store.subscribe 方法来添加监听函数 根据上述回顾,可以知道实例化的 store 包含三个方法 getState ,dispatch, subscribe,其次为了实现对对象的存储与事件的监听,还需要两个变量 currentState 和 currentListeners 分别用于存储当前对象状态和监听队列。于是可以 createStore 函数的基本结构就清楚了 export function createStore(reducer, enhancer) { // 先忽略 enhancer 的操作 // ... let currentState, currentListeners = [] function getState() { // todo } function dispatch(action) { // todo } function subscribe(listener) { // todo } return {getState, dispatch, subscribe} } 有了基础框架之后,再来仔细思考一下三个方法都具备了哪些功能: getState 方法:没有参数,能够返回当前的对象状态,故而函数内部很简单,就是直接返回 currentState; dispatch 方法:传入一个 action,将其代理到 reducer 中,由 reducer 去执行真正的状态更改并得到新的状态,同时触发状态变更事件,也即需要执行所有的监听函数; subscribe 方法:传入一个 listener,将其添加到监听队列中,一旦 store 中的状态发生变更,listener 将被执行。 createStore 实现如下: export function createStore(reducer, enhancer) { if(enhancer) { // 如果存在 enhancer,则对本函数进行转换,再将 reducer 传入 return enhancer(createStore)(reducer) } let currentState, currentListeners = [] function getState() { return currentState } function subscribe(listener) { currentListeners.push(listener) // 触发一个不可能存在的 action,使得 currentState 具备一个初始值 dispatch('@roadlin/myRedux') } function dispatch(action) { currentState = reducer(currentState, action) // 遍历监听队列,依次执行监听事件 currentListeners.forEach(v => v()) return action } return { getState, subscribe, dispatch } } 除了上述的 createStore 方法,redux 还提供了其它 API,比如当存在多个 reducer 时,需要调用 combineReducers 进行合并;当要引入中间件时,需要使用 applyMiddleware 进行注入。同样对这两个 API 的功能进行拆分分析,有助于理解其内部实现: combineReducers :传入的参数是一个对象,包含多个 reducers,最终经处理合并之后,返回一个合并之后的 reducer,代码描述如下: combineReducers({ count: countReducer, cart: cartReducer }) // 上述代码合并之后得到一个整体的 reducer function reducer(state = {}, action) { return { count: countReducer(state.count, action), cart: cartReducer(state.cart, action) } } 合并之后,在调用 dispatch 的时候,会将 action 传递给合并后的 reducer, 合并后的reducer 中会遍历获取每个 key 值对应的状态,传递给相应的原始 reducer 执行,也即无论 action 是操作哪一个状态,所有的 reducer 都会执行一次 applyMiddleware: 传入的参数是 n 个中间件(n ≥ 1),其主要作用是包装 store 原始的 dispatch 方法,使其支持中间件的功能,比如 applyMiddleware(thunk) 之后 dispatch 中支持异步操作。当 n > 1时,中间件是从右往左进行链式调用,也即最后的中间件处理之后的结果交给前一个中间件处理 combineReducers 和 applyMiddleware 实现如下: function combineReducers(reducersObj) { // 1. 首先做了去重和判断,去除重复的 key 值,保证传入的每一个属性值都是 reducer 函数,保证一个 key 只对应一个 reducer let finnalReducers = {} for(let key in reducersObj) { if(typeof reducersObj[key] === 'function') { finnalReducers[key] = reducersObj[key] } } // 2. 中间还做了下判断,保证传入的 reducers 中不包括复合型的 reducer,也就是 combineReudcers 处理后的结果不能用于再一次的 combineReducers // ... 该部分省略 // 3. 返回一个合并后的 reducer 函数 combination(state ={}, action) // 3.1 内部遍历了所有的 reducers,获取上一次 key 值对应的 state,调用对应的 reducer 函数,执行 action // 3.2 循环过程中判断是否更新了 state 值,如果更新了,则返回新的 state,否则依旧返回旧的 state return function combination(state = {}, action) { let hasChange = false, nextState = {} for(let key in finnalReducers) { let previousStateForKey = state[key] let nextStateForKey = reducersObj[key](previousStateForKey, action) nextState[key] = nextStateForKey hasChange = hasChange || nextStateForKey !== previousStateForKey } return hasChange ? nextState : state } } export function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = store.dispatch let midApi = { getState: store.getState, dispatch: (...args) => dispatch(...args) } let middleChain = middlewares.map(middleware => middleware(midApi)) dispatch = compose(middleChain)(store.dispatch) return { ...store, dispatch } } } // 遵循从右到左的链式调用,所以 compose(f, h, g) 等价于 (...args) => f(h(g(...args))) export function compose(...funcs) { if(funcs.length === 0) { return arg => arg } if(funcs.length === 1) { return funcs[0] } return funcs.reduce((ret, item) => (...args) => ret(item(...args))) } /*******************华丽分割线*************************/ // 除此之外,redux 中还有别的函数,比如: /* 调用 connect 时,如果 mapDispatchToProps 是对象时执行 bindActionCreators 如 @connect( state => ({goods: state.goods}), {addGood, deleteGood, asyncAdd} ) */ function bindActionCreator(creator, dispatch) { return (...args) => dispatch(creator(...args)) } export function bindActionCreators(creators, dispatch) { return Object.keys(creators).reduce((ret, item) => { ret[item] = bindActionCreator(creators[item], dispatch) return ret }, {}) } 三. react-redux 核心方法实现 在应用 react-redux 时,主要用到了 组件以及 connect 函数,同样的,先分析它们的主要功能: Provider 组件:通过属性传参的方式传入 store 参数,并将其传给子组件,同时渲染内部组件。为了实现子组件中可以读取到 store 参数,需要借助 context 上下文来传递,作为父组件,其需要设置 childContextTypes connect 函数:传入的是两个 map,返回一个高阶组件,将 redux 中的 state 和 dispatch 变成组件的 props 为了从 Provider 中获取到 store,需要设置 contextTypes 高阶组件最后返回的新组件中,也即经 connect 装饰之后的组件可以直接通过 this.props[key] 读取到 redux 中的数据和方法,故而需要执行两个 map,获取到 redux 数据和方法,最后以 props 的方式传给新组件 此外,还需要设置监听事件,当 redux 中的数据发生更改时,更新当前组件的 props import React from 'react' import PropTypes from 'prop-types' import {bindActionCreators} from './myRedux' export class Provider extends React.Component{ static childContextTypes = { store: PropTypes.object } getChildContext() { return {store: this.store} } constructor(props, context) { super(props, context) this.store = props.store } render() { return this.props.children } } export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => WrapComponent =>{ return class NewComponent extends React.Component { static contextTypes = { store: PropTypes.object } constructor(props, context) { super(props, context) this.state = { props: {} } } // 在组件渲染前更新,否则因为读不到对应的 props 属性值而报错 componentWillMount() { const {store} = this.context // 注册监听事件 store.subscribe(() => this.update()) this.update() } update() { const {store} = this.context const stateProps = mapStateToProps(store.getState()) const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch) // 整合所有的参数,包括组件自身的参数、redux 中的 state 及 dispatch this.setState({ props: { ...this.state.props, ...stateProps, ...dispatchProps } }) } render() { return } } }

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):