redux 源码浅析

redux 版本号: "redux": "4.0.5"

redux 作为一个十分常用的状态容器库, 大家都应该见识过, 他很小巧, 只有 2kb, 但是珍贵的是他的 reducerdispatch 这种思想方式

在阅读此文之前, 先了解/使用 redux 相关知识点, 才能更好地阅读本文

入口文件

入口是在 redux/src/index.js 中, 在入口文件中只做了一件事件, 就是引入文件, 集中导出

现在我们根据他导出的方法, 来进行分析

createStore

这个是 redux 最主要的 API

使用

搭配这使用方法一起, 可以更好的浏览源码

createStore(reducer, [preloadedState], [enhancer])

他的主要功能就是创建一个 store, 将 reducer 转换到 store

参数

一共可接受三个参数:

  1. reducer (函数): 一个返回下一个状态树的还原函数,给定当前状态树和一个要处理的动作。

  2. [preloadedState] (任意值): 初始值, 可以是来自于 storage 中的; 如果你用combinedReducers产生了reducer,这必须是一个普通对象,其类型与传递给它的键相同。

    也可以自由地传递任何你的reducer能够理解的东西。

  3. [enhancer] (函数): store 的增强器, 可以选择性的增强, 用代码来说就是 enhancer(createStore)(reducer, preloadedState), enhancer

    接受的参数就是 createStore, 同样地他也需要 return 一个类似于 createStore 的结果, 也就是说, 只有我们返回的是 一个像 createStore 的东西,

    他的具体实现我们就可以有很多微调 这里附上一篇探讨 enhancerapplyMiddleware 的文章 https://juejin.cn/post/6844903543502012429

// 简单的例子:

function counterReducer(state, action) {
switch (action.type) {
case 'counter/incremented':
return {value: state.value + 1}
case 'counter/decremented':
return {value: state.value - 1}
default:
return state
}
} let store = createStore(counterReducer, {
value: 12345
})

store

createStore 返回的当然是一个 store, 他有自己的 api

getState

返回应用程序的当前状态树

const state = store.getState()

dispatch(action)

这个其实不用我多说, 会 redux 的都应该知道这个

store.dispatch({type: 'counter/incremented'})

subscribe(listener)

添加一个监听器, 每当 action dispatch 的时候, 都会调用 listener, 在 listener 中可以使用 getState 来获取当前的状态树

const unsubscribe = store.subscribe(() => {
console.log('listener run')
const current = store.getState()
if (current.value === 12350) {
unsubscribe()
}
})

展示一个场景, 监听事件, 当达到某个条件之后, 解除监听事件

replaceReducer(nextReducer)

使用一个 reducer 替换当前的 reducer,对于 reducers 实现动态加载,也可以为 Redux 实现热重载机制

源码解析

createStore 文件是在 redux/src/createStore.js 中, 他接受的参数就是上面我们说的那三个, 返回的也就是 store

首先是一段参数的判断, 以及 enhancer 的返回

// 为了适配 createStore(reducer, enhancer) 的情况
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
} if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// enhancer 的使用场景
return enhancer(createStore)(reducer, preloadedState)
}

接下来定义一些变量和函数

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false // 如果相等 , 做了一层浅拷贝 将 currentListeners 同步到 nextListeners 中
// 避免相互影响
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

store.getState

function getState() {
// isDispatching 默认为 false, 表示当前 store 是否正在 dispatch
if (isDispatching) {
throw new Error('//...')
} // 直接返回当前 state , 默认为入参 preloadedState
return currentState
}

store.subscribe

// 忽略了错误判断
function subscribe(listener) {
let isSubscribed = true // 同步 nextListeners , currentListeners
ensureCanMutateNextListeners() // 将 listener 加入 nextListeners
nextListeners.push(listener) // 返回解除监听函数
return function unsubscribe() {
if (!isSubscribed) {
// 如果 isSubscribed 已经为 false 了 则 return
// 情况 1, 已经执行过unsubscribe了一次
return
} // flag
isSubscribed = false // 同步 nextListeners , currentListeners
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
// 搜索 监听器, 删除
currentListeners = null
}
}

store.dispatch

  function dispatch(action) {
// 省略了 action 的 错误抛出
// 总结: action 必须是一个 Object 且 action.type 必须有值存在 // 如果当前正在 isDispatching 则抛出 错误(一般来说不存在 try {
isDispatching = true
// 执行 reducer, 需要注意的是 currentReducer 不能为异步函数
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
} // 将 nextListeners 赋值给 currentListeners 执行 nextListeners 里面的监听器
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
} // 返回 action
return action
}

store.replaceReducer

function replaceReducer(nextReducer) {
// 如果 nextReducer 不是函数则抛出错误 // 直接替换
currentReducer = nextReducer // 类似 ActionTypes.INIT. 替换值
dispatch({type: ActionTypes.REPLACE})
}

store.observable

还有一个额外的 observable 对象:

// 一个 Symbol.observable 的 polyfill
import $$observable from 'symbol-observable' function observable() {
// subscribe 就是 store.subscribe
const outerSubscribe = subscribe
return {
subscribe(observer) {
// 如果 observer 不是对象或者为 null 则抛出错误 function observeState() {
if (observer.next) {
// next 的入参为 当然 reducer 的值
observer.next(getState())
}
} observeState() // 添加了监听
const unsubscribe = outerSubscribe(observeState)
return {unsubscribe}
}, // 获取到当前 对象, $$observable 值是一个 symbol
[$$observable]() {
return this
}
}
}

这里使用了 tc39 里未上线的标准代码 Symbol.observable, 如果你使用或者了解过 rxjs, 那么这个对于你来说就是很简单的, 如果不熟悉,

可以看看这篇文章: https://juejin.cn/post/6844903714998730766

剩余代码

function createStore() {
// 省略 // 初始化了下值
dispatch({type: ActionTypes.INIT}) // 返回
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}

combineReducers

使用

// 可以接受多个 reducer, 实现一种 module 的功能
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer}) // 返回值
{
potato: {
// 某些属性
}
,
tomato: {
// 某些属性
}
} const store = createStore(rootReducer, {
potato: {
// 初始值
}
})

有一点需要注意的是, reducer 都是需要默认值的,如:

function counterReducer(state = {value: 0}, action) {
//...
}

源码解析

combineReducers

先看 combineReducers 执行之后产生了什么

function combineReducers(reducers) {
// 第一步是获取 key, 他是一个数组
const reducerKeys = Object.keys(reducers)
const finalReducers = {} // 遍历 reducers, 赋值到 finalReducers 中, 确保 reducer 是一个函数, 不是函数则过滤
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i] // 省略 reducers[key] 如果是 undefined 抛出错误 if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
} // finalReducerKeys 一般来说是和 reducerKeys 相同的
const finalReducerKeys = Object.keys(finalReducers) //定义了两个遍历
let unexpectedKeyCache
let shapeAssertionError try {
// 此函数后面会详细讲述
// 答题作用就是确认 finalReducers 中都是有初始值的
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
//...
}

再看他又返回了什么(记住结果必然也是一个 reducer)

function combineReducers(reducers) {

    //...

    return function combination(state = {}, action) {
// 如果 assertReducerShape 出错则抛出错误
if (shapeAssertionError) {
throw shapeAssertionError
} // 忽略非 production 代码 // 预先定义一些变量
let hasChanged = false
const nextState = {} // 循环 finalReducerKeys
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key] const previousStateForKey = state[key] // 这是一开始的值
const nextStateForKey = reducer(previousStateForKey, action) // 通过 reducer 再次生成值 // 如果 nextStateForKey === undefined 则再次抛出异常 // 给 nextState 赋值
nextState[key] = nextStateForKey // 判断是否改变 (初始值是 false) 判断简单的使用 !== 来比较
// 如果已经为 true 就一直为 true 了
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
} // 循环后再次对 true 做出判断
// 是否少了 reducer 而造成误判
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length // 如果改变了 返回新值, 否则返回旧值
return hasChanged ? nextState : state
}
}

combineReducers 基本就是上述两个函数的结合, 通过循环遍历所有的 reducer 计算出值

assertReducerShape

function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
// 遍历参数里的 reducer
const reducer = reducers[key] //执行初始操作 产生初始值都有初始值
const initialState = reducer(undefined, {type: ActionTypes.INIT}) //... 如果 initialState 是 undefined 则抛出错误 // 如果 reducer 执行未知操作 返回的是 undefined 则抛出错误
// 情景: 当前 reducer 使用了 ActionTypes.INIT 来产生值, 这能够通过上一步
// 但在这一步就会被检测出来
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
//... 抛出错误
}
})
}

这里我们可以知道一点, 所有 reducer 我们都必须要有一个初始值, 而且他不能是 undefined, 可以是 null

compose

这里需要先讲 compose 的使用 才能顺带过渡到下面

