vue3新特性概览

Vue自3.0 beta发布以来,随着相关技术支持度增强,越来越多的企业开始使用vue3创建项目
下面大概了解一下vue3的新特性

Vue3.0新特性
一、节点打Tag
什么意思呢?
就是
真实DOM的渲染会引起整个DOM树的重排重绘,会造成非常大的开销。因此,vue采用了Virtual DOM(虚拟DOM),进行局部更新。

虚拟DOM是将真实DOM数据抽取出来,以对象的形式模拟树形结构。

在更新节点的过程中采用了diff算法。 diff的过程就是调用patch函数,比较新旧节点,一边比对一边给真实DOM打补丁。

如何进行diff比较:
diff的比较方式
在采用diff算法比较新旧节点时,比较只会在同层级进行,不会跨层级比较。
diff算法在执行时有三个维度:Tree DIFF、Component DIFF、Element DIFF。

vue在渲染的时候
vue3.0底层,会自动识别某个节点是否是动态的,如果是动态的会自动生成标识(不同的动态会有不同的标识对应,如内容文本的动态,或者id的动态),从而在每次更新dom时,直接跳过哪些静态的节点,直接定位到动态的节点,大大节省效率。

模版 template 经过 parse 过程后,会输出生成AST树,之后就是优化阶段【 optimize】,优化的原因是 Vue是数据驱动,响应式的,但!!我们模版中很多数据都是静态节点,非响应式的,也有数据是首次渲染后不会发生变化了的,那这些数据 如果在匹配(patch) 过程中跳过,那对比是不是就节省时间了呢?下面看看vue3如何做的。

首先是找到 src/compiler/optimizer.js 文件。然后找到对应的代码片段
vue3新特性概览_第1张图片

其实就是做 标记静态节点【markStatic 】和标记静态根节点【 markStaticRoots 】
如何标记的呢?
首先会执行 isStatic 方法,判断AST是否是静态的,包含表达式、v-if、v-for等其他指令都是非静态的。如果是普通元素、纯文本元素、v-pre、v-once等(自己意会去吧)都是静态节点。

如果递归遍历父节点中所有子节点的 markStatic 静态属性的话发现全部子节点都是静态的,则在父节点标记 node.staticRoot = true 静态根节点

genElement 以下就是 genElement 代码片段

genStatic的方法,如果是全 静态的根节点
vue3新特性概览_第2张图片
直接返回,不用做那么多的优化指令等条件分析,是不是很快。那么有的同学会问针对于static节点没有执行流程吗?其实只要有子节点children 和子节点都是static状态下才会标记rootStatic 为 true 所以static只是辅助标记的对象,真正做判断的就是每一个有子节点(就一个文本除外)都是静态的,那么 才会有当前节点为 rootStatic = true并且codeGen阶段很快很顺利的生成render函数。好了,关于静态节点这些知识点分析到这里。

静态节点

<span>valuespan>

value

动态节点

<span>{{value}}span>
patchFlag
<span>{{value}}span>
patchFlag
总结就是
重构了Virtual DOM
在传统Virtual DOM的数据改变渲染上,在数据变更之后,新的Virtual DOM进行patch算法比较,
匹对出新旧DOM之间的差异,再将差异进行修改。
传统Virtual DOM性能跟模板大小呈正关系,而与动态节点数量无关,
模板(组件)越大,那么进行数据更新时损耗的性能就有多大;
这种方式利用率低,数据更新时,要对比整个模板(组件),存在性能损耗。
   Vue3.0 Virtual DOM是采用动静结合的方式,将模板基于动态指令切割为嵌套的区块,
   各个区块之间是相对静止的,找到动态变化的部分,只需对比变化的部分,减少了性能的损耗。

其他开发者具体描述

二、事件开缓存
什么意思?
在Vue2.0中,针对绑定事件@click=“onClick”,每次触发都要重新生成全新的function 更新onClick;

    <div class="toolkit" @click="onClick">toolkitdiv>

默认情况下@click会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没必要去追踪它的变化,想办法将它直接缓存起来复用就可以提升性能。

在Vue3.0中,提供了事件缓存对象cacheHandlers,当cacheHandlers开启的时候,编译会自动生成一个内联函数,将其变成一个静态节点,当事件再次触发时,就无需重新创建函数直接调用缓存的事件回调方法即可。
在这里插入图片描述
上图就可以看到,onClick果然会被视为动态绑定,它有动态标记,查看刚才提到的目录中发现,8是动态属性。开启cacheHandlers后,动态标记就不存在了,那么这部分内容也就不会进行比较了。

