1 Redux及ToolKit的使用
1 纯react版本
import React, { Component} from 'react';
class Dome extends Component {
state = {count:0}
selectNumber=React.createRef()
change=() => {
this.setState({Name:'jack'})
}
increment=() => {
const {value}=this.selectNumber.current
const {count}=this.state
this.setState({count:count+value*1})
}
decrement=() => {
const {value}=this.selectNumber.current
const {count}=this.state
this.setState({count:count-value*1})
}
incrementIfOdd=() => {
const {value}=this.selectNumber.current
const {count}=this.state
if (count%2 !==0) {
this.setState({count:count+value*1} )
}
}
incrementAsync=() => {
const {value}=this.selectNumber.current
const {count}=this.state
setTimeout(() => {
this.setState({count:count+value*1} )
}, 2000);
}
render() {
const {count}=this.state
return (
<div className='parent' style={{width:'300px',height:'200px',background:'#fff7e6'}}>
<h1>当前求和为:{count}</h1>
<select name="" id="" ref={this.selectNumber}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<br />
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前和为奇数时加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
);
}
}
export default Dome;
2 redux简单版本
import React from 'react'
import Dome from './component/Dome'
class App extends React.Component {
state={}
render(){
return (
<Dome/>
)
}
}
export default App
import React, { Component} from 'react';
import store from '../redux/store';
class Dome extends Component {
//在状态改变时,帮助更新
componentDidMount(){
store.subscribe(() => {
this.setState({})
})
}
selectNumber=React.createRef()
increment=() => {
const {value}=this.selectNumber.current
//调用store.dispatch创建action,向reducer发布触发类型,以及数据
store.dispatch({type:'increment',data:value*1})
}
decrement=() => {
const {value}=this.selectNumber.current
store.dispatch({type:'decrement',data:value*1})
}
incrementIfOdd=() => {
const {value}=this.selectNumber.current
const count=store.getState().count
if (count%2 !==0) {
store.dispatch({type:'incrementIfOdd',data:value*1})
}
}
incrementAsync=() => {
const {value}=this.selectNumber.current
setTimeout(() => {
store.dispatch({type:'incrementAsync',data:value*1})
}, 1000);
}
render() {
return (
<div className='parent' style={{width:'300px',height:'200px',background:'#fff7e6'}}>
<!--store.getState().count获得redux接管后的状态count的值-->
<h1>当前求和为:{store.getState().count}</h1>
<select name="" id="" ref={this.selectNumber}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<br />
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前和为奇数时加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
);
}
}
export default Dome;
// 该文件创建为count组件服务的reducer【缩版】
// reducer能接收两个参数,一是之前的状态preState,二是动作对象action
function countReducer(preState={count:1},action) {
const { type, data } = action;
switch (type) {
case "increment":
return {...preState,count:preState.count + data};
case "decrement":
return {...preState,count:preState.count - data};
case "incrementIfOdd":
return {...preState,count:preState.count + data};
case "incrementAsync":
return {...preState,count:preState.count + data};
default:
return {...preState,count:preState.count};
}
}
export default countReducer
// 该文件用于暴露一个Store对象,整个应用只有一个Store仓库
//引入createStore,配置全局Store仓库
import {createStore}from'redux'
import countReducer from './count_reducer'
const store= createStore(countReducer)
export default store
3 Redux ToolKit版本
import React from 'react'
import Dome from './component/Dome'
import{Provider}from 'react-redux'
import store from './redux/store'
class App extends React.Component {
state={}
render(){
return (
//提供监听,如果状态发生更新便更新组件
<Provider store={store}>
<Dome/>
</Provider>
)
}
}
export default App
import React from 'react';
import{increment, decrement} from '../redux/count_reducer'
import {useDispatch, useSelector} from 'react-redux';
function Dome () {
//useSelector(state=>state.countNum)获得redux接管后的状态
const countNum= useSelector(state=>state.countNum)
//调用usedispatch
const dispatch=useDispatch()
const selectNumber=React.useRef()
const incrementA=()=>{
const {value}=selectNumber.current
//调用usedispatch,创建action,向reducer发布触发函数,以及传递数据
dispatch(increment(value*1))
}
const decrementA=()=>{
const {value}=selectNumber.current
dispatch(decrement(value*1))
}
return (
<div className='parent' style={{width:'300px',height:'200px',background:'#fff7e6'}}>
<!--获得redux接管后的状态count值-->
<h1>当前求和为:{countNum.count}</h1>
<select name="" id="" ref={selectNumber}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<br />
<button onClick={incrementA}>+</button>
<button onClick={decrementA}>-</button>
</div>
);
}
export default Dome;
import {createSlice} from '@reduxjs/toolkit'
const countSlice= createSlice({
//没啥含义,前缀名
name:'count',
//初始化状态
initialState:{
count:1
},
reducers:{
//不像之前用switch做判断,现在直接用函数,传递来的数据都在action.payload里
increment(State,action){
State.count=State.count + action.payload
},
decrement(State,action){
State.count=State.count - action.payload
}
}
})
export const {increment,decrement}=countSlice.actions
export default countSlice
// 该文件用于暴露一个Store对象,整个应用只有一个Store仓库
//引入configureStore,配置全局Store仓库
import {configureStore }from'@reduxjs/toolkit'
import countSlice from './count_reducer'
const store= configureStore ({
reducer:{
countNum:countSlice.reducer
}
})
export default store
2 UI组件的分离与整合
1 UI组件的容器组件的分离
UI组件和容器组件分离的好处,UI组件里负责显示界面,容器组件里负责功能,降低耦合
import React from 'react'
//组件分离时,调用的是容器里的组件
import Dome from './container/count_container'
import{Provider}from 'react-redux'
import store from './redux/store'
class App extends React.Component {
state={}
render(){
return (
//将仓库作为props传入,store={store}
<Provider store={store}>
<Dome/>
</Provider>
)
}
}
export default App
import Dome from "../component/Dome";
import{connect} from 'react-redux'
import{increment, decrement} from '../redux/count_reducer'
//redux中自带的方法,将State中的状态映射到Props中传入UI组件
function mapStateToProps(state) {
return {count:state.countNum.count}
}
//redux中自带的方法,将Dispatch的行为方法映射到Props中传入UI组件
function mapDispatchToProps(dispatch) {
return {
jia: (number) =>dispatch(increment(number)),
jIan:(number) =>dispatch(decrement(number))
}
}
//暴露connect,connect的两个参数分别是上面的两个自带的方法
export default connect(mapStateToProps,mapDispatchToProps)(Dome)
// 精简版
// import Dome from "../component/Dome";
// import{connect} from 'react-redux'
// import{increment, decrement} from '../redux/count_reducer'
// export default connect(
// (state)=> ({count:state.countNum.count}),
// (dispatch) =>({
// jia: (number) =>dispatch(increment(number)),
// jIan:(number) =>dispatch(decrement(number))
// }),
// )(Dome)
import React from 'react';
function Dome (props) {
const selectNumber=React.useRef()
const incrementA=()=>{
const {value}=selectNumber.current
//在props中获得方法并使用即可
props.jia(value*1)
}
const decrementA=()=>{
const {value}=selectNumber.current
props.jIan(value*1)
}
return (
<div className='parent' style={{width:'300px',height:'200px',background:'#fff7e6'}}>
<!--在props中获得状态并使用即可-->
<h1>当前求和为:{props.count}</h1>
<select name="" id="" ref={selectNumber}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<br />
<button onClick={incrementA}>+</button>
<button onClick={decrementA}>-</button>
</div>
);
}
export default Dome;
import {createSlice} from '@reduxjs/toolkit'
const countSlice= createSlice({
name:'count',
initialState:{
count:1
},
reducers:{
increment(State,action){
State.count=State.count + action.payload
},
decrement(State,action){
State.count=State.count - action.payload
}
}
})
export const {increment,decrement}=countSlice.actions
export default countSlice
// 该文件用于暴露一个Store对象,整个应用只有一个Store仓库
//引入configureStore,配置全局Store仓库
import {configureStore }from'@reduxjs/toolkit'
import countSlice from './count_reducer'
const store= configureStore ({
reducer:{
countNum:countSlice.reducer
}
})
export default store
2 UI组件的容器组件的整合优化
import React from 'react'
import Dome from './container/count_container'
import{Provider}from 'react-redux'
import store from './redux/store'
class App extends React.Component {
state={}
render(){
return (
<Provider store={store}>
<Dome/>
</Provider>
)
}
}
export default App
import React from 'react';
import{connect} from 'react-redux'
import{increment, decrement} from '../redux/count_reducer'
function Dome (props) {
const selectNumber=React.useRef()
const incrementA=()=>{
const {value}=selectNumber.current
// //UI组件在props中读取方法,并传入参数调用
props.jia(value*1)
}
const decrementA=()=>{
const {value}=selectNumber.current
props.jIan(value*1)
}
return (
<div className='parent' style={{width:'300px',height:'200px',background:'#fff7e6'}}>
<!--UI组件在props中读取状态-->
<h1>当前求和为:{props.count}</h1>
<select name="" id="" ref={selectNumber}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<br />
<button onClick={incrementA}>+</button>
<button onClick={decrementA}>-</button>
</div>
);
}
export default connect(
//向UI组件的props中传入状态,使UI组件能读取状态
(state)=> ({count:state.countNum.count}),
//向UI组件的props中传入方法,使UI组件能读取方法
(dispatch) =>({
jia: (number) =>dispatch(increment(number)),
jIan:(number) =>dispatch(decrement(number))
}),
)(Dome)
import {createSlice} from '@reduxjs/toolkit'
const countSlice= createSlice({
name:'count',
//存储状态,使UI组件状态初始化
initialState:{
count:1
},
//存储方法,使UI组件能够调用方法
reducers:{
increment(State,action){
State.count=State.count + action.payload
},
decrement(State,action){
State.count=State.count - action.payload
}
}
})
export const {increment,decrement}=countSlice.actions
export default countSlice
// 该文件用于暴露一个Store对象,整个应用只有一个Store仓库
//引入configureStore,配置全局Store仓库
import {configureStore }from'@reduxjs/toolkit'
import countSlice from './count_reducer'
const store= configureStore ({
reducer:{
countNum:countSlice.reducer
}
})
export default store
# 1 简单的发送请求 ## 大概结构
import React, { useEffect, useState } from "react";
import StudentList from "./component/StudentList";
import "./App.css";
function App() {
// 传回来的数据
const [StuData, setStuData] = useState([]);
// 判断数据是否在请求
const [Loading, setLoading] = useState();
useEffect(() => {
// 请求开始
setLoading(true);
// 发送请求
fetch("http://localhost:5001/")
.then((res) => {
return res.json();
})
.then((data) => {
setStuData(data.data);
// 请求结束
setLoading(false);
})
.catch((err) => {});
}, []);
return (
<div className="app">
{!Loading ?<StudentList stu_s={StuData} />:<p>数据正在加载中...</p>}
</div>
);
}
export default App;
import React, { useState } from 'react';
import Student from './Student';
function StudentList(props) {
const borders={border:'1px solid black'}
return (
<table style={borders}>
<caption style={borders}>学生列表</caption>
<thead style={borders}>
<tr style={borders}>
<th style={borders}>姓名</th>
<th style={borders}>性别</th>
<th style={borders}>年龄</th>
<th style={borders}>地址</th>
</tr>
</thead>
<tbody>
{props.stu_s.map(stu => <Student key={stu.id} stu={stu.attributes}/>)}
</tbody>
</table>
);
}
export default StudentList;
import React, { useState } from 'react';
function Student(props) {
const borders={border:'1px solid black'}
const{stu:{name,gender,age,address}}=props
return (
<tr>
<td style={borders}>{name}</td>
<td style={borders}>{gender}</td>
<td style={borders}>{age}</td>
<td style={borders}>{address}</td>
</tr>
);
}
export default Student;
async/await升级版
import React, { useEffect, useState } from "react";
import StudentList from "./component/StudentList";
import "./App.css";
function App() {
// 传回来的数据
const [StuData, setStuData] = useState([]);
const [Error, setError] = useState(null);
// 判断数据是否在请求
const [Loading, setLoading] = useState();
// 因为useEffect里不async()的函数,所以将async()放入普通的回调函数,在将其调用
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const res = await fetch("http://localhost:5001/");
console.log(res);
if (res.ok) {
const data = await res.json();
setStuData(data.data);
} else {
throw new Error("数据加载出错");
}
}
// 原本请求之后的响应是一个premise的异步回调,要用then来接收并返回结果
// 现在用await直接接收
// fetch("http://localhost:5001/")
// .then((res) => {
// if (res.ok) {
// return res.json();
// setLoading(false);
// 统一抛出错误信息
// throw new Error("数据加载出错");
// }
// })
// .then((data) => {
// setStuData(data.data);
// })
// 捕获抛出的错误信息
// .catch((e) =>{
// console.log(e)
// setLoading(false)
// setError(e);
// });
catch (e) {
setError(e);
// finally在结束时触发,不管出不出错
} finally {
setLoading(false);
}
}
fetchData();
}, []);
return (
<div className="app">
{(!Loading && !Error)&&<StudentList stu_s={StuData} />}
{Loading && <p>数据正在加载中...</p>}
{Error&&<p>数据加载出错!</p>}
</div>
);
}
export default App;
3 Fetch的使用
import React, { useCallback, useEffect, useState } from "react";
import StudentList from "./component/StudentList";
import "./App.css";
function App() {
const [StuData, setStuData] = useState([]);
const [Error, setError] = useState(null);
const [Loading, setLoading] = useState();
// 将函数提取出来,同时将其缓存
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const res = await fetch("http://localhost:3000/api/students");
if (res.ok) {
const data = await res.json();
setStuData(data.data);
} else {
throw new Error("数据加载出错");
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchData();
}, []);
return (
<stuContext.Provider value={{ fetchData }}>
<div className="app">
<button
onClick={() => {
fetchData();
}}
>
加载数据
</button>
{!Loading && !Error && <StudentList stu_s={StuData} />}
{Loading && <p>数据正在加载中...</p>}
{Error && <p>数据加载出错!</p>}
</div>
</stuContext.Provider>
);
}
export const stuContext = React.createContext({
fetchData: () => {},
});
export default App;
当然,你也可以使用async 和 await
注意;useEffect的第一个参数不能是异步的,所以用常量接着
我现在才知道原来try catch后面还有finally…
finally既是出错了也会执行
import React, { useState } from 'react';
import Student from './Student';
import StudentForm from './studentForm';
function StudentList(props) {
const borders={border:'1px solid black'}
return (
<table style={borders}>
<caption style={borders}>学生列表</caption>
<thead style={borders}>
<tr style={borders}>
<th style={borders}>姓名</th>
<th style={borders}>性别</th>
<th style={borders}>年龄</th>
<th style={borders}>地址</th>
<th style={borders}>操作</th>
</tr>
</thead>
<tbody>
{props.stu_s.map(stu => <Student key={stu.id} stu={stu.attributes} stuId={stu.id}/>)}
</tbody>
<tfoot><StudentForm/></tfoot>
</table>
);
}
export default StudentList;
import React, { useCallback, useContext, useState } from "react";
import {stuContext} from '../App.jsx'
function Student(props) {
const [error, setError] = useState(null);
const [Loading, setLoading] = useState();
const ctx=useContext(stuContext);
const borders = { border: "1px solid black" };
const {
stu: { name, gender, age, address },
} = props;
const delStu = useCallback(async () => {
try {
setLoading(true);
setError(null);
const res = await fetch(
`http://localhost:3000/api/students/delete/${props.stuId}`,
{
method: "get",
}
);
if (! res.ok) {
console.log(res.ok);
throw new Error("删除失败");
};
ctx.fetchData()
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}, []);
const deleteHandler = () => {
delStu();
};
return (
<>
<tr>
<td style={borders}>{name}</td>
<td style={borders}>{gender}</td>
<td style={borders}>{age}</td>
<td style={borders}>{address}</td>
<td style={borders}>
<button onClick={deleteHandler}>删除</button>
</td>
</tr>
{Loading && (
<tr>
<td colSpan={5}>正在删除数据。。。</td>
</tr>
)}
{error && (
<tr>
<td colSpan={5}>删除数据出错</td>
</tr>
)}
</>
);
}
export default Student;
import React, { useCallback, useContext, useState } from "react";
import {stuContext} from "../App.jsx";
function StudentForm() {
const [error, setError] = useState(null);
const [Loading, setLoading] = useState();
const ctx = useContext(stuContext);
const [inputData, setinputData] = useState({
name: "",
age: "",
gender: "男",
address: "",
});
const addStudent = useCallback(async (newData) => {
try {
setLoading(true);
setError(null);
const res =await fetch(`http://localhost:3000/api/students/post`, {
method: "post",
body: JSON.stringify({ data: newData }),
// 这个单词别拼错了
headers: {
"Content-type": "application/json",
},
});
if (!res.ok) {
throw new Error("出错了");
}
ctx.fetchData();
} catch (e) {
console.log(e);
setError(e);
} finally {
setLoading(false);
}
}, []);
const nameChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, name: e.target.value }));
};
const ageChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, age: e.target.value }));
};
const genderChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, gender: e.target.value }));
};
const addressChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, address: e.target.value }));
};
const submitHandler = () => {
addStudent(inputData);
};
return (
<>
<tr>
<td>
<input type="text" name="" id="" onChange={nameChangeHandler} />
</td>
<td>
<select name="" id="" defaultValue={'男'} onChange={genderChangeHandler}>
<option value="男">男</option>
<option value="女">女</option>
</select>
</td>
<td>
<input type="text" name="" id="" onChange={ageChangeHandler} />
</td>
<td>
<input type="text" name="" id="" onChange={addressChangeHandler} />
</td>
<td>
<button onClick={submitHandler}>添加</button>
</td>
</tr>
{Loading && (
<tr>
<td>添加中</td>
</tr>
)}
{error && (
<tr>
<td>添加失败</td>
</tr>
)}
</>
);
}
export default StudentForm;
4 自定义钩子
React 中的钩子函数只能在函数组件或自定钩子中调用
当我们需要将 React 中钩子函数提取到一个公共区域时,就可以使用自定义钩子
自定义钩子其实就是一个普通函数,只是它的名字需要使用 use 开头
就是将创建好的变量和函数 返回给调用者,就不用重复编写了
useFetch
(该例子,未实操,仅供学习思路)
import {useCallback, useState} from "react";
// reqObj 用来存储请求的参数
/*
* {
* url 请求的地址
* method 请求方法
*
* }
*
* cb 回调函数,请求发送成功后执行
* */
export default function useFetch(reqObj, cb) {
const [data, setData] = useState([]);
// 添加一个state来记录数据是否正在加载,false表示没有加载数据,true表示加载
const [loading, setLoading] = useState(false);
// 创建一个state来记录错误信息
const [error, setError] = useState(null);
const fetchData = useCallback(async (body) => {
try {
setLoading(true);
setError(null);
const res = await fetch('http://localhost:3000/api/'+reqObj.url, {
method:reqObj.method || 'get',
headers:{
"Content-type":"application/json"
},
body:body?JSON.stringify({data:body}):null,
});
//判断请求是否加载成功
if (res.ok) {
const data = await res.json();
setData(data.data);
//判断是否有回调函数,如果有,就执行
cb && cb();
} else {
throw new Error('数据加载失败!');
}
} catch (e) {
console.log(e);
setError(e);
} finally {
setLoading(false);
}
}, []);
// !!!!!将所需钩子进行返回值
return {
loading,
error,
data,
fetchData
};
}
就是将创建好的变量和函数 返回给调用者
import React, { useCallback, useContext, useState } from "react";
import {stuContext} from "../App.jsx";
function StudentForm() {
const ctx = useContext(stuContext);
const [inputData, setinputData] = useState({
name: "",
age: "",
gender: "男",
address: "",
});
//对钩子返回的值或方法进行接收,并解构赋值,还可以重命名
const {loading,error,data:stuData,fetchData}=useFetch({
url:`student/${props.stu.id}`,
method:'post',
},
const ctx = useContext(stuContext);
)
const nameChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, name: e.target.value }));
};
const ageChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, age: e.target.value }));
};
const genderChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, gender: e.target.value }));
};
const addressChangeHandler = (e) => {
setinputData((preState) => ({ ...preState, address: e.target.value }));
};
const submitHandler = () => {
//对接收的值或方法进行调用
addStudent(stuData);
};
return (
<>
<tr>
<td>
<input type="text" name="" id="" onChange={nameChangeHandler} />
</td>
<td>
<select name="" id="" defaultValue={'男'} onChange={genderChangeHandler}>
<option value="男">男</option>
<option value="女">女</option>
</select>
</td>
<td>
<input type="text" name="" id="" onChange={ageChangeHandler} />
</td>
<td>
<input type="text" name="" id="" onChange={addressChangeHandler} />
</td>
<td>
<button onClick={submitHandler}>添加</button>
</td>
</tr>
{Loading && (
<tr>
<td>添加中</td>
</tr>
)}
{error && (
<tr>
<td>添加失败</td>
</tr>
)}
</>
);
}
export default StudentForm;
代码编写巧思
回调函数存在即执行,没有就忽略
cb && cb();
Json 存在参数就放进去,不存在就忽略
body:body?JSON.stringify({data:body}):null,
5 rtkq的使用
RTKQ(redux toolkit query)
RTK不仅帮助我们解决了state的问题,同时,它还为我们提供了RTK Query用来帮助我们处理数据加载的问题。RTK Query是一个强大的数据获取和缓存工具。在它的帮助下,Web应用中的加载变得十分简单,它使我们不再需要自己编写获取数据和缓存数据的逻辑。
Web应用中加载数据时需要处理的问题:
- 根据不同的加载状态显示不同UI组件
- 减少对相同数据重复发送请求
- 使用乐观更新,提升用户体验
- 在用户与UI交互时,管理缓存的生命周期
- 在之前我们就是通过自己每一次发送请求,就创建对应的状态,比如isLoading,isError来处理加载时候的状态的,有了RTKQ,我们就不需要做这些事情了,我们通过RTKQ来帮我们完成这事情,它会自动封装钩子,以及一些状态
1 使用
第一步:配置RTKQ
store/studentApi.js
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react";
//创建Api对象
//createApi()用来创建RTKQ中的API对象
const studentApi = createApi({
//Api的reducer标识,不能和其他Api或reducer重复
reducerPath:'studentApi',
//指定查询的基础信息,发送请求使用的工具
//用来设置发送请求的工具,就是你是用什么发请求,RTKQ为我们提供了fetchBaseQuery作为查询工具,它对fetch进行了简单的封装,很方便,如果你不喜欢可以改用其他工具,这里暂时不做讨论。
baseQuery:fetchBaseQuery({
baseUrl:'http://localhost:3000/'
}),
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
//使用构建器来创建请求对象
getStudents:build.query({
query(arg) {
//用来指定请求的子路径
//可以直接返回请求的子路径,默认为get(axios也是默认get方式)
//也可以返回配置对象
return 'students';
},
//可以在这里将请求过来的数据进行转换并将结果返回
transformResponse(baseQueryReturnValue){
return 转换后的结果
}
})
}
}
})
//api对象创建后,对象中会根据各种方法自动的生成对应的钩子函数
//通过这些钩子函数,可以向服务器发送请求
//钩子函数的命名规则 endpoints中有一个getStudents 结合查询方式=>生成 useGetStudentsQuery
export const { useGetStudentsQuery } = studentApi;
//我们还需要使用store
export default studentApi;
第二步:创建store对象
中间件的调用
store/index.js
import {configureStore} from "@reduxjs/toolkit";
import studentApi from "./studentApi";
export default configureStore({
reducer:{
[studentApi.reducerPath]:studentApi.reducer,
},
middleware:getDefaultMiddleware => {
//getDefaultMiddleware调用后会返回所有的默认中间件
//传入studentApi的缓存生效
return getDefaultMiddleware().concat(studentApi.middleware)
}
})
主入口文件src/index.js
import ReactDOM from "react-dom/client"
import App from "./App"
import store from "./store"
import {Provider} from "react-redux";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App/>
</Provider>
)
第三步:使用RTKQ
输出查看调用api中钩子查询的返回值
可以看到,输出了三次,可以简单理解为发送前,发送中,发送完成
每一个阶段都会有具体的字段进行标识,比如isLoading,isFetching,isSuccess,isError等
这样子就可以便于我们去操作(至少我们不用自己写什么isLoading,isError了,RTKQ帮我们完成了)
import React from 'react';
import {useGetStudentsQuery} from "./store/studentApi";
const App = () => {
//调用api查询数据
//这个钩子函数会返回一个对象作为返回值,请求过程中相关数据都在该对象中
const obj = useGetStudentsQuery();//调用Api中的钩子查询数据
console.log(obj)
return (
<div>
我是App
</div>
);
};
export default App;
具体使用示例
import React from 'react';
import {useGetStudentsQuery} from "./store/studentApi";
const App = () => {
//调用api查询数据
//这个钩子函数会返回一个对象作为返回值,请求过程中相关数据都在该对象中
const { isLoading,data,isSuccess} = useGetStudentsQuery();//调用Api中的钩子查询数据
return (
<div>
{ isLoading && '加载中...'}
{isSuccess && data.map(item =>
<p key={item.id}>
姓名:{item.name} <br/>
性别:{item.sex} <br/>
年龄:{item.age} <br/>
地址:{item.address} <br/>
</p>)
}
</div>
);
};
export default App;
2 RTKQ的其他设置
在之前编辑学生数据的时候,是通过props来传递数据的,但是如果有一个人修改了数据我们没有更新,就会导致我们编辑的时候使用的是旧数据,用户体验不好
所以我们可以在编辑的时候重新请求服务器,重新查询数据
使用RTKQ关键就是如果传参和如何定义接口
//接口定义
const studentApi = createApi({
// ....
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
getStudents:build.query({
query() {
//用来指定请求的子路径
return 'students';
}
}),
getStudentInfo:build.query({
query(id) {
return `students/${id}`;
}
})
}
}
// ....
})
export const { useGetStudentsQuery,useGetStudentInfoQuery} = studentApi;
//传参-使用的时候传参
//传入id,用作查询的参数
const {data,isSuccess} = useGetStudentInfoQuery(props.id);//异步请求
const [formData,setFormData] = useState({
name: props.name ? props.name : "",
sex: props.sex ? props.sex : "男",
age: props.age ? props.age : "",
address: props.address ? props.address : "",
});
//使用useEffect重新渲染数据当状态改变
//为了避免在渲染中操作state导致无限重复渲染,我们选择在useEffect
useEffect(() => {
if(isSuccess){
setFormData({
...data,
})
}
},[isSuccess])
1 设置缓存时间和数据转换
RTKQ是自带缓存的,默认为60秒,
当我们点击修改的时候,会向后台发送请求,此时数据已经是缓存了,我们再次点击修改,数据已经被缓存,所以不会发送请求了,但是如果有人修改了数据,受到缓存的限制,不会重新请求,所以需要我们可以设置缓存时间keepUnusedDataFor,默认是60秒
每一个接口查询都可以设置缓存时间,默认为60(单位秒)
const studentApi = createApi({
// ....
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
getStudents:build.query({
query() {
//用来指定请求的子路径
return 'students';
}
}),
getStudentInfo:build.query({
query(id) {
return `students/${id}`;
},
//设置缓存时间为5秒
keepUnusedDataFor:5,
//加工处理请求过来的数据
transformResponse(baseQueryReturnValue){
return 转换后的结果
}
})
}
}
// ....
})
3 useQuery的返回值查看
refetch函数:一个函数,用来重新加载数据
status:请求的状态。pending:数据正在加载;fulfilled加载完毕;
isFetching:boolean - 数据是否正在加载,不管第几次加载,就会设置其状态
isLoading:boolean - 表示数据是否第一次加载,通过调用refetch不会改变此状态
isSuccess:boolean - 请求是否成功
isUninitialized:boolean -请求是否还没有开始发送,常用在删除操作的请求上
error:对象 有错才存在对象,无错误就没有这个
data:最新返回的数据(在重新发送请求的时候,会保存上一次请求返回的数据)
currentData:当前参数的最新数据,(当参数变化的时候,会清空(变为undefined))
比如在一个列表搜索想要的汉堡名称,搜索的时候我如果想清空原来的数据并展示等待图标,数据加载完成后才渲染返回的数据,就可以使用currentData,如果不想清空原来的数据,而是等到数据返回后替换原来的数据,就可以使用data
4 useQuery设置参数
1.useQuery第一个参数
useQuery第一个参数可以成为每一个请求的query函数传入的参数
//比如传入id
import {useGetStudentInfoQuery} from "../store/studentApi";
const {data,isSuccess} = useGetStudentInfoQuery(id);//异步请求
//则我们可以在api当中接收
const studentApi = createApi({
//...
endpoints:(build) => {
getStudentInfo:build.query({
query(id){
return `students/${id}`
}
j
})
}
//...
})
2.useQuery第二个参数
可以传入对象通过该对象可以对请求进行配置
创建的时候传入的对api的配置可以说默认配置
使用的时候传入的第二个参数配置可以更加具有定制化特点,和默认配置发生冲突的时候以这个为准
3.selectFromResult数据过滤
selectFromResult函数,返回值作为useQuery返回值,比如我可以设置data的数据,对data数据进行过滤
const res = useGetStudentsQuery(null,{
selectFromResult: (result) => {
if(result.data){
result.data = result.data.filter(item => item.age < 18)
}
return result;
}
});
4.pollingInterval轮询
默认为0,设置轮询的间隔(隔一段时间发送请求),单位毫秒,为0表示不轮询
5.skip跳过请求
skip设置是否跳过当前请求,默认false
比如一个组件既有编辑功能,也有添加功能,那么我们需要在添加功能的时候跳过请求,否则就添加的时候就会向后台请求初始化数据
//当有id的时候,才请求数据,否则不请求
const {data,isSuccess} = useGetStudentInfoQuery(props.id,{
skip:!props.id,
});//异步请求
6.refetchOnMountOrArgChange重新加载
默认false 设置是否每次都重新加载数据(也就是设置是否不使用缓存)也可以设置为数字,为数字的话就是设置缓存有效期
如果设置为true,需要在store当中设置setupListeners(store.dispatch)
import {setupListeners} from "@reduxjs/toolkit/query"
import {configureStore} from "@reduxjs/toolkit";
import studentApi from "./studentApi";
export default configureStore({
reducer:{
[studentApi.reducerPath]:studentApi.reducer,
},
middleware:getDefaultMiddleware => {
//getDefaultMiddleware调用后会返回所有的默认中间件
//传入studentApi的缓存生效
return getDefaultMiddleware().concat(studentApi.middleware)}
})
setupListeners(store.dispatch)
1 refetchOnFocus重新获取焦点时重载数据
默认false, 是否在重新获取焦点时重载数据(比如切换页面)
2 refetchOnReConnect重新连接后重载数据
默认false, 是否在重新连接后重载数据(没网了,重新连接了网)
5 TKQ构造器构造API请求
如果发送的不是get信息(当然,get也可以设置配置对象),我们就不可能像之前写query一样了
// 之前的写法
const studentAPi = createApi({
//...
endpoints:(build) => {
return {
getStudent:build.query({
query(){
return 'students'
}
})
}
}
})
//如果现在是put或者delete或者post,就需要返回配置对象的形式了
//并且更改构建器为mutation
const studentApi = createApi({
//...
endpoints:(build) => {
return {
//getStudent:build.query对比差别
delStudent:build.mutation({
query(参数){
return {
url:'students/${参数}',
method:'delete',
//如果有参数,则需要添加body
body:传递的数据
}
}
})
}
}
//...
})
删除的调用和查询的调用不太一样,因为删除不是页面加载后就立马删除,而是用户确认删除后才执行删除操作,添加的操作也是如此,修改的也是
//引入删除的接口
import {useDelStudentInfoMutation} from "../store/studentApi";
//输出查看内容
//const a = useDelStudentInfoMutation();
//打印的内容如下,是一个数组
//[f,{isError,isLoading,isSuccess,isUninitialized,originalArgs,reset,status}];
//第一个为触发器,第二个是结果集
//所以具体中我们可以获取触发器,在需要的时候调用就会执行函数
//调用的结果和状态和可以从结果集获取
const [delStudent,{isSuccess}] = useDelStudentInfoMutation();
//点击删除学生,正常的操作应该要询问用户是否删除的
const handleClick = useCallback(async (id) => {
delStudent(id);
})
jsx的具体操作添加,修改
const [formData,setFormData] = useState({
name: props.name ? props.name : "",
sex: props.sex ? props.sex : "男",
age: props.age ? props.age : "",
address: props.address ? props.address : "",
});
//修改数据
const [editStudentInfo,{isSuccess:editSuccess}] = useEditStudentInfoMutation();
//添加数据
const [addStudent] = useAddStudentInfoMutation();
/*添加回调确认*/
const handleAdd = useCallback(async () => {
//执行添加数据
addStudent({
...formData,
});
//清空数据
setFormData({
name: "",
sex: "男",
age: "",
address: "",
})
});
/*修改回调确认*/
const handleEditConfirm = useCallback(async () => {
//执行修改数据
editStudentInfo({
id:props.id,
info:formData,
});
},)
studentApi.js的query定义
//修改数据
editStudentInfo:build.mutation({
query(newInfo) {
return {
url:`students/${newInfo.id}`,
method:'put',
body:newInfo.info,
}
}
}),
//添加数据
addStudentInfo:build.mutation({
query(info){
return {
url:'students',
method:'post',
body:info,
}
}
})
6 RTKQ的数据标签
给API切片请求的数据打上标签,当标签失效的时候,就会重新加载标签对应的请求
1 创建标签tagTypes
const studentApi = createApi({
reducerPath:'xxxxx',
//用来指定API中的标签类型
tagTypes:['student'],
endpoints:(buidl) => {
//....
}
})
2 给请求数据添加标签providesTags
给API切片的钩子函数添加providesTags属性,属性值可以为数组字符串,数组对象,回调函数
const studentApi = createApi({
tagTypes:['student'],
endpoints:(build) => {
return {
xxxxx:build.query({
query(){
return xxxx
},
providesTags:xxxxx
})
}
}
})
当providesTags为数组字符串的时候
等同于数组对象的简写
只要type和invalidates对应,就会重新请求数据
providesTags:['student'];
//等同于
providesTags:[{type:'student'}]
当providesTags为数组对象的时候
所有数据(type和id)都需要和invalidates对应才会重新请求数据
id如果是字符串数字,如果有对应的id,依旧会失效
providesTags:[{type:'student',id:100}];
providesTags:[{type:'student',id:'100'}];
当providesTags为回调函数的时候
当providesTags属性的值为回调函数时,可以对标签的生效范围做更细致的划分
参数1:网络请求的返回结果
参数2:错误信息
参数3:钩子函数中传入的实参
//返回值为一个数组,符合数组元素条件的数据将生效
providesTags:(result,error,params,meta) => {
return [{type:'student',id:params.id}]
}
3 给请求的数据设置要失效的标签
通过invalidatesTags进行设置
const studentApi = createApi({
tagTypes:['student'],
endpoints:(build) => {
return {
delStudent:build.mutation({
query(id){
return {
url:xxxx,
method:'delete',
}
invalidatesTags:xxxx
}
})
}
}
})
当invalidatesTags为数组字符串时候
只要providesTags当中type包含在invalidatesTags,就会重新请求数据
数组字符串的写法等同于数组对象的简写
invalidatesTags:['student'];
providesTags:['student'];//让其失效
providesTags:[{type:'student'}];//让其失效
providesTags:[{type:'student',id:100}];//让其失效
invalidatesTags:['student'];
//等同于,二个效果是一样的
invalidatesTags:[{type:'student'}];
当invalidatesTags为数组对象的时候
指明id则让type和id二者都对应的标签失效
未指明就和数组字符串失效规则一样
invalidates:[{type:'student',id:10}];
providesTags:['student'];//不会失效
providesTags:[{type:'student'}];//不会失效
providesTags:[{type:'student',id:10}];//会失效
//实测会
providesTags:[{type:'student',id:'10'}];//会失效
providesTags:[{type:'student',id:888}];//不会失效
当invalidatesTags为回调函数的时候
invalidatesTags:(result,error,stu,meta) =>
{
return [
{type:'student',id:stu.id},
{type:'student',id:'LIST'}
]
}
4 示例
当添加数据后,会刷新列表
当编辑后,会刷新列表,但是如果数据没有变动的话,点击编辑就不会重新查询(存在缓存的前提下)
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react";
//创建Api对象
//createApi()用来创建RTKQ中的API对象
const studentApi = createApi({
reducerPath:'studentApi',//Api的标识,不能和其他Api或reducer重复
//指定查询的基础信息,发送请求使用的工具
baseQuery:fetchBaseQuery({
baseUrl:'http://localhost:3000/'
}),
//用来指定api当中的标签
tagTypes:['student'],
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
getStudents:build.query({
query() {
//用来指定请求的子路径
return 'students';
},
providesTags:[{type:'student',id:'initList'}]
}),
//编辑的时候获取信息
getStudentInfo:build.query({
query(id) {
return `students/${id}`;
},
providesTags:(result, error, arg, meta) => {
return [
{type:'student',id:arg},
]
},
设置缓存时间为5秒
//keepUnusedDataFor:5,
}),
//删除数据
delStudentInfo:build.mutation({
query(id){
return {
url:`students/${id}`,
method:'delete',
}
},
invalidatesTags:[{type:'student',id:'initList'}]
}),
//修改数据
editStudentInfo:build.mutation({
query(newInfo) {
return {
url:`students/${newInfo.id}`,
method:'put',
body:newInfo.info,
}
},
//修改数据的时候,只针对修改过的列表进行重新查询数据,未改变的不变动
invalidatesTags:(result, error, arg, meta) => {
return [
{type:'student',id:'initList'},
{type:'student',id:arg.id},
]
}
}),
//添加数据
addStudentInfo:build.mutation({
query(info){
return {
url:'students',
method:'post',
body:info,
}
},
//添加数据的时候重新刷新列表
invalidatesTags:[{type:'student',id:'initList'}]
})
}
}
})
//api对象创建后,对象中会根据各种方法自动的生成对应的钩子函数
//通过这些钩子函数,可以向服务器发送请求
//钩子函数的命名规则 endpoints中有一个getStudents 结合查询方式=>生成 useGetStudentsQuery
export const { useGetStudentsQuery,
useGetStudentInfoQuery,
useDelStudentInfoMutation,
useAddStudentInfoMutation,
useEditStudentInfoMutation,
} = studentApi;
//我们还需要使用store
export default studentApi;
7 RTKQ使用axios
其实使用很简单,就是axios的二次封装
安装
npm install axios
更改
import {createApi} from "@reduxjs/toolkit/dist/query/react";
import axios from "axios";
const studentApi = createApi({
reducerPath:'studentApi',//Api的标识,不能和其他Api或reducer重复
//指定查询的基础信息,发送请求使用的工具
//baseQuery:fetchBaseQuery({
// baseUrl:'http://localhost:3000/'
//}),
//更改为axios
baseQuery:axios.create({
baseURL:'http://localhost:3000/'
}),
})
请求体变更
比如post或者put等传参需要使用data,而不是body了
//修改数据
editStudentInfo:build.mutation({
query(newInfo) {
return {
url:`students/${newInfo.id}`,
method:'put',
//之前fetch的时候
//body:newInfo.info
data:newInfo.info,
}
},
//修改数据的时候,只针对修改过的列表进行重新查询数据,未改变的不变动
invalidatesTags:(result, error, arg, meta) => {
return [
{type:'student',id:'initList'},
{type:'student',id:arg.id},
]
}
}),