一.Vue2->Vue3

如果有Vue2的基础,并在此基础上学习Vue3,并不需要把完整的官网看完,我们只需要关注一下新功能和非兼容的变化即可进行开发.

二.Vue3变化

  • 统一元素上使用的v-if和v-for优先级已更改,但不推荐同时使用v-if和v-for
  • 组件事件需要在emits选项中声明
  • destroyed生命周期选项被重命名为unmounted
  • beforeDestroy生命周期选项被重命名为beforeUnmount
  • 自定义指令的API已更改为组件生命周期一致
  • 新增了三个组件:Fragment支持多个根节点  Suspense可以在组件渲染之前的等待时间显示指定内容  Teleport可以让子组件能够在视觉上跳出父组件(如父组件overflow:hidden)
  • 新增指令v-memo,可以缓存html模板,比如v-for列表不会变化的就缓存,简单说就是用内存换时间.
  • 用Proxy代替Object.defineProperty重构了响应式系统,可以监听到数组下标变化,及对象新增属性,因为监听的不是对象属性,而是对象本身,还可拦截apply has等13种方法
  • 重构了虚拟DOM, 在编译时会将事件缓存,将slot编译为lazy函数,保存静态节点直接复用(静态提升),以及添加静态标记,Diff算法使用最长递增子序列优化了对比流程,使得虚拟DOM生成速度提升200%
  • 支持在<style></style>里使用v-bind,给CSS绑定JS变量(color:v-bind(str))
  • 新增Composition API可以更好的逻辑复用和代码组织,同一功能的代码不至于像以前一样分散,虽然Vue2可以用mixins来实现复用代码,但也存在问题,比如方法或属性名会冲突,代码来源也不清楚等
  • 全局函数set和delete以及实例方法$set和$delete移除.基于代理的变化检测以及不再需要它们了
  • 毕竟Vue3是用TS写的,所以对TS的支持度更好
  • Vue3不兼容IE11
  • $on,$off和$once实例方法已被移除,组件实例不再实现事件触发接口

三.组合式API

原有的组件选项(datacomputedmethodswatch) 的方式来组织组件代码通常是非常有效的,但是也存在一些不好的地方,例如把原有的关联逻辑按照选项划分开来,掩盖了原有潜在的逻辑问题,这个时候我们就必须要不断地上下滚动代码来找到响应的代码块来查找,这样带来了极大的不便,特别是一开始没有编写过这组件的人来说,这导致组件难以阅读和理解。

所以针对上述的情况,Vue3提出了新的组织组件代码的方式---组合式API。组合式API需要一个可以实际使用的地方,那就是setup

setup的触发时机是在组件创建之前执行的。需要注意,在setup中你应该避免使用this,因为这个时候this并不代表组件实例。setup的调用发生在datacomputedmethods被解析之前,所以它们没法在setup中被获取。

当然,我们依然可以在Vue3使用选项(Option API)的方式来组织代码,这个和Vue2没有区别,但是不建议这样写。

Vue3.x组件的选项(Option API)写法(不建议)

<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {},
mounted() {
console.log('生命周期mounted')
},
components: {},
methods: {},
watch: {},
comuted: {}
});
</script>

Vue3.x组合式写法(推荐)

<script>
import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({
setup(){
let num = ref(0);
let fn = () => {};
onMounted(():void => {
console.log('生命周期mounted');
});
return {
num,
fn
}
}
})
</script>

下面例子代码都是在Typescript的环境下进行的,所以需要Typescript基础

3.1.生命周期

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

下表包含如何在 Option API 和 setup() 内部调用生命周期钩子

Option API setup
beforeCreate -
created -
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
activated onActivated
deactivated onDeactivated

因为 setup 是在 beforeCreatecreated 生命周期钩子前运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

<template>
<div id="div">123</div>
</template> <script lang="ts">
import { defineComponent, onMounted } from "vue";
export default defineComponent({
setup() {
console.log("setup");
onMounted((): void => {
console.log("onMounted");
console.log(document.getElementById('div')?.innerHTML);
});
},
});
</script>

3.2.ref,reactive,toRefs响应式和methods

Vue2.x默认写在data的值,初始化的时候内部会完成值的数据的响应式(getset绑定),但是Vue3要手动调用内置方法实现,那么接下来看看常用的实现数据响应式方法都有哪些。

ref不仅可以用在数据的响应式,还可以绑定DOM元素

