简单对比vue2.x与vue3.x响应式及新功能
简单对比vue2.x与vue3.x响应式
对响应方式来讲:Vue3.x 将使用Proxy ,取代Vue2.x 版本的 Object.defineProperty
。
为何要将Object.defineProperty
换掉呢?
1、我刚上手Vue2.x的时候就经常遇到一个问题,数据更新了啊,为何页面不更新呢?
2、什么时候用$set
更新,什么时候用$forceUpdate
强制更新,你是否也一度陷入困境?
后来的学习过程中开始接触源码,才知道一切的根源都是 Object.defineProperty
。
在vue2.0中vm.items[indexOfItem] = newValue这种是无法检测的。事实上,Object.defineProperty
本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。
下面个例子 Object.defineProperty
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get: () => {
console.log('get',value)
return value
},
set:(newValue)=>{
console.log('set')
value=newValue
}
})
}
function observe(data){
Object.keys(data).forEach(function(key){
defineReactive(data,key,data[key])
})
}
let arr=[1,2,3]
observe(arr)
arr[1]
arr[2]=9
arr[2]
可以看到,通过下标获取某个元素会触发 getter
方法, 设置某个值会触发 setter 方法。
Object.defineProperty
在数组中的表现和在对象中的表现是一致的,数组的索引就可以看做是对象中的 key
。
通过索引访问或设置对应元素的值时,可以触发
getter
和setter
方法。通过
push
或unshift
会增加索引,对于新增加的属性,需要再手动初始化才能被 observe。通过 pop 或 shift 删除元素,会删除并更新索引,也会触发
setter
和getter
方法。
所以,Object.defineProperty
是有监控数组下标变化的能力的,只是 Vue2.x 放弃了这个特性。
那么Object.defineProperty
和 Proxy 对比存在哪些优缺点呢?
1. Object.defineProperty
只能劫持对象的属性,而 Proxy 是直接代理对象。
由于 Object.defineProperty
只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,不需要遍历操作。
2. Object.defineProperty
对新增属性需要手动进行 Observe。
由于 Object.defineProperty
劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty
进行劫持。也正是因为这个原因,使用 Vue 给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。
总 结
Object.defineProperty
并非不能监控数组下标的变化,Vue2.x 中无法通过数组索引来实现响应式数据的自动更新是 Vue 本身的设计导致的,不是 defineProperty 的锅。Object.defineProperty
和Proxy
本质差别是,defineProperty
只能对属性进行劫持,所以出现了需要递归遍历,新增属性需要手动Observe
的问题。Proxy
作为新标准,浏览器厂商势必会对其进行持续优化,但它的兼容性也是块硬伤,并且目前还没有完整的 polyfill 方案。
自定义 Hooks
一个实现加减的例子, 这里可以将其封装成一个hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。
useCount.js
import { ref, Ref, computed } from "vue";
export default function useCount(initValue = 1) {
const count = ref(initValue); const increase = (delta) => {
if (delta) {
count.value += delta;
} else {
count.value += 1;
}
};
const multiple = computed(() => count.value * 2) const decrease = (delta) => {
if (delta) {
count.value -= delta;
} else {
count.value -= 1;
}
}; return {
count,
multiple,
increase,
decrease,
};
}
接下来看一下在组件中使用useCount
这个 hook
:
<template>
<div>
<p>count: {{ count }}</p>
<p>倍数: {{ multiple }}</p>
<div>
<button @click="increase()">加1</button>
<button @click="decrease()">减一</button>
</div>
</div>
</template> <script>
import useCount from "../hooks/useCount";
export default {
components: {},
//con==context(attrs,emit,slots)
setup(props, con) {
const { count, multiple, increase, decrease } = useCount(10);
return {
count,
multiple,
increase,
decrease,
};
},
};
</script> <style lang='scss' scoped>
</style>
Vue 3 任意传送门——Teleport
React
的同学,可能对于 Portals
比较熟悉。React 的 Portal
提供了一种将子节点渲染到存在于父组件以外的 DOM
节点的优秀的方案,我理解,Vue 3
中的 Teleport
跟这个其实是类似的。Header
中使用到Dialog
组件,我们实际开发中经常会在类似的情形下使用到 Dialog
,此时Dialog
就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index
和样式都变得困难。 Dialog
从用户感知的层面,应该是一个独立的组件,从dom结构应该完全剥离Vue顶层组件挂载的DOM;同时还可以使用到Vue组件内的状态(data
或者props
)的值。简单来说就是,即希望继续在组件内部使用Dialog
,又希望渲染的DOM结构不嵌套在组件的DOM中。<Teleport>
包裹Dialog
, 此时就建立了一个传送门,可以将Dialog
渲染的内容传送到任何指定的地方。Teleport的使用
我们希望Dialog渲染的dom和顶层组件是兄弟节点关系, 在index.html
文件中定义一个供挂载的元素:
<div id="app"></div>
+ <div id="dialog"></div>
</body>
定义一个Dialog
组件Dialog.vue
, 留意 to
属性, 与上面的id
选择器一致:
<template>
<teleport to="#dialog">
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header" v-if="title">
<slot name="header">
<span>{{title}}</span>
</slot>
</div>
</div>
<div class="dialog_content">
<slot></slot>
</div>
<div class="dialog_footer">
<slot name="footer"></slot>
</div>
</div>
</teleport>
</template>
最后在一个子组件Header.vue
中使用Dialog
组件,这里主要演示 Teleport的使用,不相关的代码就省略了。header
组件
<div class="header">
...
<navbar />
+ <Dialog v-if="dialogVisible"></Dialog>
</div>
...
可以看到,我们使用 teleport
组件,通过 to
属性,指定该组件渲染的位置与 <div id="app"></div>
同级,也就是在 body
下,但是 Dialog
的状态 dialogVisible
又是完全由内部 Vue 组件控制
Vue3 新特性 —— Suspense 异步组件
在vue2.0前后端交互获取数据时, 是一个异步过程,一般我们都会提供一个加载中的动画,当数据返回时配合v-if
来控制数据显示。
<div>
<div v-if="!loading">
...
</div>
<div v-if="loading">
加载中...
</div>
</div>
如果你使用过vue-async-manager
这个插件来完成上面的需求, 你对Suspense
可能不会陌生,Vue3.x感觉就是参考了vue-async-manager
.
Suspense
, 它提供两个template
slot, 刚开始会渲染一个fallback状态下的内容, 直到到达某个条件后才会渲染default状态的正式内容, 通过使用Suspense
组件进行展示异步渲染就更加的简单。如果使用 Suspense
, 要返回一个promise 组件的使用:
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
案例
/src/components/Async.vue
<template>
<h1>{{ result }}</h1>
</template>
<script >
export default{
setup() {
return new Promise(resolve => {
setTimeout(() => {
return resolve({ result: "HI~Async" });
}, 3000);
});
}
}
</script>
在 App.vue 中使用异步组件
注意:
- 使用
<Suspense></Suspense>
包裹所有异步组件相关代码 <Suspense></Suspense>
下<template #default></template>
插槽包裹异步组件<Suspense></Suspense>
下<template #fallback></template>
插槽包裹渲染异步组件之前的内容
/src/App.vue
<template>
<div id="app">
<Suspense>
<template #default>
<Async></Async>
</template>
<template #fallback>
<h1>Loading...</h1>
</template>
</Suspense>
</div>
</template> <script lang="ts">
import Async from "./components/Async.vue"; export default {
name: "App",
components: {
Async
}
};
</script> <style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
片段(Fragment)
template
中只允许有一个根节点:<template>
<span></span>
<span></span>
</template>
更好的 Tree-Shaking
Vue 团队希望能帮助开发者减小 Web 应用的体积。
什么是 TreeShaking
TreeShaking 是一个术语,指的是在打包构建过程中移除没有被引用到的代码,这些代码可以成为 dead code。这个概念最早在基于 ES6 的打包工具 Rollup 中提出,后来被引入到 webpack 中。TreeShaking 比较依赖于 ES6 模块系统的静态结构特性,比如 import 和 export。
我们在vue去操作dom时往往会用到vue.nextTick或this.$nextTick
// vue2.x
import Vue from "vue" Vue.nextTick(()=>{
...
})
其实在单个的 Vue 实例中也可以使用这个方法:$nextTick
,这不过是 Vue 2.x 版本中方便开发者的一种做法,这个函数的本质依然是 Vue.nextTick
。
假如你没有用到 Vue.nextTick
这个方法,或者你更喜欢用 setTimeout
来代替,这样的话 Vue 中 nextTick
的部分将会变成 dead code —— 徒增代码体积但从来不会被用到,这对于客户端渲染的 web app 来说是拖累性能的一大因素。
虽然我们借助webpack
的tree-shaking
,但是不管我们实际上是否使用Vue.nextTick()
,最终都会进入我们的生产代码, 因为 Vue实例是作为单个对象导出的, 打包器无法查出代码使用了对象的哪些属性。
vue3.x写法
在 Vue 3 中,官方团队重构了所有全局 API 的组织方式,让所有的 API 都支持了 TreeShaking。所以,当开发者在 Vue 3 中使用全局 API 时,需要主动将其导入到目标文件中,比如上面的例子,需要改写成:
import { nextTick } from "vue" nextTick(() =>{
...
})
受影响的 API
Vue.nextTick
Vue.observable (用 Vue.reactive 替换)
Vue.version
Vue.compile (仅限完整版本时可用)
Vue.set (仅在 2.x 兼容版本中可用)
Vue.delete (与上同)
vue3与vue2相比的一些变动
slot 具名插槽语法
在Vue2.x中, 具名插槽的写法:
<!-- 子组件中:-->
<slot name="title"></slot> <!-- 父组件中:-->
<template slot="title">
<h1>歌曲:成都</h1>
<template>
在vue2.x slot上面绑定数据,可以使用作用域插槽
// 子组件
<slot name="content" :data="data"></slot>
export default {
data(){
return{
data:["1234","2234","3234"]
}
}
}
<!-- 父组件中使用 -->
<template slot="content" slot-scope="scoped">
<div v-for="item in scoped.data">{{item}}</div>
<template>
在Vue2.x中具名插槽和作用域插槽分别使用slot
和slot-scope
来实现。
在Vue3.0中将slot
和slot-scope
进行了合并。
<!-- 父组件中使用 -->
<template v-slot:content="scoped">
<div v-for="item in scoped.data">{{item}}</div>
</template> <!-- 也可以简写成: -->
<template #content="{data}">
<div v-for="item in data">{{item}}</div>
</template>
自定义指令
Vue 2 中实现一个自定义指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
在Vue 2 中, 自定义指令通过以下几个可选钩子创建:
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
在Vue 3 中对自定义指令的 API进行了更加语义化的修改
所以在Vue3 中, 可以这样来自定义指令:
<input v-focus />
const { createApp } from "vue" const app = createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
v-model 升级
- 变更:在自定义组件上使用
v-model
时, 属性以及事件的默认名称变了 - 变更:
v-bind
的.sync
修饰符在 Vue 3 中又被去掉了, 合并到了v-model
里 - 新增:同一组件可以同时设置多个
v-model
- 新增:开发者可以自定义
v-model
修饰符
异步组件
Vue3 中 使用 defineAsyncComponent
定义异步组件,配置选项 component
替换为 loader
,Loader 函数本身不再接收 resolve 和 reject 参数,且必须返回一个 Promise,用法如下:
<template>
<!-- 异步组件的使用 -->
<AsyncPage />
</tempate> <script>
import { defineAsyncComponent } from "vue"; export default {
components: {
// 无配置项异步组件
AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")), // 有配置项异步组件
AsyncPageWithOptions: defineAsyncComponent({
loader: () => import(".NextPage.vue"),
delay: 200,
timeout: 3000,
errorComponent: () => import("./ErrorComponent.vue"),
loadingComponent: () => import("./LoadingComponent.vue"),
})
},
}
</script>
简单对比vue2.x与vue3.x响应式及新功能的更多相关文章
- Vue3.0工程创建 && setup、ref、reactive函数 && Vue3.0响应式实现原理
1 # 一.创建Vue3.0工程 2 # 1.使用vue-cli创建 3 # 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html# ...
- vue2与vue3实现响应式的原理区别和提升
区别: vue2.x: 实现原理: 对象类型:Object.defineProperty()对属性的读取,修改进行拦截(数据劫持): 数组类型:通过重写更新数组的一系列方法来进行拦截(对数组的变更方法 ...
- Vue2手写源码---响应式数据的变化
响应式数据变化 数据发生变化后,我们可以监听到这个数据的变化 (每一步后面的括号是表示在那个模块进行的操作) 手写简单的响应式数据的实现(对象属性劫持.深度属性劫持.数组函数劫持).模板转成 ast ...
- Vue3.0 响应式数据原理:ES6 Proxy
Vue3.0 开始用 Proxy 代替 Object.defineProperty了,这篇文章结合实例教你如何使用Proxy 本篇文章同时收录[前端知识点]中,链接直达 阅读本文您将收获 JavaSc ...
- vue2.0与3.0响应式原理机制
vue2.0响应式原理 - defineProperty 这个原理老生常谈了,就是拦截对象,给对象的属性增加set 和 get方法,因为核心是defineProperty所以还需要对数组的方法进行拦截 ...
- Vue3.0响应式原理
Vue3.0的响应式基于Proxy实现.具体代码如下: 1 let targetMap = new WeakMap() 2 let effectStack = [] //存储副作用 3 4 const ...
- Vue3.0响应式实现
基于Proxy // 弱引用映射表 es6 防止对象不能被回收 let toProxy = new WeakMap(); // 原对象: 代理过得对象 let toRaw = new WeakMap( ...
- vue3响应式模式设计原理
vue3响应式模式设计原理 为什么要关系vue3的设计原理?了解vue3构建原理,将有助于开发者更快速上手Vue3:同时可以提高Vue调试技能,可以快速定位错误 1.vue3对比vue2 vue2的原 ...
- 实现vue2.0响应式的基本思路
最近看了vue2.0源码关于响应式的实现,以下博文将通过简单的代码还原vue2.0关于响应式的实现思路. 注意,这里只是实现思路的还原,对于里面各种细节的实现,比如说数组里面数据的操作的监听,以及对象 ...
随机推荐
- 学习一下 SpringCloud (六)-- 注册中心与配置中心 Nacos、网关 Gateway
(1) 相关博文地址: 学习一下 SpringCloud (一)-- 从单体架构到微服务架构.代码拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105 ...
- go中semaphore(信号量)源码解读
运行时信号量机制 semaphore 前言 作用是什么 几个主要的方法 如何实现 sudog 缓存 acquireSudog releaseSudog semaphore poll_runtime_S ...
- [树状数组]数星星 Stars
数 星 星 S t a r s 数星星 Stars 数星星Stars 题目描述 天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标.如果一个星星的左下方(包含正左和正下)有 k k k 颗星星 ...
- BUAAOO第二单元代码分析
第一次作业 设计思路与感想 第一次作业是要求有捎带的电梯实现, 第一次作业是花费的时间比较长的一次,花费了很多的时间去思考架构的问题.起初是想要搞三个线程的:输入线程,调度器线程和电梯线程,想要搞一个 ...
- 6.2set用法
目录 1.set的定义 2.set容器内元素的访问 3.set常见使用的函数 set可以内部进行自动递增排序,且自动去除了重复元素 1.set的定义 set<typename> name; ...
- zk都有哪些使用场景?
(1)分布式协调:这个其实是zk很经典的一个用法,简单来说,就好比,你A系统发送个请求到mq,然后B消息消费之后处理了.那A系统如何知道B系统的处理结果?用zk就可以实现分布式系统之间的协调工作.A系 ...
- Java8 Map computeIfAbsent方法说明
// 方法定义 default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { . ...
- JDBC_06_处理查询结果集
JDBC处理查询结果集 * ResultSet resultSet=null 结果集对象 * executeQuery(select) 执行SQL查询语句需要的使用executeQuery方法 * i ...
- junit+maven单元测试
一.概念 junit是一个专门测试的框架 集合maven进行单元测试,可批量测试类中的大量方法是否符合预期 二.作用:单元测试:测试的内容是类中的方法,每一个方法都是独立测试的.方法是测试的基本单位. ...
- 【参数校验】 自定义校验器 (实现ConstraintValidator)
日常工作中写接口时,往往需要校验前端传来的枚举状态码,例如"1","2"等等, 这里使用java 303规范的参数校验框架封装一个自定义参数校验器: /** * ...