揭秘 Redux 设计思想与工作原理

Redux 背后的架构思想——认识 Flux 架构

Flux架构
架构模式,应用处理数据的模式

  • View(视图层)
  • Action(动作)
  • Dispatcher(派发器)
  • Store(数据层)

工作流程:
用户与 View 之间产生交互,通过 View 发起一个 Action;Dispatcher 会把这个 Action 派发给 Store,通知 Store 进行相应的状态更新。Store 状态更新完成后,会进一步通知 View 去更新界面(单向数据流)

image.png

Flux 架构到底解决了什么问题

MVC 模式在前端场景下的局限性
双向数据流MVC模式架构下,View和Model能够相互通信,这会导致数据流向无法预测,容易引发bug

Redux 关键要素与工作流回顾

image.png
Redux 主要由 3 部分组成:Store、Reducer 和 Action

  • Store:它是一个单一的数据源,而且是只读的
  • Action 人如其名,是“动作”的意思,它是对变化的描述
  • Reducer 是一个函数,它负责对变化进行分发和处理,最终将新的数据返回给 Store

工作流程:
在 Redux 的整个工作过程中,数据流是严格单向的。如果你想对数据进行修改,只有一种途径:派发 Action。Action 会被 Reducer 读取,Reducer 将根据 Action 内容的不同执行不同的计算逻辑,最终生成新的 state(状态),这个新的 state 会更新到 Store 对象里,进而驱动视图层面作出对应的改变。

Redux 是如何工作的

image.png

  • applyMiddleware.js:中间件模块
  • bindActionCreators.js:用于将传入的 actionCreator 与 dispatch 方法相结合,揉成一个新的方法
  • combineReducers.js:用于将多个 reducer 合并起来
  • compose.js:用于把接收到的函数从右向左进行组合
  • createStore.js:使用 Redux 时最先调用的方法,它是整个流程的入口,也是 Redux 中最核心的 API

    createStore.js

    ```javascript // 引入 redux import { createStore } from ‘redux’

// 创建 store const store = createStore( reducer, initial_state, applyMiddleware(middleware1, middleware2, …)

);

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/152077/1612944112415-773e074d-8b69-4ecf-a213-ca6888373b66.png#align=left&display=inline&height=991&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1981&originWidth=1403&size=282461&status=done&style=none&width=701.5)
  2. <a name="FYSpr"></a>
  3. #### Redux 工作流的核心:dispatch 动作
  4. dispatch 这个动作刚好能把 actionreducer store 这三位“主角”给串联起来<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/152077/1613356304969-f000b797-b0b2-4672-b1b7-971d5c294748.png#align=left&display=inline&height=411&margin=%5Bobject%20Object%5D&name=image.png&originHeight=821&originWidth=856&size=91744&status=done&style=none&width=428)
  5. - 通过“上锁”避免“套娃式”的 dispatch(避免再reducer中手动调用dispatch,造成死循环)
  6. ```javascript
  7. try {
  8. // 执行 reducer 前,先“上锁”,标记当前已经存在 dispatch 执行流程
  9. isDispatching = true
  10. // 调用 reducer,计算新的 state
  11. currentState = currentReducer(currentState, action)
  12. } finally {
  13. // 执行结束后,把"锁"打开,允许再次进行 dispatch
  14. isDispatching = false
  15. }

image.png

  • 触发订阅的过程

    1. // 触发订阅
    2. const listeners = (currentListeners = nextListeners);
    3. for (let i = 0; i < listeners.length; i++) {
    4. const listener = listeners[i];
    5. listener();
    6. }
    • subscribe 是如何与 Redux 主流程结合的呢

首先,我们可以在 store 对象创建成功后,通过调用 store.subscribe 来注册监听函数,也可以通过调用 subscribe 的返回函数来解绑监听函数,监听函数是用 listeners 数组来维护的;当dispatch action 发生时,Redux 会在 reducer 执行完毕后,将 listeners 数组中的监听函数逐个执行。这就是 subscribe 与 Redux 主流程之间的关系
image.png

  • 为什么会有 currentListeners 和 nextListeners 两个 listeners 数组

