npx create-react-app 项目名

没用的就删掉


redux可以应用于各个平台例如 js、nodejs

默认情况下我们做一个增加减少的代码

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <button id="sub">减少</button>
    <div></div>
    <button id="add">增加</button>
    <script>
      // 普通写法
      const sub = document.getElementById('sub')
      const add = document.getElementById('add')
      let count = 1
      let countDom = document.querySelector('div')
      countDom.innerHTML = count
      console.log(sub);
      sub.addEventListener('click',()=>{
        count--
        countDom.innerHTML = count
      })

      add.addEventListener('click',()=>{
        count++
        countDom.innerHTML = count
      })
    </script>
  </body>
</html>

每次都需要修改一下页面进行更新

二、使用reduce

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style> 
    body {
        display: flex;
    }
</style>
<body>
    <div id="username"></div>
    <button id="sub">减少</button>
    <div id="countDom">1</div>
    <button id="add">增加</button>
    <script src="https://unpkg.com/redux@4.2.0/dist/redux.js"></script>
    <script>
        // 1. 引入redux
        // 2. 创建redux
        const reducer = (state,action)=>{
            switch(action.type){
                case 'add':
                     return {...state,count:state.count+1}
                break;

                case 'reduce':
                    return {...state,count:state.count-1}
                break;
                default: 
                  return state
            }
        }
            
        // 3.通过redux对象创建store
        const store = Redux.createStore(reducer,{count:1,username:'奥特曼'})

        username.innerText = store.getState().username

        // 4. 对store中的state进行订阅 订阅:值发送变化就会触发
        store.subscribe(()=>{
            countDom.innerText = store.getState().count;
            username.innerText = store.getState().username
        });

        // 5.通过dispatch派发state的操作指令
        sub.addEventListener('click',()=>{
            // 派发
            store.dispatch({type:'reduce'})
        })

        add.addEventListener('click',()=>{
            // 派发
            store.dispatch({type:'add'})
        })
    </script>
</body>
</html>

不直接使用redux的原因

1.如果state过于复杂,将会非常难以维护

- 可以通过对state分组来解决这个问题,创建多个reducer,然后将其合并为一个

2.state每次操作时,都需要对state进行复制,然后再去修改

3.case后边的常量维护起来会比较麻烦

三、使用RTK

一、安装redux

在React中使用RTK
安装,无论是RTK还是Redux,在React中使用时react-redux都是必不可少,所以使用RTK依然需要安装两个包:react-redux和@reduxjs/toolkit。

npm

npm install react-redux @reduxjs/toolkit -S

yarn

yarn add react-redux @reduxjs/toolkit

二、创建store/index

// 1. 引入切片 
import { configureStore, createSlice } from '@reduxjs/toolkit'


// 2. 创建切片
const sliceStu = createSlice({
  // 1.名称
  name:'stu',
  // 2.初始值
  initialState: {
    name: '我是一个无聊的学生',
    desc: '敲着无聊的代码',
    hobby:'就喜欢干着无聊的事'
  },
  // 3. reduce的具体方法  参数都是函数
  reducers: {
    // 设置爱好
    setHobby(state,action) {
      state.hobby = action.payload
    }
  }
})


// 切片会根据我们对象中的reducer方法来自动创建actions对象
const { setHobby } = sliceStu.actions
// 4. 导出方法提供外部使用
export {setHobby}


// 3. 切片要放在configureStore中  sliceStu中的reducer是自动生成的
const store = configureStore({
  reducer: {
    student:sliceStu.reducer
  }
})

export default store

三、index.js中引入Provide 和store

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import  store  from './store/index'
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store} >
    <App />
  </Provider>
);

四、组件中使用

通过 useSelector(state=>state.configureStore的名称) 可以拿到切片的数据

通过useDispatch 引入后 在继续引入store的方法

useDispatch(方法(参数))

import { useSelector,useDispatch } from "react-redux";
import {setHobby} from './store'
function App() {
  const stuStore =  useSelector(state=>state.student)
  const dispatch =  useDispatch();
  console.log(stuStore);
  const editHobby =()=> {
    console.log(stuStore);
    dispatch(setHobby('hahhah'))
  }
  return (
    <div className="App">
        <p>{stuStore.name}</p>
        <p>{stuStore.desc}</p>
        <p>{stuStore.hobby}</p>
        <button onClick={()=>editHobby()}>修改成我的爱好</button>
    </div>  
  );
}

export default App;

四、RTKQ

创建store/studentApi.js 也可能放在 api/studentApi.js

// https://cn.redux.js.org/tutorials/essentials/part-7-rtk-query-basics

// 从特定于 React 的入口点导入 RTK Query 方法
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

