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