深入理解 redux
为什么写这篇文章
业余时间我也算看了不少优秀开源项目的源码比如 react、redux、vuex、vue、babel、ant-design 等,但是很少系统地进行总结,学到的知识非常有限,因此我一直想写一篇完善的源码解读方面的文章。
第二个原因是最近面试的过程中,发现很多候选人对 redux 的理解很浅,甚至有错误的理解。真正理解 redux 的思想的人非常好,更不要说理解它其中的精妙设计了。
因此就有了这篇文章的诞生。
REDUX 是什么
深入理解 redux 之前,首先来看下,redux 是什么,解决了什么问题。下面是 redux 官方给出的解释:
Redux is a predictable state container for JavaScript apps.
上面的概念比较抽象,如果对 redux 不了解的人是很难理解的。一个更容易被人理解的解释(同样是 redux 官方的解释):
redux 是 flux 架构的实现,受 Elm 启发
首先科普两个名字,flux 和 Elm。
flux
下面是 facebook 官方对 flux 的解释:
Application Architecture for Building User Interfaces
更具体地说:
An application architecture for React utilizing a unidirectional data flow.
flux 是随着 react 一起推出的数据管理框架,它的核心思想是单项数据流。
一图胜千言,让我们通过图来了解下flux
通过 react 构建 view,而 react 又是数据驱动的,那么解决数据问题就解决了 view 的问题,通过 flux 架构管理数据,使得数据可预测。这样 view 也变得可预测。 非常棒~
Elm
Elm 是一门编译代码到 JavaScript 的语言,它的特点是性能强和无运行时异常。Elm 也有虚拟 DOM 的实现。
Elm 的核心理念是使用 Model 构建应用,也就是说 Model 是应用的核心。构建一个应用就是构建 Model,构建更新 Model 的方式,以及如何构建 Model 到 view 的映射。
了解了上面的东西,你会发现其实 redux 的任务就是管理数据。redux 的数据流可以用下面的图来标示:
它精妙的设计我们在后面进行解读。
最小化实现 REDUX
其实写一个 redux 并不困难。redux 源码也就区区200行左右。里面大量使用高阶函数,闭包,函数组合等知识。让代码看起来更加简短,结构更加清晰。
我们来写一个 redux
吧!
实现
开始之前我们先看下 redux 暴漏的 api
const store = {
state: {}, // 全局唯一的state,内部变量,通过getState()获取
listeners: [], // listeners,用来诸如视图更新的操作
dispatch: () => {}, // 分发action
subscribe: () => {}, // 用来订阅state变化
getState: () => {}, // 获取state
}
上面是 redux 中最主要的 api(其实还有一个 applyMiddleware 用来实现中间件的也非常重要,将会在 redux 的核心思想部分讲解,这里暂不讨论)
createStore
createStore 是用来初始化 redux store 的,是最重要的 api。我们来实现一下:
const createStore = (reducer, initialState) => {
// internal variablesconst store = {};
store.state = initialState;
store.listeners = [];
// api-subscribe
store.subscribe = (listener) => {
store.listeners.push(listener);
};
// api-dispatch
store.dispatch = (action) => {
store.state = reducer(store.state, action);
store.listeners.forEach(listener => listener());
};
// api-getState
store.getState = () => store.state;
return store;
};
通过上面的20行左右的代码已经实现了 redux 的最基本功能了,是不是很惊讶?我们下面来试下。
使用
我们现在可以像使用redux一样使用了我们的 redux
了。
以下例子摘自官网,你可以把下面这段脚本加上我们上面实现的 redux
,拷贝到控制台执行,看下效果。是否和 redux 官方给的结果一致。
// reducerfunctioncounter(state = 0, action) {
switch (action.type) {
case'INCREMENT':
return state + 1case'DECREMENT':
return state - 1default:
return state
}
}
let store = createStore(counter)
store.subscribe(() =>console.log(store.getState())
)
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
可以看出我们已经完成了 redux 的最基本的功能了。 如果需要更新 view,就根据我们暴漏的 subscribe 去更新就好了,这也就解释了 redux 并不是专门用于 react 的,以及为什么要有 react-redux 这样的库存在。
为了方便各个阶段的人员能够看懂,我省略了 applyMiddleware 的实现,但是不要担心,我会在下面 redux 核心思想章节进行解读。
REDUX 核心思想
redux 的核心思想出了刚才提到的那些之外。个人认为还有两个东西需要特别注意。一个是 reducer, 另一个是 middlewares
reducer 和 reduce
reducer 可以说是 redux 的精髓所在。我们先来看下它。reducer 被要求是一个纯函数。
- 被要求很关键,因为 reducer 并不是定义在 redux 中的一个东西。而是用户传进来的一个方法。
- 纯函数也很关键,reducer 应该是一个纯函数,这样 state 才可预测(这里应证了我开头提到的 Redux is a predictable state container for JavaScript apps.)。
日常工作我们也会用到 reduce 函数,它是一个高阶函数。reduce 一直是计算机领域中一个非常重要的概念。
reducer 和 reduce 名字非常像,这是巧合吗?
我们先来看下 reducer 的函数签名:
fucntion reducer(state, action) {
const nextState = {};
// xxxreturn nextState;
}
再看下 reduce 的函数签名
[].reduce((state, action) => {
const nextState = {};
// xxxreturn nextState;
}, initialState)
可以看出两个几乎完全一样。最主要区别在于 reduce 的需要一个数组,然后累计变化。reducer 则没有这样的一个数组。更确切地说,reducer 累计的 时间
上的变化,reduce 是累计 空间
上的变化。
如何理解 reducer 是累计 时间
上的变化?
我们每次通过调用 dispatch(action) 的时候,都会调用 reducer,然后将 reducer 的返回值去更新 store.state。每次 dispatch 的过程,其实就是在空间上 push(action) 的过程,类似这样:
[action1, action2, action3].reduce((state, action) => {
const nextState = {};
// xxxreturn nextState;
}, initialState)
因此说,reducer 其实是时间上的累计,是基于时空的操作。
middlewares
关于 middleware 的概念我们不多介绍,感兴趣可以访问这里查看更多信息。
如下可以实现一个 redux 的 middlewares:
store.dispatch = functiondispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
上述代码会在 dispatch 前后进行打印信息。类似的 middleware 我们可以写很多。比如我们还定义了另外几个相似的中间件。
我们需要将多个中间件按照一定顺序执行:
// 用reduce实现compose,很巧妙。functioncompose(...funcs) {
if (funcs.length === 0) {
returnarg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
// applyMiddleware 的源码functionapplyMiddleware(...middlewares) {
returncreateStore => (...args) => {
const store = createStore(...args)
let dispatch = () =>null;
let chain = [];
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 将middlewares组成一个函数// 也就是说就从前到后依次执行middlewares
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
// 使用let store = createStore(
todoApp,
// applyMiddleware() tells createStore() how to handle middleware
applyMiddleware(logger, dispatchAndLog)
)
上面就是 redux 关于 middleware 的源码,非常简单。通过 compose middlewares 和 dispatch,使得开发者在 dispatch 的时候能够顺序执行各个中间件。基于此出现了很多非常优秀的第三方 redux 中间价,比如 redux-dev-tool, redux-log, redux-promise 等等。这种解决问题的思想也可以应用到我们平时写代码的过程中。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论