11. 组件化基础
组件允许我们将 UI 划分为独立的、可重用的部分来思考。组件在应用程序中常常被组织成层层嵌套的树状结构:
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件数据模型,使我们可以在每个组件内封装自定义内容与逻辑。
11.1 注册组件
注册组件分成两种:
- 全局组件:在任何其他的组件中都可以使用的组件;
- 局部组件:只有在注册的组件中才能使用的组件;
注册全局组件:
- 全局组件需要使用我们全局创建的app来注册组件; -
- 通过component方法传入组件名称、组件对象即可注册一个全局组件了; 之后,我们可以在App组件的template中直接使用这个全局组件:
<div id="app"></div>
<template id="my-app">
<component-a></component-a>
</template>
<template id="component-a">
<h2>{{title}}</h2>
<p>{{desc}}</p>
<button @click="btnClick">按钮点击</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
};
const app = Vue.createApp(App);
// 使用app注册一个全局组件app.component()
// 全局组件: 意味着注册的这个组件可以在任何的组件模板中使用
app.component("component-a", {
template: "#component-a",
data() {
return {
title: "我是标题",
desc: "我是内容, 哈哈哈哈哈",
};
},
methods: {
btnClick() {
console.log("按钮的点击");
},
},
});
app.mount("#app");
</script>
注册局部组件:
<div id="app"></div>
<template id="my-app">
<h2>{{message}}</h2>
<component-a></component-a>
</template>
<template id="component-a">
<h2>我是组件A</h2>
<p>我是内容, 哈哈哈哈</p>
</template>
<script src="../js/vue.js"></script>
<script>
const ComponentA = {
template: "#component-a"
}
const App = {
template: '#my-app',
components: {
// key: 组件名称
// value: 组件对象
ComponentA: ComponentA
},
data() {
return {
message: "Hello World"
}
}
}
const app = Vue.createApp(App);
// app.component("ComponentA", ComponentA);
app.mount('#app');
</script>
11.2 组件名称
在通过app.component注册一个组件的时候,第一个参数是组件的名称,定义组件名的方式有两种:
- 方式一:使用kebab-case(短横线分割符)
- 当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case, 例如<my-component-name> :
- 方式二:使用PascalCase(驼峰标识符)
- 当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也 就是说 <my-component-name>和<MyComponentName> 都是可接受的;
11.3 组件的使用方法
App.vue
导入子组件
子组件名称为Header.vue、Main.vue、Footer.vue;
style 标签 增加scoped 会导致样式穿透,所以子组件template内添加div解决。
示例:
<template>
<div id="app">
<Header></Header>
<Main></Main>
<Footer></Footer>
</div>
</template>
<script>
import Header from './Header';
import Main from './Main';
import Footer from './Footer';
export default {
components: {
Header,
Main,
Footer
}
}
</script>
<style scoped>
</style>
11.4父子组件之间的通信方式
父子组件之间如何进行通信呢?
- 父组件传递给子组件:通过props属性;
- 子组件传递给父组件:通过$emit触发事件;
示意图:
::: mermaid flowchart LR; id1["Patent(父组件)"]--Pass props-->id2["child(子组件)"] id2--$emit Events-->id1 :::
什么是Props呢?
- Props是你可以在组件上注册一些自定义的attribute;
- 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
Props有两种常见用法:
- 方式一:字符串数组,数组中的字符串就是attribute的名称;
- 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、 默认值等等;
Props数组的用法
使用示例:props: ['title', 'content']
父组件App.vue:
vue<template> <div> <show-message title="呵呵呵" content="我是呵呵呵呵"></show-message> </div> </template> <script> import ShowMessage from './ShowMessage.vue'; export default { components: { ShowMessage, } </script> <style scoped> </style>
子组件ShowMessage.vue:
vue<template> <div> <h2>{{title}}</h2> <p>{{content}}</p> </div> </template> <script> export default { props: ['title', 'content'] </script> <style scoped> </style>
Props的对象用法
- 数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,接下来我们来看一下对象的 写法是如何让我们的props变得更加完善的。
- 当使用对象语法的时候,我们可以对传入的内容限制更多:
- 比如指定传入的attribute的类型;
- 比如指定传入的attribute是否是必传的;
- 比如指定没有传入时,attribute的默认值;
- type支持的类型?
- String
- Number
- Boolean
- Array pObject
- Date
- Function
- Symb
示例:
vue<script> export default { props: { // 指定类型 title: String, // 指定类型,同时指定是否必选、默认值 content: { type: String, required: true, default: "123" }, counter: { type: Number }, info: { type: Object, // 对象或数组默认值必须从工厂函数获取 default() { return {name: "why"} } }, messageInfo: { type: String }, //自定义验证函数 validator(value){ //这个值必须匹配下列字符串中的一个 return ['suceess','warning', 'danger'].includes(value) } } } </script>
Prop 的大小写命名(camelCase vs kebab-case)
- HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符;
- 这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短 横线分隔命名) 命名;
什么是非Prop的Attribute呢?
- 当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的 Attribute;
- 常见的包括class、style、id属性等;
- Attribute继承 p当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中:
如果我们不希望组件的根元素继承attribute,可以在组件中设置 inheritAttrs: false:
禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素;
我们可以通过 $attrs来访问所有的 非props的attribute;
vue<template> <div> <h2 :class="$attrs.class"></h2> </div> </template> <script> export default { inheritAttrs: false, } </script> <style scoped> </style>
多个根节点的attribute
- 多个根节点的attribute如果没有显示的绑定,那么会报警告,我们必须手动的指定要绑定到哪一个属性上:
11.5子组件传递给父组件
什么情况下子组件需要传递内容到父组件呢?
当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;
子组件有一些内容想要传递给父组件的时候;
我们如何完成上面的操作呢?
首先,我们需要在子组件中定义好在某些情况下触发的事件名称,使用emits定义;
其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;
最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
父组件:
vue<template> <div> <h2>当前计数: {{counter}}</h2> <counter-operation @add="addOne" @sub="subOne" @addN="addNNum"> </counter-operation> </div> </template> <script> import CounterOperation from './CounterOperation.vue'; export default { components: { CounterOperation }, data() { return { counter: 0 } }, methods: { addOne() { this.counter++ }, subOne() { this.counter-- }, addNNum(num, name, age) { console.log(name, age); this.counter += num; } } } </script> <style scoped> </style>
子组件:
vue<template> <div> <button @click="increment">+1</button> <button @click="decrement">-1</button> <input type="text" v-model.number="num"> <button @click="incrementN">+n</button> </div> </template> <script> export default { // emits: ["add", "sub", "addN"], // 对象写法的目的是为了进行参数的验证 emits: { add: null, sub: null, addN: (num, name, age) => { console.log(num, name, age); if (num > 10) { return true } return false; } }, data() { return { num: 0 } }, methods: { increment() { console.log("+1"); this.$emit("add"); }, decrement() { console.log("-1"); this.$emit("sub"); }, incrementN() { this.$emit('addN', this.num, "why", 18); } } } </script> <style scoped> </style>
自定义事件的参数和验证
- 自定义事件的时候,我们也可以传递一些参数给父组件:
- 在vue3当中,我们可以对传递的参数进行验证
11.6 非父子组件的通信
- 在开发中,我们构建了组件树之后,除了父子组件之间的通信之外,还会有非父子组件之间的通信。
- 这里我们主要有两种方式:
- Provide/Inject;
- Mitt全局事件总线
11.6.1 Provide(提供)和Inject(注入)
Provide/Inject用于非父子组件之间共享数据:
- 比如有一些深度嵌套的组件,子组件想要获取父组件的部分内 容;
- 在这种情况下,如果我们仍然将props沿着组件链逐级传递下 去,就会非常的麻烦;
对于这种情况下,我们可以使用 Provide 和 Inject :
- 无论层级结构有多深,父组件都可以作为其所有子组件的依赖 提供者;
- 父组件有一个 provide 选项来提供数据;
- 子组件有一个 inject 选项来开始使用这些数据;
实际上,你可以将依赖注入看作是“long range props”,除了:
父组件不需要知道哪些子组件使用它 provide 的 property
子组件不需要知道 inject 的 property 来自哪里
11.6.2 Provide(提供)和Inject(注入)基本使用
结构如下:
::: mermaid flowchart LR; id1["App.vue"]-->id2["Home.vue"] id2-->id3["HomeContent.vue"] :::
示例:
App.vue:
<template>
<div>
<home></home>
<button @click="addName">+name</button>
</div>
</template>
<script>
import Home from './Home.vue';
import { computed } from 'vue';
export default {
components: {
Home
},
provide() {
return {
name: "why",
age: 18,
}
}
}
}
</script>
HomeContent.vue:
<template>
<div>
HomeContent: {{name}} - {{age}} - {{length.value}}
</div>
</template>
<script>
export default {
inject: ["name", "age", "length"],
}
</script>
<style scoped>
</style>
如果Provide中提供的一些数据是来自data,那么要使用provide()函数对象,否者会报错。
例:
vue<script> provide() { return { name: "why", age: 18, length: computed(() => this.names.length) // ref对象 .value } }, data() { return { names: ["abc", "cba", "nba"] } } </script>
在provide中引入的 this.names.length 本身并不是响应式的;
如果我们需要数据变成响应式的, 需要使用响应式的一些API来完成这些功能,比如说computed函数;
注意:我们在使用length的时候需要获取其中的value ,这是因为computed返回的是一个ref对象,需要取出其中的value来使用;
11.6.3 全局事件总线mitt库
Vue3从实例中移除了 $on、$off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库
- Vue3官方有推荐一些库,例如 mitt 或 tiny-emitter;
首先,我们需要先安装这个库:
bashnpm install mitt
其次,我们可以封装一个工具eventbus.js:
jsimport mitt from 'mitt'; const emitter = mitt(); export default emitter;
使用事件总线工具
在项目中可以使用它们:
我们在Home.vue中监听事件;
vue<script> import emitter from './utils/eventbus'; export default { created() { emitter.on("why", (info) => { console.log("why:", info); }); } } </script>
我们在App.vue中触发事件;
vue<script> import emitter from './utils/eventbus'; export default { methods: { btnClick() { console.log("about按钮的点击"); emitter.emit("why", {name: "why", age: 18}); } } } </script>
Mitt的事件取消
js//取消emitter中所有的监听 emitter.all.clear() //定义一个函数 function onFoo(){} emitter.on('foo',onFoo) //监听 emitter.off('foo',onFoo)//取消
11.7 vue的插槽
<slot>
元素是一个插槽的插口,标示了父元素提供的插槽内容将在哪里被渲染。
- 插槽的使用过程其实是抽取共性、预留不同;
- 我们会将共同的元素、内容依然在组件内进行封装;
- 如何使用slot呢?
- Vue中将 元素作为承载分发内容的出口;
- 在封装组件中,使用特殊的元素就可以为封装组件开启一个插槽;
- 该插槽插入什么内容取决于父组件如何使用
11.7.1 插槽的基本使用
示例:
App.vue
<template>
<div>
<my-slot-cpn>
<button>我是按钮</button>
</my-slot-cpn>
</div>
</template>
<script>
import MySlotCpn from './MySlotCpn.vue';
export default {
components: {
MySlotCpn,
}
}
</script>
按钮在MySlotCpn.vue
的<slot>
标签内展示,相当于slot=<button>我是按钮</button>
<template>
<div>
<slot>
</slot>
</div>
</template>
<script>
export default {
}
</script>
11.7.2 具名插槽的使用
事实上,我们希望达到的效果是插槽对应的显示,这个时候我们就可以使用 具名插槽:
具名插槽顾名思义就是给插槽起一个名字, 元素有一个特殊的 attribute:name;
一个不带 name 的slot,会带有隐含的名字 default;
跟 v-on 和 v-bind 一样,v-slot 也有缩写;
即把参数之前的所有内容 (v-slot:) 替换为字符
#
;注意: 如果还有其他的具名插槽, 那么默认插槽也必须使用template来编写
示例:
App.vue
<template>
<div>
<nav-bar :name="name">
<template #left>
<button>左边的按钮</button>
</template>
<template #center>
<h2>我是标题</h2>
</template>
<template #right>
<i>右边的i元素</i>
</template>
<template #[name]>
<i>why内容</i>
</template>
</nav-bar>
</div>
</template>
<script>
import NavBar from './NavBar.vue';
export default {
components: {
NavBar
}
}
</script>
NavBar.vue
<template>
<div class="nav-bar">
<div class="left">
<slot name="left"></slot>
</div>
<div class="center">
<slot name="center"></slot>
</div>
<div class="right">
<slot name="right"></slot>
</div>
<div class="addition">
<slot :name="name"></slot>
</div>
</div>
</template>
<script>
export default {
props: {
name: String
}
}
</script>
11.7.3 动态插槽名
- 什么是动态插槽名呢?
- 目前我们使用的插槽名称都是固定的;
- 比如
v-slot:left
、v-slot:center
等等; - 我们可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称;
示例:
<template>
<div>
<nav-bar :name="name">
<template #[name]>
<i>why内容</i>
</template>
</nav-bar>
</div>
</template>
<script>
import NavBar from './NavBar.vue';
export default {
components: {
NavBar
},
data() {
return {
name: "why"
}
}
}
</script>
11.7.4 插槽的渲染作用域
在Vue中有渲染作用域的概念:
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的;
如果我们希望插槽可以访问到子组件中的内容:
- 当一个组件被用来渲染一个数组元素时,我们使用插槽,并且希望插槽中没有显示每项的内容;
- 我们可以使用作用域插槽
示例:
App.vue
<template>
<div>
<show-names :names="names">
<template v-slot="slotProps">
<strong>{{slotProps.item}}-{{slotProps.index}}</strong>
</template>
</show-names>
</div>
</template>
<script>
import ShowNames from './ShowNames.vue';
export default {
components: {
ShowNames
},
data() {
return {
names: ["why", "kobe", "james", "curry"]
}
}
}
</script>
ShowNames.vue:
<template>
<div>
<template v-for="(item, index) in names" :key="item">
<slot :item="item" :index="index"></slot>
</template>
</div>
</template>
<script>
export default {
props: {
names: {
type: Array,
default: () => []
}
}
}
</script>
- 如果我们的插槽是默认插槽default,那么在使用的时候 v-slot:default="slotProps"可以简写为v-slot="slotProps":
- 并且如果我们的插槽只有默认插槽时,组件的标签可以被当做插槽的模板来使用,这样,我们就可以将 v-slot 直 接用在组件上
v-slot="slotProps"
可以类比这里的函数签名,和函数的参数类似,我们也可以在 v-slot
中使用解构:
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name="slotProps"。当使用缩写时是这样:
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
11.8 动态组件的使用
- 比如我们现在想要实现了一个功能:
- 点击一个tab-bar,切换不同的组件显示;
- 我们可以通过两种不同的实现思路来实现:
- 方式一:通过v-if来判断,显示不同的组件;
- 方式二:动态组件的方式
v-if实现
<template>
<div>
<button v-for="item in tabs" :key="item"
@click="itemClick(item)"
:class="{active: currentTab === item}">
{{item}}
</button>
<!-- 1.v-if的判断实现 -->
<template v-if="currentTab === 'home'">
<home></home>
</template>
<template v-else-if="currentTab === 'about'">
<about></about>
</template>
<template v-else>
<category></category>
</template>
</div>
</template>
<script>
import Home from './pages/Home.vue';
import About from './pages/About.vue';
import Category from './pages/Category.vue';
export default {
components: {
Home,
About,
Category
},
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home"
}
},
methods: {
itemClick(item) {
this.currentTab = item;
},
pageClick() {
console.log("page内部发生了点击");
}
}
}
</script>
<style scoped>
.active {
color: red;
}
</style>
动态组件的实现:
动态组件是使用 component 组件,通过一个特殊的attribute is 来实现:
这个currentTab的值需要是什么?
可以是通过component函数注册的组件;
在一个组件对象的components对象中注册的组件;
动态组件我们可以给它们传值和监听事件
- 我们只需要将属性和监听事件放到component上来使用;
<template>
<div>
<button v-for="item in tabs" :key="item"
@click="itemClick(item)"
:class="{active: currentTab === item}">
{{item}}
</button>
<!-- 2.动态组件 -->
<component :is="currentTab"
name="coderwhy"
:age="18"
@pageClick="pageClick">
</component>
</div>
</template>
<script>
import Home from './pages/Home.vue';
import About from './pages/About.vue';
import Category from './pages/Category.vue';
export default {
components: {
Home,
About,
Category
},
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home"
}
},
methods: {
itemClick(item) {
this.currentTab = item;
},
pageClick() {
console.log("page内部发生了点击");
}
}
}
</script>
<style scoped>
.active {
color: red;
}
</style>
11.9 keep-alive的使用
在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件: keep-alive。
<keep-alive include="home,about">
<component :is="currentTab"
name="coderwhy"
:age="18"
@pageClick="pageClick">
</component>
</keep-alive>
keep-alive有一些属性:
include - string | RegExp | Array。只有名称匹配的组件会被缓 存;
exclude - string | RegExp | Array。任何名称匹配的组件都不 会被缓存;
max - number | string。最多可以缓存多少组件实例,一旦达 到这个数字,那么缓存组件中最近没有被访问的实例会被销毁;
示例:
<!-- 逗号分割字符串 -->
<keep-alive include="home,about">
<component :is="currentTab"</component>
</keep-alive>
<!-- regx(使用`v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="currentTab"</component>
</keep-alive>
<!-- Array(使用`v-bind`) -->
<keep-alive :include="['a','b']">
<component :is="currentTab"</component>
</keep-alive>
- include 和 exclude prop 允许组件有条件地缓存:
- 二者都可以用逗号分隔字符串、正则表达式或一个数组来表示;
- 匹配首先检查组件自身的 name 选项;
11.10 异步组件的使用
- 如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那 么Vue中给我们提供了一个函数:defineAsyncComponent。
- defineAsyncComponent接受两种类型的参数:
- 类型一:工厂函数,该工厂函数需要返回一个Promise对象;
- 类型二:接受一个对象类型,对异步函数进行配置;
类型一:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
import { defineAsyncComponent } from 'vue'
export default {
// ...
components: {
AsyncComponent: defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
}
}
// const AsyncCategory = defineAsyncComponent(() => import("./AsyncCategory.vue"))
// export default {
// components: {
// Home,
// AsyncCategory,
// Loading
// }
// }
类型二:
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
// /**
// * err: 错误信息,
// * retry: 函数, 调用retry尝试重新加载
// * attempts: 记录尝试的次数
// */
// onError: function(err, retry, attempts) {
// }
})
11.10.1 内置组件Suspense的使用
来自vue3官网的解释
<Suspense>
组件有两个插槽:#default
和 #fallback
。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
<template>
<div>
App组件
<home></home>
<suspense>
<template #default>
<async-category></async-category>
</template>
<template #fallback>
<loading></loading>
</template>
</suspense>
</div>
</template>
在初始渲染时,<Suspense>
将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense>
会进入完成状态,并将展示出默认插槽的内容。
如果在初次渲染时没有遇到异步依赖,<Suspense>
会直接进入完成状态。
进入完成状态后,只有当默认插槽的根节点被替换时,<Suspense>
才会回到挂起状态。组件树中新的更深层次的异步依赖不会造成 <Suspense>
回退到挂起状态。
发生回退时,后备内容不会立即展示出来。相反,<Suspense>
在等待新内容和异步依赖完成时,会展示之前 #default
插槽的内容。这个行为可以通过一个 timeout
prop 进行配置:在等待渲染新内容耗时超过 timeout
之后,<Suspense>
将会切换为展示后备内容。若 timeout
值为 0
将导致在替换默认内容时立即显示后备内容。
11.11 引用元素和组件
11.11.1 $refs的使用
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例:
在Vue开发中我们是不推荐进行DOM操作的;
这个时候,我们可以给元素或者组件绑定一个ref的attribute属性;
vue<template> <div> <!-- 绑定到一个元素上 --> <h2 ref="title">哈哈哈</h2> <!-- 绑定到一个组件实例上 --> <nav-bar ref="navBar"></nav-bar> <button @click="btnClick">获取元素</button> </div> </template>
组件实例有一个$refs属性:
它一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例
jsvisitElement(){ //访问元素 console.log(this.$refs.title) //访问组件实例 console.log(this.$refs.helloCpn.$el) //访问组件实例 this.$refs.helloCpn.showMessage(); }
我们可以通过$parent来访问父元素。
HelloWorld.vue的实现:
这里我们也可以通过$root来实现,因为App是我们的根组件;
jsvisitparent(){ console.log(this.$parent.message) console.log(this.$root.message; }
注意:在Vue3中已经移除了$children的属性,所以不可以使用了。
11.12 组件的生命周期
什么是生命周期呢?
- 每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程;
- 在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑(比如组件创建完后就请求一些服 务器数据);
- 但是我们如何可以知道目前组件正在哪一个过程呢?Vue给我们提供了组件的生命周期函数;
生命周期函数:
生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调;
通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段;
那么我们就可以在该生命周期中编写属于自己的逻辑代码了;
生命周期的流程
beforeCreate() {
console.log("home beforeCreate");
},
created() {
console.log("home created");
},
beforeMount() {
console.log("home beforeMount");
},
mounted() {
console.log("home mounted");
},
beforeUnmount() {
console.log("home beforeUnmount");
},
unmounted() {
console.log("home unmounted");
},
beforeUpdate() {
console.log(this.$refs.title.innerHTML);
console.log("home beforeUpdate");
},
updated() {
console.log(this.$refs.title.innerHTML);
console.log("home updated");
}
11.12.1 缓存组件的生命周期
- 对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的:
- 但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;
- 这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听;
activated(){
console.log("about activated")
},
deactivated(){
console.log("about deactivated")
}
11.13 组件的v-model
如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢?
也是可以的,vue也支持在组件上使用v-model;
当我们在组件上使用的时候,等价于如下的操作:
<!-- 组件上使用v-model -->
<hy-input v-model="message"></hy-input>
<hy-input :modelValue="message" @update:model-value="message = $event"></hy-input>
- 我们会发现和input元素不同的只是属性的名称和事件触发的名称而已
11.13.1 组件v-model的实现
为了我们的MyInput组件可以正常的工作,这个组件内的必须:
将其 value attribute 绑定到一个名叫 modelValue 的 prop 上;
在其 input 事件被触发时,将新的值通过自定义的 update:modelValue 事件抛出;
MyInput.vue的组件代码如下:
vue<template> <div> <!-- 1.默认绑定和事件处理 --> <!-- <button @click="btnClick">hyinput按钮</button> <h2>HyInput的message: {{modelValue}}</h2> --> <!-- 2.通过input --> <!-- <input :value="modelValue" @input="btnClick"> --> <!-- 3.绑定到props中是不对的 --> <!-- <input v-model="modelValue"> --> </div> </template> <script> export default { props: { modelValue: String }, emits: ["update:modelValue"], methods: { btnClick(event) { this.$emit("update:modelValue", event.target.value); } } } </script>
TIP
不建议修改props来实现双向绑定
11.13.2 computed实现
<template>
<div>
<input v-model="value">
</div>
</template>
<script>
export default {
props: {
modelValue: String
},
emits: ["update:modelValue"],
computed: {
value: {
set(value) {
this.$emit("update:modelValue", value);
},
get() {
return this.modelValue;
}
}
}
}
</script>
11.13.3 绑定多个属性
<!-- 绑定两个v-model -->
<hy-input v-model="message" v-model:title="title"></hy-input>
v-model:title相当于做了两件事:
绑定了title属性;
监听了 @update:title的事件;
vue<template> <div> <input v-model="value"> <input v-model="why"> </div> </template> <script> export default { props: { modelValue: String, title: String }, emits: ["update:modelValue", "update:title"], computed: { value: { set(value) { this.$emit("update:modelValue", value); }, get() { return this.modelValue; } }, why: { set(why) { this.$emit("update:title", why); }, get() { return this.title; } } } } </script>