龙空技术网

它山之石可以攻玉 -- Vue3优化面面观

前端Jerry 118

前言:

此刻各位老铁们对“vue子组件修改父组件传来的值并回传”大概比较着重,同学们都想要学习一些“vue子组件修改父组件传来的值并回传”的相关知识。那么小编同时在网上汇集了一些对于“vue子组件修改父组件传来的值并回传””的相关文章,希望看官们能喜欢,大家快快来学习一下吧!

前言:Vue3给我们带来了全新的开发体验和效率提升, 那么,我们怎么充分利用好这个"神器"呢?这是本篇文章要解决的问题。我们将从四个方面一起来探讨这个话题。

相关背景:vue的前世今生

从vue1.x到vue2.x, 引入了虚拟Dom, 为后续服务端渲染和宽度按框架weex提供了基础前提。

从vue2.x开始,作者发现了vue的一系列痛点:

源码问题:源码自身的可维护性问题性能问题:数据量增大带来的渲染和更新的性能问题兼容性问题:为了兼容一直保留的鸡肋API

因此,vue3作者希望做一系列改进:

更好的编程体验更好的TypeScript支持更好的逻辑复用实践更好的性能和更小的体积一、框架优化

充分用好一个框架,了解框架层面所做的优化事项和设计亮点必不可少

1、性能优化a、 使Vue更小的Tree Shaking支持 -- Vue3 和 Vite 默认支持 Tree-shaking,没有使用到的api,在打包时直接移除掉了。我们也可以通过将无用代码标记为"sideEffects: false"来帮助 Tree-shaking 优化。

例如,如果你没有使用 过渡(transition)元素,则不会包含它。

同时,我们通过在 package.json 中配置 "sideEffects": [" .css", " .less"] 来标记 css 和 less 文件为“pure”代码。这样在打包时,就可以将没有使用到的 css 和 less 文件删除掉。

简要原理:- tree-shaking 依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。像我们在项目中没有引入 Transition、KeepAlive 等不常用的组件,那么它们对应的代码就不会打包进去。

b、 移除了一些冷门的featureVue.js 3.0 兼容了 Vue.js 2.x 绝大部分的api,但还是移除了一些比较冷门的feature:如 keyCode 支持作为 v-on 的修饰符、on,on,on,off 和 $once 实例方法、filter过滤、内联模板等。c、静态树提升(Static Tree Hoisting)

使用静态树提升,这意味着Vue3的编译器将能够检测到什么是静态组件,然后将其提升,从而降低了渲染成本。它将能够跳过为整个树结构打补丁的过程。

它通过在编译时分析模板,将静态节点提取出来缓存起来,从而减少了组件的渲染次数和运行时间。

举个例子,我们可以看一下下面这个组件的模板:

<template>  <div class="wrapper">    <h1>{{ title }}</h1>    <ul>      <li v-for="item in items" :key="item.id">{{ item.name }}</li>    </ul>  </div></template>

在 Vue 2 中,每次渲染都需要重新创建 h1 标签和 li 标签,即使它们没有发生变化。但是在 Vue 3 中,只会创建一次,然后缓存起来,大大提升了渲染效率。

d. Hoist Static(静态节点/属性提升):Vue3通过将静态节点提取出来,从而跳过不会改变节点的打补丁过程,减少了渲染次数。2、基于 Proxy 的观察者机制

Object.defineProperty是一个相对比较昂贵的操作,因为它直接操作对象的属性,颗粒度比较小。嵌套层级比较深的对象,官方会进行层次遍历进行defineProperty,如果对象层级深,就会有较大的性能负担。同时,使用Object.defineProperty进行拦截,首先需要知道拦截的Key是什么,所以并不能检测对象属性的添加和删除,尽管官方给出来.set和.set 和 .set和.delete的实例方法,但是还是增加了心智负担。同时需要注意的是,这种拦截方式不能检测到数组的变化,通过重写数组api来实现响应式 *

