Java教程

【九月打卡】第二十一天【redux-toolkit】

本文主要是介绍【九月打卡】第二十一天【redux-toolkit】,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

课程:React18 系统精讲

章节:Redux-toolkit

讲师:阿莱克斯刘

课程内容

【redux-toolkit】createSlice、reducer、与immer

上节课,我们学习了redux-toolkit的基本原理,讲解了几个比较关键的api。这些课,我们来给项目添加redux-toolkit框架,然后把产品详情页面从mvc改变为redux。

如何从mvc改变为redux的基本思路我们在第七、第八章的时候详细讲解过,所以,接下来我们不会再深入探讨原理,所有代码操作都会集中于如何使用redux-toolkit来进行的。因为redux和react-redux是本章基础,所以对前两章还不是很明白的同学,请复习一下,然后再继续我们的课程。

接下来的课程,我将会使用redux-toolkit的简称RTK。请同学打开项目,要想使用RTK,第一件事情就是安装依赖。打开命令行,输入

npm install @reduxjs/toolkit


因为redux-toolkit是全面支持typescript的,所以不需要安装类型文件。安装完成以后,正式开始代码。

与上一章我们学习的主页类似,我们首先需要定义产品详情的state。在redux 文件夹中创建productDetail文件夹,不过使用了RTK以后我们就不需要分别创建对应的reducer和action了,正如我们上节课所说的,99%的情况我们可以使用slice来为产品详情创建命名空间,而slice中将会自动包含reducer和action的映射关系。

所以,我们来创建产品详情所对应的slice,文件名称就叫slice.ts算了。Slice的概念我们上节课也讲过,就相当于redux store中的分割出来的一个子模块。

请同学们把recommendProducts文件中的Reducer也打开,放在一边,这两个文件的功能特别相似,所以我们对照着来写代码可以更容易理解一点。

与recommendProducts的状态变化与推荐列表的状态差不多,接口 productDetailState也是三个状态,loading、error、以及数据data,也就是产品细节数据,我们使用any来表示。

接下来,从@reduxjs/toolkit引入createSlice。我们将会使用createSlice来创建产品详情的store空间。

import { createSlice } from  "@reduxjs/toolkit";


创建并导出本地变量 productDetailSlice,使用createSlice函数。函数以对象的形式传入各种数据。

export  const  productDetailSlice = createSlice({

  

});


都有哪些数据呢?其实上节课我们讲过,不过现在还是让我们回到文档研究一下。

https://redux-toolkit.js.org/api/createSlice

根据文档的提示,

第一个当然就是命名空间的名称了,name等于命名空间,productDetail

第二个是初始化数据initialState,我其实特别欣赏RTK在这里强制我们定义初始化数据,因为使用传统的reducer方案的时候,初始化数据要通过reducer的参数传递进来,但是这样的写法一个不小心就会忘记传递初始化,结果网站就会报错。现在,RTK强制要求我们定义初始化数据,我们就不需要再担心网站redux store初始化出错的问题了

那么,我们的initialState是什么呢?我们来定义一下吧。首先,他的类型肯定是productDetailState。既然是初始化,loading肯定是flag为true的,连请求都都还没发送,所以error和data肯定都是null的。

const  initialState: productDetailState = {

loading:  true,

error:  null,

data:  null,

};


好了,initialState就这么简单,把它传入createSlice函数。

接下来是第三个字段,reducers。不过要请同学们注意了,createSlice中定义的reducer有几个特点,千万别搞错。

  • 第一,这里的reducer实际上是把action和reducer捆绑在一起了,所以我们不需要再单独定义action了

  • 第二,这里的reducer是对象而不是过程,每个对象对应一个action,同时也对应action的处理函数

  • 第三,因为是createSlice是面对对象而不是面对过程,所以我们不必再写switch了,这一点实在是太方便了。

好了,记住了以上三个特点以后,让我们来完成reducer对象吧。

第一个要处理的action是什么能?是不是应该要处理api请求开始呢?那么第一个action就是,fetchStart。而这个fetchStart对应的就正是他的处理函数,也就是真正的reducer,而他的参数则是当前store 状态数据,state。

接下来,请同学们注意了,真正的好戏开始了,注意注意注意,重要的事情说三遍。还记得我们之前一直以来怎么描述reducer的吗?reducer应该是一个纯函数,他不应该处理副作用,而state是immutable的,我们不可以直接修改,而应该通过创建新state来替换旧state。

