vue3.0

vue3.0

开发工具

生命周期

destroyed 生命周期选项被重命名为 unmounted
beforeDestroy 生命周期选项被重命名为 beforeUnmount

Composition API 简单使用

teleport 组件

有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置。

Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件

1
2
3
4
5
6
7
8
9
10
11
12
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>

<!-- result-->
<div id="modals">
<div>A</div>
<div>B</div>
</div>

stylelint

StyleLint 是『一个强大的、现代化的 CSS 检测工具』, 与 ESLint 类似, 是通过定义一系列的编码风格规则帮助我们避免在样式表中出现错误.
(StyleLint 使用指南)[https://www.cnblogs.com/jiaoshou/p/11284204.html]

setup 的形参

1
<script setup></script>

采用 script setup 语法糖的话这种方式就不可行了,原因是它会自动以文件名为主,不需要在写 name 属性
需要设置 name 直接在 script setup 同级中再添加一个 script 即可。

1
2
3
4
5
6
7
8
<script>
export default {
name: 'xxx',
};
</script>

<script setup>
</script>

如果同时使用了 typescript 的话,可以配合插件直接在 script 标签中设置 name,具体方式如下:

安装插件:vite-plugin-vue-setup-extend

1
<script lang="ts" setup name="xxx"></script>

reactive()ref()的比对

  1. ref() 单独地为某个数据提供响应式能力

想要保持对象内容的响应式能力,在 return 的时候必须把整个 reactive() 对象返回出去,同时在引用的时候也必须对整个对象进行引用而无法解构,否则这个对象内容的响应式能力将会丢失。

  1. reactive() 给一整个对象赋予响应式能力

在具体的业务中,如果无法使用解构取出 reactive() 对象的值,每次都需要通过 . 操作符访问它里面的属性会是非常麻烦的,所以官方提供了 toRefs() 函数来为我们填好这个坑。只要使用 toRefs() 把 reactive() 对象包装一下,就能够通过解构单独使用它里面的内容了,而此时的内容也依然维持着响应式的特性。

  1. ref可以包装基本类型和引用类型,reactive只能包装引用类型。第二点,ref取值要用.value,而reactive可以直接访问

  2. ref解构后仍然保持响应性,因为解构的是 ref 对象。而reactive解构后会丢失响应性,因为解构的是普通对象。如果需要解构并保持响应性,可以使用 toRefs。

  3. 如何选择 ref 和 reactive?

  • 优先 ref
  • 管理基本类型数据
  • 需要明确的数据引用(如传递到函数中仍保持响应性)
  • 优先 reactive
  • 管理复杂对象/数组,避免频繁使用.value
  • 需要深层嵌套的响应式数据
1
2
3
4
5
6
7
8
setup() {
const state: any = reactive({
menuList: []
})
return {
...toRefs(state)
}
}

isRef,unRef,toRef 和 toRefs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
let foo: unknown;
if (isRef(foo)) {
// foo 的类型被收窄为了 Ref<unknown>
foo.value;
}
function useFoo(x: number | Ref<number>) {
const unwrapped = unref(x);
// unwrapped 现在保证为 number 类型
}
const state = reactive({
foo: 1,
bar: 2,
});

const fooRef = toRef(state, "foo");

// 更改该 ref 会更新源属性
fooRef.value++;
console.log(state.foo); // 2

const state = reactive({
foo: 1,
bar: 2,
});

const stateAsRefs = toRefs(state);
/*
stateAsRefs 的类型:{
foo: Ref<number>,
bar: Ref<number>
}
*/

function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2,
});

// ...基于状态的操作逻辑

// 在返回时都转为 ref
return toRefs(state);
}

// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX();

ref、reactive、shallowRef、 shallowReactive、toRaw、unref、toRef、toRefs、customRef

shallowRef & shallowReactive 只对对象的第一层做监听变化

customRef 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function useDebouncedRef(value, delay = 200) {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
};
});
}

export default {
setup() {
return {
text: useDebouncedRef("hello"),
};
},
};

定义 prop emit

  1. defineProps
1
2
3
4
5
6
7
8
9
10
11
import { defineProps } from "vue";
// 类型定义
interface UserInfo {
id: number;
age: number;
}

defineProps<{
name: string;
userInfo: UserInfo;
}>();
  1. defineEmits
1
import { defineEmits } from "vue";

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<button @click="triggerFatherAdd">点击触发父组件add</button>
</template>
<script setup lang="ts">
import { defineEmits } from "vue";

