React-RTKQ及请求数据笔记---小黑是小白

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},
		]
	}
}),
  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值