手撕redux

Posted lin-fighting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撕redux相关的知识,希望对你有一定的参考价值。

redux

redux是一个全局状态管理库,通过Provider,依赖注入store,sotre可以看作一个容器,用来存放state。通过用户触发dispatch action, store会调用对应的reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State ,reducer根据action的type返回最新的state。state一档发生变化,store就会调用监听函数改变视图。

模拟reduc的实现

通过createContext去实现类似redux。

全局state的读写。


使用上下文来模拟全局的state,最终在孙组件获取到state。

打印正常;

reducer的来历(规范state创建流程)

我们可以通过上面传入的setState去修改state

这样可以去修改全局state的状态,但是这是及其不规范的。我们不能直接修改原来的对象的值。所以我们通过reducer来规范我们的行为。

模仿reducer的写法,传入state和action返回新的state。

dispatch规范setState流程。


当我们要修改多处的时候,这三个单词每次都要写一遍。

我们就将这个抽离出来。但是dispatch里访问不到setState和state的。因为state是通过contextVal传下来的,所以我们创建一个wrapper组件来包裹

使用中间组件拿到state和封装dispatch,然后传入子组件,子组件只需要dispatch就可以改变值。但是这样只能用于一个GrandSon组件。其他组件也想获取disptch和state也就需要写一个Wrapper,所以我们实现connect函数来帮我们做这个事情。

高阶函数connect


connect的目的是让组件跟全局的状态关联起来,所以接受一个子组件,返回一个包裹着一层的新组件。在包裹的一层去提供state和dispatch再传给子组件。这样connect函数就好了。

实现connect减少render

我们修改大孙子内的内容,大儿子二儿子却也跟着刷新了。因为state的状态是在app根组件定义的,我们每次修改dispatch的时候实际是通过setState修改的。所以整个app组件以及他的子孙组件都会重新渲染。所以我们不能通过useState来吃初始化store。
我们定义一个全局的store.
但是这样的是不会引起组建渲染的,所以我们先采用一个笨拙的办法

在connect里面去通过useState强制修改。

  • 现在就不会重新刷新其他组件了,但是又有一个问题,依赖store的大儿子,不会改变了。因为大儿子他并没有刷新,所有的conncet都会创建各自的dispatch函数。大孙子改变的时候,只刷新了大孙子这个组件,让其拿到最新的state。
  • 但是这样是不好的,所以我们得通过类似store.subscribe这个方法来修改。但state改变,就调用subscribe这个方法来渲染组件。
  • 在connect里面是有做这步操作的,就是手动帮我们调用了subscribe,所以我们用store的数据,在改变的时候才会自动刷新组件,你以为自动刷新,其实是connect里面帮我们监听了。以前类组件是在subscribe里面拿到最新的state,然后去this.setState修改组件的状态。
    详情可以看这篇文章connect与providerde的实现
    看看代码

    在store对象里面增加subscribe这个方法,用来收集依赖,注意需要返回一个函数去取消监听。然后每次调用的时候传入给listener,在每次修改值的时候,就遍历调用依赖函数,将最新的state传入进去。然后

    在connect里面监听,这样只要是用过connect高阶组件封装的,只有store的state一改变,就会重新渲染。如

如上,二儿子因为没有使用conncect包裹,所以不会受影响,大儿子因为包裹了connect,所以在state改变的时候也会刷新组件。
然后我们再将其抽离出去


这样一个简单的redux就完成了。

mapStatetoProps的实现

const mapStateToProps = state => ({
  age: state.age
})

一般我们mapStateToProps是这样写的,其实做法就是connect中获取这个函数,传入state,拿到对象,再传给子组件。
采用柯里化,
这样就可以了。然后
看效果:

一样会改变,但是这里其实有问题。我们connect的时候建立了监听,但是redux的数据很多,如果有一个是这个函数组件不需要的,但是又因为数据改变了而强制执行监听函数,导致组件重新渲染。所以我们要实现redux的精准渲染,让组件只在自己需要的数据变化的时候渲染。

精准渲染

精准渲染也是在connect完成的,浅层比较新老state,
在监听函数执行的时候就去新老state了,如果变化则引起组件渲染,要记得return的时候取消监听,return会在下一次执行useEffect的时候执行,目的是清除上一次的副作用。需要依赖data,如果不依赖data,每次含漱执行的时候拿到的都是第一次的data。这样就实现了精准渲染。
二儿子依赖了user,但是改变state,看二儿子会不会改变,
答案是不会。

mapDispatchToProps的实现


也是在connect传入dispatch将返回的对象给组件。

效果一样。

connect的意义

connect为什么要写成

这样呢,为什么不能三个参数都写在同个地方。这是有原因的。
mapStateToProps是对state的读,mapDispatchToProps是对state的写,而connect更像是读写的封装。如果我们有几个组件用到的数据都是一样的,就可以单独封装出来。如;

将共同的读写操作抽离出来,封装成同一个connect,然后直接使用即可。

createStore和Provider的实现


我们要实现的效果是这样的,通过createStore初始化store,通过Provier将store传入。
首先:
createStore接受两个参数,一个reducer,一个initState

通过createStore去初始化state和reducer。
其次,封装Provier组件

这样就完成了基本的redux。接口也是跟redux的接口差不多。

redux实现函数action

异步action一般是一个函数
我们模拟一个异步操作,正常我们这样操作,将dispatch传入给函数,然后 将返回的值disaptch去改变State。

但是我们不想fn(dispatch)去操作,我们想dispatch(fn)去操作。我们就需要修改一下dispatch函数。
如果action是函数,我们就将dispatch传入,如果不是函数,就证明是对象,只要正常dispatch就行。看效果。


正常,这样就知道我们的dispatch为什么要这么设计。

redux支持Promise action


将promise返回的数据作为payload。然后修改dispatch

如果是promise则.then之后dispatch。
看效果:

中间件

redux不支持异步action,如函数action,Promise action。但是redux提供在createStore的时候可以传入第三个参数,中间件,如redux-thunk redux-promise去让redux支持异步action。


这是redux-thunk的源码,可以看到跟我们实现的差不多,都是判断action是否为函数,然后传入dispatch,只不过redux规定还必须传入getState等一些参数。

这是redux-promise的源码,也是判断然后执行。

跟我们自己实现的思想其实差不多。
然后看看middleWare的实现。

可以理解MiddleWare就是为了改造dispatch,使其可以接受函数action,promise action等等

总结

  • 模拟redux的实现,首先我们要在全局创建一个store,然后定义好state,suscribe监听函数,以及修改state的方法,在修改state的时候调用监听函数,将最新的state传入。通过Provide将store从根组件传入
  • state数据改变的时候,组件内可以获取到真实数据,但不会进行渲染,这时候就需要connect了。connect是一个高阶函数,用来连接全局state和组件。内部做的事情很多,比如创建store的监听函数。然后实现mapStateToProps和mapDispatchToProps函数,将对应的值传入组件,实现新老state的变化,判断是否更新组件扽等。
    异步action就可以使用像redux-thunk或者redux-promise等中间件,实现原理是通过判断action的类型再去决定做对应的操作。middleware做的事情就是通过接收中间件,来改造dispatch,使其可以接受异步action

以上是关于手撕redux的主要内容,如果未能解决你的问题,请参考以下文章

“ES7 React/Redux/GraphQL/React-Native 片段”不适用于 javascript 文件。除了安装它,我还需要配置其他东西吗?

深度学习/机器视觉/数字IC/FPGA/算法手撕代码目录总汇

java面试之手撕代码

算法手撕代码131~140

算法手撕代码91~100

算法手撕代码141~150