5.组件

139 阅读6分钟

组件是降低复杂度和解决重用的问题,和iOS里面的自定义view的作用差不多。

组件的出现是为了实现以下两个目标:

  1. 降低整体复杂度,提升代码的可读性和可维护性
  2. 提升局部代码的可复用性

绝大部分情况下,一个组件就是页面中某个区域,组件包含该区域的:

  • 功能(JS代码)

  • 内容(模板代码)

  • 样式(CSS代码)

    要在组件中包含样式,需要构建工具的支撑

组件开发

创建组件

组件是根据一个普通的配置对象创建的,所以要开发一个组件,只需要写一个配置对象即可。

组件本质是和vue实例差不多的配置对象。

该配置对象和vue实例的配置是几乎一样

//组件配置对象
var myComp = {
  data(){
    return {
      // ...
    }
  },
  template: `....`
}

值得注意的是,组件配置对象和vue实例有以下几点差异:

  • el,因为组件不是用vue挂载的方式使用的,而是使用HTML标签的方式使用的。
  • data必须是一个函数,该函数返回的对象作为数据。而vue里面使用的data 这个配置对象,里面用的是 data:{具体的数据名称:数据}
  • 由于没有el配置,组件的虚拟DOM树必须定义在templaterender中,template里面使用的还是两个大括号的大胡子语法。template: `<button @click="count++">当前点击了{{count}}次</button>,

下面是创建一个组件,组件的使用如下:

var MyButton = {
        data() {
          return {
            count: 0,
          };
        },
        
        template: `<button @click="count++">当前点击了{{count}}次</button>`,
      };

组件里面的data为什么必须是一个函数呢?

在Vue组件中,data选项必须是一个函数,而不能直接是一个对象。这是因为Vue组件可以同时存在多个实例,如果直接使用对象形式的data选项,那么所有的实例将会共享同一个data对象,这样就会造成数据互相干扰的问题。

  因此,将data选项设置为函数可以让每个实例都拥有自己独立的data对象。当组件被创建多次时,每个实例都会调用该函数并返回一个新的data对象,从而保证了数据的隔离性。

  另外,data选项作为一个函数还具有一个重要的特性,就是它可以接收一个参数,这个参数是组件实例本身。这个特性在一些场景下非常有用,例如在定义组件时需要使用组件实例的一些属性或方法来计算初始数据。

  因此,为了避免数据共享和保证数据隔离性,以及方便使用组件实例的属性和方法,Vue组件中的data选项必须是一个函数。

注册组件

注册组件分为两种方式,一种是全局注册,一种是局部注册

全局注册

一旦全局注册了一个组件,整个应用中任何地方都可以使用该组件

全局注册的方式是:

// 参数1:组件名称,将来在模板中使用组件时,会使用该名称
// 参数2:组件配置对象
// 该代码运行后,即可在模板中使用组件
Vue.component('my-comp', myComp)

在模板中,可以使用组件了

<my-comp />
<!-- 或 -->
<my-comp></my-comp>

但在一些工程化的大型项目中,很多组件都不需要全局使用。 比如一个登录组件,只有在登录的相关页面中使用,如果全局注册,将导致构建工具无法优化打包 因此,除非组件特别通用,否则不建议使用全局注册

局部注册

局部注册就是哪里要用到组件,就在哪里注册

局部注册的方式是,在要使用组件的组件或实例中加入一个配置: 局部注册就是在组件内使用components来注册组件,这样这个被注册的组件就能够在当前的组件中使用了。

// 这是另一个要使用my-comp的组件
var otherComp = {
  components:{
    // 属性名为组件名称,模板中将使用该名称
    // 属性值为组件配置对象
    "my-comp": myComp
  },
  template: `
    <div>
      <!-- 该组件的其他内容 -->
      <my-comp></my-comp>
    </div>
  `;
}

应用组件

在模板中使用组件特别简单,把组件名当作HTML元素名使用即可。

但要注意以下几点:

  1. 组件必须有结束

组件可以自结束,也可以用结束标记结束,但必须要有结束

下面的组件使用是错误的:

<my-comp>
  1. 组件的命名

无论你使用哪种方式注册组件,组件的命名需要遵循规范。

组件可以使用kebab-case 短横线命名法,也可以使用PascalCase 大驼峰命名法

下面两种命名均是可以的

var otherComp = {
  components:{
    "my-comp": myComp,  // 方式1
    MyComp: myComp //方式2
  }
}

实际上,使用小驼峰命名法 camelCase也是可以识别的,只不过不符合官方要求的规范

使用PascalCase方式命名还有一个额外的好处,即可以在模板中使用两种组件名,小驼峰,大驼峰这里都能用。

var otherComp = {
  components:{
    MyComp: myComp
  }
}

模板中:

<!-- 可用 -->
<my-comp />
<MyComp />

因此,在使用组件时,为了方便,往往使用以下代码:

var MyComp = {
  //组件配置
}

var OtherComp = {
  components:{
    MyComp // ES6速写属性
  }
}

在组件的注册的时候,可以使用小写和-的方式,可以使用大驼峰,大驼峰还可以使用速写属性。

组件树

一个组件创建好后,往往会在各种地方使用它。它可能多次出现在vue实例中,也可能出现在其他组件中。

于是就形成了一个组件树

向组件传递数据

大部分组件要完成自身的功能,都需要一些额外的信息

比如一个头像组件,需要告诉它头像的地址,这就需要在使用组件时向组件传递数据

传递数据的方式有很多种,最常见的一种是使用组件属性 component props,data是自己提供的数据,而props是外部提供的数据。组件属性也会被注入,比方说下面的p1,p2,p3.

首先在组件中申明可以接收哪些属性:

var MyComp = {
  props:["p1", "p2", "p3"],
  // 和vue实例一样,使用组件时也会创建组件的实例
  // 而组件的属性会被提取到组件实例中,因此可以在模板中使用
  template: `
    <div>
      {{p1}}, {{p2}}, {{p3}}
    </div>
  `
}

在使用组件时,向其传递属性:

var OtherComp = {
  components: {
    MyComp
  },
  data(){
    return {
      a:1
    }
  },
  template: `
    <my-comp :p1="a" :p2="2" p3="3"/>
  `
}

注意:在组件中,属性是只读的,绝不可以更改,这叫做单向数据流

工程结构

index.html

<body>
    <div id="app"></div>
    <script src="./src/main.js" type="module"></script>
  </body>

main.js

// 入口模块文件
// 创建vue实例
import Vue from "./vue.browser.js";
import App from "./App.js";

new Vue({
  //下面的步骤可以等同于先局部注册,然后再写一个模版把他放进去。一般也只有这个地方会写一下render方法
  render: (h) => h(App), // 渲染组件App
}).$mount("#app");

App.js

import MyButton from "./components/MyButton.js";
// vue的根组件
const template = `
<div>
  <h1>App组件</h1>
  <MyButton />
</div>
`;
//组件就是一个有了一些配置的JS对象。至少要有个模版吧。
export default {
  components: {
    MyButton,
  },
  template,
};

MyButton.js

const template = `<button @click="count++">当前点击了{{count}}次</button>`;

export default {
  data() {
    return {
      count: 0,
    };
  },
  template,
};