前端学习笔记(二十一) --Redux学习

335 阅读6分钟

今天学习 redux。学习材料是中文文档和慕课网视频。

1. redux 基础

1.1 动机

react 总是特别提醒自己只是一个视图层,虽然也有可以传递 state 的方式,但是只要传递的方式稍微复杂一点,整个结构就会非常难懂。
为了解决这些状态传递的问题,需要用到 redux。(redux 和 react 本身并没有关系,只是 react 一般需要用到 redux)

1.2 核心概念

  • 为了用 redux 改变 state,需要发起一个 action,action 是一个 JS 对象,描述了要做什么,或者说描述做了什么。
  • action 只是一个用于描述的普通 JS 对象,并不会对 state 做改变,如果要做改变,则需要自己编写一个函数,这个函数被叫做 reducer。reducer 接受一个 state,接受一个 action,对 state 进行处理,返回新的 state。
  • reducer 只能有一个,但是一个应用肯定需要写很多函数,于是编写许多个小的 reducer,再在一个大的 reducer 调用这些小的 reducer。
    以上概念甚至用不上 redux API,这也是为什么 redux 这么小的原因之一。

1.3 三大原则

  1. 整个应用的 state 被存储在一个 object tree 中,然后这个 object tree 也只在唯一一个的 store 中。
  2. state 是只读的。要想改变 state,只能发起一个 action,而 action 也只是一个普通对象而已。这样无论是视图层还是网络请求都无法直接修改 state,而只能发起一个请求,等待之后被统一按顺序处理。
  3. reducer 只能是纯函数。 由于 redux 永远不会更改 state,因此可以使用一些库如 immutable.js。

2. 使用注意

2.1 action 命名规范

action 是把数据从应用传递到 store 的唯一数据来源。一般是这么用的:

const ADD_TODO = "to do"; // 用常量定义字符串,大型应用中可能从模块文件中导入常量
import {ADD_TODO} from "./my_action.js"

// 定义 action
{
  type: ADD_TODO, // 必须有一个 type,值为字符串↑
  text: "今天我打通了幕华祭 N 难度", // 除了 type,所有字段都可随意定义
  ...
}

// 命名规范
// 如果遵循命名规范,则用以下的规范命名,每个属性的含义参照下面的链接
{
  type: ADD_TODO,
  payload: {
    text: "今天我打通了幕华祭 N 难度"
  },
  error: false,
  meta: {}
}

action 的命名规范在此

2.1.1 action 创建函数。

很多 action 的参数来自于应用过程中产生,但是如果就这样把 action 写在 index.js 中就不易于维护,也不方便测试。需要一种方式把 action 存放在单独文件中。于是就有了 actionCreator 的概念。
action 创建函数只是一个普通函数,用于根据不同参数返回一个 action。存放在单独文件中,index.js 每次要创建 action 的时候就使用这个创建函数,导入应用过程中产生的参数。

2.2 reducer

reducer 是一个纯函数这非常重要,除了不能修改参数以外,还不能调用像 Date.now()Math.random() 这种非纯函数。因为 reducer 必须是确定的,只要传递的参数相同,返回的值一定也要相同。

2.3 store

store.subscribe(callback) 的含义为只要 store 发生了改变就会运行 callback。

3. UI 组件和容器组件

一个组件的 render 返回的值可能会很复杂,可以把这些分离出成为一个 UI 组件。

4. redux-thunk

使用 redux-thunk 中间件做异步请求
中间件的引用会让 redux-devtools-extension 的导入方法变更,需要参照 redux-devtools-extension 的官网。

redux-thunk 的作用是能够让 action 不止能是一个对象,还能是一个函数。
换句话说,也就是在 actionCreators 中不止能返回一个对象,还能返回一个函数。

  • 一般的 actionCreator 返回一个包含 type 属性的对象:
  • 使用了 redux-thunk 的 actionCreator 可以为: 乍一看没什么了不起的,这不就只是单纯导出一个函数吗?完全原生 js 语法,不用 redux-thunk 我照样可以做啊。但是实际上在组件中我们可以这么做了: 因为是 action,因此可以被 dispatch。图上的这个 store.dispatch(action) 其实相当于调用 action()
    但是这依然没什么了不起的,导出一个函数再调用 action() 不就行了。这看起来不就只是用 store.dispatch(action) 换掉 action() 吗? 但是好处就是在 actionCreator 中我们可以传一个 dispatch 参数,这样我们就无须在 actionCreators.js 中引入 store 了。

5. 中间件

redux 的中间件存在于 action 和 reducer之间。action 被发起后,进行一些处理(比如异步等副作用),然后再调用 reducer 返回新状态。

6. 副作用

副作用是专门对函数/表达式而言的,是函数式编程的概念:
一个函数/表达式被称为有副作用,如果:

  1. 该函数/表达式修改了函数作用域以外的状态。
  2. 该函数/表达式与外部世界有明显地交互行为。 典型例子如:
  • ajax
  • 修改函数的参数
  • DOM 操作

纯函数一定没有副作用。

7. redux-saga

这个需要生成器的知识,生成器需要迭代器的知识,迭代器需要Symbol的知识。因此去红宝书补了一下这些语法。
redux-saga 用法更加复杂,和 redux-thunk 不同的是,saga 的异步代码是写在单独的文件中。并使用 generator+yield 实现了 async/await。

8. react-redux

react-redux 让 redux 的使用变得非常简单。

  1. 在主文件 index.js 里导入 Provider,通过类似 context 的语法,将 store 传给整个应用。这样子 store 直接在整个应用里通用,而不用每个组件都导入了
  2. 上面说了,react-redux 可以让每个组件里不导入 store 了,那么这个 store 要怎么和 组件连接起来呢?这其实可以分成解决以下两个问题:
    1. 组件如果要访问 store 里的数据,是通过 store.getState().xxx 得到的。
    2. 组件如果要改变 store 里的数据,是通过 store.dispatch(action) 得到的。
    • 也就是说,连接就是要解决 state 和 dispatch 的获取问题:可以看到除了 store 不用再导入,还省去了在 subscribe 里使用 setState 更新 state 的步骤,这里便体现出了 connect 之所以叫 connect 的原因:现在 store 和组件是 connect 在一起的了,而不是每次 store 改变的时候再使用 setState 将组件的 state 和 store 的state 同步。
  3. 由于状态逻辑都被抽取到 mapDispatchToProps 上了,剩下的组件是一个无状态组件。因此其实 react-redux 把组件分成了 UI 组件(定义的组件)和容器组件(connect 后的组件)。