从0开始搭建 React


从0开始搭建 React

原创 已于 2025-08-24 02:47:29 修改 · 粉丝可见 · 108 阅读 · 0 · 0 GEO检测 · 编辑
文章链接:https://blog.csdn.net/hacker_51/article/details/149503222

一、构建项目+插件安装

创建项目

1
pnpm create vite@latest projectName -- --template react

安装react的路由react-router-dom

1
pnpm add react-router-dom

安装发送请求的axios

1
pnpm add axios

安装状态管理的react-redux

1
pnpm add react-redux redux-persist @reduxjs/toolkit

二、设置项目目录结构

Api:接口文件
Components:相关组件文件
router:路由文件
static:静态资源文件
Store:react-redux状态管理文件
utils:各工具类文件
Views:页面视图层文件

三、路由配置

配置按需加载(打包切片)

  1. 目的意义:
    单页应用只有一个html,一个主要的css,js 传统打包是全打包成一个文件后引入,用户每次浏览都需要将整个项目都拉下来,因此引入按需加载方式

  2. 简单高效的配置:
    通过webpack的import模块来实现按需加载

使用React.lazy搭配React.Suspense函数达到组件按需加载的效果,还可以优化加载速度。

router/BasicRoute.tsx   路由主配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// router/BasicRoute.tsx
import { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const App = lazy(() => import('../App'));
const Study = lazy(() => import('../view/study/study'));

const BasicRoute = () => (
<BrowserRouter>
<Suspense fallback={
<div>Loading…</div>
}>
<Routes>
<Route path='/app' element={<App />} />

<Route path='/' element={<Study />} />

</Routes>
</Suspense>
</BrowserRouter>
);

export default BasicRoute;

在main.tsx中 引用路由配置

1
2
3
4
5
6
7
8
// main.tsx 整个项目的入口

import { createRoot } from 'react-dom/client'

import BasicRoute from './router/BasicRoute'
createRoot(document.getElementById('root')!).render(
<BasicRoute />
)

子路由配置

示例配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Layout } from '@douyinfe/semi-ui';
import { Outlet, useNavigate } from 'react-router-dom';
import Sider from './module/Sider';
import Header from './module/Header';
import { isLogin } from '../../utils/commonUtils';
import { useEffect } from 'react';
import styles from './layout.module.scss';
const { Content } = Layout;

const Lay = () => {
const navigate = useNavigate();
useEffect(() => {
if(!isLogin()){
navigate('/login', {
replace: true
});
}
}, []);
return (
<Layout style={{ minHeight: '100vh', flexDirection: 'row' }}>
<Sider />
<Layout style={{ flex: 1, flexDirection: 'column', height: '100vh' }}>
<Header />
{/* 子路由内容 */}
<Content className={styles.content}>
<div className={styles.contentBox}>
<Outlet />
</div>
</Content>
</Layout>
</Layout>
);
};
export default Lay;

四、axios 封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import axios,{type AxiosResponse} from 'axios'
import { customToast } from './commonUtils'
import { getUserInfo } from '../store/user/userSelect'
import { getEnvConfig } from './commonUtils';
const envConfig = getEnvConfig();
const baseURL = envConfig.VITE_API_URL;
const req = axios.create({
// TODO 1. 基础地址,超时时间
baseURL,
timeout: 10 * 1000,
withCredentials: false
})

// 请求拦截器
req.interceptors.request.use(
(config) => {
// 添加自定义请求头
// TODO 2. 携带token
// 直接从 store 获取 token,而不使用 useSelector hook

const token = getUserInfo().token;

if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(err) => Promise.reject(err)
)

interface Result<T> {
status: number;
data: T;
description: string;
}

// 响应拦截器
req.interceptors.response.use(
(res:AxiosResponse<Result<any>>) => {
// TODO 3. 处理业务失败
// TODO 4. 摘取核心响应数据
if (res.status >= 200 && res.status < 300) {
return res;
}
// 处理业务失败,给错误提示,抛出错误
customToast({
msg: res.data.description || '未知错误,请尝试重新操作 或 联系管理员!',
type: 'error',
})
return res
},
(err) => {
console.dir(err);
let msg = err.message === 'Request failed with status code 429' ? '请求次数过多,请稍后再试!' : err.message;
msg = err.response?.data?.message || msg;
customToast({
msg: msg,
type: 'error',
})
// TODO 5. 处理401错误
return Promise.reject(err)
}
)


export { baseURL}
export default req;

五、状态管理

示例参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 导入创建切片的函数
import { createSlice } from "@reduxjs/toolkit";

// 定义初始状态
const initialState = {
user: {
id: null as number | null,
username: null as string | null,
token: null as string | null,
avatar: null as string | null,
roles: null as string[] | null,
phoneNumber: null as string | null,
wechatOpenId: null as string | null,
email: null as string | null,
},
};


const userSlice = createSlice({

name: "userStore",

initialState,

// 定义 reducers 并生成关联操作 Action 的函数
reducers: {
setUserInfo(state, action) {
state.user = action.payload;
},
clearUserInfo(state) {
state.user = {
id: null,
username: null,
token: null,
avatar: null,
roles: null,
phoneNumber: null,
wechatOpenId: null,
email: null,
};
},
},
});


// 导出操作函数
export const {
setUserInfo,
clearUserInfo
} = userSlice.actions;

// 导出 reducer 处理器
export default userSlice.reducer;

持久化配置参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// src/store/index.ts
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import musicSlice from './music/musicSlice';
import storage from "redux-persist/lib/storage";
import { persistStore, persistReducer } from 'redux-persist';
import {
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist';

// 持久化配置
const persistConfig = {
key: 'root',
storage,
};

// 创建根reducer
const rootReducer = combineReducers({
musicStore: musicSlice
});

// 使用 persistReducer 包装整个根reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);

// 导出 store
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});

export type RootState = ReturnType<typeof store.getState>;
// 创建持久化 store
export const persistor = persistStore(store);

console.log('状态:',JSON.parse(localStorage.getItem('persist:root') || '{}'))

启动状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import BasicRoute from './router/BasicRoute'
import { Provider } from 'react-redux';
import {store} from './store/index'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<BasicRoute />
</Provider>
</StrictMode>,
)

觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭

微信二维码

wechat pay

支付宝二维码

ali pay

从0开始搭建 React
http://blog.angindem.cn/2025/08/24/Angindem-CSDN博客/188_188/
作者
Angindem
发布于
2025年8月24日
许可协议