redux用法和原理

1. 概述

本文带着大家回顾下redux用法,reduxAPIcreateStorereducerdispatch等,理解用法后,一起探究原理,难点是redux中间件的原理部分。
注意:本文只探究redux,不关注react-reduxreact-redux的使用和原理后面会有专文讲解,redux的用处不限于react,任何你想要订阅事件的地方,都可以单独使用redux

2. 设计思想

  • redux是将整个应用状态存储到到一个地方,称为store,单一数据源
  • 里面保存一棵状态树state
  • 派发dispatch行为actionstore,而不是直接更改state,只读
  • 用户通过订阅(subscribe) store,当有dispatch行为时,会通知订阅者更新

下图是我画的redux流程,其实从图中看redux逻辑也简单,你只能用dispatch通过派发action更新stateaction派发后,内部有两个事情处理,一是要调用reducer更新state,二是触发之前订阅过的事件执行,就完了。

redux

3. redux基本使用

本文重点讲原理,适合有redux基础的同学,用法简单回顾。

3.1 createStore

用来创建store

import { createStore } from 'redux';

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

const store = createStore(reducer);

3.2 getState

获取当前state数据

const state = store.getState();

3.3 action

action 是一个对象。其中的type属性是必须的,表示 action 的名称。其他属性可以自由设置。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

3.3 dispatch

派发action的唯一途径

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

3.4 reducer

接受旧的stateaction作为参数,返回新的state

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

3.5 subscribe

设置订阅,dispatch时会自动执行所有订阅的事件

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

let unsubscribe = store.subscribe(() => {
    console.log("state更新了")
});
// subscribe的返回值可以取消订阅
// unsubscribe();

3.6 combineReducers

把多个reducer合并成一个,因为在实际项目中,不同的业务数据不可能全部写在一个reducer里,维护成本高,通常把reducer按业务分开,最后用combineReducers合并后,再传给createStore

import { combineReducers } from 'redux';

const reducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})
export default reducer;

4. 原理

我看的redux版本是4.1.2的源码

4.1 createStore源码分析

/**
 * 创建store
 * @param {*} reducer 
 * @param {*} preloadedState 
 * @param {*} enhancer 
 * @returns 
 */
unction createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(process.env.NODE_ENV === "production" ? formatProdErrorMessage(1) : "Expected the enhancer to be a function. Instead, received: '" + kindOf(enhancer) + "'");
    }

    return enhancer(createStore)(reducer, preloadedState);
  }
  // 上部分redux中间件再讲,我们先只关注createStore第一个参数reducer

  // reducer必须是个函数
  if (typeof reducer !== 'function') {
    throw new Error("Expected the root reducer to be a function.");
  }
  // 定义内部变量
  var currentReducer = reducer;
  // 当前state数据,初始值是preloadedState
  var currentState = preloadedState;
  // 保存所有订阅函数
  var currentListeners = [];
  // 保存的订阅函数快照
  var nextListeners = currentListeners;
  // 是否dispatch正在执行
  var isDispatching = false;

  // 为subscribe执行时,提供备份,具体在subscribe函数细看
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice();
    }
  }
  
  /**
   * getState源码,获取当前最新state
   * @returns 返回state
   */
  function getState() {
    return currentState;
  }
  
  /**
   * subscribe源码,用来收集订阅
   * @param {*} listener 订阅方法
   * @returns 返回取消此订阅的方法
   */
  function subscribe(listener) {
    // listener 必须是函数
    if (typeof listener !== 'function') {
      throw new Error("Expected the listener to be a function");
    }
    // 闭包变量,用来标识取消订阅方法只能执行一次
    var isSubscribed = true;
    // 为每次订阅提供快照备份nextListeners,
    // 防止在dispatch里遍历currentListeners过程中,触发了订阅/取消订阅功能。
    // 若直接更新currentListeners将造成当前循环体逻辑混乱,
    // 因此所有订阅/取消订阅的listeners都是在nextListeners中存储的,并不会影响当前的dispatch(action)
    ensureCanMutateNextListeners();
    nextListeners.push(listener);

    return function unsubscribe() {
      // 已经取消了,直接跳出
      if (!isSubscribed) {
        return;
      }
      // 取消成功,没有此订阅了
      isSubscribed = false;
      ensureCanMutateNextListeners();
      var index = nextListeners.indexOf(listener);
      nextListeners.splice(index, 1);
      currentListeners = null;
    };
  }
  /**
   * dispatch源码
   * @param {*} action 接受action对象
   * @returns 返回action
   */
  function dispatch(action) {
    // action是个纯对象
    if (!isPlainObject(action)) {
      throw new Error("Actions must be plain objects.");
    }
    // action对象必须有type属性
    if (typeof action.type === 'undefined') {
      throw new Error('Actions may not have an undefined "type" property. You may have misspelled an action type string constant.');
    }
    // 执行reducer时,不允许有其他dispatch操作
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.');
    }

    try {
      // 开始执行reducer
      isDispatching = true;
      // reducer接受当前state和action对象,返回新的state
      currentState = currentReducer(currentState, action);
    } finally {
      // reducer执行完成,state更新完毕
      isDispatching = false;
    }
    // 然后再执行所有subscribe订阅过的listeners
    var listeners = currentListeners = nextListeners;
    // 遍历执行
    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }
    // 返回action
    return action;
  }
  // 首先派发初始化action,目的是给state赋初始值
  // 因为reducer函数,在初始化时,可以传个state初始值,所以在内部调用下,reducer的action没有匹配时,返回默认值
  // 注意:如果createStore传了第二个参数是个对象,也代表初始state,见代码:var currentState = preloadedState;
  // 此时会覆盖reducer的默认值,因为在调用currentReducer(currentState, action)方式传的currentState就是preloadedState,所以reducer函数的默认值无效了
  dispatch({
    type: ActionTypes.INIT
  });

  return {
    dispatch: dispatch,
    subscribe: subscribe,
    getState: getState
  }
}

