龙空技术网

Vue组件基础

JAVA架构前端技术 370

前言:

当前兄弟们对“vue组件选项”大致比较注意,我们都想要剖析一些“vue组件选项”的相关知识。那么小编同时在网摘上汇集了一些有关“vue组件选项””的相关内容,希望咱们能喜欢,兄弟们快快来了解一下吧!

组件是Vue.js最推崇的,也是最强大的功能之一,核心目标是为了代码开发的重用性。我们可以把组件代码按照 template、style、script 的拆分方式,放置到对应的.vue文件中。本章节我们就来学习有关Vue组件的基础知识。

5.1 组件简介

组件的本质就是为了拆分Vue实例的代码量。我们也可以把所有的组件代码写到Vue实例中,那样的话,实例代码则显的臃肿不堪,冗长无比,这并不是我们想要的,所以,我们根据组件的功能性,以不同的的功能来划分不同的组件,将来我们需要什么样的功能,就可以取调用对应的组件即可,从而达到组件的可重用性和灵活性。

5.1.1 组件的定义

组件(Component)是Vue.js最强大的功能之一,组件可以扩展HTML元素,封装可重用代码,在较高层面上,组件是自定义元素,Vue.js的编译器为他添加特殊功能。有些情况下,组件也可以表现用 is 特性进行了扩展的原生的HTML元素,所有的Vue组件同时也都是Vue实例,所以可以接受相同的选项对象(除了一些根级特有的选项),并提供相同的生命周期钩子。

vue组件的功能:

能够把页面抽象成多个相对独立的模块;实现代码重用,提高开发效率和代码质量,使得代码易于维护。5.1.2 组件的类型组件按照注册方式不同,分为全局组件和局部组件。

什么情况下需要将组件注册为全局组件?一般是一些基础组件,频繁(3 次以上)需要用到的,需要全局注册。例如常用的 dialog(对话框)组件,search(搜索框) 组件,toast(弹出框) 组件,message(消息框)组件等。

一般情况下的组件应该是局部组件,这样会极大的减少构建应用后的代码体积,但是对于频繁使用的组件就显得麻烦了,所以建议,组件使用频率低,组件比较大的时候注册为局部组件。比如 table 组件,chart 组件等。

组件按照有无自己的状态,可以分为函数式(无状态)组件和普通(有状态)组件。

什么情况下需要将组件写为函数式组件?一般是无状态 (没有响应式数据)的组件可以注册成函数式组件,好像不用函数式组件也可以呀,为啥要注册成函数式组件?当一个组件是一个函数式组件的时候,它没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数,所以渲染开销也低很多。

组件按照是否动态分类:可以分为动态组件和普通(非动态)组件。

什么情况下需要将组件写为动态组件?一般是组件之间需要切换的情况下。但是不用动态组件也可以,那为啥要动态组件?当你导入 Dynamic Component1(动态组件) 从 1 写到 10 的时候,然后 template 再写 DynamicComponent 10 次的时候,它的好处就出来了。

组件按照是否异步分类:可以分为异步组件和普通(非异步)组件。

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

什么情况下需要将组件写为异步组件?一般是需要从服务器加载数据的组件,且需要多个地方使用的,因为它会把结果缓存起来供未来重渲染。还有就是大家使用最多的是在 Vue Router 里使用,异步组件结合 Webpack 的代码分割功能,可以轻松实现路由组件的懒加载。

组件按照是否循环引用分类:可以分为递归组件和普通(非递归)组件。

组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事。一般是组件需要调用自身的时候,比如树组件,侧边栏路由组件等,才会使用递归组件。

表5.1 组件的分类

分类标准

分类1

分类2

注册方式

全局组件

局部组件

有无自己的状态

无状态组件(函数式组件)

有状态组件(普通组件)

是否动态

动态组件

非动态组件

是否异步

异步组件

非异步组件

是否循环使用

递归组件

普通组件

以上是我们组件的分类,大家现在只需要了解组件的分类,后面我们会一一详解。

5.1.3 组件的构成要素

Vue.js的组件可以理解为预先定义好行为的ViewModel类。一个组件就像一个Vue实例一样,也可以预定义很多选项,但最核心的是以下几个:

模板(template)——模板声明了数据和最终展现给用户的DOM之间的映射关系。初始数据(data)——一个组件的初始数据状态。对于可复用的组件来说,通常是私有数据。接受的外部参数(props)——组件之间通过参数来进行数据的传递和共享。参数默认是单向绑(由上至下),但也可以显式声明为双向绑定。方法( methods)——对数据的改动操作一般都在组件的方法内进行。可以通过 v-on指令将用户输入事件和组件方法进行绑定。生命周期钩子函数(lifecycle hooks)——一个组件会触发多个生命周期钩子函数,比如created、attached、destroyed 等。在这些钩子函数中,我们可以封装一些自定义的逻辑。

