Skip to content

Vue component style Guide #24

@wangjing013

Description

@wangjing013

Vue 组件规范

Vue组件选项的顺序

export default {
  el: '#app',
  name: '',
  // 组件类型(函数式组件-无状态(data)-无实例(this))
  functional: false,

  //模版修改器
  delimiters: ["{{", "}}"],
  comments: false,

  //模版依赖
  components: {},
  directives: {},
  filters: {},

  // 组合
  extends: {},
  mixins: [],

  // 接口
  inheritAttrs: true,
  model: {},
  props: {},
  propsData: {},

  // 本地状态
  data: {},
  computed: {},

  // 事件
  watch: {},

  // 生命周期
  beforeCreate(){},
  created(){},
  beforeMount(){},
  mounted(){},
  beforeUpdate(){},
  update(){},
  activated(){},
  deactiveated(){},
  beforeDestroy(){},
  destroyed(){  },

  // 非响应式的 property
  methods: {},

  // 渲染
  template: '',
  render(){},
  renderError(){}
}

组件命名

组件的命名需遵从以下原则:

  • 有意义的: 不过于具体,也不过于抽象
  • 简短: 2~3单词, 保持单词完整(尽量不要简写)
  • 具有可读性

同时还需要注意:

  • 必须符合自定义元素规范: 使用kebab-case分隔单词,切勿使用保留字
<!-- 推荐 -->
<app-header></app-header>
<user-list></user-list>
<range-slider></range-slider>

<!-- 避免 -->
<btn-group></btn-group> <!-- 虽然简短但是可读性差. 使用 `button-group` 替代 -->
<ui-slider></ui-slider> <!-- ui 前缀太过于宽泛,在这里意义不明确 -->
<slider></slider> <!-- 与自定义元素规范不兼容 -->

文件夹名称

  • 文件名称始终保持 kebab-case 的格式. 文件包括(layout、views、utils 等)

Bad

|---views
|------Login
|------------components
|------------auth-redirect.vue
|------------index.vue

Good

|---views
|------login
|------------components
|------------auth-redirect.vue
|------------index.vue

Prop

1. Prop 定义

Prop定义应该尽量详细.

  • 是组件更容易读
  • 在开发环境下,传入不一样类型时会有警告,帮忙提前捕获潜在的错误来源.

Bad

props: ['status']

Good

props: {
  status: String
}

2. Prop名字大小写

声明 prop 的时候,其命名应该始终使用 camelCase(驼峰), 而在模版JSX中应该始终使用 kebab-case.

Bad

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

Good

  props: {
    'greetingText': String
  }
  <welcome-message greeting-text="hi"></welcome-message>

3. 组件 props 原子化

虽然 Vue 支持通过属性传递复杂JavaScript对象, 但是我们应该尽可能使用原始类型数据. 尽量只使用 JavaScript 基本上类型(strings, number, booleans) 和 functions. 避免使用复杂对象.

为什么?

  • 使组件API清晰直观.
  • 只使用基本类型和函数作为 props 使得组件 API 更接近原生 HTML 原生元素.
  • 方便其它开发更容易理解每个 prop 的含义和作用
  • 当传递复杂对象时使得我们并不知道哪些属性和方法被自定义组件使用,这会使得代码难以重构和维护.

怎么做 ?

<!-- recommended -->
<range-slider
  :values="[10, 20]"
  :min="0"
  :max="100"
  :step="5"
  @on-slide="updateInputs"
  @on-end="updateResults">
</range-slider>

<!-- avoid -->
<range-slider :config="complexConfigObject"></range-slider>

验证组件的 props

在 Vue.js 中,组件的 props 即 API,一个稳定并可预测的 API 会使得你的组件更容易被其他开发者使用.

为什么?

验证组件 props 可以保证你的组件永远是可用的(防御性编程) .即使其他开发者并未按照你预想的方法使用时也不会出错

怎么做?

  • 提供默认值
  • 使用 type 属性校验类型
  • 使用 props 之前先检查该 prop 是否存在
<template>
  <input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
  export default {
    props: {
      max: {
        type: Number, // 这里添加了数字类型的校验
        default() { return 10; },
      },
      min: {
        type: Number,
        default() { return 0; },
      },
      value: {
        type: Number,
        default() { return 4; },
      },
    },
  };
</script>

data

组件的 data 必须是一个函数. 防止在组件在多例情况下 data 被共用. 例外应用root组件可以是对象.

Bad

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

Good

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

组件样式

为组件样式设置作用域

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

这条规则只和单文件组件有关, 可以使用下面方式

  • scoped
  • CSS MOdules(基于class的类似BEM的策略)
  • BEM

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

这让覆写内部样式更容易。

Bad

<template>
  <button class="btn btn-close">X</button>
</template>

<style>
.btn-close {
  background-color: red;
}
</style>

Good

<template>
  <button class="button button-close">X</button>
</template>

<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
  border: none;
  border-radius: 2px;
}

.button-close {
  background-color: red;
}
</style>

Template

保持模版中表达式简单

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法.

Bad

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

Good

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

Computed

简单的计算属性

应该把复杂计算属性分割成尽可能多的更简单的 property.

  • 易于测试
  • 易于阅读
  • 更好的"拥抱变化"

Bad

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

Good

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

directive

指令缩写

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

Bad

<input
  v-bind:value="newTodoText"
  :placeholder="newTodoInstructions"
