巷子里的猫头鹰 發表於 2026-1-17 12:57:00

这 10 个 Vue3 性能优化技巧很实用,但很多项目都没用上

<h1 data-id="heading-0">🧑‍💻 写在开头</h1>
<p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<div>
<div>
<p>今天来分享 10 个 Vue3 的性能优化技巧。</p>
<blockquote>
<p><strong>核心原则</strong>:<br>
减少不必要的响应式追踪<br>
避免无谓的 DOM 操作<br>
按需加载资源</p>


</blockquote>
<p>咱也不要为了优化而优化!小项目用默认写法完全没问题,优化应在<strong>性能瓶颈出现后</strong>进行。</p>
<p>这些技巧不难,但都非常关键。
看完你会发现:原来 Vue3 还能这么写。</p>
<hr>
<h3 data-id="heading-0">1. 使用 <code>shallowReactive</code> 替代 <code>reactive</code></h3>
<p><strong>问题</strong>:<br>
<code>reactive</code> 会让对象里每一层都变得“敏感”——哪怕你只改了最里面的某个小字段,Vue 也会花力气去追踪它。数据一大,性能就变慢。</p>
<p><strong>解决方案</strong>:<br>
对不需要深层响应的数据,使用 <code>shallowReactive</code>,只让最外层变成响应式的。</p>
<p><strong>示例</strong>:</p>

</div>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">import { shallowReactive } from 'vue';

const data = shallowReactive({
list: [],
meta: { total: 0 }
});</pre>
</div>
<div>
<div>
<p><strong>适用场景</strong>:<br>
当你从后端拿到一大坨只读数据(比如表格列表、API 响应),且不会修改嵌套属性时。</p>
<hr>
<h3 data-id="heading-1">2. 用 <code>toRefs</code> 解构响应式对象</h3>
<p><strong>问题</strong>:<br>
如果你直接从 <code>reactive</code> 对象里解构变量(如 <code>const { name } = state</code>),这个 <code>name</code> 就变成普通变量了,修改它不会触发页面更新。</p>
<p><strong>解决方案</strong>:<br>
使用 <code>toRefs</code> 解构,保持每个属性的响应性。</p>
<p><strong>示例</strong>:</p>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const state = reactive({ name: 'Vue', age: 3 });
const { name, age } = toRefs(state); // name 和 age 依然是响应式的!</pre>
</div>
<div>
<div>
<p><strong>好处</strong>:<br>
在模板中可以直接写 <code>{{ name }}</code>,不用写 <code>{{ state.name }}</code>,代码更清爽。</p>
<hr>
<h3 data-id="heading-2">3. 优先使用 <code>watchEffect</code> 而非 <code>watch</code></h3>
<p><strong>区别</strong>:</p>
<ul>
<li><code>watch</code>:你要手动指定监听谁(比如 <code>watch(count, ...)</code>)。</li>
<li><code>watchEffect</code>:你只写逻辑,Vue 自动分析里面用了哪些响应式变量,并监听它们。</li>


</ul>
<p><strong>示例</strong>:</p>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">watchEffect(() =&gt; {
// Vue 自动发现 count.value 被用了 → 只要 count 变,这段就执行
localStorage.setItem('count', count.value);
});</pre>
</div>
<div>
<div>
<p><strong>适合场景</strong>:<br>
保存用户输入到本地缓存、根据筛选条件自动请求数据、同步状态到 URL 等。</p>
<hr>
<h3 data-id="heading-3">4. 利用 <code>&lt;Suspense&gt;</code> 优雅处理异步组件</h3>
<p><strong>问题</strong>:<br>
动态加载组件(如通过 <code>import()</code>)时,页面可能白屏几秒,用户体验差。</p>
<p><strong>解决方案</strong>:<br>
用 <code>&lt;Suspense&gt;</code> 包裹异步组件,显示 loading 提示。</p>
<p><strong>示例</strong>:</p>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;Suspense&gt;
&lt;template #default&gt;
    &lt;UserProfile /&gt; &lt;!-- 必须是异步组件 --&gt;
