Vue3入门

Vue3入门

1 为什么要用 Vue3

因为它体积更小、速度更快、提供 Composition API,增加了代码的逻辑组织和代码复用能力,提供了更好的类型检查。

Vue3优化

2 Composition API

注:在 Vue3 中其实是可以使用 Vue2 的写法,根据我们的规范,不允许出现 Vue2 的写法。

2.1 Composition API 是什么?

官网 https://v3.cn.vuejs.org/guide/composition-api-introduction.html#什么是组合式-api

看下边图,把每种色块当作一个功能,应该可以很直观地感受到 Composition API 在逻辑代码组织方面的优势
Vue2和Vue3代码结构对比
在 Options API 中,每个选项都是分离的,处理单个逻辑关注点时,必须不断地“跳转”相关代码的选项块,这种碎片化的方式会使得理解和维护复杂组件变得困难。

在 Composition API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合),当需要修改一个功能时,就不再需要在文件中跳来跳去。
即使项目很大,功能很多,都可以更快速的定位到这个功能所用到的代码。

下面举个简单例子,将处理 count 属性相关的代码放在同一个函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function useCount() {
const count = ref(10);
const double = computed(() => count.value * 2);

const handleConut = () => {
count.value *= 2;
};

console.log(count);

return {
count,
double,
handleConut,
};
}

使用 count

1
2
3
4
5
6
7
8
9
10
export default defineComponent({
setup() {
const { count, double, handleConut } = useCount();
return {
count,
double,
handleConut,
};
},
});

2.2 setup

setup() 函数是 vue3 中,专门为 vue 组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口。

那么下面我们将分别从函数的几个方面来解析 setup 函数:

  • return 返回值
  • 函数的参数
  • 何时调用
  • this 指向

2.2.1 返回值

在 setup 函数中返回一个对象,这样我们就可以在 template 模板中直接访问该对象中的属性和方法。如果只是在 setup 函数中使用某个方法,则不需要返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
setup() {
// 变量
const content = '这是 setup 中返回的数据';
// 方法
const testFun = () => {
console.log('调用 testFun 方法')
};
return {
content,
testFun,
}
}
...

2.2.2 函数的参数

使用 setup 函数时,它将接受两个参数:

  1. props
  2. context

2.2.2.1、props

setup 函数中的第一个参数是 props,它是响应式的,当传入新的 props 时,它会被更新。
使用场景:父子组件传参。

1
2
3
4
5
6
7
8
9
10
11
12
...
setup() {
props: {
title: {
type: String,
},
},
setup(props) {
console.log(props.title);
}
}
...

开发规范:https://www.yuque.com/heq286/dswu9s/sye0sb#sGYuw


因为 props 是响应式的,所以不能使用 ES6 解构,因为它会消除 props 的响应性。

