Appearance
性能优化
性能优化的两个主要方面
- 页面加载性能:首次访问时,应用展示出内容与达到可交互状态的速度。
- 更新性能:应用响应用户输入更新的速度。比如当用户在搜索框中输入时结果列表的更新速度,或者用户在一个单页面应用 (SPA) 中点击链接跳转页面时的切换速度。
页面加载优化
选用正确的架构
如果项目对页面加载性能敏感,要避免将其部署为纯客户端的 SPA,而是让服务器直接发送包含用户想要看的内容的HTML代码.纯客户端渲染存在的首屏加载缓慢的问题可以通过服务器端渲染或静态站点生成来缓解
如果主应用必须是SPA,可以考虑单独部署其他的相关页面
包体积与 Tree-shaking 优化
压缩 JavaScript 打包产物的体积是一个有效的提升加载速度的方法:
- 尽可能的采用构建步骤
- 现代的打包工具可以将许多 Vue 的 API 进行 tree-shake. 比如没有使用到内置的
<Transition>组件它就不会被打包进最终产物中. Tree-shaking 也可以移除源码中你没有使用到的模块 - 使用构建步骤时,模板会被预编译,也就无需再浏览器中载入 Vue 编译器.
- 现代的打包工具可以将许多 Vue 的 API 进行 tree-shake. 比如没有使用到内置的
- 引入新的依赖项时要注意包体积膨胀问题.
- 如果使用构建步骤,应当尽量选择提供 ES 模块格式的依赖.比如
lodash-es和lodash - 查看依赖的体积并评估其提供的功能迭代性价比.
- 如果使用构建步骤,应当尽量选择提供 ES 模块格式的依赖.比如
- 如果想要避免使用构建步骤,考虑使用
petite-vue(6kb左右)
代码分割
现代构建工具可以将构建后的JavaScript代码分割为多个较小的文件,可以按需或并行加载.通过适当的代码分割,可以在加载页面时立即下载需要的功能,且仅在需要时再加载额外的功能.
打包工具可以通过分析 ESM 动态导入语法自动进行分割
js
// lazy.js 及其依赖会被拆分到一个单独的文件中
// 并只在 `loadLazy()` 调用时才加载
function loadLazy() {
return import('./lazy.js')
}Vue 的异步组件也可以极大的优化页面的初次加载
js
import { defineAsyncComponent } from 'vue'
// 会为 Foo.vue 及其依赖创建单独的一个块
// 它只会按需加载
// (即该异步组件在页面中被渲染时)
const Foo = defineAsyncComponent(() => import('./Foo.vue'))如果使用 Vue Router,推荐使用异步组件作为路由组件
js
// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [
{ path: '/users/:id', component: UserDetails }
// 或在路由定义里直接使用它
{ path: '/users/:id', component: () => import('./views/UserDetails.vue') },
],
})更新优化
Props稳定性
Vue中,一个子组件只会在至少一个props变更时才会更新
vue
<ListItem
v-for="item in list"
:id="item.id"
:active-id="activeId" />参考这个例子,使用id和activeid来确定当前活跃的一项,但是当activeId变更时,所有的列表都会更新,离线状态下应该只更新活跃的那一个,因此可以将活跃的状态逻辑转入父组件中
vue
<ListItem
v-for="item in list"
:id="item.id"
:active="item.id === activeId" />v-once
v-once 是一个内置的指令,可以用来渲染依赖运行时数据但无需再更新的内容。它的整个子树都会在未来的更新中被跳过。
vue
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
<h1>Comment</h1>
<p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>该指令可以在你确定数据不会发生变更时使用
v-memo (3.2+)
用来有条件地跳过某些大型子树或者 v-for 列表的更新。
通用优化
大型虚拟列表
渲染数量巨大的列表时需要处理大量的DOM节点,无论什么框架都会变慢,但是屏幕的展示内容是有限的,我们可以通过列表虚拟化来提升性能:
减少大型不可变数据的响应式开销
Vue默认数据深度响应,但数据量巨大时开销也大,好在这种影响只在特定的场景才会比较明显.针对这些场景Vue也提供了shallowRef和shallowReactive来绕开深度响应.浅层式的API创建的状态只在其顶层是响应式的,不会对深层的对象进行处理.但是使用这种方式需要确定深沉的数据是不可变的,也只能通过替换整个根状态来触发更新
js
const shallowArray = shallowRef([
/* 巨大的列表,里面包含深层的对象 */
])
// 这不会触发更新...
shallowArray.value.push(newObject)
// 这才会触发更新
shallowArray.value = [...shallowArray.value, newObject]
// 这不会触发更新...
shallowArray.value[0].foo = 1
// 这才会触发更新
shallowArray.value = [
{
...shallowArray.value[0],
foo: 1
},
...shallowArray.value.slice(1)
]避免不必要的抽象组件
虽然通过无渲染组件可以更好的实现抽象或组织代码,但是因为组件实例的开销要比DOM节点昂贵的多,因此过多的抽象组件也会造成性能的损失.不过需要明白,只减少几个组件实例对性能的影响不会有明显的改善,如果项目中抽象组件只使用了几次就没必要去优化它.需要考虑这种优化方式的地方也只在一些特定的使用了大量抽象组件的场景中,就比如大型的列表中.如果列表中的每一项都使用到了无渲染组件,那么对整个列表来说就可能造成成百上千的组件实例开销,这种情况下的优化才是有意义的.