// 子组件使用defineEmits向父组件抛出事件
const emits = defineEmits(["add", "update"]); //事件数组

// 触发调用子组件时的自定义事件add
const triggerFatherAdd = () => {
emits("add", "新增数据"); //后面是参数
};
</script>

父组件

1
2
3
4
5
6
7
8
9
10
<template>
<myson @add="add"></myson>
</template>

<script setup lang="ts">
import myson from "./myson.vue";
const add = (msg: string) => {
console.log("add", msg);
};
</script>
  1. defineExpose
    script setup 默认是不对外界暴露组件实例的,所以在其他组件中通过诸如$refs和$parent 都默认无法获取当前组件实例,但是通过 defineExpose 暴露的可以获取。

显示暴露的数据,才可以在父组件拿到

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import { defineExpose } from "vue";

// 定义一个想提供给父组件拿到的数据
const msg: string = "Hello World!";

// 显示暴露的数据,才可以在父组件拿到
defineExpose({
msg,
});
</script>

使用 slots 和 attrs

  • 通过 useSlots 和 useAttrs 来定义,达到类似$slots和$attrs 的效果
1
2
3
4
5
6
// 导入 useAttrs 组件
import { useAttrs } from "vue";
// 获取 attrs
const attrs = useAttrs();
// attrs是个对象,和 props 一样,需要通过 key 来得到对应的单个 attr
console.log(attrs.msg);

使用 provide 给子孙组件传值和 inject 接收父组件的值

父组件

1
2
3
4
5
<script setup lang="ts">
import { provide } from "vue";
const curUserId = 168; //可以是简单类型,也可以是复杂类型
provide("curUserId", curUserId);
</script>

孙子组件

1
2
3
4
<script setup lang="ts">
import { inject } from "vue";
const curUserId = inject("curUserId");
</script>

watch 和 watchEffect

  1. watch ()

第一个参数为观察 ref 物件,第二个参数为一个 callBack ,当状态更新,就会针对其来执行 callback。

  1. watchEffect()

不用指定要监听的目标,只要在 callback 函式中对应响应式资料更新后就会依照对应资料来执行了,而与 watch 不同的是,watchEffect () 在初始 setup () 的时候,就会先执行一次了
第一个参数还可以是队列,如果是 reative 中的一个 key-value,用有回传值的 getter 函式,即()=>state.key,深度监听在第三个参数{deep: true}

属性透传

1
2
3
4
5
6
7
8
9
<!-- 父组件 -->
<Child class="child-style" data-id="123" />

<!-- 子组件 -->
<div v-bind="$attrs"></div>
<script setup>
// 禁用自动继承
defineOptions({ inheritAttrs: false });
</script>

v-model 的参数分析

v-model 其实是做双向绑定,实际上是语法糖

vue2.x.x 的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

// 1.单个prop实现数据的双向绑定 basic
<ChildComponent v-model="pageTitle" />
// would be shorthand for:
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

// 2. 不想用value做一个prop name的情况
// 双向绑定的变量名从value灵活成model
// ChildComponent.vue
export default {
model: {
prop: 'title',
event: 'change'
},
props: {
// this allows using the `value` prop for a different purpose
value: String,
// use `title` as the prop which take the place of `value`
title: {
type: String,
default: 'Default title'
}
}
}
// would be shorthand for:
<ChildComponent :title="pageTitle" @change="pageTitle = $event" />

// would be shorthand for:
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

// would be shorthand for:
<ChildComponent :title.sync="pageTitle" />

//3. 多个prop实现数据的双向绑定 Using v-bind.sync
this.$emit('update:title', newValue)

vue3.x.x 的语法

  • 2.x 的 v-model 只能有一个,3.x 灵活为支持多个
1
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
  • 除了像 .trim 这样的 2.x 硬编码的 v-model 修饰符外,现在 3.x 还支持自定义修饰符
1
<ChildComponent v-model.capitalize="pageTitle" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 1 -->

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>

<!-- 2 -->
<!-- props双向绑定的新写法 -->
<ChildComponent v-model:title="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

Vue3 指令新特性

  1. 支持多个 v-model

  2. 自定义修饰符:通过 modelModifiers 访问

  3. 作用域插槽

  4. 动态属性名

1
2
3
<img :src="imageUrl" :alt="altText">
<!-- 动态属性名 -->
<div :[dynamicAttr]="value"></div>

进阶

组合式 API 中组件的 setup() 钩子可以是异步的
如果使用