写在前面
最近在开发新项目,有机会从0开始配置环境,现把流程记录下来。
本文涵盖从创建 React 项目、配置依赖包、配置调试环境等内容,重在快速搭建标准 React 项目工程,希望能帮到大家。
本项目各依赖包版本:
"@reduxjs/toolkit": "^1.9.2",
"antd": "^5.1.7",
"axios": "^1.3.0",
"craco-less": "2.1.0-alpha.0",
"normalize.css": "^8.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.0",
"styled-components": "^5.3.6",
...
初始化项目以及项目目录结构设计
执行命令npx create-react-app [项目名称]
来创建一个新项目,执行yarn start (npm start)
启动该项目,之后删除项目中用不到的文件或者代码。
在 public 文件夹中保留index.html
和favicon.ico
。在 src 文件夹中保留index.js
。改写 App.js 为 memo 包裹项目。
目前精简后的项目结构:
新建文件夹并如图命名,用以管理资源,顺手创建两个页面 Login 和 Home,用以后续测试:
配置 开发引导文件
在根目录中创建jsconfig.json
文件,实现更好的代码提示和组件索引:
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"jsx": "preserve",
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
配置 Craco
执行命令yarn add @craco/craco@alpha -D
,在开发依赖环境下安装 craco ,用以覆写 config 文件内容。
在根目录中创建craco.config.js
文件并如下配置:
const path = require("path")
const CracoLessPlugin = require("craco-less")
const resolve = pathname => path.resolve(__dirname, pathname)
module.exports = {
//less
plugins: [
{
plugin: CracoLessPlugin,
}
],
//webpack
webpack: {
alias: {
"@": resolve("src"),
"components": resolve("src/components"),
"utils": resolve("src/utils")
}
}
}
将package.json
文件中的 script 里start/build/test
的react-scripts
启动更改为 craco 启动:
配置 Less
执行命令yarn add craco-less@2.1.0-alpha.0
安装 Less。
由于之前已经在 craco 中配置过 Less 的相关插件,建议在 css 中创建一个 Less 并引用之后进行测试一下,保证 Less配置的正确运行(webpack 相关的配置修改之后记得重启项目)。
配置 全局css
执行命令yarn add normalize.css
,并在 src/index.js 中添加import "normalize.css"
。
在 assets/css 中创建variables.less
文件:
@textColor: #222;
@textColorSecondary: #333;
在 assets/css 中创建reset.less
文件:
@import "./variables.less";
body, button, dd, dl, dt, form, h1, h2, h3, h4, h5, h6, hr, input, li, ol, p, pre, td, textarea, th, ul {
margin: 0;
padding: 0;
}
a {
color: @textColor;
text-decoration: none;
}
img {
vertical-align: top;
}
ul, li {
list-style: none;
}
在 assets/css 中创建index.less
文件:
@import './reset.less';
body {
font-size: 20px;
}
在index.js
中引入全局样式:
对于各个组件的样式,使用styled-components
库实现css-in-js
的编写方式。
执行命令yarn add styled-components
安装styled-components
库
未来会在各模块入口文件夹中创建文件style.js
,代码如下:
import styled from "styled-components";
export const TestAWrapper = styled.div`
height: 300px;
`
配置 Ant Design
执行命令yarn add antd
其他配置等项目要用了再写,比如语言国际化,修改默认主题等。
配置 Router
执行命令yarn add react-router-dom
在 src/index.js 中添加import { HashRouter } from "react-router-dom"
并包裹APP组件
在 src/router 中创建index.js
并添加代码:
import { Navigate } from "react-router-dom";
import { lazy } from "react";
const Home = lazy(() => import("@/views/Home"));
const Login = lazy(() => import("@/views/Login"));
const routes = [
{
path: "/",
element: <Navigate to="/login" />,
},
{
path: "/home",
element: <Home />,
},
{
path: "/login",
element: <Login />,
},
];
export default routes;
在 App 中添加代码:
执行yarn start
启动项目,输入对应的路由地址,可以正常显示对应的页面了。
login页: http://localhost:3000/#/login
home页: http://localhost:3000/#/home
配置 Redux 和 Redux toolkit
执行命令yarn add @reduxjs/toolkit react-redux
在 store 中创建 login.js:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { getUserInfo } from '@/services'
export const fetchUserInfo = createAsyncThunk("fetchUserInfo", async payload => {
const res = await getUserInfo(payload)
return res.user
})
const LoginSlice = createSlice({
name: 'testa',
initialState: {
userInfo: {}
},
reducers: {
changeUserInfo(state, { payload }) {
state.userInfo = payload
}
},
extraReducers: builder => {
builder.addCase(fetchUserInfo.fulfilled, (state, { payload }) => {
//request success todo
state.userInfo = payload
})
.addCase(fetchUserInfo.rejected, () => {
//request failed todo
console.log("request rejected")
})
}
})
export const { changeUserInfo } = LoginSlice.actions;
export default LoginSlice.reducer;
在 store 中创建 index.js:
import LoginReducer from "./login";
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {
login: LoginReducer,
},
});
export default store;
配置 Axios
执行命令yarn add axios
在 src/services 中创建 request 文件,配置拦截器
import axios from "axios";
const BASE_URL = "xxxx"
const TIMEOUT = 10000
class Request {
constructor(baseURL, timeout) {
this.instance = axios.create({
baseURL,
timeout,
});
// TODO:请求拦截器
// 响应拦截器
this.instance.interceptors.response.use(
(res) => {
// 请求成功判断
if (res.data.code === 200) return res.data.data;
else {
console.log("request error");
throw new Error(res.message);
}
},
(err) => {
//error todo
console.log("network error");
throw new Error(err);
}
);
}
request(config) {
return this.instance.request(config);
}
get(config) {
return this.request({
...config,
method: "get",
});
}
post(config) {
return this.request({
...config,
method: "post",
});
}
}
const request = new Request(BASE_URL, TIMEOUT);
export default request;
在 src/services 中创建 login 模块
import request from "./request";
import { stringify } from "qs";
export function getUserInfo(payload) {
return request.get({
url: `/auth/user/login?${stringify(payload)}`
})
}
页面开发测试
改写 login
import { Button, Input } from "antd";
import React, { memo } from "react";
const Login = memo(() => {
return (
<div>
<Input placeholder="账号" />
<br />
<Input.Password placeholder="密码" />
<br />
<Button type="primary">登录</Button>
</div>
);
});
export default Login;
改写home
import React, { memo } from "react";
import { Button } from "antd";
const Home = memo(() => {
return (
<div>
<h1>Home Page</h1>
<Button type="primary">返回</Button>
</div>
);
});
export default Home;
在React组件中实现页面路由跳转