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> ...
复制