前言

作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考。在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解。

常见问题

大概看了下主要有这么几个:

  1. redux三大原则

    这个可以直接参考官方文档
  2. redux 的优缺点。 关于优缺点,太主观了大家见仁见智。
  3. redux中间件相关,洋葱模型是什么,常见中间件。

背景

有关acton,reducer相关的部分可以看我前面的文章。我们主要关注针对store和中间件相关的部分来解读。

store的创建

作为维护和管理数据的部分,store在redux中的作用十分重要。在action发出指令,reduxer进行数据更新之后,监听数据变化和同步数据更新的操作都要借助store来实现。

createStore 输入和输出

首先看下createStore的使用,即常见的就是接受经过combineReducers处理之后的reducer和初始的state

import reducer from './reducers'
const store = createStore(reducer,initialState)

此外还可以接受第三个参数enhancer(增强器,一般就是applyMiddleware)


/**
* 创建管理state 树的Redux store
* 应用中只能存在一个store,为了区分不同action对应的reducer,
* 可以通过combineReducers来关联不同的reducer
* @param {Function} reducer combineReducers关联之后的reducer
* @param {Object} preloadedState 初始state
* @param {Function} enhancer 可以增强store功能的函数,例如中间件等。唯一适合
* @returns 返回一个Store 以维护state和监听变化
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 如果第二个参数为func,redux认为忽略了初始state,而是
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer增强剂,即利用中间件等来增强redux能力
enhancer = preloadedState
preloadedState = undefined
}
// 返回具有dispatch等属性的对象 即store
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}

按照一般的执行顺序,我们先看下对于参数的处理(平时大家也是一样,一个函数,执行之前尽量判断入参是否符合预期,避免直接处理造成的错误)

入参处理

对于三个参数,后两个是非必填的,但如果第二个参数是function,reduxe认为其实encher,不然初始状态是个函数不符合redux的预期,只能这样处理了。

// 如果第二个参数为func,redux认为忽略了初始state,而是
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer增强剂,即利用中间件等来增强redux能力
enhancer = preloadedState
preloadedState = undefined
} if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 对于存在的enhancer,高阶函数函数的用法,
// 接受createStore返回一个增加功能之后的函数,然后再传入相关reducer得到store。
return enhancer(createStore)(reducer, preloadedState)
} if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 一切符合预期,没有 enhancer,那么初始赋值
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
// 监听队列
let nextListeners = currentListeners
// dispatch标识
let isDispatching = false // 初始状态更新之后,声明init状态完成。
dispatch({ type: ActionTypes.INIT })

dispatch的实现

dispatch的作用就是根据action,执行对应reducer以更新state。并执行监听队列。

下面就来看dispatch的用法和实现。

常见使用:

// redux要求 参数必须为纯对象
dispatch({ type: ActionTypes.INIT })

那么对于纯对象,redux做了些什么呢

 /**
* 通知方法,参数为纯的js对象,标明更新内容
* @param {Object} action
*/
function dispatch(action) {
// 是否满足纯净对象
if (!isPlainObject(action)) {
throw new Error(
'省略'
)
}
// 必须的type是否存在
if (typeof action.type === 'undefined') {
throw new Error(
'省略'
)
}
// 判断是否处于某个action的dispatch中,大家一起dispatch可能死循环
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
} try {
// 开始dispatch,加锁,标明状态
isDispatching = true
// 将当前状态和更新action,传给当前reducer处理
// 这里可以回想下我们reducer中的两个参数,state和action 对应的是什么
/**
* const todos = (state = [], action) => {
*/
currentState = currentReducer(currentState, action)
} finally {
// 有异常,锁置为false,不影响别的dispatch
isDispatching = false
}
// 执行dispatch,并且更新当前监听队列为 最新队列
const listeners = (currentListeners = nextListeners)
// 依次执行,监听器
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
} return action
}

createStore初始化完成之后会执行dispatch({ type: ActionTypes.INIT }),此时执行初始化操作。

我们要关注下currentState的计算,

将currentState,action传给reducer处理,然后更新currentState。

针对初始化来说currentState其实就是initState:

// 初始化状态
let currentState = preloadedState
/****省略***/
// 这里可以回想下我们reducer中的两个参数,state和action对应的值
currentState = currentReducer(currentState, action)

reducer示例:

const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
}

getSate实现

getState就是获得store的state。这个比较简单。当结合react-redux使用时,其会帮我们进行操作。我们就不用自行调用这个方法了,所以不要疑惑从哪里获取的state。

