0%

React Toolkit

React Toolkit

首先,React Toolkit是React新提出的类Redux状态管理模式。该技术的提出是为了解决Redux的三个常见问题:

  • 🤨”配置Redux储存太复杂了“
  • 🤔”必须添加很多包才能让Redux做任何有用的事情“
  • 😯”Redux需要太多样板代码“

更多介绍可以看官方文档

安装

官方提供了基于 React+Js 或者 React+Ts 的模块(脚手架):

1
2
3
4
5
# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

如果你想在已有的项目上安装,使用如下命令:

1
2
3
4
5
# NPM
npm install @reduxjs/toolkit
or
# Yarn
yarn add @reduxjs/toolkit

当然有你需要先有react-redux

使用

官方的例子是一个加减demo,我这里是一个购物车的案例

安装

1
npm install @reduxjs/toolkit react-redux

创建Redux-Toolkit

一个项目仅有一个state,和redux一样,也是需要将各个分开的状态统一整合管理

注意:counterSliceshopCarList是用户自定义的slices,在redux中称之为reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { configureStore } from "@reduxjs/toolkit";

import counterSlice from './slices/slices_shoplist'; // 商品列表state
import shopCarList from "./slices/slices_shopCar"; // 购物车state

const store = configureStore({
reducer:{
counter:counterSlice, // 配置多个slices
shopCar:shopCarList // 更多slices
}
})

export type RooState = ReturnType<typeof store.getState> // 类型生成

export type AppDispatch = typeof store.dispatch // 类型生成

export default store

index.ts文件中引入<Provider>组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import ReactDOM from 'react-dom/client';
import App from './App';

import { Provider } from 'react-redux'; // 引入第三方监视组件,用于传入state
import store from './redux/store'; // 引入公共state

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);

// 包裹App组件
root.render(
<Provider store={store}>
<App />
</Provider>
);

创建各个状态文件在redux/slices/slices_shoplist

ts类型注解不做过多解释,PayloadAction是官方提供的TS接口,<>中注解使用该reducer传入参数的类型

每个reducer方法都有形参state,actionstate指向状态,action中的action.payload代表调用该reducer方法的时候传入的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import { createSlice,createAsyncThunk,PayloadAction } from "@reduxjs/toolkit";

export interface initArr {
key:number,
title:string,
price:number,
quantity:number
}

interface initType {
shopList:Array<initArr>
}

// 初始状态
const initial:Array<initArr> = [
{
key:1,
title:'测试文本01',
price:100,
quantity:100,
},
{
key:2,
title:'测试文本02',
price:200,
quantity:100,
},
{
key:3,
title:'测试文本03',
price:300,
quantity:100,
},
];


const initialState:initType = {
shopList:initial
}

const counterSlice = createSlice({
name:'counter',
initialState, // 初始状态
reducers:{ // reducer
addShop:(state,action:PayloadAction<initArr>)=>{
// 添加商品功能
state.shopList.push(action.payload)
},
deleShop:(state,action:PayloadAction<number>)=>{
// 删除指定商品功能
state.shopList = state.shopList.filter((tit:initArr)=>{
return tit.key !== action.payload
})
}
},
})

export const {
addShop, // 将各个reducer暴露出去
deleShop
} = counterSlice.actions;

export default counterSlice.reducer;

在组件中使用,在函数组件中使用钩子useSelector,useDispatch获取初始状态以及dispatch,代码过多请仅关注标注部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import React, { useRef } from 'react'
import { useSelector,useDispatch} from 'react-redux' // 引入两个必要的钩子
import type { RooState } from '../../redux/store'; // 引入生成的类型注解

import styled from 'styled-components';
import { addShop } from '../../redux/slices/slices_shoplist'; // 引入使用的reducer

export const OneDiv = styled.div`
h1{
font-size:25px;
font-weight:600;
color:#5592fa;
}
`
const Input = styled.input`
border:1px solid #5592fa;
border-radius:5px;
width: ${(props)=>props.width};
height: 30px;
margin-top:20px;
font-size:18px;
padding-left:10px;
outline:none;
`
const Span = styled.span`
color:#5592fa;
padding: 0px 9px;
`
const Button = styled.button`
width:100px;
height: 30px;
background-color:#5592fa;
color:#fff;
padding: 0px 9px;
border:none;
cursor: pointer;
margin:30px 0px 0px 80px;
`

const One = () => {
const shopTitle = useRef<HTMLInputElement>(null);
const shopPrice = useRef<HTMLInputElement>(null);
const shopQuantity = useRef<HTMLInputElement>(null);

// 获取状态(注意RooState类型就是store中生成的类型)
const ULS = useSelector((state:RooState)=>state.counter);
// 生成dispatch
const dispatch = useDispatch();

const addShopList = ()=>{
if (shopTitle.current?.value.trim()&&shopPrice.current?.value.trim()&&shopQuantity.current?.value.trim()) {
let obj = {
key:+new Date(),
title:shopTitle.current?.value,
price:parseInt(shopPrice.current?.value),
quantity:parseInt(shopQuantity.current?.value)
}
dispatch(addShop(obj)) // 调用方法
shopTitle.current.value = '';
shopPrice.current.value = '';
shopQuantity.current.value = '';
}else{
alert('请输入内容');
}
}

return (
<OneDiv>
<h1>添加商品</h1>
<ul>
<li>
<Span>商品名称:</Span>
<Input type="text" width="400px" ref={shopTitle} placeholder="名称"></Input>
</li>
<li>
<Span>商品价格:</Span>
<Input type="text" width="50px" ref={shopPrice} placeholder="价格"></Input>
</li>
<li>
<Span>商品数量:</Span>
<Input type="text" width="50px" ref={shopQuantity} placeholder="数量"></Input>
</li>
<li>
<Button onClick={()=>addShopList()}>添加商品</Button>
</li>
</ul>
</OneDiv>
)
}

export default One

关于异步

官方提供的Demo中的异步属于简化写法,并不会对异步进行监听

1
2
3
4
5
6
export const incrementAsync = (amount) => (dispatch) => {
// 异步方法存放于此
setTimeout(() => {
dispatch(incrementByAmount(amount)) // 可在此调用同步中的reducer方法
}, 1000)
}

第二种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { createSlice,createAsyncThunk } from "@reduxjs/toolkit";

export var loadPic = createAsyncThunk('weather/loadPic', async () => {
return new Promise((resolve:(str:string)=>void,reject)=>{
setTimeout(()=>{
resolve('数据');
},1000)
}) // 此处的返回结果会在 .fulfilled中作为payload的值
});

var counterSlice = createSlice({
name:'counter',
initialState:{
value:0 // 初始值
},
reducers:{ // reducer
incremented:(state)=>{
state.value+=1
},
decremented:(state)=>{
state.value-=1
},
add:(state,action)=>{
// action的playload是调用该函数的时候传入的值
state.value+=action.payload;
}
},
// 第一种写法
// extraReducers:{
// [loadPic.pending.type](state,action:any){
// console.log('pending',state,action);
// },
// [loadPic.fulfilled.type](state,action:any){
// console.log('fulfilled',state,action);
// state.value+=1;
// },
// [loadPic.rejected.type](state,action:any){
// console.log('rejected',state,action);
// },
// }
// 第二种写法
extraReducers(builder){
builder
.addCase(loadPic.pending,(state)=>{
console.log(state);
})
.addCase(loadPic.fulfilled,(state)=>{
console.log(state);
})
}
})


export const {incremented,decremented,add} = counterSlice.actions;

export default counterSlice.reducer;