组件
概述:组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码 。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以 表现为用is特性进行了扩展的原生 HTML 元素。组件注册的时候需要为该组件指定各种参数。
组件的定义
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed 、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项
组件可复用
可以将组件进行任意次数的复用,因为,每个组件都会各自独立维护它的 data。因为你每用一次组件,就会有一个它的新实例被创建。
data必须是一个函数
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
组件注册
- 全局注册
可以使用Vue.component(tagName, options) 注册一个全局组件, 注册之后可以用在任何新创建的 Vue 根实 例的模板中
Vue.component('my-component-name',component)
- 局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的 无谓的增加。局部注册的组件只能在当前组件中使用
new Vue({
el: '#app',
components: { 'component-a': ComponentA, 'component-b': ComponentB } })
基础实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件基础</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
{{msg}}
<br>
----组件使用
<my-com></my-com>
<my-com></my-com>
<my-com></my-com>
</div>
<script>
//1.创建组件
let myCom = {
data() {
return {
comMsg: '组件数据'
}
},
//模板
template: `
<div>
<span>{{comMsg}}</span>
<span>{{comMsg}}</span>
<button @click="comMsg='新数据'">更改数据模型中的数据</button>
</div>
`
};
//2.组件注册
//全局注册
Vue.component('my-com', myCom);
new Vue({
el: "#app",
data: {
msg: "hello"
}
})
</script>
</body>
</html>
组件交互
组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发 数据,子组件则可能要将它内部发生的事情告知父组件。在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发 送消息
props
父组件通过属性绑定的方式将参数传递给子组件,子组件通过props声明期望从父组件那里获取 的参数。camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命 名:
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>' })
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
props类型
希望每个prop都有指定的值类型。这时,你可以以对象形式列出prop,这些property的名称和 值分别是prop各自的名称和类型
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise
// or any other constructor
}
props验证
我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足 ,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: { type: String, required: true },
// 带有默认值的数字
propD: { type: Number, default: 100 }
}
})
动态传参
prop 可以通过v-bind动态赋值。传递什么类型的变量就可以将该类型变量直接进行赋值,也可 以赋常量。如果不使用v-bind就只能传递字符串
- 传入一个数字
<blog-post v-bind:likes="42"></blog-post>
<blog-post v-bind:likes="post.likes"></blog-post>
- 传入一个布尔值
<blog-post v-bind:is-published="false"></blog-post>
- 传入数组
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
- 传入一个对象
<blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }" ></blog-post>
单项数据流
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更 父级组件的状态,从而导致你的应用的数据流向难以理解
自定义事件
自定义事件是子组件向父组件传递状态的一种机制。事件名大小写严格区分
使用this.$emit() 参数:自定义事件名称,事件处理程序的实参(发射的数据)可以实现子组件向父组件传值
子组件
this.$emit('myEvent')
父组件
<my-component v-on:myEvent="doSomething"></my-component>
基于以上知识点,下面给出一个组件交互通信的完整例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件通信-交互</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
{{msg}}
<!-- 父组件给子组件传递数据 -->
<my-com :title="msg" static-attr="父组件给子组件的静态数据" @my-event="myEventHandle"></my-com>
</div>
<script>
let myCom = {
//接受父组件传递的数据
props: ["title", "staticAttr"],
data() {
//返回的值都是相互独立的
return {
comMsg: '子组件数据'
}
},
methods: {
toEmit() {
//发射 参数:自定义事件名称,事件处理程序的实参(发射的数据)
this.$emit('my-event', this.comMsg, 100)
}
},
//模板
template: `
<div>
<span>子组件内部数据:{{comMsg}}</span>
<br>
<span>父组件数据{{title}}---{{staticAttr}}</span>
<button @click="comMsg='新数据'">更改数据模型中的数据</button>
<button @click="toEmit">发射数据</button>
</div>
`
};
Vue.component('my-com', myCom);
let vm = new Vue({
el: "#app",
data: {
msg: "hello"
},
methods: {
myEventHandle(a, b) {
console.log(a, b + "-----------")
this.msg = a;
}
}
})
</script>
</body>
</html>
插槽
普通插槽
插槽允许我们在调用子组件的时候为子组件传递模板。
子组件
<a v-bind:href="url" class="nav-link" > <slot>default</slot> </a>
父组件
<navigation-link url="/profile"> Your Profile </navigation-link>
当组件渲染的时候,<slot></slot>
将会被替换为“Your Profile”。插槽内可以包含任何模板代码,甚至是其他组件。如果 从函数的角度来理解,slot为形式参数,而navigation-link中的内容就为实参。Slot标签内部的内容为默认值,也就是当调用 navigation-link组件的时候没有设置插槽内容,则组件插槽内容默认为default.
具名插槽
在一个组件中有多个插槽,调用的时候为了给不同的组件传递参数就需要为插槽进行命名。
子组件<base-layout>
<div class="container">
<header> <slot name="header"></slot> </header>
<main> <slot></slot> </main>
<footer> <slot name="footer"></slot> </footer>
</div>
父组件
<base-layout>
<template v-slot:header> <h1>Here might be a page title</h1> </template>
</base-layout>
注意点:传递到插槽中的模板可以封装到template标签中。从函数角度出发,具名插槽可以理解为是传递了多个不同参数
作用域插槽
在一个组件中有多个插槽,调用的时候为了给不同的组件传递参数就需要为插槽进行命名。
子组件 <current-user>
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
这里将user这个变量作为slot的一个参数进行绑定,目的是为了让父组件可以访问
父组件
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
通过slotProps
可以访问到绑定到插槽中的所有属性,除了使用slotProps接受所有的属性外,还可以通过解构形式获取 从函数角度来理解,作用域插槽实际上为回调函数。
缩写
与v-bind
、v-on
类似,v-slot
也具有缩写形式,即把参数之前的所有内容 (v-slot:) 替换为字符#
。例如 v-slot:header
可以被重写为 #header
基于以上知识点,我们可以组件封装一个组件,实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义封装组件</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
{{msg}}
<my-table :data="stus">
<template #header>
<th>编号</th>
<th>名称</th>
<th>年龄</th>
</template>
<template #body="{item}">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
</template>
</my-table>
</div>
<script>
Vue.component('my-table', {
data() {
return {}
},
props: ['data'],
template:
`
<table>
<thead>
<tr><slot name="header"></slot></tr>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="index">
<slot :item="item" name="body"></slot>
</tr>
</tbody>
</table>
`
})
new Vue({
el: "#app",
data: {
msg: "hello",
stus: [
{
id: 1001,
name: "zhangsan",
age: 12
},
{
id: 1002,
name: "lisi",
age: 15
},
{
id: 1003,
name: "xiaoming",
age: 14
}
],
teachers: [
{
id: 101,
name: "terry",
salary: 10000,
genderL: "male"
},
{
id: 100,
name: "terry",
salary: 10000,
genderL: "male"
},
{
id: 103,
name: "terry",
salary: 10000,
genderL: "male"
},
{
id: 103,
name: "terry",
salary: 10000,
genderL: "male"
}
],
courses: [
{
id: 1,
name: 'html',
desc: '超文本'
},
{
id: 2,
name: 'css',
desc: '层叠样式'
},
{
id: 3,
name: 'js',
desc: '脚本语言'
}
]
}
})
</script>
</body>
</html>
动态组件
动态渲染一个组件
<component v-bind:is="currentTabComponent"></component>
默认情况下,当组件在切换的时候都会重新创建组件,但是有些时候我们希望组件实例能够被 在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive>
元素将其 动态组件包裹起来
<!-- is属性的属性值为注册组件的名字 -->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
完整实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态组件</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
{{msg}}
<button @click="current='my-a'">A组件</button>
<button @click="current='my-b'">B组件</button>
<button @click="current='my-c'">C组件</button>
<!-- 静态is值为注册的组件的名称 -->
<!-- <component is="my-a"></component> -->
<keep-alive>
<!-- v-bink:attributename="variate" -->
<component :is="current" :title="msg"></component>
</keep-alive>
</div>
<script>
let myA = {
template: `
<div>A组件内容</div>
`,
created() {
console.log("A组件创建好了")
}
};
let myB = {
template: `
<div>B组件内容</div>
`,
created() {
console.log("B组件创建好了")
}
};
let myC = {
template: `
<div>C组件内容</div>
`,
created() {
console.log("C组件创建好了")
}
}
Vue.component('my-a', myA);
Vue.component('my-b', myB);
Vue.component('my-c', myC);
new Vue({
el: "#app",
data: {
// current:myA 可行
current: "my-a",
msg: "hello"
}
})
</script>
</body>
</html>