/**
* 返回应用当前状态
* 不过需要看下当前是否有更新正在进行,是的话则提示
*/
function getState() {
// 判断是否isDispatching 中
if (isDispatching) {
throw new Error('省略')
}
return currentState
}

subscribe

subscribe是比较重要的一个方法,用来供我们监听状态的变化,以执行相关操作。

例如react-redux中的handleChange 会对是否pure组件及state进行对比,以提升渲染效率。

示例:

 this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))

实现:

返回的是一个函数,可以进行unsubscribe操作。

/**
* 订阅通知
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
} if (isDispatching) {
throw new Error(
'省略'
)
}
// 是否已经监听过
let isSubscribed = true
// 监听队列是否相同,区分开,操作nextListeners
ensureCanMutateNextListeners()
// 新增监听事件
nextListeners.push(listener) return function unsubscribe() {
if (!isSubscribed) {
return
} if (isDispatching) {
throw new Error(
'省略'
)
}
// 注册完成,可以进行取消操作
isSubscribed = false
// 保持事件队列 同步
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 删除监听事件
nextListeners.splice(index, 1)
}
}

replaceReducer

这个开发比较少用,用于热更新

// 用于reducer的热替换,开发中一般不会直接使用
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
} currentReducer = nextReducer
// 更新值之后,进行dispatch。
dispatch({ type: ActionTypes.REPLACE })
}

到这里createStore已经解析完成了,大家应该了解该方法到底做了些什么操作吧。

简单概括一下就是:接收reducer和initState,返回一个store 对象。该对象提供了监听、分发等功能,以实现数据的更新。

实际使用中的问题

经过上面的解读之后,对于redux的常规应用应该有所了解了。不过实际使用中可能会遇到些问题。

例如action要求是纯对象,而我们获取数据一般是异步的,这就需要借助redux-thunk这个中间件了。

actionCreater返回一个函数。如下:

export function func1() {
return dispatch => {
dispatch({
type:'test',
data:'a'
})
}
}

在了解如何实现之前,需要先看下redux中间件的原理。

因为reducer更多的关注的是数据的操作,对于一些公共的方法,需要抽离出来,不过这些方法在何时使用呢,redux为我们提供了中间件来满足需求。

redux中间件原理

redux 借鉴了 Koa里 middleware 的思想,即鼎鼎大名的洋葱模型。

不过这里请求对应的是dispatch的过程。

每次dispatch的过程中,都要依次将中间件执行一遍。

遇到阻塞或者操作完成,执行下个中间件,直到执行完成,以便我们事先日志,监控、异常上报等功能。

那么redux 又是如何支持中间件的呢。这就离不开applyMiddleware了。

这里前面的

applyMiddleware实现思路

实现思想比较简单,通过科里化和compose,为符合规范的中间件分配访问dispatch和store的途径,以便在不同阶段来自定义数据更新。

例如异步操作,返回的不是对象,那么就执行返回的函数,然后调用下一个中间件。等异步请求结束,再次dispatch 对应的action。

export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 赋予每个中间件访问store的能力。
const middlewareAPI = {
getState: store.getState,
// 箭头函数保存dispatch,保证其的同步更新
dispatch: (...args) => dispatch(...args)
}
// 串联中间件,并赋予每个中间件访问dispatch的能力。
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 关联dispatch与中间件,组合调用之后得到类似下面的新对象
// dispatch = f1(f2(f3(store.dispatch))));
dispatch = compose(...chain)(store.dispatch) return {
...store,
dispatch
}
}
}

这样执行之后返回的,对象就是增强之后的store了。

compose的实现

redux中compose是柯里化函数的一个示例,目的是将函数串联起来。

/**
* 函数组合,科里化的串联
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} if (funcs.length === 1) {
return funcs[0]
} return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

结合redux-thunk示例

redux-thunk源码,实现也很优雅,对于返回的function,将dispatch等参数传递进去,然后执行,等待回调异步完成再dispatch。对于正常对象则进行下一步。

function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
// 每次dispatch的时候都会进行判断,如果是个函数,那就执行函数,不再进行下一步吧,这样就避免了,函数不满足action要求的问题
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
} return next(action);
};
} const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware; export default thunk;

那么实际使用时,在createStore时加入该中间件即可:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
const store = createStore(
reducer,
applyMiddleware({
...middleware,
thunk})
)

那么到这里对于redux的中间件 也就是问题2,我想大家也比较清楚了。

对于常见中间件可以参考

结束语

参考文章

redux中文文档

深入React技术栈

加上重读redux源码一带着问题看 react-redux 源码实现总算将redux及react-redux重读了一遍。可能有人会说道这些源码,看完也会忘,有这个必要吗。我感觉分情况来看,如果我们只是使用,那么看官方文档就可以了,当遇到某些疑问好像找不到贴切解释的时候,不放一看。

此外也是学习大佬们的设计思路和实现方式,有的放矢才能开卷有益。

带着问题看redux源码的更多相关文章

  1. 带你逐行阅读redux源码

    带你逐行阅读redux源码 redux版本:2019-7-17最新版:v4.0.4 git 地址:https://github.com/reduxjs/redux/tree/v4.0.4 redux目 ...

  2. 带着问题看 react-redux 源码实现

    前言 Redux作为通用的状态管理器,可以搭配任意界面框架.所以并搭配react使用的话就要借助redux官方提供的React绑定库react-redux,以高效灵活的在react中使用redux.下 ...

  3. 零基础带你看Spring源码——IOC控制反转

    本章开始来学习下Spring的源码,看看Spring框架最核心.最常用的功能是怎么实现的. 网上介绍Spring,说源码的文章,大多数都是生搬硬推,都是直接看来的观点换个描述就放出来.这并不能说有问题 ...

  4. 带着萌新看springboot源码8(spring ioc源码 完)

    上一节说到实例化了所有的单实例Bean,后面还有一步遍历 12.完成容器刷新(finishRefresh();) 那个和生命周期有关的后置处理器类型是LifecycleProcessor:监听器原理我 ...

  5. Redux源码分析之createStore

    接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象. return { dispatch, subscribe, getSt ...

  6. redux源码浅入浅出

    运用redux有一段时间了,包括redux-thunk和redux-saga处理异步action都有一定的涉及,现在技术栈转向阿里的dva+antd,好用得不要不要的,但是需要知己知彼要对react家 ...

  7. Redux源码分析之applyMiddleware

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  8. Redux源码分析之bindActionCreators

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  9. Redux源码分析之combineReducers

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

随机推荐

  1. C++性能榨汁机之虚函数的开销

    C++性能榨汁机之虚函数的开销 来源  http://irootlee.com/juicer_vtable/ 虚函数的实现 虽然C++标准并没有规定编译器实现虚函数的方式,但是大部分编译器均是采用了虚 ...

  2. 查找最大和次大元素(JAVA版)(分治法)

    问题描述:对于给定的含有n个元素的无序序列,求这个序列中最大和次大的两个不同元素. 问题求解分析(分治法):先给出无序序列数组a[low...high].第一种情况为当数组中只有一个元素时,此时只存在 ...

  3. [转载]Pytorch详解NLLLoss和CrossEntropyLoss

    [转载]Pytorch详解NLLLoss和CrossEntropyLoss 来源:https://blog.csdn.net/qq_22210253/article/details/85229988 ...

  4. iOS UIControl 事件的说明(转)

    在控件事件中,简单解释下下面几个事件. 说明:由于是在“iOS 模拟器”中测试的,所以不能用手指,只能用鼠标. 1)UIControlEventTouchDown 指鼠标左键按下(注:只是“按下”)的 ...

  5. linux 下使用 VirtualBox 搭建集群环境

    参考文章: https://www.nakivo.com/blog/virtualbox-network-setting-guide/ https://help.ubuntu.com/lts/serv ...

  6. Python-memcached的使用用法

    Memcached API set(key,val,time=0,min_compress_len=0) 无条件键值对的设置,其中的time用于设置超时,单位是秒,而min_compress_len则 ...

  7. 7.移动端自动化测试-小知识 try...except...finally语句

    异常Error 我们在写代码的时候,经常会遇见程序抛出Error无法执行的情况 一般情况下,在Python无法正常处理程序时就会发生一个异常.异常是Python对象,表示一个错误.当Python脚本发 ...

  8. .net 调用 winapi获取窗口句柄和内容

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  9. shell数组处理

    linux shell在编程方面比windows 批处理强大太多,无论是在循环.运算.已经数据类型方面都是不能比较的. 下面是个人在使用时候,对它在数组方面一些操作进行的总结.   1.数组定义   ...

  10. 004.MVC视图、辅助方法

    一.视图基础- 视图定义: 用户界面,是显示应用程序用户界面(UI)组件 Web应用程序:页面 作用: 1.输出/显示模型数据 2.出入提交 视图建议在View文件夹位置存储视图 视图引擎(了解):本 ...