Vue.js 组件

组件(Component)是 Vue.js 最强大的功能之一。

组件可以扩展 HTML 元素,封装可重用的代码。

组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

Vue.js 组件 - 图1

注册一个全局组件语法格式如下:

  1. Vue.component(tagName, options)

tagName 为组件名,options 为配置选项。注册后,我们可以使用以下方式来调用组件:

  1. <tagName></tagName>

全局组件

所有实例都能用全局组件。

全局组件实例

注册一个简单的全局组件 jishuchi,并使用它:

  1. <div id="app">
  2. <jishuchi></jishuchi>
  3. </div>
  4. <script>
  5. // 注册
  6. Vue.component('jishuchi', {
  7. template: '<h1>自定义组件!</h1>'
  8. })
  9. // 创建根实例
  10. new Vue({
  11. el: '#app'
  12. })
  13. </script>

局部组件

我们也可以在实例选项中注册局部组件,这样组件只能在这个实例中使用:

局部组件实例

注册一个简单的局部组件 jishuchi,并使用它:

  1. <div id="app">
  2. <jishuchi></jishuchi>
  3. </div>
  4. <script>
  5. var Child = {
  6. template: '<h1>自定义组件!</h1>'
  7. }
  8. // 创建根实例
  9. new Vue({
  10. el: '#app',
  11. components: {
  12. // <jishuchi> 将只在父模板可用
  13. 'jishuchi': Child
  14. }
  15. })
  16. </script>

Prop

prop 是子组件用来接受父组件传递过来的数据的一个自定义属性。

父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 "prop":

Prop 实例

  1. <div id="app">
  2. <child message="hello!"></child>
  3. </div>
  4. <script>
  5. // 注册
  6. Vue.component('child', {
  7. // 声明 props
  8. props: ['message'],
  9. // 同样也可以在 vm 实例中像 "this.message" 这样使用
  10. template: '<span>{{ message }}</span>'
  11. })
  12. // 创建根实例
  13. new Vue({
  14. el: '#app'
  15. })
  16. </script>

动态 Prop

类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:

Prop 实例

  1. <div id="app">
  2. <div>
  3. <input v-model="parentMsg">
  4. <br>
  5. <child v-bind:message="parentMsg"></child>
  6. </div>
  7. </div>
  8. <script>
  9. // 注册
  10. Vue.component('child', {
  11. // 声明 props
  12. props: ['message'],
  13. // 同样也可以在 vm 实例中像 "this.message" 这样使用
  14. template: '<span>{{ message }}</span>'
  15. })
  16. // 创建根实例
  17. new Vue({
  18. el: '#app',
  19. data: {
  20. parentMsg: '父组件内容'
  21. }
  22. })
  23. </script>

以下实例中使用 v-bind 指令将 todo 传到每一个重复的组件中:

Prop 实例

  1. <div id="app">
  2. <ol>
  3. <todo-item v-for="item in sites" v-bind:todo="item"></todo-item>
  4. </ol>
  5. </div>
  6. <script>
  7. Vue.component('todo-item', {
  8. props: ['todo'],
  9. template: '<li>{{ todo.text }}</li>'
  10. })
  11. new Vue({
  12. el: '#app',
  13. data: {
  14. sites: [
  15. { text: 'Baidu' },
  16. { text: 'Google' },
  17. { text: 'Taobao' }
  18. ]
  19. }
  20. })
  21. </script>

注意: prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。

Prop 验证

组件可以为 props 指定验证要求。

为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

  1. Vue.component('my-component', {
  2. props: {
  3. // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
  4. propA: Number,
  5. // 多个可能的类型
  6. propB: [String, Number],
  7. // 必填的字符串
  8. propC: {
  9. type: String,
  10. required: true
  11. },
  12. // 带有默认值的数字
  13. propD: {
  14. type: Number,
  15. default: 100
  16. },
  17. // 带有默认值的对象
  18. propE: {
  19. type: Object,
  20. // 对象或数组默认值必须从一个工厂函数获取
  21. default: function () {
  22. return { message: 'hello' }
  23. }
  24. },
  25. // 自定义验证函数
  26. propF: {
  27. validator: function (value) {
  28. // 这个值必须匹配下列字符串中的一个
  29. return ['success', 'warning', 'danger'].indexOf(value) !== -1
  30. }
  31. }
  32. }
  33. })

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

type 也可以是一个自定义构造器,使用 instanceof 检测。


自定义事件

父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件!

我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件

另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。

以下实例中子组件已经和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件。

实例

  1. <div id="app">
  2. <div id="counter-event-example">
  3. <p>{{ total }}</p>
  4. <button-counter v-on:increment="incrementTotal"></button-counter>
  5. <button-counter v-on:increment="incrementTotal"></button-counter>
  6. </div>
  7. </div>
  8. <script>
  9. Vue.component('button-counter', {
  10. template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
  11. data: function () {
  12. return {
  13. counter: 0
  14. }
  15. },
  16. methods: {
  17. incrementHandler: function () {
  18. this.counter += 1
  19. this.$emit('increment')
  20. }
  21. },
  22. })
  23. new Vue({
  24. el: '#counter-event-example',
  25. data: {
  26. total: 0
  27. },
  28. methods: {
  29. incrementTotal: function () {
  30. this.total += 1
  31. }
  32. }
  33. })
  34. </script>

如果你想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on 。例如:

  1. <my-component v-on:click.native="doTheThing"></my-component>

data 必须是一个函数

上面例子中,可以看到 button-counter 组件中的 data 不是一个对象,而是一个函数:

  1. data: function () {
  2. return {
  3. count: 0
  4. }
  5. }

这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例,如下所示:

实例

  1. <div id="components-demo3" class="demo">
  2. <button-counter2></button-counter2>
  3. <button-counter2></button-counter2>
  4. <button-counter2></button-counter2>
  5. </div>
  6. <script>
  7. var buttonCounter2Data = {
  8. count: 0
  9. }
  10. Vue.component('button-counter2', {
  11. /*
  12. data: function () {
  13. // data 选项是一个函数,组件不相互影响
  14. return {
  15. count: 0
  16. }
  17. },
  18. */
  19. data: function () {
  20. // data 选项是一个对象,会影响到其他实例
  21. return buttonCounter2Data
  22. },
  23. template: '<button v-on:click="count++">点击了 {{ count }} 次。</button>'
  24. })
  25. new Vue({ el: '#components-demo3' })
  26. </script>