前端代码规范

代码规范

  1. config 中的常量拼写,推荐全大写,用短下划线分隔。如 APP_LIST
  2. enums 中的常量拼写,推荐格式大驼峰。如 AppKeys
  3. enums.ts 文件中,提取业务公共枚举,各业务再引入枚举去定义其它 config,减少如上冗余,例:
1
2
3
4
5
6
7
8
9
const AppTypeEnum = {
IQIYI: 1,
JISU: 85,
};

const DeliverTypeEnum = {
DOWNLOAD: 1,
PULL: 2,
};
  1. service 中不要引用 store, 需要读取相关数据,建议调用方(composition)使用参数传入。需要更新相关数据,也在各调用方(composition)进行单独更新。
  2. domain 中不要引用 store。 数据处理的 formatter 对象不要写在 domain 文件里。 建议写在 service 文件里。
  3. domain 中不要引用 service。如果需要相关的数据处理,可以考察数据处理函数是否与业务有关,从而决定放在 utils 或 service 里。使用 service 里的数据处理,建议直接有组件/composition 来调用。
  4. config 中不要引用 domain, 如需使用其类型,应该使用 import type
  5. config 中不要引用 utils,如需相关方法,则可以把相关常量的输出逻辑,作为方法写在 utils 里。如是和业务相关,如投放表单的校验规则,推荐移至 composition 中
  6. try..catch.., 建议统一放在组件/composition 里使用。service 中只处理正常的数据流,不关心异常情况。
  7. 组件中的变量,建议声明时就明确给出它的类型。
  8. 组件/composition 中的变量,声明和初始化的位置,应该和它的业务逻辑/函数靠在一起,而不是统一集中在顶部。

1. Vue 官方推荐代码风格

https://cn.vuejs.org/style-guide/

A. Essential

  1. 组件名为多个单词
  2. props 定义尽量详细
  3. 为 v-for 设置 key
  4. 不要在同一元素上使用 v-for 和 v-if(v-for 优先级更高,因此会先遍历列表再通过 v-if 移除,造成不必要的性能浪费)
  5. 为组件样式设置作用域
  1. 组件单独分成一个文件
  2. 单文件组件文件名以大写字母开头
  3. 基础组件名,使用统一前缀标识,如 Base
  4. 紧密耦合的组件名,在文件名中体现父子关系
  5. 组件名中的单词顺序——以高级别的单词开头,以描述性的修饰词结尾
  6. 自闭合组件
  7. 模板中的组件名大小写
  8. 完整单词的组件名
  9. prop 名 camelCase
  10. 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法
  11. 应该把复杂计算属性分割为尽可能多的更简单的 property
  1. 组件的选项应该有统一的顺序
  2. 组件/元素的 attribute 应该有统一的顺序
  3. 单文件组件的顶级元素的顺序

D. Use with Caution

  1. 元素选择器应避免在 scoped 中出现
  2. 隐性的父子组件通信(推荐:prop 和事件进行父子组件通信)

2. 百度前端代码规范 (提升可读性)

https://github.com/ecomfe/spec/blob/master/es-next-style-guide.md

剩余运算符之前的元素省略名称可能带来较大的程序阅读障碍。

1
2
3
4
5
// good
let [one, two, ...anyOther] = myArray;
let other = myArray.slice(3);
// bad
let [, , , ...other] = myArray;

使用变量默认语法代替基于条件判断的默认值声明。

1
2
3
4
5
6
// good
function foo(text = "hello") {}
// bad
function foo(text) {
text = text || "hello";
}

export 与内容定义放在一起。

1
2
3
4
5
6
7
8
// good
export function foo() {}
export const bar = 3;
// bad
function foo() {}
const bar = 3;
export { foo };
export { bar };

对数组进行连接操作时,使用数组展开语法

1
2
3
4
5
6
// good
let foo = [...foo, newValue];
let bar = [...bar, ...newValues];
// bad
let foo = foo.concat(newValue);
let bar = bar.concat(newValues);

不要使用数组展开进行数组的复制操作,可读性较差。推荐使用 Array.from

1
2
3
4
// good
let otherArr = Array.from(arr);
// bad
let otherArr = [...arr];

不得为了编写的方便,将可以并行的 IO 过程串行化。并行 IO 消耗时间约等于 IO 时间最大的那个。串行则是之和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
requestData().then(function (data) {
renderTags(data.tags);
renderArticles(data.articles);
});
// good
async function requestData() {
const [tags, articles] = await Promise.all([
requestTags(),
requestArticles(),
]);
return { tags, articles };
}
// bad
async function requestData() {
let tags = await requestTags();
let articles = await requestArticles();
return Promise.resolve({ tags, articles });
}

3. 代码简介之道

https://github.com/alivebao/clean-code-js

使用有意义的变量名

1
2
3
4
5
6
7
8
9
10
11
// bad
const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
saveCityState(
cityStateRegex.match(cityStateRegex)[1],
cityStateRegex.match(cityStateRegex)[2]
);
// good
const ADDRESS = "One Infinite Loop, Cupertino 95014";
const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
let [, city, state] = ADDRESS.match(cityStateRegex);
saveCityState(city, state);

避免无意义的条件判断

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
function createMicrobrewery(name) {
var breweryName;
if (name) {
breweryName = name;
} else {
breweryName = "Hipster Brew Co.";
}
}
// good
function createMicrobrewery(name) {
var breweryName = name || "Hipster Brew Co.";
}

使用 Object.assign 设置默认对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// bad
var menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true,
};
function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable === undefined ? config.cancellable : true;
}
createMenu(menuConfig);
// good
var menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true,
};
function createMenu(config) {
config = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true,
},
config
);
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);

封装判断条件

1
2
3
4
5
6
7
8
9
10
11
// bad
if (fsm.state === "fetching" && isEmpty(listNode)) {
/// ...
}
// good
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}

4. Letjs 结构

Letjs 前端工程化,助你快速搭建页面 - 掘金

Q1:关于 store, 不要在文件最外层进行 pinia 实例的创建。
—— 场景:在 service 中使用了,为了方便,就在最外层实例化了。为了不重复数据请求,所以读取 store 中的数据进行比对。

Q2:关于 store, domain 和 service 中不推荐引入 store
—— 场景:domain(MaterialData.ts, Media.ts), 主要是希望复用 store 中定义的方法。 service 中获得了数据之后,同步更新 store

Q3: config 和 domain 相互引用
—— 场景:config 中将 domain 作为一个类型来使用

Q4:config 和 util 相互引用
—— 场景:config 中定义表单校验规则

Q5: domain 中引用 service
—— 场景:domain 中调用 service,做更便捷的数据获取