以前一直在项目中会使用到react-redux
来使用 redux
,但是从来没去仔细了解redux
的原理!!!那么最近课少啦~可以仔细研究一下啦!!🤔🤔
在阅读过程,我会向自己提很多问题,然后到处去找答案,所以也算得上学有所获吧!
这篇文章适合使用过redux的朋友阅读
关于源码版本还是用的4.0,没有讲ts版本,我认为如果用ts的版本来分析的话,会涉及讲解很多类型判断,不太利于文章可读性
文章有点长,大致目录如下:
util文件夹
---actionTypes
---isPlainObject ( 详解 ,为什么redux采用这样的方式)
---warnings
src文件夹
---index (伪函数的作用)
---createStore
---柯里化、高阶函数(帮助理解applymiddleware)
---compose(函数组合、reduce、reduceRight方法)
---appMiddlewares (重点详解,用redux-thunk的栗子来讲解)
---bindActionCreators(什么是action creator)
---combineReducer (CombineReducers调用replaceReducers的判断)
我自己的逼逼赖赖~
---为什么reducer必须是纯函数
---为什么要通过中间件派发异步action
^^应该会持续更新吧^^
总结
utils文件夹
一般在阅读源码的时候,会从index
文件开始阅读,但我认为先了解一下工具类的函数,能帮助我们更好的理解源码,所以我们先从utils
文件开始:
actionTypes
//用于生成一个随机字符串
const randomString = () =>
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
这个文件主要是用来定义三种redux的保留types:
INIT(reducer的初始化类型)
REPLACE(reducer的替换类型)
PROBE_UNKNOWN_ACTION(随机类型)
同时,为了确保这三种types的唯一性,在每个type的末尾加了一个随机生成的字符串
*isPlainObject
export default function isPlainObject(obj) {
// obj必须是一个非空的对象
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
//沿着原型链一直向上查找,直到proto的上一级(父亲)是null的时候停止,这个时候的proto是null的儿子
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
//将obj的上一级(父亲)与proto进行比较,相当于满足plainObject条件是 null => a object => plainObject
return Object.getPrototypeOf(obj) === proto
}
这个函数表面上看来平平无奇,主要是用来判断对象是否是一个简单对象
,但是它有一些很有趣的特点
首先 plain object是没有一个官方的定义的对象,一般俗称简单对象,
在上面的这个函数中,参数obj只能是通过字面量({}),或者new Object()创建的对象,
obj不能是通过Object.create()创建的对象,即使是Object.create(null)也不行,
当然,在看源码的时候,为什么这个判断不能直接这样写,明明可以实现一样的功能🧐🧐
let proto = Object.getPrototypeOf(obj)
return !!proto && Object.getPrototypeOf(proto) === null
后来我在redux的github上的commit历史上看见,他以前的写法是这样的:
改变总是有一定道理的,在redux的github的issue中有这样的讨论,作者是这样说的:
翻译过来是这样的:
它通过遍历原型链直到到达Object来处理跨领域对象,并检查“ Object”是否具有与我们需要判断的普通对象相同的原型。
对于来自iframe的对象,该对象不同于我们的本地对象,因为它们来自两个不同的JavaScript上下文。因此,我们使用这种技术来获得对象实例的Object定义。
它可以很好地提高性能,因为您可以在相当数量的情况下将它们短路,然后再进入较慢的toString()
调用(这是使lodash / isPlainObject和is-plain-object变慢的部分)。
第三句话,我的理解是他能通过在调用toString()
方法前,通过终止一些工作来提升性能(如果您有更好的理解,可以在留言区告诉我😝😝😝😝)
总的来说,使用循环判断的好处如下:
1.解决cross-realm对象的问题
2.可以拥有更高的性能
warning
export default function warning(message) {
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
try {
throw new Error(message)
} catch (e) {}
}
这个函数的功能没什么好说的啦,就是对错误信息的封装啦~
index
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) { ...警告信息 }
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
index文件作为代码的入口,除了将方法暴露出去,还有以下妙用:
不知道你有没有注意到下面这个函数:
function isCrushed() {}
这是一个伪函数
函数在被压缩的情况下,函数名会被替换成单个字母甚至_
因此,我们可以利用这个伪函数,来判断在开发环境中,是否存在代码被压缩的情况
如果存在这样的情况,就抛出错误
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
在学习接下来的源码之前,我们来想想,在实际redux项目中,是如何创建store的:
在这里enhancer就相当于通过applyMiddleware应用,并且被compose进行组合的一些中间件合集(可能听起来有点拗口😸)
createStore
createStore是redux的核心板块,他的作用是创建一个store对象,以及暴露ensureCanMutateNextListeners
、getState
、subscribe
、dispatch
、replaceReducer
、observable
这几个方法
export default function createStore(reducer, preloadedState, enhancer){
//一些判断
function ensureCanMutateNextListeners() {}
function getState() {}
function subscribe() {}
function dispatch() {}
function replaceReducer() {}
function observable() {}
dispatch({ type: ActionTypes.INIT })
}
[$$observable]
createStore代码的第一行是:
import $$observable from 'symbol-observable'
他的作用是什么呢?
在这个npm包的readme中是这样描述的
意思是在搭配
Redux
、RxJS
这样的库的时候,将我们的对象变得observable,同时需要避免在调用error、complete、unsubscribe之后再次调用error、complete等
其实在实际开发中,这个方法我们一般用不到👻👻👻👻
export default function createStore(reducer, preloadedState, enhancer) {
}
createStore接收三个参数,reducer(function)、preloadedState(初始的时候的state)、enhancer(function,是一个高阶函数)在官方文档中对这三个参数也有明确的解释:
一些判断
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
这个判断是为了保证单一enhancer的原则,如果有多个enhancer,是需要通过compose将他们组合在一起的
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
对于参数preloadedState是一个可选的,所以,当createStore的第二个参数是函数且第三个参数为undefined的时候,默认第二个参数传入的是enhancer。同时,结合上一个判断,preloadedState和enhancer不能同时为函数,这样就直接限制了preloadedState不能为函数,但这样会让我有一点迷惑,就是在redux官方文档中,preloadedState的类型是any,虽然这样有点抠字眼,后来我在github的issues中找到了答案:
其实这个点也算不上很重要,但能有一个官方解释还是挺好的👻🤡😸🤠😬🤔😝😋
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
增强store的功能,让它拥有第三方的功能,比如middleware.applyMiddleware()就是一个enhancer(在讲applyMiddleware再具体讲🧐)
这样子也相当于在存在enhancer的情况下,将创建store的过程完全交给了中间件
构建store的初始化
//存储当前的reducer函数
let currentReducer = reducer
//存储当前的state
let currentState = preloadedState
//存储当前的事件监听器
let currentListeners = []
//当前的事件监听器的引用
let nextListeners = currentListeners
//当前是否在派发action(false)
let isDispatching = false
ensureCanMutateNextListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
这是createStore的内部方法,从代码来讲,只是当nextListeners等于currentListeners时,将currentListeners拷贝并且赋值给nextListeners,感觉平平无奇。(后面再讲为啥👻)
getState
function getState() {
//...正在派发action时调用会抛出错误
return currentState
}
直接返回当前的state
subscribe
function subscribe(listener) {
//listener不是函数的时候抛出错误
if (typeof listener !== 'function') { ... }
//正在派发action的时候抛出错误
if (isDispatching) { ... }
//订阅状态设置为true
let isSubscribed = true
//对当前的currentListeners进行拷贝
ensureCanMutateNextListeners()
//对nextListeners添加lister
nextListeners.push(listener)
return function unsubscribe() {
...
}
}
unsubscribe
function subscribe(listener) {
...
return function unsubscribe() {
//没有订阅就取消
if (!isSubscribed) { return }
//正在派发action时抛出错误
if (isDispatching) { ... }
//取消订阅
isSubscribed = false
//再次拷贝,取消当前监听器
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
在这里,我们再来回顾一下 ensureCanMutateNextListeners
,为什么要进行拷贝不直接使用currentListers
其实就是避免在在dispatch
的过程中,又去调用subscribe/unsubscribe
dispatch
function dispatch(action) {
// action必须是纯对象
if (!isPlainObject(action)) { ... }
// action对象中必须有type属性
if (typeof action.type === 'undefined') { ... }
// 如果正在派发action则报错,避免在reducer中调用dispatch,dispatch又调用reducer,形成一个循环...
if (isDispatching) { ... }
try {
// 设置当前状态为 派发
isDispatching = true
// 在reducer中传入当前状态和要改变的状态,获取到最新状态
currentState = currentReducer(currentState, action)
} finally {
// action结束后,改变状态
isDispatching = false
}
//将nextListeners赋值给currentListeners以及listeners,并挨个触发监听;
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
replaceReducer
function replaceReducer(nextReducer) {
// nextReducer必须是函数
if (typeof nextReducer !== 'function') { ... }
// 改变当前的reducer
currentReducer = nextReducer
// 通知所有的reducer状态发生改变
dispatch({ type: ActionTypes.REPLACE })
}
对于 replaceReducer
的使用场景,我在官网中只看见了 代码分割、在其他地方看到的还有 热更新机制
、 动态的加载不同的reducer
observable
RxJS-based middleware for Redux that allows developers to work with async actions
他是一个基于RxJS的中间件,用于异步action,在我们的日常使用中,其实不怎么会涉及,就先忽略吧🤔
完成store的初始化
dispatch({ type: ActionTypes.INIT })
暴露一些方法
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
柯里化、高阶函数
在学习compose和applymiddleware之前,我们需要简单了解一些前置知识,如果你已经了解了,就先跳过吧 !!!
从概念上来讲:
- 高阶函数:是接收函数作为
参数
,或将函数作为返回输出
的函数
// 比如我们在react项目使用react-redux
connect()();
- 柯里化:接受
多个参数
的函数变换成接受一个单一参数
(最初函数的第一个参数)的函数,并且返回接受余下的参数
而且返回结果的新函数
的技术(我认为柯里化就是高阶函数的一种应用)
// 非柯里化的形式
function add(x, y){
return x + y;
}
add(1, 2) // 3
// 柯里化形式
function curryAdd(x) {
return function(y) {
return x + y
}
}
curryAdd(1)(2) // 3
上面提到的都没有细讲,他们涉及到很多函数式编程的模式,如果细讲,篇幅将会很长,从学习redux的角度来讲,我们先大致了解他们的意思就行
compose
compose指的是函数组合,是函数式编程中非常重要的思想,当然也不仅仅只局限于此
在redux中作用就是实现任意的、多种的、不同的功能模块的组合,从而达到增强redux的功能的作用
export default function compose(...funcs) {
//入参为空,返回任意函数
if (funcs.length === 0) {
return arg => arg
}
//入参只有一个函数,就返回这个函数
if (funcs.length === 1) {
return funcs[0]
}
//这才是compose的重点😝
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
除去一些判断,compose的核心代码也就是最后那一句话
- 首先,用到了
Array.prototype.reduce
方法,reduce方法其实很强大,更多内容可以参考文档
他的语法 arr.reduce(callback,[initialValue])
他的功能 从左向右,对数组中的每个元素按照传入回调函数和初始值进行运算
callback:函数中包含四个参数
- previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
- currentValue (数组中当前被处理的元素)
- index (当前元素在数组中的索引)
- array (调用的数组)
initialValue (作为第一次调用 callback 的第一个参数)
const arr = [1, 2, 3]
const sum = arr.reduce((sum, val) => {
return sum + val;
}, 0);
//sum: 6
compose的规律
compose(a, b)
=> (...args) => a(b(...args))
compose(a, b, c)
=> (...args) => a(b(c(...args)))
也就是说他的处理方式是按照 入参顺序 相反
的顺序执行的,上一个函数的结果,作为下一个函数的入参
这和koa的洋葱圈模型很像 (aop)
再用一张图来说明:
其实我们还可以发现,这不就是reduceRight方法嘛!!=> 文档
也就是说,compose函数可以这样写:
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1];
const rest = funcs.slice(0, -1);
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
而且从提交记录来看,以前真的用的是reduceRight方法
至于更改的原因,作者是这样回复的:
也就是说从性能上来讲,这样的方式会比之前快1到3倍
最后注意一下,compose返回的是一个函数,只有调用他的时候才会实现他的功能compose(a,b)()
applyMiddleware
默认情况下,createStore()
所创建的Redux store
没有使用middleware
,所以只支持同步数据流
。
我们可以使用applyMiddleware()
来增强createStore()
。虽然这不是必须的,但是它可以帮助你用简便的方式来描述异步的action
。
没有使用middleware:
使用middleware后:
再来看源码:
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.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
我们一段一段的分析
return createStore => (...args) => {
const store = createStore(...args)
...
}
分析之前,先来回顾一下:
redux官网中applymiddleware的用法
const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))
createStore中当enhancer
存在且为函数时,enhancer(applymiddleware)
的调用
return enhancer(createStore)(reducer, preloadedState)
结合来看,这个调用就相当于
applyMiddleware(logger)(createStore)(todos, ['Use Redux'])
or
applyMiddleware(middlewware)(createStore)(reducer, preloadedState)
这个格式让我们想到什么,柯里化
,对不对!!再想想柯里化的运行顺序!
了解到这一点后,我们再来分析一下一下源码
// createStore就是createStore函数,...args指的是createStore的参数(reducer和preloadedState)
return createStore => (...args) => {
// 利用传进来的方法,创建一个store对象,相当于redux将创建store的权利完全交给了中间件
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.'
)
}
这句代码的的意义就是,中间件构建过程中禁止调用dispatch
,为什么呢?
中间件存在的原因就是加强redux
的功能,也就是通过增强dispatch
的功能,在这个dispatch
组装好之前,他是没有覆盖redux
原有的dispatch
的,这可能会导致一些功能没办法实现,
比如redux本来只支持同步action
,在使用middleware
后才能支持异步action
,如果这个中间件还没有构建好,调用dispatch
就没有办法实现派发异步action
的功能
同时,用let定义dispatch的目的也就是为了方便更改dispatch
的引用
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 将chain传入compose中进行组合后,改装dispatch
dispatch = compose(...chain)(store.dispatch)
middlewareAPI是一个applyMiddleware的内置对象
在遍历中间件(middlewares.map)
的时候middlewareAPI
作为参数传入每个中间件中,将返回结果传递给chain
结合redux-thunk的源码来分析下这是一个怎样的过程
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
上面的代码是柯里化后的,我们转换成es5来看:
function createThunkMiddleware(extraArgument) {
// 为中间件的第一层传入middlewareAPI对象
return function({ dispatch, getState }) {
// 这里返回的函数就是chain,对chain进行compose后将store.dispatch作为参数next传入
return function(next) {
// 改写过后的dispatch,覆盖原有的dispatch
return function(action) {
// 如果参数是函数,调用改写过后dispatch
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 如果参数不是函数,调用原本的dispatch
return next(action);
};
}
}
}
说到这里可能有点绕,我们再来整理一下这个过程
-
中间件的形式都为
({getStore, dispatch}) => (next) => (action) => {...}
-
通过遍历,给中间件的第一层函数传入
middlewareAPI
对象 -
遍历后,返回了
chain
数组,chain
数组的每一项都是形如(next)=>(action)={...}
的函数 -
compose函数的每一个参数也就是
(next)=>(action)={...}
-
compose
的组合方式表明,next后得到的(action)={...}
作为下一个函数的参数next
-
最后一个组合函数的
next
是store.dispatch
-
最后
compose
后返回的函数(action)={...}
赋值给dispatch
return {
...store,
dispatch
}
返回创建的store
和包装后的dispatch
再用图来说明一下middleware
的作用👻👻👻👻👻👻
------>
bindActionCreator
首先我们要先了解一个概念,什么是action creator
,action creator
是一个创建action
的函数,他相当于一个工厂,只负责action的创建(create)
,而不能分发(dispatch)
那么bindActionCreator
做了什么呢?他相当于将这个工厂升级了,使工厂既能创建action,又能分发action
再来看看官网的定义:
把一个 value
为不同 action creator
的对象,转成拥有同名 key
的对象。同时使用dispatch
对每个action creator
进行包装,以便可以直接调用它们。
一般情况下你可以直接在 Store
实例上调用 dispatch
。如果你在 React
中使用 Redux
,react-redux
会提供 dispatch
函数让你直接调用它。
来看 bindActionCreator
的源码
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
他返回了一个函数,这个函数实现了dispatch action
的功能
我们再来看 bindActionCreators
export default function bindActionCreators(actionCreators, dispatch) {
// 当actionCreators是单个函数时,直接调用bindActionCreator
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 当actionCreators不是对象的时候报错
if (typeof actionCreators !== 'object' || actionCreators === null) {
...抛出错误
}
// 创建一个新对象
const boundActionCreators = {}
// 对actionCreators这个对象进行遍历,让他的每一项,调用bindActionCreator
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
// 最后返回一个新对象
return boundActionCreators
}
对于这个actionCreators来讲
在其他文件引入后,会变成一个对象:
对于单个的actionCreator来讲,在调用 bindActionCreator
之后,就会变成
text => dispatch(addTodo('text');
对于多个的actionCreator来讲,在调用 bindActionCreators
之后,就会变成
{
addTodo : text => dispatch(addTodo('text'));
removeTodo : id => dispatch(removeTodo('id'));
}
从而实现了dispatch调用
combineReducers
在项目中,我们常常这样使用 combineReducers
来组合多个不同 reducer
函数
再来看源码
export default function combineReducers(reducers) {
// 参数reducer是对象,获取key
const reducerKeys = Object.keys(reducers)
// 过滤后的reducer,比如过滤掉重复之类的
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
// 没有获取到对应key的value时,报错
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// 当reducer是function时,相当于一将reducer拷贝给finalReducers
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 非生产环境中,定义一个unexpectedKeyCache的‘池子’
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 检查finalReducer中的reducer接受一个{ActionTypes.INIT}或一个ActionTypes.PROBE_UNKNOWN_ACTION()时,是否依旧能够返回有效的值
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 这个返回的函数就是一个组合后的reduce
// 这个返回的函数 返回的是一个state
return function combination(state = {}, action) {
// 存在shapeAssertionError的错误就抛出错误
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
// 对入参再做一次校验,比如判断
// state是不是纯对象等
// reducers是否为空
// 通过inputState的key不在reducers和unexpectedKeyCache中对inputState进行筛选
// ...
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// 定义一个变化标示符
let hasChanged = false
// 定义新的state
const nextState = {}
// 遍历过滤后的reducerKeys
for (let i = 0; i < finalReducerKeys.length; i++) {
// 获取key 和 reducer
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 获取当前key对应的state
const previousStateForKey = state[key]
// 更改对应reducer里面的state
const nextStateForKey = reducer(previousStateForKey, action)
// 不存在state就抛出错误
if (typeof nextStateForKey === 'undefined') {
// reducer不能返回undefined
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 生成新的state对象
nextState[key] = nextStateForKey
// 通过判断对象的引用来判断是否发生改变,所以每次reducer的改变总是要返回一个新的state对象
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 在循环中已经判断了hasChanged,在这里再判断一次是为了解决:
// 当使用CombineReducers调用replaceReducers并删除传递给它的其中一个reducers时,状态不会更新的问题
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
// 返回state
return hasChanged ? nextState : state
}
}
重复判断hasChanged
的原因: github issue中的讨论
简单说说我学到的
- function isCrushed() {}
通过判断伪函数的name属性来判断是否存在开发环境中代码压缩的情况
- 组合函数(compose)的编写
reduce方法、reduceRight方法
- 纯函数(isPlainObject)的判别方式
lodash\jq\redux中都有
- 高阶函数、柯里化
在redux
源码中,很多地方都用到了高阶函数,特别是中间件那一块,刚开始只会觉得看着云里雾里,看多了觉得这样的写法很简洁
- 发布、订阅模式
发布订阅函数和观察者模式(很多时候会将他们混为一谈,虽然从功能实现上差不多,但还是有细微区别)
- redux的三大原则
单一数据源:整个应用的
state
被储存在一棵object tree
中,并且这个object tree
只存在于唯一一个store
中
State 是只读的:唯一改变 state
的方法就是触发 action
,action
是一个用于描述已发生事件的普通对象。
使用纯函数来执行修改:为了描述 action
如何改变 state tree
,你需要编写 reducers
- 纯函数
相同输入永远得到相同的输出
函数与外界接触的渠道只有传入的参数和返回值
没有副作用
- 为什么reduer必须是纯函数
我们再来看这段代码,我们是通过判断hasChanged
是否为true
来判断返回新的state,还是旧的state
的
hasChanged
又是根据比较nextStateForKey
和previousStateForKey
的引用来判断的
那么再来看这句代码:nextStateForKey
是通过previousStateForKey
获取的
如果reducer
不采用纯函数的方式,我们就会这样编写reducer
这样子就会导致
state
无法更新,因为state
的引用是没有改变的
为什么根据引用来判断而不是属性值呢,深层次的比较是很消耗性能的比如深拷贝,所以redux
干脆对reducer
做了一个纯函数的约束
- redux数据流
先放俩经典图😝😝😝😝😝😝😝😝
用户通过交互事件触发了action
,action
经过middleware
的处理包装后被dispatch
给reducer
reducer
根据action
的指令改变state
,页面view
一直监听store
中数据变化,变化后将新的state
渲染在页面,从而实现用户响应
- 为什么redux不能直接派发异步action?stackoverflow Dan Abramov的回答,建议自己去看看嗷.
简单总结一下:
不借助中间件实现异步数据流是可以的,但是这样使用,在大型项目中是不太方便的
因为我们可能会在不同的组件中执行相同的action,you might want to debounce some actions
(这句话不是很懂🤢😅😭😭,消除action??)或者某些本地状态(如自动递增ID)与action creator的关系更加紧密
所以说,从维护的角度,将action creator提取出来会更好
Redux Thunk或者Redux Promise只是一个语法糖,帮助我们dispatching thunk或者promise,也不是非要用到他
也就是说有了中间件,我们不用去思考什么异步同步,可以将我们的重心放在组件上. 讨论中还提到了redux-saga,很有意思,下次讲😝
思考中...还有很多呀,比如redux、mobx的区别、函数式编程啥啥啥的
总结
有一说一,学习源码的过程还是挺难的😵,需要静下心来猜为什么这样写,对于appyMiddleware
这部分,要多去debugger
哦!!
可能文章中存在需要不严谨的地方,欢迎在留言区与我讨论!😸
参考链接🔗基本上都放在了原文中最后附上 redux官网链接