如何用 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(() =&gt; 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 组件中使用 invoicesitems,与良好的公共 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 处理相关商店的主要内容,如果未能解决你的问题,请参考以下文章

Svelte 商店有条件的自动订阅

是否可以从外部 js 文件访问 Svelte 商店?

如何用批处理发邮件

教你如何用python6个步骤搞定金融数据挖掘预处理

QT5 如何用两个事件作画?

更改 Svelte / Sapper 中预处理器的顺序