Svelte 組件之間通訊的 6 種方法

使用組件設計用戶界面的主要挑戰是管理不同組件上的應用狀態。而 Svelte 提供了強大的能力實現在組件中進行數據傳遞。

“Great communication begins with connection.“

— Oprah Winfrey

譯註:奧普拉·蓋爾·溫弗裏(英語:Oprah Gail Winfrey,1954年1月29日-),生於美國密西西比州,美國電視脫口秀主持人、製作人、投資家、慈善家及演員,美國最具影響力的非洲裔名人之一,時代百大人物。

她是入選時代百大人物次數最多者,總共9次。2005年美國在線舉辦票選活動—《最偉大的美國人》,她被選爲美國最偉大的人物中的第九位。

接下來開始來了解 6 種實現 Svelte 組件間通訊的方法,他們分別是:

  1. 將數據發送到子組件: Props
  2. 在組件內渲染 HTML: Slots
  3. 子組件將數據通過事件方式通知父組件: Events
  4. 通過上下文 API 傳遞數據: Context API
  5. 在所有組件的實例之間共享數據: Module Context
  6. 在任意組件之間共享數據: Store

在本文中,我們寫了一些簡單的示例,應用了不同的 Svelte 組件通訊技術。這些代碼不會包含所有零碎的東西,只是爲了讓你瞭解這些技術的本質。

1. Props

在真實應用中,經常需要將數據從一個組件傳遞到其他的子組件。最基本的方法就是使用組件屬性,在 Svelte 中可以使用 export 關鍵字來定義組件屬性。

請注意下圖代碼中高亮部分的 export 關鍵字:

Card.svelte with props

簡單的引入 Card 組件,並通過屬性將數據傳遞給 Card 。

<script>
  import Card from './Card.svelte';
</script>

​​​​​​​<Card userid="#2312312" name="Jhon Doe"/>

提示: 在 Svelte 中使用 Typescript 可以解決組件內的屬性類型檢查問題。

2. Slots

Slots (槽) —— 子組件可以通過使用槽來傳遞要渲染的內容,這是基於 Web 組件槽的建議。

<div class="card">
  <slot/>
</div>

槽的默認定義方法是在子組件中使用 <slot> 這個 HTML 標籤。

槽有助於將組件設計爲一個模板,同時也可以基於命名的方式來注入 HTML 內容。下面栗子展示命名槽的做法:

Card component with slots

3. Events

在設計組件時,捕獲來自子組件的事件是一件很有意思的事情。繼續更新上述的代碼,當我們點擊心圖標的時候就會在喜歡與不喜歡之間做切換。如圖所示:

Card with toggle heart button
爲了分發事件,需要引入Svelte 的   {createEventDispatcher}  函數。然後在子組件中通過這個函數來觸發事件。來看看代碼:
<script>
	import { createEventDispatcher } from 'svelte';
	
	//Declare the dispatch
	const dispatch = createEventDispatcher();
	
	export let id = 'ID';
	export let name = 'unknown'
	export let favor = false;
	
	let hearts = ['♡','♥'];	
	let heartIcon = hearts[0];
	
	//if you wonder $: its svelte way make reactive statement
	$: heartIcon = favor ? hearts[1] : hearts[0];
	
	const favorite = (id) => {
		favor = !favor;
		
		//Dispatch the favorite event with object data
		dispatch('favorite', {id, favor});
	}
</script>

<div class="card">
	<i>{id}</i>
	<h2>{name}</h2>
	<span class="btnHeart" on:click={() => favorite(id)}>{heartIcon}</span>
</div>