// 定义我们的单个 API Slice 对象
export const studentApi = createApi({
  // 缓存减速器预计将添加到 `state.api` (已经默认 - 这是可选的)
  reducerPath: 'studentApi', // Api表示,不能喝其他的api或reducer重复
  // 我们所有的请求都有以 “/fakeApi” 开头的 URL
  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/' }),
  // “endpoints” 代表对该服务器的操作和请求
  endpoints: builder => ({
    // `getPosts` endpoint 是一个返回数据的 “Query” 操作
    getStudents: builder.query({
      // 请求的 URL 是“/fakeApi/posts”
      query: () => 'students'
    })
  })
})

// 为 `getPosts` Query endpoint 导出自动生成的 hooks
// 名字是根据上面endpoints中定义的方法添加 use……Query
export const { useGetStudentsQuery } = studentApi   

export default studentApi


index.js

import React from 'react';
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>
);


App.js

import React from 'react'
import { useGetStudentsQuery } from './store/studentApi'    // 1.导入api
export default function App() {
    const res = useGetStudentsQuery()   // 2.调用api
    console.log(res);
  return (
    <div>  </div>
  )
}

这样就可以定义一个获取请求的接口了

如果想要对数据进行更新或添加 就不能使用query了

最终版

studentApi.js

// https://cn.redux.js.org/tutorials/essentials/part-7-rtk-query-basics

// 从特定于 React 的入口点导入 RTK Query 方法
import { addListener } from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

// 定义我们的单个 API Slice 对象
export const studentApi = createApi({
  // 缓存减速器预计将添加到 `state.api` (已经默认 - 这是可选的)
  reducerPath: 'studentApi', // Api表示,不能喝其他的api或reducer重复
  // 我们所有的请求都有以 “/fakeApi” 开头的 URL
  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:1337/api/' }),
  // “endpoints” 代表对该服务器的操作和请求
  endpoints: builder => ({
    // `getPosts` endpoint 是一个返回数据的 “Query” 操作
    getStudents: builder.query({
      // 请求的 URL 是“/fakeApi/posts”
      query: () => 'students',
      // 过滤数据 只返回结果中的data
      transformResponse(baseQueryReturnValue, meta, arg) {
        return baseQueryReturnValue.data;
      },
      keepUnusedDataFor: 0,
      providesTags: [{type:'Student',id:'LIST'}] // 和invalidatesTags匹配  
    }),
    // 查询学生接口
    getStudentById: builder.query({
      query: (id) => 'students/'+id,
      transformResponse(baseQueryReturnValue, meta, arg) {
        return baseQueryReturnValue.data;
      },
      keepUnusedDataFor:1000, // 默认60s  也就是在60s之内请求接口会直接返回缓存数据 0表示不缓存
      providesTags: (result,error,query)=>[{type:'Student',id:query.id}]
    }),
    // 删除学生接口
    delStudentById: builder.mutation({
      query: (id) => {
        return {
          url: 'students/'+id,
          method: 'DELETE'
        }
      },
      invalidatesTags: [{type:'Student',id:'LIST'}]
    }),
    // 更新学生接口
    updateStudentById: builder.mutation({
      query: (stu) => {
        return {
          url: 'students/'+stu.id,
          method: 'PUT',
          body: {
            data:stu.attributes
          }
        }
      },
      invalidatesTags: (result,error,query)=>[{type:'Student',id:query.id},{type:'Student',id:'LIST'}]
    }),
      // 添加学生接口
    addStudent: builder.mutation({
      query: (stu) => {
        return {
          url: 'students',
          method: 'POST',
          body: {
            data:stu.attributes
          }
        }
      },
      invalidatesTags: [{type:'Student',id:'LIST'}]
    })
  })
})

// 为 `getPosts` Query endpoint 导出自动生成的 hooks
// 名字是根据上面endpoints中定义的方法添加 use……Query
export const { useGetStudentsQuery, useGetStudentByIdQuery, useDelStudentByIdMutation,useUpdateStudentByIdMutation,useAddStudentMutation } = studentApi   

export default studentApi

功能点:

对应更新

如果是增加或修改 导出格式为 use...Mutation 由于增加完之后列表并不会更新 所以要调用

invalidatesTags:[{type:'Student',id:'LIST'}] 触发  providesTags: [{type:'Student',id:'LIST'}]

也就是定义在addStudent上的接口 调用玩 会触发 getStudents的接口

当然 id为值的也一一对应有id的 例如更新某一个 获取 最新的某一个

keepUnusedDataFor

默认为60s 如果在60s中再次触发 不会调用 还会用之前的数据 可以修改成0 也就是每次都进行更新


store/index.js

import { configureStore } from "@reduxjs/toolkit";  // 1.导入配置store的方法
import studentApi from './studentApi'   // 2.导入api
import { setupListeners } from "@reduxjs/toolkit/dist/query"; //3.引入监听器