如果需要解构 props,可以通过使用 setup 函数中的 [toRefs](https://vue3js.cn/docs/zh/api/refs-api.html#toref) 来安全地完成此操作。

1
2
3
4
5
6
7
8
9
...
import { toRefs } from 'vue';

setup(props) {
const { title } = toRefs(props.data);

console.log(title.value);
}
...

1.2.2.2、context

setup 函数的第二个参数是 contextcontext 是一个普通的 JavaScript 对象,它暴露组件的三个 property:

1
2
3
4
5
6
7
8
9
10
11
12
...
setup(props, context) {
// Attribute (非响应式对象)
console.log(context.attrs)

// 插槽 (非响应式对象)
console.log(context.slots)

// 触发事件 (方法)
console.log(context.emit)
}
...

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。

1
2
3
4
5
...
setup(props, { attrs, slots, emit }) {
...
}
...

attrsslots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.xslots.x 的方式引用 属性。请注意,与 props 不同,attrsslots响应式的。如果你打算根据 attrsslots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<el-button type="primary" @click="getAttrs">点击获取Attrs</el-button
</template>
...
setup(props, { attrs, slots, emit }) {
console.log(attrs.title);

const getAttrs = () => {
console.log(attrs.title);
};

return {
getAttrs,
}
}
...

1.2.3 何时调用

setup 函数会在 beforeCreate 和 created 之前执行。

setup 函数在创建组件之前被调用,所以在 setup 被执行时,组件实例并没有被创建。

1.2.4 this 指向

由于在执行 setup 时,尚未创建组件实例,因此在 setup 选项中没有 this

**setup()**内部,**this**不会是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆。

1
2
3
4
5
6
7
8
9
10
...
setup() {
// 先于 created 执行,此时组件尚未创建
console.log(this); // undefined
},

created() {
console.log(this); // proxy 对象 -> 组件实例
}
...

1.2.4.1 问题:如何在 Vue3 中定义、获取全局变量?

使用场景:需要设置名称为 name 的全局变量,并在所有组件中都能访问到此变量。

1.2 生命周期

官网:https://www.vue3js.cn/docs/zh/guide/composition-api-lifecycle-hooks.html

开发规范:https://www.yuque.com/heq286/dswu9s/sye0sb#HXy1X

通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子

说明 Vue3 Vue2
创建组件前 setup beforeCreate
创建组件后 setup created
组件挂载前 onBeforeMount beforeMount
组件挂载后 onMounted mounted
组件更新前 onBeforeUpdate beforeUpdate
组件更新后 onUpdated updated
组件销毁前 onBeforeUnmount beforeDestroy
组件销毁后 onUnmounted destroyed
  • 按需加载,更能减少代码体积
  • vue3 的生命周期会优先于 vue2 的执行。

完整示例:

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
<script lang="ts">
import { //现在使用的方法都要按需引入
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'

export default {
name: "生命周期",
setup() {
console.log('1---setup,组件创建之前')
onBeforeMount(()=>{
console.log('2---onBeforeMount,组件挂载到dom前')
})
onMounted(()=>{
console.log('3---onMounted,组件挂载到dom后')
})
//组件更新前后--比如数据更新后,会触发组件更新
onBeforeUpdate(()=>{
console.log('4---onBeforeUpdate,组件更新前')
})
onUpdated(()=>{
console.log('5---onUpdated,组件更新后')
})
//组件销毁前后 节点删除或替换会触发
onBeforeUnmount(()=>{
console.log('6---onBeforeUnmount,卸载组件前')
})
onUnmounted(()=>{
console.log('7---onBeforeUnmount,卸载组件后')
})
}
}
</script>

1.3 提供/注入(provide & inject)

provide()inject() 可以实现嵌套组件之间的数据传递。
父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。

通常,当我们需要从父组件向子组件传递数据时,我们会使用 props
想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 props 沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provideinject 。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。

提供/注入

官 ​ 网:https://v3.cn.vuejs.org/guide/component-provide-inject.html

1.3.1 使用 Provide

provide 函数允许通过两个参数定义 property:

  1. property 的 name (<String> 类型)
  2. property 的 value (<String、Array、Object、Fun> 类型)

  • 按需引入
1
import { provide } from "vue";

示例:

父组件

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
<template>
<div>
<h3>我是父组件</h3>
<inject-child1 />
</div>
</template>

<script>
import { defineComponent, provide } from "vue";
import InjectChild1 from "@/components/inject-child1.vue";

export default defineComponent({
components: {
InjectChild1,
},
setup() {
// 定义数据
const themeColor = "red";

// 通过 provide 函数向子级组件共享数据(不限层级)
// provide('要共享的数据名称', 被共享的数据)
provide("globalColor", themeColor);

return {};
},
});
</script>

1.3.2 使用注入

inject 函数有两个参数:

  1. 要注入的 property 的名称
  2. 一个默认的值,当属性不存在时,读取默认的值 (可选)

  • 按需引入
1
import { inject } from "vue";

示例:

子组件

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
<template>
<div>
<h3>我是子组件</h3>
<p :style="{ color: themeColor }">文字颜色为:{{ themeColor }}</p>
<inject-child2 />
</div>
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";
import InjectChild2 from "@/components/inject-child2.vue";

export default defineComponent({
components: {
InjectChild2,
},
setup() {
// 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const themeColor = inject("globalColor");

// 把响应式数据 return 给 Template 使用
return {
themeColor,
};
},
});
</script>

子 ​ 子子组件

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
<template>
<h3>我是子子子组件</h3>
<p :style="{ color: color }">文字颜色为:{{ color }}</p>
<el-button @click="setGlobalColor">通过父组件修改</el-button>
<el-button @click="forbidColor">直接修改</el-button>
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";

export default defineComponent({
setup() {
// 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const color = inject("globalColor");

// 调用父组件提供的方法
const setGlobalColor = inject("setGlobalColor");

/**
* 修改组件中文字的颜色
*/
const forbidColor = () => {
// color.value = 'blue';
};

// 把响应式数据 return 给 Template 使用
return {
color,
setGlobalColor,
forbidColor,
};
},
});
</script>

1.3.3 响应式

为了增加提供值和注入值之间的响应性,我们可以在提供值时使用 refreactive

示例:

父组件

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
<template>
<div>
<h3>我是父组件</h3>
<el-button @click="clickTransmitData">点击给子组件传值</el-button>
<inject-child1 />
</div>
</template>

<script lang="ts">
import { defineComponent, provide, ref } from "vue";
import InjectChild1 from "@/components/inject-child1.vue";

export default defineComponent({
components: {
InjectChild1,
},
setup() {
// 定义 ref 响应式数据
const themeColor = ref("red");

// 通过 provide 函数向子级组件共享数据(不限层级)
// provide('要共享的数据名称', 被共享的数据)
provide("globalColor", themeColor);

/**
* 点击传递数据
*/
const clickTransmitData = () => {
themeColor.value = "blue";
};

return {
clickTransmitData,
};
},
});
</script>

1.3.4 修改响应式 property

当使用响应式提供/注入值时,建议尽可能,在提供者内保持响应式 property 的任何更改。然而,有时我们需要在注入数据的组件内部更新注入的数据。在这种情况下,我们建议提供一个方法来负责改变响应式 property。

父组件:

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
<template>
<div>
<h3>我是父组件</h3>
<el-button type="primary" @click="clickTransmitData"
>点击给子组件传值</el-button
>
<inject-child1 />
</div>
</template>

<script lang="ts">
import { defineComponent, provide, ref } from "vue";
import InjectChild1 from "@/components/inject-child1.vue";

export default defineComponent({
components: {
InjectChild1,
},
setup() {
// 定义 ref 响应式数据
const themeColor = ref("red");

// 通过 provide 函数向子级组件共享数据(不限层级)
// provide('要共享的数据名称', 被共享的数据)
provide("globalColor", themeColor);

/**
* 点击传递数据
*/
const clickTransmitData = () => {
themeColor.value = "blue";
};

// 提供修改全局的方法
provide("setGlobalColor", clickTransmitData);

return {
clickTransmitData,
};
},
});
</script>

子组件 ​:

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
<template>
<div>
<h3>我是子组件</h3>
<p :style="{ color: themeColor }">文字颜色为:{{ themeColor }}</p>
<el-button @click="setGlobalColor">通过父组件修改</el-button>
<el-button @click="forbidColor">直接修改</el-button>
<inject-child2 />
</div>
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";
import InjectChild2 from "@/components/inject-child2.vue";

export default defineComponent({
components: {
InjectChild2,
},
setup() {
// 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const themeColor = inject("globalColor");

// 调用父组件提供的方法
const setGlobalColor = inject("setGlobalColor");

// 把响应式数据 return 给 Template 使用
return {
themeColor,
setGlobalColor,
};
},
});
</script>

1.3.5 禁止数据的更改

如果要确保通过 provide 传递的数据不会被注入的组件更改,我们建议对提供者的 property 使用 readonly

父组件:

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
<template>
<div>
<h3>我是父组件</h3>
<el-button type="primary" @click="clickTransmitData"
>点击给子组件传值</el-button
>
<inject-child1 />
</div>
</template>

<script lang="ts">
import { defineComponent, provide, ref, readonly } from "vue";
import InjectChild1 from "@/components/inject-child1.vue";

export default defineComponent({
components: {
InjectChild1,
},
setup() {
// 定义 ref 响应式数据
const themeColor = ref("red");

// 通过 provide 函数向子级组件共享数据(不限层级)
// provide('要共享的数据名称', 被共享的数据)
provide("globalColor", readonly(themeColor));

/**
* 点击传递数据
*/
const clickTransmitData = () => {
themeColor.value = "blue";
};

// 提供修改全局的方法
provide("setGlobalColor", clickTransmitData);

return {
clickTransmitData,
};
},
});
</script>

1.3.6 完整示例

父组件 ​

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
48
49
<template>
<div>
<p class="title">测试提供/注入</p>
<h3>我是父组件</h3>
<el-button type="primary" @click="clickTransmitData"
>点击给子组件传值</el-button
>
<inject-child1 />
</div>
</template>

<script lang="ts">
import { defineComponent, provide, ref, readonly } from "vue";
import InjectChild1 from "@/components/inject-child1.vue";

export default defineComponent({
components: {
InjectChild1,
},
setup() {
// 定义 ref 响应式数据
const themeColor = ref("red");

// 定义数据
// const themeColor = 'red';

// 通过 provide 函数向子级组件共享数据(不限层级)
// provide('要共享的数据名称', 被共享的数据)
provide("globalColor", themeColor);
// 只读数据
// provide('globalColor', readonly(themeColor));

/**
* 点击传递数据
*/
const clickTransmitData = () => {
themeColor.value = "blue";
};

// 提供修改全局的方法
provide("setGlobalColor", clickTransmitData);

return {
themeColor,
clickTransmitData,
};
},
});
</script>

子组件 ​:

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
<template>
<div>
<h3>我是子组件</h3>
<p :style="{ color: color }">文字颜色为:{{ color }}</p>
<el-button @click="setGlobalColor">通过父组件修改</el-button>
<el-button @click="forbidColor">直接修改</el-button>
<inject-child2 />
</div>
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";
import InjectChild2 from "@/components/inject-child2.vue";

export default defineComponent({
components: {
InjectChild2,
},
setup() {
// 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const color: any = inject("globalColor");

// 调用父组件提供的方法
const setGlobalColor = inject("setGlobalColor");

/**
* 修改组件中文字的颜色
*/
const forbidColor = () => {
// color.value = 'blue';
};

// 把响应式数据 return 给 Template 使用
return {
color,
setGlobalColor,
forbidColor,
};
},
});
</script>

子子组件 ​:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h3>我是子子组件</h3>
<p :style="{ color: color }">文字颜色为:{{ color }}</p>
</div>
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";

export default defineComponent({
setup() {
// 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const color = inject("globalColor");

// 把响应式数据 return 给 Template 使用
return {
color,
};
},
});
</script>

1.4 模板使用(获取模板内的元素

使用场景:比如 echarts 获取 dom 元素、element-plus 表单验证

为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回:

1.4.1 元素的引用

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
<template>
<div>
<h3 ref="h3Ref">TemplateRefOne</h3>
</div>
</template>

<script>
import { defineComponent, ref, onMounted } from "vue";

export default defineComponent({
setup() {
// 创建一个 DOM 引用
const h3Ref = ref(null);

// 在 DOM 首次加载完毕之后,才能获取到元素的引用
onMounted(() => {
// 为 dom 元素设置字体颜色
// h3Ref.value 是原生DOM对象
h3Ref.value.style.color = "red";
});

// 把创建的引用 return 出去
return {
h3Ref,
};
},
});
</script>

1.4.2 组件的引用

父组件中的示例代码如下:

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
<template>
<div>
<test-c-child ref="comRef"></test-c-child>
<el-button type="primary" @click="showNumber"
>点击获取子元素里的变量值</el-button
>
</div>
</template>

<script>
import { defineComponent, ref, onMounted } from "vue";
import testCChild from "@/components/test-c-child.vue";

export default defineComponent({
components: {
testCChild,
},
setup() {
// 创建一个组件的 ref 引用
const comRef = ref(null);

// 展示子组件中 count 的值
const showNumber = () => {
console.log(comRef.value.count);
};

// 把创建的引用 return 出去
return {
comRef,
showNumber,
};
},
});
</script>

子组件中的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h5>我是子组件里的值哦 --- {{ count }}</h5>
<!-- 点击按钮,让 count 值自增 +1 -->
<el-button type="primary" @click="count += 1">点击+1</el-button>
</div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
name: "TestCChild",
setup() {
// 定义响应式的数据
const count = ref(0);

// 把响应式数据 return 给 Template 使用
return {
count,
};
},
});
</script>

3 响应式

3.1 响应式基础

使用场景推荐:

  • reactive 推荐定义复杂的数据类型
  • ref 推荐定义基本数据类型,ref 底层的本质还是 reactive,因为系统会根据我们给 ref 传入的值将它转换成对象 ref(xx)–>reactive({value: xx})

官网:https://vue3js.cn/vue-composition/#ref-vs-reactive > https://zhuanlan.zhihu.com/p/268053724

3.1.1 reactive

reactive() 函数接收一个普通对象,返回一个响应式的数据对象。

  • 按需导入 reactive 函数:
1
import { reactive } from 'vue';
  • setup() 函数中调用 reactive() 函数,创建响应式数据对象:

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<p>reactive</p>
<h3>当前的 reactiveCount 值为:{{ reactiveCount }}</h3>
<el-button @click="reactiveCount++">点击reactiveCount+1</el-button>
</div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs, ref } from "vue";