使用

就如他的名字, 是用来组合函数的, 接受刀哥函数, 返回执行的最终函数:

// 这里常用来 链接多个中间件
const store = createStore(
reducer,
compose(applyMiddleware(thunk), DevTools.instrument())
)

源码解析

他的源码也很简单:

function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} if (funcs.length === 1) {
return funcs[0]
}
// 上面都是 控制, 参数数量为 0 和 1 的情况 // 这里是重点, 将循环接收到的函数数组
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

我们将 reduce 的运行再度装饰下:

// reduce 中没有初始值的时候, 第一个 `prevValue` 是取  `funcs[0]` 的值

funcs.reduce((prevValue, currentFunc) => (...args) => prevValue(currentFunc(...args)))

reducer 返回的就是 这样一个函数 (...args) => prevValue(currentFunc(...args)), 一层一层嵌套成一个函数

举一个简单的输入例子:

var foo = compose(val => val + 10, () => 1)

foo 打印:

(...args) => a(b(...args))

执行 foo() , 返回 11

applyMiddleware

使用

applyMiddleware 是使用在 createStore 中的 enhancer 参数来增强 redux 的作用

可兼容多种三方插件, 例如 redux-thunk, redux-promise, redux-saga 等等

这里使用官网的一个例子作为展示:


function logger({getState}) {
// next 就是真正的 store.dispatch
return next => action => {
console.log('will dispatch', action) const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue
}
} const store = createStore(rootReducer, {
counter: {value: 12345}
}, applyMiddleware(logger))

源码解析

default

function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 因为使用了 enhancer 参数, 他的内部没有 createStore 的东西, 所以这里需要重新 createStore
const store = createStore(...args)
let dispatch = () => {
// 在中间件中 不允许使用 dispatch
throw new Error(
// 省略报错...
)
} // 这是要传递的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
} // 重新 map 所有 middlewares 返回需要的结果
const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 这里就是我们上面的 compose 相关的代码, 返回的结果 再次执行 得到真正的 dispatch
dispatch = compose(...chain)(store.dispatch) // 返回 store 和 dispatch
return {
...store,
dispatch
}
}
}

这里我们需要重新捋一捋函数的执行, 中间件以上述的 logger 为例子

applyMiddleware(logger) -> 返回的是一个函数(createStore) => (...args) => {/*省略*/} 我把他记为中间件函数 1

也就是说 applyMiddleware(logger) === (createStore) => (...args) => {/*省略*/}

这个函数将在 createStore 中使用 enhancer(createStore)(reducer, preloadedState) 这里的 enhancer 就是中间件函数 1 通过 createStore

的执行我们可以发现

store === createStore(reducer, preloadedState, enhancer) === enhancer(createStore)(reducer, preloadedState)

=== applyMiddleware(logger)(createStore)(reducer, preloadedState)

=== ((createStore) => (...args) => {/*省略*/})(createStore)(reducer, preloadedState)

=== 中间件函数 1 中的{/*省略*/} 返回结果 通过这一层的推论我们可以得出 store === 中间件函数 1中的 {/*省略*/} 返回结果

bindActionCreators

使用

这个 API 主要是用来方便 dispatch 的 他接受 2 个参数 , 第一个是对象或函数, 第二个就是 dispatch 返回值的类型很第一个参数相同

首先我们要定义创建 action 的函数

function increment(value) {
return {
type: 'counter/incremented',
payload: value
}
} function decrement(value) {
return {
type: 'counter/decremented',
payload: value
}
}

使用情况 1:

function App(props) {
const {dispatch} = props // 因为在 hooks 中使用 加上了 useMemo
const fn = useMemo(() => bindActionCreators(increment, dispatch), [dispatch]) return (
<div className="App">
<div>
val: {props.value}
</div>
<button onClick={() => {
fn(100)
}}>plus
</button>
</div>
);
}

使用情况 2:

function App(props) {
const {dispatch} = props const fn = useMemo(() => bindActionCreators({
increment,
decrement
}, dispatch), [dispatch]) // 如果想用 decrement 也是这样调用 fn.decrement(100)
return (
<div className="App">
<div>
val: {props.value}
</div>
<button onClick={() => {
fn.increment(100)
}}>plus
</button>
</div>
);
}

源码解析

