[TOC]

基本使用

<div id="app">
  <!-- 3.使用组件 -->
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
</div>

<script>
     // 1.创建组件构造器对象
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>我是标题</h2>
        <p>我是内容11111</p>
        <p>我是内容22222</p>
      </div>`
  })

  // 2.注册组件(全局组件,可以在多个Vue的实例下使用)
  // 语法:Vue.component('组件的标签名',构造器名)
  Vue.component('my-cpn', cpnC)
</script>

局部组件

<div id="app">
  <cpn></cpn>
</div>

<div id="app2">
  <cpn></cpn>
</div>

<script>
  // 1.创建组件构造器
  const cpnC = Vue.extend({
    template: `
     <div>
       <h2>我是标题</h2>
       <p>我是内容</p>
     </div>`
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好'
    },
    // 在 vue 实例注册的组件是局部组件
    components: {
      // cpn:使用组件时的标签名
      cpn: cpnC
    }
  })

  const app2 = new Vue({
    el: '#app2',
  })
  </script>

父子组件

<div id="app">
  <cpn2></cpn2>
</div>

<script>
  // 1.创建第一个组件构造器(子组件)
  const cpnC1 = Vue.extend({
    template: `
      <div>
        <h2>我是标题1</h2>  
        <p>我是内容1</p>  
      </div>`
  })

  // 2.创建第二个组件构造器(父组件)
  const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>我是标题2</h2>  
        <p>我是内容2</p>
        <cpn1></cpn1>
      </div>`,
    components: {
      // 注册组件
      cpn1: cpnC1
    }
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好'
    }, 
    components: {
      cpn2: cpnC2,
    }
  })
</script>

简写注册

省去了调用 Vue.extend() 的步骤,实际上原理还是调用了 Vue.extend()

<div id="app">
  <cpn1></cpn1>
  <cpn2></cpn2>
</div>

<script>
  // 全局注册
  Vue.component('cpn1', {
    template: `
      <div>
        <h2>我是标题1</h2>  
        <p>我是内容1</p>  
      </div>`
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好'
    },
    // 局部注册
    components: {
      cpn2: {
        template: `
          <div>
            <h2>我是标题2</h2>  
            <p>我是内容2</p>  
          </div>`
      }
    }
  })
</script>

模板分离

  • 使用 script 标签
  • 使用 template 标签 ```vue


<a name="2tTnH"></a>
## 组件复用

组件中并不能用 Vue 实例中的属性,只能使用组件对象中的 data 属性。并且 data 属性必须是一个函数
```vue
<div id="app">
  <cpn1></cpn1>
</div>

<!-- 2.使用 template 标签 -->
<template id="cpn1">
  <div>
    <h2>{{title}}</h2>
  </div>
</template>

<script>
  // 注册全局组件
  Vue.component('cpn1', {
    template: '#cpn1',
    // data 属性必须是一个函数,并且返回一个对象
    data() {
      return {
        title: 'abc'
      }
    }
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好'
    }
  })
</script>

组件中的 data 为什么是函数?

因为组件是可以复用的,所以必须保持组件内的数据独立性。正是因为组件内的 data 是一个函数,每创建一个组件实例都会 new 一个新的 Object 实例

// 1.注册组件
Vue.component('counter', {
  template: '#counter',
  data() {
    return {
      counter: 0
    }
    // 如果不想数据独立,可以返回同一个对象(可以有,没必要)
    // return obj
  },
  methods: {
    increment() {
      this.counter++
    },
    decrement() {
      this.counter--
    }
  }
})

const app = new Vue({
  el: '#app',
  data: {
    message: '你好'
  }
})

组件通信

组件之间传递数据
image.png

父传子

通过 props 向子组件传递数据

注意:当使用 _v-bind_ 时,不支持驼峰式写法,但可以用‘-’连接, _v-bind: c-info_

基本用法

<!-- 语法::组件 props 的属性名="传递的数据" -->
<!-- 当使用实例中的 data 时需要使用 v-bind -->
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

<script>
  Vue.component('blog-post', {
    // 数组的写法
      props: ['title'],
      template: '<h3>{{ title }}</h3>'
    })
</script>

对象写法

对象的写法可以限制数据传递到子组件的数据类型

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

