Vue3学习笔记3 生命周期、异步组件、代码分包、Suspense、依赖注入、Mitt、v-model、自定义指令directive
1. 生命周期2. 异步组件-代码分包-suspense3. Teleport传送组件4. 依赖注入Provide / Inject5. Mitt库的使用6. v-model和.sync修饰符7. 自定义指令
寒假学的时候属于是有点过于敷衍了,现在update一下😮💨
最近是跟ts一起学的,所以里面会有ts相关的内容(无伤大雅无伤大雅
内容
1. 生命周期
vue3提供了api形式的生命周期:
setup()
: beforeCreate
、created
中的代码写在setup()
中(vue3没有提供
beforeCreate
、created
对应的组合式API
onBeforeMount
: DOM即将挂载,这个里面还获取不到DOM
onMounted
: DOM挂载完毕,可以获取到DOM了
onBeforeUpdate
: DOM即将更新,比如更改了一些写在模板里的响应式数据,就会触发
onUpdated
: DOM更新完毕,onBeforeUpdate
触发后页面会重新渲染,渲染完就会执行onUpdated
onBeforeUnmount
: 即将卸载,对应vue2中的 beforeDestroy
onUnmounted
: 卸载完成,对应vue2中的destroyed
2. 异步组件-代码分包-suspense
在大型应用中,我们可能需要将应用分割成小一些的代码块,以此来减小主包的体积
目前build完是这样:
如果说项目很大,加载index.29ed9fb4.js
这个主包就要耗费长时间,这样给用户体验可能就会不好
我们就可以使用异步组件
来优化这个问题:
<script setup lang="ts">
// 异步引入
import { defineAsyncComponent } from "vue";
const A = defineAsyncComponent(() => import("./components/A.vue"));
// 静态引入
// import A from "./components/A.vue";
</script>
这样再build:
这个异步组件就会单独分一个包
配合Suspense
使用:
首先我们先建一个server.ts
(叫什么也随便啦
定时器模拟一下异步请求,这里延迟了3s
export const request = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 3000)
})
}
在子组件A.vue
中:
热知识:在script-setup
中最顶层可以直接使用await
,不需要async
(如果是放在script-setup
的函数中,那就需要给那个函数加async
关键字了
<script setup lang="ts">
import { request } from "./server";
const number = await request();
console.log(number); // 123
defineExpose({
number,
});
</script>
<template>
<div>{{ number }}</div>
</template>
在父组件App.vue
中,就需要使用Suspense
了
Suspense
就是里面提供了两个插槽,一个叫default
,default
里面放子组件。一个叫fallback
,里面放加载完子组件之前出现的东西
<script setup lang="ts">
// 异步引入
import { defineAsyncComponent } from "vue";
const A = defineAsyncComponent(() => import("./components/A.vue"));
// 同步引入
// import A from "./components/A.vue";
</script>
<template>
我是app
<Suspense>
<template #default>
<A></A>
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
然后现在的效果就会是👇这样:
3s之后:
(⚠️:async setup()必须和Suspense配合使用(🔒死,如果不用的话官方会警告:
那个组件会渲染不出来的
setup function returned a promise, but no boundary was
found in the parent component tree. A component with async setup()
must be nested in a in order to be rendered.
(⚠️:一个异步组件,在它被引用的父组件里面,都必须以defineAsyncComponent
的方式引入,不能一些以defineAsyncComponent
引入,一些以import
引入,否则打包时会出现粘包问题,导致异步组件无法分包(我刚刚就犯这个错了,检查了半天遗忘了注册的全局组件😢
3. Teleport传送组件
作用:能将我们的组件的html结构移动到指定位置,不受父级style
、v-show
等属性影响,但数据依旧能够共用
下面是一段实现弹窗的代码,如果这段代码放在某个层次较深的组件中,但是弹窗希望相对body定位,就可以像下面这样写
<teleport to="body"> <!--这里的to后接指定位置-->
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗/h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
就是说如果teleport
组件外有套父级元素,是不会受其样式
和v-show
影响的,但要注意如果父元素设置了v-if
,是会被影响的!!
4. 依赖注入Provide / Inject
作用:实现祖先和后代组件间通信,可以看作是长距离的prop
祖先组件有一个provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据(父子组件也可以用,但是没必要)
祖先组件中:
<script setup lang="ts">
import { provide, ref } from "vue";
const data = ref<string>("poem");
provide("data", data);
</script>
后代组件:
<script setup lang="ts">
import { inject, Ref } from "vue";
const data = inject("data");
// 这里如果要用到data.value但是不断言的话ts就会报错
// Ref是ref的interface
console.log((data as Ref<string>).value);
</script>
<template>
<div>{{ data }}</div>
</template>
5. Mitt库的使用
vue3中移除了$on
、$off
和 $once
方法,我们可以借助官方推荐的Mitt
库来实现事件总线模式。(但是官方说并不推荐全局的事件总线在组件间进行通信,长期维护困难,不过咱们先学再说
- 安装
npm i mitt -s
main.ts
中初始化
挂载为全局属性
import mitt from 'mitt'
const Mit = mitt()
const app = createApp(App)
// 要扩展ComponentCustomProperties类型才能获得类型提示,不然使用的时候会报错
// ComponentCustomProperties是vue3中实例的属性的类型
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
// 挂载全局api
app.config.globalProperties.$Bus = Mit
app.mount('#app')
- 使用
在App.vue
中使用A
子组件和B
子组件(A和B是兄弟组件)
<template>
<A></A>
<B></B>
</template>
假设现在要实现A 向 B 派发
// A.vue
<script setup lang="ts">
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
const emit = () => {
instance?.proxy?.$Bus.emit("send-message", "mitt");
};
</script>
<template>
<div>
<h1>我是A</h1>
<button @click="emit">emit</button>
</div>
</template>
B组件中监听:
// B.vue
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
instance?.proxy?.$Bus.on("send-message", msg => {
console.log(msg); // mitt
});
如果要派发多个事件:
// A
const emit = () => {
instance?.proxy?.$Bus.emit("send-message1", "mitt1");
instance?.proxy?.$Bus.emit("send-message2", "mitt2");
};
// B
instance?.proxy?.$Bus.on("*", (eventName, msg) => {
console.log(eventName, msg);
// send-message1 mitt1
// send-message2 mitt2
});
off()
移除事件,clear()
清空所有
<script setup lang="ts">
import { Handler } from "mitt";
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
// 这里必须提出来共有的,不然off无效
const Bus = (param: string): void => {
console.log(param);
};
instance?.proxy?.$Bus.on("send-message", <Handler>Bus);
// off(取消指定的mitt事件,取消的函数)
instance?.proxy?.$Bus.off("send-message", <Handler>Bus);
</script>
<script setup lang="ts">
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
instance?.proxy?.$Bus.on("send-message", (msg): void => {
console.log(msg);
});
// 取消所有事件
instance?.proxy?.$Bus.all.clear();
</script>
6. v-model和.sync修饰符
6.1 vue2.x
v-model
相当于绑定了value
属性+触发input
事件
// App.vue
<child v-model="pageTitle"></child>
//等效于
<child :value="pageTitle" @input="pageTitle = $event"></child>
如果想要修改属性名
/事件名
,需要添加model
选项:
// Child.vue
export default {
model: {
prop: 'title', // 属性名 value => title
event: 'change' // 事件名 input => change
},
props: {
// 使用 title 代替 value 作为 model 的 prop
title: {
type: String
}
}
}
添加完model
选项就等效于:
<child :title="pageTitle" @change="pageTitle = $event"></child>
顺便再介绍一下vue2.x中的v-bind.sync
,这是一种不使用v-model
,但也可以用来对一个prop实现双向绑定
的方法
// App.vue
<child :title.sync="pageTitle" />
// 等效于
<child :title="pageTitle" @update:title="pageTitle = $event" />
// Child.vue
// 需要将修改的新值传达给父组件
this.$emit('update:title', newValue)
6.2 vue3.x
删除了.sync
和model
选项,替换为v-model
v-model
相当于绑定了modelValue
属性并接收抛出的update:modelValue
事件
// App.vue
<child v-model="pageTitle"></child>
// 等效于
<child :modelValue="pageTitle" @update:modelValue="pageTitle = $event"></child>
如果要修改属性的名称,像👇这样改
官方说这也可以当作之前.sync
修饰符的代替,总而言之就是现在的v-model
把之前的.sync
修饰符和model
选项功能合并了
// 这就修改为title
<child v-model:title="pageTitle"></child>
// 等效于
<child :title="pageTitle" @update:title="pageTitle = $event"></child>
并且可以在组件上使用多个v-model
<child v-model:title="pageTitle" v-model:content="pageContent"></child>
7. 自定义指令directive
自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的
在vue3.x中,指令的钩子函数被重命名,为了更好地和组件的钩子函数保持一致
// 指令的钩子函数
created() {} // 元素初始化
beforeMount() {} // 指令绑定到元素,只调用一次
mounted() {} // 元素插入dom
beforeUpdate() {} // 元素被更新
updated() {} // 元素更新完
beforeUnmount() {} // 元素被移除前
unmounted() {} // 指令被移除后,只调用一次
钩子函数有4个参数:
el
:指令绑定到的元素,可用来直接操作dombinding
:一个对象,包含了一些属性,比较重要的是value
属性:传递给指令的值,其他就不列在这了,要用的时候可以自己打印vnode
:el
树的所有节点prevNode
:上一个虚拟节点,仅在beforeUpdate
和updated
钩子中可用
(⚠️:除了beforeUpdate
和updated
钩子,其他都只有el
、binding
、vnode
这3个参数)
(⚠️:除了el
之外,其他参数应该视为只读,不要修改它们)
7.1 全局注册
注册全局指令:app.directive()
接收两个参数:
- 参数1: 指令的名字
- 参数2: 可以是函数/对象,可选,不传的话用来检索全局指令
import { createApp } from 'vue'
import App from './App.vue'
// 第二个参数传函数,函数写法只在mounted和updated上触发,不关心其他钩子
app.directive('my-directive', (el,binding,vNode,prevNode) => {
console.log(el,binding,vNode,prevNode)
})
// 第二个参数传对象,对象写法包含指令的所有生命周期
app.directive('my-directive', {
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {},
unmounted() {}
})
在组件中使用:模板中使用:v-指令名
<template>
<h1 v-my-directive></h1>
</template>
7.2 局部注册
名字必须要以vNameOfDirective
的形式来命名,才能在模板中使用
对象方法也跟上面一样的用法
<script setup lang="ts">
import { Directive, DirectiveBinding } from "vue";
const vMyDirective: Directive = (el: HTMLElement, binding: DirectiveBinding) => {
el.style.backgroundColor = binding.value;
};
</script>
<template>
<h1 v-my-directive="'red'">标题</h1>
</template>
参考:
https://v3.cn.vuejs.org/guide/migration/v-model.html#_3-x-语法
更多推荐
所有评论(0)