Front End Development Libraries(五)

freeCodeCamp —- Front End Development Libraries


Redux

1. 创建一个 Redux Store

Redux 是一个状态管理框架,可以与包括 React 在内的许多不同的 Web 技术一起使用。

在 Redux 中,有一个状态对象负责应用程序的整个状态, 这意味着如果你有一个包含十个组件且每个组件都有自己的本地状态的 React 项目,那么这个项目的整个状态将通过 Redux store 被定义为单个状态对象, 这是学习 Redux 时要理解的第一个重要原则:Redux store 是应用程序状态的唯一真实来源。

这也意味着,如果应用程序想要更新状态,只能通过 Redux store 执行。 单向数据流可以更轻松地对应用程序中的状态进行监测管理。


Redux store 是一个保存和管理应用程序状态的state。 可以使用 Redux 对象中的 createStore() 来创建一个 redux store。 此方法将 reducer 函数作为必需参数, reducer 函数将在后面的挑战中介绍。该函数已在代码编辑器中为你定义, 它只需将 state 作为参数并返回一个 state 即可。

声明一个 store 变量并把它分配给 createStore() 方法,然后把 reducer 作为一个参数传入即可。

2. 从 Redux Store 获取状态

Redux store 对象提供了几种与之交互的方法, 比如,可以使用 getState() 方法检索 Redux store 对象中保存的当前 state


在代码编辑器中可以将上一个挑战中的代码更简洁地重写, 在 store 中使用 store.getState() 检索state,并将其分配给新变量 currentState

3. 定义一个 Redux Action

由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由 dispatch action 触发, action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后更新相应的状态。 有时,Redux action 也会携带一些数据。 例如,在用户登录后携带用户名, 虽然数据是可选的,但 action 必须带有 type 属性,该属性表示此 action 的类型。

我们可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store, 然后 store 根据发生的 action 进行状态的更新。


编写 Redux action 就像声明具有 type 属性的对象一样简单, 声明一个对象 action 并为它设置一个属性 type,并将它的值设置成字符串'LOGIN'

4. 定义一个 Action Creator

创建 action 后要将 action 发送到 Redux store,以便它可以更新其状态。 在 Redux 中,可以定义动作创建器来完成此任务, action creator 只是一个返回动作的 JavaScript 函数。 换句话说,action creator 创建表示动作事件的对象。


定义名为 actionCreator() 的函数,该函数在调用时返回 action 对象。

5. 分发 Action Event

dispatch 方法用于将 action 分派给 Redux store, 调用 store.dispatch(),并传递从一个 action creator 返回的值,将一个 action 送回给 store。

回想一下,action creator 返回一个具有 type 属性的对象,该属性指定已发生的动作。 然后该方法会将一个 action 对象发送到 Redux store。 基于上一个挑战的示例,下面的行是等效的,两者都会调度类 LOGIN 类型的 action:

1
2
store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });

代码编辑器中的 Redux store 具有初始化状过的 state,包含 login 属性当前设置为 false的对象, 还有一个名为 loginAction() 的 action creator,它返回类型为 LOGIN 的 action, 然后通过调用 dispatch 方法将 LOGIN 的 action dispatch 给 Redux store,并传入 loginAction() 创建的 action。

6. 在 Store 里处理 Action

在一个 action 被创建并 dispatch 之后,Redux store 需要知道如何响应该操作。 这就是 reducer 函数存在的意义。 Redux 中的 Reducers 负责响应 action 然后进行状态的修改。 reducer 将 state 和 action 作为参数,并且它总是返回一个新的 state。 我们要知道这是 reducer 的唯一的作用。 它不应有任何其他的作用:比如它不应调用 API 接口,也不应存在任何潜在的副作用。 reducer 只是一个接受状态和动作,然后返回新状态的纯函数。

Redux 的另一个关键原则是 state 是只读的。 换句话说,reducer 函数必须始终返回一个新的 state,并且永远不会直接修改状态。 Redux 不强制改变状态,但是需要在 reducer 函数的代码中强制执行它, 以后的挑战会练习这一点。


代码编辑器中具有前面的示例以及一个 reducer 函数。 需要完善 reducer 函数的内容,使得它如果收到类型为'LOGIN'的action,它将返回一个将 login 设置为 true 的 state 对象。 否则,它就返回当前的 state。 请注意,当前 state 和 dispatch 的 action 将被传递给 reducer,因此可以使用 action.type 直接获取 action 的类型。