Vue3.x 使用了Proxy API 做数据劫持。因为检测的目标是对象,所以有效的解决了Object.defineProperty来实现响应式的缺陷。由于代理的是对象而不是对象的属性,这样可以将原本对对象属性的操作变为对整个对象的操作, 颗粒度变大, 意味着性能有一定提升.

值得注意的是,在没有访问的时候,Proxy API 并不能监听到内部深层次的对象变化,因为Vue.js3.x的处理方式是在getter中去递归响应式,有访问到就递归,否则不递归响应式。这样的好处是,真正访问到的内部对象才会变成响应式,而不是无脑递归,简单的可以说是按需实现响应式/惰性实现响应式,这无疑在很大程度上提升了性能。

值得注意的是:javascript引擎在解析的时候希望对象的结构越稳定越好,如果对象一直在变,可优化性降低,proxy不需要对原始对象做太多操作。

3、源码优化a.使用monorepo来管理源码Vue.js 2.x 的源码托管在 src 目录,然后依据功能拆分出了 compiler(模板编译的相关代码)、core(与平台无关的通用运行时代码)、platforms(平台专有代码)、server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码)等目录。Vue.js 3.0,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages 目录下面不同的子目录中,每个 package 有各自的 API、类型定义和测试。b.使用Typescript来开发源码Vue.js 2.x 选用 Flow 做类型检查,来避免一些因类型问题导致的错误,但是 Flow 对于一些复杂场景类型的检查,支持得并不好。Vue.js 3.0 抛弃了 Flow ,使用 TypeScript 重构了整个项目。 TypeScript 提供了更好的类型检查,能支持复杂的类型推导;由于源码就使用 TypeScript 编写,也省去了单独维护 d.ts 文件的麻烦。4、编译优化a.生成block treeVue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部需要遍历该组件的整个 vnode 树。Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。Block tree 是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的。每个区块只需要追踪自身包含的动态节点。b.slot编译优化Vue.js 2.x 中,如果有一个组件传入了slot,那么每次父组件更新的时候,会强制使子组件update,造成性能的浪费。Vue.js 3.0 优化了slot的生成,使得非动态slot中属性的更新只会触发子组件的更新。动态slot指的是在slot上面使用v-if,v-for,动态slot名字等会导致slot产生运行时动态变化但是又无法被子组件track的操作。

最终结果是: vue3中通过对插槽生成的优化,父子组件可以分开渲染。而在Vue2.x中,当父组件重新渲染时,其子组件也必须重新渲染。

c.diff算法优化

在运行时重写了diff算法

关于diff算法优化以及 hoistStatic 静态提升、### cacheHandlers 事件监听缓存、SSR 渲染,**可以参考# Vue3实践指南二 —— Vue3性能提升分析

关于编译优化,可以参考# Vue3 是如何通过编译优化提升框架性能的?

以及# 你不知道的 Vue3 优化。

5、语法api优化

a.优化逻辑组织

使用 Vue.js 2.x 编写组件本质就是在编写一个“包含了描述组件选项的对象”,可以把它称为 Options API。我们按照 data、props、methods、computed 这些不同的选项来书写对应的代码。这种方式对于小型的组件可能代码还能一目了然,但对于大型组件要修改一个逻辑点,可能就需要在单个文件中不断上下切换和寻找逻辑代码。Vue.js 3.0 提供了一种新的 API:Composition API,它有一个很好的机制去解决这样的问题,就是将某个逻辑关注点相关的代码全都放在一个函数里,这样在修改一个逻辑时,只需要改那一块的代码了。

b.优化逻辑复用

在 Vue.js 2.x 中,我们一般会用 mixins 去复用逻辑。当抽离并引用了大量的mixins,你就会发现两个不可避免的问题:命名冲突和数据来源不清晰。Vue.js 3.0 设计的 Composition API,在逻辑复用方面就会很有优势了。

Vue.js 2.x =》mixins去复用逻辑,当一个组件混入大量mixins时

