前言
记录一下搭建React项目的流程
create-react-app
bash
npm i -g create-react-app
npx create-react-app my-app
cd my-app
npm start
这样一个基本的项目就跑起来了
CSS预处理
React项目默认支持Sass,所以直接安装相关依赖即可
bash
npm i -D node-sass sass-loader
对于Less,则要修改webpack配置,这样就引申出两种方向的做法
1、显示隐藏的配置文件,直接修改
bash
npm run eject
命令执行后,根目录下多了config文件夹,然后需要修改里面的webpack.config.js
将内置的Sass配置复制一份改成Less
2、使用craco、间接修改
bash
npm i -D @craco/craco less carco-less @babel/plugin-proposal-decorators
根目录下新建craco.config.js
js
const CracoLessPlugin = require("craco-less");
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { "@primary-color": "#3296fa" },
javascriptEnabled: true
}
}
}
}
],
babel: {
plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]]
}
};
修改package.json相关命令
json
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
路径别名、端口号
js
craco.config.js
const path = require('path');
const pathResolve = pathUrl => path.join(__dirname, pathUrl);
module.exports = {
webpack: {
alias: {
'@': pathResolve('src'),
'@components': pathResolve('src/components'),
}
},
devServer: {
port: 9090
}
}
Eslint
bash
npm i eslint -D
下一步、下一步...
.eslintrc.js
js
module.exports = {
parserOptions: {
ecmaVersion: 'latest', // 使用最新的 ECMAScript 版本
sourceType: 'module', // 使用模块化的文件结构
},
env: {
browser: true, // 启用浏览器环境
es2021: true, // 使用 ES2021 版本的特性
commonjs: true, // 启用 CommonJS 模块规范
},
extends: [
'eslint:recommended', // 使用 ESLint 推荐的基本规则
'plugin:react/recommended', // 使用 react 插件推荐的规则
'prettier', // 禁用eslint自带的格式化
],
plugins: ['react', 'prettier'], // 启用 react 插件
settings: {
react: {
version: 'detect',
},
},
rules: {
"prettier/prettier": "error", // 以eslint规则运行prettier,格式有问题时可以抛出异常
'react/react-in-jsx-scope': 0,
},
}
prettier
bash
npm i -S prettier
.prettierrc.js
js
module.exports = {
// 是否在语句末尾添加分号
semi: true,
// 是否使用单引号而不是双引号
singleQuote: true,
useTabs: true,
// 缩进的空格数
tabWidth: 2,
// 每行的最大字符数
printWidth: 120,
// 是否在多行元素的最后一行放一个括号
bracketSameLine: true,
// 末尾不需要逗号
trailingComma: 'none',
// 箭头函数的参数是否总是用括号包裹
arrowParens: 'avoid',
// 在JSX中是否使用单引号而不是双引号
jsxSingleQuote: true,
// 在对象字面量中是否添加空格,例如 { foo: bar }
bracketSpacing: true,
endOfLine: 'auto'
}
git提交校验
代码校验
bash
npm i husky lint-staged -D
// 生成.husky文件夹
npx husky install
// 监听 pre-commit 钩子,执行git暂存区校验
npx husky add .husky/pre-commit 'npx lint-staged'
package.json
json
"lint-staged": {
"src/**/*.{ts,tsx,js,jsx}": [
"eslint --fix"
]
}
提交信息校验
bash
npm i @commitlint/cli @commitlint/config-conventional -D
// 监听 commit-msg 钩子,执行提交信息校验
npx husky add .husky/commit-msg 'npm run commitlint'
package.json
json
"scripts": {
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
}
commitlint.config.cjs
cjs
module.exports = {
extends: ['@commitlint/config-conventional'],
// 校验规则
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'perf', 'revert', 'ci', 'test', 'refactor', 'build', 'style', 'chore']
],
'type-case': [0], //type 的输入格式,默认为小写'lower-case'
'type-empty': [0], //type 是否可为空
'scope-empty': [0], //scope 是否为空
'scope-case': [0], //scope 的格式,默认为小写'lower-case'
'subject-full-stop': [0, 'never'], //subject 结尾符,默认为.
'subject-case': [0, 'never'], //subject 的格式,默认其中之一:['sentence-case', 'start-case', 'pascal-case', 'upper-case']
'header-max-length': [0, 'always', 72] //header 最大长度,默认为72字符
}
}
配置路由
router/index.js
js
import { lazy, Suspense } from 'react';
import { Navigate } from 'react-router-dom';
// Suspense配合lazy实现懒加载
function SuspenseComponent(Component) {
return (
<Suspense>
<Component />
</Suspense>
);
}
const routes = [
{
path: '/',
element: <Navigate to="/home/system/user" />,
},
{
path: '/login',
element: SuspenseComponent(lazy(() => import('@/views/login/Login'))),
},
{
path: '/home',
element: SuspenseComponent(lazy(() => import('@/views/home/Home'))),
children: [
{
path: '',
element: <Navigate to="/home/system/user" />,
},
{
path: 'system',
children: [
{
path: '',
element: <Navigate to="/home/system/user" />,
},
{
path: 'user',
element: SuspenseComponent(
lazy(() => import('@/views/system/user/User'))
),
},
],
},
],
},
{
path: '*',
element: SuspenseComponent(lazy(() => import('@/views/notFound/NotFound.js')))
}
];
export default routes;
路由守卫
Premission.js
jsx
import { useRoutes, Navigate, useLocation } from 'react-router-dom';
import { getStorage } from '@/utils/storage';
import routes from '@/routes';
function Premission() {
const location = useLocation();
const RenderRoutes = () => useRoutes(routes);
const token = getStorage('token');
const toLogin = !token && location.pathname !== '/login';
return (
<>
{toLogin ? <Navigate to="/login" /> : <RenderRoutes />}
</>
);
}
export default Premission
自定义环境变量
bash
npm i cross-env -S
package.json
json
...
"scripts": {
"start": "craco start",
"start:prodev": "cross-env REACT_APP_TYPE=prodev craco start"
}
...
引入Antd
bash
npm i -S antd
jsx
import { Button } from 'antd'
function App() {
return (
<div className='App'>
<Button type='primary'>Button</Button>
</div>
)
}
export default App
注意项目的react、react-dom版本必须跟antd使用的react、react-dom版本一致,否则会报错
引入Axios
bash
npm i -S axios
js
src/utils/request.js
import axios from 'axios'
import { message } from 'antd'
// 保存环境变量
const isPrd = process.env.NODE_ENV === 'production'
const service = axios.create({
baseURL: isPrd ? '' : ''
})
service.interceptors.request.use(config => {
const token = ''
// 每个接口请求头带上token
token && (config.headers['Authorization'] = token)
// get的请求方式,带上时间戳
if (config.method === 'get') {
config.params = {
_t: Date.parse(new Date()) / 1000,
...config.params
}
}
return config
}, error => {
console.log(`发送request请求错误:${error}`)
return Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(response => {
if (response.code) {
switch (response.code) {
case 200:
return response.data
case 401:
// 未登录处理方法
break
default:
message.error(response.data.msg)
}
} else {
return response
}
})
export default service
引入Redux
bash
npm i react-redux @reduxjs/toolkit -S
store/features/system.js
js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
const initialState = {
userInfo: {},
token: '',
info: ''
}
const request = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('请求成功')
}, 3000)
})
}
// 创建异步请求
export const getInfo = createAsyncThunk('system/getInfo', async () => {
const res = await request()
return res
})
// 创建一个 Slice
export const counterSlice = createSlice({
name: 'system',
initialState,
// 定义 reducers 并生成关联的操作
reducers: {
setUserInfo: (state, { payload }) => {
state.userInfo = payload
},
setToken: (state, { payload }) => {
state.token = payload
}
},
extraReducers(builder) {
builder.addCase(getInfo.fulfilled, (state, { payload }) => {
state.info = payload
})
}
})
// 导出操作的方法
export const { setUserInfo, setToken } = counterSlice.actions
// 默认导出
export default counterSlice.reducer
store/index.js
js
import { configureStore } from '@reduxjs/toolkit'
import counterSlice from './features/system'
// configureStore创建一个redux数据
const store = configureStore({
// 合并多个Slice
reducer: {
system: counterSlice
}
})
export default store
index.js
jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import 'normalize.css'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
reportWebVitals()
src/views/Example.js
jsx
import { useSelector, useDispatch } from 'react-redux'
import { setUserInfo, setToken, getInfo } from '../../store/features/system'
const Example = () => {
const { userInfo, token, info } = useSelector(store => store.system)
const displtch = useDispatch()
return (<>
<button onClick={() => displtch(setUserInfo({name: '张三'}))}>setUserInfo</button>
<button onClick={() => displtch(setToken('xxxxxxxxx'))}>setToken</button>
<button onClick={() => displtch(getInfo())}>setUserInfo</button>
<div>{userInfo}</div>
<div>{token}</div>
<div>{info}</div>
</>)
}
export default Example