乐趣区

手写实现Redux功能applyMiddleware中间件

Rredux 是什么?

Redux 是 JavaScript 应⽤用的状态容器器。它保证程序⾏行行为⼀一致性且易易于测试。

  • React Conponent: 我们开发的的 React 组建;
  • 当组建的内容要发生改变的时候;我们要发起一个 dispatchdispatch 去派发 actionaction 里面会包裹{type,paylod}
  • Reducer就是制定修改规则的纯函数;接收一个旧的 state,和 action 返回一个新的 state

    永远不要在 reducer ⾥做这些操作:

    1. 修改传⼊参数;
    2. 执⾏有副作⽤的操作,如 API 请求和路由跳转;
    3. 调⽤⾮纯函数,如 Date.now()Math.random()
  • 共享的数据存储在 Store 里面(state)
  • 更新完的数据 Store 传给组建

安装 Rredux

 npm install Readux

Readux 的使用

创建 store 和制定 reducers 规则

src/store/index.js

import {createStore} from 'redux'


// 定义 state 初始化和修改规则
function createReducer(store=1,{type,payload=1}){
  // type: action 的修改规则,用于 switch 判断
  // paylod,当发起 dispatch 传进来的参数
  console.log(store, 'store')
  switch (type) {
    case 'ADD': // 加法规则
        return store+payload
      break;
      case 'MINUS': // 减法规则
        return store-payload
      break;
    default: // 默认导出规则
        return store
      break;
  }
}

const store = createStore(createReducer) // 定义 store 里面的修改规则

export default store
写一个组建

src/pages/ReaduxPage.js

import React, {Component} from 'react'
import store from "../store/index";
export default class ReaduxPage extends Component{constructor(){super()
  }
  render(){
    return(
      <div>
        <h3>
          ReaduxPage
        </h3>
        <div>
          获取到 store 里面设置的 state
          <p>{store.getState()}</p>
        </div>
        点击按钮,触发 dispatch
        <button onClick={()=>store.dispatch({type:'ADD'})}>
          点击增加
        </button>
      </div>
    )
  }
}

再或者改成,方法调用的方式

  add=()=>{store.dispatch({type:'ADD', payload:100})
  }

  // 定一个一个方法去使用,结果都是一样的都是去调用 dispatch
  <button onClick={this.add}>
    点击增加
  </button>

测试点击是否生效,我们在 store 中有进行打印。

说明点击数据修改了,dispatch 已经发送,但是页面没有重新渲染。
解决办法:redux 内部是有发布订阅 store.subscribe
在组建中,我们添加一个生命周期。

  // 在组建挂载完成这个组建中
  componentDidMount(){
    // 当 store 中数据发生改变时,发起订阅。store.subscribe(()=>{
      // 在数据发生改变的时候,强制页面渲染
      this.forceUpdate()})
  }

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

自己实现 Redux

了解了 Redux 的实现之后,现在来自己实现一个 Redux

首先 创建一个 createStore.js
目录结构:src 
  ├──Myredux  # 自己的 redux 文件
  ├──────index.js  # 导出文件
  ├──────createStore.js # createStore

/src/store/index.js 中,其实我们就引用了 redux 的 createStore,import {createStore} from 'redux'

首先在 createStore.js 中去创建三个使用到的方法
export default function createStore (){function getState(){return null}
  function dispatch(){return null}
  function subscribe(){return null}
  return{
    // 获取 state
    getState,
    // dispatch
    dispatch,
    // 订阅事件
    subscribe
  }
}

createStore 接收一个参数reducer;在调用使用 createStore 的时候是这样的:const store = createStore(createReducer) // 定义 store 里面的修改规则

完善 getState、dispatch、subscribe
export default function createStore (reducer){// console.log(reducer,'reducer')
    
  // 内部定义 state 初始值,let currentState = null;
  // 所有的监听记录下来做一个数组
  let currentListeners = []

  // getState:返回当前的 state
  function getState(){return currentState}
  // dispatch 接收一个 action: {type:'ADD', payload:''}, 改变 state
  function dispatch(action){
    // 调用传进来的 reducer 方法,获取到更新后的 newstate
    currentState = reducer(currentState,action)
    // 当数据修改完了之后,去发布订阅
    // 将监听里面的事件做一次执行
    currentListeners.forEach(lister=>lister())
  }
  // subscribe 接收一个监听函数 lister,监听肯定是存在多个
  function subscribe(lister){
    // 每加进来一个 lister,就 push 进去 currentListeners
    currentListeners.push(lister)
  }
  return{
    // 获取 state
    getState,
    // dispatch
    dispatch,
    // 订阅事件
    subscribe
  }
}
有订阅就必须要有 取消订阅

