深入 Redux 核心:从零手写 Reducer 源码实现

119 阅读3分钟

Redux数据流

一、Redux 基础回顾

1.1 Redux 三原则

  1. 单一数据源:整个应用的 state 存储在一个对象树中
  2. State 只读:唯一改变 state 的方法是触发 action
  3. 纯函数修改:使用纯函数 reducer 执行状态更新

1.2 Reducer 基础用法

// 初始状态
const initialState = {
  count: 0,
  todos: []
}

// 典型 reducer 结构
function appReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { 
        ...state, 
        count: state.count + 1 
      };
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    default:
      return state;
  }
}

1.3 核心特性解析

  • 纯函数特性:无副作用,相同输入必定得到相同输出
  • 不可变性:必须返回新对象,禁止直接修改原 state
  • 组合性:通过 combineReducers 组合多个 reducer

二、手写 Redux 核心实现

2.1 最小化 Redux 实现

function createStore(reducer) {
  let state;
  const listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action); // 核心逻辑
    listeners.forEach(listener => listener());
  };

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

  // 初始化 state
  dispatch({ type: '@@INIT' });

  return { getState, dispatch, subscribe };
}

2.2 Reducer 处理流程解析

// 模拟 Redux 内部处理逻辑
function dispatch(action) {
  // 1. 参数校验
  if (!isPlainObject(action)) {
    throw new Error('Actions must be plain objects');
  }

  if (typeof action.type === 'undefined') {
    throw new Error('Actions must have a type property');
  }

  // 2. 执行 reducer 计算新状态
  try {
    currentState = currentReducer(currentState, action);
  } catch (error) {
    // 错误处理逻辑
  }

  // 3. 通知所有订阅者
  const listeners = currentListeners;
  listeners.forEach(listener => {
    listener();
  });
}

三、组合 Reducer 实现原理

3.1 combineReducers 源码剖析

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  
  return function combination(state = {}, action) {
    let hasChanged = false;
    const nextState = {};
    
    reducerKeys.forEach(key => {
      const reducer = reducers[key];
      const previousStateForKey = state[key];
      const nextStateForKey = reducer(previousStateForKey, action);
      
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    });
    
    return hasChanged ? nextState : state;
  };
}

3.2 状态更新优化策略

  1. 浅层比较:通过 nextStateForKey !== previousStateForKey 快速判断状态变化
  2. 短路优化:只要有一个子 reducer 返回新对象,就触发全局更新
  3. 引用保留:未变化的子 state 保持原引用

四、性能优化实践

4.1 不可变数据操作对比

方式代码示例内存占用性能表现
直接修改state.items.push(newItem)
浅拷贝{...state, items: [...items]}
Immutable.jsstate.set('items', newList)

4.2 高效更新模式

// 使用对象解构避免深层拷贝
case 'UPDATE_USER':
  return {
    ...state,
    user: {
      ...state.user,
      ...action.payload
    }
  };

// 数组操作优化
case 'DELETE_ITEM':
  return {
    ...state,
    items: state.items.filter(
      item => item.id !== action.payload
    )
  };

五、高级模式扩展

5.1 Reducer 组合模式

function undoable(reducer) {
  const initialState = {
    past: [],
    present: reducer(undefined, {}),
    future: []
  };

  return function(state = initialState, action) {
    const { past, present, future } = state;

    switch (action.type) {
      case 'UNDO':
        return {
          past: past.slice(0, -1),
          present: past[past.length - 1],
          future: [present, ...future]
        };
      case 'REDO':
        return {
          past: [...past, present],
          present: future[0],
          future: future.slice(1)
        };
      default:
        const newPresent = reducer(present, action);
        return {
          past: [...past, present],
          present: newPresent,
          future: []
        };
    }
  };
}

5.2 异步 Action 处理

// 自定义中间件
const asyncMiddleware = store => next => action => {
  if (typeof action === 'function') {
    return action(store.dispatch, store.getState);
  }
  return next(action);
};

// 使用示例
const fetchUser = () => async dispatch => {
  dispatch({ type: 'USER_REQUEST' });
  try {
    const user = await api.getUser();
    dispatch({ type: 'USER_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'USER_FAILURE', error });
  }
};

六、最佳实践总结

  1. 保持 Reducer 纯净:永远不要在 reducer 中执行 API 调用
  2. 合理拆分粒度:按功能模块划分多个 reducer
  3. 状态范式化:使用 ID 引用代替嵌套对象
  4. 性能监控:使用 Redux DevTools 分析更新频率
  5. 类型安全:配合 TypeScript 实现类型约束

七、思考延伸

  1. Redux 如何通过时间旅行实现调试功能?
  2. 为什么 Redux Toolkit 要使用 Immer 处理不可变性?
  3. 在 10万级数据量下如何优化 Redux 性能?
  4. Redux 与 Context API 的性能对比分析?

参考资料

  1. Redux 官方文档
  2. Redux 源码仓库
  3. Redux Toolkit 实现原理

通过本文的学习,建议读者尝试自己实现一个支持中间件系统的增强版 Redux Store,这将帮助您更深刻地理解现代状态管理库的设计哲学。