M
M
文章目录
  1. 什么是规范?
  2. 为什么需要规范?
  3. 版本规范
    1. Git 分支模型
    2. 提交信息规范
  4. 项目组织规范
  5. 编码规范
    1. HTML
    2. CSS
    3. Javascript
  6. Vue风格指南
    1. 优先级 A:必要的
    2. 优先级 B:强烈推荐
    3. 一些细节
  7. 参考

如何制定前端规范?

什么是规范?

规范,名词意义上:即明文规定或约定俗成的标准,如:道德规范、技术规范等。 动词意义上:是指按照既定标准、规范的要求进行操作,使某一行为或活动达到或超越规定的标准,如:规范管理、规范操作。

为什么需要规范?

  • 降低新成员融入团队的成本, 同时也一定程度避免挖坑。
  • 提高开发效率、团队协作效率, 降低沟通成本。
  • 实现高度统一的代码风格,方便review, 另外一方面可以提高项目的可维护性。
  • 规范是实现自动化的基础。
  • 规范是一个团队知识沉淀的直接输出。

版本规范

项目的版本号应该根据某些规则进行迭代, 这里推荐使用语义化版本规范, 通过这个规范,用户可以了解版本变更的影响范围。 规则如下:

  • 主版本号:当你做了不兼容的 API 修改。
  • 次版本号:当你做了向下兼容的功能性新增。
  • 修订号:当你做了向下兼容的问题修正。

Git 分支模型

master分支
master分支表示一个稳定的发布版本。

  • 场景: 前端应用会跟随版本迭代, 在dev分支测试稳定后, 会合并到master分支, 并使用tag标记应用版本和对应的版本。
  • tag规范: v{APP_version}, 例如v0.1.0。
  • 人员:由项目负责人进行审核合并, 普通开发者没有权限。

dev分支
开发者主要工作的分支, 最新的特性或bug修复都会提交到这个分支. 开发者如果在该分支进行了提交,在push到远程之前应该先pull一下, 并尽量使用 rebase 模式,保证分支的简洁。

  • 命名规范: dev。
  • tag规范: 在dev分支中也可能会经历发布过程, 例如bug修复版本. 这里同样使用tag来标记这些发布. 例如v0.1.1。
  • 提交规范:如果实在开发分支上进行开发,在推送到远程之前,应该使用 git rebase 形式更新本地分支。

feature分支
涉及多人协作或者大功能的开发, 应该从dev分支checkout出独立的feature分支, 避免干扰dev分支。

  • 场景: 涉及多人协作: 团队多个成员在同一个项目下负责开发不同的功能, 这时候每个成员在自己的feature分支独立开发。大功能开发: 大功能开发跨越周期比较长, 需要多次迭代才会稳定. 这时候应该在独立的分支上开发. 方便跟踪历史记录, 也免于干扰dev分支的迭代和发布。
  • 命名规范: feature/name: name是功能名称。feature/version: 这也是团队常见的模式, 当无法使用一个功能名称来描述时, 可以使用版本号作为“功能”。
  • 合并时机: 当feature分支迭代稳定, 并通过测试后, 合并到dev分支. 合并到dev后, feature分支的生命周期就结束了。后续bug修复和功能优化直接在dev开发。当多个feature分支需要合并对外发布临时版本时. 合并到preview分支 . ⚠️这种情况不应该合并到dev分支, 因为feature分支可能还不稳定或未完成。 比如为了联调某些功能。
  • 合并方式: 不要使用 fast-forward. 这样可以在分支图上查看到分支历史。

preview分支
临时的预览分支, preview分支用于临时合并feature分支, 这其中可能会修复某些bug或者冲突. 可以选择性地将这些提交cherrypick回feature分支. 当预览结束后就可以销毁preview分支。

release分支
release分支有两种使用策略,第一种遵循gitFlow流程, 第二种是目前后端团队使用的策略,为了配合后端工作。

git flow 风格的release分支
当前前端应用的稳定版本绑定. release分支不一定存在, 一般情况下, 只会在前端版本稳定后, 将其合并到master, 并创建tag标记. 而只有需要为指定的正式版本修复bug时才会创建release分支。

  • 场景: 需要为某个正式版本修复bug(hotFix)时, 从master的对应tag中checkout release分支。
  • 命名规范: release/version 外部人员只会关注版本。
  • 如何修复: 如果对应bug可以在dev分支直接被修复, 可以先提交到dev分支(或者已经修复了), 然后再cherrypick到release分支。如果bug在新版本无法复现. 比如新版本升级了依赖. 那么在release分支直接修复即可。

