超全 Vue3新特性总结

Vue3 应用-技术分享与交流

新特性篇

Vue3 组合式 API VS Vue2 选项式 API

选项式 API 面临的问题:

我们在处理业务逻辑时,需要在 data computed method watch 中分别进行代码编写,碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。我们必须不断地“跳转”相关代码的选项块,让维护变得困难,也不利于代码的可读性。

在这里插入图片描述
在这里插入图片描述

组合式 API 优势

1.更好的逻辑复用

组合式 API 最基本的优势是它使我们能够通过组合函数来实现更加简洁高效的逻辑复用。在选项式 API 中我们主要的逻辑复用机制是 mixins,而组合式 API 解决了 mixins 的所有缺陷。

2.更灵活的代码组织

大部分代码都自然地被放进了对应的选项里

3.更好的类型推导

近几年来,越来越多的开发者开始使用 TypeScript 书写更健壮可靠的代码,TypeScript 还提供了非常好的 IDE 开发支持。大多数时候,用 TypeScript 书写的组合式 API 代码和用 JavaScript 写都差不太多!这也让许多纯 JavaScript 用户也能从 IDE 中享受到部分类型推导功能。

4.更小的生产包体积

搭配 <script setup> 使用组合式 API 比等价情况下的选项式 API 更高效,对代码压缩也更友好

数据响应式原理 Proxy 相对于 Object.defineProperty

  • proxy 的性能本来比 defineProperty 好,proxy
    可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性。

  • 可以监听数组变化

  • 可以劫持整个对象

  • 操作时不是对原对象操作,是 new Proxy 返回的一个新对象

  • 可以劫持的操作有 13 种

diff 更高效
  • vue3.x 中标记和提升所有的静态节点,diff 的时候只需要对比动态节点内容
小结

vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便

<script setup> 语法糖

  • <\script setup> 中的代码会在每次组件实例被创建的时候执行
  • 顶层的绑定会被暴露给模板
    当使用 <script setup> 的时候,任何在<script setup>声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用
  • 使用组件
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
  <MyComponent />
</template>
  • 动态组件
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

从已知的角度(vue2)出发学习vue3

生命周期钩子

  • setup 代替了 beforeCreate,created,如果在 options api 中书写,会在 beforeCreate 之前执行

  • mounted、beforeMount、beforeUpdate、updated ,都改成 onxxx

  • beforeDestroy 改成 onBeforeUnmount;

  • destroyed 改成 onUnmounted

VUE 生命周期
在这里插入图片描述


> onBeforeMount()	beforeMount	在组件被挂载之前被调用 onMounted()	mounted	在组件挂载完成后执行
> onBeforeUpdate()	beforeUpdate	在组件即将因为响应式状态变更而更新其 DOM 树之前调用
> onUpdated()	updated	在组件因为响应式状态变更而更新其 DOM 树之后调用
> onBeforeUnmount	beforeDestroy	在组件实例被卸载之前调用
> onUnmounted()	destroyed	在组件实例被卸载之后调用
> onErrorCaptured()	errorCaptured	在捕获了后代组件传递的错误时调用

nextTick

import { nextTick } from "vue";
// DOM更新完毕之后执行回调的两种写法
nextTick(() => {
  console.log("DOM更新完毕");
});
await nextTick();
console.log("DOM更新完毕");

双向绑定 v-model

  • 修饰符 .lazy
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
  • 修饰符 .number
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.number="age" />
  • 修饰符 .trim
<!-- 自动去除用户输入内容中两端的空格 -->
<input v-model.trim="msg" />

响应式

  • 基础类型 ref
<template>
  <div>{{ count }}</div>
</template>

<script setup>
const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
</script>
  • reactive 定义复杂数据类型的响应式


<template>
  <div>{{userInfo.name}}</div>
</template>

<script setup>
import {reactive} from 'vue'

const userInfo = reactive({
    name:''
})
</script>
  • toRef()
    当我们在渲染数据时,不希望用到前缀时,可以使用组合 toRef()

    toRef()是函数,转换响应式对象中的某个属性为单独响应式数据,他们之间依然相互绑定
<div>{{ name }}</div>;

import { reactive } from "vue";

const userInfo = reactive({
  name: "",
});
const name = toRef(userInfo, "name");
  • shallowRef()
    ref() 的浅层作用形式
const state = shallowRef({ count: 1 });

// 不会触发更改
state.value.count = 2;

// 会触发更改
state.value = { count: 2 };

组件传参

  • 父-子
// 父组件
<template>
  <Child :name="name" />
</template>
// 子组件
<script setup>
const props = defineProps({
  name: { type: String, default:'', required: true }
})
</script>
  • 子-父
// 子组件
<script setup>
const emit = defineEmits(['updateName'])
emit('updateName', name.value)
</script>

// 父组件
<template>
  <Child  @updateName="handleUpdate" />
</template>
<script setup>
const handleUpdate=()={
  //TODO
}
</script>
  • 父组件调用子组件方法传参
// 子组件
<script setup>
import { ref,defineExpose } from 'vue'
const emit = defineEmits(['updateName'])
const changeName = (val) => {
  console.log(val)
}
// 将方法、变量暴露给父组件使用,父组件才可通过ref API拿到子组件暴露的数据
defineExpose({
  changeName
})
</script>

//父组件
<template>
  <Child ref="childElementRef" />
</template>

<script setup>
import { ref,onMounted } from 'vue'
import Child from './Child.vue'
const childElementRef = ref()
onMounted(() => {
  console.log(childElementRef.value.changeName()) // DOM 元素将在初始渲染后分配给 ref
})
</script>

依赖注入 provide 和 inject(没变化)

  • demo
// 父级/祖先 组件
<script lang="ts" setup>
import { provide,computed } from 'vue'
const message = ref('hello')
provide('message',message)
</script>

// 后代组件接收参数
<script lang="ts" setup>
import { inject,Ref,ref } from 'vue'
const r = inject<Ref<string>>('message',ref('defaultMessage'))
// r.value
</script>

计算属性

  • 简单用法
<template>
  <span>{{ nextYear }} </span>
</template>
<script lang="ts" setup>
import { ref, reactive, computed} from 'vue'
const year = ref<number>(2022)
const nextYear  = computed(() => {
  return year.value++
})
</script>

  • 重写 get set
const nextYear = computed({
  get() {
    return year.value++;
  },
  set(value) {
    return (year = value + 2);
  },
});

watch

  • watch options 参数配置

    watch(WatcherSource, Callback, [WatchOptions])
    参数:
    WatcherSource:想要监听的响应式数据。
    Callback:执行的回调函数,入参(newValue,oldValue)。
    [WatchOptions]:deep、immediate、flush 可选。

deep:当需要对对象等引用类型数据进行深度监听时,设置 deep: true,默认值是 false。
immediate:默认情况下 watch 是惰性的,设置 immediate: true 时,watch 会在初始化时立即执行回调函数一次。
flush:控制回调函数的执行时机,。它可设置为 pre、post 或 sync。

pre:默认值,当监听的值发生变更时,优先执行回调函数(在 dom 更新之前执行)。
post:dom 更新渲染完毕后,执行回调函数。
sync:一旦监听的值发生了变化,同步执行回调函数(建议少用)。

  • 基本用法 监听单个数据 ref
<script lang="ts" setup>
import { watch, ref } from 'vue';
const hidden = ref<boolean>(false)
watch(hidden, (newValue, oldValue) => {
  console.log(`${oldValue} -> ${newValue}`)
})
</script>
  • 引用类型 ref 直接深度监听

    只能获取到新值,而获取不到旧的值

const count = ref({
  a: 1,
  b: 2,
});
const handleClick = function () {
  count.value.a = 5;
};
watch(
  count,
  (newValue, oldValue) => {
    console.log("值发生了变更", newValue, oldValue);
  },
  { deep: true }
);
/*
使用深拷贝解决不能获取旧值问题
watch(
  () => {
    return { ...count.value };
  },
  (newValue, oldValue) => {
    console.log('值发生了变更', newValue, oldValue);
  },
  { deep: true }
)
*/
  • 监听单个数据:reactive
const single = reactive({ count: 1, test: 2 });
const handleClick = function () {
  single.count++;
};
watch(
  () => single.count,
  (newValue, oldValue) => {
    console.log("值发生了变更", newValue, oldValue);
  },
  { immediate: true }
);
  • 监听引用类型数据:reactive
<script setup>
import { ref, reactive, watch } from 'vue';
const single = reactive({ count: 1, test: { a: 1, b: 2 } });
watch(
  single,
  (newValue, oldValue) => {
    console.log('值发生了变更', newValue, oldValue);
  },
  { immediate: true }
);
</script>

过滤器

Vue3 filter 过滤器已作废

替代方案 函数或计算属性

全局过滤器方案

const app = createApp(App)

// 给当前app的全局属性上挂载一个过滤器对象
app.config.globalProperties.$filters = {
  currencyUSD(value) {
    return '$' + value
  }
}
// 使用
<template>
  <p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>

自定义指令

<script setup>中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令

<template>
  <input v-focus />
</template>
<script setup>
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

  • 全局注册
const app = createApp({});

// 使 v-focus 在所有组件中都可用
app.directive("focus", {
  /* ... */
});
  • 指令钩子函数
const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {},
};

事件修饰符

  • .stop 阻止事件冒泡
  • .prevent 阻止默认事件
  • .capture 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理
  • .once 点击事件将只会触发一次

其他

  • v-memo
    如果 valueA 和 valueB 都保持不变,这个
    及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过
<div v-memo="[valueA, valueB]">...</div>

自定义hooks

定义

Hooks本质是一个函数,用来处理复用代码逻辑的一些封装

Vue自定义Hooks有哪些作用?

1.业务密集型模块,用来实现逻辑分离,使主界面代码更清晰
2.相比于mixin,数据来源可追溯,代码逻辑清晰,不存在命名冲突等问题。
3.降低代码耦合,实现代码高复用

它有哪些特点

1.状态共享
2.内部可使用vue组合式API及生命周期钩子等
3.命名以use开头
4.需要暴露出响应式数据或者方法供外部使用

Hooks实现,“useMousePosition”实现了鼠标点击时,获取鼠标点x,y坐标

在这里插入图片描述
在这里插入图片描述