05-Vue3语法变化
码路教育 7/24/2022
# 一、Vue3语法变化
# 1,在Vue2中,v-for 和 ref 同时使用,这会自动收集 $refs。当存在嵌套的v-for时,这种行为会变得不明确且效率低下。在Vue3中,v-for 和 ref 同时使用,这不再自动收集$refs。我们可以手动封装收集 ref 对象的方法,将其绑定在 ref 属性上。
// Demo.vue
<template>
<h1>ref</h1>
<!-- ref属性和v-for一起使用,需要手动封装方法来收集这些DOM -->
<div v-for="i in 5" :key="i" v-text="i" :ref="collect"></div>
<!-- ref属性使用单一节点上,需要声明一个ref对象来接收DOM -->
<h1 ref="hello">你好</h1>
</template>
<script setup>
import { onMounted, ref } from "vue";
const hello = ref();
const arr = [];
const collect = (ref) => {
if (ref) arr.push(ref);
};
console.log("arr:", arr);
console.log("hello:", hello);
onMounted(() => {
arr[2].style.color = "red";
hello.value.style.color = "blue";
});
</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
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
# 2,在Vue3中,使用 defineAsyncComponent 可以异步地加载组件。需要注意的是,这种异步组件是不能用在Vue-Router的路由懒加载中。
// Demo.vue
<template>
<h1>Demo组件</h1>
</template>
<script setup>
</script>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
// App.vue
<template>
<AsyncChild />
</template>
<script setup>
import { defineAsyncComponent } from "vue";
// 异步加载组件
const AsyncChild = defineAsyncComponent({
loader: () => import("./components/Demo.vue"),
delay: 200,
timeout: 3000,
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3,Vue3.0中的 $attrs,包含了父组件传递过来的所有属性,包括 class 和 style 。在Vue2中,$attrs 是接到不到 class 和 style 的。在 setup 组件中,使用 useAttrs() 访问;在非 setup组件中,使用 this.$attrs /setupCtx.attrs 来访问。
// Demo.vue
<template>
<!-- 注:当父组件有传递class和style时,这里只能使用单一根节点 -->
<div>
<h1>$attrs</h1>
</div>
</template>
<script setup>
import { useAttrs } from "vue";
const attrs = useAttrs(); // 父组件传递过来的自定义属性们
console.log("attrs:", attrs);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// App.vue
<template>
<Demo a="1" :b="2" :c="[110]" class="box" style="color: red" />
</template>
<script setup>
import Demo from "./components/Demo.vue";
</script>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4,Vue3中,移除了 $children 属性,要想访问子组件只能使用 ref 来实现了。在Vue2中,我们使用 $children 可以方便地访问到子组件,在组件树中“肆意”穿梭。
# 5,Vue3中,使用 app.directive() 来定义全局指令,并且定义指令时的钩子函数们也发生了若干变化。
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
let app = createApp(App)
// 全局指令
app.directive('upper', {
// v3中新增的
created() { },
// 相当于v2中的 bind()
beforeMount() {
},
// 相当于v2中的 inserted()
mounted(el, binding, vnode, prevVnode) {
console.log("----");
console.log(binding);
console.log(el);
el.innerHTML = binding.value.toUpperCase();
},
// v3中新增的
// beforeUpdate() { },
// 相当于v2中的 update()+componentUpdated()
// updated() { },
// v3中新增的
// beforeUnmount() { },
// 相当于v2中的 unbind()
// unmounted() { }
})
app.mount('#app')
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
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
// Demo.vue
<template>
<div>
<h1 v-upper="msg"></h1>
</div>
</template>
<script setup>
import { ref } from "vue";
let msg = ref("hello malu");
</script>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 6,data 选项,只支持工厂函数的写法,不再支持对象的写法了。在Vue2中,创建 new Vue({ data }) 时,是可以写成对象语法的。
// Demo.vue
<script>
import { createApp } from 'vue'
createApp({
data() {
return {
msg: 'Hello World'
}
}
}).mount('#app')
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 7,Vue3中新增了 emits 选项。在非script setup写法中,使用 emits选项 接收父组件传递过来的自定义,使用 ctx.emit() 来触发事件。在script setup中,使用 defineEmits 来接收自定义事件,使用 defineProps 来接收自定义属性。
在组件通信的案例中演示过了
# 8,Vue3中 移除了 $on / $off / $once 这三个事件 API,只保留了 $emit 。
- 也就是说在vue3中不能使用事件总线
# 9,Vue3中,移除了全局过滤器(Vue.filter)、移除了局部过滤器 filters选项。取而代之,你可以封装自定义函数或使用 computed 计算属性来处理数据。
- Vue3中没有过滤器了
# 10,Vue3 现在正式支持了多根节点的组件,也就是片段,类似 React 中的 Fragment。使用片段的好处是,当我们要在 template 中添加多个节点时,没必要在外层套一个 div 了,套一层 div 这会导致多了一层 DOM结构。可见,片段 可以减少没有必要的 DOM 嵌套。
// Demo.vue
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 11,函数式组件的变化:在Vue2中,要使用 functional 选项来支持函数式组件的封装。在Vue3中,函数式组件可以直接用普通函数进行创建。如果你在 vite 环境中安装了 @vitejs/plugin-vue-jsx
插件来支持 JSX语法,那么定义函数式组件就更加方便了。
- 后面讲React时,会讲解什么是JSX和函数式组件
# 12,Vue2中的Vue构造函数,在Vue3中已经不能再使用了。所以Vue构造函数上的静态方法、静态属性,比如 Vue.use/Vue.mixin/Vue.prototype 等都不能使用了。在Vue3中新增了一套实例方法来代替,比如 app.use()等。
// Demo.vue
import { createApp } from 'vue'
import router from './router'
import store from './store'
import App from './App.vue'
const app = createApp(App)
// 相当于 v2中的 Vue.prototype
app.config.globalProperties.$http = ''
// 等价于 v2中的 Vue.use
app.use(router) // 注册路由系统
app.use(store) // 注册状态管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 13,在Vue3中,使用 getCurrentInstance 访问内部组件实例,进而可以获取到 app.config 上的全局数据,比如 $route、$router、$store 和自定义数据等。这个 API 只能在 setup 或 生命周期钩子 中调用。
- 之前讲组件式API时演示过了
# 14,我们已经知道,使用 provide 和 inject 这两个组合 API 可以组件树中传递数据。除此之外,我们还可以应用级别的 app.provide() 来注入全局数据。在编写插件时使用 app.provide() 尤其有用,可以替代app.config.globalProperties。
// main.tx
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
let app = createApp(App)
// 注册全局数据
app.provide('$url', 'http://localhost:8888')
app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
// Demo.vue
<template>
<div>
<h1>app.provide()注入数据</h1>
</div>
</template>
<script setup>
import { inject } from "vue";
const $url = inject("$url");
console.log("$url:", $url);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 15,在Vue2中,Vue.nextTick() / this.$nextTick 不能支持 Webpack 的 Tree-Shaking 功能的。在 Vue3 中的 nextTick ,考虑到了对 Tree-Shaking 的支持。
nextTick介绍:
- Vue在观察到数据变化时,并不是直接更新DOM,而是开启一个队列,并且缓存同一轮事件循环中的所有数据改变。在缓冲时会除去重复的操作, 等到下一轮事件循环时,才开始更新。
- $nextTick的作用:就是用来告知DOM什么时候更新完,当DOM更新完毕后,nextTick方法里面的回调就会执行
需求: 有一个div,默认用 v-if 将它隐藏,点击一个按钮后,改变 v-if 的值,让它显示出来,同时拿到这个div的文本内容。如果v-if的值是 false,直接去获取div内容是获取不到的,因为此时div还没有被创建出来,那么应该在点击按钮后,改变v-if的值为 true,div才会被创建,此时再去获取。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取div内容</button>
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
showDiv: false
},
methods: {
// 如果像注释的代码,不加nextTick这个方法,就会报错,因为DOM更新是在下一次事件循环,才更新,所以此时获取不到div元素。
// getText: function () {
// this.showDiv = true;
// var text = document.getElementById('div').innerHTML;
// console.log(text);
// }
getText: function() {
this.showDiv = true;
this.$nextTick(function() {
var text = document.getElementById('div').innerHTML;
console.log(text);
});
}
}
});
</script>
</body>
</html>
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
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
应用场景
- mounted 获取ajax数据,后根据页面渲染数据的样式(文字占宽)来修改布局。
- 几乎所有更新数据后操作dom的操作,都需要用到异步更新队列
Vue3.x中使用
// Demo.vue
<template>
<h1>Vue3中nextTick</h1>
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取div内容</button>
</template>
<script setup>
import { ref, nextTick } from "vue";
let showDiv = ref(false);
let getText = () => {
showDiv.value = true;
nextTick(() => {
console.log(document.getElementById("div").innerHTML);
});
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 16,Vue3中,对于 v-if/v-else/v-else-if的各分支项,无须再手动绑定 key了, Vue3会自动生成唯一的key。因此,在使用过渡动画transition对多个节点进行显示隐藏时,也无须手动加 key了。
// Demo.vue
<template>
<!-- 穿梭框,把内部元素插入to属性所对应的DOM节点中去 -->
<teleport to="head">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/animate.css@4.1.1/animate.min.css"
/>
</teleport>
<div>
<transition
enter-active-class="animate__animated animate__bounce"
leave-active-class="animate__animated animate__backOutDown"
mode="out-in"
>
<!-- <transition name="ml" mode="out-in"> -->
<h1 v-if="bol">白天</h1>
<h1 v-else>黑夜</h1>
</transition>
</div>
<button @click="toggle">切换</button>
<teleport to="body">
<h1>你好</h1>
</teleport>
</template>
<script setup>
import { ref } from "vue";
const bol = ref(true);
const toggle = () => {
bol.value = !bol.value;
};
</script>
<style>
.ml-enter-from {
opacity: 0;
color: red;
}
.ml-enter-active {
transition: all ease 2s;
}
.ml-enter-to {
opacity: 1;
color: black;
}
.ml-leave-from {
opacity: 1;
color: black;
}
.ml-leave-active {
transition: all ease 2s;
}
.ml-leave-to {
opacity: 0;
color: blue;
}
</style>
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
50
51
52
53
54
55
56
57
58
59
60
61
62
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
50
51
52
53
54
55
56
57
58
59
60
61
62
# 17,在Vue2中,使用 Vue.config.keyCodes 可以修改键盘码,这在Vue3 中已经淘汰了。
- 基本上没有项目会去修改键盘码
# 18,Vue3中,$listeners 被移除了。因此我们无法再使用 $listeners 来访问、调用父组件给的自定义事件了。
- Vue2中的$listeners地址:https://www.jb51.net/article/240305.htm
# 19,在Vue2中,根组件挂载 DOM时,可以使用 el 选项、也可以使用 $mount()。但,在 Vue3中只能使用 $mount() 来挂载了。并且,在 Vue 3中,被渲染的应用会作为子元素插入到 div id='app' 中,进而替换掉它的innerHTML。
# 20,在Vue2中,使用 propsData 选项,可以实现在 new Vue() 时向根组件传递 props 数据。在Vue3中,propsData 选项 被淘汰了。替代方案是:使用createApp的第二个参数,在 app实例创建时向根组件传入 props数据。
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 使用第二参数,向App传递自定义属性
const app = createApp(App, { name:'vue3' })
app.mount('#app') // 挂载
1
2
3
4
5
6
2
3
4
5
6
// App.vue
<script setup>
import { defineProps } from 'vue'
// 接收 createApp() 传递过来的自定义属性
const props = defineProps({
name: { type: String, default: '' }
})
console.log('app props', props);
</script>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 21,在Vue2中,组件有一个 render 选项(它本质上是一个渲染函数,这个渲染函数的形参是 h 函数),h 函数相当于 React 中的 createElement()。在Vue3中,render 函数选项发生了变化:它的形参不再是 h 函数了。h 函数变成了一个全局 API,须导入后才能使用。
虚拟DOM的渲染过程
认识h函数 Vue推荐在绝大数情况下使用模板来创建你的HTML,然后一些特殊的场景,你真的需要JavaScript的完全编程的能力,这个时候你可以使用 渲染函数 ,它比模板更接近编译器;
- Vue在生成真实的DOM之前,会将我们的节点转换成VNode,而VNode组合在一起形成一颗树结构,就是虚 拟DOM(VDOM)
- 事实上,我们之前编写的 template 中的HTML 最终也是使用渲染函数生成对应的VNode
- 那么,如果你想充分的利用JavaScript的编程能力,我们可以自己来编写 createVNode 函数,生成对应的VNode
- h() 函数是一个用于创建 vnode 的一个函数
- 其实更准备的命名是 createVNode() 函数,但是为了简便在Vue将之简化为 h() 函数
代码如下:
// Demo.vue
<template>
<renderButton></renderButton>
</template>
<script setup>
// h是用来创建虚拟节点
import {
h
} from "vue";
let renderButton = {
// render叫渲染函数
render() {
return h("h2", {
class: "title"
}, "Hello Vue");
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Demo.vue
<template>
<renderButton></renderButton>
</template>
<script setup>
// h是用来创建虚拟节点
import { h, ref } from "vue";
let counter = ref(0);
let renderButton = {
// render叫渲染函数
render() {
return h("div", { class: "app" }, [
h("h2", null, `计数器:${counter.value}`),
h(
"button",
{
onClick: () => counter.value++,
},
"加1"
),
h(
"button",
{
onClick: () => counter.value--,
},
"减1"
),
]);
},
};
</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
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
# 22,Vue3中新增了实验性的内置组件suspense,它类似 React. Suspense 一样,用于给异步组件加载时,指定 Loading指示器。需要注意的是,这个新特征尚未正式发布,其 API 可能随时会发生变动。
// Demo.vue
<template>
<suspense>
<!-- 用name='default'默认插槽加载异步组件 -->
<AsyncChild />
<!-- 异步加载成功前的loading 交互效果 -->
<template #fallback>
<div> Loading... </div>
</template>
</suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncChild = defineAsyncComponent({
loader: ()=>import('./components/Child.vue'),
delay: 200,
timeout: 3000
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 23,Vue3中,过渡动画transition发生了一系列变化。之前的 v-enter 变成了现在的 v-enter-from , 之前的 v-leave 变成了现在的 v-leave-from 。
// Demo.vue
<template>
<transition name="fade">
<h1 v-if="bol">但使龙城飞将在,不教胡马度阴山!</h1>
</transition>
<button @click="bol = !bol">切换</button>
</template>
<script setup>
import { ref } from "vue";
const bol = ref(true);
</script>
<style scoped>
.fade-enter-from {
opacity: 0;
color: red;
}
.fade-enter-active {
transition: all 1s ease;
}
.fade-enter-to {
opacity: 1;
color: black;
}
.fade-leave-from {
opacity: 1;
color: black;
}
.fade-leave-active {
transition: all 1.5s ease;
}
.fade-leave-to {
opacity: 0;
color: blue;
}
</style>
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
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
# 24,在Vue3中,v-on的.native修饰符已被移除。
# 25,同一节点上使用 v-for 和 v-if ,在Vue2中不推荐这么用,且v-for优先级更高。在Vue3中,这种写法是允许的,但 v-if 的优秀级更高。
# 26,在Vue2中,静态属性和动态属性同时使用时,不确定最终哪个起作用。在Vue3中,这是可以确定的,当动态属性使用 :title 方式绑定时,谁在前面谁起作用;当动态属性使用 v-bind='object'方式绑定时,谁在后面谁起作用。
// Demo.vue
<template>
<!-- 这种写法,同时绑定静态和动态属性时,谁在前面谁生效! -->
<div id='red' :id='("blue")'>不负当下</div>
<div :title='("hello")' title='world'>不畏未来</div>
<hr>
<!-- 这种写法,同时绑定静态和动态属性时,谁在后面谁生效! -->
<div id='red' v-bind='{id:"blue"}'>不负当下</div>
<div v-bind='{title:"hello"}' title='world'>不畏未来</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 27,当使用watch选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组被改变时侦听回调将不再被触发。要想在数组被改变时触发侦听回调,必须指定deep选项。
// Demo.vue
<template>
<div v-for="t in list" v-text="t.task"></div>
<button @click.once="addTask">添加任务</button>
</template>
<script setup>
import { reactive, watch } from "vue";
const list = reactive([
{ id: 1, task: "读书", value: "book" },
{ id: 2, task: "跑步", value: "running" },
]);
const addTask = () => {
list.push({ id: 3, task: "学习", value: "study" });
};
// 当无法监听一个引用类型的变量时
// 添加第三个选项参数 { deep:true }
watch(
() => list[0].task,
() => {
console.log("list changed", list);
},
{ deep: true }
);
setTimeout(() => {
list[0].task += "666";
}, 2000);
</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
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
# 28,在Vue2中接收 props时,如果 prop的默认值是工厂函数,那么在这个工厂函数里是有 this的。在Vue3中,生成 prop 默认值的工厂函数不再能访问this了。
// Demo01.vue
<template>
<h1>Demo01组件</h1>
</template>
<script setup>
import { defineProps, inject } from "vue";
const props = defineProps({
a: { type: Number, default: 0 },
b: { type: Boolean, default: false },
// 如果自定义属性是引用数据类型,default建议使用如下写法(工厂函数)
c: {
type: Array,
default() {
return inject("list", []);
},
},
d: {
type: Object,
default() {
return inject("info", { name: "码路", age: 10 });
},
},
});
console.log("props:", props);
</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
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
// Demo.vue
<template>
<h1>Demo组件</h1>
<Demo01 :a="1" b :c="list" />
</template>
<script setup>
import Demo01 from "./Demo01.vue";
import { reactive, provide } from "vue";
const list = reactive([1, 2, 3]);
const info = reactive({ a: 1, b: 2 });
provide("list", [4, 5, 6]);
provide("info", { c: 3, d: 4 });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 29,Vue3中,新增了teleport组件,这相当于 ReactDOM.createPortal(),它的作用是把指定的元素或组件渲染到任意父级作用域的其它DOM节点上。上面第 16个知识点中,用到了teleport加载 animate.css 样式表,这算是一种应用场景。除此之外,teleport还常用于封装 Modal 弹框组件。
- 自行搜索,使用teleport封装 Modal
# 30,在Vue3中,移除了 model 选项,移除了 v-bind 指令的 .sync 修饰符。在Vue2中,v-model 等价于 :value + @input ;在Vue3中,v-model 等价于 :modelValue + @update:modelValue 。在Vue3中,同一个组件上可以同时使用多个 v-model。在Vue3中,还可以自定义 v-model 的修饰符。
v-model在组件上的简单使用:
// Demo.vue
<template>
<h1>页面</h1>
<hr />
<!-- v-model = :modelValue + @update:modelValue -->
<!-- v-model:gender = :gender + @update:gender -->
<!-- v-model:xxx = :xxx + @update:xxx -->
<!-- <Demo01 :modelValue="lang" @update:modelValue="lang = $event" /> -->
<Demo01 v-model="lang" />
</template>
<script setup>
import Demo01 from "./Demo01.vue";
import { ref } from "vue";
const lang = ref("zh");
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Demo01.vue
<template>
<div>
<div class="lang">
<span
v-for="item in langs"
:key="item.id"
v-text="item.label"
:class="{ on: modelValue === item.value }"
@click="langChange(item)"
>
</span>
</div>
</div>
</template>
<script setup>
import { reactive, defineProps, defineEmits, computed } from "vue";
const langs = reactive([
{ id: 1, value: "zh", label: "中文" },
{ id: 2, value: "en", label: "英文" },
{ id: 3, value: "fr", label: "法语" },
]);
const props = defineProps({
modelValue: { type: String, default: "zh" },
modelModifiers: { default: () => ({}) },
});
const emit = defineEmits(["update:modelValue"]);
const langChange = (item) => {
// 如果sort为真,先处理排序,再回传给父组件
// 如果trim为真,把字符串首尾空字符删除,再回传父组件
emit("update:modelValue", item.value);
};
</script>
<style>
.lang span {
cursor: pointer;
padding: 20px;
}
.lang span.on {
color: red;
}
</style>
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
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
多个v-model的使用,如下:
// Demo.vue
<template>
<h1>v-model在组件上的使用</h1>
<hr />
<!-- v-model = :modelValue + @update:modelValue -->
<!-- v-model:gender = :gender + @update:gender -->
<!-- v-model:xxx = :xxx + @update:xxx -->
<!-- <Demo01 :modelValue="lang" @update:modelValue="lang = $event" /> -->
<Demo01 v-model="lang" v-model:gender="gender" />
</template>
<script setup>
import Demo01 from "./Demo01.vue";
import { ref } from "vue";
const lang = ref("zh");
const gender = ref("unknow");
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Demo01.vue
<template>
<div>
<div class="lang">
<span
v-for="item in langs"
:key="item.id"
v-text="item.label"
:class="{ on: modelValue === item.value }"
@click="langChange(item)"
>
</span>
</div>
<div>
<!-- 对H5的单选按钮组来讲, v-model = :checked + @change -->
<!-- 对H5的文本表单来讲,v-model = :value + @input -->
<!-- 对H5的下拉框来讲,v-model = :value + @change -->
<input
type="radio"
value="man"
:checked="gender === 'man'"
@change="emit('update:gender', $event.target.value)"
/>男 <input type="radio" value="woman" v-model="g" />女
<input type="radio" value="unknow" v-model="g" />保密
</div>
</div>
</template>
<script setup>
import { reactive, defineProps, defineEmits, computed } from "vue";
const langs = reactive([
{ id: 1, value: "zh", label: "中文" },
{ id: 2, value: "en", label: "英文" },
{ id: 3, value: "fr", label: "法语" },
]);
const props = defineProps({
modelValue: { type: String, default: "zh" },
modelModifiers: { default: () => ({}) },
gender: { type: String, default: "man" },
genderModifiers: { default: () => ({}) },
});
const emit = defineEmits(["update:modelValue", "update:gender"]);
const g = computed({
get() {
return props.gender;
},
set(val) {
emit("update:gender", val);
},
});
const langChange = (item) => {
emit("update:modelValue", item.value);
};
</script>
<style>
.lang span {
cursor: pointer;
padding: 20px;
}
.lang span.on {
color: red;
}
</style>
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
自定义修饰符,如下:
// Demo.vue
<template>
<h1>页面</h1>
<hr />
<!-- v-model = :modelValue + @update:modelValue -->
<!-- v-model:gender = :gender + @update:gender -->
<!-- v-model:xxx = :xxx + @update:xxx -->
<!-- <Demo01 :modelValue="lang" @update:modelValue="lang = $event" /> -->
<Demo01 v-model.trim.sort="lang" />
</template>
<script setup>
import Demo01 from "./Demo01.vue";
import { ref } from "vue";
const lang = ref("zh");
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Demo01.vue
<template>
<div>
<div class="lang">
<span
v-for="item in langs"
:key="item.id"
v-text="item.label"
:class="{ on: modelValue === item.value }"
@click="langChange(item)"
>
</span>
</div>
</div>
</template>
<script setup>
import { reactive, defineProps, defineEmits, computed } from "vue";
const langs = reactive([
{ id: 1, value: "zh", label: "中文" },
{ id: 2, value: "en", label: "英文" },
{ id: 3, value: "fr", label: "法语" },
]);
const props = defineProps({
modelValue: { type: String, default: "zh" },
modelModifiers: { default: () => ({}) },
});
const emit = defineEmits(["update:modelValue"]);
const langChange = (item) => {
const { sort, trim } = props.modelModifiers;
console.log(sort, trim);
// 如果sort为真,先处理排序,再回传给父组件
// 如果trim为真,把字符串首尾空字符删除,再回传父组件
emit("update:modelValue", item.value);
};
</script>
<style>
.lang span {
cursor: pointer;
padding: 20px;
}
.lang span.on {
color: red;
}
</style>
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
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
自行研究如下案例:
// GoodFilter.vue
<template>
<span>请选择商家(多选):</span>
<span v-for="s in shopArr">
<input
type="checkbox"
:value="s.value"
:checked="shop.includes(s.value)"
@change="shopChange"
/>
<span v-text="s.label"></span> </span
><br />
<span>请选择价格(单选):</span>
<span v-for="p in priceArr">
<input
type="radio"
:value="p.value"
:checked="p.value === price"
@change="priceChange"
/>
<span v-text="p.label"></span>
</span>
</template>
<script setup>
import { reactive, defineProps, defineEmits, toRefs } from "vue";
const props = defineProps({
shop: { type: Array, default: [] },
// 接收v-model:shop的自定义修饰符
shopModifiers: { default: () => ({}) },
price: { type: Number, default: 500 },
// 接收v-model:price的自定义修饰符
priceModifiers: { default: () => ({}) },
});
const { shop, price } = toRefs(props);
// 接收v-model的自定义事件
const emit = defineEmits(["update:shop", "update:price"]);
const shopArr = reactive([
{ id: 1, label: "华为", value: "huawei" },
{ id: 2, label: "小米", value: "mi" },
{ id: 3, label: "魅族", value: "meizu" },
{ id: 4, label: "三星", value: "samsung" },
]);
const priceArr = reactive([
{ id: 1, label: "1000以下", value: 500 },
{ id: 2, label: "1000~2000", value: 1500 },
{ id: 3, label: "2000~3000", value: 2500 },
{ id: 4, label: "3000以上", value: 3500 },
]);
// 多选框
const shopChange = (ev) => {
const { checked, value } = ev.target;
// 使用v-model:shop的自定义修饰符
const { sort } = props.shopModifiers;
let newShop = checked
? [...shop.value, value]
: shop.value.filter((e) => e !== value);
if (sort) newShop = newShop.sort();
emit("update:shop", newShop);
};
// 单选框
const priceChange = (ev) => {
emit("update:price", Number(ev.target.value));
};
</script>
<style scoped>
</style>
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// App.vue
<template>
<GoodFilter
v-model:shop.sort='shop'
v-model:price='price'
/>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import GoodFilter from './components/GoodFilter.vue'
const shop = ref([])
const price = ref(500)
watch([shop, price], ()=>{
console.log('changed', shop.value, price.value)
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 31,新增了一个指令v-memo
// Demo.vue
<template>
<h1>v-memo指令</h1>
<!-- 只有当foo这个变量变化时,v-memo所包裹的视图结构才更新 -->
<div v-memo="[foo]">
<h1 v-text="num"></h1>
<button @click="num++">自增</button>
</div>
<hr />
<h1 v-text="foo"></h1>
<button @click="foo--">自减</button>
</template>
<script setup>
import { ref } from "vue";
const num = ref(1);
const foo = ref(1000);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 32,样式书写的必变
// Demo.vue
<template >
<h1 class="box">样式的变化</h1>
<button @click="change">改变</button>
<hr />
<h1 :class="zz">测试</h1>
<button @click="changeZZ">改变</button>
</template>
<script setup>
import { ref, useCssModule } from "vue";
const style = useCssModule();
const mlstyle = useCssModule("ml");
const cc = ref("red");
const ff = ref(20);
const zz = ref(mlstyle.r1);
const change = () => {
cc.value = "blue";
};
const changeZZ = () => {
zz.value = mlstyle.r2;
};
</script>
<style>
/* 在style标签中,只能使用v-bind */
.box {
color: v-bind(cc);
}
.r1 {
color: orange;
}
</style>
<style scoped module='ml'>
.r1 {
color: red;
}
.r2 {
color: blue;
}
.r3 {
color: green;
}
</style>
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
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
# 二,总结Vue3.x变化
# 1,移除了哪些?
- $children移除了(注意$parent还在)
- new Vue({ data:{} }) 这个写法淘汰了,如果用到data一定要使用工厂函数。
- 移除了 $on / $off / $once (注意$emit还在)
- 在V3中,事件总线这种通信这种基于订阅发布模式的通信方案也不能用了。
- 移除了全局过滤器(Vue.filter)、移除了局部过滤器 filters选项。
- 在V3中,Vue这个构造函数不能用了,所以像V2中的那个全局API都不能了,所以Vue的原型链也不能用了。
- 在V3中,不再支持修改键盘码了。
- 移除了$listeners (注意$attrs还在)
- 移除了 el选项、model选项、propsData选项
- v-on的.native修饰符已被移除。
# 2,新增了哪些?
- 在V3中,增加了 emits选项、defineEmits,用于在子组件中接收自定义事件。
- 在组件中,使用 getCurrentInstance 可以访问 app根实例。
- 新增了 suspense,用于给异步组件添加交互效果。
- 新增了 teleport的to属性,用于把包裹的视图结构“穿梭”到父级的DOM节点中去。
- 新增了样式的玩法,第一个玩法是在style中可以使用v-bind来做动态样式;在style标签上module='default'实现样式模块化,在组合中使用useCssModule访问样式模块。
- 新增了一个指令 v-memo,用法:在div上v-memo='[a, b]',有且仅有当a或b变化时,其包裹的视图结构才会更新。
- 在V3中,新增了 useSlots,用于在子组件中访问插槽实例。相当于V2中的this.$slots。
# 3,变化了哪些?
- 在V2中,v-for和ref一起作用,可以自动收集refs对象,在this.$refs上访问。在V3中,不能自动收集了,需要自己封装收集方法
- 在V2中,ref属性作用在HTML标签或组件上,可以在this.$refs上访问DOM或组件实例;在V3中,ref属性配合ref这个组合API为完成对DOM或组件实例的方法,div ref='box',这个box是一个ref对象,使用box.value访问对应的DOM。
- 在V3中,使用 defineAsyncComponent 定义更加友好的异步组件。
- 在V2中,使用this.$attrs访问父组件传递过来的属性们(不包括class和style);在V3中,使用useAttrs/context.attrs/$attrs访问父组件传递过来的自定义属性们(包含class和style)。
- 在V3中,使用 app.directive({}/fn) 自定义指令,注意在指令的选项钩了发生了若干变化。
- 在V3中,template视图模块支持多节点。(当在自定义组件上使用class/style时、在自定义组件上使用多个v-model时,都报了多节点的警告,这时建议将其改成单一根节点)
- 在V2中,provide/inject是没有响应式的。在V3中,provide用选项写法时,必须配合computed才能实现响应式;provide用组合写法时,默认就有响应式。
- 在V2中,Vue.nextTick() / this.$nextTick 不能支持 Webpack 的 Tree-Shaking 功能的。在 V3 中的 nextTick ,考虑到了对 Tree-Shaking 的支持。
- V3中,对于 v-if/v-else/v-else-if的各分支项,无须再手动绑定 key了, V3会自动生成唯一的key。比如transition对多节点执行动画时,无须再加key了。
- 在V3中,render选项的函数参数不再是h函数了,如果要使用h函数,得从vue导入。
- transition过渡动画的变化:v-enter-from/v-enter-active/v-enter-to和V2不同了;对多节点执行显示与隐藏时,不需要再加key了。当我们封装自定义组件时,如果transition是这个组件的根节点,要使用props传值来触发动画。
- 同一节点上使用 v-for 和 v-if ,在V2中不推荐这么用,且v-for优先级更高。在V3中,这种写法是允许的,但 v-if 的优秀级更高。
- watch这个组合API:允许同时监听多个变量的变化,允许停止监听。const stop = watch([a, b], callback)
- 在V3中,使用defineProps接收自定义属性时,如果这个属性是对象类型,其default: () => { return },并且在这个default工厂函数中可以使用inject。
- v-model有三个变化,在自定义组件上v-model = :modelValue + @update:modelValue, v-model:name = :name + @update:name;在自定义组件上,可以同时使用多个v-model,在People 组件上使用v-model='age' v-model:name='name';还支持自定义修饰符,注意在子组件中必须使用defineProps来分别接收自定义修饰符。