@reduxjs/toolkit
对 redux
封装了很多实用 api
,比如 createSlice
, 创建一个切片,既可以避免写很多的样板代码(代价:学习
/时间
)。其中 Toolkit
对数据请求进行了支持。
之前
redux-thunk
将函数作为action
派发,是redux
中使用作为广泛和简单的异步数据请求方案。(目前,toolkit
中内置thunk
功能)redux-saga
基于Generator
生成器,支持复杂的异步请求,学习起来相对redux-thunk
难度大- ...
下面使用 express 为后端提供 api 数据,React/Vite 作为前端,来使用 Redux 的工具箱。
安装 @reduxjs/toolkit
npm install @reduxjs/toolkit
使用 vite 初始化一个 React + TS 项目
pnpm create vite rtk-query # 选择 react/ts
前端依赖
pnpm install redux react-redux @reduxjs/toolkit qs
qs 将对象, post 表单数据数据序列变化
setNoStore: build.mutation({
query(amount) {
return {
url: "set-nostore",
method: "POST",
body: qs.stringify(amount),
};
},
}),
后端依赖
pnpm install express nodemon esno cors @types/node
- cors 处理跨域问题
- nodemon 监听文件自动重启 express 服务
- esno 运行 ts 文件
nodemon.json 简单配置
{
"verbose": false,
"debug": false,
"exec": "esno ./src/index.ts", // 重新执行文件
"ignore": [
"node_modules",
"./test",
"**/*.d.ts",
"*.test.ts",
"*.spec.ts",
"fixtures/*",
"test/**/*",
],
"events": {
"restart": ""
},
"watch": ["./server"],
"ext": "ts tsx js jsx",
"inspect": true
}
package.json 添加 server 启动
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"server": "nodemon"
},
express 服务
import path from "path";
import express from "express";
import cors from "cors";
const app = express();
app.use(
"/static",
express.static(path.join(__dirname, "../static"), {
index: false,
})
);
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.listen(9898, () => {
console.info(`===> api server is running at http://localhost:9898`);
});
- 设置静态文件 static 目录
- 设置接受跨域请求
- 设置 urlencoded 的解析
- ...
后续需要测试的接口就可以以中间的形式添加在 express 服务内部.
一个简单的测试中间件
app.get("/", (_, res) => {
res.send({ code: 1, data: { app: "express server " } });
});
如何使用 redux toolkit 的 query
- 不依赖 store
- 与 store 同行
在没有 store 情况下是使用 rtk query
mkdir src/server && cd src/server
touch nostore.ts
app.get("/query-nostore", (_, res) => {
res.send({
code: 0,
data: {
tip: 'test data',
},
msg: "success",
});
})
rtk-query 前置知识
如果熟悉 graphql 的 api, grapgql 将请求主要抽象为:
- query 查询
- muation 变化
rtk-query 也采用了类似的方式方式,构建 api
构建 api
api
需要 baseQuery
参数,@reduxjs/toolkit
默认提供基于 fetch
的 fetchBaseQuery
(当然也可以自定义的 baseQuery
, 官方文档中,提供了 axios/graphql 的两种):
import {
createApi,
fetchBaseQuery,
} from "@reduxjs/toolkit/query/react";
import qs from 'qs';
export const api = createApi({
baseQuery: fetchBaseQuery({ // fetchBaseQuery
baseUrl: "http://localhost:9899",
prepareHeaders: (headers, api) => {
headers.set(
"Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8"
);
return headers;
},
}),
endpoints: () => ({}),
});
// 分离 api
const noStoreApi = api.injectEndpoints({
endpoints: (build) => ({
getNoStore: build.query({
query: () => "query-nostore",
}),
setNoStore: build.mutation({
query(amount) {
return {
url: "set-nostore",
method: "POST",
body: qs.stringify(amount), // const a = qs.stringify({a: 1, b: 2}); // a=1&b=2
};
},
}),
}),
});
export const { useGetNoStoreQuery, useSetNoStoreMutation } = noStoreApi;
- useGetNoStoreQuery 等价于 api.endpoints.getNoStore.useQuery
使用 ApiProvider 让 hook 调用生效
import { api } from "./server/nostore";
import { ApiProvider } from "@reduxjs/toolkit/query/react";
ReactDOM.createRoot(document.getElementById("root")!).render(
<ApiProvider api={api}>
<App />
</ApiProvider>
);
前端 hooks 使用实例
import { useGetNoStoreQuery, useSetNoStoreMutation } from './server/nostore'
function App() {
const {data, error} = useGetNoStoreQuery("")
const [setNoStore] = useSetNoStoreMutation();
const onSubmit = (e) => {
e.preventDefault();
const { target } = e
setNoStore({
a: target[0].value,
b: target[1].value,
})
}
if(error) {
return <>error</>
}
return (
<div className="App">
{
JSON.stringify(data)
}
<div>
<form name='f' onSubmit={onSubmit} encType="application/x-www-form-urlencoded">
<input name="username" type="text" />
<br/>
<input name="password" type="text" />
<br />
<button type="submit">提交</button>
</form>
</div>
</div>
)
}
export default App
express 后端接口
app.use(express.urlencoded({ extended: false, }));
app.get("/query-nostore", (req, res) => {
res.send({
code: 0,
data: {
tip: 'test data',
},
msg: "success",
});
})
app.post("/set-nostore", (req, res) => {
res.send({
code: 0,
data: {
tip: 'test data',
...req.body
},
msg: "success",
});
})
- 总之, rtk-query 解决的是数据请求的问题,与 redux 专门数据管理还是不一样。
- 到这里,打通了前端后的基本使用。使用 express 作为后端服务(有跨域),提供了 get/post 两种接口的基本解析方案。
- 前端实例化 redux store 情况下使用 ApiProvider 来使得钩子函数生效, 拆分 api 的方法。
- rtk-query 默认使用 fetch 来发生请求。
- ...
与 class 组件配合
与 function 组件有所有不同,class 组件不能直接使用钩子函数获取数据,获取数据需要初始化 store, 使用 connect 函数,state/dispatch, map 为 Props, 核心方法是:
- initiate 方法
onst mapState = (state: RootState) => ({
users: api.endpoints.getUsers.select(3)(state)
});
const mapDispatch = {
getUsers: api.endpoints.getUsers.initiate,
updateUser: api.endpoints.updateUser.initiate
};
函数式编程更加推荐,因为可以直接使用,不用下载 axios 等请求工具,且能能够方便的管理 api。
后面的探索
- 与 toolkit 其他的相互配合
- 使用 store 实例
- token 存储机制
参考
- Redux 官方网站 redux.js.org/
- Redux 仓库地址 github.com/reduxjs/red…
- Redux Toolkit 官方网站 redux-toolkit.js.org/rtk-query/o…
- Redux Toolkit 仓库地址 github.com/reduxjs/red…
- Redux Thunk 仓库地址 github.com/reduxjs/red…
- Redux Saga 官方网站 redux-saga.js.org/
- Redux Sage 仓库地址 github.com/redux-saga/…
- qs 序列化 www.npmjs.com/package/qs