组件
正如我们在本教程的第一部分中提到的,虚拟 DOM 应用程序中的关键构建块是组件。组件是应用程序中一个自包含的部分,可以作为虚拟 DOM 树的一部分进行渲染,就像 HTML 元素一样。您可以将组件视为函数调用:两者都是允许代码重用和间接调用的机制。
为了说明这一点,让我们创建一个名为 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)
嵌套组件
组件可以引用它们返回的 Virtual 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
prop 的值显示不同的按钮。
组件子级
组件也可以像 HTML 元素一样嵌套。组件成为强大原语的原因之一是,它们让我们应用自定义逻辑来控制嵌套在组件中的 Virtual DOM 元素的呈现方式。
其工作方式看似简单:JSX 中嵌套在组件中的任何 Virtual DOM 元素都会作为特殊的 children
prop 传递给该组件。组件可以通过使用 {children}
表达式在 JSX 中引用它们来选择放置其子级的位置。或者,组件可以简单地返回 children
值,Preact 会将这些 Virtual DOM 元素直接呈现到组件在 Virtual DOM 树中放置的位置。
<Foo>
<a />
<b />
</Foo>
function Foo(props) {
return props.children // [<a />, <b />]
}
回想一下前面的示例,我们的 MyButton
组件期望一个 text
prop,该 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
作为其输入,并返回 Virtual DOM 树作为其输出。组件也可以编写为 JavaScript 类,这些类由 Preact 实例化,并提供一个 render()
方法,其工作方式与函数组件非常相似。
类组件是通过扩展 Preact 的 Component
基类创建的。在下面的示例中,请注意 render()
如何以 props
作为其输入,并返回 Virtual 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 在呈现 Virtual DOM 树时遇到组件时,它都会创建我们类的实例 (new MyButton()
)。
但是,如果你还记得第一章的内容 - Preact 可以反复获得新的 Virtual 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>
元素。