没有mixins都是可以定义自己的props、data,它们直接是无感的,所以很容易定义相同的变量,导致命令冲突 对于组件而言,如果模板中使用不在当前组件中定义的变量,那么就会不太容易知道这些变量在哪里定义的,这就是数据来源不清晰 Vue3.x Composition API 是类似hooks的使用方式,所以天然的有逻辑复用方面的优势,同时除了在 逻辑复用 方面有优势,也会有更好的类型支持

因为它们都是一些函数,在调用函数时,自然所以的类型就是被推到出来了,不像Options API 所以的东西使用 this 另外,Composition API 对 tree-shaking友好,代码也更容易压缩 Vue3.x Composition API属于API的增强,并不属于Vue3.0的范式,当组件很简单时,也可以使用Options API。

6、其它性能优化

1、Cache Handler(事件处理函数缓存) 将事件处理函数缓存起来,避免了组件渲染时重复创建函数的开销。

2、SSR-friendly(更友好的服务端渲染) Vue 3 在设计时就考虑了服务端渲染的需求,使其更加友好,并提供了相应的 API。

3、重写虚拟DOM (Virtual DOM Rewrite) vue3.0将 vdom 更新性能由与模版整体大小相关提升为与动态内容的数量相关

7、引入RFC:是每个版本改动可控大规模启用RFC( Request For Comments)了解每一个feature采用或被废掉的前因后果二、手动优化1、使用异步组件进行异步加载例如: const Foo = defineAsyncComponent(() => import('./Foo.vue'))2、通过对props传参的控制避免无必要的子组件更新。当传递的props值进行更新的时候,子组件就会进行更新,如下:

<ListItem  v-for="item in list"  :id="item.id"  :active-id="activeId" />
这时当每次activeId每次更新的时候,子组件都会全部进行更新,此时我们应该加一个判断
<ListItem  v-for="item in list"  :id="item.id"  :active="item.id === activeId" />
只有当activeId等于item.id的时候,再进行更新3、合理使用v-once,v-memo进行优化,可以参考# Vue v-memo 指令的使用与源码解析4、渲染大型的列表的时候,可以使用虚拟列表进行优化,可以参考#vue3官网中最佳优化实践部分的相关介绍5、合理使用响应式的api进行声明变量如果不是页面上需要进行视图更新的,我们可以不用reactive,ref更进行声明,可以使用shallowRef() 和 shallowReactive() 浅层式响应进行声明(浅层式顶部是响应的,底部都不是响应数据)6、合理使用缓存和keep-alive减少不必要的数据请求和性能开销。

keep-Alive组件可用于缓存组件的状态。这可以提高频繁切换开关的组件的性能。

参考

# Vue3中的keep-alive函数详解:优化应用性能的应用

# Vue3中的keep-alive函数详解:优化应用性能

7、第三方库/ui框架/插件等通过按需引入进行使用,这同时也更有利于tree-shaking进行工作。8、打包时开启gzip压缩,公共的样式,js文件等放到src下的assets会进行压缩

*gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。

html、js、css文件甚至json数据都可以用它压缩,可以减小60%以上的体积。*

1、npm i -D compression-webpack-plugin2、在vue.config.js中配置