<template>
<div>
<div>{{ num }}</div>
<button @click="add1">num++</button>
<p>-------------------------------</p>
<div>{{ state.count }}</div>
<button @click="add2">state.count++</button>
<p>-------------------------------</p>
<div>{{ count }}</div>
<button @click="add3">count++</button>
</div>
</template> <script lang="ts">
import { defineComponent, ref, reactive, toRefs } from "vue";
export default defineComponent({
setup() {
interface ObjItf {
count: number;
} // ref声明响应式数据,用于声明基本数据类型
let num = ref<number>(1); let obj = {
count: 1,
}; // reactive声明响应式数据,用于声明引用数据类型
let state = reactive<ObjItf>(obj); // toRefs解构响应式数据
let { count } = toRefs<ObjItf>(state); const add1 = (): void => {
num.value++; // 注意通过ref声明的变量,所以js要修改对应的值是要通过.value访问才可以,template模板不需要通过.value访问
}; const add2 = (): void => {
state.count++; // 通过reactive声明的遍历,不需要通过.value访问值
}; const add3 = (): void => {
count.value++; // 通过toRefs结构的值和ref声明的变量一样,需要通过.value访问其值
}; return {
num,
state,
count,
add1,
add2,
add3
};
},
});
</script>

注意:

  1. reactive可以传递基础数据类型和引用数据类型,基础数据类型不会被包装成响应式数据
  2. reactive返回的响应式数据本质是Proxy对象,对象里面每一层都会被包装成Proxy对象
  3. reactive返回的响应式数据和原始的数据会相互影响
  4. ref可以传递基础数据类型和引用数据类型,如果是基础数据类型,那么这个基础数据值保存在返回的响应式数据的.value上,如果是对象,响应式数据在.value上.
  5. ref本质是将一个数据变成一个对象,这个对象具有响应式特点

为什么需要toRefs和toRef?

和ref不一样的是,toRef和toRefs这两个方法,它们不创造响应式,而是延续响应式.创造响应式一般由ref和reactive来解决,而toRef和toRefs则把对象的数据进行分散和扩散,其这个对象针对的是响应式对象(reactive)而非普通对象.

3.3.watch

语法:watch(监听源 | [多个],(val,oldVal)=>{},{immediate?:false,deep:false})

watch写法上支持一个或者多个监听源,这些监听源必须只能是getter/effect函数,ref数据,reactive对象,或者是数组类型(只能是getter/effect函数,ref数据,reactive对象)

import { watch } from 'vue'
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true}) //情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
}) /* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}) //特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效

3.4.watchEffect

它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

watch的套路是:既要指明监视的属性,也要指明监视的回调。

watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

两者都可以监听 data 属性变化。

watchEffect有点像computed:

但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。

而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

<script>
let num = ref(0);
watchEffect(() => {
console.log(num.value);
});
setTimeout(() => {
num.value++;
}, 1000);
</script>

watch 和 watchEffect的区别

  • 两者都可以监听 data 属性变化;

  • watch 需要明确监听哪个属性

  • watchEffect 会根据其中的属性,自动监听其变化。

3.5. computed

<template>
<div>{{ count }}</div>
<button @click="state.count++">state.count++</button>
</template>
<script lang="ts">
import { defineComponent, reactive, computed } from "vue";
export default defineComponent({
setup() {
interface ObjItf {
count: number;
} let obj = {
count: 1,
arr: [1, 2, 3]
}; const state = reactive<ObjItf>(obj); let count = computed((): number => {
return state.count;
}); return {
count,
state
};
},
});
</script>

3.6.组件

全局组件:

const app = Vue.createApp({...})

app.component('my-component-name', {
/* ... */
})

局部组件(子传父,父传子):

子组件mychild.vue:

<template>
<div>{{ aname }}</div>
</template> <script lang="ts">
import { defineComponent, toRefs } from "vue";
export default defineComponent({
props: {
aname: {
type: String,
default: "张三",
},
},
emits: ["uname"],
setup(props, { emit }) {
let { aname } = toRefs(props);
const updateName = () => {
emit('uname', 'a改变后的名字')
};
return {
aname,
updateName,
};
},
});
</script>

父组件:

<template>
<mychild :aname="aname" @uname="updateName"></mychild>
</template> <script lang="ts">
import { defineComponent, toRefs, ref } from "vue";
import mychild from './mychild.vue';
export default defineComponent({
components: {
mychild
},
setup() {
let aname = ref('李四');
const updateName = (p: string) => {
aname.value = p;
}
return {
aname,
updateName
}
}
})
</script>

setup语法糖组件:

子组件mychild.vue:

<!--  -->
<template>
<div>{{ aname }}</div>
<button @click="updateName">修改姓名</button>
</template> <script lang="ts" setup>
// setup语法糖下defineProps,defineEmits 不需要引入
let emit = defineEmits(["updatename"]);
let props = defineProps({
aname: {
type: String,
default: "李四",
},
});
const updateName = (): void => {
emit('updatename', '修改后的名字')
};
</script> <style lang="less" scoped></style>

父组件:

<!--  -->
<template>
<div></div>
<mychild :aname="aname" @updatename="updateName"></mychild>
</template> <script lang="ts" setup>
import { ref } from "vue";
import mychild from "./b.vue";
let aname = ref("张三");
let updateName = (name: string): void => {
aname.value = name;
};
</script> <style lang="less" scoped></style>

3.7.v-model

子组件:

<!--  -->
<template>
<div>{{ name }}{{ age }}</div>
<button @click="updateName">修改姓名</button>
</template> <script lang="ts" setup>
let props = defineProps({
age: Number,
name: String,
});
let emits = defineEmits(["update:name", "update:age"]);
let updateName = () => {
emits("update:age", 30);
emits("update:name", "李四");
};
</script> <style lang="less" scoped></style>

父组件:

<!--  -->
<template>
<div></div>
<mychild v-model:name="state.name" v-model:age="state.age"></mychild>
</template> <script lang="ts" setup>
import { reactive } from "vue";
import mychild from "./b.vue";
let info = {
name: '张三',
age: 20
}
let state = reactive(info); </script> <style lang="less" scoped></style>

3.8.插槽

子组件:

<!--  -->
<template>
<slot></slot>
<slot name="title"></slot>
<slot name="footer" :user="state.user" :d="state.d"></slot>
</template> <script lang="ts" setup>
import { reactive } from "vue";
let state = reactive({
user: { a: 1, b: 2 },
d: 1,
});
</script> <style lang="less" scoped></style>

父组件:

<!--  -->
<template>
<div></div>
<mychild>
<div>匿名插槽</div>
<template #title>
<div>具名插槽</div>
</template>
<template #footer="scope">
<div>作用域插槽{{ scope.user }} {{ scope.d }}</div>
</template>
</mychild>
</template> <script lang="ts" setup>
import mychild from "./child.vue";
</script> <style lang="less" scoped></style>

3.9.await支持

不必再配合async就可以直接使用await了,这种情况下,组件的setup会自动变成async setup.

<script setup>
const post = await fetch('/api').then(() => {})
</script>

如果浏览器报了这个错误的话

在app组件的模板最外层嵌套<Suspense>标签

App.vue:

3.10.style scoped

<style scoped>
/* 修改第三方组件样式 */
::v-deep(.foo) {}
/* 简写 */
:deep(.foo) {} /* 修改插槽内容样式 */
::v-slotted(.foo) {}
/* 简写 */
:slotted(.foo) {} /* 修改全局样式 */
::v-global(.foo) {}
/* 简写 */
:global(.foo) {}
</style>

3.11.teleport

这个组件可以把组件进行传送,to属性就是要传送的位置目标,用css选择器

<template>
<div class="header"></div>
<teleport :to="target">
<p>哈哈哈哈哈</p>
</teleport>
<button @click="changeTarget">点击切换</button>
<p>-------------------------------</p>
<div class="footer"></div>
</template> <script lang="ts" setup>
import { ref } from "vue";
let target = ref<string>(".header");
const changeTarget = (): void => {
target.value = ".footer";
};
</script> <style lang="less" scoped></style>

