Redux学习笔记

FLowUs邀请链接:https://flowus.cn/login?code=AXNU63
FlowUs邀请码:AXNU63


介绍

Redux 是针对 JavaScript 应用程序的可预测状态容器。

几个核心概念

Action

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。
一般来说你会通过 store.dispatch() 将 action 传到 store。
Action 本质上是 JavaScript 普通对象。
我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
多数情况下,type 会被定义成字符串常量。
当应用规模越来越大时,建议使用单独的模块或文件来存放 action。

我们应该尽量减少在 action 中传递的数据。

Action创建函数,就是生成 action 的方法。
Redux 中只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程。
(也可以不用创建函数,直接写出来一个action结构)

actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

reducer

reducer概念

Reducers指定了应用状态的变化如何响应 actions,并把变化的结果发送到store
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state

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

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

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

指定初始状态

Redux 首次执行时,state 为 undefined,此时我们可借机设置并返回应用的初始 state(使用'undefined'判断,或者使用ES6的参数默认值语法)

不要修改state,而是返回新的state

不要修改 state!
  • 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }) 因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。
  • 你也可以开启对 ES7 提案对象展开运算符的支持, 从而使用 { ...state, ...newState } 达到相同的目的。

在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state!

Object.assign() 是 ES6 特性,但多数浏览器并不支持。
你要么使用 polyfill,Babel 插件,或者使用其它库如 _.assign() 提供的帮助方法。

switch 语句并不是严格意义上的样板代码。
Flux 中真实的样板代码是概念性的:更新必须要发送、Store 必须要注册到 Dispatcher、Store 必须是对象(开发同构应用时变得非常复杂)。
为了解决这些问题,Redux 放弃了 event emitters(事件发送器),转而使用纯 reducer。

reducer 合成

原本的reducer中更新state的一部分拆分到一个单独的函数里

多个reducer

  • 可以开发一个函数来做为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。
  • 主 reducer 并不需要设置初始化时完整的 state。
  • 初始时,如果传入 undefined, 子 reducer 将负责返回它们的默认值。
function xxx(state = {}, action) {
  return {
    redcuer1: redcuer1(state.state1, action),
    reducer2: reducer2(state.state2, action),
    ......
  }
}

combineReducers()

combineReducers() 所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。没有任何魔法。
正如其他 reducers,如果 combineReducers() 中包含的所有 reducers 都没有更改 state,那么也就不会创建一个新的对象。

combineReducers 接收一个对象,可以把所有顶级的 reducer 放到一个独立的文件中,通过 export 暴露出每个 reducer 函数,然后使用 import * as reducers 得到一个以它们名字作为 key 的 object

import { combineReducers } from 'redux'
import * as reducers from './reducers'

const todoApp = combineReducers(reducers)

store

store概念

Action,State,Reducer 三者是相互独立的,Store 就是把它们联系到一起的对象

在 Redux 应用中,所有的 state 都被保存在一个单一对象中,即store只有一个!
Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。

store负责接收dispatch请求,然后调用reducer去更新state
只需把action传给store的dispatch方法即可发起一次dispatch过程

store的职责:

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

创建store

import { createStore } from 'redux'

我们使用 combineReducers() 将多个 reducer 合并成为一个。现在我们将其导入,并传递 createStore()

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。

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

let store = createStore(xxxApp)

数据的流动

严格的单向数据流是 Redux 架构的设计核心
这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免使用多个且独立的无法相互引用的重复数据。

Redux 应用中数据的生命周期遵循下面 4 个步骤

  • 调用 store.dispatch(action)
    • 你可以在任何地方调用
  • Redux store 调用传入的 reducer 函数
    • Store 会把两个参数传入 reducer: 当前的 state 树和 action
    • reducer 是纯函数。它仅仅用于计算下一个 state。它应该是完全可预测的
  • 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
    • 根 reducer 的结构完全由你决定。
    • Redux 原生提供 combineReducers() 辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。【你可以选择不用】
  • Redux store 保存了根 reducer 返回的完整 state 树
    • 这个新的树就是应用的下一个 state!
    • 所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。

结合React使用

强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
尽管如此,Redux 还是和 React 和 Deku 这类库搭配起来用最好,因为这类库允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。

直接使用

react-redux

安装:npm install --save react-redux
注:如果你不使用 npm,你也可以从 unpkg 获取最新的 UMD 包(包括开发环境包和生产环境包)。如果你用 <script> 标签的方式引入 UMD 包,那么它会在全局抛出 window.ReactRedux 对象。

容器组件(Smart/Container Components)、展示组件(Dumb/Presentational Components)

容器组件和展示组件相分离:https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

展示组件 容器组件
作用 描述如何展现(骨架、样式) 描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源 props 监听 Redux state
数据修改 从 props 调用回调函数 向 Redux 派发 actions
调用方式 手动 通常由 React Redux 生成

大部分的组件都应该是展示型的,但一般需要少数的几个容器组件把它们和 Redux store 连接起来。
并不意味着容器组件必须位于组件树的最顶层。
如果一个容器组件变得太复杂(例如,它有大量的嵌套组件以及传递数不尽的回调函数),那么在组件树中引入另一个容器

技术上讲,容器组件就是使用 store.subscribe() 从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。
你可以手工来开发容器组件,但建议使用 React Redux 库的 connect() 方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。(这样你就不必为了性能而手动实现 React 性能优化建议 中的 shouldComponentUpdate 方法。)

用 connect() 生成容器组件

使用 connect() 前,需要先定义 mapStateToProps 这个函数来指定如何把当前 Redux store state 映射到展示组件的 props 中。

除了读取 state,容器组件还能分发 action。类似的方式,可以定义 mapDispatchToProps() 方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。

例如:

import { connect } from 'react-redux'

const mapStateToProps = state => {
  return {
    count: state
  }
}

const mapDispatchToProps = dispatch => {
  return {
    increment: data => {
      console.log('container, increment...', data);
      dispatch(createIncrementAction(data))
    },
    decrement: data => {
      console.log('container, decrement...', data);
      dispatch(createDecrementAction(data))
    },
    incrementAsync: (data, time) => {
      dispatch(createIncrementAsyncAction(data, time))
    }
  }
}

const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI) // CountUI是展示组件

传入store

所有容器组件都可以访问 Redux store,所以可以手动监听它。一种方式是把它以 props 的形式传入到所有容器组件中。但这太麻烦了,因为必须要用 store 把展示组件包裹一层,仅仅是因为恰好在组件树中渲染了一个容器组件。

建议的方式是使用指定的 React Redux 组件 <Provider> 来 魔法般的 让所有容器组件都可以访问 store,而不必显式地传递它。只需要在渲染根组件时使用即可。

posted @ 2022-01-24 13:28  wbytts  阅读(49)  评论(0编辑  收藏  举报