自定义风格release分支
当要发布一个对应的版本时(或者一开始开发时)从dev分支checkout出一个开发分支,后续需要对外发布时,将dev分支合并到release分支, 并打上版本tag。

  • 何时创建: 开启新版本开发任务时(推荐),向外发布第一个版本时。
  • 何时合并: 后面dev有版本发布都要合并到release分支,直到开启另一条release分支。
  • 好处: 对发布内容进行筛选。专门用于发布, 开发者容易过滤变更的内容。

提交信息规范

一个好的提交信息, 会帮助你提高项目的整体质量。

  • why: 格式统一的提交信息可以帮助自动化生成changelog。版本库不只是存放代码的仓库, 也记录项目的开发记录. 这些记录应该可以帮助后来者快速地学习和回顾代码. 也应该方便其他协作者review你的代码。
  • 原则: 半年后, 你能看懂你的commit做了什么东西。
  • 方式: 使用git commit(打开编辑器)而不是git commit -m。
  • 必要信息: 1、提交改变了什么, 让其他reviewer更容易审核代码和忽略无关的改变。2、简短说明使用什么方式, 策略, 修复了问题。3、说明变动功能的细节。 一个提交不应该做超过2个功能的变动。

使用 commitlint 工具,常用有以下几种类型:

  • ✨feature或feat: 引入新功能
  • 🐛fix: 修复了bug
  • 📝docs: 文档
  • 🎨style: 优化项目结构或者代码格式
  • ♻️refactor: 代码重构. 代码重构不涉及新功能和bug修复. 不应该影响原有功能, 包括对外暴露的接口
  • ✅test: 增加测试
  • ⏫chore: 构建过程, 辅助工具升级. 如升级依赖, 升级构建工具
  • ⚡️perf: 性能优化
  • ⏪ revert: revert之前的commit
    • git revert 命令用于撤销之前的一个提交, 并在为这个撤销操作生成一个提交
  • 🎉 build或release: 构建或发布版本
  • 🔒 safe: 修复安全问题
git commit -m 'feat: add list'

项目组织规范

一个典型的项目组织规范如下:

  • README.md: 项目说明, 这个是最重要。你必须在这里提供关于项目的关键信息或者相关信息的入口. 一般包含下列信息:
    • 简要描述、项目主要特性
    • 运行环境/依赖、安装和构建、测试指南
    • 简单示例代码
    • 文档或文档入口, 其他版本或相关资源入口
    • 联系方式、讨论群
    • 许可、贡献/开发指南
  • CHANGELOG.md: 放置每个版本的变动内容, 通常要描述每个版本变更的内容。方便使用者确定应该使用哪个版本. 关于CHANGELOG的规范可以参考 keep a changelog
  • package.json: 前端项目必须. 描述当前的版本、可用的命令、包名、依赖、环境约束、项目配置等信息.
  • .gitignore: 忽略不必要的文件,避免将自动生成的文件提交到版本库
  • .gitattributes: git配置,有一些跨平台差异的行为可能需要在这里配置一下,如换行规则
  • docs/: 项目的细化文档, 可选.
  • examples/: 项目的示例代码,可选.
  • build: 项目工具类脚本放置在这里,非必须。如果使用统一构建工具,则没有这个目录
  • dist/: 项目构建结果输出目录
  • src/: 源代码目录
  • tests: 全局的测试目录,通常放应用的集成测试或E2E测试等用例
  • .env*: 项目中我们通常会使用环境变量来影响应用在不同运行环境下的行为. 可以通过dotEnv来从文件中读取环境变量. 通常有三个文件:

    • .env 通用的环境变量
    • .env.development 开发环境的环境变量
    • .env.production 生成环境的环境变量

      基本上这些文件的变动的频率很少,团队成员应该不要随意变动,以免影响其他成员。所以通常会使用.env..local文件来覆盖上述的配置, 另外会设置版本库来忽略.local文件.

对于开源项目通常还包括这些目录:

  • LICENSE: 说明项目许可
  • .github: 开源贡献规范和指南

编码规范

每一个程序员心目中对“好代码”都有自己的主见,统一的编码规范可以像秦始皇统一战国一样,避免不必要的论战和争议。

其实与其自己建立前端编码规范,不如选择社区沉淀下来的规范,推荐下面的资源:

HTML

HTML代码大小写

HTML标签名、类名、标签属性和大部分属性值统一用小写

推荐:

<div class="demo"></div>

不推荐:

<div class="DEMO"></div>

