如何用 Svelte 处理相关商店
Posted
技术标签:
【中文标题】如何用 Svelte 处理相关商店【英文标题】:How to handle related stores with Svelte 【发布时间】:2021-12-30 07:04:20 【问题描述】:我有一个包含实体列表的商店,以及另一个包含这些实体的对象的商店。
我希望第一个商店中的更改能够反应性地反映在第二个商店中。
我将提供一个包含项目列表和发票列表的快速示例
export type Invoice =
id: string
customer: string
items: InvoiceItem[]
export type InvoiceItem =
id: string
name: string
price: number
每当更新发票项目的名称或价格时,我希望所有相关发票也更新。
我创建了这个非常简单的示例(repl available here),但为了更新 $invoices 存储,我必须在 $items 存储发生变化时发出 $invoices = $invoices。
另一种更优雅的方法是订阅项目商店并从那里更新发票商店,如下所示:
items.subscribe(_ => invoices.update(data => data))
<script>
import writable from 'svelte/store'
let item1 = id: 'item-01', name: 'Item number 01', price: 100
let item2 = id: 'item-02', name: 'Item number 02', price: 200
let item3 = id: 'item-03', name: 'Item number 03', price: 300
let items = writable([item1, item2, item3])
let invoices = writable([
id: 'invoice-0', customer: 'customer1', items: [item1, item3]
])
items.subscribe(_ => invoices.update(data => data)) // refresh invoices store whenever an item is changed
const updateItem1 = () =>
$items[0].price = $items[0].price + 10
// $invoices = $invoices // alternatively, manually tell invoices store that something changed every time I change and item!!!
</script>
<button on:click=updateItem1>update item 1 price</button>
<hr />
<textarea rows="18">JSON.stringify($invoices, null, 2)</textarea>
<textarea rows="18">JSON.stringify($items, null, 2)</textarea>
这是处理这种情况的最佳方式吗?
更新:感谢出色的答案和 cmets,我提出了这个更完整的示例:请参阅repl
我添加了一些功能,希望可以作为类似常见场景的基础
这就是我的 store api 的结果:
// items.js
items.subscribe // read only store
items.reset()
items.upsert(item) // updates the specified item, creates a new one if it doesn't exist
// invoices.js
invoices.subscribe // read only store
invoices.add(invocieId, customer, date) // adds a new invoice
invoices.addLine(invoiceId, itemId, quantity)
invoices.getInvoice(invoice) // get a derived store for that particular invoice
invoice.subscribe // read only store
invoice.addLine(itemId, quantity)
几个亮点
invoices 现在有一个lines
数组,每个数组都有一个项目和一个数量
invoices 是一个派生存储,用于计算每行和整个发票的总计
在 items 中实现 upsert 方法
为了在修改项目时更新发票,我运行items.subscribe(() => set(_invoices))
还创建了派生商店以获取特定发票
【问题讨论】:
显然赞成订阅方法,因为它更好地表达了您的意图(正如您所指出的那样,它更优雅),即“让发票商店按顺序订阅商品商店后者的变化会触发前者的更新”。话虽如此,发票和物品是一个糟糕的示例选择。在现实世界的应用程序中,项目将是不可变的(至少在给定发票的范围内)并且可能来自外部来源。只有它们的数量会发生变化,并且该数据将位于发票本身中,因此确实不需要物品商店;) 非常感谢您的评论,请查看更新的工作示例:svelte.dev/repl/d9fb573137cc468b8efa256701d39f6b?version=3.44.2 【参考方案1】:解决方案取决于您是否需要 items
独立(一个项目可以是多个发票的一部分)或者它是否可以是发票的一部分。如果它们可以是一个大块,我会创建invoices
作为商店并提供更新特定发票的方法。然后items
存储将从invoices
派生。
// invoices.ts
const _invoices = writable([]);
// public API of your invoices store
export const invoices =
subscribe: _invoices.subscribe,
addItemToInvoice: (invoideId, item) => ...,
..
;
// derived items:
const items = derived(invoices, $invoices => flattenAllInvoiceItems($invoice));
但是,如果它们需要分开 - 或者如果以这种方式处理项目更新更容易 - 那么我只会将项目的 ID 存储在发票存储中并创建一个使用发票+项目的派生存储创建完整的发票。
// items.ts
const _items = writable([]);
// public API of your items store
export const items =
subscribe: _items.subscribe,
update: (item) => ...,
...
;
// invoices.ts
import items from './items';
const _invoices = writable([]);
// public API of your invoices store
export const invoices =
// Assuming you never want the underlying _invoices state avialable publicly
subscribe: derived([_invoices, items], ([$invoices, $items]) => mergeItemsIntoInvoices($invoices, $items)),
addItemToInvoice: (invoideId, item) => ...,
..
;
在这两种情况下,您都可以根据需要在您的 Svelte 组件中使用 invoices
和 items
,与良好的公共 API 进行交互,并且派生存储将确保一切都同步。
【讨论】:
很好的答案,它给了我一些很棒的想法,我用你的一些想法创建了一个更详细的例子:svelte.dev/repl/d9fb573137cc468b8efa256701d39f6b?version=3.44.2【参考方案2】:您可以像这样使用派生商店:
let pipe = derived([invoices, items], ([$invoices, $items]) =>
return $invoices;
)
因此,如果发票被更改,$pipe 将返回更新的发票。
$pipe 将在两个商店($items 和 $invoice)都被触发,但只有在发票更改时才会产生结果。因此,当不属于发票的项目发生更改时,$pipe 不会产生结果。
更新。当 $invoices 没有像可写存储那样发生变化时,我预计 $pipe 不会有任何结果。但是如果 $invoices 或 $items 发生变化,派生的商店回调将始终运行。
所以我们必须检查 $invoices 是否发生变化,并且只有在我们有变化时才使用 set。
let cache = "";
let pipe = derived([invoices, items], ([$invoices, $items], set) =>
if (JSON.stringify($invoices) !== cache)
cache = JSON.stringify($invoices);
set($invoices);
, )
【讨论】:
以上是关于如何用 Svelte 处理相关商店的主要内容,如果未能解决你的问题,请参考以下文章