学习教程

¥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!

Preact 中文网 - 粤ICP备13048890号