元件
如同我們在本教學課程第一部分中所提到的,虛擬 DOM 應用程式的關鍵建構區塊是元件。元件是應用程式中一個自給自足的部分,可以像 HTML 元素一樣呈現在虛擬 DOM 樹中。您可以將元件視為函式呼叫:兩者都是允許重複使用程式碼和間接引用的機制。
為了說明,我們來建立一個名為 MyButton
的簡單元件,它會傳回一個描述 HTML <button>
元素的虛擬 DOM 樹
function MyButton(props) {
return <button class="my-button">{props.text}</button>
}
我們可以在應用程式中透過在 JSX 中參照它來使用這個元件
let vdom = <MyButton text="Click Me!" />
// remember createElement? here's what the line above compiles to:
let vdom = createElement(MyButton, { text: "Click Me!" })
在任何您使用 JSX 描述 HTML 樹的地方,您也可以描述元件樹。不同的是,元件在 JSX 中使用以大寫字母開頭的名稱來描述,該名稱對應於元件的名稱(JavaScript 變數)。
當 Preact 呈現您的 JSX 所描述的虛擬 DOM 樹時,它所遇到的每個元件函式都會在樹中的那個位置被呼叫。舉例來說,我們可以透過將描述該元件的 JSX 元素傳遞給 render()
來將我們的 MyButton
元件呈現在網頁的本文中
import { render } from 'preact';
render(<MyButton text="Click me!" />, document.body)
巢狀元件
元件可以在它們傳回的虛擬 DOM 樹中參照其他元件。這會建立一個元件樹
function MediaPlayer() {
return (
<div>
<MyButton text="Play" />
<MyButton text="Stop" />
</div>
)
}
render(<MediaPlayer />, document.body)
我們可以使用這個技巧為不同的場景呈現不同的元件樹。讓我們讓 MediaPlayer
在沒有播放聲音時顯示「播放」按鈕,在播放聲音時顯示「停止」按鈕
function MediaPlayer(props) {
return (
<div>
{props.playing ? (
<MyButton text="Stop" />
) : (
<MyButton text="Play" />
)}
</div>
)
}
render(<MediaPlayer playing={false} />, document.body)
// renders <button>Play</button>
render(<MediaPlayer playing={true} />, document.body)
// renders <button>Stop</button>
請記住:JSX 中的
{curly}
大括號讓我們跳回純 JavaScript。這裡我們使用 三元 運算式根據playing
道具的值顯示不同的按鈕。
元件子項
元件也可以像 HTML 元素一樣巢狀。元件成為強大基本元素的原因之一,是因為它們讓我們套用自訂邏輯來控制巢狀在元件中的虛擬 DOM 元素應如何呈現。
運作方式看似簡單:任何巢狀在 JSX 中元件中的虛擬 DOM 元素,都會作為一個特殊的 children
prop 傳遞給該元件。元件可以使用 {children}
表達式在 JSX 中參照其子項,選擇將其放置在哪裡。或者,元件可以簡單地傳回 children
值,而 Preact 會在虛擬 DOM 樹中將這些虛擬 DOM 元素呈現在元件放置的位置。
<Foo>
<a />
<b />
</Foo>
function Foo(props) {
return props.children // [<a />, <b />]
}
回想前一個範例,我們的 MyButton
元件預期一個 text
prop,插入到 <button>
元素中作為其顯示文字。如果我們想顯示圖片而不是文字,該怎麼辦?
讓我們改寫 MyButton
,以允許使用 children
prop 來巢狀
function MyButton(props) {
return <button class="my-button">{props.children}</button>
}
function App() {
return (
<MyButton>
<img src="icon.png" />
Click Me!
</MyButton>
)
}
render(<App />, document.body)
現在我們已經看過幾個元件呈現其他元件的範例,希望開始清楚巢狀元件如何讓我們組裝出許多較小個別部分組成的複雜應用程式。
元件類型
到目前為止,我們已經看過是函式的元件。函式元件將 props
作為其輸入,並傳回虛擬 DOM 樹作為其輸出。元件也可以寫成 JavaScript 類別,由 Preact 執行個體化,並提供一個運作方式很像函式元件的 render()
方法。
類別元件是透過擴充 Preact 的 Component
基底類別來建立的。在下面的範例中,請注意 render()
如何將 props
作為輸入,並回傳一個虛擬 DOM 樹作為輸出,就像一個函式元件一樣!
import { Component } from 'preact';
class MyButton extends Component {
render(props) {
return <button class="my-button">{props.children}</button>
}
}
render(<MyButton>Click Me!</MyButton>, document.body)
我們可能會使用類別來定義元件的原因是為了追蹤元件的生命週期。每次 Preact 在渲染虛擬 DOM 樹時遇到元件,它就會建立一個我們類別的新實體 (new MyButton()
)。
不過,如果你還記得第一章的內容,Preact 可以重複收到新的虛擬 DOM 樹。每次我們給 Preact 一個新的樹,它就會與前一個樹進行比較,以確定兩者之間的差異,然後將這些差異套用至頁面。
當一個元件使用類別定義時,樹中對該元件的任何更新都會重複使用同一個類別實體。這表示可以在類別元件中儲存資料,而這些資料在下一次呼叫其 render()
方法時會可用。
類別元件也可以實作許多 生命週期方法,Preact 會在虛擬 DOM 樹發生變更時呼叫這些方法。
class MyButton extends Component {
componentDidMount() {
console.log('Hello from a new <MyButton> component!')
}
componentDidUpdate() {
console.log('A <MyButton> component was updated!')
}
render(props) {
return <button class="my-button">{props.children}</button>
}
}
render(<MyButton>Click Me!</MyButton>, document.body)
// logs: "Hello from a new <MyButton> component!"
render(<MyButton>Click Me!</MyButton>, document.body)
// logs: "A <MyButton> component was updated!"
類別元件的生命週期讓它們成為建構應用程式區塊的有用工具,這些區塊會對變更做出回應,而不是嚴格地將 props
映射到樹。它們也提供了一種方式,可以在虛擬 DOM 樹中放置它們的每個位置分別儲存資訊。在下一章中,我們將看到元件如何更新它們的樹區段,只要它們想要變更它。
試試看!
為了練習,讓我們將我們在前面兩章中學到的元件知識,與我們的事件技巧結合起來!
建立一個 MyButton
元件,它接受 style
、children
和 onClick
屬性,並傳回一個套用這些屬性的 HTML <button>
元素。