7. 使用 Switch 语句处理多个 Actions

可以定义 Redux store 处理多种 action 类型。 假设在 Redux store 中管理用户身份验证。 希望用状态表示用户登录和注销。 使用 state 的 authenticated 属性表示它。 还需要使用 action creators 创建与用户登录和用户注销相对应的 action,以及 action 对象本身。


代码编辑器为你创建了 store、actions、action creators。 通过编写 reducer 函数来处理多个身份验证操作。 可以在 reducer 里通过使用 JavaScript 的 switch 来响应不同的 action 事件。 这是编写 Redux reducers 的标准模式。 switch 语句应该切换 action.type 并返回适当的身份验证状态。

注意: 此时,不要担心 state 的不变性,因为在这个示例中它很小而且很简单。 所以对于每个操作都可以返回一个新对象,比如 {authenticated: true}。 另外,不要忘记在 switch 语句中写一个 default case,返回当前的 state。 这是很重要的,因为当程序有多个 reducer,当每一个 action 被 dispatch 时它们都会运行,即使 action 与该 reducer 无关。 在这种情况下,你要确保返回当前的 state

8. 使用 const 声明 Action Types

在使用 Redux 时的一个常见做法是将操作类型指定为只读,然后在任何使用它们的地方引用这些常量。 可以通过将 action types 使用 const 声明重构你正在使用的代码。


将 LOGIN 和 LOGOUT 声明为 const 的值,并为它们分别分配字符串 'LOGIN' 和 'LOGOUT'。 然后,编辑 authReducer() 和 action creators 来引用这些常量而不是字符串值。

注意: 通常以全部大写形式写出常量,这也是 Redux 的标准做法。

9. 注册 Store 监听器

在 Redux store 对象上访问数据的另一种方法是 store.subscribe()。 这允许将监听器函数订阅到 store,只要 action 被 dispatch 就会调用它们。 这个方法的一个简单用途是为 store 订阅一个函数,它只是在每次收到一个 action 并且更新 store 时记录一条消息。


编写一个回调函数,每次 store 收到一个 action 时,它会递增全局变量 count,并将此函数传递给 store.subscribe() 方法。 将会看到 store.dispatch() 连续三次被调用,每次都直接传入一个 action 对象。 观察 dispatch action 之间的控制台输出,看看是否发生了更新。

10. 组合多个 Reducers

当应用程序的状态开始变得越来越复杂时,可能会将 state 分成多个块。 相反,请记住 Redux 的第一个原则:所有应用程序状态都保存在 store 中的一个简单的 state 对象中。 因此,Redux 提供 reducer 组合作为复杂状态模型的解决方案。 定义多个 reducer 来处理应用程序状态的不同部分,然后将这些 reducer 组合成一个 root reducer。 然后将 root reducer 传递给 Redux createStore()方法。

为了将多个 reducer 组合在一起,Redux 提供了combineReducers()方法。 该方法接受一个对象作为参数,在该参数中定义一个属性,该属性将键与特定 reducer 函数关联。 Redux 将使用给定的键值作为关联状态的名称。

通常情况下,当它们在某种程度上是独一无二的,为每个应用程序的 state 创建一个 reducer 是一个很好的做法。 例如,在一个带有用户身份验证的记笔记应用程序中,一个 reducer 可以处理身份验证而另一个处理用户提交的文本和注释。 对于这样的应用程序,可能会编写 combineReducers() 方法,如下所示:

1
2
3
4
const rootReducer = Redux.combineReducers({
auth: authenticationReducer,
notes: notesReducer
});

现在,notes 键将包含与注释相关联的所有状态,并由 notesReducer 处理。 这就是组合多个 reducer 来管理更复杂的应用程序状态的方式, 在此示例中,Redux store 中保存的状态将是一个包含 auth 和 notes 属性的简单对象。


代码编辑器中提供了 counterReducer() 和 authReducer() 函数以及 Redux store。 使用 Redux.combineReducers() 方法编写完成 rootReducer() 函数。 将 counterReducer 分配给一个叫做 count 的键,将 authReducer 分配给一个叫做 auth 的键。

11. 发送 Action Data 给 Store

到目前为止,你已经学会了如何将 action dispatch 给 Redux store,但到目前为止,这些 action 并未包含除 type之外的任何信息。 还可以和 action 一起发送特定数据。 事实上,这是非常常见的,因为 action 通常源于一些用户交互,并且往往会携带一些数据, Redux store 经常需要知道这些数据。