export default defineComponent({
name: "TestE",
setup() {
// reactive
// 创建响应式数据对象
const state = reactive({ reactiveCount: 0 });

return {
...toRefs(state),
};
},
});
</script>

3.1.2 ref

3.1.2.1 基本用法

ref() 函数用来根据给定的值创建一个响应式数据对象ref() 函数调用的返回值是一个对象,这个对象上只包含一个 .value 属性:

数据类型:不限,字符串、数值、布尔值、对象

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref } from "vue";

// 创建响应式数据对象 count,初始值为 0
const refCount = ref(0);

// 如果要访问 ref() 创建出来的响应式数据对象的值,必须通过 .value 属性才可以
console.log(refCount.value); // 输出 0

// 让 count 的值 +1
refCount.value += 1;

// 再次打印 count 的值
console.log(refCount.value); // 输出 1

3.1.2.2 在 template 中访问 ref 创建的响应式数据

当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加 .value

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
<template>
<div>
<p>ref</p>
<h3>当前的 refCount 值为:{{ refCount }}</h3>
<el-button @click="refCount++">点击refCount+1</el-button>
</div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs, ref } from "vue";

export default defineComponent({
name: "TestE",
setup() {
// ref
// 创建响应式数据对象 count,初始值为 0
const refCount = ref(0);
// 如果要访问 ref() 创建出来的响应式数据对象的值,必须通过 .value 属性才可以
console.log(refCount.value); // 输出 0
// 让 count 的值 +1
refCount.value += 1;
// 再次打印 count 的值
console.log(refCount.value); // 输出 1

return {
refCount,
};
},
});
</script>