有订阅,必须要有取消订阅,组建都没有了,还在执行里面的方法,执行 this.forceUpdate 是会报错的.
订阅是在生命周期中去订阅的,取消就需要在 componentWillUnmount 中去销毁
在组建中src/pages/ReaduxPage.js

 // 在组建挂载完成这个组建中
 componentDidMount(){
   // 当 store 中数据发生改变时,发起订阅。this.unsubscribe = store.subscribe(()=>{
     // 在数据发生改变的时候,强制页面渲染
     this.forceUpdate()})
 }
 // 在组建销毁的时候
 componentWillUnmount(){
   // 如果有订阅
   if(this.unsubscribe){
     // 执行取消订阅
     this.unsubscribe()}
 }

在当前我们写的 createStore 中 subscribe 方法中,还需要在做取消订阅
修改 subscribe 方法

  // subscribe 接收一个监听函数 lister,监听肯定是存在多个
  function subscribe(lister){
    // 每加进来一个 lister,就 push 进去 currentListeners
    currentListeners.push(lister)
    // 返回一个取消订阅的方法
    // 最粗暴的方法就是,将 currentListeners 置空,当然你也可以选择用 filter 找到当前的订阅,之后进行删除
    return ()=> {currentListeners=[]}
  }
异步更新

目前我们只是完成了同步的状态更新,接下来写异步的更新

Redux 只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延迟,网络请求,需要中间件的支持,⽐如我们使⽤最简单的 redux-thunkredux-logger
中间件就是⼀个函数,对 store.dispatch ⽅法进行改造,在发出 Action 和执⾏ Reducer 这两步之间,添加了其他功能。

首先安装插件

 yarn add redux-thunk redux-logger

redux-logger 记录日志的中间件。

redux-thunk 的使用

首先,我们先用 rendux 导入来看下效果
src/store/index.js

  import {createStore, applyMiddleware} from 'redux'  // applyMiddleware 使用中间件
  import thunk from 'redux-thunk'
  import logger from 'redux-logger'
// 导出 store 时,加上插件 thunk
const store = createStore(createReducer, applyMiddleware(thunk)) // 定义 store 里面的修改规则

来测试导入的 thunk 是否生效,我们在组建中执行一个异步更新,调用 disipach
src/pages/ReaduxPage.js

  <button onClick={this.Asyadd}> 点击增加 </button>
 Asyadd=()=>{store.dispatch(()=>{setTimeout(() => {store.dispatch({type: 'ADD'})
     }, 1000);
   })
 }

or 或者这样写,因为中间件会判断你当前传进来的数据类型,不是一个 object,而是一个函数,将会给函数传入参数值(dispatch,getState)

 // 改写
 Asyadd=()=>{store.dispatch((dispatch, getState)=>{setTimeout(() => {dispatch({type: 'ADD'})
     }, 1000);
   })
 }