function bindActionCreator(actionCreator, dispatch) {
return function () {
// 执行 dispatch(actionCreator()) === dispatch({type:''})
return dispatch(actionCreator.apply(this, arguments))
}
} function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
// 如果是函数直接执行 bindActionCreator
return bindActionCreator(actionCreators, dispatch)
} if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(/*省略*/)
} // 定义变量
const boundActionCreators = {} // 因为是对象 循环遍历, 但是 for in 效率太差
for (const key in actionCreators) {
const actionCreator = actionCreators[key] // 过滤
if (typeof actionCreator === 'function') {
// 和函数同样 执行 bindActionCreator 并且赋值到 boundActionCreators 中
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}

bindActionCreators 的源码相对简单一点, 理解起来相对也容易很多

总结

redux 中设计的很多地方都是很巧妙的,并且短小精悍, 值得大家作为首次源码阅读的选择

如果我讲的有什么问题, 还望不吝指教

相关文章: react-redux 源码浅析

本文代码仓库: https://github.com/Grewer/react-redux-notes

参考文档:

redux 源码浅析的更多相关文章

  1. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  2. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. Struts2源码浅析-ConfigurationProvider

    ConfigurationProvider接口 主要完成struts配置文件 加载 注册过程 ConfigurationProvider接口定义 public interface Configurat ...

  4. (转)【深入浅出jQuery】源码浅析2--奇技淫巧

    [深入浅出jQuery]源码浅析2--奇技淫巧 http://www.cnblogs.com/coco1s/p/5303041.html

  5. HashSet其实就那么一回事儿之源码浅析

    上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap,  本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...

  6. Android 手势识别类 ( 三 ) GestureDetector 源码浅析

    前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势.所 以,用户绘制出的一个完整的手势是需要一定的代码 ...

  7. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

  8. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. Android手势源码浅析-----手势绘制(GestureOverlayView)

    Android手势源码浅析-----手势绘制(GestureOverlayView)

随机推荐

  1. SpringBoot-容器启动的时候执行一些内容

    SpringBoot的ApplicationRunner.CommandLineRunner 场景: 在开发中可能会有这样的情景.需要在容器启动的时候执行一些内容.比如读取配置文件,数据库连接之类的. ...

  2. JDBC_05_ResorceBundle(资源绑定器) 绑定配置文件

    ResorceBundle(资源绑定器) 绑定配置文件 jdbc.proprtise 需要在src目录下新建一个文件夹然后将jdbc.proprtise放在文件中然后右键该文件夹选择 Rebuild ...

  3. 你可能不知道的CSS元素隐藏“失效”以其妙用

    在CSS中,让元素隐藏(指屏幕范围内肉眼不可见)的方法很多,有的占据空间,有的不占据空间:有的可以响应点击,有的不能响应点击.后宫选秀--一个一个看. { display: none; /* 不占据空 ...

  4. 从苏宁电器到卡巴斯基第16篇:我在苏宁电器当营业员 VIII

    其实不想走,其实很想留 我在之前的故事中说过,在大四的时候,我其实是想考研的,但是看了一段时间的书以后,发现自己实在不是那块料,主要是数学实在是学不明白,那么也就只能作罢了.而后来面试中石化的时候,尽 ...

  5. hdu4909 状态压缩(偶数字符子串)

    题意:       给你一个字符串,里面最多有一个'?','?'可以表示'a' - 'z',也可以什么都不表 示,这里要明确,什么都不表示不是不存在的意思,当aa什么都不表示的时候aa 也不等于aa? ...

  6. PowerShell-6.文件操作

    1.显示文本内容 Get-Content "°C:\\Program Files (x86)\\PsUpdate\\b.dat" 2.得到b.dat文件内容,然后把里面的所有'C' ...

  7. 【js】Leetcode每日一题-数组异或操作

    [js]Leetcode每日一题-数组异或操作 [题目描述] 给你两个整数,n 和 start . 数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 n == ...

  8. 【转】风控中的特征评价指标(一)——IV和WOE

    转自:https://zhuanlan.zhihu.com/p/78809853 1.IV值的用途 IV,即信息价值(Information Value),也称信息量. 目前还只是在对LR建模时用到过 ...

  9. spring.framework 版本从4.1.6.RELEASE升到5.0.20.RELEASE

    将org.springframework 使用到的jar 版本号改为5.0.20.RELEASE后运行会报错: Servlet.service() for servlet [springmvc] in ...

  10. 04.06 UCF Local Programming Contest 2017

    A.Electric Bill 题意:简单计算题,超过1000部分额外算 1 #include<stdio.h> 2 int main(){ 3 int money1,money2; 4 ...