在代码编辑器中定义了一个基础的 notesReducer() 和 addNoteText() action creator。 完成 addNoteText() 函数的主体,这样它就会返回一个 action 对象。 该对象应该包含一个 type 属性,其值为 ADD_NOTE,还有一个传入 action creator 的属性为 text 的 note 数据。 当调用 action creator 时,需要传入可以访问该对象的特定笔记信息。

接下来,完成在 notesReducer() 中编写的 switch 语句。 需要添加一个处理 addNoteText() 操作的选项。 如果 action 的类型为 ADD_NOTE,就应该触发这个 case,并且它应该在传入的 action 上返回 text 属性作为新的 state

这个 action 将在代码底部发送。 一旦完成后,运行代码并观察控制台。 这就是将特定于 action 的数据发送到 store 并在更新 store state时使用它所需的全部内容。

12.使用中间件处理异步操作

目前为止的挑战都在避免讨论异步操作,但它们是 Web 开发中不可避免的一部分。 在某些时候,需要在 Redux 应用程序中使用异步请求,那么如何处理这些类型的请求? Redux 中间件专为此目的而设计,称为 Redux Thunk 中间件。 这里简要介绍如何在 Redux 中使用它。

如果要使用 Redux Thunk 中间件,请将其作为参数传递给 Redux.applyMiddleware()。 然后将此函数作为第二个可选参数提供给 createStore() 函数, 看一下编辑器底部的代码。 然后,要创建一个异步的 action,需要在 action creator 中返回一个以 dispatch 为参数的函数。 在这个函数中,可以 dispatch action 并执行异步请求。

在此示例中,使用 setTimeout() 模拟异步请求。 通常在执行异步行为之前 dispatch action,以便应用程序状态知道正在请求某些数据(例如,这个状态可以显示加载图标)。 然后,一旦收到数据,就会发送另一个 action,该 action 的 data 是请求返回的数据同时也代表 API 操作完成。

请记住,正在将 dispatch 作为参数传递给这个特殊的 action creator。 需要 dispatch action 时只需将 action 直接传递给 dispatch,中间件就可以处理其余的部分。


在 handleAsync() 的 action creator 中编写两个 dispatch 事件。 在 setTimeout()(模拟 API 调用)之前 dispatch requestingData()。 然后在收到(模拟)数据后,dispatch receivedData() action,传入这些数据。 现在已经知道如何处理 Redux 中的异步操作, 其他所有操作都继续像以前一样。

13. 用 Redux 写一个计数器

现在已经了解了 Redux 的所有核心原则! 已经了解了如何创建 action 和 action creator,创建 Redux store,通过 store dispatch action,以及使用纯粹的 reducer 设计状态更新。 甚至已经看到过如何使用 reducer 组合管理复杂状态并处理异步操作。 这些例子很简单,但这些概念是 Redux 的核心原则。 如果已经理解这些,那么就可以开始构建自己的 Redux 应用了。 接下来的挑战包括关于 state 不变性的一些细节,但是,这里是对到目前为止学到的所有内容的回顾。


在本课程中,将从头开始使用 Redux 实现一个简单的计数器。 基本知识在代码编辑器中提供,但你必须完成细节! 使用提供的名称定义 incAction 和 decAction action creator,counterReducer()INCREMENTDECREMENT action 类型,以及 Redux store。 一旦完成,应该能够 dispatch INCREMENT 或 DECREMENT 动作来增加或减少 store 中保存的状态。

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
const INCREMENT = "INCREMENT"; // define a constant for increment action types
const DECREMENT = "DECREMENT"; // define a constant for decrement action types

// define the counter reducer which will increment or decrement the state based on the action it receives
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;

case DECREMENT:
return state - 1;

default:
return state;
}
};

// define an action creator for incrementing
const incAction = () => {
return {
type: INCREMENT
};
};

// define an action creator for decrementing
const decAction = () => {
return {
type: DECREMENT
};
};

// define the Redux store here, passing in your reducers
const store = Redux.createStore(counterReducer);

14. 永不改变状态

这些最后的挑战描述了在 Redux 中强制执行状态不变性关键原则的几种方法。 不可变状态意味着永远不直接修改状态,而是返回一个新的状态副本。

如果拍摄 Redux 应用程序一段时间状态的快照,会看到类似 state 1state 2state 3state 4...等等,每个状态可能与最后一个状态相似,但每个状态都是一个独特的数据。 事实上,这种不变性提供了时间旅行调试等功能。

