探索vue2框架的世界:选项式写法的代码书写规范

前言

在日常开发的工作中,作为一名以专业的前端为目标的工程师,除了能把自己所编写的项目跑起来,需要考虑的事还有很多,比如浏览器的兼容问题,性能优化问题,代码的可读性问题,后期的维护问题等等,其中我认为代码的可读性和书写规范尤其重要,无规矩不成方圆,一份标准的代码书写规范可以增强项目的易读性,让后来加入的开发人员更快速的融入项目,让后期的维护人员更容易去定位问题。

在vue的官网上有这么一篇专业且非约束性的文章,“vue2的风格指南”,现总结如下,与广大的同行和前辈们共勉。提前声明,本文章并非用于商业,旨在分享学习心得。

代码书写规范化

  • 遵守规则,使用eslint和自动化程序把操作层面弄得更简单
  • 规避错误
  • 在绝大多数工程中改善可读性和开发体验
  • 确保编码习惯一致性
  • 帮助老代码平稳迁移,让代码易于维护,降低bug得存在

1. 组件名为多个单词

  • 组件名应该始终事多个单词得,根组件 App 以及 之类的Vue内置组件除外

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

  • 反例

    
    Vue.component('todo', {
      // ...
    })
    
    export default {
      name: 'Todo',
      // ...
    }
    
    
  • 好例子

    
    Vue.component('todo-item', {
      // ...
    })
    
    export default {
      name: 'TodoItem',
      // ...
    }
    
    

2. 组件数据

  • 组件的 data 必须是一个函数。这样就可以形成一个闭包,保护每个组件实例中数据的隐私性

  • 反例

    
    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'
      }
    })
    
    

3. prop定义

  • 在提交的代码中跟你,prop的定义应该尽量详细,至少需要指定其类型

  • 反例

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

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

4. 为 v-for 设置键值

  • 在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。甚至在元素上维护可预测的行为,比如动画中的对象固化 (object constancy),也是一种好的做法。

  • 反例

    
    <ul>
      <li v-for="todo in todos">
        {{ todo.text }}
      </li>
    </ul>
    
    
  • 好例子

    
    <ul>
      <li
        v-for="todo in todos"
        :key="todo.id"
      >
        {{ todo.text }}
      </li>
    </ul>
    
    

5. 避免 v-if 和 v-for 用在同一个元素上

  • 为了过滤一个列表中的项目 (比如 v-for=“user in users” v-if=“user.isActive”)。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表

  • 为了避免渲染本应该被隐藏的列表 (比如 v-for=“user in users” v-if=“shouldShowUsers”)。这种情形下,请将 v-if 移动至容器元素上 (比如 ul、ol)。

  • 因为当vue处理指令时,v-for 比 v-if 具有更高的优先级,所以哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。

  • 反例

      
      <ul>
        <li
          v-for="user in users"
          v-if="user.isActive"
          :key="user.id"
        >
          {{ user.name }}
        </li>
      </ul>
      
      // 将会经过如下运算:
      this.users.map(function (user) {
        if (user.isActive) {
          return user.name
        }
      })
      
    
  • 好例子

    
    <ul>
      <li
        v-for="user in activeUsers"
        :key="user.id"
      >
        {{ user.name }}
      </li>
    </ul>
    
    computed: {
      activeUsers: function () {
        return this.users.filter(function (user) {
          return user.isActive
        })
      }
    }
    

