# Redux > 状态管理 > 适合大型项目 > 类似的还有zustand, mobx, mobx-react-lite ## hello redux ```sh npm install react-redux ``` ```html Document
1
``` ## Redux Toolkit (RTK) [Quick Start | Redux Toolkit](https://redux-toolkit.js.org/tutorials/quick-start) ``` 问题: 1.如果state过干复杂,将会非常难以维护 - 可以通过对state 分组为解次这个向题,创建多个reducer,然后將其合并为一个 2.state每次操作时,都需要对state进行复制,然后再去修改 3.case后边的常量维护起来会比较麻烦 ``` ```bash # 安装 npm install react-redux @reduxjs/toolkit -S ``` ```js /** * createSlice:创建reducer(归纳函数)切片 * - 名字, 用来自动生成action.type * - 初始值 * - reducers: 放更新state的方法 * - 方法有两个参数: state和action * state是一个代理对象,可以直接修改 * * 切片会自动生成actions, 打印actions可以发现存储多个函数:actionCreator() action创建器 * * 调用action创建器后会自动创建action对象 * action对象的结构 {type: 'name/函数名', payload: '函数的参数'} * * 创建store: configureStore(option) * - {reducer: {名字:切片的reducer}} */ ``` 示例: ```js // @/store/index.js // 创建store import { createSlice, configureStore } from '@reduxjs/toolkit' // 初始值 const initialState = { name: '唐僧', age: 30, gender: '男', address: '长安', } // 创建切片, 切片会自动生成action和reducer export const stuSlice = createSlice({ name: 'stu', initialState, reducers: { setName: (state, action) => { state.name = action.payload }, setAge: (state, action) => { state.age = action.payload }, }, }) console.log(stuSlice.actions) // actionCreator() action创建器 export const { setName, setAge } = stuSlice.actions // 把action创建器暴露出去, 代替原来手写的{type,action} const nameAction = setName('猪八戒') console.log(nameAction) // {type: 'stu/stuName', payload: '猪八戒'} // 调用action创建器生成action对象 const store = configureStore({ reducer: { student: stuSlice.reducer, // reducer也是切片自动创建的 // 这里的student名字在引入store的时候要用到,切片可能有很多个,用名字取其中一个 }, }) console.log(store) export default store ``` 怎么使用: ``` // 1. 用redux提供的Provider把整个app包起来 // 和react自带的Provider的区别是这里传入的是store ``` ```js // @/index.js import { Provider } from 'react-redux'; import store from './store' // ... root.render( ) //... ``` ``` // 2.在组件中使用 // 通过useSelector()钩子获取state, 通过useDispatch()获取派发器对象 // 修改state的时候,派发器传入切片自动生成的action创建器 // action创建器里的值是要修改的值 // 当然也可以手动传如action:格式 {type: 'stu/stuName', payload: '猪八戒'},但不推荐,容易出错 ``` ```js // @/app.js import { useSelector, useDispatch } from 'react-redux' import {useState} from 'react' import {setName} from './store' import './App.css' function App() { // 通过useSelector()钩子获取state // 默认state是所有的,可以只取一部分 const stu = useSelector(state => state.student) // 这里的名字是configureStore参数里reducer对应的名字 // 通过useDispatch()获取派发器对象 const dispatch = useDispatch() const [inputName, setInputName] = useState('') const nameChangeHandler = (e)=>{ e.preventDefault() setInputName(e.target.value) } const saveNameHandler = ()=>{ // dispatch传入切片自动生成的action dispatch(setName(inputName)) } return (

姓名: {stu.name}

年龄: {stu.age}

性别: {stu.gender}

地址: {stu.address}

