前端代码规范
代码规范
- config 中的常量拼写,推荐全大写,用短下划线分隔。如 APP_LIST
- enums 中的常量拼写,推荐格式大驼峰。如 AppKeys
- enums.ts 文件中,提取业务公共枚举,各业务再引入枚举去定义其它 config,减少如上冗余,例:
1 2 3 4 5 6 7 8 9
| const AppTypeEnum = { IQIYI: 1, JISU: 85, };
const DeliverTypeEnum = { DOWNLOAD: 1, PULL: 2, };
|
- service 中不要引用 store, 需要读取相关数据,建议调用方(composition)使用参数传入。需要更新相关数据,也在各调用方(composition)进行单独更新。
- domain 中不要引用 store。 数据处理的 formatter 对象不要写在 domain 文件里。 建议写在 service 文件里。
- domain 中不要引用 service。如果需要相关的数据处理,可以考察数据处理函数是否与业务有关,从而决定放在 utils 或 service 里。使用 service 里的数据处理,建议直接有组件/composition 来调用。
- config 中不要引用 domain, 如需使用其类型,应该使用 import type
- config 中不要引用 utils,如需相关方法,则可以把相关常量的输出逻辑,作为方法写在 utils 里。如是和业务相关,如投放表单的校验规则,推荐移至 composition 中
- try..catch.., 建议统一放在组件/composition 里使用。service 中只处理正常的数据流,不关心异常情况。
- 组件中的变量,建议声明时就明确给出它的类型。
- 组件/composition 中的变量,声明和初始化的位置,应该和它的业务逻辑/函数靠在一起,而不是统一集中在顶部。
1. Vue 官方推荐代码风格
https://cn.vuejs.org/style-guide/
A. Essential
- 组件名为多个单词
- props 定义尽量详细
- 为 v-for 设置 key
- 不要在同一元素上使用 v-for 和 v-if(v-for 优先级更高,因此会先遍历列表再通过 v-if 移除,造成不必要的性能浪费)
- 为组件样式设置作用域
B. Strongly Recommended
- 组件单独分成一个文件
- 单文件组件文件名以大写字母开头
- 基础组件名,使用统一前缀标识,如 Base
- 紧密耦合的组件名,在文件名中体现父子关系
- 组件名中的单词顺序——以高级别的单词开头,以描述性的修饰词结尾
- 自闭合组件
- 模板中的组件名大小写
- 完整单词的组件名
- prop 名 camelCase
- 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法
- 应该把复杂计算属性分割为尽可能多的更简单的 property
C. Recommended
- 组件的选项应该有统一的顺序
- 组件/元素的 attribute 应该有统一的顺序
- 单文件组件的顶级元素的顺序
D. Use with Caution
- 元素选择器应避免在 scoped 中出现
- 隐性的父子组件通信(推荐:prop 和事件进行父子组件通信)
2. 百度前端代码规范 (提升可读性)
https://github.com/ecomfe/spec/blob/master/es-next-style-guide.md
剩余运算符之前的元素省略名称可能带来较大的程序阅读障碍。
1 2 3 4 5
| let [one, two, ...anyOther] = myArray; let other = myArray.slice(3);
let [, , , ...other] = myArray;
|
使用变量默认语法代替基于条件判断的默认值声明。
1 2 3 4 5 6
| function foo(text = "hello") {}
function foo(text) { text = text || "hello"; }
|
export 与内容定义放在一起。
1 2 3 4 5 6 7 8
| export function foo() {} export const bar = 3;
function foo() {} const bar = 3; export { foo }; export { bar };
|
对数组进行连接操作时,使用数组展开语法
1 2 3 4 5 6
| let foo = [...foo, newValue]; let bar = [...bar, ...newValues];
let foo = foo.concat(newValue); let bar = bar.concat(newValues);
|
不要使用数组展开进行数组的复制操作,可读性较差。推荐使用 Array.from
1 2 3 4
| let otherArr = Array.from(arr);
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); });
async function requestData() { const [tags, articles] = await Promise.all([ requestTags(), requestArticles(), ]); return { tags, articles }; }
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
| const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; saveCityState( cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2] );
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
| function createMicrobrewery(name) { var breweryName; if (name) { breweryName = name; } else { breweryName = "Hipster Brew Co."; } }
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
| 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);
var menuConfig = { title: "Order", buttonText: "Send", cancellable: true, }; function createMenu(config) { config = Object.assign( { title: "Foo", body: "Bar", buttonText: "Baz", cancellable: true, }, config ); } createMenu(menuConfig);
|
封装判断条件
1 2 3 4 5 6 7 8 9 10 11
| if (fsm.state === "fetching" && isEmpty(listNode)) { }
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,做更便捷的数据获取