redux直通自行车

前言

redux是个状态管理工具,提供简化的api做到行为的可预测性,由于react在组件之间通信繁琐,状态管理需要状态提升造成代码冗长等局限,redux常与react结合使用,但是redux作为独立的框架,其实是可以和其他的库配合使用的,为了不被react混淆了redux的使用方法,我们这里结合jq来理解redux的使用

redux基本概念

redux有三个基本原则

  • 单一数据源:一个应用state只存在唯一一个对象树当中(state状态与试图层级对应形成树装结构,所以称为对象树),由唯一的store统一管理
  • state是只读的:redux规定不能直接修改state,修改state需要触发action,action* 是一个包含type字段的普通函数,用来描述发生的事情
  • 使用纯函数修改state:通过action描述怎样修改state,需要手动创建函数reducer(一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数)
const state ={
    visibilityFilter: 'SHOW_ALL',
    todos: [
      {
        text: 'Consider using Redux',
        completed: true,
      },
      {
        text: 'Keep all state in a single tree',
        completed: false
      }
    ]
}

我们可以根据之前定义的action更新state,创建redux

const state ={
    visibilityFilter: 'SHOW_ALL',
    todos: [
      {
        text: 'Consider using Redux',
        completed: true,
      },
      {
        text: 'Keep all state in a single tree',
        completed: false
      }
    ]
}
function reducer(state=state,action){
   switch(action.type){
      case 'ADD_TODO':
        return Object.assign({},state,{
            todos:[
                ...state.todos,
                {text:action.text,completed:false}
            ]
        });
        case 'SET_VISIBILITY':
        return Object.assign({},state,{
           visibilityFilter:action.filter 
        }); 
      default:
        return state  
   }
}

可以看到上面的两个更新是可以独立拆分出来的,分成两个小的reducer,最后合成出一个完整的state

function reducer(state=state,action){
   return {
       todos:todos(state.todos,action),
       visibilityFilter:visibilityFilter(state.visibilityFilter,action)
   }
}
function todos(state=[],action) {
   switch(action.type){
       case 'ADD_TODO':
         return [
            ...state,
            {
                text:action.text,
                completed:false
            }
         ]
        default:
          return state 
   }
}
function visibilityFilter(state='SHOW_ALL',action){
    switch(action.type){
        case 'SET_VISIBILITY_FILTER':
          return action.filter
        default:
          return state  
    }
}

redux本身提供了一个方法combineReducers(reducers),辅助我们根据给出的key值让拆分出来的函数管理对应的state,返回一个完整的reducer,合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。

 const reducer = combineReducers({
  todos,
  visibilityFilter
})

下面是combineReducers部分源码

return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }

通过上面的源码可以看到,在调用combineReducers方法后返回一个函数combination,这个combination就作为接下来要说的createStore的第一个必填参数传入,生成store

createStore

cosnt store = createStore(reducer);

createStore接收三个参数,reducers、initState、enhancer(中间件)
createStore最重要的是在内部构造了四个方法,分别是:
dispatch: 接收action,更新状态数。
getState: 得到当前最新state树。
replaceReducer: 替换reducers,初始化state树。
subscribe: 用于观察者模式,设置监听,并返回退出监听方法,在调用dispatch时,运行所有被监听对象。

createStore接受三个参数,第一参数reducers是必填,第二个参数initState用来初始化state,第三个参数用来合并中间件,这里我们分析一下store的四个方法和中间件,initSate初始化状态和服务端渲染没有做过多研究,之后补充。

createStore创建一个单一的store,用来管理唯一state,state是一个对象,store管理这个对象,通过store.getState返回最新的state。

function getState() {
    return currentState;
}

redux规定不能直接修改应用的状态 state,想要修改state只能通过dispatch方法,dispatch接受一个叫action的对象,里面必须包含一个 type 字段来声明你到底想干什么。redux相当于生成了一个共享,共享的状态如果可以被任意修改的话,那么程序的行为将非常不可预料,所以我们提高了修改数据的门槛:你必须通过 dispatch 执行某些允许的修改操作,而且必须大张旗鼓的在 action 里面声明。

/**
*通过部分源码可以看到dispatch接受action,然后调用reducers函数放回新的state更新当前的state
*至于怎么写这个reducers有很多方式,不做本文重点,不做介绍,可以看官方文档,至于用哪种方式,怎么用就看个人喜好,用与不用它就在那里不悲不喜
*/
const dispatch = (action)=>{
    currentState = reducers(currentState,action);
}

