我们会集成以下内容
react-router
redux
antd
axios
pnpm add react-router redux-react @reduxjs/toolkit antd axios
配置路由
- 采用单独配置文件的方式, 在
src
文件夹下创建router
,并在其中index.tsx
中配置
import type { RouteObject } from "react-router-dom";
import { Navigate } from "react-router-dom";
import Home from "../views/Home";
import Login from "../views/Login";
const routes: RouteObject[] = [
{
path: "/",
// 访问根路径重定向到home
element: <Navigate to={"/home"} />,
},
{
path: "/home",
element: <Home />,
},
{
path: "/login",
element: <Login />,
},
];
export default routes;
- 在
App.tsx
使用routes
,使用useRoutes
将routes
导入
用法可参考文档:useRoutes v6.4.1 | React Router
import React from "react";
import { useRoutes } from "react-router-dom";
import routes from "./router";
const App = () => {
return (
<div className="App">
<h1>Hello react</h1>
<div>{useRoutes(routes)}</div>
</div>
);
};
- 在
src
下的index.tsx
中开启路由模式,这里使用的是BrowserRouter
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
// 或者 HashRouter
import App from "./App";
const root = ReactDOM.createRoot(
document.querySelector("#root") as HTMLElement,
);
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
);
- 然后在
App.tsx
添加跳转路由的按钮即可
import React from "react";
import { Link, useRoutes } from "react-router-dom";
import routes from "./router";
const App = () => {
return (
<div className="App">
<h1>Hello react</h1>
<div>{useRoutes(routes)}</div>
<Link to="/home">Home</Link>
<Link to="/login">Login</Link>
</div>
);
};
配置redux
Redux Toolkit
是官方推荐的编写 Redux 逻辑的方法
- 在
store
文件夹下的index.ts
中创建store
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {},
});
// 获取到根的state类型,方便后面使用
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
使用
RTK
提供的configureStore
方法来创建
- 在外界使用我们创建的
store
,也就是App.tsx
中
RTK 提供
Provider
让我们使用创建的store
,这样后面就可以使用了
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import App from "./App";
import store from "./store";
const root = ReactDOM.createRoot(
document.querySelector("#root") as HTMLElement,
);
root.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>,
);
- 为了方便一会测试,这里我们创建一个
reducer
用于储存一个计数器的状态,结构目录如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P9h0yEVI-1666933285137)(E:\code\react\my-admin\README.assets\image-20221028111920964.png)]
mainReducer.ts
内容如下
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "../index";
const initialState = { // 初始状态
count: 0,
};
// createSlice用于创建切片
const mainSlice = createSlice({
name: "main",
initialState,
reducers: {
// 定义reducer
increment: (state) => {
state.count++;
},
decrement: (state) => {
state.count--;
},
},
});
// 将定义的方法解构导出方便外面使用
export const { increment, decrement } = mainSlice.actions;
// 直接将 coute的值取出,调用该方法直接拿到count的值,rootState是 store的状态类型,在上面定义过
export const selectCount = (state: RootState) => state.main.count;
export default mainSlice.reducer;
- 在 store中使用这个reducer
import { configureStore } from "@reduxjs/toolkit";
import mainReducer from "./reducer/mainReducer";
const store = configureStore({
// 创建 store
reducer: {
main: mainReducer,
},
});
// 获取到根的state类型,方便后面使用
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
- 在页面中进行使用
- 通过调用
selectCount
拿到count - 引入count的两个方法
increment
decrement
- 通过调用
import { decrement, increment, selectCount } from "store/reducer/mainReducer";
import { Button } from "antd";
import { useAppDispatch, useAppSelector } from "store/hooks";
import React from "react";
const Home = () => {
const dispatch = useAppDispatch();
return (
<div className="home">
<h1>Home Page</h1>
<h3>{useAppSelector(selectCount)}</h3>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
};
export default Home;
这里用到了 hooks 中的两个方法,是为了增强类型推断,所以就算使用 原来的
useDispatch
,useSelector
也没问题,如下import React from "react"; import { useDispatch, useSelector } from "react-redux"; import { decrement, increment, selectCount } from "store/reducer/mainReducer"; const Home = () => { // 用于创建一个dispatch函数 const dispatch = useDispatch(); return ( <HomeWrap> <h1>Home Page</h1> <h3>{useSelector(selectCount)}</h3> <button onClick={() => dispatch(decrement())}>-</button> <button onClick={() => dispatch(increment())}>+</button> </HomeWrap> ); }; export default Home;
hooks.ts
内容如下
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from "./index";
//自定义hook,调用该方法返回一个 dispatch,用于派发action
const useAppDispatch = () => useDispatch<AppDispatch>();
// 为rootstate,创建有更好的类型校验的 useSelector,之后用该方法代替 useSelector就好
const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export { useAppDispatch, useAppSelector };
引入antd
antd的引入就相对于简单些,安装完依赖之后在src
下的index.tsx
引入antd/dist/antd.css
即可,而antd
默认js
部分就支持按需引入,所以在哪要使用组件直接引入就可以了
pnpm add antd
我们在上面的Home
中两个按钮使用 antd
提供的作为测试
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { Button } from "antd";
import { decrement, increment, selectCount } from "store/reducer/mainReducer";
const Home = () => {
const dispatch = useDispatch();
return (
<HomeWrap>
<h1>Home Page</h1>
<Counter>{useSelector(selectCount)}</Counter>
<Button onClick={() => dispatch(decrement())}>-</Button>
<Button onClick={() => dispatch(increment())}>+</Button>
</HomeWrap>
);
};
export default Home;
集成axios
- 安装依赖
pnpm add axios
# 因为是ts 所以我们还需要安装类型声明
pnpm add -D @types/axios
- 对
axios
进行二次封装,结构目录如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QRBvmozF-1666933285139)(E:\code\react\my-admin\README.assets\image-20221028124536434.png)]
在request
中封装一个 Request
类,用于项目中的网络请求
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { config } from "./config";
class Request {
instance: AxiosInstance;
constructor(config: AxiosRequestConfig) {
// 重新创建一个 axios实例
this.instance = axios.create(config);
}
async request(config: AxiosRequestConfig) {
return this.instance.request(config);
}
post(config: AxiosRequestConfig) {
return this.request({ ...config, method: "post" });
}
get(config: AxiosRequestConfig) {
return this.request({
...config,
method: "get",
});
}
}
export default new Request(config);
// config.ts 内容
const config = {
// 这里因为跨域了,所以写/api,关于跨域的解决后面说
baseURL: "/api",
timeout: 3000,
};
export { config };
这里先只是最简单的请求方法,关于拦截器,后面用到了再加上
- 测试:在
api
下创建login.ts
模块作为测试,内容如下
import request from "../request";
const login = async () => {
return await request.post({
url: "admin/login",
data: {
username: "admin",
password: "admin",
},
});
};
export default login;
- 在App中发送请求
import React from "react";
import { Link, useRoutes } from "react-router-dom";
import routes from "./router";
import { login } from "./service";
const fn = () => {
login().then((res) => {
console.log(res);
});
};
const App = () => {
return (
<div className="App">
<Title>Hello react</Title>
<button onClick={fn}>发送请求</button>
<Wrap>{useRoutes(routes)}</Wrap>
<Link to="/home">
<Button>Home</Button>
</Link>
<Link to="/login">
<Button>Login</Button>
</Link>
</div>
);
};
export default App;
测试
-
启动项目
-
点击切换路由到login
3. 在home中点击 antd 按钮
4 .发送请求
结束语
到现在就大概完成了所有的配置,之后就正式开始后面的内容,嗷还有一个关于create-react-app
创建的项目跨域问题,解决如下
- 安装中间件:
http-proxy-middleware
- 在***
src
***文件夹下创建文件setupProxy.js
一定要在 src下,我放根目录,找半天不知道为啥不生效;不用再任何地方引入,启服务时自己会加载
官网有说:跨域的解决
- 内容如下
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = (app) => {
app.use(
createProxyMiddleware("/api", {
target: "http://ceshi13.dishait.cn/",
changeOrigin: true,
pathRewrite: {
"^/api": "/",
},
}),
);
};
访问 以
/api
开头的路径时,请求target
的url
,这也是为什么前面axios
配置中写/api
的原因,pathRewrite
的作用将/api
从真实的路径中去除,以免影响;当然后端真是/api
开头的那当我没说
后面我会使用
styled-components
来进行组件样式的编写,可以先安装一下依赖pnpm add styled-components pnpm add @types/styled-components -D