const store = configureStore({  // 3.配置store
    reducer: {  // 4.配置reducer
        [studentApi.reducerPath]: studentApi.reducer    // 5.配置api
    },
    middleware: getDefaultMiddleware => getDefaultMiddleware().concat(studentApi.middleware)    // 6.配置中间件
})

setupListeners(store.dispatch) // 7.配置监听器 设置以后将会支持 refetchOnFocus\refetchOnReconnect\refetchOnMountOrArgChange\refetchInterval

export default store    // 8.导出store


index.js

import React from 'react';
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>
);

App.js

import React from 'react'
import './app.css'
import { useGetStudentsQuery } from './store/studentApi'    // 1.导入api
import StudentList from './components/StudentList/StudentList'
export default function App() {
  const { data, isLoading, isSuccess, refetch } = useGetStudentsQuery(null, {
    // 对数据进行一次过滤 或者其他操作 
    // selectFromResult: result => {
    //   if (result.data) { 
    //      result.data = result.data.filter(item=>item.attributes.age<10)
    //   }
    //   return result
    //   }
    // pollingInterval: 1000, //轮训 间隔多久自动请求一次数据
    skip: false, // 是否跳过请求 使用场景: 有时候我们需要根据条件来决定是否请求数据
    refetchOnMountOrArgChange: false, // 是否在组件挂载或者参数变化时自动请求数据  true每次参数变化都会请求数据 false 正常使用缓存 数字 代表参数变化后多少秒后请求数据
    refetchOnFocus: false, // 是否在页面获取焦点时自动请求数据
    refetchOnReconnect: true, // 是否在网络重新连接时自动请求数据  
    refetchInterval: true, // 是否在指定的时间间隔内自动请求数据
    })    

    /**
      currentData:当前最新的数据 例如参数是1更换成2后 currentData会先变成undefined 在变成响应回来的数据
      data:响应回来的数据 不考虑参数的变化 直接从1的结果变成2的结果 
      isError: 是否出错,有错误时才会存在
      isFetching: 布尔值 是否正在请求
      isLoading: 布尔值 是否正在请求 数据是否第一次加载
      isSuccess: 布尔值 是否请求成功
      isuninitialized: 布尔值 是否初始化 一般不会用到 请求是否还没有开始发送
      refetch:  重新请求数据的方法
      remove:  删除数据的方法
      reset: 重置数据的方法
      status: 请求的状态 有5种 pending fulfilled rejected idle uninitialize
      error: 错误信息
     */

    console.log(data);
  return (
    <div>
      <button onClick={()=>refetch()}>刷新</button>
      {isLoading && '加载中'}
      {isSuccess && 
        <StudentList students={data} />
      }
    </div>
  )
}

使用useGetStudentsQuery 或 useGetStudentsMutation 时 可以为第二个参数 获取参数和调用方法

一些监听的数据 refetchOnMountOrArgChange 必须在 store/index.js中引入监听器


StudentList.js

import React from "react";
import Student from "../Student/Student";
import StudentForm from '../StudentForm/StudentForm'



export default function StudentList(props) {
  return (
    // 生成一个 3 * 4列的表格 无边距表格
    <table
      border={1}
      cellPadding={0}
      cellSpacing={0}
      style={{ textAlign: "center", width: "200px" }}
    >
      <thead>
        <tr>
          <th>姓名</th>
          <th>年龄</th>
          <th>性别</th>
          <th>地址</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        {props.students.map((item, index) => (
          <Student stu={item.attributes} id={item.id}  key={item.id}  />
        ))}
        <StudentForm />
      </tbody>
    </table>
  );
}

Student.js

import React,{useState} from "react";
import StudentForm from "../StudentForm/StudentForm";
import { useDelStudentByIdMutation } from "../../store/studentApi";
// import useFetch from "../../hooks/useFetch";
export default function Student({ stu: { name, age, gender, address },id }) {

  const [isEdit, setIsEdit] = useState(false)

  const [delStudent,result ] = useDelStudentByIdMutation()

  const delStudentHanlder = () => {
    delStudent(id)
    // 如何更新最新的数据呢
  }
 
  return (
    <>
      { isEdit? <StudentForm stu={{name, age, gender, address }} id={id}  cancelEdit={()=>setIsEdit(false)} /> : <tr>
        <td>{name}</td>
        <td>{age}</td>
        <td>{gender}</td>
        <td>{address}</td>
        <td>
          <button onClick={()=>setIsEdit(true)}>修改</button>
          <button onClick={delStudentHanlder}>删除</button>
        </td>
      </tr>}
    </>
  );
}

因为像删除、添加、修改 都是我们进行点击完成的并不是 一开始进行调用 所以为mutation的格式 调用这个函数 可以结构出 [ 方法,方法调用信息对象 ]

方法:接口的调用

方法调用信息对象:存在着当前接口是否被调用及loading或success