Skip to main content

2 posts tagged with "stylelint"

View All Tags

· 24 min read

最近有新的项目需求,突然想整理下新建项目的这个过程,供以后借鉴,或者给他人提供一些思路 ~

完整项目模板参考 Github 地址

技术调研和选型

技术调研和选型都应该从业务出发,用技术赋能业务。以我们的项目为例,这是一个 pc 项目,主要是对地图进行编辑(绘制图形等),对页面性能要求较高,供内部人员使用,无 seo 需求。最终确定使用的技术栈如下:

  • react17、Hooks
  • react-router v6
  • typescript
  • webpack 5
  • axios
  • mobx。数据管理,因为涉及较多的数据交互,使用 mobx 在性能上更胜一筹,模板代码也少一些。其他的选择有 redux、xstate
  • less、css module
  • ahooks。内置了很多工具 hooks,墙裂推荐 ~
  • 其他的一些必要依赖

包管理器

这个看个人喜好吧,选 yarn 或 npm 都挺好的。个人比较喜欢用 yarn,初始安装速度比 npm 快,具体对比可以参考 https://pnpm.io/zh/benchmarks

搭建完项目后就可以配置.npmrc.yarnrc文件,设置 npm 源为淘宝源,这样安装依赖的时候就不用手动指定了

# .yarnrc
registry https://registry.npmmirror.com/
# 或者建议换成公司内部的 npm 源

搭建项目

基于 create-react-app 脚手架快速搭建一个 使用 typescript 的 react 项目

yarn create react-app my-app --template typescript
# 或者
npx create-react-app my-app --template typescript

安装好后,需要先把所有必要的依赖都先安装上,注意区分开发环境的依赖和生产环境的依赖

接下来在项目模板的基础上完善项目结构,主要是调整 src 的目录结构

src
├─ assets
│ ├─ css # 全局样式
│ ├─ icon
│ └─ image
├─ containers # 页面组件
├─ components # 公共组件
├─ helper # 存放工具函数或者业务抽象函数
│ ├─ utils # 工具函数
│ ├─ hooks # 公共 hooks
│ ├─ service.ts # 请求工具函数
├─ type # 全局类型
├─ store # 状态管理
└─ ...

规范配置

目的是统一代码编写格式和规范,助力团队协作,提高代码健壮性和可读性。按照后面的方法增加完配置文件后的目录结构如下:

├─ src
├─ .vscode # vscode 编辑器配置
├─ .husky # husky 配置
├─ .commitlintrc.js
# 代码检查
├─ .stylelintrc.js
├─ .eslintrc.js
# 代码格式化
├─ .prettierrc
├─ .editorConfig
├─ .gitignore
├─ .npmrc
├─ tsconfig.json

.vscode

现在 vscode 是前端最热门的编辑器,可以约定团队成员统一使用 vscode 编码,然后预设必要的编辑器配置,比如保存自动格式化代码、默认格式化工具采用 prettier 等。在 .vscode 下新建 settings.json

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}

eslint

注意只是让 eslint 检查语法和发现错误而不是纠正格式,代码格式由 prettier 统一。可以让同事多注重下代码质量,而不是依赖格式化修正代码错误

yarn add eslint -D
yarn eslint --init
# 通过命令行交互生成 .eslintrc.js

.eslintrc.js 参考配置如下:

module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {
"no-var-requires": 0,
},
};

package.json 增加 script 命令,用于全局检查项目代码

"eslint": "eslint --fix src/**/*.{js,ts,jsx,tsx}",

需要配合 vscode 的 eslint 插件来使用

详细配置参考 https://eslint.org/docs/user-guide/configuring/

stylelint

类似于 eslintstylelint 可以帮助检查和修复 css/less/scss 代码

yarn add stylelint stylelint-config-standard -D

项目根目录下新增 .stylelintrc.js,配置参考如下:

module.exports = {
extends: "stylelint-config-standard",
customSyntax: "postcss-less",
};

增加 script 命令,便于全局检查样式文件:

"stylelint": "stylelint --fix src/**/*.{css,less}",

然后记得在 vscode 中安装 stylelint 插件

prettier

yarn add prettier -D

.prettierrc 参考配置如下:

{
"tabWidth": 2,
"singleQuote": true,
"semi": true,
"trailingComma": "all"
}

增加 script 命令,便于格式化项目代码:

"prettier": "prettier --write src/**/*.{js,ts,jsx,tsx,less,css}"

需要安装 vscode 的 prettier 插件,然后可以选择开启保存时自动格式化代码的功能

详细配置参考 https://prettier.io/docs/en/configuration.html

editorConfig

使用不同编辑器打开同一份文件,如果编辑器配置不统一,显示效果和输入内容很有可能不一致。EditorConfig 就主要用于统一代码编辑器编码风格

.editorConfig 配置参考

# https://editorconfig.org

# 已经是顶层配置文件,不必继续向上搜索
root = true

[*]
# 编码字符集
charset = utf-8
# 缩进风格是空格
indent_style = space
# 一个缩进占用两个空格,因没有设置tab_with,一个Tab占用2列
indent_size = 2
# 换行符 lf
end_of_line = lf
# 文件以一个空白行结尾
insert_final_newline = true
# 去除行首的任意空白字符
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

详细配置参考 https://editorconfig.org/

Git 扩展

  • husky。操作 git 钩子的工具
  • lint-staged。本地暂存代码检查工具,可以让 husky 只检验 git 工作区的文件
yarn add husky lint-staged -D

package.json 追加以下配置

"scripts": {
"prepare": "husky install",
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --cache --fix",
"git add"
],
"src/**/*.{css,less}": [
"stylelint --fix",
"git add"
],
},
  1. 初始化 husky
yarn prepare
  1. 安装 commitlint。这个插件可以校验提交的 commit 信息是否符合规范,不符合则不可以提交
yarn add @commitlint/cli @commitlint/config-conventional -D
  1. 在根目录下创建 .commitlintrc.js
module.exports = {
extends: ["@commitlint/config-conventional"],
};
  1. 然后执行以下命令,会自动在生成的 .husky 文件夹下新建两个钩子文件commit-msgpre-commit
npx husky add .husky/commit-msg 'yarn commitlint --edit "$1"'
npx husky add .husky/pre-commit 'yarn lint-staged'

文件内容如下(确保自动生成的文件内容跟以下的保持一致):

  • commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "========= 校验 commit-msg ======="
yarn commitlint --edit $1
  • pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "========= 执行 lint-staged ======="
yarn lint-staged

在 git commit 之前会进入工作区文件的扫描,执行 prettier 脚本,修改 eslint 问题,校验 commit msg,通过后再提交到工作区

注意:以上脚本执行的路径要确保与 .git 同级,如果出现以下情况

- .git
- front
- src
- package.json

先调整 package.json

"scripts": {
//...
"prepare": "cd .. && husky install front/.husky",
},

然后执行 yarn prepare,重新生成 hook 脚本

npx husky add .husky/commit-msg 'yarn commitlint --edit "$1"'
npx husky add .husky/pre-commit 'yarn lint-staged'

修改文件内容如下

  • commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "========= 校验 commit-msg ======="
cd front
yarn commitlint --edit $1
  • pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "========= 执行 lint-staged ======="
cd front
yarn lint-staged
  • commitizen【可选】。命令行交互辅助输入 commit msg
npm i -g commitizen
commitizen init cz-conventional-changelog --save --save-exact

之后在提交 commit 时运行 git cz

cz-customizable。英文看着不爽的话,可以用这个插件自定义 commitizen 中文配置,自行搜索和配置吧!

扩展 webpack