所以,如果我们应该写成这样,对不对?在fetchStart函数中通过展开操作符号,利用state中的旧数据,创建一个全新的对象,并且返回return这个新数据。这种方法完全遵守了纯函数和immutable的原则。同学们对这种模式应该已经很熟悉了。

fetchStart: (state) => {

return { ...state, loading:  true };

},


但是,我们是谁啊,我们是程序员,对吧。程序员最大的特点是什么啊?就是懒,懒得动手、懒得敲代码,多写一个字就跟要了命一样。很显然,各路react大牛们对纯函数、对immutable是有抵触心理,当然我这里说的抵触不是只思想,而是指的是代码的长短、代码的字数,所以,为了满足程序员懒惰的需求,还记得我们上节课探讨过一个叫做immier的框架吗?

而这个immer就是为了解决这个问题的才出现的。有了immer我们就可以像处理普通赋值一样,直接使用对象的链式结构来更新数据,并且还能干掉return关键词。

那么,我们改怎么写代码呢?注释掉之前的代码,直接写 state.loading = true;。好了,代码完成。

是不是突然有一种神清气爽的感觉?没错,就是这么直接、这么暴力,我们不需要在考虑什么乱七八糟的纯函数、什么immutabe之类的概念了。当我们不考虑设计模式的时候,我们的代码真的就成了这幅图中的老伯伯说的,提起键盘就是一把梭。

sXdYut.jpg

我其实挺欣赏immer这种处理方法的,虽然react和redux的理念相当不错,但是当我们在学习的过程中拼命被灌输各种设计模式、各种全新概念的时候,无形中会推高学习这门技术的门槛,反而不利于技术的发展。

也许是redxu官方意识到了这个问题,也觉得应该相应的减少设计模式和各种概念的输出,而让我们程序员更关注于代码本身而不是底层模式。所以,immer这个框架是redxu官方推荐的,甚至在redux-toolkit框架中成为了默认选择,

所以,其实当我们在用createSlice创建reducer的时候,immer就已经在底层开始运行了,他会把我们在reducer中所有的代码自动转化immutable,并且输出一个全新的state对象,而这一切对于我们来说都是透明的,我们看不到,也不需要知道,整个reducer处理过程中我们完全感受不到immer的存在,而这才是一个框架最高的使用境界。

对于immer,我一开始其实是很抗拒的,我抗拒他违反了redux的原则,违背了函数式编程的理念,但是在尝试了一段时间以后,最总还是逃不过真香定律。

好了,言归正传,immer的话题到此结束。让我们继续完成剩下的action以及reducer。

那么,第二个action要处理什么呢?很明显是数据请求成功的情况,fetchSuccess。所以,自然第三个action就是处理api请求出错的情况了,fetchFail。这两种情况比第一种情况多了一个参数,就是action,他的类型是已经被RTK自动定义好了,其中payload的类型是any,感觉真实方便。

当然,如果你需要自己定义action类型,可以使用PayloadAction,我们可以引用一下试试看。比如,我们可以在fetchFail中定义一下action,,因为error的类型是字符串,所以范型定义个string。如果我们输出一下payload,就会看到payload的类型被指定为string了。

fetchFail: (state, action: PayloadAction<string>) => {

const  ddd = action.payload;

},


数据请求成功的时候aciton的payload带来的数据应该就是api的返回数据,所以我们可以直接使用payload数据来更新state。请求结束,loading等于false;state data等于payload; 同时清空error。

fetchSuccess: (state, action) => {

state.loading = false;

state.data = action.payload;

state.error = null;

},


数据请求失败的时候aciton的payload带来的数据就是api的失败数据字符串,我们同样可以使用payload更新state。

fetchFail: (state, action: PayloadAction<string>) => {

// const ddd = action.payload;

state.loading = false;

state.error = action.payload;

},


好了,产品详情的slice完成,包含了reducer和action的所有代码就这么点。那么接下来,就让我们使用这个slice吧。

请同学们打开store.ts文件,引入产品详情的slice。

import { productDetailSlice } from  "./productDetail/slice";


因为RKT的兼容性极强,几乎可以完美兼容目前普通的redux结构,所以接下来的代码,我们将会实现普通reudx和RKT混编的形式,代码的混合的主要目的是为了留给同学们自己通过对比学习和巩固之前的知识。