javascript复制代码const CompressionPlugin = require('compression-webpack-plugin')configureWebpack: config => {    if (process.env.NODE_ENV === 'production') {        return {            plugins: [new CompressionPlugin({                test: /.js$|.html$|.css/,                threshold: 10240,                deleteOriginalAssets: false            })]        }    }}
3、在NGINX中配置
ini复制代码http {    gzip  on;    gzip_min_length    1k;    gzip_buffers        4 8k;    gzip_http_version  1.0;    gzip_comp_level    8;    gzip_proxied        any;    gzip_types          application/javascript text/css image/gif;    gzip_vary          on;    gzip_disable        "MSIE [1-6].";    #以下的配置省略...}

nginx -s reload :修改配置后重新加载生效

9、图片的优化:图片压缩、懒加载、使用cdn托管减少使用本地图片(不过这个取决于网络,可能没有本地图片加载的快)等10、合理使用cdn引入插件(有的插件可以使用cdn进行引入,不过这个也是有风险的,有时候插件进行更新,你就需要替换版本,否则会导致项目有问题)11、在我们使用vue框架开发的时候,一些指令的合理使用v-for跟v-if的使用,现在v3中v-if的优先级大于v-forv-show和v-if的使用v-for需要搭配key的使用,在diff算法更新的时候,可以更快速的只更新需要更新的数据,而不是无脑的全部进行更新computed和watch、watchEffect的合理运用使用监听/定时器的时候,在页面卸载的时候进行移除监听/取消定时器将公共的样式,js等进行提取合理的使用组件(组件的渲染要比dom渲染消耗的性能要大一点)12、路由使用懒加载和魔法注释拆分chunk component:()=>import('../views/home.vue')13、代码分割:Vue3 的动态组件和异步组件可以帮助我们进行代码分割,Vue3 会自动将异步组件打包成单独的文件,减少首次加载的大小。

例如,我们可以将需要按需加载的动态组件单独打包成一个文件,示例代码如下:

import { defineAsyncComponent } from 'vue';const PidanCode = defineAsyncComponent(() => import(/* webpackChunkName: "pidancode" */ './PidanCode.vue'));export default {  components: {    PidanCode,  },};

在上面的代码中,我们通过 defineAsyncComponent 方法将 PidanCode 组件单独打包,并设置为按需加载。这样当我们需要使用 PidanCode 组件时,才会去加载对应组件代码。这样可以减少不必要的加载,提高应用性能。

14、尽量使用模板编译:Vue 3支持运行时和模板编译。模板编译更快,应尽可能使用。15、js最小拆包配置vite.config 的 output 属性

  output: {    // 最小化拆分包    manualChunks(id) {      if (id.includes('node_modules')) {        return id.toString().split('node_modules/')[1].split('/')[0].toString();      }    }  },
让打开那个页面,加载那个页面的js ,让之间的关联足够小按需加载 js 每个页面的 js 逻辑16、ui组件库、vue子包的优化ui 组件库 是打包变大的 显著的原因之一 ,可能全量导入或批量导入了ui组件(去页面一次性加载了)如果只用到 ui 组件库的部分 ,如何做到用多少,打包多少 ?下面推荐一个 vite 的插件 (以 element Plus ,Antd of vue ,最常用的2中ui库来举例)

① 安装:

	npm  i  -D unplugin-vue-components

② main.js => 无需配置

main.js 不需要在 import 组件,再去use 绑定了 【注意:否则包变大,反而无效】直接用,用多少打包多少

③ 只需配置 => vite.config

import Components from 'unplugin-vue-components/vite' // 按需加载自定义组件import { ElementPlusResolver, AntDesignVueResolver} from 'unplugin-vue-components/resolvers'export default defineConfig {  // ...  plugins: [    // 按需引入    Components({      dts: true,      dirs: ['src/components'], // 按需加载的文件夹      resolvers: [          ElementPlusResolver(),  // Antd   按需加载          AntDesignVueResolver()  // ElementPlus按需加载     ]     })  ],  // ..................................}
dirs属性 => 设置 按需加载的文件夹 如 src/components ;该文件夹 的组件 不需要 import 按命名就可全局引入;ElementPlusResolver() 、 AntDesignVueResolver() 分别为element Plus 和 Antd 按需导入方法;同理vue子包也可以通过此方式进行优化。17、防抖节流

可以用Vue Use这个插件库。

18、打包文件中去掉map文件

打包的app.js过大,另外还有一些生成的map文件。先将多余的map文件去掉,

找到config文件夹下index.js文件,把这个build里面的productionSourceMap改成false即可

19、关于层级较深的嵌套数据对象的响应式优化:

**可以参考# 揭秘,Vue3 性能优化之 Non-reactive Object

简单来说就是让不需要进行响应式绑定的某个属性的数据类型的值设置为不在此['Object', 'Array', 'Map', 'Set', 'WeakMap', 'WeakSet']数组范围内即可,其中,判断数据类型的方法为Object.prototype.toString.call(target).slice(8, -1)。

或者满足!Object.isExtensible(value)成立即可。

20、如果需要使用 v-for 给每项元素绑定事件时使用事件代理

21、特别地,对于首屏优化:

请求优化: CDN 将第三方的类库放到 CDN 上,能够大幅度减少生产环境中的项目体积,另外 CDN 能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。

缓存:将长时间不会改变的第三方类库或者静态资源设置为强缓存,将 max-age 设置为一个非常长的时间,再将访问路径加上哈希达到哈希值变了以后保证获取到最新资源,好的缓存策略有助于减轻服务器的压力,并且显著的提升用户的体验

gzip:开启 gzip 压缩,通常开启 gzip 压缩能够有效的缩小传输资源的大小。

http2:如果系统首屏同一时间需要加载的静态资源非常多,但是浏览器对同域名的 tcp 连接数量是有限制的(chrome 为 6 个)超过规定数量的 tcp 连接,则必须要等到之前的请求收到响应后才能继续发送,而 http2 则可以在多个 tcp 连接中并发多个请求没有限制,在一些网络较差的环境开启 http2 性能提升尤为明显。

懒加载:当 url 匹配到相应的路径时,通过 import 动态加载页面组件,这样首屏的代码量会大幅减少,webpack 会把动态加载的页面组件分离成单独的一个 chunk.js 文件

预渲染:由于浏览器在渲染出页面之前,需要先加载和解析相应的 html、css 和 js 文件,为此会有一段白屏的时间,可以添加 loading,或者骨架屏幕尽可能的减少白屏对用户的影响体积优化

合理使用第三方库: 对于一些第三方 ui 框架、类库,尽量使用按需加载,减少打包体积

使用可视化工具分析打包后的模块体积:webpack-bundle- analyzer 这个插件在每次打包后能够更加直观的分析打包后模块的体积,再对其中比较大的模块进行优化

提高代码使用率: 利用代码分割,将脚本中无需立即调用的代码在代码构建时转变为异步加载的过程

封装:构建良好的项目架构,按照项目需求就行全局组件,插件,过滤器,指令,utils 等做一些公共封装,可以有效减少我们的代码量,而且更容易维护资源优化

图片懒加载: 使用图片懒加载可以优化同一时间减少 http 请求开销,避免显示图片导致的画面抖动,提高用户体验

使用 svg 图标: 相对于用一张图片来表示图标,svg 拥有更好的图片质量,体积更小,并且不需要开启额外的 http 请求

压缩图片: 可以使用 image-webpack-loader,在用户肉眼分辨不清的情况下一定程度上压缩图片

三、生态优化构建优化

1、将使用到的第三方库通过动态链接库进行拆分

2、代码压缩

3、使用缓存

4、对第三方库进行合理引入

5、多进程打包,happypack 或者 其它插件。或者使用 web worker进行多进程开发和构建。

使用web worker实现

MDN-webwork

Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。

worker脚本与主进程脚本,要遵守同源协议,他们的域名,端口号,协议要相同。

// 生成一个专用 workervar myWorker = new Worker('worker.js');//你可以通过postMessage() 方法和onmessage事件处理函数触发 workers 的方法。当你想要向一个 worker 发送消息时,你只需要这样做(main.js):first.onchange = function() {  myWorker.postMessage([first.value,second.value]);  console.log('Message posted to worker');}second.onchange = function() {  myWorker.postMessage([first.value,second.value]);  console.log('Message posted to worker');}

这段代码中变量 first 和 second 代表 2 个元素;它们当中任意一个的值发生改变时,myWorker.postMessage([first.value,second.value]) 会将这 2 个值组成数组发送给 worker。你可以在消息中发送许多你想发送的东西。

// 在 worker 中接收到消息后,我们可以写这样一个事件处理函数代码作为响应(worker.js):onmessage = function(e) {  console.log('Message received from main script');  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);  console.log('Posting message back to main script');  postMessage(workerResult);}

onmessage 处理函数允许我们在任何时刻,一旦接收到消息就可以执行一些代码,代码中消息本身作为事件的 data 属性进行使用。这里我们简单的对这 2 个数字作乘法处理并再次使用 postMessage() 方法,将结果回传给主线程。

// 回到主线程,我们再次使用 onmessage 以响应 worker 回传的消息:myWorker.onmessage = function(e) {  result.textContent = e.data;  console.log('Message received from worker');}

终止 web worker

myWorker.terminate(); // 而在 worker 线程中,workers 也可以调用自己的 close 方法进行关闭: close();

...

可以参考这篇文章#vue3.0项目构建优化与性能优化

vite配置优化参考:

// vite config.js build:{    // 是否拆分css,    cssCodeSplit:true,    // 不生成sourcemap    sourcemap:false,        // minify: 两种模式 1. terser打包体积最小  2.  打包速度快    minify:"esbuild",    // assetsInlineLimit:4000 意思是如果小于4000 就将它编译为base64的文件    assetsInlineLimit:4000  },
脚手架升级

Vue CLI 4 是一个全新的构建工具,它基于 Vue 3 构建而成,并提供了一些新的特性,包括:

更快的构建速度和更小的输出大小。对 Vue 3 和 TypeScript 的原生支持。提供了可选的 GUI 界面,使得创建和管理项目变得更加容易。四、优化指标/工具1、优化指标

页面加载性能

首次访问的时候,应用展示出内容,并且达到可交互的状态。这个一般会用谷歌定义出来的一系列web指标去衡量。

常见的有:

First Contentful Paint (FCP)首次内容绘制的时间,浏览器第一次绘制dom相关的内容。也是用户第一次看到页面内容的时间。

Speed Index:页面各个可见部分的显示平均时间当我们存在轮播图或者需要从后端获取内容加载时,这个内容会被影响到。

Largest Contentful Paint:页面中最大内容的绘制时间,页面最大的元素绘制时间

Time to Interactive : 从页面开始渲染到用户可以交互的时间,内容必须完成渲染,交互元素绑定的事件已经注册完成。

Total Blocking Time :记录了用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。

Cumulative Layout Shift :计算布局偏移量得分,会比较两次渲染帧的偏移情况,可能会导致用户像点击A按钮,但下一帧中A按钮,被挤到旁边,导致用户实际点击了B按钮。

此外,还有最大内容绘制(LCP),首次输入延迟(FID),首次内容绘制(FCP)等等。

更新性能

应用响应用户输入更新的速度。比如当用户在搜索框中输入时结果列表的更新速度。或者用户在一个单页面应用(SPA)中点击链条跳转页面的切换速度。

理想情况

最理想的是将两者都最大化,但是不同的前端架构往往会影响到这些方面是否能达到更理想的性能。

题外话,你构建的应用的类型会极大的影响你在性能方面优先考虑的问题,所以性能优化第一步是选择合适的框架(vue或者react等)。

2、分析工具

为了提高性能,我们要先知道怎么衡量,有个标准。我们可以用一些工具来做。

比如这个网站:pagespeed.web.dev/ 你输入网址之后它会帮你分析网站的性能。

也可以使用谷歌浏览器自带工具**lighthouse、performance(将vue的app.config.performance设置为true,将会开启vue特有的性能标记。标记在chrome开发者工具的性能时间线上进行查看分析)**等

五、性能优化的目标

通过对性能优化的目标进行拆解,从而寻找最佳解决路径进行对应性能优化

Vue3应用程序性能优化的目标可以从以下几个方面进行考虑:

提高页面加载速度,减少页面渲染时间;降低CPU和内存的占用率,提高系统资源利用率;减少HTTP请求次数,提高数据传输速度;加快数据处理速度,提高用户交互效果;提高代码的可维护性和可扩展性。总结:

在项目开发过程和面试过程中,性能优化是个亘古不变的主题,同时项目优化和项目的框架选型与架构设计也有着千丝万缕的联系。那么,在框架不断地进行更新迭代的同时,我们的思维和眼界也需要做到与时俱进、开拓创新、不断进取。抛砖引玉,希望给大家带来一些灵感和共鸣!

标签: #vue子组件修改父组件传来的值并回传