Redux 并没有主动地在其 store 或者 reducer 中强制执行状态不变性,责任落在程序员身上。 幸运的是,JavaScript(尤其是 ES6)提供了一些有用的工具,可以用来强制执行状态的不变性,无论是 stringnumberarray 或 object。 请注意,字符串和数字是原始值,并且本质上是不可变的。 换句话说,3 总是 3, 不能改变数字 3 的值。 然而,array 或 object 是可变的。 实际上,状态可能会包括 array 或 object,因为它们经常用来描述一些代表信息的数据结构。


代码编辑器中有一个 store 和 reducer,用于管理待办事项。 完成 reducer 里的 ADD_TO_DO 用例,使其可以将一个新的待办事项附加到 state。 使用通过标准 JavaScript 或 ES6 的一些方法实现此目的。 看看是否可以找到一种方法来返回一个新数组,其中来自 action.todo 的项目添加到数组的末尾。

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
const ADD_TO_DO = "ADD_TO_DO";

// A list of strings representing tasks to do:
const todos = [
"Go to the store",
"Clean the house",
"Cook dinner",
"Learn to code"
];

const immutableReducer = (state = todos, action) => {
switch (action.type) {
case ADD_TO_DO:
// don't mutate state here or the tests will fail

return state.concat(action.todo);
// or return [...state, action.todo]

default:
return state;
}
};

// an example todo argument would be 'Learn React',
const addToDo = todo => {
return {
type: ADD_TO_DO,
todo
};
};

const store = Redux.createStore(immutableReducer);

保存!你的代码已保存到你的浏览器本地存储中。

  1. 前端开发库
  2. Redux

15. 在数组中使用扩展运算符

ES6 中有助于在 Redux 中强制执行状态不变性的一个解决方案是扩展运算符:...。 扩展运算符具有很多的应用,其中一种非常适合通过一个已有的数组生成一个新数组。 这是相对较新的但常用的语法。 例如,如果你有一个数组 myArray 并写:

1
let newArray = [...myArray];

newArray 现在是 myArray 的克隆。 两个数组仍然分别存在于内存中。 如果你执行像 newArray.push(5) 这样的变异, myArray 不会改变。 ... 有效将 myArray 中的值展开到一个新数组中。 要克隆数组,但在新数组中添加其他值,可以编写 [...myArray, 'new value']。 这将返回一个由 myArray 中的值和字符串 new value(作为最后一个值)组成的新数组。 扩展语法可以像这样在数组组合中多次使用,但重要的是要注意它只是生成数组的浅拷贝副本。 也就是说,它只为一维数组提供不可变的数组操作。


添加待办事项时,使用 spread 运算符返回新的状态副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const immutableReducer = (state = ["Do not mutate state!"], action) => {
switch (action.type) {
case "ADD_TO_DO":
// don't mutate state here or the tests will fail
let arr = [...state, action.todo];
return arr;
default:
return state;
}
};

const addToDo = todo => {
return {
type: "ADD_TO_DO",
todo
};
};

const store = Redux.createStore(immutableReducer);

16. 从数组中删除项目

是时候练习从数组中删除项目了。 扩展运算符也可以在这里使用。 其他有用的JavaScript方法包括 slice() 和 concat()


reducer 和 action creator 被修改为根据项目的索引从数组中删除一个项目。 完成编写 reducer 以便返回一个新的状态数组,并删除特定索引处的项目。

17. 使用 Object.assign 拷贝对象

最后几个挑战适用于数组,但是当状态是 object 时,有一些方法可以实现状态不变性。 处理对象的一个常用的方法是 Object.assign()。 Object.assign() 获取目标对象和源对象,并将源对象中的属性映射到目标对象。 任何匹配的属性都会被源对象中的属性覆盖。 通常用于通过传递一个空对象作为第一个参数,然后是要用复制的对象来制作对象的浅表副本。 下面是一个示例:

1
const newObject = Object.assign({}, obj1, obj2);

这会创建 newObject 作为新的 object,其中包含 obj1 和 obj2 中当前存在的属性。


Redux state 和 actions 被修改为处理 state 的 object 。 编辑代码,为类型为 ONLINE 的 actions 返回一个新的 state 对象,这个类型将 status 属性设置为 online 字符串。 尝试使用 Object.assign() 来完成挑战。