现在可以获取到state并修改它,那我们想每次更新状态的时候做一些统一的操作,比如打印出state看到变化,我们需要调用store.subscribe

nextListeners.push(listener);

return function unsubscribe() {
    var index = nextListeners.indexOf(listener);
    nextListeners.splice(index, 1);
};

通过部分源码我们可以看到,subscribe其实就是接收一个函数,然后把它存在一个数组,每次dispatch调用时遍历一遍这个数组

//dispatch部分源码
for (var i = 0; i < listeners.length; i++) {
  listeners[i]();
}

还有一个replaceReducer方法,他接受一个新的reducers替换旧的reducers,然后初始化一个新的state

function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer         // 就是这么耿直简单粗暴!
    dispatch({ type: ActionTypes.INIT }) // 触发生成新的 state 树
  }

dispatch与middleware

store里的dispatch函数只能接收一个action然后传给reducer更新state然后重新渲染,想要扩展一些操作就需要中间件来加强dispatch的功能,例如向服务器请求后进行后再进行更新state的操作,可自己定义一个中间件来进行想要的强化,输出不够补输出,防御不够补护甲。

applyMiddleware(...middleware)

applyMiddleware方法接收多个中间件方法,将多个中间方法合成一个新的dispatch,然后执行新的dispatch时根据传入顺序逐个执行

var store = createStore(reducer, preloadedState, enhancer)
      
      var dispatch = store.dispatch // 指向原 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, // store 的 API 中保留 getState / subsribe / replaceReducer
        dispatch  // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会触发 chain 内的中间件链式串联执行
      }

在上面代码中最主要的就是compose函数,它将个所有的中间件合并然后生成新的增强后dispatch,就像dispatch加了一层又一层的buff,为什么是说它是一层一层的是因为compose将所有的中间件组合生成新的dispatch就是洋葱一样将dispatch一层层包裹传递

function compose(...funcs) {
  return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}

我们调用compose函数将chain里的函数从右向左依次执行,传入的arg就是store.dispatch,假设我们添加了三个中间件最后生成的dispatch

dispatch = f1(f2(f3(store.dispatch))))

就像洋葱一样将dispatch一层层包裹,首先执行f3然后将f3中间件的返回值是一个函数作为参数传给传给f2,f2执行后又包裹了一层f2的返回值,然后传给f1,最后f1将自己的返回值与之前两个中间件的返回值组一起生成新的dispatch函数

我们用一个简单的logger中间件理解一下这个过程

const logger = (store)=>(next)=>(action)=>{
    console.log(store.getState());
    next(action);
    console.log(store.getState());
}

之前在applyMiddleware有这样一行代码

chain = middlewares.map(middleware => middleware(middlewareAPI)) 

它生出一个中间件的数组集合的同事其实也执行了每个中间件,所以中间函数到下一层返回了一个函数

(next)=>(action)=>{
    console.log(store.getState());
    next(action);
    console.log(store.getState());
}

而到了compose合并的时候返回的的新函数就是

(action)=>{
    console.log(store.getState());
    next(action);
    console.log(store.getState());
}

理解这个过程很重要,这里执行dispatch没有直接调用store里的dispatch而是传进去,是因为如果是多个中间件调用的话,这里的next可能是上一层的(action)=>{}函数不是store.dispatch
我们在加一个logNext的函数将next打印出来看一下效果

const logNext = (store)=>(next)=>(action)=>{
    console.log(next.toString());
    next(action);
}

我们可以看到此时的next

function (n){console.log(e.getState()),t(n),console.log(e.getState())}

它的运行顺序

             --------------------------------------
            |            middleware1              |
            |    ----------------------------     |
            |    |       middleware2         |    |
            |    |    -------------------    |    |
            |    |    |  middleware3    |    |    |
            |    |    |                 |    |    |
          next next next  ———————————   |    |    |
dispatch  —————————————> |  reducer |   |    |    |
nextState <————————————— |          |   |    |    |
            |    |    |  ———————————    |    |    |
            |    |    |                 |    |    |
            |    |    -------------------    |    |
            |    ----------------------------     |
            --------------------------------------

在生成新的dispatch是层层包裹。层层进入,层层冒出,就像是剥洋葱

总结

本文介绍了redux的一些机制,它可以帮助我们管理状态,并且通过准守它的规定,可以让状态的修改可预料,并且实现了自动渲染,redux是优秀的函数式编程应用,体现了函数式编程的灵活性,可以根据自己的需求自定义扩展,来管理使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容