找个前端规范的最佳实践, 先从脚手架开始, 该脚手架只会提供最基础的功能, 上层功能还得继续封装
该脚手架支持如下功能:
在node安装的情况下,本文用 pnpm
进行安装
先执行如下命令
pnpm create vite
选择 template
为 react-ts
pnpm
管理包工具,执行如下命令即安装依赖且启动项目服务 pnpm i pnpm dev
main.tsx
、App.tsx
文件,并移除tsx文件中引用的各项依赖|-- src |-- App.tsx # 主组件 |-- main.tsx # 入口文件
将src文件夹下添加如下文件夹
|-- src |-- assets # 样式以及图片 |-- components # 基础组件 |-- config # 公共配置 |-- features # 业务组件 |-- pages # 路由对应页面 |-- routers # 路由配置 |-- stores # 全局状态 |-- types # types |-- utils # 工具库 |-- App.tsx # 主组件 |-- main.tsx # 入口文件
开发环境支持配置如下:
先安装 @types/node
pnpm add -D @types/node
编辑 vite.config.ts
文件
import { resolve } from "path";export default defineConfig({ resolve: { alias: { "@": resolve(__dirname, "./src"), }, extensions: [".js", ".json", ".ts", "tsx"], },});
编辑 tsconfig.json
文件, 添加如下配置
"compilerOptions":{ "baseUrl": ".", "paths": { "@/*": ["./src/*"] }}
编辑 vite.config.ts
文件, 这里根据自身需求来
server: { proxy: { '/api': { target: 'http://127.0.0.1:8001', changeOrigin: false, rewrite: (path) => path.replace(/^\/api/, '') } } },
vite 默认已经集成了eslint, 因此这里我先不做补充, 直接集成 eslint-plugin-simple-import-sort
pnpm add -D eslint-plugin-simple-import-sort
补充 eslint.config.js
文件:
import js from "@eslint/js";import globals from "globals";import reactHooks from "eslint-plugin-react-hooks";import reactRefresh from "eslint-plugin-react-refresh";import tseslint from "typescript-eslint";import simpleImportSort from "eslint-plugin-simple-import-sort";export default tseslint.config({ ignores: ["dist", "node_modules"]}, { extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { "react-hooks": reactHooks, "react-refresh": reactRefresh, "simple-import-sort": simpleImportSort, }, rules: { ...reactHooks.configs.recommended.rules, "react-refresh/only-export-components": [ "warn", { allowConstantExport: true }, ], "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", },}, );
因为这里会使用 tailwindCSS
, 所以先把 tailwindCSS
插件先一并装上
pnpm add -D prettier prettier-plugin-tailwindcss
根目录创建 .prettierrc
文件, 写入如下内容, 这里的规则可以自行修改, 我只提供一个示例
{ "useTabs": true, "tabWidth": 2, "printWidth": 100, "singleQuote": true, "trailingComma": "none", "bracketSpacing": true, "semi": true, "plugins": ["prettier-plugin-tailwindcss"], "tailwindConfig": "./tailwind.config.js"}
提交三件套为 husky
、 lint-staged
、 commitlint
, 主要是对提交时做文件校验以及提交文案校验
此时 husky 版本来到v9
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional
修改 package.json
文件如下:
{ "scripts": { "prepare": "husky", "lint": "eslint .", "prettier": "prettier --write **.{tsx,ts,json}", "lint-staged": "lint-staged" // ... }, "lint-staged": { "*.{js,ts,tsx,jsx}": ["pnpm eslint"], "*.{js,ts,tsx,jsx,css,less,scss}": ["pnpm prettier"] }}
执行 pnpm prepare
命令, 会自动生成 .husky
文件夹, 里边手动创建 commit-msg
文件写入如下内容:
npx --no -- commitlint --edit \
手动创建 pre-commit
文件,写入如下内容:
pnpm lint-staged
根目录添加 commitlint.config.js
文件
export default { extends: ["@commitlint/config-conventional"], // 以下时我们自定义的规则 rules: { "subject-case": [0], "type-enum": [ 2, "always", [ "bug", // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况 "feat", // 新功能(feature) "fix", // 修补bug "docs", // 文档(documentation) "style", // 格式(不影响代码运行的变动) "refactor", // 重构(即不是新增功能,也不是修改bug的代码变动) "test", // 增加测试 "chore", // 构建过程或辅助工具的变动 "revert", // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit) "merge", // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址 ], ], },};
此时完成配置, 测试一下, 提交一个错误的文件提交
上述报错阻止提交成功, 证明lint-staged 配置成功
继续测试提交一个正确的文件, 但是 commit 语句不规范, 按照 git commit -m "123"
提交
此时也报错, 提示没有 subject 以及 type, 因此补充完整结构再次提交, git commit -m "feat: init"
, 此时成功提交
使用 unplugin-auto-import
插件
pnpm add -D unplugin-auto-import
修改 vite.config.ts
文件
import AutoImport from "unplugin-auto-import/vite";export default defineConfig({ plugins: [ react(), AutoImport({ imports: ["react", "mobx", "react-router", "react-router-dom"], }), ], // ...});
编写 app.tsx
文件, 引入useState, 但不需要 import 语句, 重新 pnpm dev
发现根目录多了一个 auto-imports.d.ts
文件, 这个文件就是相当于ts类型声明文件, 将项目中用到的api类型从对应的插件中导入, 防止代码报错, 在编译时会自动引入对应的插件
这个文件需要在 tsconfig.json
中引入一下, 且不需要提交, 因此修改 tsconfig.json
文件 include
配置
{ "include": ["src", "src/vite-env.d.ts", "auto-imports.d.ts"]}
在 .gitignore
中添加 auto-imports.d.ts
执行如下命令安装 react-router-dom
, 此时 react-router
版本到了6
pnpm add react-router-dom
在 routers
文件夹下创建 index.tsx
文件,并写入
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom';const Home = lazy(() => import('@/pages/Home/index.tsx'));const About = lazy(() => import('@/pages/About/index.tsx'));const NotFound = lazy(() => import('@/features/NotFound/index.tsx'));const routers = [ { path: '/', element: <Navigate to="/home" replace={true} /> }, { path: '/home', element: <Home /> }, { path: '/about', element: <About /> }, { path: '*', element: <NotFound /> }];const browserRouter = createBrowserRouter(routers);const Routes = () => <RouterProvider router={browserRouter} />;export default Routes;
其中 About
和 Home
为在 pages
中定义好的组件,内容随意
react-error-boundary
用来捕获组件错误
编辑 app.tsx
文件中写入如下代码
import { Suspense } from 'react';import { ErrorBoundary } from 'react-error-boundary';import { ErrorFallback, Loading } from '@/features';import Routes from './routers/index.tsx';function App() { return ( <> <Suspense fallback={<Loading />}> <ErrorBoundary fallback={<ErrorFallback />}> <Routes /> </ErrorBoundary> </Suspense> </> );}export default App;
ErrorFallback, Loading 为 features
中的组件,具体可以自行定义内容
引入 mobx 作为状态管理
pnpm add mobx mobx-react
在 stores
文件夹中创建 index.ts 文件 和 countStore.ts 示例文件
编写 countStore.ts 文件
import { action, makeObservable, observable } from 'mobx';class CounterStore { constructor() { makeObservable(this, { count: observable, increment: action, }); } count = 0; increment() { console.log(this); this.count++; }}export default new CounterStore();
在 index.ts
进行统一导出
import countStore from './countStore';export { countStore };
安装如下依赖:
pnpm add -D tailwindcss postcss autoprefixer
这里使用 iconify 中的 icon 组件, 因此继续安装如下两个插件, 如果你不用这个组件库可以忽略
pnpm add -D @iconify/json @iconify/tailwind
根目录新建文件 tailwind.config.js
, 写入如下内容:
const { addDynamicIconSelectors} = require("@iconify/tailwind");export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [addDynamicIconSelectors()],};
根目录新建文件 postcss.config.js
, 写入如下内容:
export default { plugins: { tailwindcss: {}, autoprefixer: {}, },};
在 assets/styles/index.css 顶部添加如下内容:
@tailwind base;@tailwind components;@tailwind utilities;
该css应在 main.tsx
中被引用
此时 tailwindcss 已经集成完毕, 在 app.tsx 中测试一下
写入一个div类名
function App() { return ( <> <button className="rounded-md border px-5 py-2 hover:shadow-md"> test </button> <button className="icon-[ep--menu]" /> </> );}export default App;
展示效果如下:
icon-[ep--menu]
为 iconify 的图标, 可以在 iconify 中找到
我这里使用的是自己封装的 @hushaha/request
, 如果你使用axios或者fetch则可以跳过这里
pnpm add @hushaha/request
在 utils/request/index.ts 中添加如下代码
import { createRequest } from '@hushaha/request';import { apis, APISchemaRes } from './schema';const { apiList: http } = createRequest<APISchemaRes>( { baseURL: '/api' }, apis);export { http };
在 utils/request/schema.ts 中添加如下代码
import type { APISchemaResponse, ApiSchemas } from '@hushaha/request';export interface APISchemaRes extends APISchemaResponse { getUser: { request: { petId: string; }; response: { code: number; data: { id: number; name: string; }; }; };}export const apis: ApiSchemas<APISchemaRes> = { getUser: { path: 'GET pet/:petId', headers: { 'Content-Type': 'application/json' } }};
此时即可在组件中通过如下方式调取接口
import { http } from '@/utils';const getUserData = async () => { const res = await http.getUser({ petId: '1' })}
这整篇文章代码量居多, 具体的每个插件更新迭代速度比较快, 大版本更新可能会导致使用方法有差异, 因此最好的方式是知道自己想要什么, 然后针对性去看对应文档的使用方法, 再逐步集成
主要常用的插件如上方介绍, 如果有新的好使的插件可以自行封装进去, 至于其他的技术栈脚手架, 道理都是一样的, 主要分为开发插件及应用插件, 开发插件大同小异, 应用插件按照业务场景来, 因此无论是 next、vue、monorepo仓库等, 都可以参照这个流程进行封装
这个脚手架已经集成到我的 hush-cli 脚手架中, 可以通过如下命令快速创建
pnpm create hush-cli
hush-cli 就是一个基于vite二次封装的脚手架库, 把常用的插件封装进去了, 后续会继续补充更多插件类型以及使用示例, 可以向我提 issues 一起维护