Redux不只是使用

671 阅读9分钟

Redux的设计思想很简单,1、web应用是一个状态机,视图与状态一一对应;2、所有状态保存在一个对象里

基本概念和API

  1. Store 应用状态的管理者,可以看作一个数据库,包含以下函数

        const store = {
            dispatch(action),               // 触发state改变的唯一方法
            subscribe(listener),            // 订阅函数,store变化触发的订阅的监听函数,返回取消订阅的方法
            getState(),                     // 获取state的方法
            replaceReducer(nextReducer),    // 替换reducer
            [?observable]: observable
          }
    

    一个应用只应有一个单一的 store,其管理着唯一的应用状态 state Redux提供createStore函数,用于生成store

    import { createStore } from 'redux';
    const store = createStore(reducer);
    
  2. state

    一个对象,包含所有数据,可以看作数据库中的数据,通过const state = store.getState()获取

  3. Action

    一个包含type属性的对象,作用是描述如何修改state,它会运输数据到store,作为reducer函数的参数

  4. Action Creator

    生成Action的函数

  5. dispatch

    view发出Action的唯一方法,redux规定不能直接在业务代码中修改state,若想要修改,只能通过store.dispatch(Action)实现

  6. Reducer

    一个纯函数,会根据Acion的type值生成新的state替换调原来调state

源码解析

