常见导致不必要组件重新渲染的操作及示例
- 频繁更新响应式变量
- 示例:在组件内部有一个响应式变量
count
,并且在setInterval
中频繁更新它,如下:
<script>
let count = 0;
setInterval(() => {
count++;
}, 100);
</script>
<div>{count}</div>
- 原因:每次
count
更新,Svelte会重新渲染包含它的组件,即使DOM更新可能没有实际视觉变化,过于频繁的更新就会导致性能问题。
- 传递不必要的动态props
- 示例:父组件向子组件传递一个动态变化但子组件实际并不依赖的props。比如父组件有一个不断变化的时间戳
timestamp
,传递给一个只显示静态文本的子组件:
<!-- 父组件 -->
<script>
import Child from './Child.svelte';
let timestamp = new Date().getTime();
setInterval(() => {
timestamp = new Date().getTime();
}, 1000);
</script>
<Child {timestamp}/>
<!-- 子组件 -->
<script>
export let timestamp;
</script>
<p>这是一个静态文本</p>
- 原因:子组件虽然不依赖
timestamp
进行渲染,但由于props的变化,Svelte默认会重新渲染子组件。
- 在组件生命周期函数中进行大量计算
- 示例:在
onMount
生命周期函数中进行复杂的数组排序等计算:
<script>
import { onMount } from'svelte';
let largeArray = Array.from({ length: 10000 }, (_, i) => i);
let sortedArray;
onMount(() => {
sortedArray = largeArray.sort((a, b) => a - b);
});
</script>
- 原因:每次组件挂载(或者重新挂载)时,都会执行这些计算,可能会阻塞主线程,并且如果组件频繁重新渲染(比如因为父组件传递的props变化导致组件重新创建挂载),这些计算会重复执行,影响性能。
避免方法
- 控制响应式变量更新频率
- 解决方案:可以使用
requestAnimationFrame
或lodash
的debounce
/throttle
函数来控制更新频率。例如使用throttle
:
<script>
import { throttle } from 'lodash';
let count = 0;
const updateCount = throttle(() => {
count++;
}, 500);
setInterval(updateCount, 100);
</script>
<div>{count}</div>
- 原理:
throttle
函数会限制updateCount
函数的调用频率,每500毫秒最多调用一次,减少了不必要的重新渲染。
- 使用
bind:group
或$$props
优化props传递
- 解决方案:对于子组件不依赖的props,可以使用
bind:group
来减少不必要的重新渲染。例如,在上述父 - 子组件示例中,如果子组件确实不需要timestamp
,可以这样优化:
<!-- 父组件 -->
<script>
import Child from './Child.svelte';
let timestamp = new Date().getTime();
setInterval(() => {
timestamp = new Date().getTime();
}, 1000);
</script>
<Child bind:group={[/* 子组件真正需要的props */]}/>
<!-- 子组件 -->
<script>
// 这里不接收timestamp
</script>
<p>这是一个静态文本</p>
- 原理:
bind:group
会告诉Svelte只有组内的props变化才会触发子组件重新渲染。另外,也可以在子组件中使用$$props
来过滤不需要的props,只对需要的props做出响应。
- 将复杂计算移出组件生命周期
- 解决方案:可以将复杂计算提前到组件外部进行,或者使用
derived
来缓存计算结果。例如,对于上述数组排序的示例:
<script>
import { onMount } from'svelte';
import { derived } from'svelte/store';
let largeArray = Array.from({ length: 10000 }, (_, i) => i);
let sortedArray = derived(largeArray, ($largeArray) => {
return $largeArray.sort((a, b) => a - b);
});
onMount(() => {
// 这里只需要处理已缓存的sortedArray
});
</script>
- 原理:
derived
会缓存计算结果,只有当依赖的largeArray
真正发生变化时,才会重新计算,避免了在组件生命周期中重复进行高成本计算。