&lt;/template&gt;
&lt;template #fallback&gt;
    &lt;div&gt;加载中,请稍候…&lt;/div&gt;
&lt;/template&gt;
&lt;/Suspense&gt;</pre>
</div>
<div>
<div>
<p><strong>注意</strong>:<br>
仅适用于<strong>异步组件</strong>(即用 <code>defineAsyncComponent</code> 或 <code>() =&gt; import(...)</code> 定义的组件)。</p>
<hr>
<h3 data-id="heading-4">5. 使用 <code>&lt;Teleport&gt;</code> 解决模态框层级问题</h3>
<p><strong>问题</strong>:<br>
弹窗写在组件内部,可能被父级的 <code>overflow: hidden</code> 或 <code>z-index</code> 限制,导致显示不全或盖不住其他内容。</p>
<p><strong>解决方案</strong>:<br>
用 <code>&lt;Teleport&gt;</code> 把组件“传送”到 <code>&lt;body&gt;</code> 底部,脱离当前 DOM 树。</p>
<p><strong>示例</strong>:</p>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;Teleport to="body"&gt;
&lt;Modal v-if="show" /&gt;
&lt;/Teleport&gt;</pre>
</div>
<div>
<div>
<p><strong>类比</strong>:<br>
就像你在客厅写了个气球,但它实际飘到了天空——不受房间天花板限制。</p>
<p><strong>常用目标</strong>:<code>to="body"</code> 是最常见用法。</p>
<hr>
<h3 data-id="heading-5">6. 自定义指令封装高频操作(如复制)</h3>
<p><strong>问题</strong>:<br>
复制文本、防抖点击、自动聚焦……这些功能到处都要用,每次都写一堆代码很麻烦。</p>
<p><strong>解决方案</strong>:<br>
写一个自定义指令,一次定义,处处使用。</p>
<p><strong>示例</strong>:</p>

</div>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">app.directive('copy', {
mounted(el, binding) {
    el.addEventListener('click', () =&gt; {
      navigator.clipboard.writeText(binding.value);
    });
}
});</pre>
</div>
<p>使用:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;button v-copy="'要复制的内容'"&gt;点我复制&lt;/button&gt;</pre>
</div>
<div>
<div>
<p><strong>好处</strong>:逻辑集中、复用性强、模板干净。</p>
<hr>
<h3 data-id="heading-6">7. 用 Pinia 插件扩展 store 能力</h3>
<p><strong>问题</strong>:<br>
每个 store 都想加个“重置”功能?手动一个个写太重复。</p>
<p><strong>解决方案</strong>:<br>
通过 Pinia 插件,一次性给所有 store 添加 <code>$reset()</code> 方法。</p>
<p><strong>正确实现</strong>:</p>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">pinia.use(({ store }) =&gt; {
// 保存初始状态快照(深拷贝)
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () =&gt; {
    store.$state = initialState;
};
});</pre>
</div>
<p>使用:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const userStore = useUserStore();
userStore.$reset(); // 恢复初始状态</pre>
</div>
<div>
<div>
<p><strong>适用场景</strong>:表单重置、清除缓存、统一日志等。</p>
<blockquote>
<p>注意:不能直接用 <code>store.$patch(store.$state)</code>,因为 <code>$state</code> 是当前状态,不是初始状态!</p>
</blockquote>
<hr>
<h3 data-id="heading-7">8. <code>v-memo</code> 优化大型列表渲染</h3>
<p><strong>问题</strong>:<br>
列表有上千项,哪怕只改了一行的状态,Vue 默认会重新比对整张表,浪费性能。</p>
<p><strong>解决方案</strong>:<br>
用 <code>v-memo</code> 告诉 Vue:“只有这些值变了,才需要重新渲染这一行”。</p>
<p><strong>示例</strong>:</p>

