05月25, 2021

Vue实战(四)-- Vue 组件

四、Vue 组件

ES6知识补充

  • ES6模块化
    • 没有模块化的世界:全局变量污染、难以管理的依赖
    • 常见的模块化标准:CommonJS、ES6 Module、AMD、CMD、UMD。。。

组件概念

一个完整的网页是复杂的,如果将其作为一个整体来进行开发,将会遇到下面的困难

  • 代码凌乱臃肿
  • 不易协作
  • 难以复用

vue推荐使用一种更加精细的控制方案——组件化开发

所谓组件化,即把一个页面中区域功能细分,每一个区域成为一个组件,每个组件包含:

  • 功能(JS代码)
  • 内容(模板代码)
  • 样式(CSS代码)

由于没有构建工具的支撑,CSS代码暂时无法放到组件中

组件开发

创建组件

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

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

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

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

  • el
  • data必须是一个函数,该函数返回的对象作为数据
  • 由于没有el配置,组件的模板必须定义在template
  • template中的所有HTML元素必须且只能有一个根元素

注册组件

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

全局注册

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

全局注册的方式是:

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

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

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

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

局部注册

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

局部注册的方式是,在要使用组件的组件或实例中加入一个配置:

// 这是另一个要使用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速写属性
  }
}

注意,PascalCase命名不可以直接在html中使用,但可以在template配置中使用 想想为什么?

组件树

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

于是就形成了一个组件树

向组件传递数据

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

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

传递数据的方式有很多种,最常见的一种是使用组件属性 component props

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

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"/>
  `
}

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

组件的生命周期

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

组件生命周期 beforeCreate:在实例开始初始化时同步调用。此时数据观测、事件等都尚未初始化。

created:在实例创建之后调用。此时已完成数据观测、事件方法,但尚未开始DOM编译,即未挂载到document中。

beforeMount:在mounted之前运行。

mounted:在编译结束时调用。此时所有指令已生效,数据变化已能触发DOM更新,但不保证$el已插入文档。

beforeUpdate:在实例挂载之后,再次更新实例(例如更新 data)时会调用该方法,此时尚未更新DOM结构。

updated:在实例挂载之后,再次更新实例并更新完DOM结构后调用。

beforeDestroy:在开始销毁实例时调用,此刻实例仍然有效。

destroyed:在实例被销毁之后调用。此时所有绑定和实例指令都已经解绑,子实例也被销毁。

工程结构

调整了文件夹结构,适应工程化 -w209 将自定义组件放在components文件夹中,libs是需要引入的vue.js源文件(注意是支持模块化的源文件),main.js是主要的文件,index.html中引入了main.js

UserInfo.js

var template = `<div>
<p>姓名:{{username}}</p>
<p>年龄:{{age}}</p>
</div>`;

export default{
    data(){
        return {
            username:"张三",
            age:17
        }
    },
    template:template
}

main.js

import Vue from './libs/vue-module.js';
import UserInfo from './components/UserInfo.js'

var template = `
<div>
    <h1>{{title}}</h1>
    <UserInfo></UserInfo>
</div>
`;

new Vue({
    el:"#app",
    components:{UserInfo},
    data:{
        title:"模块化"
    },
    template
});

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="src/main.js" type="module"></script>
</html>

这里的UserInfo组件并不能接收参数,作为一个组件,最好能接收参数.

这其实就是组件和组件之间的传值问题,那么现在是父组件往子组件传值

加入一个新的组件UserInfoParam.js

var template = `<div>
<p>姓名:{{username}}</p>
<p>电话:{{tel}}</p>
</div>`;

export default{
    props:["username","tel"], // 需要调用页面传送过来的变量名
    template:template
}

将项目结构再次做出修改,不需要在main.js中加入太多代码,main.js的功能只在于启动程序 -w215 现在代码主要写在app.js文件中,而main.js只负责引用vue.js与引用app .js

main.js

import Vue from './libs/vue-module.js';
import App from './app.js'

new Vue({  
    render:(h)=>h(App)
}).$mount("#app");

app.js

import UserInfo from './components/UserInfo.js'
import UserInfoParam from './components/UserInfoParam.js'

var template = `<div>
<h1>{{title}}</h1>
<UserInfo></UserInfo>
<hr />
<UserInfoParam v-for="(item,i) in users" :key="i" :username="item.username" :tel="item.tel"></UserInfoParam>
</div>`;

export default{
    data(){
        return {
            title:"模块化",
            users:[
                {username:"张三",tel:"12345"},
                {username:"李四",tel:"12345"},
                {username:"王五",tel:"12345"},
                {username:"赵六",tel:"12345"},
            ]
        }
    },
    components:{UserInfo,UserInfoParam},
    template
};

上面演示的是传多个的单值,当然也可以直接传数组,将上面的代码稍微修改

UserInfoParam.js

var template = `<div>
<div v-for="(user,i) in users" :key="i">
    <p>姓名:{{user.username}}</p>
    <p>电话:{{user.tel}}</p>
</div>

</div>`;

export default{
    props:{
        users:{
            type:Array,
            required:true
        }
    },
    template:template
}

app.js

import UserInfo from './components/UserInfo.js'
import UserInfoParam from './components/UserInfoParam.js'

var template = `<div>
<h1>{{title}}</h1>
<UserInfo></UserInfo>
<hr />
<UserInfoParam :users="users"></UserInfoParam>
</div>`;

export default{
    data(){
        return {
            title:"模块化",
            users:[
                {username:"张三",tel:"12345"},
                {username:"李四",tel:"12345"},
                {username:"王五",tel:"12345"},
                {username:"赵六",tel:"12345"},
            ]
        }
    },
    components:{UserInfo,UserInfoParam},
    template
};

本文链接:http://www.yanhongzhi.com/post/VueInAction-4.html

-- EOF --

Comments