只对关键代码进行解读。

  1. 目录结构

  1. index.ts

    先看入口文件

    import createStore from './createStore'
    import combineReducers from './combineReducers'
    import bindActionCreators from './bindActionCreators'
    import applyMiddleware from './applyMiddleware'
    import compose from './compose'
    import warning from './utils/warning'
    import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
    
    //...
    
    export {
      createStore,
      combineReducers,
      bindActionCreators,
      applyMiddleware,
      compose,
      __DO_NOT_USE__ActionTypes
    }
    

    很简单,导出一个对象,接下来一个个分析

  2. createStore.ts

        import ?observable from './utils/symbol-observable'
        
        import {
          Store,
          PreloadedState,
          StoreEnhancer,
          Dispatch,
          Observer,
          ExtendState
        } from './types/store'
        import { Action } from './types/actions'
        import { Reducer } from './types/reducers'
        import ActionTypes from './utils/actionTypes'
        import isPlainObject from './utils/isPlainObject'
        
    
        export default function createStore (
          reducer,   // 因为dispatch会自动触发reducer的执行,所以在生成store的时候需要把reducer函数传递进来
          preloadedState?,  // 初始state
          enhancer?  // 增强,通过一系列中间件增强dispatch函数,例如对于异步Action对处理,后面详细介绍
        ) {
            // ...对参数的判断
            
            if (typeof enhancer !== 'undefined') {
                if (typeof enhancer !== 'function') {
                  throw new Error('Expected the enhancer to be a function.')
                }
                return enhancer(createStore)(
                  reducer,
                  preloadedState as PreloadedState<S>
                ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
              }
        
            let currentReducer = reducer                      // reducer函数
            let currentState = preloadedState                 // 当前state
            let currentListeners = []  // 用于存储订阅的回调函数,dispatch 后逐个执行
            let nextListeners = currentListeners              // 下一个事件循环的回调函数数组
            let isDispatching = false                         // 是否正在dispatch
            
            // 判断当前的回调函数队列是否与下一个回调函数队列相同,不能影响本次的执行
            function ensureCanMutateNextListeners() {
                if (nextListeners === currentListeners) {
                  nextListeners = currentListeners.slice()
                }
            }
        
    
            // 获取state
            function getState() {
              // 直接返回state
              return currentState
            }
          
            // 订阅--取消订阅
            function subscribe(listener) {
        
                let isSubscribed = true
        
                ensureCanMutateNextListeners()
                nextListeners.push(listener)
        
                // 取消订阅
                return function unsubscribe() {
                    if (!isSubscribed) {
                        return
                    }
        
                    isSubscribed = false
        
                    ensureCanMutateNextListeners()
                    // 将取消订阅的回调函数从数组中移除
                    const index = nextListeners.indexOf(listener)
                    nextListeners.splice(index, 1)
                    currentListeners = null
                }
            }
        
            // 派发一个Action用于改变state
            // 如果dispatch的不是一个对象类型的action(同步),而是一个Promise/Thunk(异步),
            // 就需要引入redux-thunk 等中间件来反转控制权,具体会在applyMiddlewares()方法中解析
            function dispatch(action) {
                // 确保是一个普通的对象,若非普通对象,原型链上会出现其他属性
                if (!isPlainObject(action)) {
                    throw new Error(
                        'Actions must be plain objects. ' +
                        'Use custom middleware for async actions.'
                    )
                }
        
                // 确保拥有type属性
                if (typeof action.type === 'undefined') {
                    throw new Error(
                        'Actions may not have an undefined "type" property. ' +
                        'Have you misspelled a constant?'
                    )
                }
        
                // 是否正在dispatch
                if (isDispatching) {
                    throw new Error('Reducers may not dispatch actions.')
                }
        
                try {
                    isDispatching = true
                    // dispatch 触发reducer函数,返回state,赋值给currentState,这也是为什么我们要在创建store对象的时候传入reducer
                    currentState = currentReducer(currentState, action)
                } finally {
                    isDispatching = false
                }
        
                // 逐个执行订阅的回调函数
                const listeners = (currentListeners = nextListeners)
                for (let i = 0; i < listeners.length; i++) {
                    const listener = listeners[i]
                    listener()
                }
                return action
            }
        
            // 替换当前的reducer
            function replaceReducer (
                nextReducer
            ) {
        
                // 直接替换,简单粗暴
                currentReducer = nextReducer
            
                dispatch({ type: ActionTypes.REPLACE })
                return store
            }
        
            // 这是留给 可观察/响应式库 的接口,关于响应式编程我了解甚微,具体可以自行百度一下
            function observable() {
                const outerSubscribe = subscribe
                return {
              
                    subscribe(observer) {
                        if (typeof observer !== 'object' || observer === null) {
                            throw new TypeError('Expected the observer to be an object.')
                        }
        
                        function observeState() {
                        const observerAsObserver = observer
                        if (observerAsObserver.next) {
                            observerAsObserver.next(getState())
                        }
                    }
        
                    observeState()
                    const unsubscribe = outerSubscribe(observeState)
                    return { unsubscribe }
                },
        
                [?observable]() {
                    return this
                }
            }
          }
        
          // 初始化state
          dispatch({ type: ActionTypes.INIT })
        
          // 返回store对象
          const store = {
            dispatch,
            subscribe,
            getState,
            replaceReducer,
            [?observable]: observable
          }
          return store
        }
    
    

    createStore(reducer, preloadState?, enhancer?)是Redux的核心代码,它给我们提供了获取state的方法getState()、触发state改变的方法dispatch(Action)等

    这里重点讲下为什么要设置了currentListeners,还要设置nextListeners

举个🌰:

const store = createStore(reducer)

let A = store.subscribe(() => {
    // ...
})

let B = store.subscribe(() => {
    // ...
    A()
})

let C = store.subscribe(() => {
    // ...
    store.subscribe(() => { //...})
})

结合源码中subscribe函数和dispatch部分,初始状态下currentListeners = nextListeners = [],当我们开始注册订阅事件时,即向nextListeners数组中添加子集(函数),

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

而subscribe函数返回的时unsubscribe函数,用于取消订阅,实际就是从nextListeners数组中移除对应的函数

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
    currentListeners = null

那上面我们注册了3个订阅事件,即nextListeners = [订阅函数1, 订阅函数2, 订阅函数3],而A = 取消订阅函数1

当我们dispatch(Action)时,就会执行以下代码

    // 依次执行订阅的回调函数
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
    }

如果只有一个listeners,当执行到订阅函数2时,它又会取消订阅函数,就会出现从listener = [订阅函数1, 订阅函数2, 订阅函数3]变成listener = [订阅函数2, 订阅函数3],但此时在for循环中i = 1了,而订阅函数3还没有执行循环就结束了,这么不受重视的吗!所以这种情况是觉对不允许出现的。

这也是ensureCanMutateNextListeners这个函数存在的意义。

    function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
            nextListeners = currentListeners.slice() // 返回一个新的数组,用于改变而不会影响当前
        }
    }
  1. combineReducers.ts
    import { Reducer } from './types/reducers'
    import { AnyAction, Action } from './types/actions'
    import ActionTypes from './utils/actionTypes'
    import warning from './utils/warning'
    import isPlainObject from './utils/isPlainObject'
    import {
      ReducersMapObject,
      StateFromReducersMapObject,
      ActionFromReducersMapObject
    } from './types/reducers'
    import { CombinedState } from './types/store'
    
    export default function combineReducers(reducers) {
      const reducerKeys = Object.keys(reducers)
      const finalReducers: ReducersMapObject = {}
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
      }
      // 所有reducer的key集合
      const finalReducerKeys = Object.keys(finalReducers)

      let unexpectedKeyCache
      
    // 返回合成后的 reducer
    return function combination(
        state,
        action
    ) {
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i]
          const reducer = finalReducers[key]
          const previousStateForKey = state[key]                        // 获取当前子的state
          const nextStateForKey = reducer(previousStateForKey, action)  // 执行起对应的reducer
          nextState[key] = nextStateForKey                              // 将子nextState挂载到对应的键名
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state                           // 返回总的state
      }
    }