总结:
一般为一个节点设置了监听事件,每次页面进行更新,就会重新生成新的监听函数,启用了cacheHandlers,就会在第一次更新的时候进行自动识别是否可以缓存,如果可以就进行缓存,这样页面更新就不需要重新生成,尤其是在组件上,极大地减少了子组件的不必要刷新和资源消耗。

三、响应式Proxy [Proxy(vue3.0) vs Object.defineProperty (vue2.0)]
Vue2.0响应式原理是运用Object.defineProperty
Object.defineProperty不足之处:

1.在Vue中,无法监控到数组下标变化,当通过数组下标对数组进行操作时,不能实时响应,具有局限性;
2.只能劫持对象的属性,如果想获取一个完整的对象还需要通过递归以及遍历数据对象来实现数据的监控。
vue3中使用的Proxy,可以劫持整个对象,并返回一个新的对象,并且具有13种劫持操作:
可参MDN

Object.defineProperty()

响应化过程需要遍历data,props等,消耗较大
不支持Set/Map、class、数组等类型
新加或删除属性无法监听
数组响应化需要额外实现
对应的修改语法有限制

Proxy API(代理:代理模式);
Proxy对于复杂的数据结构减少了循环递归的监听,初始渲染循环递归是非常耗性能的;
Proxy对于数组的变异方法(会修改原数组),不再需要单独用数组原生方法重写、处理;
语法比defineProperty简洁,直接监听属性

四、Composition API
Composition译过来是组合、结构化的意思
Composition函数式开发,很大程度地提高为了组件、业务逻辑的复用性;
高度解耦,提高代码质量、开发效率;减少代码体积。

但是为何Vue3兼容了vue2的写法呢?
原意就是:不要为了Composition而Composition
不同的开发者能力不同,好的开发者提高了代码质量的上限,新开发者也降低了代码质量下限
所以它被定位为高级特性,因为它旨在解决的问题主要出现在大型应用程序中。
当初尤大佬就说我们不打算彻底修改文档来把它用作默认方案。但是因为追新,已经错失了本来的想法,而很多人把它当作默认方案

五、Teleport(传送门)
中文译为:传入
Teleport 是什么?它是干啥的?

Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像LOL中的技能TP

场景:像Modal、Dialog、Select、Dropdown…,toast 等这样的元素,很多情况下,我们将它完全的和我们的 Vue 应用的 DOM 完全剥离,管理起来反而会方便容易很多【如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难】
vue3新特性概览_第3张图片

六、一个 Vue 模板可以有多个根节点(Fragments)?
Vue 3中,我们可以期待的另一个令人兴奋的补充是Fragment,翻译过来是碎片。
如果你创建一个Vue组件,那么它只能有一个根节点。

这意味着不能创建这样的组件:

<template>
  <div>Hello <div>
  <div>World <div>
<template>

原因是代表任何Vue组件的Vue实例需要绑定到一个单一的DOM元素中。唯一可以创建一个具有多个DOM节点的组件的方法就是创建一个没有底层Vue实例的功能组件。

结果发现React社区也遇到了同样的问题。他们想出的解决方案是一个名为 Fragment 的虚拟元素。它看起来差不多是这样的:

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello <td>
        <td>World <td>
      <React.Fragment>
    );
  }
}

尽管Fragment看起来像一个普通的DOM元素,但它是虚拟的,根本不会在DOM树中呈现。这样我们可以将组件功能绑定到一个单一的元素中,而不需要创建一个多余的DOM节点。

目前你可以在Vue 2中使用vue-fragments库来使用Fragments,而在Vue 3中,你将会在开箱即用!

vue3新特性概览_第4张图片

template中不需要用一个div包裹即没必要只有一个根节点,可以多个标签(节点)并列

七、Custom Renderer API ( createRenderer )
Custom Renderer API是什么?
开放了 自定义 render 函数的 API
有啥用?
vue官方实现的 createApp 会给我们的 template 映射生成 html 代码,但是要是你不想渲染生成到 html ,而是要渲染生成到 canvas 之类的不是html的代码的时候,那就需要用到 Custom Renderer API 来定义自己的 render 渲染生成函数了。

八、Tree Shaking
Vue3最重要的变化之一就是引入了Tree-Shaking,Tree-Shaking带来的bundle体积更小是显而易见的。在2.x版本中,很多函数都挂载在全局Vue对象上,比如nextTick、set等函数,因此虽然我们可能用不到,但打包时只要引入了vue这些全局函数仍然会打包进bundle中。

而在Vue3中,所有的API都通过ES6模块化的方式引入,这样就能让webpack或rollup等打包工具在打包时对没有用到API进行剔除,最小化bundle体积;我们在main.js中就能发现这样的变化:

//src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
 
const app = createApp(App);
app.use(router).mount("#app");

创建app实例的方式,从原来的new Vue(),变为通过createApp函数进行创建;
不过一些核心的功能:比如virtualDOM更新算法和响应式系统无论如何都是会被打包的;
这样带来的变化就是以前在全局配置的组件(Vue.component)、指令(Vue.directive)、混入(Vue.mixin)和插件(Vue.use)等变为直接挂载在实例上的方法;
我们通过创建的实例来调用,带来的好处就是一个应用可以有多个Vue实例,
不同实例之间的配置也不会相互影响:

const app = createApp(App)
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

因此Vue2.x的以下全局API也需要改为ES6模块化引入:

Vue.nextTick
Vue.observable不再支持,改为reactive
Vue.version
Vue.compile (仅全构建)
Vue.set (仅兼容构建)
Vue.delete (仅兼容构建)
除此之外,vuex和vue-router也都使用了Tree-Shaking进行了改进,不过api的语法改动不大:
按需加载


//src/store/index.js
import { createStore } from "vuex";
 
export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {},
});
//src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
 
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

除了保留一些必须的部分如:Virtual DOM更新算法、响应式系统,其他都是按需加载例如v-model等。

九、Suspense
来自React生态系统的另一个伟大的想法将在Vue 3中采用,那就是Suspense组件。

Suspense会暂停你的组件渲染,并重现一个回落组件,直到满足一个条件。在Vue London期间,Evan You简单地触及了这个话题,并展示了我们或多或少可以期待的API。事实证明,Suspense将只是一个具有插槽的组件:

                            loading...

在Suspended-component完全渲染之前,备用内容会被显示出来。如果是异步组件,Suspense可以等待组件被下载,或者在设置函数中执行一些异步操作,等待异步的组件渲染,等所有的组件渲染完成之后,才会渲染到界面DOM上去。
十、生命周期函数
我们都知道,在Vue2.x中有8个生命周期函数:
beforeCreate----->created
beforeMount----->mounted
beforeUpdate----->updated
beforeDestroy----->destroyed

在vue3中,新增了一个setup生命周期函数
setup执行的时机是在beforeCreate生命函数之前执行,
因此在这个函数中是不能通过this来获取实例的;
同时为了命名的统一,将beforeDestroy改名为beforeUnmount,destroyed改名为unmounted,因此vue3有以下生命周期函数:

beforeCreate(建议使用setup代替)
created(建议使用setup代替)
setup
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted

同时,vue3新增了生命周期钩子,我们可以通过在生命周期函数前加on来访问组件的生命周期,我们可以使用以下生命周期钩子:
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
onErrorCaptured
onRenderTracked
onRenderTriggered
那么这些钩子函数如何来进行调用呢?我们在setup中挂载生命周期钩子,当执行到对应的生命周期时,就调用对应的钩子函数:

import { onBeforeMount, onMounted } from "vue";
export default {
  setup() {
    console.log("----setup----");
    onBeforeMount(() => {
      // beforeMount代码执行
    });
    onMounted(() => {
      // mounted代码执行
    });
  },
}

https://blog.csdn.net/qq_27318177/article/details/119170748

https://blog.csdn.net/weixin_39554021/article/details/111216928

十1、Better TypeScript Support
Codebase written in TS w/ auto-generated type definitions
API is the same in JS and TS
In fact, code will alse be largely the same
TSX support
Class component is still supported

细节踩坑

事件
vue3新特性概览_第5张图片

默认自动挂载根节点、废弃xxx.native事件
vue2.x 语法

<my-component
  v-on:close="handleComponentEvent"
  v-on:click.native="handleNativeClickEvent"
/>

默认情况下,传递给带有 v-on 的组件的事件监听器只能通过 this.$emit 触发。要将原生 DOM 监听器添加到子组件的根元素中,可以使用 .native 修饰符:

vue3.x 语法

<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>
MyComponent.vue

<script>
  export default {
    emits: ['close']
  }
</script>

v-on 的 .native 修饰符已被移除。同时,新增的 emits 选项允许子组件定义真正会被触发的事件。

因此,对于子组件中未被定义为组件触发的所有事件监听器,Vue 现在将把它们作为原生事件监听器添加到子组件的根元素中 (除非在子组件的选项中设置了 inheritAttrs: false
[class, style, events, css scope])。
迁移策略
删除 .native 修饰符的所有实例。
确保所有组件都使用 emits 选项记录其事件。

你可能感兴趣的:(vue.js,javascript)