3.1.3 响应式状态解构

当我们想使用大型响应式对象的一些 property 时,可能很想使用 ES6 解构来获取我们想要的 property,比如下边的代码:

1
2
3
4
5
6
7
8
9
10
11
import { reactive } from "vue";

const book = reactive({
author: "Vue Team",
year: "2020",
title: "Vue 3 Guide",
description: "You are reading this book right now ;)",
price: "free",
});

const { year, title } = book;

遗憾的是,使用解构的两个 property 的响应性都会丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { reactive } from "vue";

const book = reactive({
author: "Vue Team",
year: "2020",
title: "Vue 3 Guide",
description: "You are reading this book right now ;)",
price: "free",
});

const { title, year } = toRefs(book); // 转换多个值时

const titleRef = toRef(book, "title"); // 转单个值时

title.value = "我是新的值"; // 我们需要使用 .value 作为标题,现在是 ref

console.log(book.title); // '我是新的值''
console.log(title.value); // '我是新的值'

官网:https://v3.cn.vuejs.org/guide/reactivity-fundamentals.html#响应式状态解构
ref:https://www.vue3js.cn/docs/zh/api/refs-api.html#ref

3.1.4 使用 readonly 防止更改响应式对象

有时我们想跟踪响应式对象 (refreactive) 的变化,但我们也希望防止在应用程序的某个位置更改它。例如,当我们有一个被 provide 的响应式对象时,我们不想让它在注入的时候被改变。为此,我们可以基于原始对象创建一个只读的 Proxy 对象:

1
2
3
4
5
6
7
8
9
10
const original = reactive({ count: 0 });

const copy = readonly(original);

// 在copy上转换original 会触发侦听器依赖

original.count += 1;

// 转换copy 将导失败并导致警告
copy.count += 1; // 警告: "Set operation on key 'count' failed: target is readonly."

3.2 响应式计算

使用场景:当依赖某个值的数据发生变化时,就会用到它。例如总价 = 数量 * 价格,当数量和价格发生变化时都会影响总价,这时计算属性就派上用场了。

computed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入:

1
import { computed } from "vue";

3.2.1 创建只读计算属性

在调用 computed() 函数期间,传入一个 function 函数,可以得到一个只读的计算属性,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建一个 ref 响应式数据
const count = ref(1);

// 根据 count 的值,创建一个响应式的计算属性 plusOne
// 它会根据依赖的 ref 自动计算并返回一个新的 ref
const plusOne = computed(() => count.value + 1);

// 等同于它
const plusOne = computed(() => {
return count.value + 1;
});

plusOne.value += 1; // Write operation failed: computed value is readonly

3.2.2 创建可读可写的计算属性

在调用 computed() 函数期间,传入一个包含 getset 函数的对象,可以得到一个可读可写的计算属性,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个 ref 响应式数据
const count = ref(1);

// 创建一个 computed 计算属性
const plusOne = computed({
// 取值函数
get: () => count.value + 1,
// 赋值函数
set: (val) => {
count.value = val - 1;
},
});

3.3 响应式监听

watch() 函数用来监视某些数据项的变化,从而触发某些特定的操作,使用之前需要按需导入:

1
import { watch } from "vue";

3.3.1 基本用法

1
2
3
4
5
6
7
8
9
10
const count = ref(0);

// 定义 watch,只要 count 值变化,就会触发 watch 回调
watch(
() => count.value,
(newVal, oldVal) => {
console.log(`新值${newVal}`);
console.log(`旧值${oldVal}`);
}
);

3.3.2 监视指定的数据源

监视 reactive 类型的数据源:

1
2
3
4
5
6
7
8
9
// 定义数据源
const state = reactive({ count: 0 });
// 监视 state.count 这个数据节点的变化
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
);

监视 ref 类型的数据源:

1
2
3
4
5
6
7
8
9
10
const count = ref(0);

// 定义 watch,只要 count 值变化,就会触发 watch 回调
watch(
() => count.value,
(newVal, oldVal) => {
console.log(`新值${newVal}`);
console.log(`旧值${oldVal}`);
}
);

3.3.3 监视多个数据源

1
2
3
4
5
6
7
8
9
watch(
[() => count.value, () => num.value],
([newCount, newNum], [prevCount, prevNum]) => {
console.log(`count新值${newCount}`);
console.log(`count旧值${prevCount}`);
console.log(`num新值${newNum}`);
console.log(`num旧值${prevNum}`);
}
);

官网:https://v3.cn.vuejs.org/guide/reactivity-computed-watchers.html#侦听响应式对象

3.3.4 监听对象

监听对象,需要使用到 watch 的深度监听,语法如下:

1
2
3
4
5
6
7
8
9
10
11
watch(
() => props.data,
(newVal, oldVal) => {
console.log(newVal);
console.log(oldVal);
},
{
deep: true, // 深度监听
immediate: true, // 默认执行
}
);

监听对象时,会遇到没有旧值的问题,可参考下方链接

官网:https://v3.cn.vuejs.org/guide/reactivity-computed-watchers.html#侦听响应式对象

网上:https://blog.csdn.net/qq_41777791/article/details/108213187

3.3.4、清除监视

setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:

1
2
3
4
5
6
7
// 创建监视,并得到 停止函数
const stop = watch(() => {
/* ... */
});

// 调用停止函数,清除对应的监视
stop();

4 如何使用 vue-router

1
2
3
4
5
6
7
8
9
10
11
import { useRoute, useRouter } from 'vue-router';
...
setup () {
const route = useRoute();
const router = useRouter();

// router.push({
// name: 'home'
// });
},
...

引入的 useRoute,useRouter 相当于 vue2 的 this.$route(),this.$router()

5 如何使用状态管理

1
2
3
4
5
6
7
8
9
10
11
12
import { useStore } from 'vuex';
...
setup () {
const store = useStore();

// 获取变量
// store.state.XXXX

// 更新值
// store.dispatch('方法名', 新值);
},
...

