我什么时候应该在 Svelte 定制商店中使用派生?

When should I use derived in Svelte custom stores?

我遵循了出色的 Svelte 教程,但我无法理解何时 我应该在我的自定义商店中使用 derived。在此示例中,我正在创建一个具有 3 个状态的游戏:

我想 return 一个布尔值检查,当我处于其中一个状态时,我认为 它最好在自定义商店本身。

这是我当前的代码:

import { writable, derived } from 'svelte/store';

export const gamestate = (() {
  const { set, subscribe } = writable('PRE_GAME');

  return {
    subscribe,
    set
  };
})();

export const preGame = derived(gamestate, ($gamestate) => $gamestate === 'PRE_GAME');
export const inGame = derived(gamestate, ($gamestate) => $gamestate === 'IN_GAME');
export const postGame = derived(gamestate, ($gamestate) => $gamestate === 'POST_GAME');

是否可以像 gamestate.preGame() 那样将派生方法移动到 gamestate 存储中?在 Svelte 中这样做有意义吗?然后我可以调用 $gameState 并获取任何值,而且当我需要一个显式值时 return 布尔值检查。

我希望能够在不需要导出其值的情况下检查内部值。也许像下面这样,但它总是 returns false 因为 gamestate 是一个可写对象。

export const createStore = (() {
  const { set, subscribe } = writable('PRE_GAME');

  return {
    subscribe,
    set,
    preGame: () => gamestate === 'PRE_GAME',
    inGame: () => gamestate === 'IN_GAME',
    postGame: () => gamestate === 'POST_GAME',
  };
})();

我误会了什么?

使用派生存储,您可以轻松拥有额外的变量或值,这些变量或值在 svelte 组件中是反应性的。反应性可以在没有派生存储的情况下实现,但这将是一个 hacky 和丑陋的解决方案。

这里有一个“强制”反应的例子:

store.js:

  function createStore() {
      const { set, subscribe } = writable('PRE_GAME');
      let state
      subscribe((v)=>state=v)
      return {
        subscribe,
        set,
        preGame: () => gamestate === 'PRE_GAME',
        inGame: () => state === 'IN_GAME',
        postGame: () => gamestate === 'POST_GAME',
      };
    }

App.svelte:

<script>
  import {gamestate} from "./store.js"
  $: st=gamestate.inGame($gamestate)
  $: console.log("state", st) 
</script>

<h1 on:click={()=>$gamestate="IN_GAME"}>Hello {$gamestate}!</h1>
<p>state: {st}</p>

一些解释:
state -变量保存商店状态,每次商店更新时它都会更新,因此我们需要订阅我们自己的商店。 inGame-函数returns是否为状态“IN_GAME”。

在 app-component 中,我们需要创建一个变量 st,因为它是在组件中创建的,所以它是响应式的。但它的价值只能通过将其标记为要观察来评估。可观察线由 $: 构成,但有一个问题:您还需要为 svelte 提供一些可观察的东西,因此有 $gamestore-变量作为函数参数。函数不需要它,但现在 svelte 知道每次 $gamestore 更新时更新这一行。
这是一个 REPL:

编辑:我更新了 REPL 以包含评论中提到的解决方案

https://svelte.dev/repl/2991d70689674f50a5fb3e30216d699e?version=3.24.1

对于这种反应,您绝对应该使用派生商店。每次派生商店更新时,派生商店都会更新。
但是派生商店也有一个问题。如果要更新派生商店,则必须在组件中使用它。您必须在某些 svelte 组件的某处使用 $derived_store (auto-subscription) 或手动订阅派生商店

您所做的是 return 订阅自定义商店中的派生商店:

import { derived, writable } from 'svelte/store'

export const gameState = (() => {
  const store = writable('PRE_GAME')
  const store2 = derived(store, $store => ({
    preGame: $store === 'PRE_GAME',
    inGame: $store === 'IN_GAME',
    postGame: $store === 'POST_GAME'        
  }))
    
  return {
    set: store.set,
    subscribe: store2.subscribe
  }
})()

现在,当您在商店上执行 set 时,它将在 store 上设置值,然后 store2 将派生新的值和订阅者将对这些更改做出反应:

<script>
  import { gameState } from "./store.js"
</script>

<button on:click="{() => $gameState='PRE_GAME'}">go PRE_GAME</button>
<button on:click="{() => $gameState='IN_GAME'}">go IN_GAME</button>
<button on:click="{() => $gameState='POST_GAME'}">go POST_GAME</button>

<br />

{#if $gameState.preGame}
    <span>PRE_GAME</span>
{/if}

{#if $gameState.inGame}
    <span>IN_GAME</span>
{/if}

{#if $gameState.postGame}
    <span>POST_GAME</span>
{/if}

备选

您还可以为每个特定状态定义不同的getters

import { derived, writable } from 'svelte/store'

export const gameState = (() => {
  const store = writable('PRE_GAME')
    const { set, subscribe } = store

  return {
    set,
    subscribe,
        get preGame() { return derived(store, $store => $store === 'PRE_GAME') },
        get inGame() { return derived(store, $store => $store === 'IN_GAME') },
        get postGame() { return derived(store, $store=> $store === 'POST_GAME') },
  }
})()

现在唯一的问题是 $gameState.preGame 不存在,因为它试图从 $gameState[ 的内容中获取 preGame 道具=34=],并且执行 gameState.$preGame 或类似操作是 无效语法 。为了解决这个问题,您可以在需要它们的地方将道具从商店中解构出来:

<script>
  import { gameState } from './store.js'
  const { preGame, inGame, postGame } = gameState
</script>

<span>Current State: {$gameState}</span>
{#if preGame}<span>PRE_GAME</span>{/if}
{#if inGame}<span>IN_GAME</span>{/if}
{#if postGame}<span>POST_GAME</span>{/if}

商店上的自定义方法用于改变状态(设置和更新),但商店通常只有一个 getter:状态。而且我不认为你希望它不是这样。

从这里开始,我看到三个解决方案:

  • 将您的状态设为对象{ gameSate: 'PRE_GAME', isPreGame: true, isInGame: false, isPostGame: false },然后使用自定义更新函数。
  • 像您一样使用派生商店(我认为在主商店中导入派生商店或使用 get() 毫无意义)。
  • 在组件中进行计算,可能会制作专用组件。您的组件仍然是纯粹的声明式的,唯一发生变化的东西仍然在商店中。

我会根据纯粹的枯燥程度来决定:导入还是重复条款。