使用脚手架搭建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)

相关推荐
ekskef_sef36 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr2 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼3 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js