在组件的构成要素中,我们发现data,methods,以及生命周期函数这些都是跟我们的Vue实例相似的,其实在功能上的初衷也是一样的,但是在使用上还是有差别的,接下来我们就来学习组件的封装和使用。

5.2组件封装和使用5.2.1 封装组件

封装一个组件有以下三种方式:

表5.2封装组件的方法

方法

方法描述

Vue.extend()

使用extend()方法封装一个组件

Template

使用template标签来封装一个组件

Script

使用script标签来封装一个组件

封装好组件就可以直接使用组件了吗,我们仍然需要一系列的步骤,来看Vue组件的使用步骤:

使用任意的封装方法进行封装组件的内容;使用Vue.component()方法进行组件的注册;根据业务需要进行数据的通信;

了解完步骤后,我们知道组件根据注册方式的不同,分为全局和局部,接下来我们使用全局注册来演示Vue组件的封装的三种方式,现在先来看第一种方式。

例5-01 Demo0501.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue组件的第一种封装方式</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 3、通过注册的名字使用组件 --><hello></hello></div></body><script>//第一种使用extend()封装Vue组件//1、封装组件的模板let com1 = Vue.extend({template: "<h3 style='color:red;'>这是使用h3定义的组件</h3>"});//2、使用Vue.component(组件的名字,组件的模板)方法注册组件Vue.component('hello', com1);let vm = new Vue({el: '#app',});</script></html>

程序的运行结果如下:

这就是我们自己封装的组件

图 5- 01 使用Vue.extend()方法封装的组件模板

根据例题Demo0501,我们来总结以下注意事项:

使用Vue.extend()方法来封装组件模板,模板内容必须使用{}括起来。根据编程常识,我们知道在{}中可以有多个选项,不止template一个选项。在5.1.3中我们学习了模板的构成要素,那么这些构成要素就可以放在{}中;使用Vue.component("组件的名字",模板) 注册成为组件,在给组件起名字的时候是有注意事项的。在注册时,注册的名字是驼峰命名法,则在使用的时候,必须把大驼峰变为小写,中间加-,例如注册的名字为myCom,则在根容器中引入该名字标签的时候要使用<my-com></my-com>。如果注册时没有使用驼峰命名,则不需要进行此转化,例如注册时hellocom,则在根标签使用的时候就是<hellocom></hellocom>。

接下来我们来看第一种封装方式的简化版本:

例5-02 Demo0502.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue组件的第一种封装方式</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 注册时候使用驼峰命名,使用的时候必须使用中间杠进行拆分 --><my-com></my-com></div></body><script>//第一种的简化版本,封装一个p标签和一个按钮//注意:如果封装的模板中有两个html元素,那么该封装必须有个根标签,一般我们使用div作根标签Vue.component("myCom", Vue.extend({template: "<div><p>p标签里面的内容</p><button>点击按钮</button></div>"}));let vm = new Vue({el: '#app',});</script></html>

程序的运行结果如下:

这是自己的封装的组件,该组件中有两个html元素,所以必须有div根容器

图 5- 02 这是使用extend()封装的简化版本

通过例5-02,进行如下总结:

我们发现组件封装的模板是有html和css组成的;同时,也发现,在双引号中写html元素是一件很不方便的事情,为了解决这个问题,接下来我们使用template标签来实现第二种方式的封装;

例5-03 Demo0503.html,使用template标签进行封装;

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue组件的第二种封装方式</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 注册时候使用驼峰命名,使用的时候必须使用中间杠进行拆分 --><hellocom></hellocom></div><!-- 第二种使用template标签进行封装,多个元素必须有根标签包裹起来 --><template id="hellocom"><div><button>测试按钮1</button><button>测试按钮2</button></div></template></body><script>//注册,Vue.component(组件的名字,组件的模板);Vue.component("hellocom", { template: "#hellocom" });let vm = new Vue({el: '#app',});</script></html>

程序的运行结果如下:

这是使用template标签封装的组件

图 5- 03 template标签封装模板

根据例5-03我们总结以下:

template标签必须写到body中,给template标签一个id属性,方便注册的时候通过id引入;

无论是使用哪种方式封装模板,如果模板中有多个html元素,都必须使用根标签包裹;

接下来我们来看第三种方式封装组件:

例5-04 Demo0504.html,使用script标签进行封装;

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue组件的第三种封装方式</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 注册时候使用驼峰命名,使用的时候必须使用中间杠进行拆分 --><my-com></my-com></div></body><!-- 第三种使用script标签封装组件,必须加入type='text/x-template',申明id,方便注册引入 --><script type='text/x-template' id='myCom'><div><p>hello,Vue</p><div style="width:200px;height:100px;border:1px solid red;">我是一个div</div></div></script><script>//全局方式注册组件Vue.component("myCom", Vue.extend({template: "#myCom"}));let vm = new Vue({el: '#app',});</script></html>

程序的运行结果如下:

这是使用scirpt标签封装的组件

