MST

星途 面试题库

面试题:Svelte中writable与derived的性能优化考量

假设在一个复杂的Svelte应用中,频繁地更新状态,使用writable和derived时可能会遇到哪些性能问题?如何针对这些潜在问题进行优化?请结合具体代码示例说明。
11.5万 热度难度
前端开发Svelte

知识考点

AI 面试

面试题答案

一键面试

可能遇到的性能问题

  1. 过多的重新渲染
    • 原因:在Svelte中,当writable状态发生变化时,依赖它的组件会重新渲染。如果频繁更新writable状态,可能导致不必要的组件重新渲染,特别是在复杂应用中,一些组件可能并不需要每次状态变化都重新渲染。例如:
    <script>
      import { writable } from'svelte/store';
      const count = writable(0);
    </script>
    <div>
      <p>{$count}</p>
      <button on:click={() => $count++}>Increment</button>
      <!-- 每次点击按钮,包含<p>的整个组件都会重新渲染 -->
    </div>
    
    • 对于derived:如果derived依赖的writable状态频繁变化,并且derived在多个地方被使用,那么每次依赖的writable变化时,所有依赖该derived的组件都会重新渲染。例如:
    <script>
      import { writable, derived } from'svelte/store';
      const count = writable(0);
      const doubleCount = derived(count, $count => $count * 2);
    </script>
    <div>
      <p>{$doubleCount}</p>
      <button on:click={() => $count++}>Increment</button>
      <!-- 每次点击按钮,依赖doubleCount的组件都会重新渲染 -->
    </div>
    
  2. 计算开销
    • 对于derivedderived状态是基于其他状态计算得出的。如果计算逻辑复杂,频繁更新依赖的writable状态会导致频繁执行复杂的计算逻辑,消耗性能。例如:
    <script>
      import { writable, derived } from'svelte/store';
      const numbers = writable([1, 2, 3]);
      const sumOfSquares = derived(numbers, $numbers => {
        return $numbers.reduce((acc, num) => acc + num * num, 0);
      });
    </script>
    <div>
      <p>{$sumOfSquares}</p>
      <button on:click={() => $numbers.push(4)}>Add number</button>
      <!-- 每次添加数字,sumOfSquares的复杂计算逻辑都会重新执行 -->
    </div>
    

优化方法

  1. 减少不必要的重新渲染
    • 使用$: 声明局部变量:如果一个组件只需要状态的某个衍生值,而不需要整个状态对象,可以使用$: 声明局部变量,这样只有这个局部变量相关的代码会在状态变化时重新执行,而不是整个组件重新渲染。例如:
    <script>
      import { writable } from'svelte/store';
      const count = writable(0);
      $: doubled = $count * 2;
    </script>
    <div>
      <p>{doubled}</p>
      <button on:click={() => $count++}>Increment</button>
      <!-- 这里只有doubled相关代码会在count变化时重新执行,而不是整个组件重新渲染 -->
    </div>
    
    • 组件拆分:将依赖不同状态的部分拆分成不同的组件。例如:
    <!-- Parent.svelte -->
    <script>
      import { writable } from'svelte/store';
      import Child1 from './Child1.svelte';
      import Child2 from './Child2.svelte';
      const count = writable(0);
      const message = writable('Hello');
    </script>
    <div>
      <Child1 {count}/>
      <Child2 {message}/>
    </div>
    
    <!-- Child1.svelte -->
    <script>
      export let count;
    </script>
    <p>{$count}</p>
    
    <!-- Child2.svelte -->
    <script>
      export let message;
    </script>
    <p>{$message}</p>
    
    这样,count状态变化不会导致Child2组件重新渲染,message状态变化也不会导致Child1组件重新渲染。
  2. 优化derived计算
    • 缓存计算结果:可以使用一个变量来缓存derived的计算结果,只有当依赖的状态发生有意义的变化时才重新计算。例如:
    <script>
      import { writable, derived } from'svelte/store';
      const numbers = writable([1, 2, 3]);
      let cachedSumOfSquares;
      const sumOfSquares = derived(numbers, ($numbers, set) => {
        if (cachedSumOfSquares === undefined || $numbers.length!== cachedNumbers.length) {
          const newSum = $numbers.reduce((acc, num) => acc + num * num, 0);
          cachedSumOfSquares = newSum;
          cachedNumbers = $numbers.slice();
          set(newSum);
        } else {
          set(cachedSumOfSquares);
        }
      });
    </script>
    <div>
      <p>{$sumOfSquares}</p>
      <button on:click={() => $numbers.push(4)}>Add number</button>
      <!-- 只有在数字数组长度变化时才重新计算sumOfSquares -->
    </div>
    
    • 防抖或节流:如果状态更新过于频繁,可以对更新操作进行防抖或节流。例如,使用lodashdebounce函数:
    <script>
      import { writable } from'svelte/store';
      import { debounce } from 'lodash';
      const count = writable(0);
      const updateCountDebounced = debounce(() => $count++, 300);
    </script>
    <div>
      <p>{$count}</p>
      <button on:click={updateCountDebounced}>Increment</button>
      <!-- 点击按钮后,300毫秒内多次点击只会触发一次count更新 -->
    </div>