6. 为组件样式设置作用域

  • 对于应用来说,顶级 App 组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。

  • 这条规则只和单文件组件有关。你不一定要使用 scoped attribute。设置作用域也可以通过 CSS Modules,那是一个基于 class 的类似 BEM 的策略,当然你也可以使用其它的库或约定。

  • 不管怎样,对于组件库,我们应该更倾向于选用基于 class 的策略而不是 scoped attribute

  • 这让覆写内部样式更容易:使用了常人可理解的 class 名称且没有太高的选择器优先级,而且不太会导致冲突。

  • 如果你和其他开发者一起开发一个大型工程,或有时引入三方 HTML/CSS (比如来自 Auth0),设置一致的作用域会确保你的样式只会运用在它们想要作用的组件上。

  • 不止要使用 scoped attribute,使用唯一的 class 名可以帮你确保那些三方库的 CSS 不会运用在你自己的 HTML 上。比如许多工程都使用了 button、btn 或 icon class 名,所以即便你不使用类似 BEM 的策略,添加一个 app 专属或组件专属的前缀 (比如 ButtonClose-icon) 也可以提供很多保护。

  • 反例

    
    <template>
      <button class="btn btn-close">X</button>
    </template>
    
    <style>
    .btn-close {
      background-color: red;
    }
    </style>
    
    
  • 好例子

    
    // 使用 `scoped` attribute 
    
    <template>
      <button class="button button-close">X</button>
    </template>
    
    
    <style scoped>
    .button {
      border: none;
      border-radius: 2px;
    }
    
    .button-close {
      background-color: red;
    }
    </style>
    
    
    
    // 使用 CSS Modules 
    
    <template>
      <button :class="[$style.button, $style.buttonClose]">X</button>
    </template>
    
    <style module>
    .button {
      border: none;
      border-radius: 2px;
    }
    
    .buttonClose {
      background-color: red;
    }
    </style>
    
    
    
    // 使用 BEM 约定 
    
    <template>
      <button class="c-Button c-Button--close">X</button>
    </template>
    
    <style>
    .c-Button {
      border: none;
      border-radius: 2px;
    }
    
    .c-Button--close {
      background-color: red;
    }
    </style>
    
    

