react进阶----redux,中间件和对应源码

375 阅读5分钟

知识点

纯函数

  1. 如果函数的调用参数相同,则永远返回相同的结果。
  2. 它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
  3. 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。

Reduce

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer)); // 没有初始值
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5)); //初始值为5
// expected output: 15

Reducer

  1. 概念:reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
  1. 之所以将这样的函数称之为 reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue) 里的回调函数属 于相同的类型。
  2. 保持 reducer 纯净非常重要,不要在 reducer修改传入参数,不要执行有副作用的操作,如api请求和跳转路由,不要调用非纯函数,如 Date.now() 或 Math.random()。

redux使用

简单的同步数据流动的场景

安装redux

yarn add redux

使用

  1. 需要一个store来存储数据
  2. store里的reducer初始化state并定义state修改规则
  3. 通过dispatch一个action来提交对数据的修改
  4. action提交到reducer函数里,根据传入的action的type,返回新的state。

创建store---src/store/index.js

import {createStore} from "redux";

function countReducer(state = 0, action) {
  switch (action.type) {
    case "ADD":
      return state + 1;
    case "MINUS":
      return state - 1;
    default:
      return state;
  } 
}
const store = createStore(countReducer);
export default store;

创建ReduxPage

import React, {Component} from "react";
import store from "../store/";
export default class ReduxPage extends Component {

  componentDidMount() {
    store.subscribe(() => {
      this.forceUpdate();
    });
  }
  
  add = () => {
    store.dispatch({type: "ADD"});
  };
  
  minus = () => {
    store.dispatch({type: "MINUS"});
  }; 
  
  render() {
    console.log("store", store); //sy-log
    return (
      <div>
        <h3>ReduxPage</h3>
        <p>{store.getState()}</p>
        <button onClick={this.add}>add</button>
        <button onClick={this.minus}>minus</button>
      </div>
    ); 
  }
}

注意:如果点击按钮不能更新,查看是否订阅(subscribe)状态变更。

还可以在src/index.js的render里订阅状态变更

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from './store/'

const render = () => ReactDOM.render(<App />, document.getElementById("root"));

render()

store.subscribe(render)

检查点

  1. createStore创建store
  2. reducer初始化、修改状态函数
  3. getState获取状态值
  4. dispatch提交更新
  5. subscribe变更订阅

redux源码

核心实现

  1. 存储状态state
  2. 获取状态getState
  3. 更新状态dispatch
  4. 变更订阅subscribe

核心代码

export default function createStore(reducer, enhancer) {
  if (enhancer) {
    // 原版dispatch只能接受普通对象,加强之后变强大,可以处理多种形式,如callback、promise等
    return enhancer(createStore)(reducer);
  }
  let currentState;
  let currentListeners = [];
  function getState() {
    return currentState;
  }
  function dispatch(action) {
    currentState = reducer(currentState, action);
    currentListeners.forEach(listener => listener());
  }
  function subscribe(listener) {
    currentListeners.push(listener);

    return () => {
      // 自己实现过滤
      currentListeners = [];
    };
  }

  // 主动调一次dispatch是为了返回初始值
  dispatch({type: "REDUX/ZXB"});

  return {
    getState, //获取状态
    dispatch, // 触发改变state
    subscribe //订阅
  };
}

异步

Redux只是个纯粹的状态管理器,默认只支持同步,实现异步任务 比如延 迟,网络请求,需要中间件的支持,,比如我们使用最简单的redux-thunk和 redux-logger,redux-promise等

中间件

  1. 中间件就是一个函数,对store.dispatch方法进行改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
  2. redux 的 middleware 是为了增强 dispatch 而出现的

应用中间件

  yarn add redux-thunk redux-logger
 
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counterReducer from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(thunk, logger));
  1. 实现applyMiddleware
// 第一步:函数式编程思想设计 middleware,
// middleware 的设计有点特殊,是一个层层包裹的匿名函数,
// 这其实是函数式编程中的柯里化 curry,
// 一种使用匿名单参数函数来实现多参数函数的方法。
// applyMiddleware 会对 logger 这个 middleware 进行层层调用,
// 动态地对 store 和 next 参数赋值。
export default function applyMiddleware(...middlewares) {
  return createStore => reducer => {
    const store = createStore(reducer);
    let dispatch = store.dispatch;

    const midApi = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    };
    // 第二步:map让每一个middleware携带 midApi 这个参数分别执行一遍,获得 chain 数组,[f1, f2, ... , fx, ...,fn],
    // 因为闭包,每个匿名函数都可以访问相同的 store,即 middlewareAPI。
    const middlewareChain = middlewares.map(middleware => middleware(midApi));

    // 第三部:组合串联 middlewares
    // compose 是函数式编程中的组合
    // compose 将 chain 中的所有匿名函数,[f1, f2, ... , fx, ..., fn],组装成一个新的函数,即新的 dispatch
    // 当新 dispatch 执行时,[f1, f2, ... , fx, ..., fn],从左到右依次执行( 所以顺序很重要)
    // dispatch被加强了
    dispatch = compose(...middlewareChain)(store.dispatch);

    return {
      ...store,
      //  返回加强之后的dispatch
      dispatch
    };
  };
}

// compose(...chain) 返回的是一个匿名函数,函数里的 funcs 就是 chain 数组
// 当调用 reduce 时,依次从 funcs 数组的右端取一个函数 fx 拿来执行
// fx 的参数 composed 就是前一次 fx+1 执行的结果
// 而第一次执行的fn(n代表chain的长度)的参数 arg 就是 store.dispatch。
// 所以当 compose 执行完后,我们得到的 dispatch 是这样的,假设 n = 3,,会得到:dispatch = f1(f2(f3(store.dispatch))))
// 这个时候调用新 dispatch,每个 middleware 的代码就依次执行了嘛

function compose(...funcs) {
  if (funcs.length === 0) {
    return args => args;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((composed, func) => (...args) => composed(func(...args)));
}

总结: applyMiddleware 机制的核心在于组合 compose,将不同的 middlewares 一层一层包裹到原生的 dispatch 之上,而为了方便进行 compose,需对 middleware 的设计采用柯里化 curry 的方式,达到动态产生 next 方法以及保持 store 的一致性。由于在 middleware 中,可以像在外部一样轻松访问到 store, 因此可以利用当前 store 的 state 来进行条件判断,用 dispatch 方法拦截老的 action 或发送新的 action。

  1. 实现redux-thunk
// !next就是聚合函数
function thunk({dispatch, getState}) {
  return next => action => {
    if (typeof action === "function") {
      return action(dispatch, getState);
    }
    return next(action);
  };
}
  1. 实现redux-logger
function logger({getState}) {
  return next => action => {
    console.log(action.type + "执行了!"); //sy-log

    let prevState = getState();
    console.log("prev state", prevState); //sy-log
    
    // 执行next()之后,拿到新的state
    const returnValue = next(action);
    let nextState = getState();
    console.log("next state", nextState); //sy-log
    
    return returnValue;
  };
}
  1. redux-promise
import isPromise from "is-promise";

function promise({dispatch}) {
  return next => action => {
    return isPromise(action) ? action.then(dispatch) : next(action);
  };
}