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

    • 为什么需要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 是只读的

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

    • 使用纯函数进行修改

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

    • action

    action是一个普通的JavaScript对象。

    1. {
    2. type: 'ADD_TODO',
    3. text: 'redux',
    4. id: Data.now,
    5. }

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

    1. function addTodo(text) {
    2. return {
    3. type: 'ADD_TODO',
    4. text: 'redux',
    5. id: Data.now,
    6. }
    7. }
    • reducer

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

    1. const todosReducer = (state = [], action) => {
    2. switch (action.type) {
    3. case 'ADD_TODO':
    4. return [
    5. ...state,
    6. {
    7. text: action.text,
    8. completed: false,
    9. id: Date.now(),
    10. }
    11. ];
    12. case 'TOGGLE_TODO':
    13. return state.map((todo) => {
    14. if (todo.id === action.id) {
    15. return {
    16. ...todo,
    17. completed: !todo.completed,
    18. }
    19. }
    20. return todo;
    21. });
    22. default:
    23. return state;
    24. }
    25. };
    26. const {createStore} = Redux;
    27. const store = createStore(todosReducer);

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

    1. const todoReducer = (state, action) => {
    2. switch (action.type) {
    3. case 'ADD_TODO':
    4. return {
    5. text: action.text,
    6. completed: false,
    7. id: Date.now(),
    8. };
    9. case 'TOGGLE_TODO':
    10. return {
    11. ...state,
    12. completed: state.id === action.id ? !state.completed : state.completed,
    13. }
    14. }
    15. };
    16. const TodosReducer = (state = [], action) => {
    17. switch (action.type) {
    18. case 'ADD_TODO':
    19. return [
    20. ...state,
    21. todoReducer(undefined, action),
    22. ];
    23. case 'TOGGLE_TODO':
    24. return state.map((todo) => {
    25. return todoReducer(todo, action);
    26. });
    27. default:
    28. return state;
    29. }
    30. };
    31. const {createStore} = Redux;
    32. const store = createStore(todosReducer);

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

    1. const visibilityFilter = (state = 'all', action) => {
    2. switch (action.type) {
    3. case 'SET_VISIBILITY_FILTER':
    4. return action.filter;
    5. default:
    6. return state;
    7. }
    8. };
    9. const {createStore, combineReducers} = Redux;
    10. const store = createStore(combineReducers({
    11. todos: todosReducer,
    12. visibilityFilter: visibilityFilter
    13. }));
    • store

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

    1. getState 获取当前状态;
    2. dispatch 发送action;
    3. subscribe 监听状态更新,调用返回的函数可以取消监听;
    1. // 发送action
    2. store.dispatch({
    3. type: 'ADD_TODO',
    4. text: input.value,
    5. });
    6. // 设置监听
    7. this.unsubscribe = store.subscribe(() => {
    8. // 监听回调
    9. })
    10. // 取消监听
    11. this.unsubscribe();
    12. // createStore的简易实现
    13. const createStore = (reducer) => {
    14. let state = undefined;
    15. let listeners = [];
    16. const getState = () => state;
    17. const dispatch = (action) => {
    18. state = reducer(state, action);
    19. listeners.forEach(listenter => listenter());
    20. };
    21. const subscribe = (listener) => {
    22. listeners.push(listener);
    23. return () => {
    24. listeners = listeners.filter(l => l !== listener)
    25. };
    26. };
    27. dispatch({});
    28. return {getState, dispatch, subscribe};
    29. };
    • 中间件

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

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

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

    1. // 修改后的middleware实现
    2. const logger = store => next => action => {
    3. console.log('dispatching', action)
    4. let result = next(action)
    5. console.log('next state', store.getState())
    6. return result
    7. }
    8. const crashReporter = store => next => action => {
    9. try {
    10. return next(action)
    11. } catch (err) {
    12. console.error('Caught an exception!', err)
    13. Raven.captureException(err, {
    14. extra: {
    15. action,
    16. state: store.getState()
    17. }
    18. })
    19. throw err
    20. }
    21. }
    22. // middleware的应用实现
    23. function applyMiddleware(store, middlewares) {
    24. middlewares = middlewares.slice()
    25. middlewares.reverse()
    26. let dispatch = store.dispatch
    27. middlewares.forEach(middleware =>
    28. dispatch = middleware(store)(dispatch)
    29. )
    30. return Object.assign({}, store, { dispatch })
    31. }