可以引入 logger 看一下,就是打印的日志`

applyMiddleware(thunk, logger)

自己实现 applyMiddleware

首先创建文件src/Myredux/applyMiddleware.js

export default function applyMiddleware(){}

src/Myredux/index.js中导入和导出

//  将 createStore 导入
import createStore from './createStore'
// 将 reudx 使用中间件的方法导入
import applyMiddleware from './applyMiddleware'

// 导出
export {
  createStore,
  applyMiddleware
}

在 store 中去使用我们自己的 redux

import {createStore,applyMiddleware} from '../Myredux'

等等等 …. 等一下,现在的页面,我们自己的写 redux 还没有实现,state 的初始值

所以我们的 createStore 还要进行修改一下。
在源码中,是这样进行处理的:在 createStore 中去执行一下 dispatch,type 的内容只要保持唯一性就可以了,源码是写的随机的字符串

  // 内部定义 state 初始值,let currentState;
// 在 dispatch 之后去手动执行一遍 dispatch,type 的值随便写
 dispatch({type:'AAAASAJKHSDFH'})

插曲结束


现在我们来实现,加强版的 dispatch,现在写的是只能接收一个对象。

有些许的绕这个思路,我后面放一个思维导图,尽量说详细一些。这块也是看了很久。
首先我们要明白,为什么要加强 dispatch。是因为原本 redux 的 dispatch 只能接收一个对象,而现在我们引入了 thunk,需要修改 dispatch,让它自它接收处理异步,promise

在 src/Myredux/createStore.js 中进行调整

export default function createStore (reducer, enhancer){
  // enhancer 加强版
  // 主要就是加强 dispatch
  if(enhancer){ // 首先判断 有没有 传入这个 redux 使用中间件的方法‘applyMiddleware’// 如果存在
    // 首先我们要明确,中间件其实是对 dispatch 进行了加强,所以还是需要用到 createStore,对它进行处理;dispatch 还是会用到 reducer 这个参数。// dispatch 二次包装
     return enhancer(createStore)(reducer) // 返回一个加强版的 store
  }

applyMiddleware

其实就做了一件事,就是返回新的 store,在这其中加强了 dispatch

src/Myredux/applyMiddleware.js

export default function applyMiddleware(...middleware){
  // ...middleware 接收的参数,就是中间件

  // 1. 首先明确返回值 是一个函数,因为我们需要获取到 createStore 里面的 dispatch
  return createStore=>reducer=>{ // 双箭头函数,就是少了一层 return 包裹,可以看下 ES6

    // 返回 store
    let store = createStore(reducer)
    // 获取到当前的 dispatch
    let dispatch =store.dispatch


    // 3. 来写一个加强的 dispatch
    ........
    
    // 2. 函数执行的结果,是返回新的 sotore 和加强版的 dispatch
    return {
      ...store,
      dispatch // 加强版本的 dispatch
    }
  }
}
applyMiddleware 完整版
export default function applyMiddleware(...middleware){
  // ...middleware 接收的参数,就是中间件

  // 1. 首先明确返回值 是一个函数,因为我们需要获取到 createStore 里面的 dispatch
  return createStore=>reducer=>{ // 双箭头函数,就是少了一层 return 包裹,可以看下 ES6

    // 返回 store
    let store = createStore(reducer)
    // 获取到当前的 dispatch, 原版的 dispatch
    let dispatch =store.dispatch


    // 3. 来写一个加强的 dispatch
    // 最终的目的还是要获取到 type,更新 state
        
    // 定义 mids 需要执行的 api,也就是需要用到的参数
    const midApi={
      getState: store.getState,
      // dispatch, 需要在包一层,dispatch 是派发方法的,每个对象都去派发方法,防止被相互污染
      dispatch:(actions, ...args)=>dispatch(actions, ...args)
    }


    // 为什么要将,传进来的参数连起来,因为每次需要去执行,而不能让用户自己手动执行
    // 将 middleware 连起来  (thunk, logger)
    // thunk 的执行需要 dispatch logger 日志的打印需要 oldstate 和 newstate
    const middlewareChain=middleware.map(mids=>mids(midApi)) // 将中间件进行遍历执行

    // 加强 dispatch
    dispatch = compose(...middlewareChain)(store.dispatch)
    
    // 2. 函数执行的结果,是返回新的 sotore 和加强版的 dispatch
    return {
      ...store,
      dispatch // 加强版本的 dispatch
    }
  }
}

// 函数聚合的方法
function compose(...funcs){if(!funcs.length){return args =>args}
  if(funcs.length===1){return funcs[0]
  }
  return funcs.reduce((a,b)=>(...args)=>a(b(...args)))
}

自己实现 redux-thunk

thunk 的源码也很简单就 14 行

function thunk({dispatch, getState}){
  //thunk 里面首先执行的可能是一个 dipatch,也有可能是一个 function,所以在不确定的情况下,我们将它定义为 next
  return next => action=>{
    // 第二种情况:是个回调函数
    console.log(next,'next') // 前面聚合的函数,compose()的聚合出来的结果, 目前测试就一个回调,可以理解为 dispatch
    console.log(action, 'action')
    if(typeof action === 'function'){return action(dispatch, getState)
    }

    //  第一种情况:如果传进来的是一个对象,那么就直接执行
    return next(action)
  }
}

点击异步更新 dispatch 是的打印结果

自己实现 redux-logger

function logger ({getState}){
// 在这里也可以获取到 dispatch 的,但是用不到,我们只需要用到 getState。// logger 主要是打印:执行的方法,和 oldstate 和 newstate
return next=> action=>{console.log(next,'next')
  console.log('------------')
  const preState= getState()
  console.log('preState',preState)
  console.log('执行的方法:' ,action.type)
  const returnValue = next(action)
  const nextState = getState()
  console.log(nextState)
  console.log('------------')    
  return returnValue
}
}

next 的理解:
不用去声明 next,是调用的的实参;在 compose 方法中的 reduce 的第一个参数(a);执行 reduce 的时候是一层套一层的执行;每一次进行 reduce 的循环,都会生成一个新的 next

自己实现 redux-promise

promise 中间件,就是需要在 dispatch 中去调用 promise

首先安装
yarn add redux-promise
在组建中执行:src/pages/ReaduxPage.js

  promiseMinus=()=>{
    store.dispatch(
      Promise.resolve({
        // 减法
        type:'MINUS',
        payload:100
      })
    )
  }
 <button onClick={this.promiseMinus}> promise 点 </button>

redux-promise 的实现,需要去引入两个库

 yarn add is-promise flux-standard-action

redux-promise 的实现

import isPromise from 'is-promise' // 判断是不是 promise 类型
import {isFSA} from 'flux-standard-action' // 判断是不是标准的 action 有 type,payload
function promise({dispatch}){
  return next=>action=>{if(!isFSA(action)){ // 判断是不是标准的 action
      // 再判断是不是标准的 Promise? 是 执行.then 方法:不是就直接执行
      return isPromise(action)? action.then(dispatch):next(dispatch)
    }
    // 是 promise
    // console.log(action)   -----{type: "MINUS", payload: 100}
    return isPromise(action.payload)?
    action.payload
    .then(res => dispatch({...action, payload:res}))
    .catch(error => {dispatch({...action, payload: error, error:true});
        return Promise.reject(error)
    })
    :next(dispatch)
  }
}

目前更新到这里 …..,最还是看下源码或者开发中对 reudx 已经很熟练了再来看,要不然很难理解,因为 reudx 有些绕。

退出移动版