关于 Redux 学习
很早就接触了 React,自然也就认识了 Redux,但因为一直做的是 Vue 相关,所以其实没有真正的理解。最近又重新在学习 React,在深刻理解 React 的基本思想和概念之后,我觉得自己已经掌握了 React 的基础。直到再一次遇到 Redux 这个硬骨头,还是遇到点困难,不得不说,Redux 的学习需要一点时间。
首先,不像 React 基本的 JSX、props、state 等概念,Redux 是状态管理,涉及的东西太多,概念太多,虽然每一个都不难,有的可能只有几行代码的实现,但是你可能很久之后才完全认识它们所有、并且认识到每一个都是不可或缺的。例如:
- redux 本身的实现
- redux 的设计原则
- 深入理解 store、state、action、reducer、dispatch
- 深入理解 immutable、函数式编程
- 深入理解高阶函数
- react-redux 的功能
- middleware 的概念
applyMiddlewarecompose- action creators
- Selectors
- Reselect
- Normalizing
- thunk 的概念
- redux-thunk 的实现
- Store enhancer
- reducer enhancers
combineReducers- Higher Order Reducers
- ……
Redux 如此丰富,这个列表可以一直列下去。别的不说,关于 redux 异步的实现就是一堆,thunk、saga 等等,哪一个不是要理解原理才能搞懂呢?redux 本身是不依赖任何东西的,其他所有的都是围绕 redux 创造出来的,为了增强 redux 的功能,为了高效、优雅的写法。
其次,redux 官网有点混乱,可能是因为东西多、想表达的东西多。我们总说“多看文档”,但是我觉得 Redux 的文档根本看不完。更不用提里面提到的链接有多少了,多么让人绝望!几个明显的问题:
- Tutorials 里面 Essentials 和 Fundamentals 几乎是重复的
- Redux ToolKit 的加重了 Redux 的复杂度
- 没有一个完整的指引,学习的先后顺序
这些都会让 Redux 的学习变得困难。
时间旅行
但是在学习 Redux 的过程中,一直隐隐有个问题困扰着我。应该说是我最想搞懂的。这个问题,也是 Redux 最早要实现的功能,用以宣称所具有的巨大优势。那就是时间旅行。出现在 Dan 最初的演讲,Redux 的面世:Dan Abramov - Live React: Hot Reloading with Time Travel at react-europe 2015
在第一看的时候,我就想知道这个时间旅行到底是怎么做的。最近看文档,算是终于看到相关的实现了。
主要是文档 Implementing Undo History 介绍了 undo todo 的实现。
首先预备知识:reducer enhancer。
设计数据结构,要实现 undo、redo 的功能,要有如下的数据结构:
{
past: Array<T>,
present: T,
future: Array<T>
}
- past、future 表示存储的过去和未来的操作的所有数据(state)列表
- present 表示现在的数据
实现 undo、redo 的 reducer:
const initialState = {
past: [],
present: null, // (?) How do we initialize the present?
future: []
}
function undoable(state = initialState, action) {
const { past, present, future } = state
switch (action.type) {
case 'UNDO':
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future]
}
case 'REDO':
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture
}
default:
// (?) How do we handle other actions?
return state
}
}
问题是如何在 undo、redo 时将数据存入 past 和 future?
这里就涉及 reducer enhancer,设计一个 undoable 函数,传入 todo reducer,返回一个 reducer,通过 HOF,既能对原来 todo 的 state 进行操作,也能对 undoable 的 state(包含 past、future,present 为 todo state)进行操作。
核心源码:
function undoable(reducer) {
// Call the reducer with empty action to populate the initial state
const initialState = {
past: [],
present: reducer(undefined, {}),
future: []
}
// Return a reducer that handles undo and redo
return function (state = initialState, action) {
const { past, present, future } = state
switch (action.type) {
case 'UNDO':
const previous = past[past.length - 1]
const newPast = past.slice(0, past.length - 1)
return {
past: newPast,
present: previous,
future: [present, ...future]
}
case 'REDO':
const next = future[0]
const newFuture = future.slice(1)
return {
past: [...past, present],
present: next,
future: newFuture
}
default:
// Delegate handling the action to the passed reducer
const newPresent = reducer(present, action)
if (present === newPresent) {
return state
}
return {
past: [...past, present],
present: newPresent,
future: []
}
}
}
}
这个 undoable 有库实现,就是 redux-undo。
关于 Redux 学习总结
Redux 的实现与相关生态,依赖于 JavaScript 函数的灵活和强大,利用高阶函数,到处都是组合、抽象、封装,所以不仅是 middleware 高阶、dispatch 可以高阶(action 也要用函数返回一下、以及 thunk)、甚至 reducer 也可以高阶,只要一直高阶下去,就能实现任何功能。学习 React 和 Redux 的过程,感觉可以学到很多东西,感觉代码可以一步步变好变规范。对比学习 Vue 的过程是,学习它的 api、用法,然后学习它的实现原理(更好的使用它),但是你没办法 hack 到它的底层,因为它有自己的模式