欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 锐评 > 【前端】第12节:Vue3新特性

【前端】第12节:Vue3新特性

2025/11/6 1:50:45 来源:https://blog.csdn.net/alikami/article/details/143961137  浏览:    关键词:【前端】第12节:Vue3新特性

引入

说起 vue3 的新特性,就会不由自主想到 vue3 和 vue2 之间的差异,例如:双向绑定、根节点数量、生命周期、this 等等,详细可以见这篇文章(参考)——

vue2和vue3的差异整理(轻松过度到vue3)_vue2和vue3区别-CSDN博客

咱们今天就主要来大概梳理一下 vue3 的新特性。

1composition API
2生命周期
3异步组件
4自定义指令
5teleport
6自定义hooks

目录

引入

vue3 和 vue2 响应式原理的区别

vue2 : Object.defineProperty()

vue3 : proxy

vue3 代码实现

特性一:composition API

1. 创建

2. 添加响应式的变化

reactive

shallowReactive

ref


vue3 和 vue2 响应式原理的区别

vue2 : Object.defineProperty()

我们知道,vue2 是通过 Object.defineProperty 这个api,重新定义对象的 gettersetter 对数据进行劫持,以此监测数据的响应变更,触发视图的更新,从而实现双向绑定。

 Object.defineProperty(官网):Object.defineProperty() - JavaScript | MDN

数据劫持(参考): 第二十一章 javascript数据代理(数据劫持)_js 数据代理-CSDN博客

那具体怎么实现的呢?我们可以试着手写一个:

// 初始数据
const initData = {value: 1
}
// 响应式数据
const data = {}Object.keys(initData).forEach(key => {Object.defineProperty(// 挂载对象data,// 挂载对象的 keykey,{// 挂载对象的 getter 和 setterget() {console.log("访问:", key)// 返回初始数据中的值return initData[key];},set(val) {console.log("修改", key)initData[key] = val;},})
})

在浏览器的控制台中测试:

因为添加的响应式数据 data 只能针对遍历后的 initData 进行监测,所以  data 无法获取到 initData 的新增属性,也就没法实现新增属性的双向绑定,这也是 vue2 的一个缺陷。

解决方法:$set

作用:可以对新增属性添加响应式的变化,并触发视图更新。

参考:

vue中$set用法详细讲解_vue $set-CSDN博客

这里简单介绍下 set ——

用法:Vue.set ( target , key , value )   

set 是如何实现的:

1. null undefined boolean 等这些类型会报错 因为只有 object 才可以设置属性 

2. 数组 : splice

        取两个数组的最大值,作为最新值,利用 splice 将更新值加入数组之中。

        因为 splice 是原型 (Array.prototype.splice) 上的一个方法 , 当 target 在收集依赖的时候,原型上的方法也会改变。此时会重新遍历数组,也就相当于是手动触发了一次更新~

3. 对象:判断 key 是否存在  ( target [ key ] = value )

        存在就替换

        不存在就判断target是否是响应式:是就替换,不是就报错。

        ( 如果是响应式的话,会用到 vue2 中的依赖收集,就会触发 defineReactive 进行更新 )

vue3 : proxy

proxy 是 js 提供的一种响应式的依赖,作用是作为一层拦截。当外界想要访问本身的对象时,需要通过一层拦截机制。所以 proxy 相当于是一层代理。

proxy(官网):Proxy() 构造函数 - JavaScript | MDN

使用:

// target : 需要拦截的目标
// handler : 拦截的方法
const proxy = new Proxy(target,handler)

代码实现 proxy :

const initData = {value: 1
}
const proxy = new Proxy(initData, {// receiver 用来接收数据get: (target, key, receiver) => {console.log("访问", key)// Reflect 针对当前数据进行拦截,是 proxy 默认提供的apireturn Reflect.get(target, key, receiver)},set: (target, key, value, receiver) => {console.log("修改", key)Reflect.set(target, key, value, receiver)}
})

 Reflect(官网):https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

浏览器控制台测试效果:

可以看到,proxy 实现了数据的双向绑定。无论是源数据还是新增属性,都可以监测到变化从而触发视图更新。

vue3 代码实现

现在正式开始讲 vue3 的新特性了~ 😜

首先创建一个项目:

// 创建项目
pnpm create vite-app demo// 进入文件
cd demo// 安装依赖
pnpm install

vue2 和 vue3 入口文件的差异:

vue2 中的声明只有一个 vue 的实例,因为 vue2 是单例模式,因此是用 this 上各种各样的属性去获取到内容。

new Vue({...
})this.xxx

vue3 通过 createApp 绑定到 App 实例上,再通过 mount 绑定到 app 这个节点上,来涵盖多种实例。 

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'createApp(App).mount('#app')

可以看到:

vue3 支持多实例,多实例之间相互隔离,互不影响。

特性一:composition API

vue2 的写法容易冗杂,很容易单页面就写出几千行代码的情况。就算有 mixin 但是容易因为变量命名而导致冲突。

vue3 则提供了 composition API ,把零散的逻辑耦合在一起,按照功能拆分成不同的子模块,利用 hooks,更方便代码阅读和后期维护。

由面向业务编程转为函数式编程,按照代码的本质进行操作,减少单文件的量级。

用代码示例 composition API:

1. 创建

main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'// 引入
import Composition from './Compostion.vue'
// 挂载到创建的 composition 节点上
createApp(Composition).mount('#composition')createApp(App).mount('#app')

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title>
</head>
<body>
<div id="app"></div>
<!--composition-->
<div id="composition"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

Composition.vue (vue文件一般用大驼峰命名)

<template><div><div>count:{{ count }}</div></div>
</template><script>
import { defineComponent } from 'vue'
export default defineComponent({// setup:为 composition api 提供的新的属性,类似于 vue2 中的 beforeCreated 生命周期setup(){const count = 0return {count}}
})
</script><style scoped lang=scss></style>

页面效果:

当然,这只是一个非常简单的示例,也没有响应式,而且在项目中,更多的还是挂载到 #app 节点上。

2. 添加响应式的变化

reactive

是一个函数,接收一个对象,返回一个响应式的数据结构。

如果接收的对象层级很深(对象嵌套对象),则会通过递归去调用、关联。

官网:https://cn.vuejs.org/api/reactivity-core.html#reactive

创建个 reactive :

import {defineComponent, reactive} from 'vue'
export default defineComponent({setup() {const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}}const reactiveObj = reactive(obj)console.log(reactiveObj, "<------reactiveObj-----------✨✨✨✨")return {obj}}
})