7. 私有 property 名

  • 使用模块作用域保持不允许外部访问的函数的私有性。如果无法做到这一点,就始终为插件、混入等不考虑作为对外公共API的自定义私有property 使用 $_ 前缀。并附带一个命名空间以回避和其他作者的冲突(比如 $_yourPluginName_

  • 详解

    • Vue 使用 _ 前缀来定义其自身的私有 property,所以使用相同的前缀 (比如 _update) 有覆写实例 property 的风险。即便你检查确认 Vue 当前版本没有用到这个 property 名,也不能保证和将来的版本没有冲突。
    • 对于 $ 前缀来说,其在 Vue 生态系统中的目的是暴露给用户的一个特殊的实例 property,所以把它用于私有 property 并不合适。
    • 不过,我们推荐把这两个前缀结合为 $_,作为一个用户定义的私有 property 的约定,以确保不会和 Vue 自身相冲突。
  • 反例

    
    // 反例 1
    
    var nyGreatMixin= {
        // ...
        methods:{
      	  update:function(){
      		  // ....
      	  }
        }
    }
    
    
     // 反例 2
    
    var myGreatMixin = {
      // ...
      methods: {
        _update: function () {
          // ...
        }
      }
    }
    
     // 反例 3
    
    var myGreatMixin = {
      // ...
      methods: {
        $update: function () {
          // ...
        }
      }
    }
    
    
    // 反例 4
    
    
    var myGreatMixin = {
      // ...
      methods: {
        $_update: function () {
          // ...
        }
      }
    }
    
    
  • 好例子

    
    var myGreatMixin={
        // ...
        methods:{
      	  $_myGreatMixin_update:function(){
      		  // ...
      	  }
        }
    }
    
    

// 甚至更好!
var myGreatMixin = {
// …
methods: {
publicMethod() {
// …
myPrivateFunction()
}
}
}

function myPrivateFunction() {
// …
}

export default myGreatMixin


# 8、组件文件

* 只要有能够拼接文件的构建系统,就把每个组件单独分成文件
* 当你需要编辑一个组件或查阅一个组件的用法时,可以更快速的找到它
* 反例

```javascript

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

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

  • 好例子

    
    components/
    |- TodoList.js
    |- TodoItem.js
    
    components/
    |- TodoList.vue
    |- TodoItem.vue
    
    

9、单文件组件文件名的大小写

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

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

  • 反例

    
    components/
    |- mycomponent.vue
    
    components/
    |- myComponent.vue
    
    
  • 好例子

    
    components/
    |- MyComponent.vue
    
    components/
    |- my-component.vue
    
    

10、基础组件名

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

  • 这些组件为你的应用奠定了一致的基础样式和行为。它们可能只包括

    • HTML 元素
    • 其它基础组件
    • 第三方 UI 组件库
  • 但是它们绝不会包括全局状态 (比如来自 Vuex store)

  • 它们的名字通常包含所包裹元素的名字 (比如 BaseButton、BaseTable),除非没有现成的对应功能的元素 (比如 BaseIcon)。如果你为特定的上下文构建类似的组件,那它们几乎总会消费这些组件 (比如 BaseButton 可能会用在 ButtonSubmit 上)。

  • 这样做的几个好处

    • 当你在编辑器中以字母顺序排序时,你的应用的基础组件会全部列在一起,这样更容易识别。
    • 因为组件名应该始终是多个单词,所以这样做可以避免你在包裹简单组件时随意选择前缀 (比如 MyButton、VueButton)
  • 反例

    
    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
    
    

11、单例组件名

  • 只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性

    这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。

  • 反例

    components/
    |- Heading.vue
    |- MySidebar.vue
    
  • 好例子

    components/
    |- TheHeading.vue
    |- TheSidebar.vue
    

紧密耦合的组件名

  • 和父组件紧密耦合的子组件应该以父组件名作为前缀命名。

  • 如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。

  • 详解

    • 可以试着通过在其父组件命名的目录中嵌套子组件以解决这个问题

      components/
      |- TodoList/
         |- Item/
            |- index.vue
            |- Button.vue
         |- index.vue
         
       或者  
         
        components/
        |- TodoList/
           |- Item/
              |- Button.vue
           |- Item.vue
        |- TodoList.vue 
      
    • 但是这种方式并不推荐,因为这会导致:

      • 许多文件的名字相同,使得在编辑器中快速切换文件变得困难。
      • 过多嵌套的子目录增加了在编辑器侧边栏中浏览组件所花的时间
  • 反例

    components/
    |- TodoList.vue
    |- TodoItem.vue
    |- TodoButton.vue
    
    //或者
    
    components/
    |- SearchSidebar.vue
    |- NavigationForSearchSidebar.vue
    
  • 好例子

    components/
    |- TodoList.vue
    |- TodoListItem.vue
    |- TodoListItemButton.vue
    
    或者
    
    components/
    |- SearchSidebar.vue
    |- SearchSidebarNavigation.vue
    
    

12、组件名中的单词顺序

  • 组件名应该以高级别的(通常是一般化描述)单词开头,以描述性的修饰词结尾

  • 例如,针对搜索比较好的的命名

    components/
    |- SearchButtonClear.vue
    |- SearchButtonRun.vue
    |- SearchInputExcludeGlob.vue
    |- SearchInputQuery.vue
    |- SettingsCheckboxLaunchOnStartup.vue
    |- SettingsCheckboxTerms.vue
    
    // 因为编辑器通常会按照字母顺序组织文件,所以现在组件之间的重要关系一目了然
    // 如果换成多级目录的方式,比如把所有的搜索组件放到"search"目录,把所有的设置组件放到"setting"目录,我们只考虑在非常大型(如有100+个组件)的应用下才考虑这么做,因为:
    
       // 在多级目录间找来找去,要比在单个 components 目录下滚动查找要花费更多的精力。
       // 存在组件重名 (比如存在多个 ButtonDelete 组件) 的时候在编辑器里更难快速定位。
       // 让重构变得更难,因为为一个移动了的组件更新相关引用时,查找/替换通常并不高效。
    
    
  • 反例

    components/
    |- ClearSearchButton.vue
    |- ExcludeFromSearchInput.vue
    |- LaunchOnStartupCheckbox.vue
    |- RunSearchButton.vue
    |- SearchInput.vue
    |- TermsCheckbox.vue
    

13、自闭合组件

  • 在单文件组件、字符串模板和JSX中没有内容的组件应该是自闭和的–但是在DOM模板里永远不要这么做
  • 自闭和组件标识它们不仅没有内容,而且刻意没有内容,其不同之处就好像书上的一张白纸对比贴有"本页有意留白"标签的白纸,而且没有额外的闭合标签,代码会更加整洁。
  • HTML 并不支持自闭合的自定义元素——只有官方的“空”元素。所以上述策略仅适用于进入 DOM 之前 Vue 的模板编译器能够触达的地方,然后再产出符合 DOM 规范的 HTML。
  • 标准案例
    <!-- 在单文件组件、字符串模板和 JSX-->
    <MyComponent/>
    
    <!--DOM 模板中 -->
    <my-component></my-component>
    

14、模板中的组件名大小写

  • 对于绝大多数项目来说,在单文件组件和字符串模板中组件名应该总是 PascalCase(大骆驼拼写法) 的——但是在 DOM 模板中总是 kebab-case(短横拼写法)的

  • PascalCase相比kebab-case有一些优势:

    • 编译器可以在模板里自动补全组件名,因为PascalCase同样适用于JavaScript
    • 视觉上比 更能够和单个单词的 HTML 元素区别开来,因为前者的不同之处有两个大写字母,后者只有一个横线。
    • 如果你在模板中使用任何非 Vue 的自定义元素,比如一个 Web Component,PascalCase 确保了你的 Vue 组件在视觉上仍然是易识别的
  • 不幸的是,由于 HTML 是大小写不敏感的,在 DOM 模板中必须仍使用 kebab-case。

  • 反例

    <!-- 在单文件组件和字符串模板中 -->
    <mycomponent/>
    
    <!-- 在单文件组件和字符串模板中 -->
    <myComponent/>
    
    <!--DOM 模板中 -->
    <MyComponent></MyComponent>
    
    
  • 好例子

    !-- 在单文件组件和字符串模板中 -->
    <MyComponent/>
    
    <!--DOM 模板中 -->
    <my-component></my-component>
    
    // 或者
    
    <!-- 在所有地方 -->
    <my-component></my-component>
    

15、JS/JSX 中的组件名大小写

  • JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用 Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串。

  • 在 JavaScript 中,PascalCase 是类和构造函数 (本质上任何可以产生多份不同实例的东西) 的命名约定。Vue 组件也有多份实例,所以同样使用 PascalCase 是有意义的。额外的好处是,在 JSX (和模板) 里使用 PascalCase 使得代码的读者更容易分辨 Vue 组件和 HTML 元素。

  • 然而,对于只通过 Vue.component 定义全局组件的应用来说,我们推荐 kebab-case 作为替代。原因是:

    • 全局组件很少被 JavaScript 引用,所以遵守 JavaScript 的命名约定意义不大。
    • 这些应用往往包含许多 DOM 内的模板,这种情况下是必须使用 kebab-case 的。
  • 反例

    Vue.component('myComponent', {
      // ...
    })
    
    import myComponent from './MyComponent.vue'
    
    export default {
      name: 'myComponent',
      // ...
    }
    
    export default {
      name: 'my-component',
      // ...
    }
    
  • 好例子

    Vue.component('MyComponent', {
      // ...
    })
    
    Vue.component('my-component', {
      // ...
    })
    
    import MyComponent from './MyComponent.vue'
    
    export default {
      name: 'MyComponent',
      // ...
    }
    

16. 完整单词的组件名

  • 组件名应该趋向于完整单词而不是缩写

  • 编辑器中的自动补全已经让书写长命名的代价非常之低了,而其带来的明确性却是非常宝贵的,不常用的缩写尤其应该避免

  • 反例

    components/
    |- SdSettings.vue
    |- UProfOpts.vue
    
  • 好例子

    components/
    |- StudentDashboardSettings.vue
    |- UserProfileOptions.vue
    

17. Prop名大小写

  • 在声明prop的时候,其命名应该始终使用camelCase,而在模块和JSX中应该始终使用kebab-case

  • 在 JavaScript 中更自然的是 camelCase。而在 HTML 中则是 kebab-case。

  • 反例

    props: {
      'greeting-text': String
    }
    <WelcomeMessage greetingText="hi"/>
    
  • 好例子

    props: {
      greetingText: String
    }
      <WelcomeMessage greeting-text="hi"/>
    

18. 多个attribute的元素

  • 多个attribute的元素应该分多行撰写,每个attribute一行

  • 反例

    <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"
    />
    
    

19. 模板中简单的表达式

  • 组件模板(template中)应该只包含简单的表达式,复杂的表达式则应该重构为计算属性和方法

  • 复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

  • 反例

    {{
      fullName.split(' ').map(function (word) {
        return word[0].toUpperCase() + word.slice(1)
      }).join(' ')
    }}
    
  • 好例子

    <!-- 在模板中 -->
    {{ normalizedFullName }}
    
    // 复杂表达式已经移入一个计算属性
    computed: {
      normalizedFullName: function () {
        return this.fullName.split(' ').map(function (word) {
          return word[0].toUpperCase() + word.slice(1)
        }).join(' ')
      }
    }
    

20. 简单的计算属性

  • 把复杂的计算属性分割为尽可能多的更简单的property

  • 反例

    computed: {
      price: function () {
        var basePrice = this.manufactureCost / (1 - this.profitMargin)
        return (
          basePrice -
          basePrice * (this.discountPercent || 0)
        )
      }
    }
    
  • 好例子

    computed: {
      basePrice: function () {
        return this.manufactureCost / (1 - this.profitMargin)
      },
      discount: function () {
        return this.basePrice * (this.discountPercent || 0)
      },
      finalPrice: function () {
        return this.basePrice - this.discount
      }
    }
    

21. 带引号的attribute值

  • 非空 HTML attribute 值应该始终带引号

  • 在html中,不带空格的attribute,attribute值是可以没有引号的,但这鼓励了大家在特征值里不写空格,导致可读性变差。

  • 反例

    <input type=text>
    <AppSidebar :style={width:sidebarWidth+'px'}>
    
  • 好例子

    <input type="text">
    <AppSidebar :style="{ width: sidebarWidth + 'px' }">
    

22. 指令缩写

  • 指令缩写 (用 : 表示 v-bind:、用 @ 表示 v-on: 和用 # 表示 v-slot:) 应该要么都用要么都不用

  • 反例

    <input
      v-bind:value="newTodoText"
      :placeholder="newTodoInstructions"
    >
    
    <input
      v-on:input="onInput"
      @focus="onFocus"
    >
    
    <template v-slot:header>
      <h1>Here might be a page title</h1>
    </template>
    
    <template #footer>
      <p>Here's some contact info</p>
    </template>
    
  • 好例子

    <input
      :value="newTodoText"
      :placeholder="newTodoInstructions"
    >
    
    <input
      v-bind:value="newTodoText"
      v-bind:placeholder="newTodoInstructions"
    >
    
    <input
      @input="onInput"
      @focus="onFocus"
    >
    
    <input
      v-on:input="onInput"
      v-on:focus="onFocus"
    >
    
    <template v-slot:header>
      <h1>Here might be a page title</h1>
    </template>
    
    <template v-slot:footer>
      <p>Here's some contact info</p>
    </template>
    
    
    <template #header>
      <h1>Here might be a page title</h1>
    </template>
    
    <template #footer>
      <p>Here's some contact info</p>
    </template>
    
    

23. 组件/实例的选项的顺序 ***

  • 组件/实例的选项应该有统一的顺序
  1. 副作用(触发组件外的影响)
  • el
  1. 全局感知(要求组件以外的知识)
  • name
  • parent
  1. 组件类型(更改组件的类型)
  • functional
  1. 模板修改器(改变模板的编译方式)
  • delimiters
  • comments
  1. 模板依赖(模板内使用的资源)
  • components
  • directives
  • filters
  1. 组合(向选项里合并property)
  • extends
  • mixins
  1. 接口(组件的接口)
  • inheritAttrs
  • model
  • props/propsData
  1. 本地状态 (本地的响应式 property)
  • data
  • computed
  1. 事件 (通过响应式事件触发的回调)
  • watch
  • 生命周期钩子 (按照它们被调用的顺序)
    • beforeCreate
    • created
    • beforeMount
    • mounted
    • beforeUpdate
    • updated
    • activated
    • deactivated
    • beforeDestroy
    • destroyed
  1. 非响应式的 property (不依赖响应系统的实例 property)
  • methods
  1. 渲染(组件输出的声明式描述)
  • template/render
  • renderError

24. 元素 attribute 的顺序

  1. 定义(提供组件的选项)
  • is
  1. 列表渲染(创建多个变化的相同元素)
  • v-for
  1. 条件渲染(元素是否渲染和显示)
  • v-if
  • v-else-if
  • v-else
  • v-show
  • v-cloak
  1. 渲染方式(改变元素的渲染方式)
  • v-pre
  • v-once
  1. 全局感知
  • id
  1. 唯一的 attribute (需要唯一值的 attribute)
  • ref
  • key
  1. 双向绑定(把绑定和事件结合起来)
  • v-model
  1. 其它 attribute (所有普通的绑定或未绑定的 attribute)
  2. 事件 (组件事件监听器)
  • v-on
  1. 内容 (覆写元素的内容)
  • v-html
  • v-text

25. 组件/实例选项中的空行

当组件开始觉得密集或难以阅读时,在多个和property之间添加空行可以让其变得易读

26. 单文件组件的顶级元素的顺序

  • 单文件组件应该总是让

你可能感兴趣的:(Vue,vue.js,前端,javascript,react.js,java)