redux_redux原理 - CSDN
redux 订阅
Redux由Dan Abramov在2015年创建的科技术语。是受2014年Facebook的Flux架构以及函数式编程语言Elm启发。很快,Redux因其简单易学体积小在短时间内成为最热门的前端架构。 展开全文
Redux由Dan Abramov在2015年创建的科技术语。是受2014年Facebook的Flux架构以及函数式编程语言Elm启发。很快,Redux因其简单易学体积小在短时间内成为最热门的前端架构。
信息
创建时间
2015年
发布人
Dan Abramov
属    性
前端架构
外文名
Redux
redux简介
Redux对于JavaScript应用而言是一个可预测状态的容器。换言之,它是一个应用数据流框架,而不是传统的像underscore.js或者AngularJs那样的库或者框架。Redux最主要是用作应用状态的管理。简言之,Redux用一个单独的常量状态树(对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用actions和reducers)。
收起全文
精华内容
参与话题
  • 本套课程让你轻松掌握redux,非常细致的讲解redux怎么应用,结合react巧妙使用,让你再也不用面对枯燥的文档,不知道如何解脱,一套教程让你彻底学会redux
  • 简单完全理解redux

    千次阅读 多人点赞 2020-09-28 16:12:39
    完全理解 redux 前言 记得开始接触 react 技术栈的时候,最难理解的地方就是 redux。全是新名词:reducer、store、dispatch、middleware 等等,我就理解 state 一个名词。 网上找的 redux 文章,要不有一本书的...

    完全理解 redux
    前言
    记得开始接触 react 技术栈的时候,最难理解的地方就是 redux。全是新名词:reducer、store、dispatch、middleware 等等,我就理解 state 一个名词。

    网上找的 redux 文章,要不有一本书的厚度,要不很玄乎,晦涩难懂,越看越觉得难,越看越怕,信心都没有了!

    花了很长时间熟悉 redux,慢慢的发现它其实真的很简单。本章不会把 redux 的各种概念,名词解释一遍,这样和其他教程没有任何区别,没有太大意义。我会带大家从零实现一个完整的 redux,让大家知其然,知其所以然。

    开始前,你必须知道一些事情:

    redux 和 react 没有关系,redux 可以用在任何框架中,忘掉 react。
    connect 不属于 redux,它其实属于 react-redux,请先忘掉它,下一章节,我们会介绍它。
    请一定先忘记 reducer、store、dispatch、middleware 等等这些名词。
    redux 是一个状态管理器。
    Let’s Go!

    状态管理器
    简单的状态管理器
    redux 是一个状态管理器,那什么是状态呢?状态就是数据,比如计数器中的 count。

     `let state = {   count: 1 }`
    

    我们来使用下状态

    console.log(state.count);
    

    我们来修改下状态

    state.count = 2;
    

    好了,现在我们实现了状态(计数)的修改和使用了。

    读者:你当我傻吗?你说的这个谁不知道?捶你?!

    笔者:哎哎哎,别打我!有话好好说!redux 核心就是这个呀!我们一步一步扩展开来嘛!

    当然上面的有一个很明显的问题:修改 count 之后,使用 count 的地方不能收到通知。我们可以使用发布-订阅模式来解决这个问题。

    /*------count 的发布订阅者实践------*/
    let state = {
      count: 1
    };
    let listeners = [];
    
    /*订阅*/
    function subscribe(listener) {
      listeners.push(listener);
    }
    
    function changeCount(count) {
      state.count = count;
      /*当 count 改变的时候,我们要去通知所有的订阅者*/
      for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i];
        listener();
      }
    }
    

    我们来尝试使用下这个简单的计数状态管理器。

    /来订阅一下,当 count 改变的时候,我要实时输出新的值/

    subscribe(() => {
      console.log(state.count);
    });
    

    /我们来修改下 state,当然我们不能直接去改 state 了,我们要通过 changeCount 来修改/

    changeCount(2);
    changeCount(3);
    changeCount(4);

    现在我们可以看到,我们修改 count 的时候,会输出相应的 count 值。

    现在有两个新的问题摆在我们面前

    这个状态管理器只能管理 count,不通用
    公共的代码要封装起来
    我们尝试来解决这个问题,把公共的代码封装起来

    const createStore = function (initState) {
      let state = initState;
      let listeners = [];
    
      /*订阅*/
      function subscribe(listener) {
        listeners.push(listener);
      }
    
      function changeState(newState) {
        state = newState;
        /*通知*/
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i];
          listener();
        }
      }
    
      function getState() {
        return state;
      }
    
      return {
        subscribe,
        changeState,
        getState
      }
    }
    

    我们来使用这个状态管理器管理多个状态 counter 和 info 试试

    let initState = {
      counter: {
        count: 0
      },
      info: {
        name: '',
        description: ''
      }
    }
    
    let store = createStore(initState);
    
    store.subscribe(() => {
      let state = store.getState();
      console.log(`${state.info.name}:${state.info.description}`);
    });
    store.subscribe(() => {
      let state = store.getState();
      console.log(state.counter.count);
    });
    
    store.changeState({
      ...store.getState(),
      info: {
        name: '前端九部',
        description: '我们都是前端爱好者!'
      }
    });
    
    store.changeState({
      ...store.getState(),
      counter: {
        count: 1
      }
    });
    

    到这里我们完成了一个简单的状态管理器。

    这里需要理解的是 createStore,提供了 changeState,getState,subscribe 三个能力。

    本小节完整源码见 demo-1

    有计划的状态管理器
    我们用上面的状态管理器来实现一个自增,自减的计数器。

    let initState = {
      count: 0
    }
    let store = createStore(initState);
    
    store.subscribe(() => {
      let state = store.getState();
      console.log(state.count);
    });
    /*自增*/
    store.changeState({
      count: store.getState().count + 1
    });
    /*自减*/
    store.changeState({
      count: store.getState().count - 1
    });
    /*我想随便改*/
    store.changeState({
      count: 'abc'
    });
    

    你一定发现了问题,count 被改成了字符串 abc,因为我们对 count 的修改没有任何约束,任何地方,任何人都可以修改。

    我们需要约束,不允许计划外的 count 修改,我们只允许 count 自增和自减两种改变方式!

    那我们分两步来解决这个问题

    制定一个 state 修改计划,告诉 store,我的修改计划是什么。
    修改 store.changeState 方法,告诉它修改 state 的时候,按照我们的计划修改。
    我们来设置一个 plan 函数,接收现在的 state,和一个 action,返回经过改变后的新的 state。

    /注意:action = {type:’’,other:’’}, action 必须有一个 type 属性/

    function plan(state, action) {
      switch (action.type) {
        case 'INCREMENT':
          return {
            ...state,
            count: state.count + 1
          }
        case 'DECREMENT':
          return {
            ...state,
            count: state.count - 1
          }
        default:
          return state;
      }
    }
    

    我们把这个计划告诉 store,store.changeState 以后改变 state 要按照我的计划来改。

    /*增加一个参数 plan*/
    const createStore = function (plan, initState) {
      let state = initState;
      let listeners = [];
    
      function subscribe(listener) {
        listeners.push(listener);
      }
    
      function changeState(action) {
        /*请按照我的计划修改 state*/  
        state = plan(state, action);
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i];
          listener();
        }
      }
    
      function getState() {
        return state;
      }
    
      return {
        subscribe,
        changeState,
        getState
      }
    }
    我们来尝试使用下新的 createStore 来实现自增和自减
    
    let initState = {
      count: 0
    }
    /*把plan函数*/
    let store = createStore(plan, initState);
    
    store.subscribe(() => {
      let state = store.getState();
      console.log(state.count);
    });
    /*自增*/
    store.changeState({
      type: 'INCREMENT'
    });
    /*自减*/
    store.changeState({
      type: 'DECREMENT'
    });
    /*我想随便改 计划外的修改是无效的!*/
    store.changeState({
      count: 'abc'
    });
    

    到这里为止,我们已经实现了一个有计划的状态管理器!

    我们商量一下吧?我们给 plan 和 changeState 改下名字好不好?**plan 改成 reducer,changeState 改成 dispatch!**不管你同不同意,我都要换,因为新名字比较厉害(其实因为 redux 是这么叫的)!

    本小节完整源码见 demo-2

    多文件协作
    reducer 的拆分和合并
    这一小节我们来处理下 reducer 的问题。啥问题?

    我们知道 reducer 是一个计划函数,接收老的 state,按计划返回新的 state。那我们项目中,有大量的 state,每个 state 都需要计划函数,如果全部写在一起会是啥样子呢?

    所有的计划写在一个 reducer 函数里面,会导致 reducer 函数及其庞大复杂。按经验来说,我们肯定会按组件维度来拆分出很多个 reducer 函数,然后通过一个函数来把他们合并起来。

    我们来管理两个 state,一个 counter,一个 info。

    let state = {
      counter: {
        count: 0
      },
      info: {
        name: '前端九部',
        description: '我们都是前端爱好者!'
      }
    }
    他们各自的 reducer
    
    /*counterReducer, 一个子reducer*/
    /*注意:counterReducer 接收的 state 是 state.counter*/
    function counterReducer(state, action) {
      switch (action.type) {
        case 'INCREMENT':
          return {
            count: state.count + 1
          }
        case 'DECREMENT':
          return {
            ...state,
            count: state.count - 1
          }
        default:
          return state;
      }
    }
    /*InfoReducer,一个子reducer*/
    /*注意:InfoReducer 接收的 state 是 state.info*/
    function InfoReducer(state, action) {
      switch (action.type) {
        case 'SET_NAME':
          return {
            ...state,
            name: action.name
          }
        case 'SET_DESCRIPTION':
          return {
            ...state,
            description: action.description
          }
        default:
          return state;
      }
    }
    那我们用 combineReducers 函数来把多个 reducer 函数合并成一个 reducer 函数。大概这样用
    
    const reducer = combineReducers({
        counter: counterReducer,
        info: InfoReducer
    });
    我们尝试实现下 combineReducers 函数
    
    function combineReducers(reducers) {
    
      /* reducerKeys = ['counter', 'info']*/
      const reducerKeys = Object.keys(reducers)
    
      /*返回合并后的新的reducer函数*/
      return function combination(state = {}, action) {
        /*生成的新的state*/
        const nextState = {}
    
        /*遍历执行所有的reducers,整合成为一个新的state*/
        for (let i = 0; i < reducerKeys.length; i++) {
          const key = reducerKeys[i]
          const reducer = reducers[key]
          /*之前的 key 的 state*/
          const previousStateForKey = state[key]
          /*执行 分 reducer,获得新的state*/
          const nextStateForKey = reducer(previousStateForKey, action)
    
          nextState[key] = nextStateForKey
        }
        return nextState;
      }
    }
    我们来尝试下 combineReducers 的威力吧
    
    const reducer = combineReducers({
      counter: counterReducer,
      info: InfoReducer
    });
    
    let initState = {
      counter: {
        count: 0
      },
      info: {
        name: '前端九部',
        description: '我们都是前端爱好者!'
      }
    }
    
    let store = createStore(reducer, initState);
    
    store.subscribe(() => {
      let state = store.getState();
      console.log(state.counter.count, state.info.name, state.info.description);
    });
    /*自增*/
    store.dispatch({
      type: 'INCREMENT'
    });
    
    /*修改 name*/
    store.dispatch({
      type: 'SET_NAME',
      name: '前端九部2号'
    });
    

    本小节完整源码见 demo-3

    state 的拆分和合并
    上一小节,我们把 reducer 按组件维度拆分了,通过 combineReducers 合并了起来。但是还有个问题, state 我们还是写在一起的,这样会造成 state 树很庞大,不直观,很难维护。我们需要拆分,一个 state,一个 reducer 写一块。

    这一小节比较简单,我就不卖关子了,用法大概是这样(注意注释)

    /* counter 自己的 state 和 reducer 写在一起*/
    let initState = {
      count: 0
    }
    function counterReducer(state, action) {
      /*注意:如果 state 没有初始值,那就给他初始值!!*/  
      if (!state) {
          state = initState;
      }
      switch (action.type) {
        case 'INCREMENT':
          return {
            count: state.count + 1
          }
        default:    
          return state;
      }
    }
    我们修改下 createStore 函数,增加一行 dispatch({ type: Symbol() })
    
    const createStore = function (reducer, initState) {
      let state = initState;
      let listeners = [];
    
      function subscribe(listener) {
        listeners.push(listener);
      }
    
      function dispatch(action) {
        state = reducer(state, action);
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i];
          listener();
        }
      }
    
      function getState() {
        return state;
      }
      /* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */
      dispatch({ type: Symbol() })
    
      return {
        subscribe,
        dispatch,
        getState
      }
    }
    

    我们思考下这行可以带来什么效果?

    createStore 的时候,用一个不匹配任何 type 的 action,来触发 state = reducer(state, action)
    因为 action.type 不匹配,每个子 reducer 都会进到 default 项,返回自己初始化的 state,这样就获得了初始化的 state 树了。
    你可以试试

    /*这里没有传 initState 哦 */
    const store = createStore(reducer);
    /这里看看初始化的 state 是什么/
    console.dir(store.getState());
    本小节完整源码见 demo-4

    到这里为止,我们已经实现了一个七七八八的 redux 啦!

    中间件 middleware
    中间件 middleware 是 redux 中最难理解的地方。但是我挑战一下用最通俗的语言来讲明白它。如果你看完这一小节,还没明白中间件是什么,不知道如何写一个中间件,那就是我的锅了!

    中间件是对 dispatch 的扩展,或者说重写,增强 dispatch 的功能!

    记录日志
    我现在有一个需求,在每次修改 state 的时候,记录下来 修改前的 state ,为什么修改了,以及修改后的 state。我们可以通过重写 store.dispatch 来实现,直接看代码

    const store = createStore(reducer);
    const next = store.dispatch;
    
    /*重写了store.dispatch*/
    store.dispatch = (action) => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    我们来使用下
    
    store.dispatch({
      type: 'INCREMENT'
    });
    日志输出为
    
    this state { counter: { count: 0 } }
    action { type: 'INCREMENT' }
    1
    next state { counter: { count: 1 } }
    现在我们已经实现了一个完美的记录 state 修改日志的功能!
    
    记录异常
    我又有一个需求,需要记录每次数据出错的原因,我们扩展下 dispatch
    
    const store = createStore(reducer);
    const next = store.dispatch;
    
    store.dispatch = (action) => {
      try {
        next(action);
      } catch (err) {
        console.error('错误报告: ', err)
      }
    }
    

    这样每次 dispatch 出异常的时候,我们都会记录下来。

    多中间件的合作
    我现在既需要记录日志,又需要记录异常,怎么办?当然很简单了,两个函数合起来呗!

    store.dispatch = (action) => {
      try {
        console.log('this state', store.getState());
        console.log('action', action);
        next(action);
        console.log('next state', store.getState());
      } catch (err) {
        console.error('错误报告: ', err)
      }
    }
    

    如果又来一个需求怎么办?接着改 dispatch 函数?那再来10个需求呢?到时候 dispatch 函数肯定庞大混乱到无法维护了!这个方式不可取呀!

    我们需要考虑如何实现扩展性很强的多中间件合作模式。

    我们把 loggerMiddleware 提取出来

    const store = createStore(reducer);
    const next = store.dispatch;
    
    const loggerMiddleware = (action) => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    
    store.dispatch = (action) => {
      try {
        loggerMiddleware(action);
      } catch (err) {
        console.error('错误报告: ', err)
      }
    }
    我们把 exceptionMiddleware 提取出来
    
    const exceptionMiddleware = (action) => {
      try {
        /*next(action)*/
        loggerMiddleware(action);
      } catch (err) {
        console.error('错误报告: ', err)
      } 
    }
    store.dispatch = exceptionMiddleware;
    现在的代码有一个很严重的问题,就是 exceptionMiddleware 里面写死了 loggerMiddleware,我们需要让 next(action)变成动态的,随便哪个中间件都可以
    
    const exceptionMiddleware = (next) => (action) => {
      try {
        /*loggerMiddleware(action);*/
        next(action);
      } catch (err) {
        console.error('错误报告: ', err)
      } 
    }
    /*loggerMiddleware 变成参数传进去*/
    store.dispatch = exceptionMiddleware(loggerMiddleware);
    

    同样的道理,loggerMiddleware 里面的 next 现在恒等于 store.dispatch,导致 loggerMiddleware 里面无法扩展别的中间件了!我们也把 next 写成动态的

    const loggerMiddleware = (next) => (action) => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    

    到这里为止,我们已经探索出了一个扩展性很高的中间件合作模式!

    const store = createStore(reducer);
    const next = store.dispatch;
    
    const loggerMiddleware = (next) => (action) => {
      console.log('this state', store.getState());
      console.log('action', action);
      next(action);
      console.log('next state', store.getState());
    }
    
    const exceptionMiddleware = (next) => (action) => {
      try {
        next(action);
      } catch (err) {
        console.error('错误报告: ', err)
      }
    }
    
    store.dispatch = exceptionMiddleware(loggerMiddleware(next));
    

    这时候我们开开心心的新建了一个 loggerMiddleware.js,一个exceptionMiddleware.js文件,想把两个中间件独立到单独的文件中去。会碰到什么问题吗?

    loggerMiddleware 中包含了外部变量 store,导致我们无法把中间件独立出去。那我们把 store 也作为一个参数传进去好了~

    const store = createStore(reducer);
        const next  = store.dispatch;
        
        const loggerMiddleware = (store) => (next) => (action) => {
          console.log('this state', store.getState());
          console.log('action', action);
          next(action);
          console.log('next state', store.getState());
        }
        
        const exceptionMiddleware = (store) => (next) => (action) => {
          try {
            next(action);
          } catch (err) {
            console.error('错误报告: ', err)
          }
        }
        
        const logger = loggerMiddleware(store);
        const exception = exceptionMiddleware(store);
        store.dispatch = exception(logger(next));
    

    到这里为止,我们真正的实现了两个可以独立的中间件啦!

    现在我有一个需求,在打印日志之前输出当前的时间戳。用中间件来实现!

    const timeMiddleware = (store) => (next) => (action) => {
      console.log('time', new Date().getTime());
      next(action);
    }
    
    ...
    const time = timeMiddleware(store);
    store.dispatch = exception(time(logger(next)))
    

    ;
    本小节完整源码见 demo-6

    中间件使用方式优化
    上一节我们已经完全实现了正确的中间件!但是中间件的使用方式不是很友好

    import loggerMiddleware from './middlewares/loggerMiddleware';
    import exceptionMiddleware from './middlewares/exceptionMiddleware';
    import timeMiddleware from './middlewares/timeMiddleware';
    
    ...
    
    const store = createStore(reducer);
    const next = store.dispatch;
    
    const logger = loggerMiddleware(store);
    const exception = exceptionMiddleware(store);
    const time = timeMiddleware(store);
    store.dispatch = exception(time(logger(next)));
    

    其实我们只需要知道三个中间件,剩下的细节都可以封装起来!我们通过扩展 createStore 来实现!

    先来看看期望的用法

    /*接收旧的 createStore,返回新的 createStore*/
    const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);
    
    /*返回了一个 dispatch 被重写过的 store*/
    const store = newCreateStore(reducer);
    实现 applyMiddleware
    
    const applyMiddleware = function (...middlewares) {
      /*返回一个重写createStore的方法*/
      return function rewriteCreateStoreFunc(oldCreateStore) {
         /*返回重写后新的 createStore*/
        return function newCreateStore(reducer, initState) {
          /*1. 生成store*/
          const store = oldCreateStore(reducer, initState);
          /*给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/
          /* const chain = [exception, time, logger]*/
          const chain = middlewares.map(middleware => middleware(store));
          let dispatch = store.dispatch;
          /* 实现 exception(time((logger(dispatch))))*/
          chain.reverse().map(middleware => {
            dispatch = middleware(dispatch);
          });
    
          /*2. 重写 dispatch*/
          store.dispatch = dispatch;
          return store;
        }
      }
    }
    

    让用户体验美好
    现在还有个小问题,我们有两种 createStore 了

    /*没有中间件的 createStore*/
    import { createStore } from './redux';
    const store = createStore(reducer, initState);
    
    /*有中间件的 createStore*/
    const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
    const newCreateStore = rewriteCreateStoreFunc(createStore);
    const store = newCreateStore(reducer, initState);
    为了让用户用起来统一一些,我们可以很简单的使他们的使用方式一致,我们修改下 createStore 方法
    
    const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
        /*如果有 rewriteCreateStoreFunc,那就采用新的 createStore */
        if(rewriteCreateStoreFunc){
           const newCreateStore =  rewriteCreateStoreFunc(createStore);
           return newCreateStore(reducer, initState);
        }
        /*否则按照正常的流程走*/
        ...
    }
    最终的用法
    
    const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
    
    const store = createStore(reducer, initState, rewriteCreateStoreFunc);
    

    本小节完整源码见 demo-7

    完整的 redux
    退订
    不能退订的订阅都是耍流浪!我们修改下 store.subscribe 方法,增加退订功能

      function subscribe(listener) {
        listeners.push(listener);
        return function unsubscribe() {
          const index = listeners.indexOf(listener)
          listeners.splice(index, 1)
        }
      }
    

    使用

    const unsubscribe = store.subscribe(() => {
      let state = store.getState();
      console.log(state.counter.count);
    });
    /*退订*/
    
    unsubscribe()
    

    中间件拿到的store
    现在的中间件拿到了完整的 store,他甚至可以修改我们的 subscribe 方法,按照最小开放策略,我们只用把 getState 给中间件就可以了!因为我们只允许你用 getState 方法!

    修改下 applyMiddleware 中给中间件传的 store

    /*const chain = middlewares.map(middleware => middleware(store));*/
    const simpleStore = { getState: store.getState };
    const chain = middlewares.map(middleware => middleware(simpleStore));
    compose
    我们的 applyMiddleware 中,把 [A, B, C] 转换成 A(B(C(next))),是这样实现的
    
    const chain = [A, B, C];
    let dispatch = store.dispatch;
    chain.reverse().map(middleware => {
       dispatch = middleware(dispatch);
    });
    

    redux 提供了一个 compose 方式,可以帮我们做这个事情

    const chain = [A, B, C]; dispatch = compose(...chain)(store.dispatch)

    看下他是如何实现的

    export default function compose(...funcs) {
      if (funcs.length === 1) {
        return funcs[0]
      }
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    

    当然 compose 函数对于新人来说可能比较难理解,你只需要他是做什么的就行啦!

    省略initState
    有时候我们创建 store 的时候不传 initState,我们怎么用?

    const store = createStore(reducer, {}, rewriteCreateStoreFunc);
    

    redux 允许我们这样写

    const store = createStore(reducer, rewriteCreateStoreFunc);
    我们仅需要改下 createStore 函数,如果第二个参数是一个object,我们认为他是 initState,如果是 function,我们就认为他是 rewriteCreateStoreFunc。

    function craeteStore(reducer, initState, rewriteCreateStoreFunc){
        if (typeof initState === 'function'){
        rewriteCreateStoreFunc = initState;
        initState = undefined;
      }
      ...
    }
    2 行代码的 replaceReducer
    reducer 拆分后,和组件是一一对应的。我们就希望在做按需加载的时候,reducer也可以跟着组件在必要的时候再加载,然后用新的 reducer 替换老的 reducer。
    
    const createStore = function (reducer, initState) {
      ...
      function replaceReducer(nextReducer) {
        reducer = nextReducer
        /*刷新一遍 state 的值,新来的 reducer 把自己的默认状态放到 state 树上去*/
        dispatch({ type: Symbol() })
      }
      ...
      return {
        ...
        replaceReducer
      }
    }
    

    我们来尝试使用下

    const reducer = combineReducers({
      counter: counterReducer
    });
    const store = createStore(reducer);
    
    /*生成新的reducer*/
    const nextReducer = combineReducers({
      counter: counterReducer,
      info: infoReducer
    });
    /*replaceReducer*/
    store.replaceReducer(nextReducer);
    replaceReducer 示例源码见 demo-5
    
    bindActionCreators
    

    bindActionCreators 我们很少很少用到,一般只有在 react-redux 的 connect 实现中用到。

    他是做什么的?他通过闭包,把 dispatch 和 actionCreator 隐藏起来,让其他地方感知不到 redux 的存在。

    我们通过普通的方式来 隐藏 dispatch 和 actionCreator 试试,注意最后两行代码

    const reducer = combineReducers({
      counter: counterReducer,
      info: infoReducer
    });
    const store = createStore(reducer);
    
    /*返回 action 的函数就叫 actionCreator*/
    function increment() {
      return {
        type: 'INCREMENT'
      }
    }
    
    function setName(name) {
      return {
        type: 'SET_NAME',
        name: name
      }
    }
    
    const actions = {
      increment: function () {
        return store.dispatch(increment.apply(this, arguments))
      },
      setName: function () {
        return store.dispatch(setName.apply(this, arguments))
      }
    }
    /*注意:我们可以把 actions 传到任何地方去*/
    /*其他地方在实现自增的时候,根本不知道 dispatch,actionCreator等细节*/
    actions.increment(); /*自增*/
    actions.setName('九部威武'); /*修改 info.name*/
    

    我眼睛一看,这个 actions 生成的时候,好多公共代码,提取一下

    const actions = bindActionCreators({ increment, setName }, store.dispatch);
    来看一下 bindActionCreators 的源码,超级简单(就是生成了刚才的 actions)

    /核心的代码在这里,通过闭包隐藏了 actionCreator 和 dispatch/

    function bindActionCreator(actionCreator, dispatch) {
      return function () {
        return dispatch(actionCreator.apply(this, arguments))
      }
    }
    
    /* actionCreators 必须是 function 或者 object */
    export default function bindActionCreators(actionCreators, dispatch) {
      if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
      }
    
      if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error()
      }
    
      const keys = Object.keys(actionCreators)
      const boundActionCreators = {}
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
          boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
      }
      return boundActionCreators
    }
    

    bindActionCreators 示例源码见 demo-8

    大功告成
    完整的示例源码见 demo-9,你可以和 redux 源码做一下对比,你会发现,我们已经实现了 redux 所有的功能了。

    当然,为了保证代码的理解性,我们少了一些参数验证。比如 createStore(reducer)的参数 reducer 必须是 function 等等。

    最佳实践
    纯函数
    什么是纯函数?

    纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。

    通俗来讲,就两个要素

    相同的输入,一定会得到相同的输出
    不会有 “触发事件”,更改输入参数,依赖外部参数,打印 log 等等副作用
    /不是纯函数,因为同样的输入,输出结果不一致/

    function a( count ){
       return count + Math.random();
    }
    
    /*不是纯函数,因为外部的 arr 被修改了*/
    function b( arr ){
        return arr.push(1);
    }
    let arr = [1, 2, 3];
    b(arr);
    console.log(arr); //[1, 2, 3, 1]
    
    /*不是纯函数,以为依赖了外部的 x*/
    let x = 1;
    function c( count ){
        return count + x;
    }
    

    我们的 reducer 计划函数,就必须是一个纯函数!

    只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

    总结
    到了最后,我想把 redux 中关键的名词列出来,你每个都知道是干啥的吗?

    createStore

    创建 store 对象,包含 getState, dispatch, subscribe, replaceReducer

    reducer

    reducer 是一个计划函数,接收旧的 state 和 action,生成新的 state

    action

    action 是一个对象,必须包含 type 字段

    dispatch

    dispatch( action ) 触发 action,生成新的 state

    subscribe

    实现订阅功能,每次触发 dispatch 的时候,会执行订阅函数

    combineReducers

    多 reducer 合并成一个 reducer

    replaceReducer

    替换 reducer 函数

    middleware

    扩展 dispatch 函数!

    你再看 redux 流程图,是不是大彻大悟了?

    redux 流程图在这里插入图片描述

    展开全文
  • Redux 快速上手指南

    万次阅读 2019-07-02 09:13:32
    Redux简介 如果要用一句话来概括Redux,那么可以使用官网的这句话:Redux是针对JavaScript应用的可预测状态容器。此句话虽然简单,但包含了以下几个含义: 可预测性(predictable): 因为Redux用了reducer与纯函数...

    Redux简介

    如果要用一句话来概括Redux,那么可以使用官网的这句话:**Redux是针对JavaScript应用的可预测状态容器。**此句话虽然简单,但包含了以下几个含义:

    • 可预测性(predictable): 因为Redux用了reducer与纯函数(pure function)的概念,每个新的state都会由旧的state建来一个全新的state。因而所有的状态修改都是"可预测的"。
    • 状态容器(state container): state是集中在单一个对象树状结构下的单一store,store即是应用程序领域(app domain)的状态集合。
    • JavaScript应用: 这说明Redux并不是单指设计给React用的,它是独立的一个函数库,可通用于各种JavaScript应用。

    Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据可以在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动。

    具体的模型图如下图所示:
    这里写图片描述

    为了说明整个模型的运作流程,首先我们需要弄清Redux模型中的几个组成对象:action 、reducer、store。

    • action:官方的解释是action是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源;要通过本地或远程组件更改状态,需要分发一个action;
    • reducer:action发出了做某件事的请求,只是描述了要做某件事,并没有去改变state来更新界面,reducer就是根据action的type来处理不同的事件;
    • store:store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态。

    在Redux中,所有的数据(比如state)被保存在一个被称为store的容器中 ,在一个应用程序中只能有一个store对象。当一个store接收到一个action,它将把这个action代理给相关的reducer。reducer是一个纯函数,它可以查看之前的状态,执行一个action并且返回一个新的状态。

    其实说到Redux,就不得不提到Flux,不论是Flux或其他以Flux架构为基础延伸发展的函数库(Alt、Reflux、Redux…)都是为了要解决同一个问题:App state的管理。

    不管什么应用程序都需要有App state(应用程序状态),不论是在一个需要用户登录的应用,要有全局的记录着用户登录的状态,或是在应用程序中不同操作介面(组件)或各种功能上的数据沟通,都需要用到它。

    属性React.js的同学都知道,React被设计为一个MVC架构中的View(视图)的函数库,但实际上它可以作的事情比MVC中的View(视图)还要更多,它甚至可以作类似Model(模型)或Controller(控制器)的事情。

    同时,在React中的组件是无法直接更动state(状态)的包含值,要透过setState方法来进行更动,这有很大的原因是为了Virtual DOM(虚拟DOM)的所设计,这是其中一点。另外在组件的树状阶层结构,父组件(拥有者)与子组件(被拥有者)的关系上,子组件是只能由父组件以props(属性)来传递属性值,子组件自己本身无法更改自己的props,这也是为什么一开始在学习React时,都会看到大部份的例子只有在最上层的组件有state,而且都是由它来负责进行当数据改变时的重新渲染工作,子组件通常只有负责呈现数据。

    当然,有一个很技巧性的方式,是把父组件中的方法声明由props传递给子组件,然后在子组件触发事件时,调用这个父组件的方法,以此来达到子组件对父组件的沟通,间接来更动父组件中的state。不过这个作法并不直觉,需要事先规范好两边的方法。在简单的应用程序中,这沟通方式还可行,但如果是在有复杂的组件嵌套阶层结构时,例如层级很多或是不同树状结构中的子组件要互相沟通时,这个作法是派不上用场的。

    在复杂的组件树状结构时,唯一能作的方式,就是要将整个应用程序的数据整合在一起,然后独立出来,也就是整个应用程序领域的数据部份。另外还需要对于数据的所有更动方式,也要独立出来。这两者组合在一起,就是称之为"应用程序领域的状态",为了区分组件中的状态(state),这个作为应用程序领域的持久性数据集合,会被称为store(存储)。

    说明:以上两段来自慕课网对Redux的总结。

    单向数据流是Flux架构的核心设计,其流程示意图如下:
    这里写图片描述

    这个数据流的位于最中心的设计是一个AppDispatcher(应用发送器),你可以把它想成是个发送中心,不论来自组件何处的动作都需要经过它来发送。每个store会在AppDispatcher上注册它自己,提供一个callback(回调),当有动作(action)发生时,AppDispatcher(应用发送器)会用这个回调函数通知store。

    由于每个Action(动作)只是一个单纯的对象,包含actionType(动作类型)与数据(通常称为payload),我们会另外需要Action Creator(动作创建器),它们是一些辅助函数,除了创建动作外也会把动作传给Dispatcher(发送器),也就是调用Dispatcher(发送器)中的dispatch方法。

    Dispatcher(发送器)的用途就是把接收到的actionType与数据(payload),广播给所有注册的callbacks。它这个设计并非是独创的,这在设计模式中类似于pub-sub(发布-订阅)系统,Dispatcher则是类似Eventbus的概念。

    Redux配置

    配置Redux开发环境的最快方法是使用create-react-app工具。在开始之前,确保已经安装并更新了nodejs、npm和yarn。下面以生成一个redux-shopping项目并安装Redux为例。

    如果没有安装create-react-app工具,请使用下面的命令先执行安装操作。

    npm install -g create-react-app
    

    然后,在使用下面的命令创建redux-shopping项目。

    create-react-app redux-shopping
    
    cd redux-shopping
    yarn add redux
    

    首先,删除src文件夹中除index.js以外的所有文件。打开index.js,删除所有代码,键入以下内容:

    import { createStore } from "redux";
    
    const reducer = function(state, action) {
      return state;
    }
    
    const store = createStore(reducer);
    

    上面代码的意思是:

    1. 从redux包中引入createStore()方法;
    2. 创建了一个名为reducer的方法,第一个参数state是当前保存在store中的数据,第二个参数action是一个容器,用于:
      type - 一个简单的字符串常量,例如ADD, UPDATE, DELETE等。
      payload - 用于更新状态的数据。
    3. 创建一个Redux存储区,它只能使用reducer作为参数来构造。存储在Redux存储区中的数据可以被直接访问,但只能通过提供的reducer进行更新。

    目前,state为undefined或null,要解决这个问题,需要分配一个默认的值给state,使其成为一个空数组。例如:

    const reducer = function(state=[], action) {
      return state;
    }
    

    目前我们创建的reducer是通用的,那么我们如何使用多个reducer呢?此时我们可以使用Redux包中提供的combineReducers函数。做如下内容修改:

    // src/index.js
    
    import { createStore } from "redux";
    import { combineReducers } from 'redux';
    
    const productsReducer = function(state=[], action) {
      return state;
    }
    
    const cartReducer = function(state=[], action) {
      return state;
    }
    
    const allReducers = {
      products: productsReducer,
      shoppingCart: cartReducer
    }
    
    const rootReducer = combineReducers(allReducers);
    
    let store = createStore(rootReducer);
    

    接下来,我们将为reducer定义一些测试数据。

    // src/index.jsconst initialState = {
      cart: [
        {
          product: 'bread 700g',
          quantity: 2,
          unitCost: 90
        },
        {
          product: 'milk 500ml',
          quantity: 1,
          unitCost: 47
        }
      ]
    }
    
    const cartReducer = function(state=initialState, action) {
      return state;
    }
    
    …
    
    let store = createStore(rootReducer);
    
    console.log("initial state: ", store.getState());
    

    接下来,我们可以在终端中执行npm start或者yarn start来运行dev服务器,并在控制台中查看state。
    这里写图片描述

    现在,我们的cartReducer什么也没做,但它应该在Redux的存储区中管理购物车商品的状态。我们需要定义添加、更新和删除商品的操作(action)。此时我们可以做如下的一些定义:

    // src/index.jsconst ADD_TO_CART = 'ADD_TO_CART';
    
    const cartReducer = function(state=initialState, action) {
      switch (action.type) {
        case ADD_TO_CART: {
          return {
            ...state,
            cart: [...state.cart, action.payload]
          }
        }
    
        default:
          return state;
      }
    }

    我们继续来分析一下代码。一个reducer需要处理不同的action类型,因此我们需要一个SWITCH语句。当一个ADD_TO_CART类型的action在应用程序中分发时,switch中的代码将处理它。

    接下来,我们将定义一个action,作为store.dispatch()的一个参数。action是一个Javascript对象,有一个必须的type和可选的payload。我们在cartReducer函数后定义一个:

    …
    function addToCart(product, quantity, unitCost) {
      return {
        type: ADD_TO_CART,
        payload: { product, quantity, unitCost }
      }
    }

    在这里,我们定义了一个函数,返回一个JavaScript对象。在我们分发消息之前,我们添加一些代码,让我们能够监听store事件的更改。

    …
    let unsubscribe = store.subscribe(() =>
      console.log(store.getState())
    );
    
    unsubscribe();
    

    接下来,我们通过分发消息到store来向购物车中添加商品。将下面的代码添加在unsubscribe()之前:

    …
    store.dispatch(addToCart('Coffee 500gm', 1, 250));
    store.dispatch(addToCart('Flour 1kg', 2, 110));
    store.dispatch(addToCart('Juice 2L', 1, 250));
    

    下面是整个index.js文件的源码:

    // src/index.js
    
    import { createStore } from "redux";
    import { combineReducers } from 'redux';
    
    const productsReducer = function(state=[], action) {
      return state;
    }
    
    const initialState = {
      cart: [
        {
          product: 'bread 700g',
          quantity: 2,
          unitCost: 90
        },
        {
          product: 'milk 500ml',
          quantity: 1,
          unitCost: 47
        }
      ]
    }
    
    const ADD_TO_CART = 'ADD_TO_CART';
    
    const cartReducer = function(state=initialState, action) {
      switch (action.type) {
        case ADD_TO_CART: {
          return {
            ...state,
            cart: [...state.cart, action.payload]
          }
        }
    
        default:
          return state;
      }
    }
    
    function addToCart(product, quantity, unitCost) {
      return {
        type: ADD_TO_CART,
        payload: {
          product,
          quantity,
          unitCost
        }
      }
    }
    
    const allReducers = {
      products: productsReducer,
      shoppingCart: cartReducer
    }
    
    const rootReducer = combineReducers(allReducers);
    
    let store = createStore(rootReducer);
    
    console.log("initial state: ", store.getState());
    
    let unsubscribe = store.subscribe(() =>
      console.log(store.getState())
    );
    
    store.dispatch(addToCart('Coffee 500gm', 1, 250));
    store.dispatch(addToCart('Flour 1kg', 2, 110));
    store.dispatch(addToCart('Juice 2L', 1, 250));
    
    unsubscribe();
    

    保存代码后,Chrome会自动刷新,可以在控制台中确认新的商品已经添加了。
    这里写图片描述

    代码拆分

    会发现,index.js中的代码逐渐变得冗杂。所以,接下来我们对上面的项目进行一个组织拆分,使之成为Redux项目。首先,在src文件夹中创建一下文件和文件夹,文件结构如下:

    src/
    ├── actions
    │ └── cart-actions.js
    ├── index.js
    ├── reducers
    │ ├── cart-reducer.js
    │ ├── index.js
    │ └── products-reducer.js
    └── store.js
    

    然后,我们把index.js中的代码进行整理:

    // src/actions/cart-actions.js
    
    export const ADD_TO_CART = 'ADD_TO_CART';
    
    export function addToCart(product, quantity, unitCost) {
      return {
        type: ADD_TO_CART,
        payload: { product, quantity, unitCost }
      }
    }
    
    // src/reducers/products-reducer.js
    
    export default function(state=[], action) {
      return state;
    }
    
    // src/reducers/cart-reducer.js
    
    import  { ADD_TO_CART }  from '../actions/cart-actions';
    
    const initialState = {
      cart: [
        {
          product: 'bread 700g',
          quantity: 2,
          unitCost: 90
        },
        {
          product: 'milk 500ml',
          quantity: 1,
          unitCost: 47
        }
      ]
    }
    
    export default function(state=initialState, action) {
      switch (action.type) {
        case ADD_TO_CART: {
          return {
            ...state,
            cart: [...state.cart, action.payload]
          }
        }
    
        default:
          return state;
      }
    }
    
    // src/reducers/index.js
    
    import { combineReducers } from 'redux';
    import productsReducer from './products-reducer';
    import cartReducer from './cart-reducer';
    
    const allReducers = {
      products: productsReducer,
      shoppingCart: cartReducer
    }
    
    const rootReducer = combineReducers(allReducers);
    
    export default rootReducer;
    
    // src/store.js
    
    import { createStore } from "redux";
    import rootReducer from './reducers';
    
    let store = createStore(rootReducer);
    
    export default store;
    
    // src/index.js
    
    import store from './store.js';
    import { addToCart }  from './actions/cart-actions';
    
    console.log("initial state: ", store.getState());
    
    let unsubscribe = store.subscribe(() =>
      console.log(store.getState())
    );
    
    store.dispatch(addToCart('Coffee 500gm', 1, 250));
    store.dispatch(addToCart('Flour 1kg', 2, 110));
    store.dispatch(addToCart('Juice 2L', 1, 250));
    
    unsubscribe();
    

    整理完代码之后,程序依然会正常运行。现在我们来添加修改和删除购物车中商品的逻辑。修改cart-actions.js和cart-reducer.js文件:

    // src/reducers/cart-actions.js
    …
    export const UPDATE_CART = 'UPDATE_CART';
    export const DELETE_FROM_CART = 'DELETE_FROM_CART';
    …
    export function updateCart(product, quantity, unitCost) {
      return {
        type: UPDATE_CART,
        payload: {
          product,
          quantity,
          unitCost
        }
      }
    }
    
    export function deleteFromCart(product) {
      return {
        type: DELETE_FROM_CART,
        payload: {
          product
        }
      }
    }
    
    // src/reducers/cart-reducer.js
    …
    export default function(state=initialState, action) {
      switch (action.type) {
        case ADD_TO_CART: {
          return {
            ...state,
            cart: [...state.cart, action.payload]
          }
        }
    
        case UPDATE_CART: {
          return {
            ...state,
            cart: state.cart.map(item => item.product === action.payload.product ? action.payload : item)
          }
        }
    
        case DELETE_FROM_CART: {
          return {
            ...state,
            cart: state.cart.filter(item => item.product !== action.payload.product)
          }
        }
    
        default:
          return state;
      }
    }
    

    最后,我们在index.js中分发这两个action:

    // src/index.js// Update Cart
    store.dispatch(updateCart('Flour 1kg', 5, 110));
    
    // Delete from Cart
    store.dispatch(deleteFromCart('Coffee 500gm'));

    使用Redux工具进行调试

    Redux拥有很多第三方的调试工具,可用于分析代码和修复bug。最受欢迎的是time-travelling tool,即redux-devtools-extension。设置它只需要三个步骤。

    1. 首先,在Chrome中安装Redux Devtools扩展;
    2. 然后,在运行Redux应用程序的终端里使用Ctrl+C停止服务器。并用npm或yarn安装redux-devtools-extension包;
    yarn add redux-devtools-extension
    

    一旦安装完成,我们对store.js稍作修改都会反映到结果上。例如,我们还可以把src/index.js中日志相关的代码删除掉。返回Chrome,右键单击该工具的图标,打开Redux DevTools面板。
    这里写图片描述

    这里写图片描述

    可以看到,Redux Devtools很强大。你可以在action, state和diff(方法差异)之间切换。选择左侧面板上的不同action,观察状态树的变化,你还可以通过进度条来播放actions序列。

    集成React

    如果你的项目使用的是React,那么Redux可以很方便的与React集成。

    1. 首先,停止服务器,并安装react-redux包,安装的命名如下:
    yarn add react-redux
    
    1. 接下来,在index.js中加入React代码。我们还将使用Provider类将React应用程序包装在Redux容器中;
    // src/index.jsimport React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    
    const App = <h1>Redux Shopping Cart</h1>;
    
    ReactDOM.render(
      <Provider store={store}>
        { App }
      </Provider> ,
      document.getElementById('root')
    );

    目前,已经完成了集成的第一部分。可以启动服务器以查看效果。第二部分涉及到使用刚刚安装的react-redux包中的几个方法。通过这些方法将React组件与Redux的store和action相关联。此外,还可以使用Express和Feathers这样的框架来设置API。

    在Redux中,我们还可以安装其他一些包,比如axios等。我们React组件的state将由Redux处理,确保所有组件与数据库API的同步。想要查看更多的内容,可以访问下面的链接Build a CRUD App Using React, Redux and FeathersJS

    展开全文
  • Redux

    2019-01-04 19:38:18
    Redux是为了解决应用程序状态(State)管理而提出的一种解决方案。对于应用开发来讲,UI上显示的数据、控件状态、登陆状态、数据加载画面的不同状态等等全部可以看作状态。 Redux 的三个概念:Reducer、Action、...

    转载:https://www.cnblogs.com/tarena/p/8473961.html

    Redux是为了解决应用程序状态(State)管理而提出的一种解决方案。对于应用开发来讲,UI上显示的数据、控件状态、登陆状态、数据加载画面的不同状态等等全部可以看作状态。

    Redux 的三个概念:Reducer、Action、Store。

    Store 一般负责:保存应用状态、提供访问状态的方法、派发Action的方法以及对于状态订阅者的注册和取消等。(可以理解成内存数据库)。

    Reducer 其实就是用来维护状态的。reducer就是对数组元素进行累加计算成为一个值。(可以理解成数据库表,但这种说法不太精确)。

    Action 在Redux规范中,所有的会引发状态更新的交互行为都必须通过一个显性定义的Action来进行。(Reducer和Store之间的通信靠Action)。

    注意:

    export interface Action {
    
      type: string;
    
      payload?: any; // 这个值可有可无
    
    }

    ngrx是一套利用RxJS的类库,其中@ngrx/store就是基于Redux规范制定的Angular2框架。

    @ngrx/store

    RxJS 基于Redux的设计思想,为Angular应用提供强有力的状态管理工具。

    @ngrx/store是一个旨在提高写性能的控制状态的容器,在angular的应用程序上是一致的。

    核心:

    1. State是一个不可变的数据结构
    2. Action描述State的改变
    3. Reducer(纯函数)拿到下一个State和之前的State来计算一个新的State
    4. 通过Store访问State,一个可观测state和一个actions观察者

    安装使用:npm install @ngrx/store --save 或 yarn add @ngrx/store

    State 与 Angular的Component的关系

    1. State驱动Component进行渲染(this.store.dispatch)
    2. Component发action来改变State

    @ngrx/effects

    @ngrx/effects 提供一套API(装饰器@Effect( )和Action)来帮助检查Store.dispatch( )出来的Action。将特定类型的Action过滤出来进行处理,监听特定Action,当发现特定的Action发出之后,自动执行某些操作,然后将处理的结果重新发送回store中。

    核心:

    1. 监听派发出来(@ngrx/store 的Store.dispatch)的Action
    2. 隔离业务和组件(Component只通过select state 和 dispatch actions即可)
    3. 提供新的reducer state(基于网络请求、web socket 消息 或 time事件驱动[定时刷新])

    安装使用:npm install @ngrx/effects --save 或 yarn add @ngrx/effects

    Effects 通常注入到service类

     

    注册Effect

    EffectsModule.forRoot( ) 必须在根模块下注册,如果不需要注册任何根级别的Effect,可以Provider一个空数组。

    //app.module.ts
    @NgModule({
      imports: [
        EffectsModule.forRoot([
          SourceA,
          SourceB,
          SourceC,
        ])
        //EffectsModule.forRoot([])  //提供一个空数组
      ]
    })
    export class AppModule {  }
    

    EffectsModule.forFeature( )可以在任何ng模块使用(导入)EffectsModule.forFeature(),无论是根模块(AppModule),还是任何一个功能模块。

    //feature.module.ts
    @NgModule({
      imports: [
        EffectsModule.forFeature([
          FeatureSourceA,
          FeatureSourceB,
          FeatureSourceC,
        ])
      ]
    })
    export class FeatureModule { }
    

    Init Action

    import { Injectable } from '@angular/core';
    import { Actions } from '@ngrx/effects';
    
    @Injectable()
    export class SomeEffectsClass {
      constructor(private actions$: Actions) {}
    }
    

    示例:

    import { Action } from '@ngrx/store';
    import { Actions, Effect } from '@ngrx/effects';
    import { defer } from 'rxjs/observable/defer';
    import * as auth from '../actions/auth.actions';
    
    @Injectable()
    export class AppEffects {
    
        @Effect()
        init$: Observable<Action> = defer(() => {
          return of(new auth.LoginAction());
        });
    
        constructor(private actions$: Actions) { }
    }
    

    ofType

    import { Injectable } from '@angular/core';
    import { Actions, Effect, ofType } from '@ngrx/effects';
    import { tap } from 'rxjs/operators';
    
    @Injectable()
    export class SomeEffectsClass {
      constructor(private actions$: Actions) {}
    
      @Effect() authActions$ = this.action$.pipe(
        ofType<LoginAction | LogoutAction>('LOGIN', 'LOGOUT'),
        tap(action => console.log(action))
      );
    }
    

    @ngrx/router-store

    通过@ngrx/store绑定连接angular路由

    安装使用:npm install @ngrx/router-store --save 或 yarn add @ngrx/router-store

    /**
     * Payload of ROUTER_NAVIGATION.
     */
    export declare type RouterNavigationPayload<T> = {
      routerState: T;
      event: RoutesRecognized;
    };
    /**
     * An action dispatched when the router navigates.
     */
    export declare type RouterNavigationAction<T = RouterStateSnapshot> = {
      type: typeof ROUTER_NAVIGATION;
      payload: RouterNavigationPayload<T>;
    };
    

     

    展开全文
  • Redux的一些总结

    千次阅读 2018-02-05 10:52:43
    Flux可以说是一个框架,其有本身的 Dispatcher 接口供开发者;也可以说是一种数据流单向控制的架构设计,围绕单向数据流的核心,其定义了一套行为规范,如下图: ...本文是笔者近半年使用react+redux

    Flux可以说是一个框架,其有本身的 Dispatcher 接口供开发者;也可以说是一种数据流单向控制的架构设计,围绕单向数据流的核心,其定义了一套行为规范,如下图:

    Redux的设计就继承了Flux的架构,并将其完善,提供了多个API供开发者调用。借着react-redux,可以很好的与React结合,开发组件化程度极高的现代Web应用。本文是笔者近半年使用react+redux组合的一些总结,不当之处,敬请谅解。

    Action

    Action是数据从应用传递到 store/state 的载体,也是开启一次完成数据流的开始。

    以添加一个todo的Action为例:

    {
        type:'add_todo',
        data:'我要去跑步'
    }

    这样就定义了一个添加一条todo的Action,然后就能通过某个行为去触发这个Action,由这个Action携带的数据(data)去更新store(state/reducer):

    store.dispatch({
        type:'add_todo',
        data:'your data'
    })

    type 是一个常量,Action必备一个字段,用于标识该Action的类型。在项目初期,这样定义Action也能愉快的撸码,但是随着项目的复杂度增加,这种方式会让代码显得冗余,因为如果有多个行为触发同一个Action,则这个Action要写多次;同时,也会造成代码结构不清晰。因而,得更改创建Action的方式:

    const ADD_TODO = 'add_todo';
    
    let addTodo = (data='default data') => {
        return {
            type: ADD_TODO,
            data: data
        }
    }
    
    //触发action
    store.dispatch(addTodo());

    更改之后,代码清晰多了,如果有多个行为触发同一个Action,只要调用一下函数 addTodo 就行,并将Action要携带的数据传递给该函数。类似 addTodo 这样的函数,称之为 Action Creator。Action Creator 的唯一功能就是返回一个Action供 dispatch 进行调用。

    但是,这样的Action Creator 返回的Action 并不是一个标准的Action。在Flux的架构中,一个Action要符合 FSA(Flux Standard Action) 规范,需要满足如下条件:

    • 是一个纯文本对象
    • 只具备 type 、payloaderror 和 meta 中的一个或者多个属性。type 字段不可缺省,其它字段可缺省
    • 若 Action 报错,error 字段不可缺省,切必须为 true

    payload 是一个对象,用作Action携带数据的载体。所以,上述的写法可以更改为:

    let addTodo = (data='default data') => {
        return {
            type: ADD_TODO,
            payload: {
                data
            }
        }
    }

    在 redux 全家桶中,可以利用 redux-actions 来创建符合 FSA 规范的Action:

    import {creatAction} from 'redux-actions';
    
    let addTodo = creatAction(ADD_TODO)
    //same as
    let addTodo = creatAction(ADD_TODO,data=>data)

    可以采用如下一个简单的方式检验一个Action是否符合FSA标准:

    let isFSA = Object.keys(action).every((item)=>{
       return  ['payload','type','error','meta'].indexOf(item) >  -1
    })

    中间件

    在我看来,Redux提高了两个非常重要的功能,一是 Reducer 拆分,二是中间件。Reducer 拆分可以使组件获取其最小属性(state),而不需要整个Store。中间件则可以在 Action Creator 返回最终可供 dispatch 调用的 action 之前处理各种事情,如异步API调用、日志记录等,是扩展 Redux 功能的一种推荐方式。

    Redux 提供了 applyMiddleware(...middlewares) 来将中间件应用到 createStore。applyMiddleware 会返回一个函数,该函数接收原来的 creatStore 作为参数,返回一个应用了 middlewares 的增强后的 creatStore。

    export default function applyMiddleware(...middlewares) {
      return (createStore) => (reducer, preloadedState, enhancer) => {
        //接收createStore参数
        var store = createStore(reducer, preloadedState, enhancer)
        var dispatch = store.dispatch
        var chain = []
    
        //传递给中间件的参数
        var middlewareAPI = {
          getState: store.getState,
          dispatch: (action) => dispatch(action)
        }
    
        //注册中间件调用链
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        //返回经middlewares增强后的createStore
        return {
          ...store,
          dispatch
        }
      }
    }

    创建 store 的方式也会因是否使用中间件而略有区别。未应用中间价之前,创建 store 的方式如下:

    import {createStore} from 'redux';
    import reducers from './reducers/index';
    
    export let store = createStore(reducers);

    应用中间价之后,创建 store 的方式如下:

    import {createStore,applyMiddleware} from 'redux';
    import reducers from './reducers/index';
    
    let createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
    export let store = createStoreWithMiddleware(reducers);

    那么怎么自定义一个中间件呢?

    根据 redux 文档,中间件的签名如下:

    ({ getState, dispatch }) => next => action

    根据上文的 applyMiddleware 源码,每个中间件接收 getState & dispatch 作为参数,并返回一个函数,该函数会被传入下一个中间件的 dispatch 方法,并返回一个接收 action 的新函数。

    以一个打印 dispatch action 前后的 state 为例,创建一个中间件示例:

    export default function({getState,dispatch}) {
        return (next) => (action) => {
            console.log('pre state', getState());
            // 调用 middleware 链中下一个 middleware 的 dispatch。
            next(action);
            console.log('after dispatch', getState());
        }
    }

    在创建 store 的文件中调用该中间件:

    import {createStore,applyMiddleware} from 'redux';
    
    import reducers from './reducers/index';
    import log from '../lib/log';
    
    //export let store = createStore(reducers);
    
    //应用中间件log
    let createStoreWithLog = applyMiddleware(log)(createStore);
    export let store = createStoreWithLog(reducers);

    可以在控制台看到输出:

    可以对 store 应用多个中间件:

    import log from '../lib/log';
    import log2 from '../lib/log2';
    
    let createStoreWithLog = applyMiddleware(log,log2)(createStore);
    export let store = createStoreWithLog(reducers);

    log2 也是一个简单的输出:

    export default function({getState,dispatch}) {
        return (next) => (action) => {
            console.log('我是第二个中间件1');
            next(action);
            console.log('我是第二个中间件2');
        }
    }

    看控制台的输出:

    应用多个中间件时,中间件调用链中任何一个缺少 next(action) 的调用,都会导致 action 执行失败

    异步

    Redux 本身不处理异步行为,需要依赖中间件。结合 redux-actions 使用,Redux 有两个推荐的异步中间件:

    两个中间件的源码都是非常简单的,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;

    从源码可知,action creator 需要返回一个函数给 redux-thunk 进行调用,示例如下:

    export let addTodoWithThunk = (val) => async (dispatch, getState)=>{
        //请求之前的一些处理
    
        let value = await Promise.resolve(val + ' thunk');
        dispatch({
            type:CONSTANT.ADD_TO_DO_THUNK,
            payload:{
                value
            }
        });
    };

    效果如下:

    这里之所以不用 createAction,如前文所说,因为 createAction 会返回一个 FSA 规范的 action,该 action 会是一个对象,而不是一个 function:

    {
        type: "add_to_do_thunk",
        payload: function(){}
    }

    如果要使用 createAction,则要自定义一个异步中间件。

    export let addTodoWithCustom = createAction(CONSTANT.ADD_TO_DO_CUSTOM, (val) => async (dispatch, getState)=>{
        let value = await Promise.resolve(val + ' custom');
        return {
            value
        };
    });

    在经过中间件处理时,先判断 action.payload 是否是一个函数,是则执行函数,否则交给 next 处理:

    if(typeof action.payload === 'function'){
        let res = action.payload(dispatch, getState);
    } else {
        next(action);
    }

    而 async 函数返回一个 Promise,因而需要作进一步处理:

    res.then(
        (result) => {
            dispatch({...action, payload: result});
        },
        (error) => {
            dispatch({...action, payload: error, error: true});
        }
    );

    这样就自定义了一个异步中间件,效果如下:

    当然,我们可以对函数执行后的结果是否是Promise作一个判断:

    function isPromise (val) {
        return val && typeof val.then === 'function';
    }
    
    //对执行结果是否是Promise
    if (isPromise(res)){
        //处理
    } else {
        dispatch({...action, payload: res});
    }

    那么,怎么利用 redux-promise 呢?redux-promise 是能处理符合 FSA 规范的 action 的,其对异步处理的关键源码如下:

    action.payload.then(
        result => dispatch({ ...action, payload: result }),
        error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
        }
    )

    因而,返回的 payload 不再是一个函数,而是一个 Promise。而 async 函数执行后就是返回一个 Promise,所以,让上文定义的 async 函数自执行一次就可以:

    export let addTodoWithPromise = createAction(CONSTANT.ADD_TO_DO_PROMISE, (val) =>
        (async (dispatch, getState)=>{
            let value = await Promise.resolve(val + ' promise');
            return {
                value
            };
        })()
    );

    结果如下图:

    示例源码:redux-demo


    组件拆分

    在 关于Redux的一些总结(一):Action & 中间件 & 异步 一文中,有提到可以根据 reducer 对组件进行拆分,而不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性。

    在常规的组件开发方式中,组件自身的数据和状态是耦合的,这种方式虽能简化开发流程,在短期内能提高开发效率,但只适用于小型且复杂度不高的SPA 应用开发,而对于复杂的 SPA 应用来说,这种开发方式不具备良好的扩展性。以开发一个评论组件 Comment 为例,常规的开发方式如下:

    class CommentList extends Component {
        constructor(){
            super();
            this.state = {commnets: []}
        }
    
        componentDidMount(){
            $.ajax({
                url:'/my-comments.json',
                dataType:'json',
                success:function(data){
                    this.setState({comments:data});
                }.bind(this)
            })
        }
    
        render(){
            return <ul>{this.state.comments.map(renderComment)}</ul>;
        }
    
        renderComment({body,author}){
            return <li>{body}-{author}</li>;
        }
    }

    随着应用的复杂度和组件复杂度的双重增加,现有的组件开发方式已经无法满足需求,它会让组件变得不可控制和难以维护,极大增加后续功能扩展的难度。并且由于组件的状态和数据的高度耦合,这种组件是无法复用的,无法抽离出通用的业务无关性组件,这势必也会增加额外的工作量和开发时间。

    在组件的开发过程中,从组件的职责角度上,将组件分为 容器类组件(Container Component) 和 展示类组件(Presentational Component)。前者主要从 state 获取组件需要的(最小)属性,后者主要负责界面渲染和自身的状态(state)控制,为容器组件提供样式。

    按照上述的概念,Comment应该有两部分组成:CommentListContainer和CommentList。首先定义一个容器类组件(Container Component):

    //CommentListContainer
    class CommentListContainer extends Component {
        constructor(){
            super();
            this.state = {commnets: []}
        }
    
        componentDidMount(){
            $.ajax({
                url:'/my-comments.json',
                dataType:'json',
                success:function(data){
                    this.setState({comments:data});
                }.bind(this)
            })
        }
    
        render(){
            return <CommnetList comments={this.state.comments}/>;
        }
    }

    容器组件CommentListContainer获取到数据之后,通过props传递给子组件CommentList进行界面渲染。CommentList是一个展示类组件:

    //CommentList
    class CommentList extends Component {
        constructor(props){
            super(props);
            this.state = {commnets: []}
        }
    
    
        render(){
            return <ul>{this.props.comments.map(renderComment)}</ul>;
        }
    
        renderComment({body,author}){
            return <li>{body}-{author}</li>;
        }
    }

    将Comment组件拆分后,组件的自身状态和异步数据被分离,界面样式由展示类组件提供。这样,对于后续的业务数据变化需求,只需要更改容器类组件或者增加新的展示类业务组件,极大提高了组件的扩展性。

    Container Component

    容器类组件主要功能是获取 state 和提供 action,渲染各个子组件。各个子组件或是一个展示类组件,或是一个容器组件,其职责具体如下:

    • 获取 state 数据;
    • 渲染内部的子组件;
    • 无样式;
    • 作为容器,嵌套其它的容器类组件或展示类组件;
    • 为展示类组件提供 action,并提供callback给其子组件。

    Presentational Component

    展示类组件自身的数据来自于父组件(容器类组件或展示类组件),组件自身提供样式和管理组件状态。展示类组件是状态化的,其主要职责如下:

    • 接受props传递的数据;
    • 接受props传递的callback;
    • 定义style;
    • 使用其它的展示类组件;
    • 可以有自己的状态(state)。

    连接器:connect

    react-redux 为 React 组件和 Redux 提供的 state 提供了连接。当然可以直接在 React 中使用 Redux:在最外层容器组件中初始化 store,然后将 state 上的属性作为 props 层层传递下去。

    class App extends Component{
    
      componentWillMount(){
        store.subscribe((state)=>this.setState(state))
      }
    
      render(){
    
        return <Comp state={this.state}
                     onIncrease={()=>store.dispatch(actions.increase())}
                     onDecrease={()=>store.dispatch(actions.decrease())}/>
      }
    }

    但这并不是所推荐的方式,相比上述的方式,更好的一个写法是结合 react-redux。

    首先在最外层容器中,把所有内容包裹在 Provider 组件中,将之前创建的 store 作为 prop 传给 Provider。

    const App = () => {
      return (
        <Provider store={store}>
          <Comp/>
        </Provider>
      )
    };

    Provider 内的任何一个组件(比如这里的 Comp),如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件(MyComp)」进行包装后的产物。

    class MyComp extends Component {
      // content...
    }
    
    const Comp = connect(...args)(MyComp);


    connect 会返回一个与 store 连接后的新组件。那么,我们就可以传一个 Presentational Component 给 connect,让 connect 返回一个与 store 连接后的 Container Component。

    connect 接受四个参数,返回一个函数:

    export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}){
            //code
    
            return function wrapWithConnect(WrappedComponent){
                //other code
                ....
                //merge props
                function computeMergedProps(stateProps, dispatchProps, parentProps) {
                        const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps)
                        if (process.env.NODE_ENV !== 'production') {
                               checkStateShape(mergedProps, 'mergeProps')
                        }
                       return mergedProps
            }
    
        ....
    
        render(){
    
            //other code
            ....
    
            if (withRef) {
                  this.renderedElement = createElement(
    WrappedComponent, {
                            ...this.mergedProps,
                           ref: 'wrappedInstance'
                  })
            } else {
                  this.renderedElement = createElement(
                    WrappedComponent,
                    this.mergedProps
                   )
            }
    
                 return this.renderedElement
          }
    
            }
    }

    wrapWithConnect 接受一个组件作为参数,在 render 会调用 React 的 createElement 基于传入的组件和新的 props 返回一个新的组件。

    以 connect 的方式来改写Comment组件:

    //CommentListContainer
    import getCommentList '../actions/index'
    import CommentList '../comment-list.js';
    
    function mapStateToProps(state){
        return {
            comment: state.comment,
            other: state.other
        }
    }
    
    function mapDispatchToProps(dispatch) {
        return {
            getCommentList:()=>{ 
                dispatch(getCommentList());
            }
        }
    }
    
    export default connect(mapStateToProps,mapDispatchToProps)(CommentList);

    在Comment组件中,CommentListContainer 只作为一个连接器作用,连接
    CommentList 和 state:

    //CommentList
    class CommentList extends Component {
        constructor(props){
            super(props);
        }
    
        componentWillMount(){
            //获取数据
            this.props.getCommentList();
        }
    
        render(){
            let {comment}  = this.props;
    
            if(comment.fetching){
                //正在加载
                return <Loading />
            }
    
            //如果对CommentList item的操作比较复杂,也可以将item作为一个独立组件
            return <ul>{this.props.comments.map(renderComment)}</ul>;
        }
    
        renderComment({body,author}){
            return <li>{body}-{author}</li>;
        }
    }

    关于 connect 比较详细的解释可以参考:React 实践心得:react-redux 之 connect 方法详解

    Redux:自问自答

      前段时间看了Redux的源码,写了一篇关于Redux的源码分析: Redux:百行代码千行文档,没有看的小伙伴可以看一下,整篇文章主要是对Redux运行的原理进行了大致的解析,但是其实有很多内容并没有明确地深究为什么要这么做?本篇文章的内容主要就是我自己提出一些问题,然后试着去回答这个问题,再次做个广告,欢迎大家关注我的掘金账号和我的博客。   

    为什么createStore中既存在currentListeners也存在nextListeners?

      看过源码的同学应该了解,createStore函数为了保存store的订阅者,不仅保存了当前的订阅者currentListeners而且也保存了nextListenerscreateStore中有一个内部函数ensureCanMutateNextListeners:   

    function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
    }

      这个函数实质的作用是确保可以改变nextListeners,如果nextListenerscurrentListeners一致的话,将currentListeners做一个拷贝赋值给nextListeners,然后所有的操作都会集中在nextListeners,比如我们看订阅的函数subscribe:

    function subscribe(listener) {
    // ......
        let isSubscribed = true
    
        ensureCanMutateNextListeners()
        nextListeners.push(listener)
    
        return function unsubscribe() {
            // ......
            ensureCanMutateNextListeners()
            const index = nextListeners.indexOf(listener)
            nextListeners.splice(index, 1)
    }

      我们发现订阅和解除订阅都是在nextListeners做的操作,然后每次dispatch一个action都会做如下的操作:

    function dispatch(action) {
        try {
          isDispatching = true
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
        // 相当于currentListeners = nextListeners const listeners = currentListeners
        const listeners = currentListeners = nextListeners
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
        return action
      }

      我们发现在dispatch中做了const listeners = currentListeners = nextListeners,相当于更新了当前currentListenersnextListeners,然后通知订阅者,到这里我们不禁要问为什么要存在这个nextListeners?
      
      其实代码中的注释也是做了相关的解释:

    The subscriptions are snapshotted just before every dispatch() call.If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on the dispatch() that is currently in progress.However, the next dispatch() call, whether nested or not, will use a more recent snapshot of the subscription list. 

      来让我这个六级没过的渣渣翻译一下: 订阅者(subscriptions)在每次dispatch()调用之前都是一份快照(snapshotted)。如果你在listener被调用期间,进行订阅或者退订,在本次的dispatch()过程中是不会生效的,然而在下一次的dispatch()调用中,无论dispatch是否是嵌套调用的,都将使用最近一次的快照订阅者列表。用图表示的效果如下:
      

      

      

      我们从这个图中可以看见,如果不存在这个nextListeners这份快照的话,因为dispatch导致的store的改变,从而进一步通知订阅者,如果在通知订阅者的过程中发生了其他的订阅(subscribe)和退订(unsubscribe),那肯定会发生错误或者不确定性。例如:比如在通知订阅的过程中,如果发生了退订,那就既有可能成功退订(在通知之前就执行了nextListeners.splice(index, 1))或者没有成功退订(在已经通知了之后才执行了nextListeners.splice(index, 1)),这当然是不行的。因为nextListeners的存在所以通知订阅者的行为是明确的,订阅和退订是不会影响到本次订阅者通知的过程。

      这都没有问题,可是存在一个问题,JavaScript不是单线程的吗?怎么会出现上述所说的场景呢?百思不得其解的情况下,去Redux项目下开了一个issue,得到了维护者的回答:

      

      得了,我们再来看看测试相关的代码吧。看完之后我了解到了。的确,因为JavaScript是单线程语言,不可能出现出现想上述所说的多线程场景,但是我忽略了一点,执行订阅者函数时,在这个回调函数中可以执行退订或者订阅事件。例如:

    const store = createStore(reducers.todos)
    const unsubscribe1 = store.subscribe(() => {
        const unsubscribe2 = store.subscribe(()=>{})
    })

      这不就实现了在通知listener的过程中混入订阅subscribe与退订unsubscribe吗?   

    为什么Reducer中不能进行dispatch操作?

      我们知道在reducer函数中是不能执行dispatch操作的。一方面,reducer作为计算下一次state的纯函数是不应该承担执行dispatch这样的操作。另一方面,即使你尝试着在reducer中执行dispatch,也并不会成功,并且会得到"Reducers may not dispatch actions."的提示。因为在dispatch函数就做了相关的限制:   

    function dispatch(action) {
        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')
        }
        try {
          isDispatching = true
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
    
        //...notice listener
    }

      在执行dispatch时就会将标志位isDispatching置为true。然后如果在currentReducer(currentState, action)执行的过程中由执行了dispatch,那么就会抛出错误('Reducers may not dispatch actions.')。之所以做如此的限制,是因为在dispatch中会引起reducer的执行,如果此时reducer中又执行了dispatch,这样就落入了一个死循环,所以就要避免reducer中执行dispatch

    为什么applyMiddleware中middlewareAPI中的dispathc要用闭包包裹?

      关于Redux的中间件之前我写过一篇相关的文章Redux:Middleware你咋就这么难,没有看过的同学可以了解一下,其实文章中也有一个地方没有明确的解释,当时初学不是很理解,现在来解释一下:  

    export default function applyMiddleware(...middlewares) {            
        return (next)  => 
            (reducer, initialState) => {
    
                  var store = next(reducer, initialState);
                  var dispatch = store.dispatch;
                  var chain = [];
    
                  var middlewareAPI = {
                    getState: store.getState,
                    dispatch: (action) => dispatch(action)
                  };
    
                  chain = middlewares.map(middleware =>
                                middleware(middlewareAPI));
                  dispatch = compose(...chain, store.dispatch);
                  return {
                    ...store,
                    dispatch
                  };
               };
    } 

    这个问题的就是为什么middlewareAPI中的dispathc要用闭包包裹,而不是直接传入呢?首先用一幅图来解释一下中间件:

      
      

      

      如上图所示,中间件的执行过程非常类似于洋葱圈(Onion Rings),假设我们在函数applyMiddleware中传入中间件的顺序分别是mid1、mid2、mid3。而中间件函数的结构类似于:

    export default function createMiddleware({ getState }) {
        return (next) => 
            (action) => {
                //before
                //......
                next(action)
                //after
                //......
            };
    }

      那么中间件函数内部代码执行次序分别是:
      

      但是如果在中间件函数中调用了dispatch(用mid3-before中为例),执行的次序就变成了:

      

      所以给中间件函数传入的middlewareAPIdispatch函数是经过applyMiddleware改造过的dispatch,而不是redux原生的store.dispatch。所以我们通过一个闭包包裹dispatch:

    (action) => dispatch(action)

      这样我们在后面给dispatch赋值为dispatch = compose(...chain, store.dispatch);,这样只要 dispatch 更新了,middlewareAPI 中的 dispatch 应用也会发生变化。如果我们写成:

    var middlewareAPI = {
        getState: store.getState,
        dispatch: dispatch
    };

    那中间件函数中接受到的dispatch永远只能是最开始的redux中的dispatch

    最后,如果大家在阅读Redux源码时还有别的疑惑和感受,欢迎大家在评论区相互交流,讨论和学习。

    展开全文
  • redux&&react-redux

    2020-10-11 09:31:38
    文章目录reduxreduxgitStatesubscriberedux的封装dispatchcombineReducers 多个reducer合并成一个react-redux 将数据放到组件的props上redux模拟indexindex-getStateindex-subscriberedux源码补充redux案例(加减)...
  • 1. 之前说过可以引起React组件更新的元素有两种,一个是props的更新,一个是state的更新,但是props的跟新放眼整个react应用也是state的更新。...3. Redux可以通过subscribe来订阅store中state的更新。
  • 介绍react的一些基本的知识
  • 最近看啦react路由的各种实现,原先不止如何入手写这片文章,现在想将他们对比来写更好理解一点,也方便大家如何选择适合自己的方式,最后会结合express &amp;&amp; jwt &amp;&amp;...
  • react-redux的使用

    2019-10-21 21:09:13
    react-redux的使用 代码:github-learn-react 分别存放action和reducer 如postReducer export const postReducer = function (state: any = { list: [{ title: 'hellow', id: -1 }] }, action: any) { switch ...
  • 轻松理解Redux原理及工作流程

    万次阅读 2018-07-15 11:19:16
    轻松理解Redux原理及工作流程 Redux由Dan Abramov在2015年创建的科技术语。是受2014年Facebook的Flux架构以及函数式编程语言Elm启发。很快,Redux因其简单易学体积小在短时间内成为最热门的前端架构。 本文中我...
  • 函数式编程与闭包

    2020-09-10 08:55:17
    为什么要学习函数式编程...另外react中的一些生态,比如redux它使用了函数式编程的思想,所以我们想要真正学好react和redux的话我们需要了解函数式编程 Vue3也开始拥抱函数式编程。 我们都知道Vue3对Vue2做了很大一部
  • 一般在react项目中,我们通过redux和react-redux来存储和管理数据,但是使用redux存储数据时,会有一个问题,如果用户刷新了页面,那么通过redux存储的全局数据就会被重置,比如登录状态等。 这个时候有没有解决办法...
  • 本文简单介绍了在 Redux 使用 redux-devtools-extension 插件的用法,可以对状态变化进行可视化查看。 本文例子源码 源码地址 欢迎 Star!欢迎 Watch! 前提条件 请先安装 redux:npm install redux -S redux-...
  • 使用Redux DevTools浏览器插件调试redux

    千次阅读 2017-03-19 20:17:31
    使用Redux DevTools浏览器插件调试reduxredux的Devtools模块不同,该工具主要依赖浏览器插件完成。模式也比Devtools简单点。 step1 下载插件 Chrome地址(360极速模式也可以用):  ...
  • 模块开发之redux-devtools-extension调试工具使用详解(十一) 模块开发之redux-devtools-extension调试工具使用详解(十一) 前言 安装调试工具插件 非侵入式 没有使用中间件的情况 使用中间件情况 侵入式 安装...
  • 简要介绍:redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作。之前一直使用redux-thunk处理...
  • 解决redux刷新数据丢失,

    千次阅读 2019-02-20 17:19:35
    为什么要存储react-redux中的数据? 当页面刷新之后,redux中的数据会回到初始值,之前存储到redux中的数据也就不复存在了。废话不多说,直接上代码: 在reducer文件中: let shopDetailInfo = sessionStorage....
  • 为什么要用react-router-redux

    千次阅读 2018-05-30 18:53:29
    react-router-redux 将react-router 和 redux 集成到一起,用redux的方式去操作react-router。例如,react-router 中跳转需要调用 router.push(path),集成了react-router-redux 就可以通过dispatch的方式使用router...
  • redux 多种触发dispatch方式

    千次阅读 2018-10-18 18:51:37
    https://blog.csdn.net/u011077672/article/details/82684019
1 2 3 4 5 ... 20
收藏数 31,057
精华内容 12,422
关键字:

redux