组件基础

    组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。

    Element 是基于 Vue 开发的一个知名的第三方组件库,它能帮助我们更加快速的构建应用。

    安装:

    Hello World:

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="UTF-8">
    5. <!-- 引入样式 -->
    6. <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    7. </head>
    8. <body>
    9. <div id="app">
    10. <el-button @click="visible = true">按钮</el-button>
    11. <el-dialog :visible.sync="visible" title="Hello world">
    12. <p>欢迎使用 Element</p>
    13. </el-dialog>
    14. </div>
    15. </body>
    16. <!-- 先引入 Vue -->
    17. <script src="https://unpkg.com/vue/dist/vue.js"></script>
    18. <!-- 引入组件库 -->
    19. <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    20. <script>
    21. new Vue({
    22. el: '#app',
    23. data: function() {
    24. return { visible: false }
    25. }
    26. })
    27. </script>
    28. </html>

    使用组件

    组件的定义方式分为两种,全局定义和局部定义:

    • 全局组件定义在全局,在任意组件中都可以直接使用
    • 局部组件定义在组件内部,只能在当前组件使用
    • 建议把通用的组件定义在全局,把不通用的组件定义在局部

    注册:

    1. Vue.component('my-component', {
    2. template: '<div>A custom component!</div>'
    3. });
    4. // 创建根实例
    5. el: '#example'
    6. });

    在模板中使用组件:

    1. <div id="example">
    2. <my-component></my-component>
    3. </div>

    渲染结果:

    1. <div id="example">
    2. <div>A custom component!</div>
    3. </div>

    局部注册

    你不必把每个组件都注册到全局。你可以通过某个 Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件:

    注册:

    1. new Vue({
    2. // ...
    3. components: {
    4. // <my-component> 将只在父组件模板中可用
    5. 'my-component': {
    6. }
    7. }
    8. });

    使用:

    1. <div id="example">
    2. <div>A custom component!</div>
    3. </div>

    组件的模板

    • DOM 模板
    • 字符串模板
    • .vue 单文件组件中的 template 模板

    组件的 data 必须是函数

    构造 Vue 实例时传入的各种选项大多数都可以在组件里使用。只有一个例外:data 必须是函数。

    1. Vue.component('simple-counter', {
    2. template: '<button v-on:click="counter += 1">{{ counter }}</button>',
    3. data: function () {
    4. return { counter: 0 }
    5. }
    6. });
    7. new Vue({
    8. el: '#example-2'
    9. });
    • 组件无法访问外部作用域成员
    • 外部作用域也无法访问组件内部成员

    组件组合

    组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。
    在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的。

    prop 向下传递,事件向上传递


    组件化构建 TodoMVC


    在树形结构里面,组件之间有几种典型的关系:父子关系、兄弟关系、没有直接关系。

    相应地,组件之间有以下几种典型的通讯方案:

    • 直接的父子关系
    • 直接的父子关系
    • 没有直接关系
    • 利用 cookie 和 localstorage 进行通讯。
    • 利用 session 进行通讯。
    • 父传子 Props Down
    • 子通知父亲 Events Up

    • 通过 ref 父亲直接访问子组件

      • 给子组件起个 ref
      • 然后在父组件中通过 this.$refs.子组件ref名
    • 子组件可以在内部通过 this.$parent 直接访问父组件
    • 非父子关系
      • 事件通信 Events Bus
      • Global Bus
    • 集中式状态管理 Vuex

    父子组件通信:Props Down

    1. 在父组件中通过子组件标签属性传递数据

    1. <child message="hello!"></child>

    2. 在子组件显式地用 props 选项声明它预期的数据并使用

    camelCase vs. kebab-case

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

    1. Vue.component('child', {
    2. // 在 JavaScript 中使用 camelCase
    3. props: ['myMessage'],
    4. template: '<span>{{ myMessage }}</span>'
    5. });
    1. <!-- 在 HTML 中使用 kebab-case -->
    2. <child my-message="hello!"></child>

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

    动态 Prop

    与绑定到任何普通的 HTML 特性相类似,我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:

    1. <div>
    2. <input v-model="parentMsg">
    3. <br>
    4. <child v-bind:my-message="parentMsg"></child>
    5. </div>

    你也可以使用 v-bind 的缩写语法:

      初学者常犯的一个错误是使用字面量语法传递数值:

      1. <!-- 传递了一个字符串 "1" -->
      2. <comp some-prop="1"></comp>

      因为它是一个字面量 prop,它的值是字符串 “1” 而不是一个数值。如果想传递一个真正的 JavaScript 数值,则需要使用 v-bind,从而让它的值被当作 JavaScript 表达式计算:

      1. <!-- 传递真正的数值 -->
      2. <comp v-bind:some-prop="1"></comp>

      单向数据流

      Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

      另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

      在两种情况下,我们很容易忍不住想去修改 prop 中数据:

      1. Prop 作为初始值传入后,子组件想把它当作局部数据来用
      2. Prop 作为原始数据传入,由子组件处理成其它数据输出

      1. 定义一个局部变量,并用 prop 的值初始化它:

      1. props: ['initialCounter'],
      2. data: function () {
      3. return { counter: this.initialCounter }
      4. }

      2. 定义一个计算属性,处理 prop 的值并返回:

      1. // ...
      2. props: ['size'],
      3. computed: {
      4. normalizedSize: function () {
      5. }
      6. },

      !> 注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

      Prop 验证

      我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。
      要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组:

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

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

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

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


      父子组件通信:Events Up

      我们知道,父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。

      1. 在子组件中调用 $emit() 方法发布一个事件

      1. Vue.component('button-counter', {
      2. template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
      3. data: function () {
      4. return {
      5. counter: 0
      6. }
      7. },
      8. methods: {
      9. incrementCounter: function () {
      10. this.counter += 1
      11. // 发布一个名字叫 increment 的事件
      12. this.$emit('increment')
      13. }
      14. },
      15. });

      2. 在父组件中提供一个子组件内部发布的事件处理函数

      1. new Vue({
      2. el: '#counter-event-example',
      3. data: {
      4. total: 0
      5. },
      6. methods: {
      7. incrementTotal: function () {
      8. this.total += 1
      9. }
      10. }
      11. });

      3. 在使用子组件的模板的标签上订阅子组件内部发布的事件

      1. <div id="counter-event-example">
      2. <p>{{ total }}</p>
      3. <!--
      4. 订阅子组件内部发布的 increment 事件
      5. 当子组件内部 $commit('increment') 发布的时候,就会调用到父组件中的 incrementTotal 方法
      6. -->
      7. <button-counter v-on:increment="incrementTotal"></button-counter>
      8. </div>

      给组件绑定原生事件

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

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

      在一些情况下,我们可能会需要对一个 prop 进行“双向绑定”。
      就是当一个子组件改变了一个带 .sync 的 prop 的值时,这个变化也会同步到父组件中所绑定的值。

      在使用子组件的时候加上 .sync 修饰符:

      1. <comp :foo.sync="bar"></comp>

      在子组件内部更新 foo 的值时,显示的触发一个更新事件:

      1. this.$emit('update:foo', newValue);

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

      1. var bus = new Vue();
      1. // 触发组件 A 中的事件

      专业组件通信大杀器:Vuex


      使用插槽分发内容