<DIV CLASS="DEMO"></DIV>

段落元素与标题元素只能嵌套内联元素

推荐:

<h1><span></span></h1>
<p><span></span><span></span></p>

不推荐:

<h1><div></div></h1>
<p><div></div><div></div></p>

更多参考 HTML规范

CSS

代码格式化

样式书写一般有两种:一种是紧凑格式 (Compact)

.jdc{ display: block;width: 50px;}

一种是展开格式(Expanded)

.jdc{
display: block;
width: 50px;
}

团队约定: 统一使用展开格式书写样式

代码大小写

样式选择器,属性名,属性值关键字全部使用小写字母书写,属性字符串允许使用大小写。

/* 推荐 */
.jdc{
display:block;
}

/* 不推荐 */
.JDC{
DISPLAY:BLOCK;
}

选择器

  • 尽量少用通用选择器 *
  • 不使用 ID 选择器
  • 不使用无具体语义定义的标签选择器
/* 推荐 */
.jdc {}
.jdc li {}
.jdc li p{}

/* 不推荐 */
*{}
#jdc {}
.jdc div{}

更多参考 CSS规范

另外可以学习一下 BEM命名规范

BEM 的语法:

.block {} //块(block)
.block__element {} //元素(element)
.block--modifier {} //修饰符(modifier)

Javascript

统一编码规范不仅可以大幅提高代码可读性,甚至会提高代码质量。当我们设计了一套关于编码规范的规则集时,需要工具去辅助检测,这就是 ESLint

规则集需要统一集中配置,ESLint 会默认读取配置文件 .eslintrc 来解析,而规则集在 rules 中进行配置:

"rules": {
"semi": ["error", "always"],
"quotes": ["error", "double"]
}

而我们需要做的是设定我们的代码规范,即 rules 项,关于它的文档及最佳实践参考业界标杆Airbnb JavaScript Style Guide

第一层约束:项目引入ESLint

当不符合代码规范的第一时间,我们就要感知到它,及时反馈,快速纠正,比直到最后积攒了一大堆错误要高效很多。

1.安装依赖

npm install eslint eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue --save-dev

或在 package.json 中配置以下依赖,执行 npm install 安装

"eslint": "^7.3.1",
"eslint-config-airbnb-base": "^14.2.0",
// "eslint-config-prettier": "^6.11.0",
// "eslint-friendly-formatter": "^4.0.1",
// "eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.0",
// "eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^6.2.2",

2.添加eslint配置文件

在项目根路径添加两个文件 .eslintrc.js.eslintignore

.eslintrc.js eslint规则配置文件

module.exports = {
// 规则运行的环境
"env": {
"browser": true,
"es2020": true,
"node": true
},
// 使用的规则集,根据不同项目(react/vue等)使用不同的规则
"extends": [
// "eslint:recommended",
"plugin:vue/essential",
"airbnb-base",
// "plugin:prettier/recommended"
],
// 指定编译器
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": [
"vue",
// "prettier"
],
/**
* 自定义规则
* “off” 或 0 - 关闭规则
* “warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
* “error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
*/
"rules": {
// 多个特性的元素应该分多行撰写,每个特性一行
"vue/max-attributes-per-line": [
2,
{
"singleline": 1,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
],
"vue/no-parsing-error": [
2,
{
"x-invalid-end-tag": false
}
],
"import/order": 0,
"no-unused-vars": 1,
"no-console": 0,
"max-len": [
2,
300,
4,
{
"ignoreUrls": true
}
],
"import/prefer-default-export": 0,
// 末尾分号
"semi": 0,
// 尾随逗号
"comma-dangle": 0,
// 换行符(平台不同,windows CRLF, unix LF)
"linebreak-style": 0,
// require()函数仅在模块的顶层调用它,禁用
"global-require": 0,
"import/no-dynamic-require": 0
// "prettier/prettier": [
// "error",
// {
// "singleQuote": true,
// "trailingComma": "none",
// "bracketSpacing": true,
// "jsxBracketSameLine": true
// }
// ]
},
"settings": {
"import/resolver": {
"node": {
"paths": ["src"],
"extensions": [".js", ".jsx", ".vue"]
}
}
}
};

.eslintignore 对特定目录(文件)忽略eslint检测的配置文件

.eslintrc.js
.vscode/settings.json
/build/
/config/
/dist/

babel-eslinteslint:recommended 规范

module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
// 换行符(平台不同,windows CRLF, unix LF)
"linebreak-style": 0,
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/html-self-closing": 0,
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': 0,
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}