6 Vue2 和 Vue3 对比

6.1 代码结构

Vue2

把变量、声明周期、methods 等分割到独立的属性区域的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default {
props: {
title: String
},
data () {
return {
username: '',
password: '',
a: '',
}
},
methods: {
login () {
// 登陆方法
}
aa () {
}
}
}

Vue3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default {
props: {
title: String,
},
setup() {
const state = reactive({
username: "",
password: "",
});

const login = () => {
// 登陆方法
};
return {
login,
state,
};
},
};

6.2 生命周期

Vue2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default {
props: {
title: String,
},
data() {
return {
username: "",
password: "",
};
},
mounted() {
console.log("组件已挂载");
},
methods: {
login() {
// login method
},
},
};

Vue3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { reactive, onMounted } from "vue";

export default {
props: {
title: String,
},
setup() {
// ..

onMounted(() => {
console.log("组件已挂载");
});

// ...
},
};

6.3 计算属性

效果:添加一个计算属性来转换username成小写字母

Vue2

1
2
3
4
5
6
7
8
export default {
// ..
computed: {
lowerCaseUsername() {
return this.username.toLowerCase();
},
},
};

Vue3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { reactive, onMounted, computed } from 'vue'

export default {
props: {
title: String
},
setup () {
const state = reactive({
username: '',
password: '',
lowerCaseUsername: computed(() => state.username.toLowerCase())
})

// ...
}

6.4 接收 Props

Vue2

在 Vue2,this代表的是当前组件,不是某一个特定的属性。所以我们可以直接使用this访问 prop 属性值。

1
2
3
4
5
6
7
8
export default {
props: {
title: String,
},
mounted() {
console.log("title: " + this.title);
},
};

Vue3

在 Vue3 中,this无法直接拿到 props 属性,emit events(触发事件)和组件内的其他属性。不过全新的setup()方法可以接收两个参数:

  1. props - 传过来的参数
  2. context - Vue3 暴露出来的属性(emit,slots,attrs)
1
2
3
4
5
6
7
8
9
setup (props) {
// ...

onMounted(() => {
console.log('title: ' + props.title)
})

// ...
}

6.5 事件

效果:点击提交按钮时触发一个login的事件

Vue2

在 Vue2 中我们会调用到this.$emit然后传入事件名和参数对象。

1
2
3
4
5
6
7
8
9
export default {
// .....
login() {
this.$emit("login", {
username: this.username,
password: this.password,
});
},
};

Vue3

刚刚说过this已经不是和 vue2 一样代表着这个组件了。

setup()中的第二个参数content对象中就有emit,这个是和this.$emit是一样的。那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在 setup 方法中使用了。

1
2
3
4
5
6
7
8
9
10
11
12
setup (props, { emit }) {
// ...

const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}

// ...
}

7 其他

7.1 全局变量

7.1.1 设置全局变量

1
2
const app = createApp(App);
app.config.globalProperties.name = "liuyan";

官网:https://vue3js.cn/docs/zh/api/application-config.html#globalproperties

7.1.2 获取全局变量

1
2
3
4
5
6
7
8
9
10
11
...
import { getCurrentInstance } from 'vue'

setup() {
const internalInstance = getCurrentInstance();

const name = internalInstance.appContext.config.globalProperties.name;

console.log(name); // liuyan
}
...

官网:https://vue3js.cn/docs/zh/api/composition-api.html#getcurrentinstance

7.2 兄弟组件传参

实现方式:Vuex、通过父组件、第三方 pubsub-js
npm:https://www.npmjs.com/package/pubsub-js

7.2.1 发布消息

1
2
3
import PubSub from "pubsub-js"; // 引入

PubSub.publish("changeVal", 1); // 发布消息,第一个参数事件名、第二个数据

7.2.2 订阅和取消订阅消息

1
2
3
4
5
6
7
8
9
10
import PubSub from "pubsub-js"; // 引入

// 订阅消息
const subName = PubSub.subscribe("changeVal", (name: number, val: number) => {
console.log(name);
console.log(val);
});

// 取消订阅
PubSub.unsubscribe(subName);
作者

刘燕

发布于

2021-07-02

更新于

2021-08-04

许可协议

CC BY-NC-SA 4.0

评论