<script>
  Vue.component('blog-post', {
    // 对象的写法
      props: {
      // 传入一个函数可以实现自定义检测类型
         title: String,
      // 限制多种类型
      title: [String, Number],
    },
      template: '<h3>{{ title }}</h3>'
    })
</script>

多种属性

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

<script>
  Vue.component('blog-post', {
    // 对象的写法
      props: {
         title: {
        // 限制类型
           type: String,
        // 提供默认值
        default: 'Why Vue is so fun',
        // required 为 true 时,必须传一个值过来
        required: true,
      }
    },
      template: '<h3>{{ title }}</h3>'
    })
</script>

注意:当默认值是对象或者数组时,必须从一个工厂函数获取(高版本才有要求)

<script>
  Vue.component('blog-post', {
    // 对象的写法
      props: {
         cmovies: {
        type: Array,
        default() {
          return []
        }
      },
    },
      template: '<h3>{{ title }}</h3>'
    })
</script>

验证函数

验证传递过来的数据

<script>
  Vue.component('blog-post', {
    // 对象的写法
      props: {
      type: String,
      validator: function (val) {
        // 这个值必须匹配下列字符串中的一个
        return val === 'fade' || val === 'slide'
      },
      defalut:'slide'
    }
      template: '<h3>{{ title }}</h3>'
    })
</script>

子传父

通过 $emit 向父组件发送自定义事件

 <!-- 父组件模板 -->
<div id="app" :style="{ fontSize: postFontSize + 'em' }">
  <p>Hello World</p>
  <!-- 2.用来监听子组件发送出来的事件,通过 $event 来拿到传递过来的参数 -->
  <cpn @enlarge-text="postFontSize += $event"></cpn>
</div>

<!-- 子组件模板 -->
<template id="cpn">
    <div>
    <!-- 1.通过 $emit 发送一个事件,并附带参数,第二个值是可选的 -->
    <button v-on:click="$emit('enlarge-text', 0.1)">
          Enlarge text
        </button>
  </div>
</template>

<script>
  // 子组件
  const cpn = {
    template: '#cpn',
  }

  // 父组件
  const app = new Vue({
    el: '#app',
    data: {
      postFontSize: 1
    },
    components: {
      cpn
    },
  })
</script>

或者,如果这个事件处理函数是一个方法:

<cpn @enlarge-text="onEnlargeText"></cpn>

那么这个值将会作为第一个参数传入这个方法:

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

访问元素&组件

直接访问元素或组件中的数据,在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。

注意:节制地使用 $parent$children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信

访问根

在每个 new Vue 实例的子组件中,其根实例可以通过 $root property 进行访问。在绝大多数情况下,强烈推荐使用 Vuex 来管理应用的状态。

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})

所有的子组件都可以将这个实例作为一个全局 store 来访问或使用。

// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

子访父

$root 类似,$parent property 可以用来从一个子组件访问父组件的实例,以替代将数据以 prop 的方式传入子组件的方式。

this.$parent

组件化的目的是提高复用性,当子组件需要向父组件获取数据时,复用性就变低了。

父访子

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。

$refs

注意$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs

通过 ref 这个 attribute 为子组件赋予一个 ID 引用(同样也可以在组件内部定义)

<base-input ref="usernameInput"></base-input>

通过 $refs 访问这个组件实例

this.$refs.usernameInput.focus()

refv-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。

$children
_
所有子组件都会存放在一个数组里面,通过 $children 来拿到这个数组

for (let c of this.$children) {
   console.log(c.name);
}

组件插槽

基本使用

它允许你像这样合成组件:

<navigation-link>
  Your Profile
</navigation-link>

然后通过 <slot> 标签在组件中预留一个插槽

<a>
  <slot></slot>
</a>

组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”。插槽内可以包含任何模板代码

<a>
  <slot>Hello World</slot>
</a>

<navigation-link> 里面没有内容时, <slot> 的内容将会是默认内容

具名插槽

有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout> 组件:

<div class="container">
  <header>
    <!-- 我们希望把页头放这里 -->
  </header>
  <main>
    <!-- 我们希望把主要内容放这里 -->
  </main>
  <footer>
    <!-- 我们希望把页脚放这里 -->
  </footer>
</div>

<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot>我是默认内容</slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的 name“default”。

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

任何一种写法都会渲染出:

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。

然而,如果你希望更明确一些,仍然可以在一个 <template> 中包裹默认插槽的内容:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

注意 v-slot 只能添加在