更多的规则可以参考官方文档按需引入:eslint 中文文档

3.vscode 格式化Vue代码

ESlint:javascript代码检测工具,可以配置每次保存时格式化js。

Vetur:可以格式化html、标准css(有分号 、大括号的那种)、标准js(有分号 、双引号的那种)、vue文件,但是!格式化的标准js文件不符合ESlint规范,会给你加上双引号、分号等;

Prettier - Code formatter:只关注格式化,并不具有eslint检查语法等能力,只关心格式化文件(最大长度、混合标签和空格、引用样式等),包括JavaScript · Flow · TypeScript · CSS · SCSS · Less · JSX · Vue · GraphQL · JSON · Markdown。

vscode json配置项

{
// git路径
"git.path": "D:/tool/Git/cmd/git.exe",
"git.confirmSync": false,
"[vue]": {
"editor.defaultFormatter": "octref.vetur"
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
//.vue文件template格式化支持,并使用js-beautify-html插件
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.format.defaultFormatterOptions": {
// 对属性进行换行。
// - auto: 仅在超出行长度时才对属性进行换行。
// - force: 对除第一个属性外的其他每个属性进行换行。
// - force-aligned: 对除第一个属性外的其他每个属性进行换行,并保持对齐。
// - force-expand-multiline: 对每个属性进行换行。
// - aligned-multiple: 当超出折行长度时,将属性进行垂直对齐。
"js-beautify-html": {
// "wrap_line_length": 120,
"wrap_attributes": "force-expand-multiline",
"end_with_newline": false
},
"prettier": {
"semi": false, //不使用分号结尾
"singleQuote": true, //使用单引号
"eslintIntegration": true //开启 eslint 支持
}
},
//根据文件后缀名定义vue文件类型
"files.associations": {
"*.cjson": "jsonc",
"*.wxss": "css",
"*.wxs": "javascript",
"*.vue": "vue"
},
//保存自动格式化
// "editor.formatOnSave": true,
//配置 ESLint 检查的文件类型
"eslint.validate": ["javascript", "javascriptreact", "vue"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
// "eslint.run": "onSave",
"eslint.alwaysShowStatus": true,
"eslint.format.enable": true,
//保存时eslint自动修复错误
"eslint.autoFixOnSave": true,
"files.autoSave": "afterDelay"
}

推荐插件:

  • indent-rainbow (突出显示代码缩进)
  • Rainbow Brackets (彩虹括号)

4.webpack 配置

webpack.base.conf.js 可以通过配置 eslint-loader 让你有不符合 eslint 的时候在命令行或者界面里提示你有什么错误。

安装依赖

npm install eslint-friendly-formatter eslint-loader --save-dev

package.json

"eslint": "^7.3.1",
"eslint-config-airbnb-base": "^14.2.0",
// "eslint-config-prettier": "^6.11.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.0",
// "eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^6.2.2",

build\webpack.base.conf.js:

const createLintingRule = () => ({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})

module.exports = {
entry: {
},
output: {
},
resolve: {
},
externals: {
},
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : [])
],
loaders: [
]
},
plugins: [
]
}

config\index.js:

module.exports = {
dev: {

// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: true,
// If true, eslint errors and warnings will also be shown in the error overlay
// in the browser.
showEslintErrorsInOverlay: false,

},

build: {
}
}

TIP:showEslintErrorsInOverlay 这个变量可以控制错误提示是否需要在浏览器界面中提示。

第二层约束:Git Hooks

为了保证每次提交的 git 代码是正确的,为此我们可以使用 eslint 配合 git hook, 在进行 git commit 的时候验证 eslint 规范。如果 eslint 验证不通过,则不能提交。

我们需要安装一个 git 的 hook 工具 —— husky

npm install husky --save-dev

安装好之后在 package.jsonscripts 下面加入 esliint 的命令验证 eslint 的规则

"scripts": {
"lint": "node_modules/.bin/eslint --ext .js,.vue src",
"lint:fix": "node_modules/.bin/eslint --ext .js,.vue src --fix"
}

接下来在 package.json 下面加入 husky 的配置项

"husky": {
"hooks": {
"pre-commit": "echo 'husky' && npm run lint"
}
}

意思是在进行 git commit 的时候 先去执行 pre-commit 里面的命令: 我们在这里输出 husky 并且执行 npm run lint (我们之前加上的验证eslint的命令)。

如果 eslint 验证通过了,则会进行 commit 操作,否则会报 eslint 的错误提示。

最后:一定要使用 npm 安装 eslint 和 husky

Vue风格指南