控制台打印结果:

 

说明,用 reactive 创建的对象是一个 proxy ,也就是响应式对象。

而且,reactiveObj 内部的对象也是响应式的,因为是递归关联的:

    const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}}const reactiveObj = reactive(obj)const reactiveContact = reactiveObj.contactconsole.log(reactiveContact, "<----------reactiveContact-------✨✨✨✨")

打印结果:

shallowReactive

只针对表层内容做响应式创建,而不去递归。 

官网:https://cn.vuejs.org/api/reactivity-advanced.html#shallowreactive

reactive 与 shallowReactive (参考):Vue3中shallowReactive 与 shallowRef 的用法-CSDN博客

举个栗子:

const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}
}const shallowReactiveObj = shallowReactive(obj)
const shallowReactiveContact = shallowReactiveObj.contactconsole.log(shallowReactiveObj, "<------shallowReactiveObj-----------✨✨✨✨")
console.log(shallowReactiveContact, "<------shallowReactiveContact-----------✨✨✨✨")

打印的 shallowReactiveObj 值为:

这时也是一个 proxy 对象,但是当我们打印其中的 contact 的时候,就会发现这只是个普通对象了:

所以 shallowReactive 只是实现浅层的响应式,对于深层次的对象不再具有响应性。

如果想转为响应式,就需要用到 toRef 或是 toRefs。要了解的话可以参考这篇——

vue3中的ref,toRef,toRefs三个是干嘛的,有什么作用呢。_vue3 ref torefs-CSDN博客

按照我们举的例子来说,如果想把 shallowReactiveContact 转化为响应式的,可以这样:

const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}
}const shallowReactiveObj = shallowReactive(obj)
const shallowReactiveContact = shallowReactiveObj.contact
// 用 toRefs 进行转换
const toRefsShallowReactiveContact = toRefs(shallowReactiveContact)console.log(toRefsShallowReactiveContact, "toRefsShallowReactiveContact")

打印结果为:

ref

根据当前给定值,创建一个响应式对象,通过 .value 来获取。

官网:https://cn.vuejs.org/api/reactivity-core.html#ref

举个栗子:

const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}
}const refObj = ref(obj)
console.log(refObj, "<-----------------✨✨✨✨")
console.log(refObj.value, "<-----------------✨✨✨✨")

打印结果:

ref 创建的也是深层次的响应。而且ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。

还需要注意,官网中有这样一段话:

① 如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。

② 若要避免这种深层次的转换,请使用 shallowRef() 来替代。

 我们先为 ① 举个栗子:

const contact = ref(0)
const obj = reactive({name: 'alikami',age: 24,contact
})// 结果为 0  因为会自动解包,不用.value来获取值,但是响应式还存在
console.log(obj.contact, "<-----contact.value------------✨✨✨✨")
// 结果为 true
console.log(obj.contact === contact.value, "<------------✨✨✨✨")contact.value = 1
// 结果为 1
console.log(contact.value, "<-----contact.value------------✨✨✨✨")
// 结果为 1
console.log(obj.contact, "<------obj.contact-----------✨✨✨✨")obj.contact = 2
// 结果为 2
console.log(contact.value, "<-----contact.value------------✨✨✨✨")
// 结果为 2
console.log(obj.contact, "<------obj.contact-----------✨✨✨✨")

响应式嵌套响应式,还是响应式的值。reactive 会把 ref 解包,这也是一种二者之间的关系。

未完待续。。。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词