寒假学的时候属于是有点过于敷衍了,现在update一下😮‍💨
最近是跟ts一起学的,所以里面会有ts相关的内容(无伤大雅无伤大雅


1. 生命周期

vue3提供了api形式的生命周期:

setup(): beforeCreatecreated中的代码写在setup()中(vue3没有提供

beforeCreatecreated对应的组合式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就是里面提供了两个插槽,一个叫defaultdefault里面放子组件。一个叫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结构移动到指定位置,不受父级stylev-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库来实现事件总线模式。(但是官方说并不推荐全局的事件总线在组件间进行通信,长期维护困难,不过咱们先学再说

  1. 安装
    npm i mitt -s
  2. 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')
  1. 使用
    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
});
  1. 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

删除了.syncmodel选项,替换为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:指令绑定到的元素,可用来直接操作dom
  • binding:一个对象,包含了一些属性,比较重要的是value属性:传递给指令的值,其他就不列在这了,要用的时候可以自己打印
  • vnode: el树的所有节点
  • prevNode:上一个虚拟节点,仅在beforeUpdateupdated钩子中可用
    (⚠️:除了beforeUpdateupdated钩子,其他都只有elbindingvnode这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-语法

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