2

Vue.js的组件

注册组件

全局注册

要注册一个全局组件,你可以使用 Vue.component(tagName, options)。例如:

Vue.component('my-component', {
  // 选项
})

组件在注册之后,便可以在父实例的模块中以自定义元素 < my-component >< /my-component > 的形式使用。要确保在初始化根实例之前注册了组件:

<div id="box">
  <my-component></my-component>
</div>
// 注册组件
Vue.component('my-component', {
    template: '<p>hi girl</p>'
})
// 创建根实例
new Vue({
    el: '#box'
})

结果

<div id="box"> 
    <p>hi,girl</p>
</div> 

局部注册

不必在全局注册每个组件。通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用:

var Child = {
  template: '<div>A custom component!</div>'
}
new Vue({
  // ...
  components: {
    'my-component': Child
  }
})

当我们感觉template里面写的东西太多了,我们还可以利用vue给我们提供的一个方法 x-template:

<script type="text/x-template" id="a">
// 这里面我们可以书写html
    <div>hi girl</div>
</script>
var Child = {
  template: '#a'
}

DOM 模板解析说明

当使用 DOM 作为模板时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。尤其像这些元素 < ul >,< ol >,< table >,< select > 限制了能被它包裹的元素,而一些像 < option > 这样的元素只能出现在某些其它元素内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>
    <my-row>...</my-row>
</table>

自定义组件 <my-row> 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:

<table>
    <tr is="my-row"></tr>
</table>

data 必须是函数

通过 Vue 构造器传入的各种选项大多数都可以在组件里用。data 是一个例外,它必须是函数。实际上,如果你这么做:

Vue.component('my-component', {
  template: '<span>{{ message }}</span>',
  data: {
    message: 'hello'
  }
})

那么 Vue 会停止,并在控制台发出警告,告诉你在组件中 data 必须是一个函数。理解这种规则的存在意义很有帮助,让我们假设用如下方式来绕开 Vue 的警告:

<div id="example-2">
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
</div>
var data = { counter: 0 }
Vue.component('simple-counter', {
  template: '<button v-on:click="counter += 1">{{ counter }}</button>',
  // 技术上 data 的确是一个函数了,因此 Vue 不会警告,
  // 但是我们返回给每个组件的实例却引用了同一个 data 对象
  data: function () {
    return data
  }
})
new Vue({
  el: '#example-2'
})
0 0 0

由于这三个组件共享了同一个 data,因此增加一个 counter 会影响所有组件!这不对。我们可以通过为每个组件返回全新的 data 对象来解决这个问题:

data: function () {
  return {
    counter: 0
  }
}

现在每个 counter 都有它自己内部的状态了:

0 0 0

构成组件

组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。
在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。看看它们是怎么工作的。

使用 props 传递数据

如果我们定义了组件本身的属性时,我们需要 props 来注册属性,这样我们才能利用属性

<div id="box">
    <mycom a="ddd"></mycom>   
</div>
Vue.component('mycom',{
    template:"<button v-on:click='sss'>{{ msg }}</button>",
    props:['a'],
    metods:{
        sss:function(){
            alert(this.a)
        }
    }
    })

结果

ddd

父元素向子元素传递数据

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的 props 选项。

<div id="box">
    <mycom  v-bind:a="ddd"></mycom>   
</div>
Vue.component('mycom',{
    template:"<button v-on:click='sss'>{{ msg }}</button>",
    props:['a'],
    methods:{
        sss:function(){
            alert(this.a)
        }
    }
})
new Vue({
    el:'#box',
    data:{
        ddd:'hi'
    }
})

结果

hi

子元素向父元素传递数据

<div id="box">
    <mycom v-on:jieshou='gg'></mycom>  
</div>
Vue.component('mycom',{
    template:"<button v-on:click='sss'>{{ msg }}</button>",
    props:['a'],
    methods:{
        sss:function(){
            this.$emit('fff')
        }
    }
})
new Vue({
    el:'#box',
    data:{
        ddd:'hi'
    },
    methods:{
        gg:function(){
            alert(111)
        }
    }
})

结果

111

我们也可以在组件触发的函数上传入参数

Vue.component('mycom',{
    template:"<button v-on:click='sss'>{{ msg }}</button>",
    props:['a'],
    methods:{
        sss:function(){
            this.$emit('fff','a','b')
        }
    }
})
new Vue({
    el:'#box',
    data:{
        ddd:'hi'
    },
    methods:{
        gg:function(x,y){
            alert(x+y)
        }
    }
})

结果

ab

非父子关系传入数据

有时候两个组件也需要通信 (非父子关系)。在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线:

var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
  // ...
})

camelCase vs. kebab-case

HTML 特性是不区分大小写的。所以,当使用的不是字符串模板,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名:

Vue.component('child', {
  // camelCase in JavaScript
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>

如果你使用字符串模板,则没有这些限制。

Prop 验证

我们可以为组件的 props 指定验证规格。如果传入的数据不符合规格,Vue 会发出警告。当组件给其他人使用时,这很有用。
要指定验证规格,需要用对象的形式,而不能用字符串数组:

Vue.component('example', {
  props: {
    // 基础类型检测 (`null` 意思是任何类型都可以)
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },
    // 数字,有默认值
    propD: {
      type: Number,
      default: 100
    },
    // 数组/对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

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

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol
  1. 也可以是一个自定义构造器函数,使用 instanceof 检测。

当 prop 验证失败,Vue 会抛出警告 (如果使用的是开发版本)。注意 props 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还无法使用。

slot 内容分发

在使用组件时,我们常常要像这样组合它们:

<app>
    <app-header></app-header>
    <app-footer></app-footer>
</app>

注意两点:
< app > 组件不知道它会收到什么内容。这是由使用 < app > 的父组件决定的。
< app > 组件很可能有它自己的模板。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发 (或 “transclusion” 如果你熟悉 Angular)。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 < slot > 元素作为原始内容的插槽。

单个 Slot

除非子组件模板包含至少一个 < slot > 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。
最初在 < slot > 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
假定 my-component 组件有下面模板:

<div>
    <h2>我是子组件的标题</h2>
    <slot>
        只有在没有要分发的内容时才会显示。
    </slot>
</div>

父组件模板:

<div>
  <h1>我是父组件的标题</h1>
  <my-component>
    <p>这是一些初始内容</p>
    <p>这是更多的初始内容</p>
  </my-component>
</div>

渲染结果:

<div>
  <h1>我是父组件的标题</h1>
  <div>
    <h2>我是子组件的标题</h2>
    <p>这是一些初始内容</p>
    <p>这是更多的初始内容</p>
  </div>
</div>

具名 Slot

<slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。
例如,假定我们有一个 app-layout 组件,它的模板为:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

父组件模板:

<app-layout>
  <h1 slot="header">这里可能是一个页面标题</h1>
  <p>主要内容的一个段落。</p>
  <p>另一个主要段落。</p>
  <p slot="footer">这里有一些联系信息</p>
</app-layout>

渲染结果为:

<div class="container">
    <header>
        <h1>这里可能是一个页面标题</h1>
    </header>
    <main>
        <p>主要内容的一个段落。</p>
        <p>另一个主要段落。</p>
    </main>
    <footer>
        <p>这里有一些联系信息</p>
    </footer>
</div>

在组合组件时,内容分发 API 是非常有用的机制。


Smallmotor
478 声望33 粉丝

心若向阳