## 框架(Software Framework) :::success 框架以應用為目的,結合了多數常用的元件所形成的初始主題架構。框架在建構好大致的骨架後,一旦有應用需求就只需在框架之上進行符合自己需求的設定和修正即可。 ::: ## DOM(Document Object Model) :::info 將HTML中的標籤都變成物件並形成**樹狀結構**,通常被用來和JS溝通(用JS操作DOM中元素),節點分為Document、Element、Text、Attribute。 - Document:HTML文件的開端,所有標籤會由此向下延伸 - Element:文件的各種標籤,像是\<head>、\<body>、\<p>、\<div>等,都屬於元素節點 - Text:被標籤包起來的文字,被Element包起來的文字就是text eg.\<h3>text\</h3> - Attribute:個各標籤內的相關屬性,附加在Element上,用來敘述其相關性質 ![image](https://hackmd.io/_uploads/BJ1DHFK0p.png) ### 為何需要DOM 瀏覽器是一種編譯器負責編譯我們所寫的網頁,W3C訂定規則讓各大瀏覽器可以按照規則設計瀏覽器,DOM就是其中一種 瀏覽器會一步一步把HTML解析成一顆DOM tree eg. ```html <html> <head> <title>example</title> </head> <body> <h1 class="txt">Hello World</h1> </body> </html> ``` 從最一開始的Document,往下遇到`<html>`這個element,其中包含`<head>`和`<body>`兩個element,`<head>`中又包含`<title>`,以此類推,最後形成以下DOM tree ![image](https://hackmd.io/_uploads/Hyh46tKCT.png) ### DOM操作 - document.getElementById('idName') 找尋DOM中符合此id的元素並回傳 - document.getElementsByClassName('className') 找尋DOM中符合此class name的元素並回傳相對應的元素集合(HTMLCollection) ::: ## Svelte框架和傳統框架的區別 :::success 網頁是由瀏覽器負責運行,所以瀏覽器其實就是一種編譯器負責編譯我們寫的網頁程式 #### 傳統框架(React、Vue) 執行當下處理模板,並在瀏覽器上動態生成和更新DOM,以呈現應用程式的介面,會產生Virtul DOM,響應時比較新舊VDOM差異再去改變真實的DOM * 缺點:框架在運行時需要一些額外的程式碼來處理這些動態行為。 #### Sevlte框架 先編譯再執行,在編譯時會將組件模板轉換為純粹的JavaScript程式碼(靜態編譯),所以**是一種編譯器**,不會產生VDOM,直接修改真實DOM * 優點:減少應用程式的體積,提高執行效率(當應用程式運行時,並不需要額外的程式碼來處理模板,在編譯時就處理好了)、直接操作DOM,速度較快 ::: ## Sevlte簡介 :::info ### 添加文本 Svelte可以把javascript裡面定義的變數用大括號的方式直接替換到html裡 大括號一般都是插入一段字串(沒有任何效果那種) 例: ```html <script> let name = 'Svelte';//這是一般的JS變數定義 </script> . . . <h1>Hello {name}!</h1> <!--做出來的效果會是: Hello Svelte!--> ``` 如果要讓大括號裡的html功能能夠運作則需要加上"@html"標記 `{@html 包含html_tag的字串}` ### 動態屬性 可以向使用大括號控制文本般控制元素屬性 ```html <script> let src = 'tutorial/image.gif'; </script> <img src={src} alt=''> ``` 因為屬性和值名稱常常相同,svelte也提供更簡便的方式eg.`<img {src} alt="A man dances.">` ### 引用外部元件 可引用外部的svelte元件,避免將所有元件寫在同一個檔案當中 每個`.svelte`檔是一個單獨元件,封裝了HTML、CSS、JS,可重複使用 ```html //引入 <script> import 元件名稱 from '檔案路徑'; </script> //使用 <元件名稱 /> ``` 當前元件中的CSS樣式不會溢出套用到外部引用的元件中 p.s元件名稱首字母大寫,為了區分自訂義元件和HTML標籤 ### Reactivity響應 Svelte會保持DOM和應用程式的同步(也就是可以及時處理js裡的程式碼反映到html裡),快速渲染、及時響應畫面 [可參考這個!](https://medium.com/itsems-frontend/javascript-expressions-and-statements-7bd374effc95) - reactive assignment 如下方程式,count+=1就是reactive assignment: ```html! <script> let count = 0; function increment() { count += 1; } </script> <!--當點按鈕時就會跑一次increment()的程式,count就會被+1--> <button on:click={increment}> Clicked {count}<!-- +1後的count就會被放這--> <!--最後按鈕上會出現Clicked (1,2,3...),點幾次就是幾--> </button> ``` - Reactive declarations 有時元件部分會用到其他部分的值的計算得出,並在其更改時重新計算 這時可以用`$: `來解決,svelte會將其解釋為「參考值改變就重新運行此程式碼」 假設有一個數一直是count的2倍 不能寫成: ```html! <script> let count = 0; let doubled = count * 2; function increment() { count += 1; } <!--count變成1,2,3...時,double還是維持0--> </script> ``` 要這樣做: ```html <script> let count = 0; $: doubled = count * 2; <!--count變成1,2,3...時,double就會跟著變成2,4,6...--> </script> ``` (`$:`代表當count的值改變時會更新doubled的值) NOTE:響應式宣告或敘述會在script之後html渲染之前完成 - Reactive statements - statements是指`if-else`、`console.log`、`alert()`等,一定會執行動作,但不會回傳結果 - 用template string(JS筆記提過)可將變數或表達式用`${...}`的方式插入字串 ```html $: if (count >= 10) { alert(`I SAID THE COUNT IS ${count}`); count = 9; } //每次狀態改變時就會判斷一次if ``` - 更新array和object 像是`push`、`splice`、`pop`等不是statement的語句無法觸發響應(但可用在一般的js更新array、object),所以有幾種解決辦法 - 法一:加上`obj = obj` ```htmlembedded <script> let numbers = [1, 2, 3, 4]; function addNumber() { numbers.push(numbers.length + 1); numbers = numbers; //添加多餘的assignment } $: sum = numbers.reduce((t, n) => t + n, 0); </script> <p>{numbers.join(' + ')} = {sum}</p> <button on:click={addNumber}> Add a number </button> ``` - 法二:將method功能展開 ```html function addNumber() { numbers = [...numbers, numbers.length + 1]; } //將原本的numbers陣列展開並加上numbers.length + 1這個數字再assign給numbers陣列 //其中"...numbers"代表展開numbers這個陣列 ``` - 法三:直接賦值給屬性`obj.foo += 1`或對象`array[i] = x` ``` function addNumber() { numbers[numbers.length] = numbers.length + 1; } ``` ::: ## Props(Property) :::success 用途:使得各元件的值可以互相傳遞 為了從上層元件(App.svelte)傳入資料到下層元件(Nested.svelte),所以我們需要在下層組件宣告屬性,Svelte 的規則是在屬性前面加上 export 字樣, 例如 `export let answer` [參考點這裡!](https://editor.leonh.space/2021/svelte-quickstart-2/) eg.在`Nested.svelte`裡用 `export` 令 `answer` 對外暴露(代表App可以傳值給Nested) ```html App.svelte <script> import Nested from './Nested.svelte'; </script> <Nested answer={42} /> <Nested/> --- Nested.svelte <script> export let answer = 'a mystery'; </script> <p>The answer is {answer}</p> ``` 在 App 元件中,第一次調用 Nested 元件時我們對 answer 賦值 42,而在第二次調用 Nested 時,我們不賦值,那 answer 的值就會是default的 a mystery p.s.若Nested不對answer賦值則為undefined --- 上層元件可以傳遞任意的值(props)進下層元件,這些在下層元件內所有的(**包含未定義的**) props 會全部儲存在一個特殊的物件 `$$props` 內 eg.Nested.svelte不宣告任何值 ```html App.svelte <script> import Nested from './Nested.svelte'; </script> <Nested answer={42}/> --- Nested.svelte <p>The answer is {$$props.answer}</p> ``` p.s. Svelte說這會使程式難以優化(compile 時難以針對這種非預期的 props 做效能優化),所以盡可能還是在元件內明確宣告要 export 的變數 --- ### Props展開 可以將要傳入的值都包裝在一個物件內,並且物件內的名稱和 props 的名稱取的一樣,可以用 `{...pkg}` 的形式把值傳入下層元件,Svelte 會把 pkg 展開丟到對應的 props ```htmlmixed= App.svelte <script> import Info from './Info.svelte'; const pkg = { name: 'svelte', version: 3, speed: 'blazing', website: 'https://svelte.dev' }; </script> <Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/> <Info {...pkg}/> //結果和13行一樣 --- Info.svelte <script> export let name; //名稱和pkg裡的物件一樣 export let version; export let speed; export let website; </script> <p> The <code>{name}</code> package is {speed} fast. Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a> and <a href={website}>learn more here</a> </p> ``` ::: ## Logic :::info ### if-else ```html! {#if count > 10} <p>{count} is greater than 10</p> {:else if count < 5} <p>{count} is less than 5</p> {:else} <p>{count} is between 5 and 10</p> {/if} ``` 在Svelte裡面,`if`條件要用"{}"包起來,且前方要加上"#"的符號 "#"通常代表一個**block的開始**,而"/"代表整個**block的結束**,所以`{:else if}`和`{:else}`都要被包在`{#if}`和`{/if}`之間,整個block都是做 *if-else* 功能 * `{#if}`:區塊開頭 * `{:else if}`、`{:else}`: ":"為區塊中的continuation tag * `{/if}:`區塊結束 --- ### each迴圈 用來遍歷陣列裡的資料 語法:`{#each array as item, index}...{/each}` p.s.`index`可有可無 ```html <script> let cats = [ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, ]; </script> {#each cats as cat, i} <p><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">{i + 1}: {cat.name}</a></p> {/each} ---------------------------------------------- 1: Keyboard Cat 2: Maru ``` #### 迴圈加key key:svelte用來辨識每一個陣列值的依據(所以必須唯一) 迴圈也具有reactivity特性,當來源陣列的值發生改變時,迴圈結構也會發生變化。但當來源陣列加減一個物件時,加減會作用在迴圈的尾端,也就是最後一個成員(DOM nodes)被加減 eg.`things`陣列砍掉第一個成員,迴圈卻是砍最後一個成員 ```htmlembedded= App.svelte <script> import Thing from './Thing.svelte'; let things = [ { id: 1, name: 'apple' }, { id: 2, name: 'banana' }, { id: 3, name: 'carrot' }, ]; function handleClick() { things = things.slice(1); //砍掉things第一個成員 } </script> <button on:click={handleClick}> //每次按鈕按下things砍掉第一個成員,但迴圈砍掉最後一個成員 Remove first thing </button> {#each things as thing} <Thing name={thing.name}/> //每個thing會產生一個對應的Thing object //每次砍掉時會砍掉最後一個object {/each} --- Things.svelte <script> const emojis = { apple: "🍎", banana: "🍌", carrot: "🥕", }; //emoji在一開始初始化時就被固定 //因此即使之後name因為props而有不同的值emoji仍是原本的值 export let name; const emoji = emojis[name]; </script> <p>The emoji for { name } is { emoji }</p> ``` 執行結果: (還未按任何Button) The emoji for apple is 🍎 The emoji for banana is 🍌 The emoji for carrot is 🥕 (按一次Button) The emoji for banana is 🍎 The emoji for carrot is 🍌 因為name=banana會對應到原本apple的物件、name=carrot會對應到原本banana的物件 為了解決這個問題,有兩種方法, 法一:可以在回圈內加上key,如此Svelte 會去比較變更前後 key 的變化,並反映到相對的 HTML 陣列上(svelte會去比較新舊陣列中的元素,將DOM更新成和新陣列的值同步) eg.將`things.id`為key,用來唯一識別,一旦`things`發生變動,Svelte 會去比較`thing.id`的變化 ```html! {#each things as thing (thing.id) } <Thing name={thing.name}/> {/each} ``` 法二: 直接幫Thing元件內的emoji加上reactivity的特性即可(name改變時emoji也會更動) ``` $: emoji = emoji[name]; ``` --- ### Await 使用`await`在HTML裡處理promise[非同步](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Asynchronous/Introducing)操作(暫停目前正在執行的函數,等待promise回傳的結果) - JS是一種**單線程 (single-thread)** 的程式語言,也就是說它在任意一個時間點只能執行一個任務,當程式碼在運行時,如果遇到一個耗時較長的操作 (複雜的計算 or 巨大的迴圈),如果這個操作是同步的,則整個程式碼會被阻塞 (blocked)直到這個操作完成,這種方式稱為同步操作,反之,當程式碼遇到一個非同步操作時,它不會等待這個操作完成,而是會繼續執行下去,直到該操作完成後才會通知程式碼 - `Promise`是一個封裝非同步操作的物件,有三種狀態:`等待 (pending)`、`實現 (fulfilled)`、`拒絕 (rejected)`,以及其產生的值 - Svelte的`await`區塊可針對三種狀態呈現不同HTML ```htmlembedded= {#await 儲存promise物件的變數} <!-- 等待階段所呈現的 HTML 元素 --> {:then result} <!-- 實現階段所呈現的 HTML 元素 --> {:catch error} <!-- 拒絕階段所呈現的 HTML 元素 --> {/await} ``` - 第二行:當promise還在pending階段時,呈現這個段落的 HTML 元素 - 第三行:這是一個表示 promise 到達fulfilled階段的邏輯段落,並將所得到的結果放進變數 result - 第五行:這是一個表示 promise 到達rejected階段的邏輯段落,並將 promise 發生錯誤導致拒絕的內容放進變數 error。 - 可根據需求簡化await區塊,例如:有信心不會產生error即可省略`catch`block、在fulfilled前不想做任何動作即可'#await promise then value'省略`then`block ::: ## Event :::success ### DOM events - 以往會在JS中利用`addEventListener`做出事件處理器 (event handler) - 當使用事件處理器時,系統會自動產生一個監聽器(監聽DOM中的元素),在觸發時產生物件並傳給指定函數 事件處理器寫在`<script>`標籤內 ```html! <script> let m = { x: 0, y: 0 }; //event代表觸發事件的物件(這裡是div) function handleMove(event) { m.x = event.clientX; m.y = event.clientY; } </script> <div on:pointermove={handleMove}> The pointer is at {m.x} x {m.y} </div> <!--當滑鼠移動(on:pointermove),"handleMove"被觸發,將事件傳遞到function中處理,此時變數m中的x,y便會設為滑鼠的X/Y座標(event.clientX/Y)--> <!--注意:這裡的"handleMove"是事件處理器,不是普通function,所以這邊寫成"handleMove()"會捕捉不到滑鼠座標--> ``` 行內(inline)事件處理器 ```html! <script> let m = { x: 0, y: 0 }; </script> <div on:pointermove={(e) => { m = { x: e.clientX, y: e.clientY };} } > <!--e在這指的是"on:pointermove"的事件,紀錄滑鼠位子等數值給後面的m調用--> The pointer is at {m.x} x {m.y} </div> ``` --- ### modifiers 可添加額外的修飾符(modifiers) ` - 語法:`event_handler` | `modifier`(可以將多個modifier串接:以|相接) ```html <script> function handleClick() { alert('clicked') } </script> <button on:click|once={handleClick}> Click me </button> ``` 常見modifiers: * `once`代表事件處理只做一次,做完以後監聽器就會被移除 * `preventDefault`:停止事件的默認動作eg.`<a>`預設按下會跳轉連結,使用`preventDefault`就不會執行「跳轉連結」的動作 * 使用情境:將表單中的submit按鈕預設的重新刷新頁面動作(重新出現一個空的表單)取消 * `stopPropagation`:阻止冒泡事件,讓事件不在DOM樹中向上/下延伸效果,停留在你所指定的位置(平行的節點仍會被觸發) * 使用情境:在巢狀div中加入的按鈕,被觸發時只會影響某個div其他周圍的不會被影響 * `capture`:在捕獲階段就觸發處理程序,而非冒泡階段 * 使用情境:確保特定組件的事件被觸發的時間必定會早於其他事件 * `self`:當event.target是元素本身時才觸發事件處理程序(仍會冒泡) * 使用情境:在同一個頁面中,若點擊彈跳出來的視窗範圍之外的會取消彈跳視窗的顯示,反之不會發生變化 * `passive`:優化滾動性能(svelte會自動加上) * 使用情境: p.s.可以將modifiers組合在一起使用eg.`on:click|once|capture={...}` #### 冒泡事件補充 [參考網站](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/) 當有事件被觸發時,在DOM中會先從最上面的根結點開始傳遞事件(capturing),遇到被觸發的事件(at_target)後會觸發開始執行,並將事件逆著傳回根節點(bubbling)並依序觸發執行,因此只要在這個路徑上有eventListener就會被觸發 eg:在巢狀中,點擊內部元素時,同時觸發內部和外部的事件處理 ![image](https://hackmd.io/_uploads/rJFuefWgR.png) * 先捕獲再冒泡 * 當事件傳到 target 本身,沒有分捕獲跟冒泡 * 使用情境:事件代理,像是有一個 ul,底下 1000 個 li,如果你幫每一個 li 都加上一個 eventListener,你就新建了 1000 個 function,但實際上只要在 ul 身上掛一個 listener 就好。 :::success ### dispatcher [參考點這裡!](https://ithelp.ithome.com.tw/articles/10329603) - 用元件客製化事件的方法,Svelte提供createEventDispatcher(),我們用它創建一個dispatch 實體供派送事件 - 不會冒泡,所以要手動傳送事件出去 → event forwarding ```html App.svelte <script> import Inner from './Inner.svelte'; function handleMessage(event) { alert(event.detail.text); //處理事件及其內容 } </script> <Inner on:message={handleMessage} /> //監聽message事件,若被觸發就會執行handleMessage //on: 事件綁定的語法,並非 props 的語法 Inner.svelte <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function sayHello() { dispatch('message', { text: 'Hello!' //藉由dispatch 發送一個叫做message的事件, //並攜带一个包含'Hello!'文本的對象。 }); } </script> <button on:click={sayHello}> Click to say hello </button> ``` ps createEventDispatcher要在元件一開始就定義 ### Event forwarding - 可以用在DOM事件、客製化事件 - 方法:在內部元件中加上`on:事件` ```html Outer.svelte接收來自Inner.svelte的自訂事件 Outer.svelte <script> import Inner from './Inner.svelte'; </script> <Inner on:message /> ``` ps 為了簡化,on:message沒有指定特殊值時代表轉發所有message事件 ::: ## Binding :::info - property:資料從父元件傳遞給子元件 - 事件傳遞:子元件的資料變更傳給父元件 - bind:打破只能單向傳遞,將子元件和父元件的資料綁再一起,使其雙向連動 - 語法:在原本props前加上`bind:` eg:`bind:value={name}` 以下為表單相關的舉例 numeric inputs ``` <input type="number" value={a} min="0" max="10" /> ``` 選項選擇與否(checkedbox) ``` <input type="checkbox" bind:checked={yes}> //yes是一個布林型態的變數,儲存現在是否有選取選項 ``` 下拉選單 ``` <select bind:value={selected} on:change={() => (answer = '')}> {#each questions as question} <option value={question}> {question.text} </option> {/each} </select> ``` 單選/複選的值 - `bind:group`:將選的值綁訂在同一個變數中 - 單選:儲存單一值(互斥) - 複選(checkbox/select multiple):所選的值用數組方式儲存在一起 ``` {#each [1, 2, 3] as number} <label> <input type="radio" name="scoops" value={number} bind:group={scoops} /> {number} {number === 1 ? 'scoop' : 'scoops'} </label> {/each} ``` 文字框 ``` <textarea bind:value={value}></textarea> ``` ps. bind中若屬性值和變數值相同可簡化寫法 eg:`bind:value={value}` = `bind:value` ::: ## Lifecycle :::success 每個元件(component)都有自己的生命周期,當一個元件被掛載到DOM上時,其生命周期開始,一直到元件從DOM中被卸載為止,即生命周期結束 [參考連結](https://editor.leonh.space/2021/svelte-quickstart-5/) ### onMount 在該元件掛載到 DOM 之後會執行的函式,只接受一個參數,也就是想要在元件被掛載之後立刻執行的函式,也可以回傳函式 * 使用時機:元素使用完畢釋放記憶體、確保所有元素都可以在被掛載到DOM後再被使用 ```html import { onMount } from 'svelte'; onMount(() => { //元件被掛載之後立刻執行的函式 }); return () => { //元件被銷毀(eg跳轉到其他頁面)時要執行的函式 //若沒有使用return則在元件被銷毀以後仍會繼續執行,導致資源浪費 }; ``` ```htmlembedded= //svelte tutorial部分片段 <script> import { onMount } from 'svelte'; import { paint } from './gradient.js'; onMount(() => { //canvas要在元件被掛載後再繪製, //若沒有用onMount則元素還沒被掛載到DOM上 //會出現找不到canvas元素的情況 const canvas = document.querySelector('canvas'); const context = canvas.getContext('2d'); ... return () => { cancelAnimationFrame(frame); }; }); </script> <canvas width={32} height={32} /> ``` [參考連結](https://ithelp.ithome.com.tw/articles/10335519) ### onDestroy 在該元件被卸載時會執行的函式,通常用來清理元素、釋放記憶體,功能和onMount的return一樣 ### beforeUpdate、afterUpdate * 使用時機:要動態驅動的地方 * beforeUpdate:在DOM更新前執行,所以在Mount之前也會執行 * afterUpdate:在DOM同步完成之後執行 ```html beforeUpdate(() => { //要執行的函式 }); afterUpdate(() => { //要執行的函式 }); ``` ### tick 可以在任何時候調用,會回傳一個promise對象,若元件pending state沒有變化,則會立即resolve,反映到DOM上,否則會等到pending state變化完再渲染到DOM上 p.s.當元件發生變化時並不會馬上反映到DOM上,會在下一個microtask才統一將產生變化的state放到DOM上,在未到達下一個microtask都還能繼續改變或取消改變,如此能增加效率 [參考連結](https://juejin.cn/post/7279265985911685156) ::: ## Stores :::info Store 是 Svelte 的資料交換機制,被定義成 store 的變數(要搭配export使外部可見),可以被多個元件使用,一個 store 變數的值的變化,也可以透過 subscribe 的機制,即時反映給元件們,要在結束時 unsubscribe 將資源釋放回去。 * 之前的資料交換都是縱向eg.使用bind,但當專案複雜、元件很多時,很難區分階層結構,所以store可以讓互不隸屬的元件各自持有資料,資料水平溝通 * store內的變數寫在`.js`檔裡 ### store和props差別 * Store 是獨立於元件的,而 props 是元件內的 * Store 在使用上與元件的父子關係無關,而 props 的使用都是父元件傳給子元件的 ### writable stores writable()函數會回傳一個特殊的store物件,該變數是可複寫的 * line 2: 引入store類型writable * line 4: 給變數count一個writable物件,並初始化為0,且須`export`使該變數能被其他元件使用 * line 28: 使用store的方法`update()`更新值 * line 41: 使用store的方法`set(0)`重設值為0 * line 16: 使用store的方法`subscribe()`,使用一個callback function(回呼函式)為傳入參數,只要store內物件的值發生變化就會執行該回呼函式,該回呼函式的傳入參數就是store內儲存變數的值,所以如此就能讓writable()物件的值的變化即時反映到其他元件當中,而呼叫`subscribe()`會得到一個回傳函式,如果呼叫該回傳函式可以終止`subscribe`的行為 * line 20: 觸發`unsubscribe`函式,終止`subscribe`的行為避免記憶體空間浪費 ```html= //store.js import { writable } from 'svelte/store'; export const count = writable(0); //APP.svelte <script> import { onDestroy } from 'svelte'; import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte'; let count_value; const unsubscribe = count.subscribe((value) => { count_value = value; }); onDestroy(unsubscribe); </script> //Incrementer <script> import { count } from './stores.js'; function increment() { count.update((n) => n + 1); } </script> <button on:click={increment}> + </button> //resetter.svelte <script> import { count } from './stores.js'; function reset() { count.set(0); } </script> <button on:click={reset}> reset </button> ``` ### Auto-subcription/unsubcription svelte提供使用`$變數名`從store中直接取得變數值,如此就不需要手動寫subscribe/unsubscribe,svelte會自動做到 承上範例 * line 9: 當store變數count改變時,自動更新值 ```html= //App.svelte <script> import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte'; </script> <h1>The count is {$count}</h1> <Incrementer/> <Decrementer/> <Resetter/> ``` ### Readable stores 唯讀,只能在建立時給予初始值以及更新方式,外部無法修改值,第一個參數,是 store 的初始值,第二個參數是個 callback 函式,會在 store 被初次訂閱時執行 ```html= //stores.js import { readable } from 'svelte/store'; export const time = readable(new Date(), function start() { const interval = setInterval((set) => { set(new Date()); }, 1000); return function stop() { clearInterval(interval); }; }); ``` ### derived stores 以某個 store 為基礎,可以做出衍生的 store,監聽依賴的store當期發生變化時derived也會跟著更新,第一個參數是初值,第二個參數則是一個函式。 ```html= import { readable, derived } from 'svelte/store'; export const time = readable(new Date(), function start(set) { const interval = setInterval(() => { set(new Date()); }, 1000); return function stop() { clearInterval(interval); }; }); const start = new Date(); //以time為基礎的derived store,time產生變化時,會觸發derived export const elapsed = derived( time, ($time) => Math.round(($time - start) / 1000) ); ``` ### custom stores 當不想外部直接使用writable()物件時,可包裝起來並自訂義方法,如下範例,外部只能呼叫使用`subscribe`、`increment`、`decrement`,而無法使用來自writable()的`update`和`set` p.s.`subscribe`是store必要的要素,一定要開放外部給外界呼叫 ```html= //stores.js import { writable } from 'svelte/store'; function createCount() { const { subscribe, set, update } = writable(0); //引入了 writable() 的三個方法 subscribe、update 和 set return { subscribe, //自訂義三個方法 subscribe、update 和 reset increment: () => update((n) => n + 1), decrement: () => update((n) => n - 1), reset: () => set(0) }; } export const count = createCount(); //對外暴露 //app.svelte <script> import { count } from './stores.js'; </script> <h1>The count is {$count}</h1> //只能呼叫允許的 subscribe、increment、decrement、reset 四個方法 <button on:click={count.increment}>+</button> <button on:click={count.decrement}>-</button> <button on:click={count.reset}>reset</button> ``` ### store binding 和之前提到的bind概念一樣,可以讓值可以和store互通 :::