揭秘 Redux 设计思想与工作原理
Redux 背后的架构思想——认识 Flux 架构
Flux架构
架构模式,应用处理数据的模式
- View(视图层)
- Action(动作)
- Dispatcher(派发器)
- Store(数据层)
工作流程:
用户与 View 之间产生交互,通过 View 发起一个 Action;Dispatcher 会把这个 Action 派发给 Store,通知 Store 进行相应的状态更新。Store 状态更新完成后,会进一步通知 View 去更新界面(单向数据流)
Flux 架构到底解决了什么问题
MVC 模式在前端场景下的局限性
双向数据流MVC模式架构下,View和Model能够相互通信,这会导致数据流向无法预测,容易引发bug
Redux 关键要素与工作流回顾
Redux 主要由 3 部分组成:Store、Reducer 和 Action
- Store:它是一个单一的数据源,而且是只读的
- Action 人如其名,是“动作”的意思,它是对变化的描述
- Reducer 是一个函数,它负责对变化进行分发和处理,最终将新的数据返回给 Store
工作流程:
在 Redux 的整个工作过程中,数据流是严格单向的。如果你想对数据进行修改,只有一种途径:派发 Action。Action 会被 Reducer 读取,Reducer 将根据 Action 内容的不同执行不同的计算逻辑,最终生成新的 state(状态),这个新的 state 会更新到 Store 对象里,进而驱动视图层面作出对应的改变。
Redux 是如何工作的
- 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, …)
);
![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)
<a name="FYSpr"></a>
#### Redux 工作流的核心:dispatch 动作
dispatch 这个动作刚好能把 action、reducer 和 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)
- 通过“上锁”避免“套娃式”的 dispatch(避免再reducer中手动调用dispatch,造成死循环)
```javascript
try {
// 执行 reducer 前,先“上锁”,标记当前已经存在 dispatch 执行流程
isDispatching = true
// 调用 reducer,计算新的 state
currentState = currentReducer(currentState, action)
} finally {
// 执行结束后,把"锁"打开,允许再次进行 dispatch
isDispatching = false
}
触发订阅的过程
// 触发订阅
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
- subscribe 是如何与 Redux 主流程结合的呢
首先,我们可以在 store 对象创建成功后,通过调用 store.subscribe 来注册监听函数,也可以通过调用 subscribe 的返回函数来解绑监听函数,监听函数是用 listeners 数组来维护的;当dispatch action 发生时,Redux 会在 reducer 执行完毕后,将 listeners 数组中的监听函数逐个执行。这就是 subscribe 与 Redux 主流程之间的关系
- 为什么会有 currentListeners 和 nextListeners 两个 listeners 数组
currentListeners 在此处的作用,就是为了记录下当前正在工作中的 listeners 数组的引用,将它与可能发生改变的 nextListeners 区分开来,以确保监听函数在执行过程中的稳定性。
Redux 的三大原则:
- 单一数据源
- 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,这就是中间件的引入过程
// 引入 redux
import { createStore, applyMiddleware } from 'redux'
......
// 创建 store
const store = createStore(
reducer,
initial_state,
applyMiddleware(middleware1, middleware2, ...)
);
中间件的工作模式
redux-thunk——经典的异步 Action 解决方案
Redux 中间件是如何与 Redux 主流程相结合的?
Redux 中间件的工作模式
- 中间件的执行时机,即 action 被分发之后、reducer 触发之前;
- 中间件的执行前提,即 applyMiddleware 将会对 dispatch 函数进行改写,使得 dispatch 在触发 reducer 之前,会首先执行对 Redux 中间件的链式调用。
Redux 中间件机制是如何实现的
1. applyMiddleware 是如何与 createStore 配合工作的?
2.dispatch 函数是如何被改写的?
3. compose 源码解读:函数的合成
中间件与面向切面编程
AOP(面向切面)
在流程执行的节点上拓展逻辑功能,形成一个可以拦截前序逻辑的切面
- 即插即用
- 提升了我们组织逻辑的灵活度与干净度
- 规避掉了逻辑冗余、逻辑耦合
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 是如何实现路由跳转的?
路由器: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 不会。不过这一点问题不大,我们可以通过自定义事件和全局事件总线来手动触发事件