Svelte Store基础: writable与readable的使用
Svelte Store基础: writable与readable的使用
在前端开发中,状态管理是一个至关重要的环节。Svelte 作为一种新兴的前端框架,提供了独特且高效的状态管理方案,其中 Svelte Store 是其核心功能之一。Svelte Store 主要分为 writable
和 readable
两种类型,它们各自有着不同的用途和特点,理解并正确使用它们对于构建复杂的 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
函数返回的对象包含三个属性:subscribe
、set
和update
。- 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>
在这个例子中,创建了一个名为
time
的readable
存储。readable
函数的第一个参数是初始值,这里是当前的日期和时间。第二个参数是一个函数,该函数在存储被订阅时会被调用,它接受一个set
函数作为参数,用于设置存储的值。在这个函数内部,通过setInterval
每秒更新一次时间,并返回一个清理函数,在存储取消订阅时会调用这个清理函数来清除定时器。 -
readable 存储的结构
readable
函数返回的对象主要包含subscribe
属性,用于订阅存储的变化。由于readable
存储通常是不可写的,所以没有set
和update
方法。例如:<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:具有
set
和update
方法,允许直接修改存储的值,适合用于需要经常变化且可被多个地方修改的状态,如用户的登录状态、购物车中的商品数量等。 - readable:通常不可直接修改值,其值的变化通常由内部逻辑或基于其他存储的变化而更新,适合用于表示派生数据、只读数据或需要复杂初始化逻辑的数据,如格式化后的日期、基于多个状态计算得出的总值等。
- writable:具有
-
使用场景
- writable:在组件内部需要频繁修改某个状态,并且希望相关组件能实时响应这个变化时,
writable
是很好的选择。例如,在一个聊天应用中,用户输入的消息可以存储在一个writable
存储中,当消息发生变化时,聊天窗口组件会自动更新显示。 - readable:当你有一些数据是基于其他数据计算得出,并且不希望外部直接修改这个计算结果时,
readable
更为合适。比如,在一个电商应用中,购物车商品的总价是基于商品数量和单价计算得出的,这个总价可以存储在一个readable
存储中,而商品数量和单价可以存储在writable
存储中。
- writable:在组件内部需要频繁修改某个状态,并且希望相关组件能实时响应这个变化时,
-
性能考虑
- writable:由于可以随时修改值,可能会导致更多的组件重新渲染。因此,在使用
writable
时,要注意合理控制状态的变化范围,避免不必要的重新渲染。例如,可以通过使用batch
函数(Svelte 提供的用于批量更新状态的函数)来减少多次状态更新导致的多次渲染。 - readable:因为其值通常不是直接修改,而是通过内部逻辑或基于其他存储的变化来更新,所以在性能上相对更可控。特别是对于那些不经常变化的派生数据,使用
readable
可以避免不必要的重新计算和渲染。
- writable:由于可以随时修改值,可能会导致更多的组件重新渲染。因此,在使用
4. 复杂应用中的实践
在一个稍微复杂的应用场景中,比如一个任务管理应用,我们可以结合 writable
和 readable
来实现高效的状态管理。
-
任务列表的状态管理 假设我们有一个任务列表,每个任务有一个标题、是否完成的状态等。我们可以使用
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
这样过于通用的名字,而应该根据存储所代表的具体内容来命名,如userProfile
、productList
等。 -
避免过度使用 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
存储不会重新计算图表数据,从而提高性能。
总之,writable
和 readable
是 Svelte 状态管理中非常重要的工具,理解它们的特性、正确使用它们,并遵循最佳实践,能够帮助你构建出高效、可维护的前端应用。通过在不同的场景中灵活运用这两种存储类型,你可以更好地管理应用的状态,提升用户体验。无论是简单的交互组件还是复杂的单页应用,掌握 Svelte Store 的使用都是前端开发人员必备的技能之一。在实际开发中,不断地实践和总结经验,能够让你更加熟练地运用 writable
和 readable
来解决各种状态管理问题。