今天來介紹在 Svelte 中常用的 template 語法: logic blocks,是能讓我們在 markup 中更簡單控制 render 的語法。
基本上從名字就知道它的作用是什麼了,在 Svelte 中要做到 conditional rendering 就是要靠 {#if}
來達成。
在 Svelte 中這種 {#}
形式的語法都是需要結束 tag 的,所以實際上會是這樣使用:
{#if expression}...{/if}
{#if expression}...{:else if expression}...{/if}
{#if expression}...{:else}...{/if}
可以看出來跟 JS 中的 if
、elseif
、else
的行為基本上一致,就是接受一個 expression 然後看是否為 truthy 的值如果是就執行所屬的 block
{#if true}
<h1>it will always be shown</h1>
{/if}
{#if false}
<h1>it will never be shown</h1>
{/if}
以上面的例子來說第一個的 {#if}
就一定會渲染出 <h1>it will always be shown</h1>
,而第二個就永遠不會被渲染出來。
<script>
let randomNumber = Math.floor(Math.random() * 5);
</script>
{#if randomNumber === 0}
<h1>randomNumber is 0</h1>
{:else if randomNumber >= 1 && randomNumber <= 3}
<h1>randomNumber is between 1 and 3</h1>
{:else}
<h1>randomNumber is 4</h1>
{/if}
而 {#each}
就是拿來迭代資料用的,只要是 array-like 的值都可以拿來使用,像是 Map 或者 Set 甚至是字串,基本上就是有辦法 map over 的資料都可以。
或者可以說只要能被
Array.form
轉換的都可以
基本的用法如下,只要 expression 是一個可以被 map over 的資料就可以,而 as
就是被遍歷出的個別元素
{#each expression as name}...{/each}
<script lang="ts">
const user = {
name: 'Todd',
email: 'todd@example.com',
phone: '09123456789'
};
const map = new Map([
['name', 'Todd'],
['email', 'todd@example.com'],
['phone', '09123456789']
]);
</script>
{#each ['Todd', 'Larry', 'Danny'] as name}
<p>name: {name}</p>
{/each}
{#each Object.entries(user) as [key, value]}
<p>{key} : {value}</p>
{/each}
{#each map as [key, value]}
<p>{key} : {value}</p>
{/each}
除了 as name
以外還有 index
以及 (key)
的用法, index 很好理解就是現在取得的元素的 index
,而 key
的通常是用在被 each 的這個值是會動態的增減的情況下,可以告訴編譯器知道要怎麼更新這個列表,而如果沒有使用 key
的話預設就是在尾端增減。
{#each expression as name, index}...{/each}
{#each expression as name (key)}...{/each}
{#each expression as name, index (key)}...{/each}
{#each expression as name}...{:else}...{/each}
{#await}
就是在 markup 執行非同步的行為,聽起來好像沒什麼了不起的但我覺得他可能是在這些語法中最強大的,因為比起 react 來說在只是要「在 component mounted時打一次 api 然後渲染」這件事情來說, Svelte 的 #await
真的是少寫了很多程式碼。
以 React 來比較的話通常我們會是使用 state 來存放執行非同步行為後的資料,所以當這個 state 有變化時 component 也會隨之 re-render
簡單的範例如下:
const getPromise = () =>
new Promise((resolve) => {
setTimeout(() => {
resolve('Hello, World!');
}, 2000);
});
const App = () => {
const [apiData, setApiData] = useState({
data: undefined,
status: "init",
});
const fetchData = useCallback(async () => {
setApiData((prev) => ({
...prev,
status: "pending",
}));
const response = await promise;
setApiData({
data: response,
status: "fulfilled",
});
}, [setApiData]);
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<>
{apiData?.status === "pending" && <p>loading...</p>}
{apiData?.status === "fulfilled" && <p>{apiData.data}</p>}
</>
);
};
會發現我為了非同步所以必須使用 useEffect
,然後我需要讓畫面在獲得 API 來的資料後重新渲染所以我需要 useState
,但為了他我必須多寫這些程式碼。更別說我需要另外管理 status
的更新。
當然,在實際開發上會使用 TanStack query 、apollo query 或者 redux toolkit 這種「處理非同步行為的 library 但順便使用狀態來管理資料」或是「狀態管理的 library 但可以順便處理非同步行為」的東西。
那在 Svelte 我只需要:
<script lang="ts">
const getPromise = (): Promise<string> =>
new Promise((resolve) => {
setTimeout(() => {
resolve('Hello, World!');
}, 2000);
});
</script>
{#await getPromise()}
<p>loading...</p>
{:then result}
<p>{result}</p>
{/await}
沒錯這樣就好,就是這麽簡單。
首先我們先來看一下他的基本用法,其實就跟我們在操作 promise
時很像
{#await expression}...{:then name}...{:catch name}...{/await}
也就是說我們放入一個 async
的 expression ,也就是會回傳 Promise<T>
的東西,我們就可以使用 {:then}
以及 {:catch name}
來獲得成功或失敗的結果,但更棒的是我們能夠在{#await expression}...{:then name}
之間塞入 promise pending 時要渲染怎樣的畫面,那如果只要顯示結果也可以用以下的形式
{#await expression then name}...{/await}
{#await expression catch name}...{/await}
用起來就會像是這樣
<script lang="ts">
const getPromise = (): Promise<string> =>
new Promise((resolve) => {
setTimeout(() => {
resolve('Hello, World!');
}, 2000);
});
const getErrorPromise = () =>
new Promise((_, reject) => {
setTimeout(() => {
reject('Error: Something went wrong');
}, 2000);
});
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = (await response.json()) as {
id: number;
title: string;
completed: boolean;
userId: number;
};
return data;
};
</script>
{#await getPromise()}
<p>Loading...</p>
{:then result}
<p>{result}</p>
{/await}
{#await getErrorPromise()}
<p>Loading...</p>
{:catch error}
<p>Error Message: {error}</p>
{/await}
{#await fetchData() then result}
<div>
<p>id: {result.id}</p>
<p>title: {result.title}</p>
<p>completed: {result.completed}</p>
<p>userId: {result.userId}</p>
</div>
{:catch error}
<h1>Error Message: {error}</h1>
{/await}