currentListeners 在此处的作用,就是为了记录下当前正在工作中的 listeners 数组的引用,将它与可能发生改变的 nextListeners 区分开来,以确保监听函数在执行过程中的稳定性。


Redux 的三大原则:

  • 单一数据源
    • 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store
  • state是只读的
    • 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  • 使用纯函数来执行修改
    • 为了描述 action 如何改变 state tree ,你需要编写 reducers

Redux 的执行流程:
用户触发view层派发一个action,action被reducers读取后生成新的state,state合并到store上,view订阅store数据的变更,当store数据发生变化是,驱动view发生改变。

从 Redux 中间件实现原理切入,理解“面向切面编程”

认识 Redux 中间件

中间件的引入

redux 对外暴露了 applyMiddleware 这个方法。applyMiddleware 接受任意个中间件作为入参,而它的返回值将会作为参数传入 createStore,这就是中间件的引入过程

  1. // 引入 redux
  2. import { createStore, applyMiddleware } from 'redux'
  3. ......
  4. // 创建 store
  5. const store = createStore(
  6. reducer,
  7. initial_state,
  8. applyMiddleware(middleware1, middleware2, ...)
  9. );

中间件的工作模式

redux-thunk——经典的异步 Action 解决方案

它允许我们以函数的形式派发一个 action

Redux 中间件是如何与 Redux 主流程相结合的?

image.png
Redux 中间件的工作模式

  1. 中间件的执行时机,即 action 被分发之后、reducer 触发之前;
  2. 中间件的执行前提,即 applyMiddleware 将会对 dispatch 函数进行改写,使得 dispatch 在触发 reducer 之前,会首先执行对 Redux 中间件的链式调用

    Redux 中间件机制是如何实现的

    1. applyMiddleware 是如何与 createStore 配合工作的?

    2.dispatch 函数是如何被改写的?

    3. compose 源码解读:函数的合成

中间件与面向切面编程

AOP(面向切面)
在流程执行的节点上拓展逻辑功能,形成一个可以拦截前序逻辑的切面

  • 即插即用
  • 提升了我们组织逻辑的灵活度与干净度
  • 规避掉了逻辑冗余、逻辑耦合

image.png
OOP(面向对象)
通过类的继承实现逻辑的复用,如果需要增加功能,需要将拓展父类的功能,会造成公共类的臃肿;如果将功能散落到各个子类,会造成代码冗余和耦合问题

用 reducer 实现 类似f1(f2(f3(args))) 的嵌套调用 实现f1().f2().f3().f4() 的链式调用

从 React-Router 切入,系统学习前端路由解决方案

认识 React-Router

```javascript import React from “react”;

// 引入 React-Router 中的相关组件

import { BrowserRouter as Router, Route, Link } from “react-router-dom”;

// 导出目标组件

const BasicExample = () => (

// 组件最外层用 Router 包裹

  • // 具体的标签用 Link 包裹 Home
  • // 具体的标签用 Link 包裹 About
  • // 具体的标签用 Link 包裹 Dashboard

// Route 是用于声明路由映射到应用程序的组件层

);

// Home 组件的定义

const Home = () => (

Home

);

// About 组件的定义

const About = () => (

About

);

// Dashboard 的定义

const Dashboard = () => (

Dashboard

);

export default BasicExample;

```

React-Router 是如何实现路由跳转的?

模块三:“周边生态”帮你拓宽技术视野 - 图8

路由器:BrowserRouter 和 HashRouter

路由器负责感知路由的变化并作出反应

  • HashRouter
    • example.com/#/some/path
    • HashRouter 是通过 URL 的 hash 属性来控制路由跳转的
    • hash 模式
      • hash 的感知:通过监听 “hashchange”事件
  • BrowserRouter
    • example.com/some/path
    • BrowserRouter 是使用 HTML 5 的 history API 来控制路由跳转的。
    • history 模式
      • 每当浏览历史发生变化,popstate 事件都会被触发。
      • go、forward 和 back 等方法的调用确实会触发 popstate,但是pushState 和 replaceState 不会。不过这一点问题不大,我们可以通过自定义事件和全局事件总线来手动触发事件