<style>
	.card{position:relative;margin:50px auto;width:300px;padding:20px;border-radius:9px;background-color:#F5F7F0;	}
	i{ color:#999;}
	h2{margin:0; color:#491D3C;}
	.btnHeart{position:absolute;right:20px;	top:10px;font-size:32px;cursor:pointer;	}
</style>

CardEvent.svelte 文件根據點擊來分發事件。

這裏我們獲取到三個值,分別是 id, name, favor ,然後通過 dispatch 分發給父組件:

<script>
 import Card from './CardEvent.svelte'
 
 let user ={
  id : '#1212',
  name: 'Jhon Doe',
  favor : false
 }
 
 const whenFavored = (event) => user.favor = event.detail.favor;
 
</script>

<Card {...user} on:favorite={whenFavored}/>

App.svelte using CardEvent.svelte

這裏使用了 {...}操作符來設置 user 對象作爲子組件的屬性,並偵聽 favorite 點擊事件,當有點擊的時候觸發 whenFavored()函數執行並設置值到父對象中。

黑科技

  1. 在多級嵌套的組件中,可以使用 <card on:favorite />來轉發事件,它將事件傳遞給父組件,這個機制同樣也適用於 DOM 的事件。
  2. 可以將子組件作爲一個引用傳遞給父組件,並在父組件中調用所有子組件輸出的方法。例如可以使用 <Card bind:this={userCard} />來引用 Card 組件爲一個 userCard 對象。

4. Context API

上下文 API。 現在我們進入 Svelte 通訊技術裏高級部分,這部分非常有用。上下文 API 提供了一個強大的機制實現組件間的對話。

“Communication is only effective when we communicate in a way that is meaningful to the recipient, not ourselves.“
"只有當我們以對接收者而不是我們自己有意義的方式進行溝通時,溝通才有效。“

— Rich Simmonds (LinkedIn 聯合創始人兼 CEO)

首先頂層的組件需要使用 setContext() 來設置數據,然後其他組件通過 getContext() 來獲取數據。是不是相當簡單?

//App.svelte

<script>

import Card from './CardContext.svelte' 
import {setContext} from 'svelte'; 

let user ={
  id:123456,
  name:'Jhon Doe',
  favor : true
} 

setContext('user', user);

</script>

<Card/>

我們通過上下文 API 將對象 user 傳遞給所有子組件:

<script>
	import {getContext} from 'svelte';
  
	//Get the Ancestor user object
        const user = getContext('user');
  
	let hearts = ['♡','♥'];	
	let heartIcon = hearts[0];
  
	//Apply the favor value to reactive heartIcon
        $: heartIcon = user.favor ? hearts[1] : hearts[0];	
</script>

<div class="card">
	<i>{user.id}</i>
	<h2>{user.name}</h2>
	<span class="btnHeart">{heartIcon}</span>
</div>

<style>
	.card{	position:relative; margin:50px auto; width:300px; padding:20px; border-radius:9px;background-color:#F5F7F0;}
	i{ color:#999;}
	h2{margin:0; color:#491D3C;}
	.btnHeart{ position:absolute; right:20px; top:10px; font-size:32px; cursor:pointer; }
</style>

CardContext.svelte

注意,上下文中的狀態僅對子組件有效,如果要使用組件的多個實例,而不使一個組件的狀態干擾其他組件的狀態,這將非常有用。

5. Module Context (模塊上下文)

在 Svelte 中,同一個組件的多個實例想要共享相同數據是非常簡單的,只需要將這些變量定義在 <script context='module'></script> 之間即可。

來看這麼一個例子,當點擊標籤名詞的時候,其他組件實例也會相應的展示相同效果。

點擊 Clear All 可以清除所有按鈕狀態。

首先在 App.svelte 中使用 users 對象創建 Card 實例並使用屬性來傳遞 user 對象。

<script>
 import Card,{ clearAll } from './CardWithModuleContext.svelte'
 let users=[
  {id:101, name:'Jhon Doe', tags:['Javascript','HTML','PHP']},
  {id:102, name:'Mark Dane', tags:['C++','HTML','CSS']},
  {id:103, name:'Clark Jose', tags:['Javascript','HTML','CSS']},
 ]; 
</script>
<button on:click={clearAll} 
    style="border-radius:9px;"
    >Clear All
</button>
{#each users as user}
  <Card {user}/>
{/each}

App.svelte

同時添加一個模塊方法 {clearAll} 來清除所有高亮的標籤:

<script context="module">	
       //data shared accross each instance of this componenet
	let tagSelected; 
	export function clearAll() { tagSelected = "" }
</script>

<script>
	import { onMount, onDestroy } from "svelte";  
	export let user;  
	let choosed;	
	let interval;
  
	const selectIt = (tag) => {		
		tagSelected = tag;
		choosed = tagSelected		
	}
	
  	//read the data on interval
	onMount(() => {
	    interval = setInterval(()=> choosed = tagSelected, 100);		
	  });
  
	//destroy the timer
  	onDestroy(() => clearInterval(interval));
</script>

<div class="card">
	<i>#{user.id}</i>
	<h2>{user.name}</h2>	
	<dl>
		{#each user.tags as tag}		
		<dd on:click={()=>selectIt(tag)} class:apply={choosed == tag}>{tag}</dd>	
		{/each}
	</dl>
</div>

<style>
	.card{width:300px;padding:20px;border-radius:9px;background-color:#F5F7F0;margin-bottom:10px;}
	i{ color:#999;}
	h2{margin:0; color:#491D3C;}
	dl{margin:0; padding:0; margin-top:10px;}
	dd{display:inline-block;margin:0; margin-right:10px; 
		color:#999; padding:1px 10px; border:1px solid #ccc; 
		border-radius:15px; font-size:14px;cursor:pointer;
	}
	.apply{color:#900; background:#ccc;}
	
</style>

CardWithModuleContext.svelte

變量 tagSelected 將在組件的不同實例間共享,理解起來非常有趣,這裏添加一個 100 毫秒的定時器來更新標籤的高亮。如你所見,子組件中的所有邏輯就是用來和其他實例通訊的。

6. Store

隨着應用越來越複雜,增加越來越多的特性,越來越多的組件,複雜度日益增加。這個時候我們需要在組件的層次結構之外保存一些應用的狀態。而 Svelte 內建的 store 就可以實現這個。

在 Svelte 存儲中,可以存單個對象、數組等。Svelte 提供了多種存儲,包括  writable, readable, derived, 或者 custom.

接下來做一個簡單例子實現圖書列表:

BookStore.js

import { writable } from 'svelte/store'
export let bookStore = writable([
 {name:"Hamlet",author:"William Shakespeare"},
 {name:"The Great Gatsby",author:"F. Scott Fitzgerald"}
]);

BookList.svelte

<script>
 import { bookStore } from './BookStore.js' 
</script>
<ul>
 {#each $bookStore as book}
  <li>{book.name} - {book.author}</li>
 {/each} 
</ul>

BookForm.svelte

<script>
 import { bookStore } from './BookStore.js' 
 let bookName;
 let author;
 const addNew = ()=>{
  $bookStore = [{name:bookName, author:author},...$bookStore,];
 }
</script>
<input type="text" bind:value={bookName} placeholder="Book Name"/>
<input type="text" bind:value={author} placeholder="Author Name"/>
<button on:click={addNew}>+ Add Book</button>

App.svelte

<script>
 import BookList from './BookList.svelte'
 import BookForm from './BookForm.svelte' 
</script>
<BookForm/>
<BookList/>

這裏創建了一個 bookStore作爲 writable 數組,通過 $ 這個語法糖在表單和列表組件中引用來訪問數據。

Context vs Store

Context 和 Store 二者比較類似,區別在於 Store 可在應用的任一組件中訪問,而 Context 只能用於子組件。

示例代碼

瞭解本文中介紹的所有示例,這些示例在 Svelte REPL 中可用。要進行測試,請通過導入不同的卡組件來更新 App.svelte 文件,以檢查結果。 (demo)

結論

構建組件之間的通信是應用程序設計中最重要的部分。在Svelte中,我們有用於狀態管理的內置功能,可以給我們很好的靈活性來設計更好的應用程序。

本文翻譯自 https://betterprogramming.pub/6-ways-to-do-component-communications-in-svelte-b3f2a483913c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章