关于前端:redux用法和原理

6次阅读

共计 6548 个字符,预计需要花费 17 分钟才能阅读完成。

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,二是触发之前订阅过的事件执行,就完了。

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 原理。

如有谬误,请斧正,欢送评论交换,关注我,只写干货

正文完
 0