浅析redux

redux适用于不少场景,须要用到全局存储状态的应用均可以用到它,无论是换肤的应用仍是购物车的场景,须要将不一样的组件经过相同的状态关联起来,或者相同状态的变化触发不一样视图的更新,都很适合用到redux。

这篇文章经过实现一个简单的redux,理解redux是怎样把状态和视图关联起来的,接下来会实现redux这几个接口:redux

export { 
  createStore, // 建立store,接受reducer函数和初始化的状态state
  combineReducer, // 合并多个reducer
  bindActionCreator, // 转换action对象
}

redux会有一个存放和管理状态的函数reducer,用户更改状态需经过dispatch才能修改里面的状态,这样能避免用户直接修改state:app

import * as TYPES from '../typings'
function reducer(state,action){
  switch(action.type){
    case TYPES.ADD:
      return {...state,action.payload}
    default:
      return state;
  }
}
这里能够看出, action是一个对象,并且必须有 type属性, reducer经过判断 type属性对state进行不一样的合并操做并返回更新后的 state。另外,这里还经过 typings文件维护不一样的 type变量。

接下来是建立createStore函数,store会向外抛出getStatedispatchsubscribe这三个接口,方便用户调用:函数

export default function createStore(reducer,initState){

  return {
    getState, // 获取最新state状态
    dispatch, // 触发状态更新,接受参数对象{type}
    subscribe, // 订阅视图更新函数
  }
}

针对这个目标,咱们就开始编写内部函数,首先是建立做用域内的state,将初始值initState赋值给它,接着getState函数固然就是返回它了:优化

let state = initState;
  const getState = ()=> state;

dispatch方法须要接收一个带有type属性的对象action,方便reducer函数调用:this

const dispatch = (action)=>{
  
    if(!isPlainObject(action)){
      throw new Error('action 必须是纯对象')
    }
    if(typeof action.type == "undefined"){
      throw new Error('必须定义action.type属性');
    }

    state = reducer(state, action);
    
    return action
  }

这里看源码的时候还会判断action是否为纯对象,这里附上isPlainObject的实现:spa

function isPlainObject(obj) {
  if(typeof obj !=="object" || obj == null) return false;
  
  let objPro = obj;
  
  // 这里拿到obj最初始的__proto__
  while (Object.getPrototypeOf(objPro)) {
    objPro = Object.getPrototypeOf(objPro);
  }

  if (objPro === Object.getPrototypeOf(obj)) return true
  
}

接着是实现subscribe函数,方便用户更新视图时调用:code

let listeners=[];
 const subscribe = (listener)=>{
      listeners.push(listener);
      let subscribed = true;
      return ()=>{
        if(!subscribed) return;
        let index = listeners.indexOf(listener);
        listeners.splice(index,1);
        subscribed = false;
      }
  }

而后dispatch函数里面,每次更新完state再执行下listeners里面存放的订阅方法:对象

const dispatch = (action)=>{
    ...

    state = reducer(state, action);
    // 更新完state,紧接着触发订阅方法
    listeners.forEach(fn=>fn());
    ...
  }

三个函数实现后,基本就完成了,不过咱们还须要在函数内部执行下dispatch方法,初始化用户传过来的state值:接口

dispatch({ type: "@@@Redux/INIT"})

ok,这样这个简单版的createStore函数就算完成了。作用域

接下来,实现combineReducer函数,这个函数是为了合并多个reducer时使用的:

function combineReducer(reducers) {
  let reducersKeys = Object.keys(reducers);
  return function(state={},actions){
    let combineState = {};
    for(let i=0;i<reducersKeys.length;i++){
      let key = reducersKeys[i]; // 拿到每一个reducer的key值
      let reducer = reducers[key]; // 拿到每一个reducer方法
      combineState[key] = reducer(state[key], actions);
    }
    return combineState;
  }
}

combineReducer的实现方式有不少,查阅资料的时候也看到有其它的版本,不过咱们只有理解最终返回的值,是一个合并后的大reducer,照着reducer的参数和返回值来写就好理解了。

还有最后一个bindActionCreator方法,目的是将actions对象转换,把属性函数替换成可以执行的dispatch方法:

function bindActionCreator(actions,dispatch) {
  if(typeof actions === "function"){
    return function(){
      dispatch(actions.apply(this,arguments))
    }
  }
  let actionCreator = {};
  for(let name in actions){
    actionCreator[name] = function(){
      dispatch(actions[name].apply(this, arguments))
    }
  }
  return actionCreator;
}

这样,一个简单的redux就实现了,redux的思想在不少场景中均可以使用到,不过我以为关键还在于理解何时须要使用它,适用的场景中使用能够优化代码结构,提升可读性,不适用的场景反而会让应用变得复杂,不易理解。

相关文章
相关标签/搜索