这里是官方的 Vue 特有代码的风格指南。如果在工程中使用 Vue,为了回避错误、小纠结和反模式,该指南是份不错的参考。不过我们也不确信风格指南的所有内容对于所有的团队或工程都是理想的。所以根据过去的经验、周围的技术栈、个人价值观做出有意义的偏差是可取的。

优先级 A:必要的

组件名为多个单词

组件名应该始终是多个单词的,根组件 App 除外。

这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。

不推荐:

Vue.component('todo', {
// ...
})

export default {
name: 'Todo',
// ...
}

推荐:

Vue.component('todo-item', {
// ...
})

export default {
name: 'TodoItem',
// ...
}

组件的 data 必须是一个函数

当在组件中使用 data 属性的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数。

不推荐:

Vue.component('some-comp', {
data: {
foo: 'bar'
}
})

export default {
data: {
foo: 'bar'
}
}

推荐:

Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})

// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}

// 在一个 Vue 的根实例上
// 直接使用对象是可以的,
// 因为只存在一个这样的实例。
new Vue({
data: {
foo: 'bar'
}
})

Prop 定义应该尽量详细

不推荐:

// 这样做只有开发原型系统时可以接受
props: ['status']

推荐:

props: {
status: String
}

// 更好的做法!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}

永远不要把 v-ifv-for 同时用在同一个元素上

一般我们在两种常见的情况下会倾向于这样做:

  • 为了过滤一个列表中的项目 (比如 v-for=”user in users” v-if=”user.isActive”)。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
  • 为了避免渲染本应该被隐藏的列表 (比如 v-for=”user in users” v-if=”shouldShowUsers”)。这种情形下,请将 v-if 移动至容器元素上 (比如 ul, ol)。

详解

不推荐:

<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
<li>
</ul>

<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
<li>
</ul>

推荐:

<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
<li>
</ul>

<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
<li>
</ul>

优先级 B:强烈推荐

单文件组件文件的大小写

单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。

单词大写开头对于代码编辑器的自动补全最为友好,因为这使得我们在 JS(X) 和模板中引用组件的方式尽可能的一致。然而,混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因。

不推荐:

components/
|- mycomponent.vue

components/
|- myComponent.vue

推荐:

components/
|- MyComponent.vue

components/
|- my-component.vue

基础组件名

应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。

不推荐:

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

推荐:

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue

components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

多个特性的元素

多个特性的元素应该分多行撰写,每个特性一行。

在 JavaScript 中,用多行分隔对象的多个属性是很常见的最佳实践,因为这样更易读。模板和 JSX 值得我们做相同的考虑。

不推荐:

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">

<MyComponent foo="a" bar="b" baz="c"/>

推荐:

<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo"
>

<MyComponent
foo="a"
bar="b"
baz="c"
/>

一些细节

区分Vuex与props的使用边界

  • 业务相关组件 - 用Vuex
  • 通用组件 - 用props

如何分不清什么是业务组件,什么是通用组件?无脑用Vuex,如果你发现你用Vuex根本无法写代码的时候,你就知道什么情况需要使用props

watch 与 计算属性 傻傻分不清楚??

相同点:

  • 都可以观察数据
  • 数据发送变化时,函数会执行

不同点:

  • watch只能观察一个,computed 可以观察无限个
  • computed只有被 watcher 观察后,函数才会执行(只有被读取时会执行,内部使用getter实现)
  • watch 的参数更丰富

计算属性setter的玩法:

computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}

watch 的参数:

var vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5
}
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 方法名
b: 'someMethod',
// 深度 watcher
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: function (val, oldVal) { /* ... */ },
immediate: true
},
e: [
function handle1 (val, oldVal) { /* ... */ },
function handle2 (val, oldVal) { /* ... */ }
],
// watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) { /* ... */ }
}
})
vm.a = 2 // => new: 2, old: 1

如何在每一个路由后面添加query

非常hack,也是目前唯一优雅的解决方案

const transitionTo = router.history.transitionTo

router.history.transitionTo = function (location, onComplete, onAbort) {
const query = {a: 'xx'}
location = typeof location === 'object'
? {...location, query: {...location.query, ...query}}
: {path: location, query}

transitionTo.call(router.history, location, onComplete, onAbort)
}

什么是好的设计?

  • 展示层组件尽量轻,它只有一个作用,拿到数据渲染。
  • 不要把组件,当做page来写
  • 展现组件太复杂时,建议提取功能性组件。

总之,不要让业务展现组件太过复杂。

参考

支持一下
扫一扫,支持一下