</div>

</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;li v-for="item in list" :key="item.id" v-memo=""&gt;
{{ item.name }} —— 状态:{{ item.status }}
&lt;/li&gt;</pre>
</div>
<div>
<div>
<p><strong>注意事项</strong>:</p>
<ul>
<li>适合<strong>内容稳定、更新频率低</strong>的大列表。</li>
<li>不要和 <code>&lt;transition-group&gt;</code> 一起用(会失效)。</li>
<li>高频变动的列表慎用,可能适得其反。</li>
</ul>
<blockquote>
<p><code>v-memo</code> 是 Vue 3.2+ 的功能。</p>
</blockquote>
<hr>
<h3 data-id="heading-8">9. 虚拟滚动(Virtual Scrolling)</h3>
<p><strong>问题</strong>:<br>
渲染 10,000 条消息?浏览器直接卡死!</p>
<p><strong>解决方案</strong>:<br>
只渲染“当前可见区域”的内容,滑动时动态替换,内存和性能都省下来。</p>
<p><strong>推荐库(Vue 3 兼容)</strong>:</p>
<ul>
<li><code>vueuc</code>(轻量、活跃维护)</li>
<li><code>vue-virtual-scroll-grid</code></li>


</ul>
<p><strong>安装 &amp; 示例(以 vueuc 为例)</strong>:</p>

</div>

</div>

</div>
<blockquote>
<p>npm install vueuc</p>
</blockquote>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;script setup&gt;
import { VirtualList } from 'vueuc';
&lt;/script&gt;

&lt;template&gt;
&lt;VirtualList :items="messages" :item-height="60" :bench="10"&gt;
    &lt;template #default="{ item }"&gt;
      &lt;MessageItem :msg="item" /&gt;
    &lt;/template&gt;
&lt;/VirtualList&gt;
&lt;/template&gt;</pre>
</div>
<p>类比:<br>就像微信聊天记录——你往上滑,旧消息才加载;不滑的时候,几千条其实没真画出来。</p>
<hr>
<h3 data-id="heading-9">10. 路由与组件懒加载 + 图片优化</h3>
<h4 data-id="heading-10">组件懒加载</h4>
<p>原理:不是一打开网页就加载所有页面,而是“用到哪个才加载哪个”。</p>
<p>写法:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">{ path: '/about', component: () =&gt; import('./views/About.vue') }</pre>
</div>
<div>
<div>
<p><strong>好处</strong>:首屏加载更快,节省流量和内存。</p>
<h4 data-id="heading-11">图片优化</h4>
<ul>
<li><strong>用 WebP 格式</strong>:比 JPG/PNG 小 30%~50%,清晰度不变(现代浏览器都支持)。</li>
<li><strong>图片懒加载</strong>:屏幕外的图先不加载,滑到附近再加载。</li>
<li><strong>关键图预加载</strong>:首页 Banner 图提前加载,避免白块。</li>
</ul>
<p><strong>简单懒加载(原生支持)</strong>:</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;img src="image.jpg" loading="lazy" alt="示例图" /&gt;
</pre>
</div>
<p>  </p>
<div>
<blockquote>
<p><strong>兼容性提示</strong>:<code>loading="lazy"</code> 在 Chrome/Firefox/Edge 支持良好,但 Safari 15.4 以下和 IE 不支持。若需兼容旧环境,建议搭配 <code>IntersectionObserver</code> 或第三方库(如 <code>lazysizes</code>)。</p>
</blockquote>
<hr>
<h2 data-id="heading-12">总结</h2>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202601/2149129-20260117125644174-896740494.png" alt="ScreenShot_2026-01-17_125239_004" loading="lazy"></p>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p>
</div>
</div>
</div>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19495772
頁: [1]
查看完整版本: 这 10 个 Vue3 性能优化技巧很实用,但很多项目都没用上