核心组件

核心组件

Redux的整个系统图解可以参考下图:

  • Store - an object that keeps whole the state of our application

  • Actions - plain objects representing the facts about “what happened” in our app

  • Reducers - pure functions updating the state according to actions

All this staff responds to the side of logic. Let’s look further… What’s about view? There are two types of components:

  • Container - “smart” components,which are concerned with “how things work”

  • Presentational -“dumb” components,which are concerned with “how things look”

交互角度的组件协同

Action

Action是把数据从应用传到store的有效载荷。它是store数据的唯一来源。一般来说你会通过store.dispatch()action传到store

const ADD_TODO = 'ADD_TODO'

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Action本质上是JavaScript普通对象。我们约定,action内必须使用一个字符串类型的type字段来表示将要执行的动作。多数情况下,type会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放action

Action Creator

Action创建函数 就是生成action的方法,在Redux中的action创建函数只是简单的返回一个action:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}

这样做将使action创建函数更容易被移植和测试。在 传统的Flux实现中,当调用action创建函数时,一般会触发一个dispatch,像这样:

function addTodoWithDispatch(text) {
  const action = {
    type: ADD_TODO,
    text,
  };
  dispatch(action);
}

不同的是,Redux中只需把action创建函数的结果传给dispatch()方法即可发起一次dispatch过程。

dispatch(addTodo(text));
dispatch(completeTodo(index));

或者创建一个被绑定的action创建函数来自动dispatch

const boundAddTodo = (text) => dispatch(addTodo(text));
const boundCompleteTodo = (index) => dispatch(completeTodo(index));

然后直接调用它们:

boundAddTodo(text);
boundCompleteTodo(index);

store里能直接通过store.dispatch()调用dispatch()方法,但是多数情况下你会使用react-redux提供的connect()帮助器来调用。bindActionCreators()可以自动把多个action创建函数 绑定到dispatch()方法上。

Reducer

Reducers指定了应用状态的变化如何响应actions并发送到store的,记住actions只是描述了有事情发生了这一事实,并没有描述应用如何更新state

设计State的结构

Redux应用中,所有的state都被保存在一个单一对象中。建议在写代码前先想一下这个对象的结构。如何才能以最简的形式把应用的state用对象描述出来?以todo应用为例,需要保存两种不同的数据:

  • 当前选中的任务过滤条件;
  • 完整的任务列表。

通常,这个state树还需要存放其它一些数据,以及一些UI相关的state。这样做没问题,但尽量把这些数据与UI相关的state分开。

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}

开发复杂的应用时,不可避免会有一些数据相互引用。建议你尽可能地把state范式化,不存在嵌套。把所有数据放到一个对象里,每个数据以ID为主键,不同实体或列表间通过ID相互引用数据。把应用的state想像成数据库。这种方法在normalizr文档里有详细阐述。例如,实际开发中,在state里同时存放 todosById: { id -> todo }todos: array<id> 是比较好的方式,本文中为了保持示例简单没有这样处理。

Action处理

reducer就是一个纯函数,接收旧的stateaction,返回新的state

(previousState, action) => newState;

之所以将这样的函数称之为reducer,是因为这种函数与被传入Array.prototype.reduce(reducer, ?initialValue)里的回调函数属于相同的类型。保持reducer纯净非常重要。永远不要在reducer里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如API请求和路由跳转;
  • 调用非纯函数,如Date.now()Math.random()

只要传入参数相同,返回计算得到的下一个state就一定相同。没有特殊情况、没有副作用,没有API请求、没有变量修改,单纯执行计算。

import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters,
} from "./actions";

// ...

function todoReducer(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter,
      });
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false,
          },
        ],
      });
    default:
      return state;
  }
}

不直接修改state中的字段,而是返回新对象。新的todos对象就相当于旧的todos在末尾加上新建的todo。而这个新的todo又是基于action中的数据创建的。

combineReducer

combineReducers()为我们提供了便捷的Reducer合并,所做的只是生成一个函数,这个函数来调用你的一系列reducer,每个reducer根据它们的key来筛选出state中的一部分数据并处理,然后这个生成的函数再将所有reducer的结果合并成一个大的对象。

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c,
});

function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action),
  };
}

具体在TodoApp中,其用法如下:

import { combineReducers } from "redux";

function todosReducer(state = [], action) {
  // ...
}

function visibilityFilterReducer(state = SHOW_ALL, action) {
  // ...
}

const todoReducer = combineReducers({
  visibilityFilter: visibilityFilterReducer,
  todos: todosReducer,
});

Store

Store就是把它们联系到一起的对象。Store有以下职责:

  • 维持应用的state
  • 提供getState()方法获取state
  • 提供dispatch(action)方法更新state
  • 通过subscribe(listener)注册监听器;
  • 通过subscribe(listener)返回的函数注销监听器。

当需要拆分数据处理逻辑时,你应该使用reducer组合 而不是创建多个store。在前一个章节中,我们使用combineReducers()将多个reducer合并成为一个。现在我们将其导入,并传递createStore()

import { createStore } from "redux";
import todoApp from "./reducers";
let store = createStore(todoApp);

createStore()的第二个参数是可选的,用于设置state初始状态。这对开发同构应用时非常有用,服务器端redux应用的state结构可以与客户端保持一致,那么客户端可以将从网络接收到的服务端state直接用于本地数据初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER);

发起Actions

import {
  addTodo,
  toggleTodo,
  setVisibilityFilter,
  VisibilityFilters,
} from "./actions";

// 打印初始状态
console.log(store.getState());

// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() => console.log(store.getState()));

// 发起一系列 action
store.dispatch(addTodo("Learn about actions"));
store.dispatch(addTodo("Learn about reducers"));
store.dispatch(addTodo("Learn about store"));
store.dispatch(toggleTodo(0));
store.dispatch(toggleTodo(1));
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED));

// 停止监听 state 更新
unsubscribe();

Links

上一页
下一页