使用脚手架搭建React项目,再加亿点点细节

前言

记录一下搭建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

参考文章

Redux 最佳实践 Redux Toolkit 🔥🔥 - 掘金 (juejin.cn)

React配置路径别名、ESLint设置、git commit前规范检查 - 掘金 (juejin.cn)

相关推荐
passerby606139 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc