redux是什么
一句话概括:可预测的状态管理工具。不是我说的,官网说的!
redux有什么用
组件通信只能通过props传递,当我们组件嵌套比较深也就是父子组件相隔比较远时,会发生props传递的地狱,耗时耗力。redux可以将需要共享的属性进行统一管理,并且遵循单向数据流的原则。
单向数据流简单说就是我们组件之间的通信都是通过传递props去实现的,子组件通过props拿到状态实现子传父,子组件通过调用props中的回调函数去修改状态实现子传父。
这就很复合react的设计理念,这或许是redux和react如此适配的原因?所以在这里我们可以把redux抽象理解为一个公共的父组件,本质还是通过props与我们编写的组件(子组件)进行通信。
redux怎么工作的
我们先理解两个核心概念,提前走位,对后面的理解有帮助
immutable
在redux世界里,我们保持数据的不变性。这就要求我们的组件不能直接修改store中的状态,并且所有的reducer必须是纯函数。
纯函数
这里是react文档对纯函数的解释
第一点,函数自管自己的事,并且在函数内部不会修改已经存在的对象或者变量。
第二点,前后两次相同值的输入就会有的相同的值输出。这里我们类比我们数学中函数的概念,y=2x,当我们的输入值x=3,那么y永远只会输出6。
我的理解:纯函数就是执行完当前函数后对当前函数之外的作用域不造成任何影响。我们联系immutable来理解纯函数,state是存放在store中的,所以reducer拿到的state只是一个副本,指向同一块内存空间。我们必须深拷贝一份state开辟一块新内存空间再对新state进行操作才不会影响到原state,所以我们在reducer中修改state其实本质是覆盖state
reducer
reducer:本质上是一个函数,一个reducer维护一个状态,函数返回值为一个状态。reducer就是store里面的管理员,store里可以有多个reducer。通过const reducer = (state,action){}
生成,根据action.type
可以区分不同的状态操作。
这里官网解释了reducer名字的由来:Why Are They Called 'Reducers?'
因为实现的过程与Array.reduce这个数组API很相识,笼统来看就是通过传入一个旧状态,返回一个新状态。
我们类比一下Array.reduce的实现,Array.reduce=(pre,cur)=>{return pre+cur}
。其中pre为旧状态,cur为改变状态的因素(当前数组元素),那么return的pre+cur就是一个新状态了。这不就是reducer的工作模式嘛?
reducer的规则
第一点说的是reducer返回的新状态只由旧状态以及action决定
第二点说的是不允许直接修改state的值,指出reducer中的状态必须是immutable不可变,要想改变旧状态必须深拷贝一份状态并修改新状态,最后返回新状态覆盖旧状态。核心就是利用覆盖状态达到修改状态的目的。
第三点说的是不允许存在异步逻辑以及副作用
啥是副作用?
对于副作用我的理解就是:我们不是通过函数返回新状态来”修改“状态。在reducer函数中我们就是通过返回新状态覆盖旧状态。那么当我们不在函数返回值这儿发生了状态改变,那么这就产生一个副作用了。比如设置了一个定时器,定时器中设置3秒后修改状态,返回值中虽然返回了原状态,但在3秒之后我们的状态就发生了改变,也就是说我们输入相同值返回结果有可能不同,那么这里我们的状态就变得不可控制了。而我们reducer讲究的是就是可预测嘛,所以副作用达咩。
reducer和纯函数的关系
这里redux文档明确表示:reducer函数本质上就是一个纯函数
reducer为什么要设置为纯函数,这是文档的解释
1、使你的代码可预测,就是说在当前函数中,一个输入值对应一个输出值
2、我们的状态我们自己管理,也就是说reducer里的状态不受外界影响,不然当你输入一个状态时,由于外界对当前状态的影响,使得函数的输出值不是你想象中的值,也就是说变得不可预测了,不符合redux的设计理念
3、reducer也不要对外界造成影响,不要有副作用。比如修改了外界的状态,那就有可能造成UI渲染出现bug
4、redux devtool的time travel功能就是基于可预测这个特性去实现的,因为每一个step都是独立的,解耦的
官网进一步解释了为什么不能直接修改原状态
主要原因就是会造成UI渲染出错。因为react的响应式编程中UI = render(state),试想一下当我们的state是个引用类型数据比如对象,那么我们在reducer中修改的是原状态并返回原状态,此时对象的内存地址不变,所以UI判断当前依赖的状态并没有发生变化(因为浅层对比发现地址相同,所以判断状态没发生改变),所以导致UI不会更新。而当我们通过深拷贝原状态返回新状态就可以触发UI重新渲染啦(引用地址改变了,判断当前状态发生改变了)
这里有一个伏笔,当状态存在引用数据的嵌套,我们得一层一层去解构复制,太麻烦了吧
reducer的核心
reducer核心围绕一个点就是predictabe state,可预测的状态。这也是redux的核心。
只有在纯函数中,当输入值确定时,输出值就能确定。这就说明了我们的状态是可预测的。而一旦出现副作用了,状态可能就莫名其妙地改变了,那么我们的状态就会变得不可控。
小结
我们再来理一下reducer这两个核心概念:
纯函数:
1、自己的事自己管。不修改别人的东西,比如已经存在的状态,要使用先拷贝一份,对自己的副本想怎么搞都行
2、一个输入值对应一个输出值。所以说明我们的纯函数的返回值是可预测的
3、无副作用
redux中的reducer就完美符合上述条件,传入一个state,通过action的值去改变状态并返回,一切都是有迹可循,有理有据的,可以预测的。
副作用:
1、你修改了已经存在的状态,对函数之外的作用域造成影响
2、存在副作用的函数叫做副作用函数
store
代码实现最简单的store,实现了核心功能。其实源码也差不多这样(doge
store:理解成一个装着state的容器,我们可以在store里面管理不同的属性,store中暴露对外的接口实现状态传递,也就是说store就是redux与组件沟通的桥梁。通过creactStore()生成store,一个store对应一个rootReducer。
store内置三个方法
getState():获取状态值,返回状态对象
subscribe(callback):store提供订阅接口,store会收集当前的回调函数,函数返回一个unsubscribe()函数,去掉当前的回调函数
dispatch(actions) :store对外提供disptach接口用于修改状态,在dispatch内部会调用reducer去修改状态
官网再次提及不能直接修改redux维护的状态,因为我们在UI组件中拿到的state是reducer中state 的引用,不是一个copy值,所以如果不手动深拷贝一份再修改而是直接修改的话会使得redux当前维护的状态变得不可预测了,因为我们在reducer之外修改了state。
enhancer
加强器就是加强版的store,本质上就是对store的内置方法进行改写(dispatch,subscribe,getState)。作为creactStore()的参数传入,redux用compose合并多个enhancer。
举个例子:
Middleware
我理解的中间件,其实就是复用性极强的一段逻辑,可在代码中插拔式地植入这段逻辑,帮助实现某些功能需求。
而在redux中的中间件本质上是在enhancer中重写的dispatch方法。也是作为creactStore()的参数传入,传入之前需要用applyMiddleware()包装一下。中间件的解构挺复杂的,有三层函数的嵌套,细节自行看文档介绍吧,我们这里就学会怎么去用就行了。
官网这段话的意思:中间件就是在触发dispatch到reducer执行这之间执行逻辑的。也就是在disptach执行之前作用,一般就是做一些副作用或者异步操作
我们把中间件看成一个管道,管道中的中间件排成队列依次执行自身的逻辑。
举个例子:
middleware存在的意义
最大的好处就是在reducer处理action之前可以实现额外的逻辑,可以有副作用。因为我们知道在reducer不可能存在异步操作,也没有副作用。所以当我们想要进行异步操作的话只能在store之外去实现。react-thunk等中间件应运而生
but
我们看到redux-thunk源码,这里说明disptach的参数可以为一个异步函数。如果是普通的对象则通过next(action)将action传给下个中间件直到传给reducer去处理
也就是说thunk可以自动识别dispatch传入的东西是同步还是异步的。对于处理异步操作来说thunk中间件其实可以看作这是一个语法糖。我的理解是中间件充其量就是一个工具tool,用不用都行,只是说thunk可以使代码格式更规范,维护起来更容易。
为什么在异步中需要redux中间件,还有更详细的分析可以看看这文章。
end
自己写的mini-redux感兴趣可以看看,github.com/YMnotafraid…
参考文献: