iT邦幫忙

2024 iThome 鐵人賽

DAY 13
0
Modern Web

Svelte 的奇妙冒險系列 第 13

[Svelte 的奇妙冒險] Day 13 - runed,一個好用的 utility library

  • 分享至 

  • xImage
  •  

runed 是一個 Svelte 的 utility library ,它在 rune 的基礎上增加許多實用的功,今天就來介紹其中幾個我覺得比較酷的功能。

開始前請先安裝這個 package

pnpm add runed 
# npm install runed
# yarn add runed

watch

之前有提過 $effect 會自動追蹤依賴進而知道何時該重新執行 effect ,如果我們不想要它自動追蹤,那我們就必須要手動的加上 untrack

但就是有時候我們真的在眾多 state 中只是想追縱特定的 state 更新時再去執行 effect,但我又同時需要其他 state 的值。

watch的基本用法是:

import { watch } from "runed";
 
let count = $state(0);
watch(() => count, () => {
    console.log(count);
  }
);

watch 第一個參數是一個匿名函式回傳要監聽的值,第二個參數則是要執行的 effect

像是以下的例子,使用了兩個 $state ,但 watch 只監聽了 countOne

<script lang="ts">
	import { watch } from 'runed';

	let countOne = $state(0);
	let countTwo = $state(0);
	watch(
		() => countOne,
		() => {
			console.log('countOne', countOne, 'countTwo', countTwo);
		}
	);
</script>

<div class="flex gap-12">
	<button class="btn" onclick={() => countOne++}>Count One {countOne}</button>
	<button class="btn" onclick={() => countTwo++}>Count Two {countTwo}</button>
</div>

相比於 $effect 來說不用特別使用 untrack(() => countTwo) ,就可以達到「只有 countOne 有更新才執行 effect 」這件事情。

StateHistory

StateHistory 正如它的名字就是可以記錄一個 state 的歷史紀錄,而且它同時封裝的 canUndocanRedoundoredo 等 api,這點真的蠻方便的。

<script lang="ts">

	import { StateHistory } from 'runed';

	let countOne = $state(0);

	const history = new StateHistory(
		() => countOne,
		(c) => (countOne = c)
	);

</script>


<div>
	<button class="btn" disabled={!history.canUndo} onclick={() => history.undo()}>Undo</button>
	<button class="btn" disabled={!history.canRedo} onclick={() => history.redo()}>Redo</button>
</div>
<div>
	{#each history.log as { snapshot, timestamp }}
		<div class="flex gap-4">
			<div>
				value: {snapshot}
			</div>
			<div>
				{new Date(timestamp)}
			</div>
		</div>
	{/each}
</div>

FiniteStateMachine

應該看得出來它就是能夠幫助我們建立有限狀態機(FSM),稍微解釋一下 FSM ,就是一個描述一個在有限個數的狀態下各個狀態互相轉移的模型。

像是如果我要描述一個開關在 FSM 中我可以這樣描述

當前狀態 事件 下一狀態
OFF 按下開關 ON
ON 按下開關 OFF

也就是我當前狀態如果是 OFF 那我按下開關時就會把當前狀態變為 ON ,如果當前狀態為 ON 那我按下開關時就會把當前狀態變為 OFF

在 runed 的 FiniteStateMachine 是這樣寫的:我需要先定義好兩個 type 分別是「當前狀態」跟「事件」,以我們上面的例子來說按下開關就是 toggle 當前狀態就是有 offon

然後我們就能描述說在哪個狀態下做了什麼事件會切換成什麼狀態了

type ToggleStates = 'off' | 'on';
type ToggleEvents = 'toggle';

const toggleFSM = new FiniteStateMachine<ToggleStates, ToggleEvents>('disabled', {
    enabled: {
        toggle: 'disabled'
    },
    disabled: {
        toggle: 'enabled'
    }
});

在使用上就是使用 current 來拿到「當前狀態」,用 send 來「發送事件」

<div class="mt-12">
    toggleFSM {toggleFSM.current}
    <button class="btn" onclick={() => toggleFSM.send('toggle')}>Toggle</button>
</div>


當然這個例子有點簡單,來寫一個複雜一點點的例子:咖啡機

當前狀態 事件 下一狀態
關機 按下開機按鈕 待機
待機 按下啟動按鈕 研磨
研磨 研磨完成 沖泡
沖泡 沖泡完成 待機
待機 按下關機按鈕 關機
研磨 按下關機按鈕 關機
沖泡 按下關機按鈕 關機

簡單地歸納是

  • 按下開機按鈕:

    • 如果狀態是 關機,則轉換為 待機。
  • 按下啟動按鈕:

    • 如果狀態是 待機,則轉換為 研磨。
  • 研磨完成:

    • 如果狀態是 研磨,則轉換為 沖泡。
  • 沖泡完成:

    • 如果狀態是 沖泡,則轉換回 待機。
  • 按下關機按鈕:

    • 如果狀態是 待機,則轉換為 關機。

    • 如果狀態是 研磨,則轉換為 關機。

    • 如果狀態是 沖泡,則轉換為 關機

那這樣就能寫出他的 FSM 的程式碼:

  type CoffeeMachineStates = 'off' | 'idle' | 'grinding' | 'brewing';
  type CoffeeMachineEvents = 'powerOn' | 'powerOff' | 'start' | 'grindComplete' | 'brewComplete';

  const coffeeMachineFSM = new FiniteStateMachine<CoffeeMachineStates, CoffeeMachineEvents>('off', {
      off: {
          powerOn: 'idle'
      },
      idle: {
          start: 'grinding',
          powerOff: 'off'
      },
      grinding: {
          grindComplete: 'brewing',
          powerOff: 'off'
      },
      brewing: {
          brewComplete: 'idle',
          powerOff: 'off'
      }
  });

會發現當我們的狀態愈複雜、事件愈多時使用 FSM 會變得更加容易撰寫。當然 runed 中 FiniteStateMachine 相比於其他 FSM library (xstate 之類的)來說可能還是有那麼一點不足,但如果只是想寫簡單的 FSM 來說還是蠻適合的。


參考資料

source code

https://github.com/toddLiao469469/30days-for-svelte5/blob/main/src/routes/day13/%2Bpage.svelte


上一篇
[Svelte 的奇妙冒險] Day 12 - global state management
下一篇
[Svelte 的奇妙冒險] Day 14 - TanStack Query
系列文
Svelte 的奇妙冒險30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言