Vue3.0与Vue2.0的对比
1. 修改了 Vue2.x 时期遗留的一些缺陷
2. 引入了 组合式 API 、自定义渲染 API 等新特性
Vue3.0 的新特性
1.Proxy 响应式绑定
2.Tree-Shaking Support
3.组合式 API
4.Fragment 片段、Teleport [ˈtelɪpɔːt] (传送)、Suspense[səsp'ens]
5.自定义渲染 API
6.源码优化
一、Proxy 响应式绑定
Vue2.x是通过 Object.defineProperty 的get和 set方法实现数据结持,从而实现双向数据绑定。这个API必须预先知道要拦截的 key 是什么,所以它并不能检测对象属性的添加和删除。造成数组元素的直接修改不会触发响应式机制。
例如,对象obj的text属性进行劫持:
const obj = {};
Object.defineProperty(obj, 'text', {
get: function() {
console.log('get val');
},
set: function(newVal) {
console.log('set val:' + newVal);
document.getElementById('input').value = newVal;
document.getElementById('span').innerHTML = newVal;
}
});
const input = document.getElementById('input');
input.addEventListener('keyup', function (e) {
obj.text = e.target.value;
})
复制代码
Vue3.0使用Proxy API做数据劫持,劫持的是整个对象,对于对象属性的增加和删除都能检测到。
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
// Reflect: 反映,反射,表达
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
复制代码
通过Proxy实现双向响应式绑定,相比 defineProperty的遍历属性的方式效率更高,性能更好,另外Virtual DOM 更新只 diff 动态部分、事件缓存等,也带来了性能上的提升。
二、Tree-Shaking Support(摇树优化)
tree-sharking在构建工具打包后消除程序中无用的代码,来减少包的体积。相比Vue2.0导入整个Vue对象,Vue3.0支持按需导入,只打包需要的代码。Tree-Shaking依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。像我们在项目中如果没有引入 Transition、KeepAlive 等不常用的组件,那么它们对应的代码就不会打包进去。
三、组合式 API
首先需要明确的是,Vue2.0中组件传统的 data,computed,watch,methods 写法,我们称之为选项式 API(Options API )。
(1)选项式 API 存在的缺陷
随着业务复杂度越来越高,代码量会不断的加大;由于代码需要遵循 option 的配置写到特定的区域,导致后续维护非常的复杂,代码可复用性也不高。比如,很长的 methods 区域代码、data变量声明与方法区域在一起。
(2)与 mixins 的比较
对于上述提到的问题,我们可能会想到会 mixins 来解决,但是当抽离并引用了大量的 mixins,就会发现两个不可避免的问题:命名冲突和数据来源不清晰。
组合式 API 和 mixins 二者的差别:
· 层级不同:组合式 API 与组件是嵌套关系,而mixin与组件是同层级关系
· 影响面不同:组合式 API 作为组件的被调用方,并且变量逻辑是组件控制,耦合性很低。而 mixins 是耦合在代码逻辑里,并且存在变量的互相引用,为将来的升级和维护埋下隐患。
(3)与 React Hook 的比较
组合式 API 的实现与 React Hook 是截然不同的,比 React Hooks简便了不少:
· 同样的逻辑组合、组件复用能力
· 只调用一次 setup 方法
更加符合 JS 直觉
没有闭包变量问题
没有内存/GC压力
不存在内联回调导致子组件永远更新的问题
(4)组合式 API 的使用
下面简单概述了组合式 API 的使用,主要是一些方法增加和概念迁移:
setup方法:
vue3.0 中,所有的代码逻辑将在setup方法中实现,包括 data、watch、computed、methods 等,并且不再有this。
vue3.0 会先执行setup方法,再执行兼容2.0的其他方法。
vue3.0 中setup方法在组件生命周期内只执行一次,不会重复执行。
生命周期钩子:
2.0中生命周期钩子放在跟 methods 同级属性下。
3.0中需要先导入钩子,然后在 setup 方法中注册钩子回调,并且钩子命名也跟 React 保持一样。
3.0移除了2.0中的 beforeCreate 和 created 钩子,通过 setup 方法代替。
// vue2.0
export default {
data () {
return {}
},
methods: {
...
},
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeDestroy() {},
destroyed() {}
}
复制代码
// vue3.0
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('component is onBeforeMount')
})
onMounted(() => {
console.log('component is onMounted')
})
onBeforeUpdate(() => {
console.log('component is onBeforeUpdate')
})
onUpdated(() => {
console.log('component is onUpdated')
})
onBeforeUnmount(() => {
console.log('component is onBeforeUnmount')
})
onUnmounted(() => {
console.log('component is onUnmounted')
})
}
}
复制代码
响应式数据对象相关方法:reactive方法、ref方法、toRef等方法,数据的只读属性,计算属性,方法的拆解,watchEffect,useStore 等相关方法不在此一一详解。
四、Fragment 片段、Teleport、Suspense
(1)Fragment 片段
· Vue2.0中,vue template只允许有一个根节点。
· Vue3.0中,vue template支持多个根节点。
// vue2.0
<template>
<div>
<Headers></Headers>
<Main></Main>
<Footer></Footer>
</div>
</template>
复制代码
// Vue3.0
<template>
<Headers></Headers>
<Main></Main>
<Footer></Footer>
</template>
复制代码
(2)Teleport
Teleport 是一种能够将我们的模板渲染至指定 DOM 节点,不受父级 style 、v-show 等属性影响,但 data、prop 数据依旧能够共用的技术;类似于 React 的 Portal。
(3)Suspense
<Suspense>
是一个特殊的组件,它将呈现回退内容,而不是对于的组件,直到满足条件为止,这种情况通常是组件 setup 功能中,发生的异步操作或者是异步组件中使用。
使用场景:父组件展示的内容包含异步的子组件,异步的子组件需要一定的时间才可以加载并展示,这时就需要一个组件处理一些占位逻辑或者加载异常逻辑。
// vue2.x
<template>
<div>
<div v-if="!loading">
...
</div>
<div v-if="loading">Loading...</div>
</div>
</template>
复制代码
// vue3.x
<Suspense>
<template >
<Suspended-component />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
复制代码
五、自定义渲染 API
自定义渲染 API 将 Virtual DOM(虚拟DOM)和平台相关的渲染分离。
通过 createRendererAPI 我们可以自定义 Virtual DOM 渲染到某一平台中时的所有操作,比如新增、修改、删除一个“元素”,我们可以在这些方法中替换或修改为我们自定义的逻辑,从而打造一个我们自定义的渲染器。
利用这个 API,在 Vue3.0 中我们可以自由方便地去构建 Web(浏览器)平台或非 Web 平台的自定义渲染器。
在 Vue 中是利用 runtime-dom 方法提供的一个上层的抽象层,它帮我们完成了 Virtual DOM 渲染到 Web DOM 中的复杂浏览器接口编程操作。
通过编写自定义渲染器,极大丰富了 Vue 的使用场景。
六、源码优化
(1)使用 monorepo 来管理源码
· Vue.js 2.x 的源码托管在 src 目录,然后依据功能拆分 出了 compiler(模板编译的相关代码)、core(与平台无关的通用运行时代码)、platforms(平台专有代码)、server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码)等目录。
· Vue.js 3.0,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages 目录下面不同的子目录中,每个 package 有各自的 API、类型定义和测试。
(2)使用 Typescript 来开发源码
· Vue.js 2.x 选用 Flow 做类型检查,来避免一些因类型问题导致的错误,但是 Flow 对于一些复杂场景类型的检查,支持得并不好。
· Vue.js 3.0 抛弃了 Flow ,使用 TypeScript 重构了整个项目。TypeScript 提供了更好的类型检查,能支持复杂的类型推导;由于源码就使用 TypeScript 编写,也省去了单独维护 d.ts 文件的麻烦。
Vue3 面试题总结
Vue3.0 源码分析
· Vue.js 3.0 响应式系统的实现原理?
· 响应式是惰性的
vue3 新增 Composition API
在Vue3中,定义 methods
、watch
、computed
、data
数据 等都放在了 setup()
函数中
setup()
函数会在created()
生命周期之前执行。
执行顺序为:beforeCreate > setup > created
setup() 函数的第一个参数是 props,组件接收的 props 数据可以在 setup() 函数内访问到。
setup(props) {
console.log(props.p1)
}
复制代码
context 是 setup() 的第二个参数,它是一个上下文对象,可以通过 context 来访问Vue的实例 this 。注意:在 setup() 函数中访问不到Vue的 this 实例
setup(props, context) {
console.log(this)
console.log(context)
}
复制代码
reactive() 函数接收一个普通的对象,返回出一个响应式对象。在Vue2.x的版本中,我们只需在 data() 中定义一个数据就能将它变为响应式数据,在 Vue3.0 中,需要用 reactive 函数或者 ref 来创建响应式数据。
用reactive创建响应式对象
// 在组件库中引入reactive
import { reactive } from '@vue/composition-api'
setup() {
// 创建响应式对象
const state = reactive({
count:0
});
// 将响应式对象return出去,暴露给模板使用
return state;
}
// 使用响应式对象
<p>当前的count的值为:{{count}}</p>
<button @click="count++">点击增加count</button>
复制代码
用 ref 创建响应式对象
ref() 函数可以根据给定的值来创建一个响应式的数据对象,返回值是一个对象,且只包含一个 .value 属性。
// 引入 ref
import { ref } from '@vue/composition-api'
setup() {
// 创建响应式对象
const count = ref(0);
return {
count
}
}
// 使用响应式对象
<p>当前的count的值为:{{count}}</p>
<button @click="count++">点击增加count</button>
复制代码
ref 的注意事项:
在 setup() 函数内,由 ref() 创建的响应式数据返回的是对象,所以需要用 .value 来访问;
而在 setup() 函数外部则不需要 .value ,直接访问即可。
可以在 reactive 对象中访问 ref() 函数创建的响应式数据。
新的 ref() 会覆盖旧的 ref() 。
3. computed()
computed()
用来创建计算属性,返回值是一个 ref()
实例。按照惯例,使用前需要先引入。
-
computed创建只读计算属性
给
computed()
传入一个函数,可以得到一个只读的计算属性:
const count = ref(1)
// 创建一个计算属性,使其值比 count 大 1
const bigCount = computed(() => count.value + 1)
console.log(bigCount.value) // 输出 2
bigCount.value++ // error 不可写
复制代码
- computed创建可读可写计算属性
const count = ref(1)
// 创建一个 computed 计算属性,传入一个对象
const bigCount = computed({
// 取值函数
get: () => (count.value + 1),
// 赋值函数
set: val => {
count.value = val - 1
}
})
// 给计算属性赋值的操作,会触发 set 函数
bigCount.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 8
复制代码
4. readonly()
传入一个响应式对象、普通对象或 ref ,返回一个只读的对象代理。这个代理是深层次的,对象内部的数据也是只读的。
const state = reactive({ count: 0 })
const copy = readonly(state)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// state 上的修改会触发 copy 上的侦听
state.count++
// 这里只读属性不能被修改
copy.count++ // warning!
复制代码
5. watchEffect()
watchEffect()
会立即执行传入的函数,并响应式侦听其依赖,并在其依赖变更时重新运行该函数。
基本用法:
const count = ref(0)
// 初次直接执行,打印出 0
watchEffect(() => console.log(count.value))
setTimeout(() => {
// 被侦听的数据发生变化,触发函数打印出 1
count.value++
}, 1000)
复制代码
停止侦听:
watchEffect()
使用时返回一个函数,当执行这个返回的函数时,就停止侦听。
const stop = watchEffect(() => {
/* ... */
})
// 停止侦听
stop()
复制代码
6. watch()
composition-api 中的 watch 和 Vue2.x 中是一样的,watch 需要侦听数据,并执行它的侦听回调。默认情况下初次渲染不执行。
watch 与 watchEffect 的不同
watch 初次渲染不执行
watch 侦听的更具体
watch 可以访问侦听数据变化前后的值
watch 侦听单个数据源
侦听的数据可以是个 reactive 创建出的响应式数据(拥有返回值的 getter 函数),也可以是个 ref
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
复制代码
watch
侦听多个数据源
在侦听多个数据源时,把参数以数组的形式给 watch
watch([ref1, ref2], ([newRef1, newRef2], [prevRef1, prevRef2]) => {
/* ... */
})
复制代码
isRef()
isRef()
顾名思义,是用来判断某个值是否为 ref()
创建出来的响应式的值。
当你需要展开某个可能为 ref()
创建的响应式的值的时候,会用到它:
import { isRef } from '@vue/composition-api'
const unwrapper = isRef(foo) ? foo.value : foo
复制代码
toRefs()
toRefs()
可以将 reactive()
创建出来的响应式对象转换成内容为 ref 响应式的值的普通对象
在搞清楚 toRefs() 的用法之前,我们需要先了解一下用 reactive() 和 ref() 创建出来的响应式对象的区别:
用 reactive() 创建的响应式对象,整个对象是响应式的,而对象里的每一项都是普通的值。当你把它用展开运算符展开后,整个对象的普通值都不是响应式的;
而用 ref() 创建的响应式的值,本身就是响应式的,并不依赖于其他对象。
所以需要展开 reactive()
创建的响应式对象,又不想让他们失去响应式特点的时候,就需要用 toRefs()
将它进行转换:
import { toRefs } from '@vue/composition-api'
setup() {
// 定义响应式数据对象
const state = reactive({
count: 0
})
// 定义简单的函数,使count每次+1
const add = () => {
state.count++
}
// 将setup函数的内容return出去,供外界使用
return {
// 将 state 展开导出,同时将其属性都转化为 ref 形式的响应式数据
...toRefs(state),
add
}
}
<template>
<div>
<p>当前的count值为:{{count}}</p>
<button @click="add">点击+1</button>
</div>
</template>
复制代码
Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的 Options Api 有什么区别?
· vue3 新增内置组件 Teleport
· Props 初始化和更新流程改进
· Vue3 Slot内容分发
· Vue 3.0 在编译方面有哪些优化?
· Vue3 依赖注入子孙组件如何共享数据
· Vue3 侦听器实现原理与使用场景
· Vue3 组件实现原理核心源码解读
· Vuex 数据流管理方案
· 原生服务端渲染(SSR)的实现、同构开发
· Nuxt.js 集成式 SSR 框架
vue3.0实战应用
1.说说 Vue2.0 和 Vue3.0 有什么区别
2.Vue3 的新特性
3.基于 vite/webpack
4.实现 Vue3 工程化部署
5.掌握 setup 和 10 种响应式系统 API
6.掌握新生命周期函数
7.模板 refs 的使用
当使用组合式API时,reactive refs 和 template refs的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样在setup()中声明一个ref并返回它:
<template>
<div class="home" ref='root'>
我是hme元素
</div>
</template>
<script>
import {ref, onMounted} from 'vue';
export default {
setup(){
const root = ref(null);
onMounted(()=>{
console.log(root.value) // <div class="home"> 我是hme元素 </div>
})
return {
root,
}
}
}
</script>
复制代码
1.1 配合render函数
<template>
<div></div>
</template>
<script>
import { ref, h } from 'vue';
export default {
setup() {
const root = ref(null);
return () => h('div', {
ref: root,
style: "width:100px;height:100px;background:red;"
})
}
}
</script>
复制代码
1.2 使用JSX语法
<template>
<div></div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const root = ref(null);
return ()=><div ref = {root} style="width:100px;height:100px;background:blue;"/>
}
}
</script>
复制代码
1.3 ref在v-for中的使用
模板ref在v-for中使用vue,没有做特殊处理,需要使用函数型的ref来自定义处理方式:
<template>
<ul>
<li v-for="(item, i) in lists" :ref="el => { divs[i] = el }" :key="i">
{{item}}
</li>
</ul>
</template>
<script>
import {ref, reactive, onBeforeUpdate} from 'vue';
export default {
setup(){
const lists = reactive([1,2,3]);
const divs = ref([]);
// 确保在每次变更之前重置引用
onBeforeUpdate(()=>{
divs.value = [];
})
return {
lists,
divs
}
}
}
</script>
复制代码
8.Vue3 中的响应式系统和 dom-diff
作者:yuqifang
链接:https://juejin.cn/post/7028372909596885005
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。