假如我们的应用有两个状态需要管理,一个是打印日志,另一个是计数器功能,我们可以把它们写在一个reducer中,但如果还有其他动作,这些堆在一起会让代码变得难以维护,所以最好但办法是拆分reducer,使每一个动作变得单一,最后在整合在一起,combineReducers()就是为此而生的。需要注意的是,子reducer的名称应该与起对应的state相同。

比如我们的目录结构如下

reducers/
   ├── index.js
   ├── counterReducer.js
   ├── todosReducer.js

就可以这样实现

/* reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducer from './todosReducer'

const rootReducer = combineReducers({
  counter: counterReducer, // 键名就是该 reducer 对应管理的 state
  todos: todosReducer
})

export default rootReducer

-------------------------------------------------

/* reducers/counterReducer.js */
export default function counterReducer(counter = 0, action) { // 传入的 state 其实是 state.counter
  switch (action.type) {
    case 'INCREMENT':
      return counter + 1 // counter 是值传递,因此可以直接返回一个值
    default:
      return counter
  }
}

-------------------------------------------------

/* reducers/todosReducers */
export default function todosReducer(todos = [], action) { // 传入的 state 其实是 state.todos
  switch (action.type) {
    case 'ADD_TODO':
      return [ ...todos, action.payload ]
    default:
      return todos
  }
}

这样做的好处不言而喻。无论您的应用状态树有多么的复杂,都可以通过逐层下分管理对应部分的 state:

                                 counterReducer(counter, action) -------------------- counter
                              ↗                                                              ↘
rootReducer(state, action) —→∑     ↗ optTimeReducer(optTime, action) ------ optTime ↘         nextState
                              ↘—→∑                                                    todo  ↗
                                   ↘ todoListReducer(todoList,action) ----- todoList ↗