那么,支持使用slice的第一步就是要替换我们现在的combineReducers函数,这一步非常简单,三个步骤:

  • 第一,从redux中删除对combineReducers的引用

  • 第二,import ‘@reduxjs/toolkit’,并且换成从这里引用combineReducers。两个combineReducers可以无缝衔接,报错会消失。

  • 第三,因为新的combineReducers可以支持处理,slice中的reducer,所以我们需要把产品详情的reducer也放进来。

import { productDetailSlice } from  "./productDetail/slice";

import { combineReducers } from  '@reduxjs/toolkit'

const  rootReducer = combineReducers({

language:  languageReducer,

recommendProducts:  recommendProductsReducer,

productDetail:  productDetailSlice.reducer

})


好了,rootReducer完成,如果同学们把鼠标放在type RootState上面。我们会看到现在productDetail的类型也进来了。

也许同学们还记得我们上节课讲了一个RTK中的configureStore函数,当时介绍的时候说的是我们可以使用configureStore函数来创建store。不过,其实到现在为止,我们的RTK配置就完成,依赖于RTK良好的兼容性,我们就算不使用configureStore函数,也可以直接调用slice数据。

那么,接下来,让我先回到detail页面,完成页面与redxu的连接。在完成所有业务以后,下节课会回来这里,我们将会仔细探讨configureStore与createStore的区别。请同学们打开detalpage.tsx。

首先,我们引入几个依赖,productDetailSlice、然后从自定义hook中引入useSelector ;最后,从react-redux中引入useDispatch函数。

import { productDetailSlice } from  "../../redux/productDetail/slice";

import { useSelector } from  "../../redux/hooks";

import { useDispatch } from  "react-redux";

  

接下来,我们使用useSelector来连接产品详情数据中的loading、error以及data这三个字段的数据。

const  loading = useSelector((state) =>  state.productDetail.loading);

const  product = useSelector((state) =>  state.productDetail.data);

const  error = useSelector((state) =>  state.productDetail.error);


现在,组件状态就从自己身上转移到redux中了。

然后就是分发各种action。首先, 从useDispatch hook取得dispatch函数。

const  dispatch = useDispatch();


接着在, useEffect中,我们分别分发请求开始、请求成功、以及请求失败这三个action。接下来的操作有点特殊,请同学们注意了。

首先,请同学们思考一下,我们的action从哪里来。没错,从slice中来,就是我们的productDetailSlice。我们在创建slice的时候,会把reducer和acition捆绑起来,但是当我们在分发action的时候,还是需要使用action creator来指明action类型以及数据。

幸运的是,RTK在绑定action和reducer的时候,会根据reducer的函数签名来自动为我们生成相应的action creator,而我们不需要在写重复的action模版代码,只需要直接调用就可以了。

比如说,在fetchData函数中,我们需要分发的第一个action就是,productDetailSlice.actions.fetchStart()。请注意,这里dispatch出去的action一定要加上小括号,因为我们需要的是action creator的输出对象。

dispatch(productDetailSlice.actions.fetchStart())


同样,对于api请求成功我们可以dispatch productDetailSlice.actions.fetchSuccess。而payload数据则是api的返回数据,data。

最后,api请求失败,我们需要dispatch,action就是

productDetailSlice.actions.fetchFail(e.message)


const  loading = useSelector((state) =>  state.productDetail.loading);

const  product = useSelector((state) =>  state.productDetail.data);

const  error = useSelector((state) =>  state.productDetail.error);

const  dispatch = useDispatch();

useEffect(() => {

const  fetchData = async () => {

dispatch(productDetailSlice.actions.fetchStart());

try {

const { data } = await  axios.get(

`http://123.56.149.216:8080/api/touristRoutes/${touristRouteId}`

);

dispatch(productDetailSlice.actions.fetchSuccess(data));

} catch (e) {

dispatch(productDetailSlice.actions.fetchFail(e.message));

}

};

fetchData();

}, []);


好了,代码结束,保存一下,运行网站。好的,网站打开一起正常,而detail页面也完美呈现出来了。这就说明我们网站的redux-toolkit架构成功了,下节课,我们来学习如何在redux-toolkit使用redux-thunk来处理异步逻辑。

这篇关于【九月打卡】第二十一天【redux-toolkit】的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!