nameChangeHandler(e)}/>

) } export default App ``` ### 拆分模块 实际使用不会把切片都写在index里, 一般拆分开, 稍微修改下上面的代码 ```js // 新建@/store/stuSlice.js import { createSlice } from '@reduxjs/toolkit' const initialState = { name: '唐僧', age: 30, gender: '男', address: '长安', } export const stuSlice = createSlice({ name: 'stu', initialState, reducers: { setName: (state, action) => { state.name = action.payload }, setAge: (state, action) => { state.age = action.payload }, }, }) export const { setName, setAge } = stuSlice.actions export const {reducer:stuReducer} = stuSlice ``` ```js // @/store/index.js也要改下 import { configureStore } from '@reduxjs/toolkit' import {stuReducer} from './stuSlice' const store = configureStore({ reducer: { student: stuReducer, }, }) export default store ``` ```js // 引入的部分也要改下 // @/app.js import { useSelector, useDispatch } from 'react-redux' import {setName as setStuName} from './store/stuSlice' // ... const {student:stu} = useSelector(state => state) const dispatch = useDispatch() const saveNameHandler = ()=>{ // dispatch传入切片自动生成的action dispatch(setName(inputName)) } //... ``` ## RTKQ [RTK Query | Redux Toolkit](https://redux-toolkit.js.org/tutorials/rtk-query) > Q: query, 简化在 Web 应用程序中加载数据的常见情况 ### example #### 1 . 创建模块 ```js // 创建rtkq中的api对象,会自动根据各种方法生成对应的钩子函数 // 参数: // - reducerPath: 标识 // - baseQuery: 指定查询的基本信息, 发送请求使用的工具 // - endpoints: 指定api的各种功能, build: 请求的构建器 // - query 查询的方法,指定请求的路径 // - mutation 更新的方法 // - (可选)transformResponse 对响应数据进行处理 // 导出钩子命名规则: getStuList => useGetStuListQuery use表示钩子,Query表示查询方法 ``` ```js // src/store/stuApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' export const stuApi = createApi({ reducerPath: 'stuApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/' }), endpoints: (build) => ({ getStuList: build.query({ query: () => 'students', // transformResponse对响应数据进行处理 transformResponse(res){return res.data} }), getStuInfoById: build.query({ query: (id) => `students/${id}`, // 接收路径参数 }), updateStuById: build.mutation({ query: ({ id, stu }) => { return { url: `students/${id}`, method: 'put', body: { data: stu }, } }, }), }), }) export const {useGetStuListQuery} = stuApi ``` #### 2 . 配置 store ```js // src/store/index.js import { configureStore } from '@reduxjs/toolkit' import { stuApi } from './stuApi' // 添加到store export const store = configureStore({ reducer: { // api对象的路径名当store的标识 // api对象的reducer自动生成的 [stuApi.reducerPath]: stuApi.reducer, }, // 添加中间件用来实现缓存, 失效, 轮寻 // 必须添加 middleware: getDefaultMiddleware => { return getDefaultMiddleware().concat(stuApi.middleware) }, }) ``` #### 3 . 注入 store ```jsx // src/main.jsx import React from 'react' import ReactDOM from 'react-dom/client' import { Provider } from 'react-redux' import App from './App' import './index.css' import store from './store' ReactDOM.createRoot(document.getElementById('root')).render( ) ``` #### 4 . 使用 api ```jsx import { useState } from 'react' import './App.css' import { useGetStuListQuery } from './store/stuApi' function App() { // 调用钩子查询数据 // 钩子返回一个对象,包括请求过程中相关的数据 const obj = useGetStuListQuery() console.log(obj) const { isLoading, isSuccess, data} = obj return (
{isLoading &&

--数据正在加载--

} {isSuccess && data.map(item=>{ return

姓名: {item.attributes.name} | 性别: {item.attributes.gender==='male'?'男':'女'} | 年龄: {item.attributes.age} | 地址: {item.attributes.address}

}) }
) } export default App ``` ### `keepUnusedDataFor`:缓存功能 >rtkq会查询数据后会默认缓存60秒, 在期间不重复发请求而是使用缓存 设置缓存时间`keepUnusedDataFor` ```js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/' }), endpoints: (build) => ({ getPosts: build.query({ query: () => 'posts', keepUnusedDataFor: 5, // 单位是秒,0为不缓存 }), }), }) ``` ### useQuery返回的对象 ```js /** * refetch: 用来重新加载数据的函数 * status: 字符串,请求的状态 * isFetching: boolean,是否正在加载 * isLoading: boolean,数据是否第一次加载,调用refetch不会改变状态 * isSuccess: boolean,请求是否成功 * isError: boolean,是否出错 * isUninitialized: boolean,是否还未开始发送 * data: 最新的数据 * currentData: 当前参数的最新数据,和data的区别:当参数发生变化的时候,最开始保存的是undefined */ ``` ### useQuery传参 ```js // useQuery可以接收一个对象作为第二个参数,对请求进行配置 const { isFetching, isSuccess,isError, data } = useGetStuInfoByIdQuery(props.id, { selectFromResult: result => result, // 对返回进行处理 pollingInterval: 0, // 轮寻间隔,单位毫秒.0表示不轮寻 skip:!props.id, // 是否跳过,默认false refetchOnMountOrArgChange: false, // 设置是否每次组件挂载都重新加载数据,也可以传时间,单位是秒.例如设置2,表示两秒内,不重新加载 refetchOnFocus: false, // 页面获取焦点时是否重新加载数据,例如切换浏览器标签 refetchOnReconnect: false, // 网络恢复连接重新加载数据 // refetchOnFocus和refetchOnReconnect 需要额外在'@/store/index.js'里添加 // 1. import { setupListeners } from '@reduxjs/toolkit/query' // 2. setupListeners(store.dispatch) }) ``` ### useMutation #### 1 . 创建模块 ```js // build.mutation 更新的方法 // return的是一个对象,包括url, method, body // 导出钩子同样有规则, use前缀表示钩子, Mutation表示更新 ``` ```js // src/store/stuApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' export const stuApi = createApi({ reducerPath: 'stuApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/' }), endpoints: build => ({ // 删除学生信息 delStuById: build.mutation({ query: id => { // 如果发送的不是get请求,要返回一个对象,并配置信息 return { url: `students/${id}`, method: 'delete', } }, }), // 添加学生 addStu: build.mutation({ query: stu => { // post请求包含body属性 return { url: 'students', method: 'post', body: { data: stu }, } }, }), // 更新学生信息 updateStuById: build.mutation({ query: ({ id, stu }) => { return { url: `students/${id}`, method: 'put', body: { data: stu }, } }, }), }), }) export const { useDelStuByIdMutation, useAddStuMutation, useUpdateStuByIdMutation, } = stuApi ``` #### 2 . 配置 store ```jsx import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' import { stuApi } from './stuApi' // 添加到store const store = configureStore({ reducer: { // api对象的路径名当store的标识 // api对象的reducer自动生成的 [stuApi.reducerPath]: stuApi.reducer, }, // 添加中间件用来实现缓存, 失效, 轮寻 // 必须添加 // 多个中间件, contat(a,b,c) middleware: getDefaultMiddleware => { return getDefaultMiddleware().concat(stuApi.middleware) }, }) setupListeners(store.dispatch) export default store ``` #### 3 . 注入 store ```jsx // src/main.jsx import React from 'react' import ReactDOM from 'react-dom/client' import { Provider } from 'react-redux' import App from './App' import './index.css' import store from './store' ReactDOM.createRoot(document.getElementById('root')).render( ) ``` #### 4 . 使用 api ```js // useMutation和useQuery不一样,返回的是数组,第一个是触发器,第二个是结果 // result的结构类似useQuery返回 // 触发器返回的res也是一个 promise对象 ``` ```jsx import {useDelStuByIdMutation} from '@/store/stuApi' // ... const [delStu, {isLoading:delLoading}] = useDelStuByIdMutation() const delStuHandler = () => {delStu(id)} // id是从props中解构得到的 // ... ``` ### useMutation 返回的 result对象 ```js /** * isError: boolean * isLoading: boolean * isSuccess: boolean * isUninitialized: boolean * originalArgs: * reset: fn * status: string */ ``` ### 使数据失效: 数据标签 > 举例: 更新某个学生的信息, 保存成功后, 要让学生列表的组件重渲染 ```js // 1. 设置有哪些标签类型 tagTypes // 2. 需要更新的api功能设置 providesTags 提供标签 // 3. 触法更新的api功能设置 invalidatesTags 使标签失效 ``` ```js // src/store/stuApi.js /** * 1. getStuList的标签是'stuList', addStu可以让所有的'stuList'标签失效,从而触发getStuList重新加载数据, 组件重渲染 * 标签可以有多个 * 2. 需要更颗粒化控制, 例如id为1的学生更新了信息, 只需要下次查询id为1的学生不使用缓存, 其他id如果有缓存就使用缓存 * providesTags属性设置为函数,函数返回一个数组,数组里放对象,对象包括type属性和id,id来自query传参的id * invalidatesTags同样属性设置为函数,函数返回一个数组,数组里放对象,对象包括type属性和id,id来自query传参的id **/ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' export const stuApi = createApi({ reducerPath: 'stuApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/' }), tagTypes: ['stuList','stuInfo'], // 指定api中的标签类型--type endpoints: build => ({ // 获取学生列表 getStuList: build.query({ query: () => 'students', // transformResponse对响应数据进行处理 transformResponse(res) { return res.data }, providesTags: ['stuList'] // 当标签失效的时候,触发数据加载. 这里的标签可以有多个,其中一个失效,就重新获取 }), // 根据id获取学生信息 getStuInfoById: build.query({ query: id => `students/${id}`, transformResponse(res) { return res.data.attributes }, keepUnusedDataFor: 30, // 设置缓存时间,单位秒,0是不缓存 providesTags: (result,error,id)=>{ // 设置tag更加详细,可以让某个type+id的失效,而不是整个type // result: 请求返回的信息 // error: 错误信息 // id: query接收的id return [{type: 'stuInfo',id}] } }), // 删除学生信息 delStuById: build.mutation({ query: id => { // 如果发送的不是get请求,要返回一个对象,并配置信息 return { url: `students/${id}`, method: 'delete', } }, }), // 添加学生 addStu: build.mutation({ query: stu => { return { url: 'students', method: 'post', body: { data: stu }, } }, invalidatesTags: ['stuList'] // 使标签失效 }), // 更新学生信息 updateStuById: build.mutation({ query: ({ id, stu }) => { return { url: `students/${id}`, method: 'put', body: { data: stu }, } }, invalidatesTags: (result,error,id)=>{ return ['stuList',{type:'stuInfo',id}] // 类似providesTags } }), }), }) export const { useGetStuListQuery, useGetStuInfoByIdQuery, useDelStuByIdMutation, useAddStuMutation, useUpdateStuByIdMutation, } = stuApi ``` 上面的`tagTypes`可以简化成一个, 学生列表没有id, 可以给它指定一个独有的id, 如`[{type:'student',id:'list'}]` ```js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' export const stuApi = createApi({ reducerPath: 'stuApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/' }), tagTypes: ['student'], // type改成只有一个 endpoints: build => ({ getStuList: build.query({ query: () => 'students', transformResponse(res) { return res.data }, providesTags: [{type:'student',id:'list'}] // 指定id为'list' }), // 根据id获取学生信息 getStuInfoById: build.query({ query: id => `students/${id}`, transformResponse(res) { return res.data.attributes }, keepUnusedDataFor: 30, providesTags: (result,error,id)=>{ return [{type: 'student',id}] } }), // 删除学生信息 delStuById: build.mutation({ query: id => { return { url: `students/${id}`, method: 'delete', } }, invalidatesTags: [{type:'student',id:'list'}] // 使{type:'student',id:'list'}标签失效 }), // 添加学生 addStu: build.mutation({ query: stu => { return { url: 'students', method: 'post', body: { data: stu }, } }, invalidatesTags: [{type:'student',id:'list'}] // 使标签失效 }), // 更新学生信息 updateStuById: build.mutation({ query: ({ id, stu }) => { return { url: `students/${id}`, method: 'put', body: { data: stu }, } }, invalidatesTags: (result,error,id)=>{ return [{type:'student',id:'list'},{type:'student',id}] // 使多个标签失效 } }), }), }) export const { useGetStuListQuery, useGetStuInfoByIdQuery, useDelStuByIdMutation, useAddStuMutation, useUpdateStuByIdMutation, } = stuApi ``` ### 设置请求头 ```js // baseQuery 参数里, 除了 baseUrl,还有prepareHeaders用来设置请求头 // 第一个参数是默认headers, 第二个参数有获取state的方法 // 因为useSelect只能在函数组件或者自定义勾子中使用 ``` ```js baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/', prepareHeaders: (headers, { getState }) => { // 获取token const token = getState().auth.token if (token) { headers.set('Authorization', `Bearer ${token}`) } return headers }, }), ```