前言
探索Redux 和 Mobx 原理从我做起,从这篇文章看起!
所以
一位程序员的职业生涯大约十年,只有人寿命的十分之一。前端项目只是你生活工作的一部分,而你却是它的全部,你是他的灵魂。请放下长时间的游戏、工作时的摸鱼。多学习来以最完美的状态好好陪你项目!
正文
这篇文章将会详细分析 Redux 和 Mobx 核心 Api, 看一遍学不会就看两次、三次、手写一次!
知识点
- Redux 基本使用(没有)
- createStore
- bindActionCreators
- combineReducers
- applyMiddleware
- 彩蛋
thunk
- Mobx 基本使用(没有)
- observable
- autorun
- observer
这个 代码很多哦 , 我这菜鸟看不懂 ,我看了文章,应该能实现 这些api 和 vue的的 响应式差不多.
Redux
createStore
这个是个比较核心的函数之创建仓库,分为了3个核心函数 dispatch
,getState
,subscribe
这三个函数的基本使用是这样的...
// 构建仓库
const store = createStore(reducer);
const unListen = store.subscribe(() => {
console.log("监听器1", store.getState());
})
store.dispatch(createAddUserAction({
id: 1,
name: "遇见同学",
age: 21
}));
unListen(); //取消监听
本质就如思维导图思路
基本实现且看代码的详细解释
/**
* 得到一个指定长度的随机字符串
* @param {*} length
*/
function getRandomString(length) {
return Math.random().toString(36).substr(2, length).split("").join(".")
}
/**
* 判断某个对象是否是一个plain-object
* @param {*} obj
*/
function isPlainObject(obj) {
if (typeof obj !== "object") {
return false;
}
return Object.getPrototypeOf(obj) === Object.prototype;
}
// 上面连个都为辅助工具函数
/**
* 实现createStore的功能
* @param {function} reducer reducer
* @param {any} defaultState 默认的状态值
*/
export default function createStore(reducer, defaultState) {
let currentReducer = reducer, //当前使用的reducer
currentState = defaultState; //当前仓库中的状态
const listeners = []; //记录所有的监听器(订阅者)
function dispatch(action) {
//验证action
if (!isPlainObject(action)) {
throw new TypeError("action must be a plain object");
}
//验证action的type属性是否存在
if (action.type === undefined) {
throw new TypeError("action must has a property of type");
}
currentState = currentReducer(currentState, action)
//运行所有的订阅者(监听器)
for (const listener of listeners) {
listener();
}
}
function getState() {
return currentState;
}
/**
* 添加一个监听器(订阅器)
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
listeners.push(listener); //将监听器加入到数组中
let isSubscribed = false;//是否已经移除掉了
return function () {
if (isSubscribed) {
return;
}
//将listener从数组中移除
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
isSubscribed = true;
}
}
//创建仓库时,需要分发一次初始的action
dispatch({
type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
})
return {
dispatch,
getState,
subscribe
}
}
实现的代码基本对标 Github Redux
由于官方使用的是Ts 写的 阅读起来可能更加困难
所以过滤许多的细节处理和TS,将本质的思维呈现出来!
从入口带你理解下一个核心函数bindActionCreators
bindActionCreators
- 作用
用于返回可直接调用的dispatch
的函数方法 参数
- object | function
- (store.dispatch)
const actionCreators = { addUser: createAddUserAction, deleteUser: createDeleteUserAction } // 这可直接传入一个函数 const actions = bindActionCreators(actionCreators, store.dispatch) actions.addUser({ id: 1, name: "遇见同学", age: 21 })
使用还是非常简单的, 本质也是一个函数, 非常的精妙,一起看看吧!
思路是这样,核心函数是另外一个getAutoDispatchActionCreator
/**
* @param {object | function } actionCreators
* @param {*} dispatch
* @returns
*/
export default function (actionCreators,dispatch) {
// 为函数时 直接调用自动分发的action创建函数
if(typeof actionCreators === 'function'){
_getAutoDispatchActionCreator(actionCreators,dispatch)
// 为对象时 遍历对象 转化为已创建自动分发的action创建函数的对象
}else if(typeof actionCreators === 'object'){
const result = {}
Object.keys(actionCreators).forEach((key)=>{
result[key] = _getAutoDispatchActionCreator(actionCreators[key],dispatch)
})
return result
}else{
throw new TypeError("actionCreators must be an object or function which means action creator")
}
}
/**
* 自动分发的action创建函数
* @param {function} actionCreator
* @param {any} dispatch
* @returns
*/
function _getAutoDispatchActionCreator(actionCreator, dispatch){
return function(...args){
return dispatch(actionCreator(...args))
}
}
是不是豁然开朗了,乘热打铁进入下一个函数吧combineReducers
combinReducers
基本思路是这样的
**
* 合并reducers函数
* @param {object} reducers
* @returns
*/
export default function (reducers) {
validateReducers(reducers);
/**
* 返回的是一个reducer函数
*/
return function (state = {}, action) {
const newState = {}; //要返回的新的状态
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const reducer = reducers[key];
newState[key] = reducer(state[key], action);
}
}
return newState; //返回状态
}
}
function validateReducers(reducers) {
if (typeof reducers !== "object") {
throw new TypeError("reducers must be an object");
}
if (!isPlainObject(reducers)) {
throw new TypeError("reducers must be a plain object");
}
//验证reducer的返回结果是不是undefined
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const reducer = reducers[key];//拿到reducer
//传递一个特殊的type值
let state = reducer(undefined, {
type:`@@redux/INIT${getRandomString(6)}`
})
if (state === undefined) {
throw new TypeError("reducers must not return undefined");
}
state = reducer(undefined, {
type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
})
if (state === undefined) {
throw new TypeError("reducers must not return undefined");
}
}
}
}
来对比一下这个Redux 的 combinReducers官方代码
总得来说这几个都属于辅助函数,而createStore
和 传入的reducer
才是调动整个 状态数据的核心, 不过Redux 提供了 一个核心机制 中间件
也是Redux 的灵魂之一。
接下来看下 applyMiddleware
是如何实现的
applyMiddleware
像 Koa
、Redux
等优秀库都提供了中间思想, 中间件一出, 必有 组合函数
我们看下 Redux
的组合函数
function compose(...funcs: Function[]) {
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
省略一些边界判断 , 实现就一个 reducer
就这一行
funcs.reduce((a, b) => (...args) => a(b(...args)))
函数由内向外,返回作为 reducer
的新的累加值 依次执行 b、a、...
再看看applyMiddleware
函数
import compose from "./compose"
/**
* 注册中间件
* @param {...any} middlewares 所有的中间件 本质是一个函数
*/
export default function (...middlewares) {
return function (createStore) { //给我创建仓库的函数
//下面的函数用于创建仓库
return function (reducer, defaultState) {
//创建仓库
const store = createStore(reducer, defaultState);
let dispatch = () => { throw new Error("目前还不能使用dispatch") };
const simpleStore = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
//给dispatch赋值
//根据中间件数组,得到一个dispatch创建函数的数组
const dispatchProducers = middlewares.map(mid => mid(simpleStore));
dispatch = compose(...dispatchProducers)(store.dispatch);
return {
...store,
dispatch
}
}
}
}
以我理解干了这几件事
调用返回了 一个具备以上能力的函数 抛出给 createStore
的第二个参数, 我们看看 createStore
有何改动
export default function createStore(reducer, defaultState, enhanced) {
//enhanced表示applymiddleware返回的函数
if (typeof defaultState === "function") {
//第二个参数是应用中间件的函数返回值
enhanced = defaultState;
defaultState = undefined;
}
if (typeof enhanced === "function") {
//进入applyMiddleWare的处理逻辑
return enhanced(createStore)(reducer, defaultState);
}
// ....同上面代码
return {
dispatch,
getState,
subscribe
}
}
执行
applyMiddleware
返回一个需要仓库函数 的函数applyMiddleware( thunk, // 返回中间件函数 logger )
- 接着 传入
createStore
仓库函数函数本身 很巧妙 - 得到一个新的仓库构造函数 传入
(reducer, defaultState)
- 用第一次传入的
createStore
和(reducer, defaultState)
执行构造出没使用中间件的仓库 - 挂载
store
和dispatch
新对象传给 每一个中间件函数,返回新的dispatch
数组 - 合并所有新的
dispatch
,执行第一次 - 返回
store
和 新的dispatch
这便是 中间件的基本 运行流程 有些绕 好好学习,天天向上
送你一个彩蛋 thunk
实现
代码是个只有几行的函数
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
// 创建 thunk 中间件
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
看图 我带你 穿起来 代码 一起解读
红色部分是 中间件函数
将 dispatch, getState
丢给 thunk
, 接着返回函数
图中红丝部分代表着 thunk
处理部分 , 判断是否 为 函数 不是就对给下步去执行
我们再看下 next
是什么。
清晰的看一下 next 就是 dispatch
函数
后面两个 action
就是 一个普通 平面对象 类型 和 异步函数 thunk 处理类型
Mobx
Mobx 也是一款状态管理工具 ,但是思维模式不同
类似 Vue 响应式原理 , 配合 观察者模式 收集依赖 实现
Api 有点多 我没看懂 , 下面仅供参考
observable
这个方法显而易见 构建出 一个 可观察的对象
import reaction from './reaction'
export default function observable(target,key,descritor){
// 当使用装饰器 修饰属性时
if( typeof key === 'string'){
let Reaction = new reaction
let v = descritor.initailizer()
v = createObservable(v)
return {
enumerable:true,
configurable:true,
get(){
Reaction.collect()
return v
},
set(value){
v = value
Reaction.run()
},
}
}
return createObservable(target)
}
function createObservable(val){
let handle = () =>{
let Reaction = new reaction
return {
get(target,key){
Reaction.collect()
return Reflect.get(target,key)
},
set(target,key,value){
let v = Reflect.set(target,key,value)
Reaction.run()
return v
},
}
}
return deepProxy(val,handle)
}
function deepProxy(val,handle){
if(typeof val !== 'object') return val
// 从后往前依此实现代理
for(let key in val){
val[key] = deepProxy(val[key],handle)
}
return createObservable(val, handle())
}
可以很清楚的看到 这个函数实现了以下
- 判断是否处于装饰器修饰使用状态
- Proxy观察一个深度对象
- 在get时触发 依赖收集
reaction
let nowFn = null
let counter = 0
class Reaction {
constructor(){
this.id = ++counter
this.store = {}
}
run(){
if(this.store[this.id]){
this.store[this.id].forEach(w=>w())
}
}
collect(){
if(nowFn){
this.store[this.id] = this.store[this.id] || []
this.store[this.id].push(nowFn)
}
}
static start(handle){
nowFn = handle
}
static end (){
nowFn = null
}
}
autorun
import reaction from './reaction'
export default function autorun(handle){
reaction.start()
handle()
reaction.end()
}
再 看看 autorun
和 reaction
基本就是一个简单的依赖收集 和 触发
相信 各位 知道 观察者模式 和 了解 Vue 依赖收集的对这样的写法并不陌生
而 最后的一个 observer
observer
/**
* 装饰器 mobx-react 修饰类组件
* @param {*} target
*/
export default function observer(target){
let cwm = target.prototype.componentWillMount;
target.prototype.componentWillMount = function(){
cwm && cwm.call(this);
autorun(()=>{
this.render();
this.forceUpdate();
})
}
}
这个仅只是 修饰 react 类组件的 状态
对比
Redux
- 单一数据源
- 状态数据只读
- 使用纯函数修改状态
Mobx
- 多数据源
- 被观察对象
- 观察依赖
- 触发动作
两者的区别:
比较 | redux | mobx |
---|---|---|
核心模块 | Action,Reducer,Store,没有调度器的概念 | observerState、Derivations、Actions... |
Store | 只有一个 Store, Store 和更改逻辑是分开的,带有分层 reducer的单一 Stor | 多个 store |
状态 | 状态是不可改变的(建议不可变值) | 通常将状态包装成可观察对象,观察者依赖收集模式 |
编程思想 | 遵循函数式编程思想 | 函数响应式编程编程 |
对象 | JavaScript对象 | 可观察对象 |
总结
- Redux 的 源码实现 了解中间件的核心思想
- 实现Mobx 的 核心 (其他的慢慢摸索是吧)
- 对比两中状态管理的 差异 优劣
本文首发 GitHub |
源码 Analysis
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。