Redux 学习笔记2——基础教程

193 阅读4分钟

Redux 是十分简单的,如果有 Flux 的基础的话,当真没有任何学习难度。即使是新接触的人,也并不难。本文将官网上的基础教程整理成文档,按照教程一步步实现一个 Todo App.

Action

action是一个信息的载体,用于向 store 传输信息。也是 store 信息的唯一来源。通过 store.dispach() 来进行发布。

下面代码是 action 的一个示例

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

Action 是一个普通的 object 对象,必须有一个 type 属性用于标识是什么类型。type 是一个字符串常量,如果 type 类型很多时,可以单独放到一个文件来存储。

除了 type 外,action 中的其他的结构都可以按照自己的需求进行编写,但是原则上是数据结构和内容尽量简洁。

Action 制造器

Action 制造器:一个函数——传入参数,返回一个 action。

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

完整的 action.js

/*
 * action types
 */

export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * other constants
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action creators
 */

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

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

Reducer

Reducer 在 store 接收到 action 的时候,state 如何更改。Actions 只是告知发生了什么,但是不具体描述 state 如何更改。

设计 state

在 Redux 中,应用的所有 state 都存储在一个 object 中,编写 state 树需要尽量设计最简洁的格式。

在 todolist 一共需要做两个事:

  1. 过滤器
  2. 整个 todolist 的数据
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
处理 actions

Reducer 是一个纯函数。这个函数接收前一个 state 和 actions, 经过处理,返回一个新的 state。

(previousState, action) => newState

Reducer 不能做的事情:

  1. 改变参数;
  2. 产生副作用,例如接口请求和路由过渡;
  3. 请求非纯函数,例如日期和随机数函数。

相同的入参,通过计算,产生相同的返回 state,无意外,无副作用,没有 api 请求,不发生突变,就是一个纯计算函数。

首先写一个 reducer 函数,定义一个初始的 state 值,因为 redux 会首先使用一个 undefined state,我们可以通过这个契机,返回初始 state。

import { VisibilityFilters } from './actions'

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
}

function todoApp(state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }

  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

如果应用 ES6 的语法,可以给参数设置一个初始值,这样可以处理 state 为 undefined 的状态。代码更加简洁。

function todoApp(state = initialState, action) {
  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}

接下来,处理 SET_VISIBILITY_FILTER 类型的 action。

import {
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'

...

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

以上代码需要注意的:

  1. 不改变原始的 state 值,我们应用 object.assign 函数,将前一个 state 和 新改变的 state 存储在一个空对象中返回。我们同样可以应用 ES6 新语法来完成这步操作。
  2. 在 default 的状态下,返回前一个 state,真很重要。

在应用变得复杂的时候,我们可以通过拆分细化 reducer 函数,然后应用 redux 的一个特殊函数 combineReducers, 将细化后的 reducer 进行合并返回。

以下为官网上完整的 reducer 代码:

import { combineReducers } from 'redux'
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

Store

前面讲到,actions 是描述发生了什么,reducers 根据 actions 进行处理,返回新的 actions。

store 将上述变化集成起来,主要有五种作用:

  1. 存储 state;
  2. 通过 getState() 获取 state;
  3. 通过 dispatch(action) 更新 state;
  4. 通过 subscribe(listener) 注册监听;
  5. 处理为注册的监听。

需要注意的是,一个应用只有一个 store, 这是前面讲过的三个原则之一: 单一数据源。如果需要进行业务拆分的话,就拆分 reducer 处理函数。

可以通过上步生产的 reducer 生成需要的 store。

import { createStore } from 'redux'
import todoApp from './reducers'

const store = createStore(todoApp)

数据流

Redux 框架遵循一个严格的数据流:所有的数据都是严格单向流动的。在这个严格数据流的约束下,你的状态管理将变得更加简单,可控,可预测。

数据流一共有以下四个部分:

  1. store.dispatch(action) 告诉 store 发生了什么,action 就是对这个事件的描述。

  2. store 监听这个变化,并且调用 reducer 函数,处理这个变化,返回新的 state。

  3. 根 reducer 将多个 reducer 合并成单一的 state 树。

  4. store 根据根 reducer 的返回,存储一个完整的 state 树。