图 5-04 这是使用script标签封装的组件

通过demo0504,总结如下:

使用script标签封装组件模板,最好在body标签外部定义,这样能很好的识别;必须指定type='text/x-template',声明id方便注册的时候通过id引入。

以上就是我们封装组件的三种使用方式,同时我们也学习了组件的使用步骤,第一,要封装内容,第二,注册组件,目前我们使用的是全局注册,第三,根据业务进行逻辑处理,这个后续讲解。

5.2.2 注册组件

在上一节中,我们使用三种封装方式以及全局注册对组件的使用有了清晰的了解,接下来,详细讲解组件的注册。

组件的注册有两种,全局注册和局部注册。

全局注册使用Vue.component(组件的注册名字,组件),使用该方法注册的组件可以在多个Vue实例中共享,我们来看实例:

例5-05 Demo0505.html,全局注册组件。

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>全局注册组件</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><h1>实例1</h1><!-- 这是vue组件的根容器 --><div id="app"><h1>实例1的根组件</h1><my-com></my-com></div><hr><h1>实例2</h1><div id="com"><h1>实例2的根组件</h1><my-com></my-com></div></body><!-- 第三种使用script标签封装组件,必须加入type='text/x-template',申明id,方便注册引入 --><script type='text/x-template' id='myCom'><div><p>hello,Vue</p><div style="width:200px;height:100px;border:1px solid red;">我是一个div</div></div></script><script>//全局方式注册组件,多个实例共享Vue.component("myCom", Vue.extend({template: "#myCom"}));//下面是两个Vue实例let vm1 = new Vue({el: '#app',});let vm2 = new Vue({el: "#com"});</script></html>

程序的运行结果如下:

这是全局注册测组件

这是全局注册测组件

图 5-05 这是全局注册组件

通过例5-05,总结如下:

使用Vue.component()实现全局注册,全局注册的组件可以共享;一个页面可以写多个Vue实例,不同的实例控制不同的DOM元素。接下来,我们来看看局部组件:

例5-06 Demo0506.html,局部注册组件。

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>全局注册组件</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><h1>实例1</h1><!-- 这是vue组件的根容器 --><div id="app"><h1>实例1的根组件</h1><my-com></my-com></div><hr><h1>实例2</h1><div id="com"><h1>实例2的根组件</h1><my-com></my-com></div></body><script type='text/x-template' id='myCom'><div><p>hello,Vue</p><div style="width:200px;height:100px;border:1px solid red;">我是一个div</div></div></script><script>//下面是两个Vue实例let vm1 = new Vue({el: '#app',components: {'myCom': {template: '#myCom'}},//components是component的复数形式});let vm2 = new Vue({el: "#com"});</script></html>

程序的运行结果如下:

图 5-06 局部注册组件

通过demo0506结果我们发现,在两个实例中都使用了新注册的组件,但是只在实例1中显示,这说明局部注册的组件不具备共享性。同时发现,局部组件的注册的时候,需要在某一个Vue实例中使用components属性,那么在哪个Vue实例中注册就只能在哪个Vue实例中共享。

想一想:全局注册的组件换个页面引入还可以使用吗?

全局注册的组件只在当前页面的多个Vue实例中共享,并不能跨页面。

5.2.3 开发组件

通过对组建的初步了解,上述案例都是在html页面中定义组件,其实,真正在开发的时候,我们是把根据功能划分不同的组件,每个组件都是以.vue结尾的文件,每个文件都是有三部分组成,分别是template,script,style部分,如下所示:

<template><div><!-- 写组件的模板 --></div></template><script>export default {data() {return {//写组件的数据};},methods: {//写组件的方法},};</script><style lang="scss" scoped>div {width: 200px;height: 100px;background-color: pink;}</style>

以上的写法,我们在学习了vue与webpack结合后,就会大量使用,现在大家先做了解。

5.3组件数据通信

在实际开发过程中,Vue.js中的组件会与所在的环境进行通信,总结起来,组件通信有三种数据传递方式:

props组件通信slot

接下来我们分别对每一种数据通信进行详细的阐述。

5.3.1 props

"props”是组件数据的一个字段,期望从父组件传下来数据。因为组件实例的作用域是孤立的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据,所以子组件需要显式地用props选项来获取父组件的数据。props选项可以是字面量,也可以是表达式,还可以绑定修饰符。下面我们详细看一下它是如何使用的。

1.字面量语法

我们可以给子组件传一个常量值,也就是字面量,来看看子组件如何接受:

接下来演示普通常量值的传递,如例9-15所示。

例5-07 Demo0507.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>props字面量的传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 这是局部组件 --><!-- 给子组件自定义属性hello,然后给这个属性赋值 --><test hello="this is a son"></test></div></body><script type='text/x-template' id='myCom'><div><p>hello,Vue</p><span>{{hello}}</span></div></script><script>let vm = new Vue({el: '#app',components: {'test': {props: ['hello'],//使用props这个属性来接受传过来的值,props是个数组,里面写的是自定义属性的名字template: '#myCom',}},});</script></html>

程序的运行结果如下:

这是子组件,其中span标签内的内容是通过props接受过来的

图 5-07 传递字面常量

例5-07中,通过在子组件上自定义了一个属性“hello”,然后给这个属性一个字面常量,在子组件的内部通过props属性来接受。接受到后,可以正常使用插值表达式进行显示。

注意:props这个数组里写的是自定义属性的名字,所以是字符串。

自定义属性的名字,如果是kebab-case命名法,则在props接受得时候转为驼峰命名,例如

<son my-hello=’hi,son’></son>,则在子组件使用props接受得时候必须写成props:[‘myHello’]

2.动态语法

类似于用v-bind将HTML 特性绑定到一个表达式,我们也可以用v-bind将动态将父组件得数据绑定到自定义属性中。每当父组件的数据变化时,该变化也会传导给子组件,代码示例如下:

接下来演示动态绑定,如例5-08所示。

例5-08 Demo0508.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>props字面量的传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 这是局部组件 --><!-- 给子组件自定义属性hello,让这个自定义属性绑定父组件得msg数据 --><test :hello="msg"></test></div></body><script type='text/x-template' id='myCom'><div><p>hello,Vue</p><span>{{hello}}</span></div></script><script>let vm = new Vue({el: '#app',data: {msg: "我是父组件得数据"},components: {'test': {props: ['hello'],//使用props这个属性来接受传过来的值,props是个数组,里面写的是自定义属性的名字template: '#myCom',}},});</script></html>

程序的运行结果如下:

图 5-08 动态传递父组件的数据

例5-08中,如果自定义属性要绑定父组件data中的数据,则需要使用v-bind,v-bind简写为冒号。

3.props类型

通常,我们只看到了以字符串数组形式列出的 props,例如,props: ['title', 'likes', 'isPublished', 'commentIds', 'author']。但是,如果你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

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

type类型有如下:

StringNumberBooleanObjectFunctionArray4.props验证

我们可以为组件的 prop 指定验证要求,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

let vm = new Vue({el: '#app',data: {msg: "我是父组件得数据"},components: {'test': {props: {// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)propA: Number,// 多个可能的类型propB: [String, Number],// 必填的字符串propC: {type: String, //数据类型required: true //必填项},// 带有默认值的数字propD: {type: Number,default: 100 //默认值},// 带有默认值的对象propE: {type: Object,// 对象或数组默认值必须从一个工厂函数获取default: function () {return { message: 'hello' }}},// 自定义验证函数propF: {validator: function (value) {// 这个值必须匹配下列字符串中的一个return ['success', 'warning', 'danger'].indexOf(value) !== -1}}},template: '#myCom',data() {//这个是组件自己的数据return {hi: 'hello'}},}},});

注意:props的数据来自父级,data中数据是组件自己的数据。

子组件中的data和Vue实例中的data是不一样的,子组件的data是个函数,而且数据必须定义在return的对象中。

5.单项数据流

所有的 prop 都使得父子 prop 之间形成了一个单向向下的数据流:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

父组件===>子组件:vue允许的,会主动触发的,也叫正向传递。

子组件===>父组件:vue允许的,不会主动触发,需要手动(被动)触发,叫做逆向传递。

另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 通过prop传递的值,如果你这样做了,Vue 会在浏览器的控制台中发出警告。

例5-09 Demo0509.html,通过

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>props字面量的传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><!-- 这是vue组件的根容器 --><div id="app"><h1>这是根组件</h1><!-- 这是局部组件 --><!-- 给子组件自定义属性hello,让这个自定义属性绑定父组件得msg数据 --><test :hello="msg"></test></div></body><script type='text/x-template' id='myCom'><div><p>hello,Vue</p><span>{{hello}}</span></div></script><script>let vm = new Vue({el: '#app',data: {msg: "我是父组件得数据"},components: {'test': {props: ['hello'],//使用props这个属性来接受传过来的值,props是个数组,里面写的是自定义属性的名字template: '#myCom',}},});</script></html>

程序的运行结果如下:

图 5-09 使用验证方式接受传值

如例5-09 显示,我们使用的是验证方式接受父组件传过来的值。接下来我们对父组件穿过的数据及性能修改,控制台警告如下:

一旦修改父组件传过来的值,浏览器的控制台发出警告,不允许修改父组件的数据

通过文本框修改父组件传过来的数据

图 5- 10 通过子组件修改父组件的数据

5.3.2 组件通信

上一节我们研究了父组件将值传递给子组件,叫做正向传值,子组件将值传递给父组件,叫做逆向传值;需要借助自定义事件。

vue.js 中允许正向传值,所以正向传值不需要条件触发,是主动的;逆向传值,也是允许的,但是需要主动(手动)触发,需要借助事件触发器:

$on():监听事件$emit():把事件沿着作用域链向上传递

使用步骤:

第一步:在父组件中引用的子组件的标签上,自定义事件;

<body>

<div id="app">

<h1>这是根组件</h1>

显示子组件传过来的值:<span>{{sonmsg}}</span>

<hr>

<!--1、 getMsg 是自定义事件,该事件对应一个函数 -->

<test @getmsg="fn"></test>

</div>

</body>

<script type='text/x-template' id='myCom'>

<div>

<h4>我是子组件</h4>

<input type="text"><input type="button" value="点击向父组件传值">

</div>

</script>

<script>

let vm = new Vue({

el: '#app',

data: {

msg: "我是父组件得数据",

sonmsg: "" //用来接受从子组件传过来的值

},

//2、定义一个函数

methods: {

fn(val) {

this.sonmsg = val; //val 是子组件在触发该函数的时候传过来的参数,把该参数传给父组件中的data

}

},

});

</script>

</html>

第二步:在子组件的模板中,定义事件函数;

<script type='text/x-template' id='myCom'>

<div>

<h4>我是子组件</h4>

<!-- 定义点击事件,点击该按钮把文本框中的值传给父组件 -->

<input type="text"><input type="button" value="点击向父组件传值" @click="sonfn">

</div>

</script>

第三步:在第二步骤的函数中,触发事件;

components: {

'test': {

template: '#myCom',

data() {//用来存放局部组件数据

return {

name: ""

}

},

methods: {//用来放局部组件的方法

sonfn() {

this.$emit("getmsg", this.name);

}

},

}

}

接下来演示子组件向父组件传值完整案例,如例5-10所示。

例5-10 Demo0510.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><div id="app"><h1>这是根组件</h1>显示子组件传过来的值:<span>{{sonmsg}}</span><hr><!--1、 getMsg 是自定义事件,该事件对应一个函数 --><test @getmsg="fn"></test></div></body><script type='text/x-template' id='myCom'><div><h4>我是子组件</h4><!-- 定义点击事件,点击该按钮把文本框中的值传给父组件 --><input type="text" v-model="name"><input type="button" value="点击向父组件传值" @click="sonfn"></div></script><script>let vm = new Vue({el: '#app',data: {msg: "我是父组件得数据",sonmsg: "" //用来接受从子组件传过来的值},//2、定义一个函数methods: {fn(val) {this.sonmsg = val; //val 是子组件在触发该函数的时候传过来的参数,把该参数传给父组件中的data}},components: {'test': {template: '#myCom',data() {//用来存放局部组件数据return {name: ""}},methods: {//用来放局部组件的方法sonfn() {this.$emit("getmsg", this.name);}},}}});</script></html>

程序的运行结果如下:

图 5- 11 子组件向父组件传值

通过例5-10,总结如下:

模板对象的构成要素中,我们使用了template,data,methods这些选项;template指的是组建的模板内容,data里面放的是局部组件内部的数据,methods放的是组件内部的方法;data是个函数,存放的数据必须放在return的对象中;子组件传值给父组件,必须在子组件中通过点击事件对应的函数来触发父组件中自定义的事件。

5.3.3 slot使用

我们在构建页面过程中一般会把用的比较多的公共的部分抽取出来作为一个单独的组件,但是在实际使用这个组件的时候却又不能完全的满足需求,我们希望在这个组件中添加一点东西,这时候就需要用到插槽来分发内容。插槽(slot)是对组件的扩展,通过slot插槽向组件内部指定位置传递内容,通过slot可以父子传参。slot又可以分为三类,分别是匿名插槽,具名插槽以及作用域插槽。

1.匿名插槽

匿名插槽,顾名思义,就是没有名字的插槽。

插槽的具体使用步骤:

第一:在子组件中定义内容,但是对于不确定的内容使用slot占位置;

第二:注册子组件;

第三:在父组件中调用子组件,同时把不确定的内容补充上。

具体使用我们来看例5-11,如下:

例5-11 Demo0511.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script></head><body><div id="app"><h1>这是根组件</h1><hr><son-test><span>我是通过插槽添加到子组件的内容</span></son-test></div><!-- 这是定义子组件模板 --><template id="childCom"><div><h4>我是子组件</h4><!-- 下面是一个匿名插槽,本质就是为html元素占位置 ,也就是为后面的span占位置--><slot></slot></div></template></body><script>let vm = new Vue({el: '#app',components: {'son-test': {template: '#childCom'}}});</script></html>

程序的运行结果如下:

图 5- 12 匿名插槽

例5-11中,跟普通组件定义的区别,就是在定义子组件模板的时候多了个slot标签,而这个slot标签的作用就是为子组件中不确定的html内容占位置。在父组件中调用子组件的时候,在子组件内部写的span标签就会自动插入到slot标签所占的位置。

2.具名插槽

假设我们的电脑主板上的各种插槽,有插CPU的,有插显卡的,有插内存的,有插硬盘的,所以假设有个组件是computer,我们不可能把显卡插到内存的位置上,具名slot也就是每个slot都有名字,不能随意替换,要对应插入,接下来我们演示具名插槽的使用。

例5-12 Demo0512.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script><style>.content {width: 600px;height: 300px;background-color: pink;}.head {width: 600px;height: 30px;background-color: blue;}.footer {width: 600px;height: 80px;background-color: green;}</style></head><body><div id="app"><h1>这是根组件</h1><hr><son-test><template v-slot:content><div class="content"></div></template><template v-slot:footer><div class="footer"></div></template><template v-slot:head><div class="head"></div></template></son-test></div><!-- 这是定义子组件模板 --><template id="childCom"><div><h4>我是子组件</h4><!-- 下面是有名字的插槽,slot的位置顺序此时已经决定--><slot name="head"></slot><slot name="content"></slot><slot name="footer"></slot></div></template></body><script>let vm = new Vue({el: '#app',components: {'son-test': {template: '#childCom'}}});</script></html>

程序的运行结果如下:

图 5- 13 具名插槽

案例5-12中,跟5-11的区别是,在子组件模板定义的时候,每个slot都有name值。在父组件引入子组件的时候,如何给对应的slot内容呢?每个slot插入的内容使用template标签,该标签使用v-slot:插槽名字,从而能够把对应的内容插入到对应的插槽中。注意: v-slot 只能添加在 <template> 上,v-slot:插槽名字可以缩写为#插槽名字,如下:

<template v-slot:header>

<div class=”header”></div>

</template>

简写后:

<template #header>

<div class=”header”></div>

</template>

注意:插入的内容的顺序是在定义子组件的时候,slot的先后顺序决定的,并不是在调用子组件时候插入内容的先后顺序决定的。如下:

定义模板的时候顺序是头部,内容,底部

插入内容的时候是:

插入内容的顺序是中间内容,底部,头部,但是显示的时候依然是头,内容,底部,因为顺序是在定义模板的时候决定的

3.作用域插槽

作用域插槽,主要解决的是父组件在向子组件插槽传递模板内容时存在访问子组件数据的问题。我们来看下面的案例:

例5-12 Demo0512.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script><style>.content {width: 600px;height: 300px;background-color: pink;}.head {width: 600px;height: 30px;background-color: blue;}.footer {width: 600px;height: 80px;background-color: green;}</style></head><body><div id="app"><h1>这是根组件</h1><hr><!-- 这是子组件 --><son-test><template #content><div class="content">{{user.midName}}</div></template><template #footer><div class="footer">{{user.midName}}</div></template><template #head><div class="head">{{user.midName}}</div></template></son-test></div><!-- 这是定义子组件模板 --><template id="childCom"><div><h4>我是子组件</h4><!-- 下面是有名字的插槽,slot的位置顺序此时已经决定--><slot name="head"></slot><slot name="content"></slot><slot name="footer"></slot></div></template></body><script>let vm = new Vue({el: '#app',components: {'son-test': {template: '#childCom',data() {return {user: {headName: '头部内容',midName: '中间精彩',footName: '底部版权'}}}}},});</script></html>

程序的运行结果如下:

这是因为在使用子组件标签的时候,给slot白哦前插入值的受无法访问子组件内的数据

没有显示子组件的内容

图 5- 14 给插槽内容访问子组件数据

我们来看看子组件定义的数据:

下面是通过插槽调用子组件内部的数据:

我们发现给插槽内容的时候,我们无法访问子组件内容的数据,那怎么办呢?

第一步:我们可以给slot标签设置自定义属性,该自定义属性绑定我们要显示的数据,例如:

匿名插槽:

<slot :user=”user”></slot>

具名插槽:

<slot name=”header” :user=”user”></slot>

第二步:在使用子组件标签的时候通过插槽的prop拿到子组件绑定的数据:

匿名插槽:

<son-test>

<template #default="hi">

<span>{{hi.user.headName}}</span>

</template>

</son-test>

具名插槽:

<son-test>

<template #content="slotProp">

<div class="content">{{slotProp.user.midName}}</div>

</template>

<template #footer="slotProp">

<div class="footer">{{slotProp.user.midName}}</div>

</template>

<template #head="slotProp">

<div class="head">{{slotProp.user.midName}}</div>

</template>

</son-test>

完整案例如下:

例5-13 Demo0513.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script><style>.content {width: 600px;height: 300px;background-color: pink;}.head {width: 600px;height: 30px;background-color: blue;}.footer {width: 600px;height: 80px;background-color: green;}</style></head><body><div id="app"><h1>这是根组件</h1><hr><!-- 这是子组件 --><son-test><template #content="slotProp"><div class="content">{{slotProp.user.midName}}</div></template><template #footer="slotProp"><div class="footer">{{slotProp.user.midName}}</div></template><template #head="slotProp"><div class="head">{{slotProp.user.midName}}</div></template></son-test></div><!-- 这是定义子组件模板 --><template id="childCom"><div><h4>我是子组件</h4><!-- 下面是有名字的插槽,slot的位置顺序此时已经决定--><slot name="head" :user="user"></slot><slot name="content" :user="user"></slot><slot name="footer" :user="user"></slot></div></template></body><script>let vm = new Vue({el: '#app',components: {'son-test': {template: '#childCom',data() {return {user: {headName: '头部内容',midName: '中间精彩',footName: '底部版权'}}}}},});</script></html>

程序的运行结果如下:

图 5- 15 作用域插槽

例5-13中,要想在父组件引用子组件标签的时候,给slot插入模板的时候访问子组件的数据,我们可以经过两个步骤,第一步,给slot自定义属性绑定数据,第二步,使用template的#default="slotProp"可以访问插槽的prop,其中默认插槽使用#default,如果是具名插槽则是用#插槽名字,等号右边的是给插槽的prop起个具体的名字,这个名字是自己起的,并不是固定的,这样通过两个步骤我们就可以在给slot指定内容的时候访问子组件内部的数据。

5.4 动态组件和异步组件5.4.1 动态组件

动态组件是指,在一个挂载点使用多个组件,并进行动态切换。可能对于新手来说,这句话有些难理解,什么是挂载点?可以简单的理解为页面的一个位置。最常见的就是:tab的切换功能。

在vue要实现这个功能通常用两种方式,一是使用<component>元素的 is 的特性,二是使用 v-if 。

1.component的is特性

<component :is=”组件的名称”></component>

接下来我们来看动态组件的第一种用法:

例5-14 Demo0514.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script><style>span {display: inline-block;width: 80px;height: 40px;text-align: center;line-height: 40px;background-color: bisque;margin-left: 3px;}</style></head><body><div id="app"><h1>这是根组件</h1><span v-for="(item,index) in tabList" :key="index" @click="fn(index)">{{item}}</span><hr><component :is="comName"></component></div><!-- 这是定义子组件模板 --><template id="vd"><div style="width:200px;height:30px;background:red;">视频区域</div></template><template id="pic"><div style="width:200px;height:30px;background:blue;">图片区域</div></template><template id="wz"><div style="width:200px;height:30px;background:green;">精彩文章</div></template></body><script>let vm = new Vue({el: '#app',components: {'com0': { template: '#vd' },'com1': { template: '#pic' },'com2': { template: '#wz' },},data: {tabList: ['视频', '图片', '文章'],comName: ""},methods: {fn(index) {this.comName = 'com' + index;}}});</script></html>

程序的运行结果如下:

图 5- 16 动态组件实现方式一

点击不同的span标签,下面动态出现不同的内容

例5-14,我们有三个tab标签,点击不同的tab标签,显示不同的组件,在这里充分利用了component的is属性。is属性绑定comName数据,comName这个变量会根据点击的tab标签不一样,存储的组件名字也不一样,从而达到了动态显示组件的效果。

2.v-if

同样的效果我们来使用v-if来实现,如例5-15所示。

例5-15 Demo0515.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script><style>span {display: inline-block;width: 80px;height: 40px;text-align: center;line-height: 40px;background-color: bisque;margin-left: 3px;}</style></head><body><div id="app"><h1>这是根组件</h1><span v-for="(item,index) in tabList" :key="index" @click="fn(index)">{{item}}</span><hr><com0 v-if="index == 0"></com0><com1 v-else-if="index == 1"></com1><com2 v-else></com2></div><!-- 这是定义子组件模板 --><template id="vd"><div style="width:200px;height:30px;background:red;">视频区域</div></template><template id="pic"><div style="width:200px;height:30px;background:blue;">图片区域</div></template><template id="wz"><div style="width:200px;height:30px;background:green;">精彩文章</div></template></body><script>let vm = new Vue({el: '#app',components: {'com0': { template: '#vd' },'com1': { template: '#pic' },'com2': { template: '#wz' },},data: {tabList: ['视频', '图片', '文章'],index: 0},methods: {fn(index) {this.index = index;}}});</script></html>

程序的运行结果如下:

图 5- 17 动态组件实现方式二

例5-15中,我们使用v-if 以及v-else-if 来实现组件的动态切换。

3.缓存

上述讲到两种方式虽然能够实现动态组件的切换,但是每次切换都会把上一个组件销毁,然后渲染下一个组件,对于多次切换而言,显然每次的销毁和重新渲染,很大消耗了我们的性能。所以我们可以通过keep-alive对组件进行缓存,对于不显示的组件不是去销毁它,而是使它处于不激活的状态,当需要显示时再去激活,那么如何实现呢,如下:

<keep-alive>

<component :is="comName"></component>

</keep-alive>

和keep-alive 相关的两个生命周期:

components: {

'com0': {

template: '#vd',

activated() {

console.log("视频页面被添加");// 被缓存的组件激活时触发

},

deactivated() {

console.log("视频页面被移除"); // 被切换到其他组件时触发

}

},

'com1': { template: '#pic' },

'com2': { template: '#wz' },

},

注意:只有当组件在 <keep-alive> 内被切换,才会有activated 和 deactivated 这两个钩子函数,通过描述我们知道,它会缓存不活动的组件,而不是销毁。这样组件之间的切换就能保存上个组件的状态,而不是切换之后又得重新操作。

keep-alive的两个属性:

Include:表示只能允许匹配到的组件生效Exclude:则相反,除了匹配到的组件之外有效

使用方法如下:

<!-- 字符串 逗号分隔字符串, a,b 分别为组件名 -->

<keep-alive include="a,b">

<component :is="comName"></component>

</keep-alive>

<!-- 正则表达式 -->

<keep-alive :include="/a|b/">

<component :is="comName"></component>

</keep-alive>

<!-- 数组 -->

<keep-alive :include="['a', 'b']">

<component :is="comName"></component>

</keep-alive>

5.4.2 异步组件

在大型应用中,我们可能需要将应用拆分为多个小模块,按需从服务器下载。为了进一步简化,Vue.js 允许将组件定义为一个工厂函数,异步地解析组件的定义。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。为什么需要异步组件,如果一开始就加载所有的组件,那么是比较耗时的,所以我们可以将一些组件定义为异步组件,在需要使用的时候再进行加载。

异步组件就是定义的时候什么都不做,只在组件需要渲染(组件第一次显示)的时候进行加载渲染并缓存,缓存是以备下次访问。

下面我们来看个初步了解的案例:

例5-16 test.js,先写个定义组件的js

window.async_comp = {template: '\<ol>\<li v-for="item in list">{{ item }}</li>\</ol>',props: {list: Array}};

例5-16 demo0516.html,然后再写html页面

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>子组件向父组件传值</title><!-- 在线引入vue.js,必须引入否则无法使用vue--><script src=";></script><style>span {display: inline-block;width: 80px;height: 40px;text-align: center;line-height: 40px;background-color: bisque;margin-left: 3px;}</style></head><body><div id="app"><h1>这是根组件</h1><span v-for="(item,index) in tabList" :key="index" @click="fn(index)">{{item}}</span><hr><async-comp :list="['我是一个异步组件,','如果加载完成,','我就会在这里显示']"></async-comp></div></body><script>var vm = new Vue({el: '#app',components: {/* 异步组件async-comp */'async-comp': function () {return {/** 要渲染的异步组件,必须是一个Promise对象 */component: new Promise(function (resolve, reject) {var script = document.createElement('script');script.type = 'text/javascript';script.src = '/test.js';document.head.appendChild(script);script.onerror = function () {reject('load failed!');}script.onload = function () {if (typeof async_comp !== 'undefined')resolve(async_comp);else reject('load failed!')}}),/* 加载过程中显示的组件 */loading: {template: '<p>loading...</p>'},/* 出现错误时显示的组件 */error: {template: '\<p style="color:red;">load failed!</p>\'},/* loading组件的延迟时间 */delay: 10,/* 最长等待时间,如果超过此时间,将显示error组件。 */timeout: 3200}}}})</script></html>

程序的运行结果如下:

图5.18 异步组件运行结果

5.5 本章小结组件(Component)是Vue.js最强大的功能之一,组件可以扩展HTML元素,封装可重用代码,在较高层面上,组件是自定义元素,Vue.js的编译器为他添加特殊功能。有些情况下,组件也可以表现用 is 特性进行了扩展的原生的HTML元素,所有的Vue组件同时也都是Vue实例,所以可以接受相同的选项对象(除了一些根级特有的选项),并提供相同的生命周期钩子。组件的分类:全局组件,局部组件,动态组件和异步组件。组件是由template,data,methods,props,生命周期钩子函数构成。组件封装有三种方式,第一种是Vue.extend()方法来封装模板,然后注册,其次是使用template标签来封装组件的内容模板,最后是使用script标签来封装内容模板。组件之间根据业务需要是要数据通信的,父组件传值给子组件使用props,子组件传值给父组件使用自定义事件,以及使用slot插槽也可以实现子组件的数据在父组件中使用。动态组件是指,在一个挂载点使用多个组件,并进行动态切换。可能对于新手来说,这句话有些难理解,什么是挂载点?可以简单的理解为页面的一个位置。最常见的就是:tab的切换功能。异步组件就是定义的时候什么都不做,只在组件需要渲染(组件第一次显示)的时候进行加载渲染并缓存,缓存是以备下次访问。5.6 理论试题与实践练习1.填空题常见的组件构成要素有 。封装组件内容的三种方式 。开发组建的模板包含 。2.编程题

2.1 使用组件实现一个分页器。

2.2 使用组件实现一个计数器。

2.3 使用组件实现一个前端页面的封装。

标签: #vue组件选项 #css双引号样式