>

Good

<input
  :value="newTodoText"
  :placeholder="newTodoInstructions"
>
<!--or-->
<input
  v-bind:value="newTodoText"
  v-bind:placeholder="newTodoInstructions"
>

避免使用this.$parent

Vue.js 支持组件嵌套,并且子组件可访问父组件的上下文. 访问组件之外的上下文违反了基于模块开发的第一原则. 因此你应该尽量避免使用 this.$parent。

为什么 ?

  • 组件都应该都保持独立, 如果一个组件需要访问父组件,则这规则将被打破.
  • 如果一个组件需要访问它都父组件, 它就不能再在不同的上下文中被重用.

怎么做 ?

  • 通过 props 将值传递给子组件中
  • 通过 props 传递回调函数给子组件中调用
  • 通过子组件中发射都事件来通知父组件

请谨慎使用this.$refs

Vue 支持组件通过 ref 属性去访问其它组件和HTML元素. 并通过 this.$refs 去访问组件和DOM 元素的上下文.
在大多情况,通过 this.$refs 去访问其它组件的上下文是可以避免的.

为什么 ?

  • 组件都应该都保持独立, 如果一个组件不支持所有访问需求,该组件可能设计或实现有问题.
  • Prop 和 Events 对于大多数组件来说应该足够了

怎么做 ?

  • 创建良好组件API
  • 设计组件时应该考虑组件的用途(目的)
  • 不要定制代码,如果需要在通用组件中编写特定代码,这意味着它的API不够通用, 或者您可能需要一个新组件来管理其他情况.
  • 检查所有的 props 是否有缺失,如果有缺失抛出问题或完善组件.
  • 检查所有事件, 子与父通信通常是通过事件来的(events), 大多数开发者只关注 props 而忽略这一点.
  • Props 向下传递, Events 向上传递,这样父子之间是隔离的,这样尽可能保证他们都能独立运行.
  • propsevents 不能满足需求时, 才考虑使用 this.$refs
  • 当 element 不能通过数据绑定或指令方式去操作时可以使用 this.$refs 而不是 document.getElement*.
<!-- good, no need for ref -->
<range :max="max"
  :min="min"
  @current-value="currentValue"
  :step="1"></range>
<!-- good example of when to use this.$refs -->
<modal ref="basicModal">
  <h4>Basic Modal</h4>
  <button class="primary" @click="$refs.basicModal.hide()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>

<!-- Modal component -->
<template>
  <div v-show="active">
    <!-- ... -->
  </div>
</template>

<script>
  export default {
    // ...
    data() {
        return {
            active: false,
        };
    },
    methods: {
      open() {
        this.active = true;
      },
      hide() {
        this.active = false;
      },
    },
    // ...
  };
</script>
<!-- avoid accessing something that could be emitted -->
<template>
  <range :max="max"
    :min="min"
    ref="range"
    :step="1"></range>
</template>

<script>
  export default {
    // ...
    methods: {
      getRangeCurrentValue() {
        return this.$refs.range.currentValue;
      },
    },
    // ...
  };
</script>

将this赋值给component变量

在 Vue.js 组件上下文中, this指向了组件实例. 因此当你切换到了不同的上下文时, 要确保 this 指向一个可用的 component 变量。

换句话说, 如果你正在使用 ES6 的话,就不要再编写 var self = this; 这样的代码了,您可以安全地使用 Vue 组件

为什么?

  • 使用 ES6, 不需要把 this 保存到变量中.
  • 当使用箭头函数时,它词法作用则为当前上下文.
  • 仅仅当不能使用 ES6 和 箭头函数时,才去考虑把 this 保存到变量中.

怎么做 ?

<script type="text/javascript">
export default {
  methods: {
    hello() {
      return 'hello';
    },
    printHello() {
      console.log(this.hello());
    },
  },
};
</script>

<!-- avoid -->
<script type="text/javascript">
export default {
  methods: {
    hello() {
      return 'hello';
    },
    printHello() {
      const self = this; // unnecessary
      console.log(self.hello());
    },
  },
};
</script>

组件事件名称

每个组件事件名称应该遵循良好的名字方式,以避免的开发过程中出现问题. 原因如下:

为什么 ?

  • 开发人员随意使用文件名,即使用原生的事件名称,这可能会引起混乱
  • 过于宽泛的事件名称,可能会造成 DOM模版不兼容的问题

怎么做 ?

  • 事件名称格式使用 kebab-cased格式
  • 事件名称应该表示当前操作,例如(upload-error or dropzone-upload-success)

组件使用

1. 自闭合组件

单文件组件、字符串模版、JSX 中没有内容的组件应该是自闭合的 --- DOM 模版中就不要这样做.

Bad

<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent></MyComponent>

<!-- 在 DOM 模板中 -->
<my-component/>

Good

<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent/>

<!-- 在 DOM 模板中 -->
<my-component></my-component>

2. 模版中的组件名大小写

在单文件组件和字符串模版中组件名应该总是PascalCase的.

DOM模版中总是 kebab-case 的.

bad

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

Good

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

3. JS/JSX中组件名大小写

JS/JSX中的组件名应该始终是PascalCase的.

bad

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

import myComponent from './MyComponent.vue'

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

export default {
  name: 'my-component',
  // ...
}

Good

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

import MyComponent from './MyComponent.vue'

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions