Redux 的理解

Zhanghao,redux

随着前端应用的发展,JavaScript 需要管理比以前更多的状态 (state),这些状态包括服务器响应,缓存数据,UI 状态等。状态管理的复杂程度主要体现在数据的变化和异步,在开发中要追踪数据的传递流程,保持界面与数据的一致性,异步状态的及时更新。redux 是一个状态容器,提供可预测化的状态管理。

react 为例 (后面的示例也是 react),在 react 中,数据的传递是单向的,正常的使用没有问题,但是当组件变得复杂后就变得难以管理了,某一个子组件的数据来源可能是其父组件的父组件,这样数据就传递了 3 层,而且中间层级的组件很可能不需要用到这样的数据,假如子组件的部分交互需要更新父组件的某些数据,那么父组件的回调函数也需要一级一级的传递下去,如果层级再多点那么多余的数据传递就变得更像明显了。 why-redux.jpg 上图左边便是传统的单项数据流,而右边是 redux 的数据传递。在 redux 中,有一个 store 进行数据的管理和消息的分发,其中的数据是全局唯一的,负责整个 APP 的状态数据。在这个基础下,所有的组件都可以从 store 中获取数据,也可以发送消息更新 store 中的数据,不需要组件间的数据传递。

reduxstore 中保存着全局的状态数据 (本质上是一个 JavaScript 对象),通过发送 action (携带动作类型和具体数据的普通对象) 描述将要进行的数据更新类型和具体数据,reducer 在接受到数据后更新相应的 state,然后再根据 store 的订阅情况进行界面更新。

整个应用的 state 被存在一个 Object Tree 中,并且这个 Object Tree 在存在与唯一的 store

唯一改变 state 的方法是触发 actionaction 是一个描述事件的普通对象。

reducer 进行数据修改,并且 reducer 必须是一个纯函数,接受 state 和 action,返回一个新的 state。

action 是一个普通的 JavaScript 对象。

{
  type: 'ADD_TODO',
  text: 'redux',
  id: Data.now,
}

type 字段描述事件行为,其他字段为需要更新的数据。 一般在使用 action 时会搭配 action createor 使用,action creater 是一个返回 action 的函数。

function addTodo(text) {
  return {
    type: 'ADD_TODO',
    text: 'redux',
    id: Data.now,
  };
}

reducer 是一个纯函数,接收 stateaction 并返回一个新的 state。如果没有可以匹配的 type 就返回 state 本身。

const todosReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false,
          id: Date.now(),
        },
      ];
    case 'TOGGLE_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return {
            ...todo,
            completed: !todo.completed,
          };
        }
        return todo;
      });
    default:
      return state;
  }
};
const { createStore } = Redux;
const store = createStore(todosReducer);

一个 APP 中可以有多个 reducerreducer 也可以调用其他 reduce 更新 state

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        text: action.text,
        completed: false,
        id: Date.now(),
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        completed: state.id === action.id ? !state.completed : state.completed,
      };
  }
};
 
const TodosReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, todoReducer(undefined, action)];
    case 'TOGGLE_TODO':
      return state.map(todo => {
        return todoReducer(todo, action);
      });
    default:
      return state;
  }
};
const { createStore } = Redux;
const store = createStore(todosReducer);

如上述代码,将 todo 的操作拆分到 todoReducer 中,todosReducer 调用 todoReducer 更新 todo 的数据内容。 如果是使用多个 reducer 组合可以使用 combineReducers 实现。

const visibilityFilter = (state = 'all', action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter;
    default:
      return state;
  }
};
 
const { createStore, combineReducers } = Redux;
const store = createStore(
  combineReducers({
    todos: todosReducer,
    visibilityFilter: visibilityFilter,
  }),
);

store 是将 action 和 reucer 连接在一起的对象,在 redux 中,发送 action 和监听刷新都是 store 的职责,

  1. getState 获取当前状态;
  2. dispatch 发送 action;
  3. subscribe 监听状态更新,调用返回的函数可以取消监听;
// 发送action
store.dispatch({
  type: 'ADD_TODO',
  text: input.value,
});
 
// 设置监听
this.unsubscribe = store.subscribe(() => {
  // 监听回调
});
// 取消监听
this.unsubscribe();
 
// createStore的简易实现
const createStore = reducer => {
  let state = undefined;
  let listeners = [];
 
  const getState = () => state;
  const dispatch = action => {
    state = reducer(state, action);
    listeners.forEach(listenter => listenter());
  };
  const subscribe = listener => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  };
 
  dispatch({});
 
  return { getState, dispatch, subscribe };
};

redux 的状态更新过程中都是同步的,如果要处理异步消息怎么办呢?答案是中间件 (middleware)。 中间件本质上就是对 store 进行增强,store 发起 action,在到达 reducer 之前进行拦截并做相应的处理 (打印日志,网络请求等),然后再重新 dispatch 处理后的数据,使其到达 reducrer 进行状态更新。

// 简易的日志中间件
const logger = (store, action) => {
  console.log('dispatch: ', action);
  store.dispatch(action);
  console.log('nextState: ', store.getState());
};

如果有多个中间件进行使用,道理是一样的。

// 修改后的middleware实现
const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
};
 
const crashReporter = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Caught an exception!', err);
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState(),
      },
    });
    throw err;
  }
};
 
// middleware的应用实现
function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();
 
  let dispatch = store.dispatch;
  middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)));
 
  return Object.assign({}, store, { dispatch });
}
Twitter · GitHub · Email © Zhang Hao.