Vue2到Vue3的改变的更多相关文章

  1. Vue2和Vue3技术整理1 - 入门篇 - 更新完毕

    Vue2 0.前言 首先说明:要直接上手简单得很,看官网熟悉大概有哪些东西.怎么用的,然后简单练一下就可以做出程序来了,最多两天,无论Vue2还是Vue3,就都完全可以了,Vue3就是比Vue2多了一 ...

  2. vue2和vue3生命周期的区别

    概念 首先,我们了解一下"生命周期"这个词.通俗的来说,生命周期就是一个事务从出生到消失的过程.例如,一个人从出生到去世.在vue中,vue的生命周期是指,从创建vue对象到销毁v ...

  3. 盘点Vue2和Vue3的10种组件通信方式(值得收藏)

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异:本文将通过选项式API 组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式.其中将要实现的通信 ...

  4. Vue2 到 Vue3,重温这 5 个常用的 API

    距离Vue3发布已经过去一年多时间了,从Vue2到Vue3是一个不小的升级,包括周边生态等.虽然目前大多数开发者们在使用的仍旧以Vue2为准,但Vue3显然是Vue开发者们未来必须面对的,而且前不久V ...

  5. vue2和vue3区别

    1. vue2和vue3双向数据绑定原理发生了改变 vue2的双向数据绑定是利用了es5 的一个API Object.definepropert() 对数据进行劫持 结合发布订阅模式来实现的.vue3 ...

  6. vue2升级vue3:vue2 vue-i18n 升级到vue3搭配VueI18n v9

    项目从vue2 升级vue3,VueI18n需要做适当的调整.主要是Vue I18n v8.x 到Vue I18n v9 or later 的变化,其中初始化: 具体可以参看:https://vue- ...

  7. vue2升级vue3指南(二)—— 语法warning&error篇

    本文总结了vue2升级vue3可能会遇到的语法警告和错误,如果想知道怎样升级,可以查看我的上一篇文章:vue2升级vue3指南(一)-- 环境准备和构建篇 Warning 1.deep /deep/和 ...

  8. vue2升级vue3:Vue Demij打通vue2与vue3壁垒,构建通用组件

    如果你的vue2代码之前是使用vue-class-component 类组件模式写的.选择可以使用 https://github.com/facing-dev/vue-facing-decorator ...

  9. vue2和vue3的区别?

    vue2和vue3的主要区别在于以下几点: 1.生命周期函数钩子不同 2.数据双向绑定原理不同 3.定义变量和方法不同 4.指令和插槽的使用不同 5.API类型不同 6.是否支持碎片 7.父子组件之间 ...

  10. vue2升级vue3:vue-i18n国际化异步按需加载

    vue2异步加载之前说过,vue3还是之前的方法,只是把 i18n.setLocaleMessage改为i18n.global.setLocaleMessage 但是本文还是详细说一遍: 为什么需要异 ...

随机推荐

  1. ThinkPHP网站安全方案

    一.ThinkPHP介绍 ThinkPHP是一款优秀的网站内容管理系统,因其功能强大,操作简单,拥有海量用户和其他CMS一样,安全漏洞也是其无法避免的问题.虽然官方不断发布补丁.升级版本,但安全问题依 ...

  2. 文件的上传&预览&下载学习(四)

    0.参考博客 https://blog.csdn.net/Chengzi_comm/article/details/53037967 逻辑清晰 https://blog.csdn.net/alli09 ...

  3. 用声网 Android UIKit 为实时视频通话应用添加自定义背景丨声网 SDK 教程

    使用声网 SDK 和 UIKit 创建视频推流应用非常简单,而且声网还有许多功能,可以提高视频通话的质量和便利性.例如,我们可以在视频通话过程中使用虚拟背景,为视频通话增添趣味性. 我们可以通过以下三 ...

  4. Go语言 :使用简单的 for 迭代语句进行 TDD 驱动测试开发与 benchmark 基准测试

    前提准备与运行环境请参考:(新手向)在Linux中使用VScode编写 "Hello,world"程序,并编写测试-Ubuntu20.4   在 Go 中 for 用来循环和迭代, ...

  5. 记一次 .NET 某手术室行为信息系统 内存泄露分析

    一:背景 1. 讲故事 昨天有位朋友找到我,说他的程序内存存在泄露导致系统特别卡,大地址也开了,让我帮忙看一下怎么回事?今天上午看了下dump,感觉挺有意思,在我的分析之旅中此类问题也蛮少见,算是完善 ...

  6. 安装部署keepalived的HA环境

    每一台配置下keepalived #master01 配置: cat >/etc/keepalived/keepalived.conf<<"EOF" ! Conf ...

  7. 基于SpringBoot实现单元测试的多种情境/方法(二)

    本文分享自天翼云开发者社区@<基于SpringBoot实现单元测试的多种情境/方法(二)>,  作者:才开始学技术的小白 1 Mock基础回顾 在上一篇分享中我们详细介绍了简单的.用moc ...

  8. 7.OAuth2

    1.近几天在学习OAuth2协议,实际开发中用的最多的就是授权码模式   2.OAuth2的授权码模式流程:首先是用户去访问资源服务器,服务器会给用户一个授权码:用户根据授权码去访问认证服务器,服务器 ...

  9. Idea快捷键——查找源码

    双击shift 输入要查找源码类 相当于查 java_jdk_chm Ctrl+F12 :浏览类

  10. 【解释器设计模式详解】C/Java/Go/JS/TS/Python不同语言实现

    简介 解释器模式(Interpreter Pattern)是一种行为型设计模式.这种模式实现了一个表达式接口,该接口解释一个特定的上下文.这种模式常被用在 SQL 解析.符号处理引擎等. 解释器模式常 ...