注:左侧表示 dispatch 分发流,∑ 表示 combineReducers;右侧表示各实体 reducer 的返回值,最后汇总整合成 nextState
  1. bindActionCreator.ts
    import { Dispatch } from './types/store'
    import {
      AnyAction,
      ActionCreator,
      ActionCreatorsMapObject
    } from './types/actions'
    
    function bindActionCreator<A extends AnyAction = AnyAction>(
      actionCreator: ActionCreator<A>,
      dispatch: Dispatch
    ) {
      return function (this: any, ...args: any[]) {
       // 给Action Creator 加上dispatch技能,也就是包裹一下
        return dispatch(actionCreator.apply(this, args))
      }
    }

    
    export default function bindActionCreators(
      actionCreators,
      dispatch
    ) {
        // 处理actionCreators是函数的情况
        if (typeof actionCreators === 'function') {
            return bindActionCreator(actionCreators, dispatch)
        }
    
        if (typeof actionCreators !== 'object' || actionCreators === null) {
            throw new Error(
              `bindActionCreators expected an object or a function, instead received ${
                actionCreators === null ? 'null' : typeof actionCreators
              }. ` +
                `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
            )
        }
        
        // 处理actionCreators是对象的情况
        const boundActionCreators = {}
        for (const key in actionCreators) {
            const actionCreator = actionCreators[key]
            if (typeof actionCreator === 'function') {
              boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
            }
        }
        return boundActionCreators
    }

bindActionCreators(actionCreators, dispatch)这个函数的功能也比较单一,就是dispatch(ActionCreator(XXX)),这个方法在异步情况下,没什么卵用。。。

  1. compose.ts
export default function compose(...funcs) => 

// 复合函数 compose(func1, func2, func3)(0) => func3(func2(func1(0)))
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}

这个函数非常简单,功能也很单一,主要用了数组的reduce方法,将函数作为reduce的回调函数的参数,参考文档

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  1. applyMiddlewares.ts

中间件就是一个函数,对dispatch进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。目的是为了在dispatch前后,统一处理想做对事。

import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'

export default function applyMiddleware(
  ...middlewares
) {
  return (createStore) => (
    reducer,
    ...args
  ) => {
    const store = createStore(reducer, ...args)
    let dispatch = () => {
    }
    // 中间件增加api
    const middlewareAPI = {
      getState,
      dispatch
    }
    // chain存放中间件的数组,逐个增加api
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose复合函数 a(b(c(0))) => compose(a, b, c)(0)
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

从源码可以看出,这个函数的主要作用就是通过各种中间件对dispatch进行增强,借此我们就可以处理异步Action等问题了。

我们可以思考一下为什么处理异步Action就需要对dispatch进行增强,原因如下:

(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。

(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

所以只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。

异步操作的基本思路是什么呢?

同步操作只需要发出一种Action,异步操作则需要发出3种,发送时、成功时、失败时,例如:

{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

同样的state也需要进行对应的改造

let state = {
    // ... 
    isFetching: true,       // 是否正在抓去数据
    didInvalidate: true,    // 数据是否过时
    lastUpdated: 'xxxxxxx'  // 更新数据时间
}

所以异步操作的思路应该是:

  • 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
  • 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

Redux-Thunk

前面说到处理异步Action需要借助到redux-thunk等中间件,现在具体分析一下redux-thunk是怎么处理异步请求的。

异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?

最主要的就是在Action Creator中处理,例如store.dispatch(fetchPosts())

fetchPosts()就是一个Action Creator,看一下内部实现:

const fetchPosts = () => (dispatch, getState) => {
  // 先发送第一个Action,同步的 
  dispatch({type: 'FETCH_BIGIN'});
  return fetch(`/some/API`)
    .then(response => response.json())
    .then(json => 
        // 请求到结果后,发送第二个Action,异步
        dispatch({type: 'FETCH_END', payload: json});
    )
  };
};

值得注意的是,Action Creator返回的是一个函数,而正常返回应该是一个对象,并且这个函数的参数是dispatch和getState两个方法。

这个我们就需要用到redux-thunk帮我们处理一下,看下源码

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

竟是如此简单的函数,这里我们结合applyMiddleware分析一下:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

const store = createStore(
  reducer,
  {},
  applyMiddleware(thunk)
);

applyMiddleware函数的参数是thunk函数 ==>

    ({ dispatch, getState }) => (next) => (action) => {
        if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
        }
        return next(action);
    }

回顾一下applyMiddleware函数 ==>

import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'

export default function applyMiddleware(
  ...middlewares
) {
  return (createStore) => (
    reducer,
    ...args
  ) => {
    const store = createStore(reducer, ...args)
    let dispatch = () => {
    }
    // 中间件增加api
    const middlewareAPI = {
      getState,
      dispatch
    }
    // chain存放中间件的数组,逐个增加api
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose复合函数 a(b(c(0))) => compose(a, b, c)(0)
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

应用了redux-thunk再通过enhancer(createStore)({reducer, preloadedState})调用,即applyMiddleware(thunk)(createStore)({reducer, preloadedState}),dispatch函数就会变成,

    dispatch = (store.dispatch) => (action) => {
        // 处理action是函数的情况
        if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
        }
        
        // 处理action是对象的情况
        return store.dispatch(action)
    }

React-Redux

Redux的作者封装了一个React专用的库,React-Redux

看一个例子:

import React from 'react'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'

// React component
const Counter = (props) => {
	const { value, changeVal } = props
    
    return (
    	<div>
        	<span>{value}</span>
            <button onClick={changeVal} >change</button>
        </div>
    )
}

// Action
const rideAction = {
	type: 'ride',
    payload: {
    	num: 2
    }
}

// Reducer
function reducer (state = {count: 0}, action) {
	const count = state.count
    switch(action.type) {
    	case 'ride':
        	return { count: count * action.payload.num };
        default:
        	return state;
    }
}

// Store
const store = createStore(reducer)

// map Redux state to component props
function mapStateToProps (state) {
	return {
    	value: state.count
    }
}

// map Redux actions to component props
function mapDispatchToProps (dispatch) {
	return {
    	changeVal: () => dispatch(rideAction)
    }
}

// connect component
const App = redux.connect(mapStateToProps, mapDispatchToProps)(Counter)

// Provider
React.ReactDom.render(
	<Provider store={store} >
    	<App />
    </Provider>,
    document.getElementById('root')
)
 
  1. connect 用于将ui组件与Redux建立连接
const app = Redux.connect(mapStateToProps, mapDispatchToProps)(component)
  1. mapStateToProps函数构建从state对象到props对象的映射关系

  2. mapDispatchToProps函数用于构建UI组件的参数到store.dispatch方法的映射

  3. <Provider>组件 connet方法生成容器组件以后,需要让容器组件拿到state对象,才能生成UI组件的参数。如果作为props传递是在太麻烦,所以React-Redux提供Provider组件。

    原理是利用React组件的context属性

    class Provider extends Component {
    	getChildContext() {
        	return {
            	store: this.props.store
            }
        }
        render() {
        	return this.props.children
        }
    }
    
    Provider.childContextTypes = {
    	store: React.PropTypes.object
    }
    

总结了这些,如有不对的地方还望大家指正,希望有兴趣的朋友一起交流交流。