Redux
也是我列在 THE LAST TIME
系列中的一篇,因为如今正在着手探究关于我目前正在开发的业务中状态管理的方案。因此,这里打算先从 Redux
中学习学习,从他的状态中取取经。毕竟,成功老是须要站在巨人的肩膀上不是。前端
话说回来,都 2020 年了还在写 Redux
的文章,真的是有些过期了。不过呢,当时 Redux
孵化过程当中必定也是回头看了 Flux
、CQRS
、ES
等。react
本篇先从 Redux
的设计理念到部分源码分析。下一篇咱们在注重说下 Redux
的 Middleware
工做机制。至于手写,推荐砖家大佬的:彻底理解 redux(从零实现一个 redux)git
Redux
Redux
并非什么特别 Giao 的技术,可是其理念真的提的特别好。github
说透了,它就是一个提供了 setter
、getter
的大闭包,。外加一个 pubSub
。。。另外的什么 reducer
、middleware
仍是 action
什么的,都是基于他的规则和解决用户使用痛点而来的,仅此而已。下面咱们一点点说。。。web
在 jQuery 时代的时候,咱们是面向过程开发,随着 react 的普及,咱们提出了状态驱动 UI 的开发模式。咱们认为: Web 应用就是状态与 UI 一一对应的关系。编程
可是随着咱们的 web 应用日趋的复杂化,一个应用所对应的背后的 state 也变的愈来愈难以管理。redux
而 Redux
就是咱们 Web 应用的一个状态管理方案。api
如上图所示,store 就是 Redux
提供的一个状态容器。里面存储着 View 层所须要的全部的状态(state)。每个 UI 都对应着背后的一个状态。Redux
也一样规定。一个 state 就对应一个 View。只要 state 相同,View 就相同。(其实就是 state 驱动 UI)。数组
Redux
如上所说,咱们如今是状态驱动 UI,那么为何须要 Redux
来管理状态呢?react 自己就是 state drive view 不是。微信
缘由仍是因为如今的前端的地位已经愈发的不同啦,前端的复杂性也是愈来愈高。一般一个前端应用都存在大量复杂、无规律的交互。还伴随着各类异步操做。
任何一个操做均可能会改变 state,那么就会致使咱们应用的 state 愈来愈乱,且被动缘由愈发的模糊。咱们很容易就对这些状态什么时候发生、为何发生、怎么发生而失去控制。
如上,若是咱们的页面足够复杂,那么view
背后state
的变化就可能呈现出这个样子。不一样的 component
之间存在着父子、兄弟、子父、甚至跨层级之间的通讯。
而咱们理想中的状态管理应该是这个样子的:
单纯的从架构层面而言,UI 与状态彻底分离,而且单向的数据流确保了状态可控。
而 Redux
就是作这个的!
State
的变化可预测
下面简单介绍下 Redux
中的几个概念。其实初学者每每就是对其概念而困惑。
保存数据的地方,你能够把它当作一个容器,整个应用只能有一个
Store
。
某一个时刻,存储着的应用状态值
View 发出的一种让
state
发生变化的通知
能够理解为
Action
的工厂函数
View 发出
Action
的媒介。也是惟一途径
根据当前接收到的
Action
和State
,整合出来一个全新的State
。注意是须要是纯函数
Redux
的使用,基于如下三个原则
单一数据源这或许是与 Flux 最大的不一样了。在 Redux
中,整个应用的 state
都被存储到一个object
中。固然,这也是惟一存储应用状态的地方。咱们能够理解为就是一个 Object tree
。不一样的枝干对应不一样的 Component
。可是归根结底只有一个根。
也是受益于单一的 state tree
。之前难以实现的“撤销/重作”甚至回放。都变得轻松了不少。
惟一改变 state
的方法就是 dispatch
一个 action
。action
就是一个令牌而已。normal Object
。
任何 state
的变动,均可以理解为非 View
层引发的(网络请求、用户点击等)。View
层只是发出了某一中意图。而如何去知足,彻底取决于 Redux
自己,也就是 reducer。
store.dispatch({
type:'FETCH_START',
params:{
itemId:233333
}
})
复制代码
所谓纯函数,就是你得纯,别变来变去了。书面词汇这里就不作过多解释了。而这里咱们说的纯函数来修改,其实就是咱们上面说的 reducer
。
Reducer
就是纯函数,它接受当前的 state
和 action
。而后返回一个新的 state
。因此这里,state
不会更新,只会替换。
之因此要纯函数,就是结果可预测性。只要传入的 state
和 action
一直,那么就能够理解为返回的新 state
也老是同样的。
Redux
的东西远不止上面说的那么些。其实还有好比 middleware、actionCreator 等等等。其实都是使用过程当中的衍生品而已。咱们主要是理解其思想。而后再去源码中学习如何使用。
Redux 源码自己很是简单,限于篇幅,咱们下一篇再去介绍
compose
、combineReducers
、applyMiddleware
Redux
源码自己就是很简单,代码量也不大。学习它,也主要是为了学习他的编程思想和设计范式。
固然,咱们也能够从 Redux
的代码里,看看大佬是如何使用 ts 的。因此源码分析里面,咱们还回去花费很多精力看下 Redux
的类型说明。因此咱们从 type 开始看
看类型声明也是为了学习Redux
的 ts 类型声明写法。因此类似声明的写法形式咱们就不重复介绍了。
类型声明也没有太多的须要去说的逻辑,因此我就写注释上吧
// Action的接口定义。type 字段明确声明
export interface Action<T = any> {
type: T
}
export interface AnyAction extends Action {
// 在 Action 的这个接口上额外扩展的另一些任意字段(咱们通常写的都是 AnyAction 类型,用一个“基类”去约束必须带有 type 字段)
[extraProps: string]: any
}
export interface ActionCreator<A> {
// 函数接口,泛型约束函数的返回都是 A
(...args: any[]): A
}
export interface ActionCreatorsMapObject<A = any> {
// 对象,对象值为 ActionCreator
[key: string]: ActionCreator<A>
}
复制代码
// 定义的一个函数,接受 S 和继承 Action 默认为 AnyAction 的 A,返回 S
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
// 能够理解为 S 的 key 做为ReducersMapObject的 key,而后 value 是 Reducer的函数。in 咱们能够理解为遍历
export type ReducersMapObject<S = any, A extends Action = Action> = {
[K in keyof S]: Reducer<S[K], A>
}
复制代码
上面两个声明比较简单直接。下面两个稍微麻烦一些
export type StateFromReducersMapObject<M> = M extends ReducersMapObject<
any,
any
>
? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
: never
export type ReducerFromReducersMapObject<M> = M extends {
[P in keyof M]: infer R
}
? R extends Reducer<any, any>
? R
: never
: never
复制代码
上面两个声明,我们来解释其中第一个吧(稍微麻烦些)。
StateFromReducersMapObject
添加另外一个泛型
M
约束
M
若是继承
ReducersMapObject<any,any>
则走
{ [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
的逻辑
never
。啥也不是
{ [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
很明显,这就是一个对象,
key
来自
M
对象里面,也就是
ReducersMapObject
里面传入的
S
。
key
对应的
value
就是须要判断
M[P]
是否继承自
Reducer
。不然也啥也不是
infer
关键字和
extends
一直配合使用。这里就是指返回
Reducer
的这个
State
的类型
types
目录里面其余的好比 store
、middleware
都是如上的这种声明方式,就再也不赘述了,感兴趣的能够翻阅翻阅。而后取其精华的应用到本身的 ts 项目里面
能够看到,整个createStore.ts 就是一个createStore 函数。
三个参数:
reducer
:就是 reducer,根据 action 和 currentState 计算 newState 的纯 Function
preloadedState
:initial State
enhancer
:加强store的功能,让它拥有第三方的功能
createStore
里面就是一些闭包函数的功能整合。
// A extends Action
dispatch({ type: ActionTypes.INIT } as A)
复制代码
这个方法是Redux
保留用的,用来初始化State
,其实就是dispatch
走到咱们默认的 switch case default
的分支里面获取到默认的 State
。
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
复制代码
ts 的类型转换语法就不说了,返回的对象里面包含dispatch
、subscribe
、getState
、replaceReducer
、[$$observable]
.
这里咱们简单介绍下前三个方法的实现。
function getState(): S {
if (isDispatching) {
throw new Error(
`我 reducer 正在执行,newState 正在产出呢!如今不行`
)
}
return currentState as S
}
复制代码
方法很简单,就是 return currentState
subscribe
的做用就是添加监听函数listener
,让其在每次dispatch action
的时候调用。
返回一个移除这个监听的函数。
使用以下:
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
unsubscribe();
复制代码
function subscribe(listener: () => void) {
// 若是 listenter 不是一个 function,我就报错(其实 ts 静态检查能检查出来的,但!那是编译时,这是运行时)
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 同 getState 一个样纸
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://`Redux`.js.org/api/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
// 直接将监听的函数放进nextListeners里
nextListeners.push(listener)
return function unsubscribe() {// 也是利用闭包,查看是否以订阅,而后移除订阅
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://`Redux`.js.org/api/store#subscribelistener for more details.'
)
}
isSubscribed = false//修改这个订阅状态
ensureCanMutateNextListeners()
//找到位置,移除监听
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
复制代码
一句话解释就是在 listeners 数据里面添加一个函数
再来讲说这里面的ensureCanMutateNextListeners
,不少 Redux
源码都么有怎么说起这个方法的做用。也是让我有点困惑。
这个方法的实现很是简单。就是判断当前的监听数组里面是否和下一个数组相等。若是是!则 copy 一份。
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
复制代码
那么为何呢?这里留个彩蛋。等看完 dispatch
再来看这个疑惑。
function dispatch(action: A) {
// action必须是个普通对象
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 必须包含 type 字段
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 同上
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 设置正在 dispatch 的 tag 为 true(解释了那些判断都是从哪里来的了)
isDispatching = true
// 经过传入的 reducer 来去的新的 state
// let currentReducer = reducer
currentState = currentReducer(currentState, action)
} finally {
// 修改状态
isDispatching = false
}
// 将 nextListener 赋值给 currentListeners、listeners (注意回顾 ensureCanMutateNextListeners )
const listeners = (currentListeners = nextListeners)
// 挨个触发监听
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
复制代码
方法很简单,都写在注释里了。这里咱们再回过头来看ensureCanMutateNextListeners
的意义
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener: () => void) {
// ...
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action: A) {
// ...
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// ...
return action
}
复制代码
从上,代码看起来貌似只要一个数组来存储listener
就能够了。可是事实是,咱们偏偏就是咱们的 listener
是能够被 unSubscribe
的。并且 slice
会改变原数组大小。
因此这里增长了一个 listener
的副本,是为了不在遍历listeners
的过程当中因为subscribe
或者unsubscribe
对listeners
进行的修改而引发的某个listener
被漏掉了。
限于篇幅,就暂时写到这吧~
其实后面打算重点介绍的 Middleware
,只是中间件的一种更规范,甚至咱们能够理解为,它并不属于 Redux
的。由于到这里,你已经彻底能够本身写一份状态管理方案了。
而 combineReducers
也是我认为是费巧妙的设计。因此这些篇幅,就放到下一篇吧~
公众号【全栈前端精选】 | 我的微信【is_Nealyang】 |
---|---|
![]() |
![]() |