# 多文件协作

# reducer的拆分和合并

这一小节我们来处理下reducer的问题。啥问题?

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

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

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

const 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
});

const initState = {
  counter: {
    count: 0
  },
  info: {
    name: '前端九部',
    description: '我们都是前端爱好者!'
  }
};

const store = createStore(reducer, initState);

store.subscribe(() => {
  const 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 (opens new window)

# state的拆分和合并

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

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

// counter 自己的 state 和 reducer 写在一起
const 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) {
  const state = initState;
  const 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
  }
}

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

  1. createStore的时候,用一个不匹配任何type的action,来触发state = reducer(state, action)

  2. 因为action.type不匹配,每个子reducer都会进到default项,返回自己初始化的state,这样就获得了初始化的state树了。

你可以试试

// 这里没有传 initState 哦
const store = createStore(reducer);
// 这里看看初始化的 state 是什么
console.dir(store.getState());

本小节完整源码见demo-4 (opens new window)

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

最近更新时间: 2020/5/26 19:03:58