学习教程
¥Tutorial
本指南将引导你构建一个简单的 "滴答作响的时钟" 组件。如果你是 Virtual DOM 新手,请尝试 完整的 Preact 教程。
¥This guide walks through building a simple "ticking clock" component. If you're new to Virtual DOM, try the full Preact tutorial.
💁本指南假设你已完成 入门 文档并已成功设置工具。如果没有,请从 Vite 开始。
¥💁 This guide assumes that you completed the Getting Started document and have successfully set up your tooling. If not, start with Vite.
你好世界
¥Hello World
开箱即用后,你在任何 Preact 代码库中总会看到的两个函数是 h()
和 render()
。h()
函数用于将 JSX 转换为 Preact 可以理解的结构。但它也可以直接使用,而不涉及任何 JSX:
¥Out of the box, the two functions you'll always see in any Preact codebase are h()
and render()
. The h()
function is used to turn JSX into a structure Preact understands. But it can also be used directly without any JSX involved:
// With JSX
const App = <h1>Hello World!</h1>;
// ...the same without JSX
const App = h('h1', null, 'Hello World');
仅此一点并不能起到任何作用,我们需要一种方法将 Hello-World 应用注入到 DOM 中。为此,我们使用 render()
函数。
¥This alone doesn't do anything and we need a way to inject our Hello-World app into the DOM. For this we use the render()
function.
import { render } from 'preact';
const App = <h1>Hello World!</h1>;
// Inject our app into the DOM
render(App, document.getElementById('app'));
Run in REPL恭喜,你已经构建了你的第一个 Preact 应用!
¥Congratulations, you've build your first Preact app!
互动你好世界
¥Interactive Hello World
渲染文本是一个开始,但我们想让我们的应用更具交互性。我们希望在数据发生变化时更新它。🌟
¥Rendering text is a start, but we want to make our app a little more interactive. We want to update it when data changes. 🌟
我们的最终目标是拥有一个应用,用户可以在提交表单时输入名称并显示该名称。为此,我们需要有一些东西可以存储我们提交的内容。这就是 组件 发挥作用的地方。
¥Our end goal is that we have an app where the user can enter a name and display it, when the form is submitted. For this we need to have something where we can store what we submitted. This is where Components come into play.
那么让我们把现有的 App 变成 组件:
¥So let's turn our existing App into a Components:
import { h, render, Component } from 'preact';
class App extends Component {
render() {
return <h1>Hello, world!</h1>;
}
}
render(<App />, document.getElementById("app"));
Run in REPL你会注意到,我们在顶部添加了新的 Component
导入,并将 App
变成了一个类。仅此一点是没有用的,但它是我们下一步要做的事情的先驱。为了让事情变得更令人兴奋,我们将添加一个带有文本输入和提交按钮的表单。
¥You'll notice that we added a new Component
import at the top and that we turned App
into a class. This alone isn't useful but it's the precursor for what we're going to do next. To make things a little more exciting we'll add a form with a text input and a submit button.
import { h, render, Component } from 'preact';
class App extends Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<form>
<input type="text" />
<button type="submit">Update</button>
</form>
</div>
);
}
}
render(<App />, document.getElementById("app"));
Run in REPL现在我们正在说话!它开始看起来像一个真正的应用!但我们仍然需要使其具有交互性。请记住,我们需要将 "Hello world!"
更改为 "Hello, [userinput]!"
,因此我们需要一种方法来了解当前输入值。
¥Now we're talking! It's starting to look like a real app! We still need to make it interactive though. Remember that we'll want to change "Hello world!"
to "Hello, [userinput]!"
, so we need a way to know the current input value.
我们将其存储在组件的一个名为 state
的特殊属性中。它很特别,因为当通过 setState
方法更新它时,Preact 不仅会更新状态,还会安排该组件的渲染请求。处理请求后,我们的组件将使用更新后的状态重新渲染。
¥We'll store it in a special property called state
of our Component. It's special, because when it's updated via the setState
method, Preact will not just update the state, but also schedule a render request for this component. Once the request is handled, our component will be re-rendered with the updated state.
最后,我们需要通过设置 value
并将事件处理程序附加到 input
事件来将新状态附加到我们的输入。
¥Lastly we need to attach the new state to our input by setting value
and attaching an event handler to the input
event.
import { h, render, Component } from 'preact';
class App extends Component {
// Initialise our state. For now we only store the input value
state = { value: '' }
onInput = ev => {
// This will schedule a state update. Once updated the component
// will automatically re-render itself.
this.setState({ value: ev.currentTarget.value });
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<form>
<input type="text" value={this.state.value} onInput={this.onInput} />
<button type="submit">Update</button>
</form>
</div>
);
}
}
render(<App />, document.getElementById("app"));
Run in REPL此时,从用户的角度来看,应用应该不会发生太大变化,但我们将在下一步中将所有部分整合在一起。
¥At this point the app shouldn't have changed much from a users point of view, but we'll bring all the pieces together in our next step.
我们将以类似的方式向 <form>
的 submit
事件添加一个处理程序,就像我们刚刚为输入所做的那样。不同之处在于它写入了 state
的另一个属性,称为 name
。然后我们交换标题并在那里插入 state.name
值。
¥We'll add a handler to the submit
event of our <form>
in similar fashion like we just did for the input. The difference is that it writes into a different property of our state
called name
. Then we swap out our heading and insert our state.name
value there.
import { h, render, Component } from 'preact';
class App extends Component {
// Add `name` to the initial state
state = { value: '', name: 'world' }
onInput = ev => {
this.setState({ value: ev.currentTarget.value });
}
// Add a submit handler that updates the `name` with the latest input value
onSubmit = ev => {
// Prevent default browser behavior (aka don't submit the form here)
ev.preventDefault();
this.setState({ name: this.state.value });
}
render() {
return (
<div>
<h1>Hello, {this.state.name}!</h1>
<form onSubmit={this.onSubmit}>
<input type="text" value={this.state.value} onInput={this.onInput} />
<button type="submit">Update</button>
</form>
</div>
);
}
}
render(<App />, document.getElementById("app"));
Run in REPL繁荣!我们完成了!现在,我们可以输入自定义名称,单击 "更新",我们的新名称就会出现在标题中。
¥Boom! We're done! We can now enter a custom name, click "Update" and our new name appears in our heading.
时钟组件
¥A Clock Component
我们编写了第一个组件,所以让我们多做一些练习。这次我们构建了一个时钟。
¥We wrote our first component, so let's get a little more practice. This time we build a clock.
import { h, render, Component } from 'preact';
class Clock extends Component {
render() {
let time = new Date().toLocaleTimeString();
return <span>{time}</span>;
}
}
render(<Clock />, document.getElementById("app"));
Run in REPL好吧,这很容易!问题是,时间不会改变。当我们渲染时钟组件时它就被冻结了。
¥Ok, that was easy enough! Problem is, that the time doesn't change. It's frozen at the moment we rendered our clock component.
因此,我们希望在组件添加到 DOM 后启动一个 1 秒计时器,并在组件被删除时停止计时器。我们将创建计时器并将其引用存储在 componentDidMount
中,并在 componentWillUnmount
中停止计时器。在每个计时器滴答声中,我们将使用新的时间值更新组件的 state
对象。这样做会自动重新渲染组件。
¥So, we want to have a 1-second timer start once the Component gets added to the DOM, and stop if it is removed. We'll create the timer and store a reference to it in componentDidMount
, and stop the timer in componentWillUnmount
. On each timer tick, we'll update the component's state
object with a new time value. Doing this will automatically re-render the component.
import { h, render, Component } from 'preact';
class Clock extends Component {
state = { time: Date.now() };
// Called whenever our component is created
componentDidMount() {
// update time every second
this.timer = setInterval(() => {
this.setState({ time: Date.now() });
}, 1000);
}
// Called just before our component will be destroyed
componentWillUnmount() {
// stop when not renderable
clearInterval(this.timer);
}
render() {
let time = new Date(this.state.time).toLocaleTimeString();
return <span>{time}</span>;
}
}
render(<Clock />, document.getElementById("app"));
Run in REPL我们又做到了!现在我们有 滴答作响的时钟 了!
¥And we did it again! Now we have a ticking clock!