Redux学习笔记(一):基本概念

前言

终于到放寒假,可以安静的学点有用的知识了。

说实话,Redux 基础部分已经学了好几次了。其中看过视频,读过二手文档,但理解的总是模模糊糊(其实就是没理解),这次直接跟着官方文档走一遍。

官方文档就是最好的教材。

Basic Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { createStore } from 'redux'
/**
* This is a reducer, a pure function with (state, action) => state signature.
* It describes how an action transforms the state into the next state.
*
* The only important part is that
* you should not mutate the state object,
* but return a new object if the state changes.
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counter)

// You can use subscribe() to update the UI in response to state changes.
store.subscribe(() => console.log(store.getState()))

// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

基础示例展示了这几个东西:

  • counter 是一个 reducer,reducer 是一个 pure function,接收 state 和 action,并返回一个新的 state,函数里面定义好会有哪些 action,不同的 action 对应不同的操作
  • createStore 接收 reducer,并创建一个 store
  • store 有三个 API
    • subscribe(订阅。state 改变后被调用)
    • dispatch(分发。接收一个 action,自动调用 reducer,并将 action 传给 reducer)
    • getState (获取 state)

这么看来感觉也就 reducer 和 dispatch 没那么直白。

In a typical Redux app, there is just a single store with a single root reducing function. As your app grows, you split the root reducer into smaller reducers independently operating on the different parts of the state tree. This is exactly like how there is just one root component in a React app, but it is composed out of many small components.

这段话表示,我们可以根据应用将 reducer 切分成许多独立的小 reducers,每一个小的 reducer 负责一部分功能,就像 React 的根组件和子组件一样。

不同功能之间互不影响,这样确实有利于维护。

Three Principles

Single source of truth(单一数据源)

The state of your whole application is stored in an object tree within a single store.

一个 app 中只有一个 state tree。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log(store.getState())
/* Prints
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
*/

State is read-only(State 为 只读)

The only way to change the state is to emit an action, an object describing what happened.

通过 dispatch 接受一个 action 的描述对象,来改变 state,而不是直接修改state。

1
2
3
4
5
6
7
8
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
})
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
})

Changes are made with pure functions(通过纯函数来改变)

To specify how the state tree is transformed by actions, you write pure reducers.

不同的功能用不同的 reducer 来管理,最终将所有的 reducers 合并在一起。

1
2
3
4
5
6
7
8
9
function visibilityFilter(state = 'SHOW_ALL', action) {
// ...
}
function todos(state = [], action) {
// ...
}
import { combineReducers, createStore } from 'redux'
const reducer = combineReducers({ visibilityFilter, todos })
const store = createStore(reducer)

看多了就知道 reducer 和 dispatch 是个啥东西了,没那么复杂。

Basic Tutorial

Actions

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

Actions 用来表示一个动作,这个动作告诉我们 state 将要如何变化。在使用时,只需要将 action 发送给 store.dispatch()

例子:

1
2
3
4
5
6
const ADD_TODO = 'ADD_TODO';

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

Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants. Once your app is large enough, you may want to move them into a separate module.

Actions Creators

Action creators 是一个函数,用来接收动作描述,并返回一个 action。

1
2
3
4
5
6
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}

现在就可以直接通过 Action creators 来获取 action:

1
2
dispatch(addTodo(text))
dispatch(completeTodo(index))

另外,我们可以创建一个 bound action creator 来自动分派 action:

1
2
3
4
5
6
const boundAddTodo = text => dispatch(addTodo(text));
const boundCompleteTodo = index => dispatch(completeTodo(index));

// 直接调用即可
boundAddTodo(text)
boundCompleteTodo(index)

写中文太耗时间了,原文就足够说明本质,所以下面直接复制官网文档中比较重点的段落。

Reducers

Reducers specify how the application’s state changes in response to actions sent to the store. Remember that actions only describe what happened, but don’t describe how the application’s state changes.

It’s very important that the reducer stays pure. Things you should never do inside a reducer:

  • Mutate its arguments;
  • Perform side effects like API calls and routing transitions;
  • Call non-pure functions, e.g. Date.now() or Math.random().

Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.

下面是将 todosvisibilityFilter 这两个 reducers 合并为一个 root reducer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
}
}

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

function todoApp(state = {}, action) {

// 这里是一个对象,属性名为对应 reducers 的名字,值为对应 reducers 返回的 state
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}

也可以直接使用 combineReducers()来将这两个 reducers 合并在一起:

1
2
3
4
5
6
7
8
import { combineReducers } from 'redux'

const todoApp = combineReducers({
visibilityFilter,
todos
})

export default todoApp

注意:现在的 state tree 是一个对象,这个对象的每一个属性都是 reducers 的名字,其值是每一个 reducers 所独自管理的 state(可以是任何类型)。

Store

Store 的职责:

Data Flow

Redux architecture revolves around a strict unidirectional data flow(严格的单向数据流).

既然是单向数据流,那么所有数据都遵循相同的数据生命周期。

The data lifecycle in any Redux app follows these 4 steps:

  1. You call store.dispatch(action).

You can call store.dispatch(action) from anywhere in your app, including components and XHR callbacks, or even at scheduled intervals.

  1. The Redux store calls the reducer function you gave it.

  2. The root reducer may combine the output of multiple reducers into a single state tree.

这条也解释了前面的疑问,combineReducers()不只是合并了 reducers,还将 reducers 的返回值也合并到了一起。

  1. The Redux store saves the complete state tree returned by the root reducer.