一、组件基础

1.1 组件的使用

组件是可复用的 Vue 实例,且带有一个名字。

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

一个组件的data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。

组件的template有三种写法:

<div id="app">
    <article-vue></article-vue>
    <article-vue2></article-vue2>
    <article-vue3></article-vue3>
</div>
<script type="text/x-template" id="temp2">
    <div>
        <h2>标题2</h2><p>这里是正文2...{{message}}</p>
    </div>
</script>
<template id="temp3">
    <div>
        <h2>标题3</h2><p>这里是正文3...</p>
    </div>
</template>
<script type="text/javascript">
    Vue.component('article-vue', {
        template: '<div><h2>标题</h2><p>这里是正文...</p></div>'
    });
    Vue.component('article-vue2', {
        template: '#temp2',
        data: function(){
            return {
                message: 'lazyrabbit'
            }
        }
    });

    var app = new Vue({
        el: '#app',
        data: {
            message: 'lazyrabbit'
        },
        components: {
            'article-vue3': {// vue不可以使用横线 ,所以使用引号引起来
                template: '#temp3'
            }
        }
    });
</script>

字符串模板:指的是在组件选项里用 template:”” 指定的模板,换句话说,写在 js 中的 template:”” 中的就是字符串模板

非字符串模板:在单文件里用 指定的模板,换句话说,写在 html 中的就是非字符串模板。

每个组件必须只有一个根元素。

1.2 组件名

Vue.component('my-component-name', { /* ... */ })

定义组件名的方式有两种:

1、使用 kebab-case (短横线分隔命名)

2、使用 PascalCase (首字母大写命名)

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)

1.3 全局注册

使用Vue.component注册的组件都是全局组件

1.4 局部注册

在需要注册的组件的components属性中定义

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

局部注册的组件在其子组件中不可用,若要使用则需要在子组件内部再使用components定义组件

二、通过 Prop 向子组件传递数据

2.1 Prop

Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个属性。

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名),如果使用的是字符串模板,那么这个限制就不存在了

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

<blog-post post-title="hello!"></blog-post>

prop 支持多种类型:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

2.2 传递静态或动态 Prop

直接在内部直接定义属性来传递静态值,也可以通过 v-bind 动态赋值。

<!-- 静态的值 -->
<blog-post title="My journey with Vue"></blog-post>

<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

2.3 传入一个对象

如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)

post: {
  id: 1,
  title: 'My Journey with Vue'
}

<blog-post v-bind="post"></blog-post>

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

三、监听子组件事件

3.1 $emit

子组件可以通过调用 $emit 方法并传入事件名称来触发一个父组件事件,父组件的名称为传入的第一个参数。

<div id="app">
    <article-vue v-bind='article' @more="more"></article-vue>
    <p>已被查看{{clickTimes}}次</p>
</div>

<template id="temp">
    <div>
        <h2>{{title}}</h2>
        <p>{{content}}<a href="javascript:void(0);" @click="getMore">...</a></p>
    </div>
</template>
<script type="text/javascript">

    Vue.component('article-vue',{
        template: '#temp',
        props: ['title','content'],
        methods: {
            getMore: function(){
                this.$emit('more');
            }
        }
    });

    var article = {
        title: '阿森纳主帅阿尔特塔确诊新冠状病毒',
        content: '阿森纳球员将自我隔离14天'
    }
    var app = new Vue({
        el: '#app',
        data: {
            article: article,
            clickTimes: 0
        },
        methods: {
            more: function(){
                this.clickTimes++;
            }
        }

    });
</script>

若想向父组件传入一个值,可以在 $emit 方法上传入第二个参数,当在父级组件监听这个事件的时候,这个值将会作为第一个参数传入这个方法。

3.2 事件名

不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的)。

官网推荐始终使用 kebab-case 的事件名。

3.3 在组件上使用 v-model

在html元素中使用v-model时 v:model实际是使用了bind和click事件来做到双向绑定

<input v-model="searchText">

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

在组件上时,v-model的实际效果:

<custom-input v-model="searchText"></custom-input>

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

因此,在组件上使用v-model指令时,需要在组件内部做以下两件事:

  1. 绑定属性
  2. input 事件被触发时,将新的值通过自定义的 input 事件抛出

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

<base-checkbox v-model="lovingVue"></base-checkbox>

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

3.4 .sync 修饰符

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。

子组件触发父组件的update:title事件并传递一个新的title值

this.$emit('update:title', newTitle)

在父组件中更新title

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

上边的写法等同于

<text-document v-bind:title.sync="doc.title"></text-document>

四、插槽

4.1

在模板内部可以使用元素作为承载分发内容的出口。

<div id="app">
    <article-vue v-bind='article' @more="more"><p>已被查看{{clickTimes}}次</p></article-vue>
</div>

<template id="temp">
    <div>
        <h2>{{title}}</h2>
        <p>{{content}}<a href="javascript:void(0);" @click="getMore">...</a></p>
        <slot></slot>
    </div>
</template>

4.2 编译作用域

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

4.3 默认内容

在slot元素内可以添加默认内容,使用组件时没有提供任何插槽内容时,默认内容就会生效。

<slot>已被查看0次</slot>

4.4 具名插槽

当组件内部有多个插槽时,可以指定slot的name属性。

一个不带 name 的 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个