纯函数
在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:
- 此函数在相同的输入值时,需产生相同的输出。
- 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
- 函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
::: tip
在计算机科学中,副作用表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响, 比如修改了全局变量,修改参数或者改变外部的存储;
例如:
slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组;splice:splice截取数组,会返回一个新的数组,也会对原数组进行修改;
那么,slice就是一个纯函数,而splice函数不是一个纯函数。
:::
React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改。
Redux基本使用
原则
-
单一数据源
整个应用程序的state被存储在一棵object tree中,并且这个object tree只存储在一个 store 中:
Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护; 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;
-
State是只读的
唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State;这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state;、
这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
-
使用纯函数来执行修改
通过reducer将旧state和actions联系在一起,并且返回一个新的State:
随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;
所有的reducer都应该是纯函数,不能产生任何的副作用;
定义一个store
创建Store来存储state;
创建store时必须创建reducer;
获取state
可以通过 store.getState 来获取当前的state;
修改store中的数据
通过action来修改state;
通过dispatch来派发action;
reducer是一个纯函数,不要直接修改state;
订阅store中的数据
动态生产action
如果需要多次修改store的数据,那么定义action的操作会很繁琐,而且很可能会出错。
我们可以使用函数进行替换
当action生成函数过多的时候,可以将函数放在 actionCreators.js 文件中。
Redux结构划分
如果我们将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护。
可以将代码拆分为:
- store/index.js
- store/reducer.js
- store/actionCreators.js
- store/constants.js
Redux使用流程
在React中使用Redux
react-redux
redux和react没有直接的关系,你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux。
在react中,每个页面使用redux都需要写一堆重复代码,我们可以通过将重复代码抽取成高阶的形式来简化开发。
通常是使用react-redux插件。
redux-thunk
在之前的代码中,redux中保存的counter是一个本地定义的数据,我们可以直接通过同步的操作来dispatch action,state就会被立即更新。 但在真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中。
网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给redux来管理;
redux中使用中间件进行异步的操作,通常使用redux-thunk插件。
默认情况下的dispatch(action),action是一个对象;而redux-thunk可以让dispatch的action是一个函数;该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数;dispatch函数用于我们之后再次派发action; getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;
浏览器安装完redux-devtools插件后,默认情况下,我们是无法查看redux里面的数据的。
如果需要查看,我们需要在代码中添加相应代码
如何添加相关代码:
https://github.com/reduxjs/redux-devtools/tree/main/extension#installation
Redux模块
redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并。
combineReducers原理:
在前面我们学习Redux的时候应该已经发现,redux的编写逻辑过于的繁琐和麻烦。 并且代码通常分拆在多个文件中(虽然也可以放到一个文件管理,但是代码量过多,不利于管理);
Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题;
::: tip
Redux Toolkit 只是对redux的代码进行了封装
:::
核心API:
-
configureStore
包装createStore以提供简化的配置选项和良好的默认值。
它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
-
createSlice
:
接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
-
createAsyncThunk
:
接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending / fulfilled / rejected 基于该承诺分派动作类型的 thunk
重构reducer
先对counter的reducer进行重构:
-
通过createSlice创建一个slice。
参数:
- name:用户标记slice的名词,在之后的redux-devtool中会显示对应的名词;
- initialState:第一次初始化时的值;
- reducers:对象类型,可以添加函数;添加的函数类似于redux原来reducer中的一个case语句;函数的参数:state,action;
-
导出
重构store
configureStore用于创建store对象。
参数:
- reducer,将slice中的reducer可以组成一个对象传入此处;
- middleware:可以使用参数,传入其他的中间件;
- devTools:是否配置devTools工具,默认为true;
将RTK的store和react联系起来
导出counter的action,导出的名字与reducers对象里面的函数名一样。
react代码只需导入adction即可,剩下的代码和以前一样
RTK的异步操作
在之前的开发中,我们通过redux-thunk中间件让dispatch中可以进行异步操作。Redux Toolkit默认已经给我们继承了Thunk相关的功能。
当请求获取到数据后需要保存,上面代码是通过extraReducers来保存,我们也可以直接在fetchDataAction保存数据。
实现react-redux的connect
定义一个上下文,用来传递store
实现逻辑
使用
打印dispatch日志
比如我们现在有一个需求:在dispatch之前,打印一下本次的action对象,dispatch完成之后可以打印一下最新的store state;
如果没有中间件,我们可以在派发的前后进行相关的打印。
很明显,由于每一次的dispatch操作都需要在前面加上上面的逻辑代码,会导致存在大量重复的代码;
事实上,我们可以利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑;对代码进行如下的修改:
在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog;
实现redux-thunk
redux中利用一个中间件redux-thunk可以让我们的dispatch不再只是处理对象,还可以处理函数
实现applyMiddleware
显然,当中间件多起来后,单个调用某个函数来合并中间件并不是特别的方便,所以我们可以封装一个函数来实现所有的中间件合并。