基于 react-app-rewired 扩展,这样后续也能享受到 react-scripts 的更新。当然如果是需要高度定制 webpack 配置的话,可以把配置文件导出来(eject)

alias

比较常规的路径映射有 @ --> src 如下:

import { utils } from "@/helper/utils"
  1. 借助 react-app-rewiredcustomize-cra 扩展 webpack 配置
yarn add react-app-rewired customize-cra

修改 package.json

"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},

项目根目录下新增 config-overrides.js 文件

const { override, addWebpackAlias } = require("customize-cra");
const path = require("path");

module.exports = {
webpack: override(
// 配置alias
addWebpackAlias({
["@"]: path.resolve(__dirname, "src"),
["components"]: path.resolve(__dirname, "src/components"),
["mock"]: path.resolve(__dirname, "src/mock"),
})
),
};
  1. 需要先修改 tsconfig,确保在编译期间 ts 能正确映射到某个路径下的文件。正常情况下是直接配置 tsconfigpaths 就可以了,但是 CRA 脚手架执行项目(start)时会将其覆盖。可以通过在 tsconfig 引入 paths.json,避开这个问题
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"extends": "./paths.json",
"include": ["src", "config"]
}

根目录新增 paths.json

{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}

然后尝试在组件中使用 @ 引入模块吧!

less + css module

css 预处理语言,其实 sass 和 less 都可以吧,也是看个人喜好,然后结合 css module 设置样式作用域。在 config-overrides.js 中追加配置如下:

yarn add less less-loader -D
// config-overrides.js
const { override } = require("customize-cra");
// 需要先安装 yarn add customize-cra-less-loader -D
const addLessLoader = require("customize-cra-less-loader");

module.exports = {
webpack: override(
// ...
// 添加less解析器
addLessLoader({
lessOptions: {
javascriptEnabled: true,
sourceMap: false,
// modifyVars: { '@primary-color': '#1DA57A' },
},
})
),
// ...
};

之后在项目中使用,不过可能会报下面的错

Cannot find module './index.module.less' or its corresponding type declarations

需要在 react-app-env.d.ts 中追加 less 文件的声明:

declare module '*.less' {
const content: { [className: string]: string };
export default content;
}

设置全局 less 变量,新增 src/styles/global.less

// base variables
@font-size-base: 16px;
@font-size-lg: @font-size-base + 2px;
@font-size-sm: 12px;

// color
@color-green: #00b050;
@color-yellow: yellow;
@color-orange: orange;
@color-red: red;

// animation
@animation-duration-slow: 0.3s;
@animation-duration-base: 0.2s;
@animation-duration-fast: 0.1s;

// z-index list

更新 webpack 的 less 文件处理规则,需要安装 style-resources-loader

const { override, adjustStyleLoaders } = require("customize-cra");

module.exports = {
webpack: override(
//...
adjustStyleLoaders((rule) => {
if (rule.test.toString().includes("less")) {
rule.use.push({
loader: "style-resources-loader",
options: {
patterns: path.resolve(__dirname, "src/styles/global.less"),
injector: "append",
},
});
}
})
),
};

环境配置

1.默认使用 dotenv 和 env 文件来配置环境变量。CRA 默认有开发(.env.development)和线上环境(.env.production),如果只有这两个环境需要配置,只需要在 src 目录下新建这两个文件,然后配置变量就行了,构建过程会自动加载环境变量,参考官方文档 https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used

如果不只是这两个环境,可以在 src 下新建 env 文件夹,存放对应的环境配置文件,然后参考以下方式修改 package.json(不同环境对应不同的构建命令)

"scripts": {
"buid:dev": "dotenv -e env/.env.dev react-app-rewired build",
"build:prod": "dotenv -e env/.env.prod react-app-rewired build",
// ...
}

记得安装 dotenv 插件

yarn add dotenv-cli -D

注意变量需要以 REACT_APP_ 开头,其余自定义的变量会被忽略。配置文件参考:

// .env.dev
# 自定义环境变量
REACT_APP_ENV=dev
# 路由前缀
REACT_APP_BASEURL="/"
# 静态资源路径前缀
PUBLIC_URL="/"

2.可以通过 cross-env 和不同的执行命令注入环境变量,调整 package.json,示例如下:

"scripts": {
"start": "cross-env REACT_APP_NODE_ENV=dev react-app-rewired start",
"build:test": "cross-env REACT_APP_NODE_ENV=test react-app-rewired build",
"build": "cross-env REACT_APP_NODE_ENV=prod react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},

之后可以在代码中通过区分 REACT_APP_NODE_ENV 来加载对应的环境变量。参考以下配置文件:

const configMap: Record<Env, IConfig> = {
[Env.Dev]: { ...commonConfig, ...devConfig },
[Env.Test]: { ...commonConfig, ...testConfig },
[Env.Prod]: { ...commonConfig, ...prodConfig },
};
const currentEnv = process.env.REACT_APP_NODE_ENV ?? Env.Dev;
export const envConfig = configMap[currentEnv as Env];

这个是相比 dotenv 要多出的一个前置步骤

增加编译进度条

yarn add chalk@^4 progress-bar-webpack-plugin -D
// config-overrides.js
const chalk = require('chalk');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
// ...
module.exports = {
webpack: override(
// ...
addWebpackPlugin(
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`,
}),
),
}

增加项目信息输出

// config-overrides.js
const path = require("path");
const exec = require("child_process").execSync;

process.env.REACT_APP_NAME = require("./package.json").name;
process.env.REACT_APP_VERSION = require("./package.json").version;
process.env.REACT_APP_BUILD_TIME = new Date().toLocaleString("zh-CN", {
hour12: false,
});
process.env.REACT_APP_GIT_COMMIT = exec('git rev-parse --short=8 HEAD')
.toString()
.trim();

增加基础函数

// helper/utils/index.ts
/**
* 显示项目配置信息,比如项目名、版本等
*/
export function showProjectInfo() {
log.capsule(
`${process.env.REACT_APP_NAME}`,
`v${process.env.REACT_APP_VERSION}`
);
log.primary(`Build Time: ${process.env.REACT_APP_BUILD_TIME}`);
log.primary(`Last Commit: ${process.env.REACT_APP_GIT_COMMIT}`);
}

然后修改入口文件

// src/index.tsx
import { showProjectInfo } from "./utils";
// ...
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root"),
() => {
// show project info in console
showProjectInfo();
}
);

最终的 config-overrides.js 文件参考 https://github.com/GitHubJackson/react-spa-template/blob/main/config-overrides.js

包体积分析

借助 webpack-bundle-analyzer 插件

yarn add webpack-bundle-analyzer -D

扩展 webpack 配置

// config-overrides.js
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// ...
addWebpackPlugin(
// ...
new BundleAnalyzerPlugin(),
),

修改 package.json,增加相关的命令行

"scripts": {
// ...
"build:analyzer": "react-app-rewired build --stats && webpack-bundle-analyzer build/bundle-stats.json -m static -r build/bundle-stats.html -O",
}

执行命令后,浏览器打开 build/bundle-stats.html 文件就可以看到项目依赖的体积总览

项目实践

路由配置

基于 react-router v6,借助 React.lazy 和动态 import 实现路由懒加载

yarn add react-router-dom

根目录新增 routes/index.ts,路由配置如下:

import React from "react";

const LazyLogin = React.lazy(() => import("@/components/login"));
const LazyHome = React.lazy(() => import("@/components/home"));

export const routes = [
{
/**
* 登录页面
*/
path: "/login",
component: LazyLogin,
},
{
/**
* 主页
*/
path: "/home",
component: LazyHome,
},
];

App.tsx 中新增路由代码

import React, { Suspense } from "react";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from "react-router-dom";
import { routes } from "./routes";
import "antd/dist/antd.min.css";
import "./App.css";

function App() {
const FallbackComponent = null;
return (
<Router>
<Suspense fallback={FallbackComponent}>
<Routes>
{routes.length > 0 &&
routes.map((router) => {
return (
<Route
path={router.path}
element={<router.component />}
key={router.path}
/>
);
})}
<Route path="*" element={<Navigate to="/login" />} />
</Routes>
</Suspense>
</Router>
);
}

export default App;

在布局组件中,可以通过以下的方式插入子页面

import { Outlet } from "react-router";
// ...
<Content className={styles["content"]}>
<Outlet />
</Content>;

状态管理

mobx 可以给 react 增加响应式更新的能力,减少多余组件渲染的开销。参考代码如下:

import { createContext } from "react";
import { makeAutoObservable } from "mobx";

// NOTE 如果要限制,得先确保所有代码只通过 action 修改 store 属性
// 否则渲染过程有打印 warning log,会造成一定的性能损耗
// configure({ enforceActions: "always" });

// Model the application state.
class CommonStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
add = () => {
this.count++;
};
reduce = () => {
this.count--;
};
get getCount() {
return "count is " + this.count;
}
}
export const commonStore = new CommonStore();
export const rootStore = {
commonStore,
};

export const storeContext = createContext(rootStore);
export const StoreProvider = storeContext.Provider;

index.tsx 中引入 store

//...
import { rootStore, StoreProvider } from "./store";

ReactDOM.render(
<React.StrictMode>
<StoreProvider value={rootStore}>
<App />
</StoreProvider>
</React.StrictMode>,
document.getElementById("root")
);

还可以写个公共 hooks,供函数组件调用

// useStore.ts
import { useContext } from "react";
import { storeContext } from ".";

export const useStore = () => useContext(storeContext);

在组件中使用

import React from "react";
import { useLocalStore, useObserver } from "mobx-react-lite";
import { useStore } from "@/helper/hooks/useStore";
import styles from "./index.module.less";

function Check() {
const { commonStore } = useStore();
const { add, reduce } = commonStore;
// 定义局部 store,仅限当前组件使用
const todo = useLocalStore(() => ({
// state
title: "Click to toggle",
done: false,
// action
toggle() {
todo.done = !todo.done;
},
// getter
get emoji() {
return todo.done ? "😜" : "🏃";
},
}));

return useObserver(() => (
<div className={styles["container"]}>
<button onClick={add}>add</button> {commonStore.count}
<button onClick={reduce}>reduce</button>
</div>
));
}

export default Check;

webpack 需要扩展配置来支持 mobx-react

mobx-react 有用到装饰符,CRA 目前还没有内置的装饰器支持,需要增加相应的 babel 插件。如果是只使用 React Hook 和mobx-react-lite 开发,则不需要配置

const {
override,
addDecoratorsLegacy,
disableEsLint,
//...
} = require("customize-cra");

module.exports = {
webpack: override(
addDecoratorsLegacy(),
disableEsLint()
//...
),
};

disableEsLint 是用于防止报错 Parsing error: Using the export keyword between a decorator and a class is not allowed. Please use 'export @dec class' instead

接口使用规范

这个主要看个人编码喜好吧,我自己定义的接口使用规范涉及三层:

  1. 基于 axios 创建 service 工具函数,在这里设置一些基本配置,比如 api url 前缀、拦截器逻辑、通用的状态码处理、...
// helper/utils/service.ts
import axios from "axios";
import { envConfig } from "@/config";

export const service = axios.create({
baseURL: envConfig.baseUrl,
timeout: 100000,
});

// Add a request interceptor
service.interceptors.request.use(
function (config) {
// Do something before request is sent
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);

// Add a response interceptor
service.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
}
);
  1. 接口函数,封装具体接口的处理逻辑、异常处理、...
// services/index.ts
import { service } from "@/helper/utils/service.ts";

export async function getUserList(): Promise<IUserData[]> {
const res = await service.get("xxx");
if (res.code !== 200) {
message.error(`message: ${res.msg}, error: ${res.errorMsg}`, 2.5);
return;
}
// NOTE obj2CamelCase 用于转换下划线为驼峰命名,因为前端统一使用驼峰命名
return obj2CamelCase(res.data) as IUserData[];
}
  1. useRequest(ahooks),业务层消费接口
import { getUserList } from "./services/index.ts";
// 获取用户列表
const { data } = useRequest(getUserList, {
manual: false,
onSuccess: (data) => {
//...
},
});

新建页面

示例代码如下:

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const : React.FC<RouteComponentProps> = props => {

return (
<div>

</div>
);
}

export default ;

可以结合 vscode 定制页面模板代码(preferences > user snippets),快速导入。比如新增上述模板代码,先选择 typescript react 进入 json 文件,新增以下配置:

{
//...
"quickly hook": {
"prefix": "reacthook",
"body": [
"import React from 'react';",
"import { RouteComponentProps } from 'react-router-dom';",
"",
"const $1: React.FC<RouteComponentProps> = props => {",
"",
"\treturn (",
"\t\t<div>",
"\t\t\t",
"\t\t</div>",
"\t);",
"}",
"",
"export default $1;"
],
"description": "quick reacthook"
}
//...
}

之后在 tsx 文件中,输入 reacthook 就可以通过提示导入模板代码

单元测试

前端可以通过 Jest 做一些逻辑和组件的单元测试,场景包括 UI 组件库、utils、公共 hooks 等

CRA 其实已经集成了这个能力,我们可以通过编写满足条件的测试用例文件,来快速使用这个能力

Jest 将使用以下任何流行的命名约定来查找测试文件:

  • __tests__ 文件夹中带有 .[js|ts] 后缀的文件
  • 带有 .test.[js|ts] 后缀的文件
  • 带有 .spec.[js|ts] 后缀的文件

这些文件可以放在 src 下任意文件夹中,建议统一放置于 src/__tests__ 中。编写完之后可以通过 yarn test 运行测试,Jest 将以 watch(观察) 模式启动。 每次保存文件时,它都会重新运行测试

更多内容可以参考 https://www.html.cn/create-react-app/docs/running-tests/

sentry

sentry 可以帮助我们监控和收集页面的异常

yarn add @sentry/react @sentry/tracing

然后在 src/index.tsx 中引入即可

import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing";

Sentry.init({
dsn: "xxx", // TODO 这个需要在 sentry 中注册后获取,create project 时注意选择 react 应用
integrations: [new Integrations.BrowserTracing()],

// 我们建议在生产中调整此值,或使用 tracesSampler 进行更精细的控制
tracesSampleRate: 1.0,
});

之后所有未处理的异常都会被 Sentry 自动捕获,为了验证配置的有效,可以尝试在 App.tsx 中加入这个组件

<button onClick={methodDoesNotExist}>Break the world</button>

然后启动应用,之后可以在 project > 问题 中看到对应的错误信息

sentry 默认是纯英文,可以进入 User settings 修改 language,改为 Simplified Chinese 然后刷新页面

添加 ErrorBoundary

如果您使用的是 React 16 或更高版本,则可以使用 Error Boundary 组件将组件树内部的 Javascript 错误自动发送到 Sentry,并设置回退 UI

Mock

前端经常需要先 mock 数据开发,我们可以通过搭建一个 mock server 来帮助我们快速高效地 mock 接口数据

yarn add koa koa-router koa-bodyparser mockjs nodemon -D

在 src 下新建一个 mock 文件夹,然后在 mock 文件夹下创建 server.js 文件

const Koa = require("koa");
const router = require("koa-router")();
const bodyParser = require("koa-bodyparser");
const testData = require("./test.js");

const app = new Koa();
app.use(bodyParser());
app.use(router.routes());

router.get("/test", async (ctx, next) => {
ctx.body = testData;
await next();
});

// error-handling
app.on("error", (err, ctx) => {
console.error("server error", err, ctx);
});

app.listen(3001);

新增 test.js,编写测试接口。接口定义建议先和后端对齐

const Mock = require("mockjs");

const data = Mock.mock({
"list|1-10": [
{
"id|+1": 1,
},
],
});

module.exports = data;

然后修改 package.json

"mock": "nodemon src/mock/server.js",

在实际开发的时候,就可以多开一个终端运行 mock server,然后在前端页面直接访问 mock 的接口就可以了

最后

我建立了一个仓库 react-spa-template,集成了以上的能力,欢迎 comment ~

持续更新,更多玩法探索中......

· 9 min read

编码规范

以我们团队的项目为例,整体是从以下几个方面约定编码规范:

当然还有其他方面的编码规范,比如 vue、JSON 等等

以上规范只是说提供一套规范依据(可以协助 code review),团队在开发的过程中要尽可能遵循(当然可以提出自己的修改意见),然后在实践过程中渐渐形成自己的一套规范,并不断去完善这一套规范

这些编码规范都可以借助一些第三方插件来辅助落实。编辑器建议用 vscode,然后在本地可以借助 vscode 插件来约束代码(可以让我们在编写代码时实时发现一些语法问题,以及保存时自动格式化代码)

辅助工具

eslint

针对 js/ts/react/vue/... 代码进行语法检查和错误预警,减少一些低级错误;同时也能提高 code review 的效率。注意 eslint 不做代码格式规范

yarn add eslint -D
yarn eslint --init

.eslintrc.js 推荐配置:

module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {
"no-var-requires": 0,
},
};

然后记得在 vscode 中安装 eslint 插件

eslint 详细配置参考 https://eslint.org/docs/user-guide/configuring/

stylelint

类似于 eslintstylelint 可以帮助检查和修复 css/less/scss 等样式的代码

yarn add stylelint stylelint-config-standard -D

项目根目录下新增 .stylelintrc.js

module.exports = {
extends: "stylelint-config-standard",
rules: {}, // 自定义规则
};

然后记得在 vscode 中安装 stylelint 插件

prettier

统一代码格式,包括 js/ts/css/less/...,可以让团队代码形成一种统一的风格,提高代码可读性,也便于多人开发协作

yarn add prettier -D

.prettierrc.js 推荐配置:

module.exports = {
printWidth: 160, //编辑器每行的长度,默认80
tabWidth: 4, //制表符tab的宽度,默认值是2
useTabs: false, //代码缩进是否用制表符tab,默认false
semi: true, //是否使用分号,默认true,使用分号
singleQuote: true, //是否使用单引号,默认为false
quoteProps: "as-needed", //对象属性的引号使用 as-needed 仅在需要的时候使用 consistent 有一个属性需要引号,就都需要引号 preserve 保留用户输入的情况
jsxSingleQuote: false,
trailingComma: "none", //末尾逗号 none 末尾没有逗号 es5 es5有效的地方保留 all 在可能的地方都加上逗号
bracketSpacing: true, //字面量对象括号中的空格,默认true true - Example: { foo: bar }. false - Example: {foo: bar}.
jsxBracketSameLine: false,
arrowParens: "avoid", //箭头函数中的括号always avoid
htmlWhitespaceSensitivity: "ignore",
vueIndentScriptAndStyle: false, //是否给vue中的 <script> and <style>标签加缩进
endOfLine: "auto", //行末尾标识
eslintIntegration: true, //不让prettier使用eslint的代码格式进行校验
};

需要安装 vscode 的 prettier 插件,然后可以选择开启保存时自动格式化代码的功能

prettier 详细配置参考 https://prettier.io/docs/en/configuration.html

EditorConfig

使用不同编辑器打开同一份文件,如果编辑器配置不统一,显示效果和输入内容很有可能不一致。EditorConfig 就主要用于统一代码编辑器编码风格

.editorConfig 配置文件参考

# https://editorconfig.org

# 已经是顶层配置文件,不必继续向上搜索
root = true

[*]
# 编码字符集
charset = utf-8
# 缩进风格是空格
indent_style = space
# 一个缩进占用两个空格,因没有设置tab_with,一个Tab占用2列
indent_size = 2
# 换行符 lf
end_of_line = lf
# 文件以一个空白行结尾
insert_final_newline = true
# 去除行首的任意空白字符
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

详细配置参考 https://editorconfig.org/

commitlint

commitlint 用于检测 commit message 是否合法。message 的格式如下:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
  1. 标题行(subject): 必填, 描述主要修改类型和内容
  2. 主题内容(body): 描述为什么修改, 做了什么样的修改 以及开发的思路等等。
  3. 页脚注释(footer): 可以写注释,放 BUG 号的链接。=

type 类型约定如下:

feat: 新功能、新特性
fix: 修改 bug
perf: 更改代码,以提高性能(在不影响代码内部行为的前提下,对程序性能进行优化)
refactor: 代码重构(重构,在不影响代码内部行为、功能下的代码修改)
docs: 文档修改
style: 代码格式修改, 注意不是 css 修改(例如分号修改)
test: 测试用例新增、修改
build: 影响项目构建或依赖项修改
chore: 其他修改(不在上述类型中的修改)
revert: 恢复上一次提交
ci: 持续集成相关文件修改
release: 发布新版本

配置方式在下一节 Husky 中提及

Husky

Husky 是一个操作 git 钩子的工具,可以做代码提交检测,阻止不规范的代码提交和推送。注意:husky 要求 git 版本 > 2.13.0

lint-stage 是一个本地暂存代码检查工具,可以让 husky 只检验 git 工作区的文件

  1. 安装依赖,包括 commitlint 相关依赖
npm i husky lint-stage -D
npm i @commitlint/cli @commitlint/config-conventional -D
  1. 在 package.json 追加配置
"scripts": {
//...
"prepare": "husky install",
"precommit": "eslint src/**/*.js"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --cache --fix",
"git add"
]
},
  1. 配置 commitlint 规则(commitlint.config.js):
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {}, // 自定义规则
};
  1. 初始化 husky
yarn prepare
# 这个命令在 yarn 安装依赖后会自动执行
  1. 然后执行以下命令,会自动在生成的 .husky 文件夹下新建两个钩子文件 commit-msgpre-commit
npx husky add .husky/commit-msg 'yarn commitlint --edit "$1"'
npx husky add .husky/pre-commit 'yarn lint-staged'

文件内容如下(确保自动生成的文件内容跟以下的保持一致):

  • commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "========= 校验 commit-msg ======="
yarn commitlint --edit $1
  • pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "========= 执行 lint-staged ======="
yarn lint-staged

注意:以上脚本执行的路径要确保与 .git 同级,不同级可以参考 https://blog.zhouweibin.top/FEED/create-react-app/#git扩展-9 提供的解决方法

配置好 eslint/stylelint/prettier 后,在 package.json 中配置对应的 script 命令便于全局检查和格式化项目代码

"scripts": {
"eslint": "eslint --fix src/**/*.{js,ts,jsx,tsx}",
"stylelint": "stylelint --fix src/**/*.{css,less}",
"prettier": "prettier --write src/**/*.{js,ts,jsx,tsx,less,css}"
}

其他工具

  • commitlizen。以命令行交互的方式协助输入 commit 信息

备注

以上工具均需要在项目中增加额外的依赖和配置文件,更好的方式是通过定制脚手架的方式快速安装依赖和生成配置文件,提供开箱即用的项目模板