4.2 combineReducers源码解析

对照combineReducers使用方法,再看原理

import { combineReducers } from 'redux';
function chatLog(state, action) {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
}
function statusMessage(state, action) {
  // 同chatLog
}
function userName(state, action) {
  // 同chatLog
}
const reducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})
const store = createStore(reducer);

原理

function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {};

  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key];
    }
  }

  var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
  
  // 返回还是reducer函数的类型
  // dispatch实际执行的reducer
  return function combination(state, action) {
    // state没有默认值时,初始化一个对象
    if (state === void 0) {
      state = {};
    }
    // 标识state中每个key的值更新前后是否有变化
    var hasChanged = false;
    // 更新之后的state
    var nextState = {};
    // 遍历每个小的reducer
    for (var _i = 0; _i < finalReducerKeys.length; _i++) {
      // combineReducers参数对象的key
      var _key = finalReducerKeys[_i];
      // key对应的reducer
      var reducer = finalReducers[_key];
      // 通过 combineReducers参数对象的key 取出之前的状态
      var previousStateForKey = state[_key];
      // 每个reducer都会调用
      var nextStateForKey = reducer(previousStateForKey, action);
      // 保存每个key的返回的新的state
      nextState[_key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }

    hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
    // 有变化返回新的state,没变化返回旧的state
    return hasChanged ? nextState : state;
  };
}

总结下

  • combineReducers的返回值还是个reducer形式的函数
  • 因为createStore方法内,会初始执行一次dispatch,所以源码中combination(组合后的reducer)会首先调用一次,返回与combineReducers参数对象一样结构的state,即state的结构同combineReducers参数对象,对象的属性值是每个reducer对应的返回值
  • dispatch派发时,所有的reducer都会执行一次,reducer接收的是state对应keyvalue,不是整个state状态数据

5. 结尾

到这redux原理就完了,其实发现redux源码简单,核心就是createStore方法。
一句话描述redux原理:通过subscribe订阅更新,dispatch改变state时遍历执行所有的订阅事件。

redux中间后面会写个专文来讲解,本篇文章内容不少了,不放在本篇了,先理解好redux原理。

如有错误,请指正,欢迎评论交流,关注我,只写干货

你可能感兴趣的:(redux用法和原理)