MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Svelte Store基础: writable与readable的使用

2023-01-194.0k 阅读

Svelte Store基础: writable与readable的使用

在前端开发中,状态管理是一个至关重要的环节。Svelte 作为一种新兴的前端框架,提供了独特且高效的状态管理方案,其中 Svelte Store 是其核心功能之一。Svelte Store 主要分为 writablereadable 两种类型,它们各自有着不同的用途和特点,理解并正确使用它们对于构建复杂的 Svelte 应用至关重要。

1. writable 存储

writable 是 Svelte 中最常用的存储类型,它用于创建一个可写的存储对象。这意味着你可以随时修改存储中的值,并且当值发生变化时,依赖该存储的组件会自动重新渲染。

  • 创建 writable 存储 要创建一个 writable 存储,你可以使用 svelte/store 模块中的 writable 函数。以下是一个简单的示例:

    <script>
    import { writable } from'svelte/store';
    
    // 创建一个名为 count 的 writable 存储,初始值为 0
    const count = writable(0);
    </script>
    
    <p>Count: {$count}</p>
    <button on:click={() => count.update(n => n + 1)}>Increment</button>
    

    在上述代码中,首先从 svelte/store 导入 writable 函数,然后使用它创建了一个名为 count 的存储,初始值为 0。在模板部分,通过 {$count} 语法来访问存储的值,并在按钮的点击事件中使用 count.update 方法来更新存储的值。update 方法接受一个函数,该函数接收当前存储的值,并返回新的值。

  • writable 存储的结构 writable 函数返回的对象包含三个属性:subscribesetupdate

    • subscribe:用于订阅存储的变化。当存储的值发生变化时,订阅的回调函数会被调用。例如:
    <script>
    import { writable } from'svelte/store';
    
    const count = writable(0);
    
    const unsubscribe = count.subscribe(value => {
      console.log('The count has changed to:', value);
    });
    
    // 模拟一些操作导致 count 值变化
    setTimeout(() => {
      count.set(5);
    }, 2000);
    
    // 取消订阅
    setTimeout(() => {
      unsubscribe();
    }, 4000);
    </script>
    

    在这个例子中,通过 count.subscribe 方法订阅了 count 存储的变化,当 count 的值在两秒后通过 set 方法更新为 5 时,订阅的回调函数会在控制台打印出相应的日志。四秒后,通过调用 unsubscribe 函数取消订阅。

    • set:用于直接设置存储的值。例如 count.set(10) 会将 count 存储的值设置为 10
    • update:正如前面示例中看到的,update 方法接受一个函数,该函数以当前存储的值作为参数,并返回新的值来更新存储。例如 count.update(n => n + 1) 会将 count 的值加 1
  • 在组件间共享 writable 存储 一个常见的场景是在多个组件之间共享 writable 存储。假设我们有一个 App.svelte 组件和一个 Counter.svelte 组件: App.svelte

    <script>
    import { writable } from'svelte/store';
    import Counter from './Counter.svelte';
    
    const count = writable(0);
    </script>
    
    <Counter {count}/>
    

    Counter.svelte

    <script>
    import { $ } from'svelte/store';
    
    export let count;
    </script>
    
    <p>Count: {$count}</p>
    <button on:click={() => count.update(n => n + 1)}>Increment</button>
    

    App.svelte 中创建了 count 存储,并将其传递给 Counter.svelte 组件。在 Counter.svelte 组件中,通过 $count 语法访问存储的值,并可以通过 count.update 方法更新它。这样,多个组件就可以共享和操作同一个 writable 存储。

2. readable 存储

readable 用于创建一个可读的存储对象。与 writable 不同,readable 存储的值通常是不可直接修改的,它更适合用于表示那些基于其他数据派生出来的值,或者是需要一些初始化逻辑的值。

  • 创建 readable 存储 使用 svelte/store 模块中的 readable 函数来创建可读存储。以下是一个简单的示例:

    <script>
    import { readable } from'svelte/store';
    
    const time = readable(new Date(), set => {
      const interval = setInterval(() => {
        set(new Date());
      }, 1000);
    
      return () => clearInterval(interval);
    });
    </script>
    
    <p>The current time is: {$time}</p>
    

    在这个例子中,创建了一个名为 timereadable 存储。readable 函数的第一个参数是初始值,这里是当前的日期和时间。第二个参数是一个函数,该函数在存储被订阅时会被调用,它接受一个 set 函数作为参数,用于设置存储的值。在这个函数内部,通过 setInterval 每秒更新一次时间,并返回一个清理函数,在存储取消订阅时会调用这个清理函数来清除定时器。

  • readable 存储的结构 readable 函数返回的对象主要包含 subscribe 属性,用于订阅存储的变化。由于 readable 存储通常是不可写的,所以没有 setupdate 方法。例如:

    <script>
    import { readable } from'svelte/store';
    
    const message = readable('Initial message', set => {
      setTimeout(() => {
        set('Updated message');
      }, 3000);
    
      return () => {
        console.log('Unsubscribed from message store');
      };
    });
    
    const unsubscribe = message.subscribe(value => {
      console.log('The message has changed to:', value);
    });
    
    setTimeout(() => {
      unsubscribe();
    }, 6000);
    </script>
    

    在这个例子中,message 存储在三秒后更新其值,订阅的回调函数会打印出更新后的消息。六秒后取消订阅,清理函数会在控制台打印出相应的日志。

  • 基于其他存储创建 readable 存储 可以基于已有的 writable 或其他 readable 存储来创建新的 readable 存储。例如:

    <script>
    import { writable, readable } from'svelte/store';
    
    const count = writable(0);
    
    const doubleCount = readable(0, set => {
      const unsubscribe = count.subscribe(value => {
        set(value * 2);
      });
    
      return unsubscribe;
    });
    </script>
    
    <p>Count: {$count}</p>
    <p>Double Count: {$doubleCount}</p>
    <button on:click={() => count.update(n => n + 1)}>Increment Count</button>
    

    在这个例子中,doubleCount 是一个基于 count 存储创建的 readable 存储。每当 count 的值发生变化时,doubleCount 会相应地更新为 count 值的两倍。

3. writable 与 readable 的对比

  • 可写性

    • writable:具有 setupdate 方法,允许直接修改存储的值,适合用于需要经常变化且可被多个地方修改的状态,如用户的登录状态、购物车中的商品数量等。
    • readable:通常不可直接修改值,其值的变化通常由内部逻辑或基于其他存储的变化而更新,适合用于表示派生数据、只读数据或需要复杂初始化逻辑的数据,如格式化后的日期、基于多个状态计算得出的总值等。
  • 使用场景

    • writable:在组件内部需要频繁修改某个状态,并且希望相关组件能实时响应这个变化时,writable 是很好的选择。例如,在一个聊天应用中,用户输入的消息可以存储在一个 writable 存储中,当消息发生变化时,聊天窗口组件会自动更新显示。
    • readable:当你有一些数据是基于其他数据计算得出,并且不希望外部直接修改这个计算结果时,readable 更为合适。比如,在一个电商应用中,购物车商品的总价是基于商品数量和单价计算得出的,这个总价可以存储在一个 readable 存储中,而商品数量和单价可以存储在 writable 存储中。
  • 性能考虑

    • writable:由于可以随时修改值,可能会导致更多的组件重新渲染。因此,在使用 writable 时,要注意合理控制状态的变化范围,避免不必要的重新渲染。例如,可以通过使用 batch 函数(Svelte 提供的用于批量更新状态的函数)来减少多次状态更新导致的多次渲染。
    • readable:因为其值通常不是直接修改,而是通过内部逻辑或基于其他存储的变化来更新,所以在性能上相对更可控。特别是对于那些不经常变化的派生数据,使用 readable 可以避免不必要的重新计算和渲染。

4. 复杂应用中的实践

在一个稍微复杂的应用场景中,比如一个任务管理应用,我们可以结合 writablereadable 来实现高效的状态管理。

  • 任务列表的状态管理 假设我们有一个任务列表,每个任务有一个标题、是否完成的状态等。我们可以使用 writable 来存储任务列表:

    <script>
    import { writable } from'svelte/store';
    
    const tasks = writable([
      { id: 1, title: 'Learn Svelte', completed: false },
      { id: 2, title: 'Build a project', completed: false }
    ]);
    
    const addTask = () => {
      tasks.update(t => {
        const newTask = { id: t.length + 1, title: 'New Task', completed: false };
        return [...t, newTask];
      });
    };
    
    const toggleTask = taskId => {
      tasks.update(t => {
        return t.map(task => {
          if (task.id === taskId) {
            return {...task, completed:!task.completed };
          }
          return task;
        });
      });
    };
    </script>
    
    <ul>
    {#each $tasks as task}
      <li>{task.title} - {task.completed? 'Completed' : 'Not Completed'}
        <input type="checkbox" checked={task.completed} on:change={() => toggleTask(task.id)}>
      </li>
    {/each}
    </ul>
    <button on:click={addTask}>Add Task</button>
    

    在这个例子中,tasks 是一个 writable 存储,addTask 函数用于添加新任务,toggleTask 函数用于切换任务的完成状态。通过 tasks.update 方法来更新任务列表,组件会自动根据任务列表的变化重新渲染。

  • 计算已完成任务的数量 我们可以基于 tasks 存储创建一个 readable 存储来计算已完成任务的数量:

    <script>
    import { writable, readable } from'svelte/store';
    
    const tasks = writable([
      { id: 1, title: 'Learn Svelte', completed: false },
      { id: 2, title: 'Build a project', completed: false }
    ]);
    
    const completedTaskCount = readable(0, set => {
      const unsubscribe = tasks.subscribe(t => {
        const count = t.filter(task => task.completed).length;
        set(count);
      });
    
      return unsubscribe;
    });
    
    const addTask = () => {
      tasks.update(t => {
        const newTask = { id: t.length + 1, title: 'New Task', completed: false };
        return [...t, newTask];
      });
    };
    
    const toggleTask = taskId => {
      tasks.update(t => {
        return t.map(task => {
          if (task.id === taskId) {
            return {...task, completed:!task.completed };
          }
          return task;
        });
      });
    };
    </script>
    
    <ul>
    {#each $tasks as task}
      <li>{task.title} - {task.completed? 'Completed' : 'Not Completed'}
        <input type="checkbox" checked={task.completed} on:change={() => toggleTask(task.id)}>
      </li>
    {/each}
    </ul>
    <p>Completed Tasks: {$completedTaskCount}</p>
    <button on:click={addTask}>Add Task</button>
    

    在这个例子中,completedTaskCount 是一个 readable 存储,它根据 tasks 存储的变化来计算已完成任务的数量。当任务列表中的任务完成状态发生变化时,completedTaskCount 会自动更新,并且相关组件会重新渲染。

5. 最佳实践与注意事项

  • 合理命名存储 为存储对象取一个清晰、有意义的名字,有助于提高代码的可读性和可维护性。例如,不要使用诸如 data 这样过于通用的名字,而应该根据存储所代表的具体内容来命名,如 userProfileproductList 等。

  • 避免过度使用 writable 虽然 writable 非常灵活,但过度使用可能会导致状态管理变得混乱,难以追踪状态变化的源头。尽量只在确实需要可写状态的地方使用 writable,对于派生数据或只读数据,优先考虑使用 readable

  • 正确处理订阅和取消订阅 在使用 subscribe 方法时,要确保在适当的时候取消订阅,以避免内存泄漏。特别是在组件销毁时,要记得调用 unsubscribe 函数。在 Svelte 组件中,可以使用 onDestroy 生命周期函数来处理取消订阅操作,例如:

    <script>
    import { writable } from'svelte/store';
    import { onDestroy } from'svelte';
    
    const count = writable(0);
    
    const unsubscribe = count.subscribe(value => {
      console.log('Count has changed:', value);
    });
    
    onDestroy(() => {
      unsubscribe();
    });
    </script>
    
  • 利用 readable 的缓存特性 readable 存储在值没有发生变化时,不会触发订阅的回调函数。这意味着对于一些计算代价较高的派生数据,可以利用 readable 的这个特性来避免不必要的重复计算。例如,在计算一个复杂的图表数据时,如果数据源没有变化,readable 存储不会重新计算图表数据,从而提高性能。

总之,writablereadable 是 Svelte 状态管理中非常重要的工具,理解它们的特性、正确使用它们,并遵循最佳实践,能够帮助你构建出高效、可维护的前端应用。通过在不同的场景中灵活运用这两种存储类型,你可以更好地管理应用的状态,提升用户体验。无论是简单的交互组件还是复杂的单页应用,掌握 Svelte Store 的使用都是前端开发人员必备的技能之一。在实际开发中,不断地实践和总结经验,能够让你更加熟练地运用 writablereadable 来解决各种状态管理问题。