title: React Toolkit
date: 2022-09-29 14:42:35
tags:
- React
- Redux
- 框架
- TypeScript
categories: - React
React Toolkit
首先,React Tookit是React新提出的类Redux状态管理模式。该技术的提出是为了解决Redux的三个常见问题:
- 🤨”配置Redux储存太复杂了“
- 🤔”必须添加很多包才能让Redux做任何有用的事情“
- 😯”Redux需要太多样板代码“
更多介绍可以看官方文档
安装
官方提供了基于 React+Js 或者 React+Ts 的模块(脚手架):
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
如果你想在已有的项目上安装,使用如下命令:
# NPM
npm install @reduxjs/toolkit
or
# Yarn
yarn add @reduxjs/toolkit
当然有你需要先有react-redux
使用
官方的例子是一个加减demo,我这里是一个购物车的案例
安装
npm install @reduxjs/toolkit react-redux
创建Redux-Toolkit
一个项目仅有一个state,和redux一样,也是需要将各个分开的状态统一整合管理
注意:counterSlice
和shopCarList
是用户自定义的slices
,在redux中称之为reducer
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>
组件
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,action
,state
指向状态,action
中的action.payload
代表调用该reducer方法的时候传入的参数
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,代码过多请仅关注标注部分
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中的异步属于简化写法,并不会对异步进行监听
export const incrementAsync = (amount) => (dispatch) => {
// 异步方法存放于此
setTimeout(() => {
dispatch(incrementByAmount(amount)) // 可在此调用同步中的reducer方法
}, 1000)
}
第二种写法:
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,action)=>{
console.log(state);
console.log(action.payload);//'数据' 异步请求到的数据
})
}
})
export const {incremented,decremented,add} = counterSlice.actions;
export default counterSlice.reducer;
异步方法使用:
...
<button onClick={()=>{dispatch(getMovieData())}}>获取数据</button>
...