iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0
Modern Web

Svelte 的奇妙冒險系列 第 10

[Svelte 的奇妙冒險] Day 10 - 先寫一個最陽春的 Todo list (2)

  • 分享至 

  • xImage
  •  

今天來繼續完善 Todo list

封裝 Component

首先我們能運用在 Day 08 時提到的方法將我們要 reuse 的部分另外寫成一個 .svelte 檔

<script lang="ts">
	interface InputProps {
		label: string;
		value: string;
	}
	let { label, value = $bindable() }: InputProps = $props();
</script>

<label class="form-control w-full">
	<div class="label">
		<span class="label-text text-primary">{label}</span>
	</div>
	<input type="text" class="input input-bordered w-full" bind:value />
</label>

  <Input label="Title" bind:value={title} />
  <Input label="Content" bind:value={content} />

一點點 Snippet

但如果我就是不想每個 component 都抽成 .svelte 檔,我只是想在這個 +page.svelte 裡 reuse 部分的 markup 而已那該怎麼做呢?這時可以使用 {#snippet} 以及 {@render}

{#snippet} 就是可以讓 markup 片段可以被 reuse 的寫法,而 {@render} 就是負責渲染 {#snippet}

先把 Todo 卡片來寫成一個 snippet

{#snippet card(todo: Todo)}
	<div class="card shadow bg-base-200 mb-4">
		<div class="card-body">
			<div class="flex justify-between items-center">
				<h2 class="card-title">{todo.title}</h2>
				<input type="checkbox" bind:checked={todo.done} class="checkbox checkbox-lg" />
			</div>
			<p>{todo.content}</p>
		</div>
	</div>
{/snippet}

{#snippet card(todo:Todo)} 的意思就是我宣告了一個名稱為 card 的 snippet 且他可以傳入一個 type 為 Todo 的參數。

而我們原本卡片的部分就可以改寫成這樣

{#each todoList as todo (todo.id)}
      {@render card(todo)}
{/each}

除了能夠 reuse markup 以外我覺得另外一個好處是它能提升程式碼的可讀性,試想一下如果我這邊不只一層{#each} 或甚至還有 {#if} 再加上原本的 HTML tag 這些加在一起就讓程式碼變得不太好閱讀了吧。

那如果我的 props 是想要有其他 component 呢?或者該說 markup 的結構呢? 沒錯還是用 {#snippet} ,那就先來把原本 page 中最外層的那個 div 抽出去做為一個獨立的 component 。

<script lang="ts">
	import { type Snippet } from 'svelte';

	interface PageLayoutProps {
		children: Snippet;
	}
	let { children }: PageLayoutProps = $props();
</script>

<div class="max-w-3xl mx-auto">
	{@render children()}
</div>

<PageLayout>
	<div class="grid grid-cols-2 mb-8 gap-x-8">
		<Input label="Title" bind:value={title} />
		<Input label="Content" bind:value={content} />
	</div>

	<button class="btn btn-primary" onclick={addTodo}> Add Todo </button>

  <!-- 省略其餘部分 -->
</PageLayout>

而這裡的 children 是一個特殊的naming ,只要是「在 component 裡的 markup 片段會自動成為該 component 的 children props 的值」,所以這段 <PageLayout></PageLayout> 裡的程式碼才能被 {@render children()} 吃到。

所以其實他的意思就跟這個是一樣的

<PageLayout>
	{#snippet children()}
		<div class="grid grid-cols-2 mb-8 gap-x-8">
			<Input label="Title" bind:value={title} />
			<Input label="Content" bind:value={content} />
		</div>

		<button class="btn btn-primary" onclick={addTodo}> Add Todo </button>

       <!-- 省略其餘部分 -->
  {/snippet}
</PageLayout>

補上其他樣式及互動

現在來實作勾選 Todo 卡片右上角的 checkbox 後可以被標註成完成狀態,那這邊就可以使用 Day 07 時提到 class:

{#snippet card(todo: Todo)}
	<div class="card shadow bg-base-200 mb-4">
		<div class="card-body">
			<div class="flex justify-between items-center">
				<h2 class:line-through={todo.done} class="card-title">{todo.title}</h2>
				<input type="checkbox" bind:checked={todo.done} class="checkbox checkbox-lg" />
			</div>
			<p class:text-gray-700={todo.done}>{todo.content}</p>
		</div>
	</div>
{/snippet}

使用了 class:line-through={todo.done}class:text-gray-700={todo.done} 這兩個directives 後,我們就能在當 todo.done === true 時加上這兩個 class 了。

最後再補上刪除卡片的功能就是用 array.filter() 來實現刪除特定 idtodo ,然後因為 array.filter() 是不會更改到原本的 array 的,所以我還是得使用 immutable update 的方式去更新原本的 todoList

<script lang="ts">
  // 省略其餘部分

  const removeTodo = (id: number) => {
      todoList = todoList.filter((todo) => todo.id !== id);
    };
</script>

<!-- 省略其餘部分 -->

 {#snippet card(todo: Todo)}
	<div class="card shadow bg-base-200 mb-4">
		<div class="card-body">
			<div class="flex justify-between items-center">
				<h2 class:line-through={todo.done} class="card-title">{todo.title}</h2>
				<input type="checkbox" bind:checked={todo.done} class="checkbox checkbox-lg" />
			</div>
			<p class:text-gray-700={todo.done}>{todo.content}</p>
			<button class="ml-auto w-20 btn btn-error" onclick={() => removeTodo(todo.id)}>
				Remove
			</button>
		</div>
	</div>
{/snippet}

小結

至此算是把一個最簡單的 Todo List 完成了,會發現 Svelte 在寫這種小功能非常簡單、快速,特別是在狀態更新和動態樣式的寫法相當直覺。


source code

https://github.com/toddLiao469469/30days-for-svelte5/tree/day10/src/routes/todo-list


上一篇
[Svelte 的奇妙冒險] Day 09 - 先寫一個最陽春的 Todo list
下一篇
[Svelte 的奇妙冒險] Day 11 - motion 與 transition
系列文
Svelte 的奇妙冒險30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言