帮助
支持我们

组件

组件表示 Preact 中的基本构建模块。它们是轻松构建复杂 UI 的基本构建模块。它们还负责将状态附加到我们的渲染输出。

Preact 中有两种组件,我们将在本指南中讨论它们。



函数组件

函数组件是接收 `props` 作为第一个参数的普通函数。函数名称**必须**以大写字母开头,才能在 JSX 中工作。

function MyComponent(props) {
  return <div>My name is {props.name}.</div>;
}

// Usage
const App = <MyComponent name="John Doe" />;

// Renders: <div>My name is John Doe.</div>
render(App, document.body);
在 REPL 中运行

请注意,在早期版本中,它们被称为 `“无状态组件”`。有了 hooks-addon,这不再成立。

类组件

类组件可以具有状态和生命周期方法。后者是特殊方法,将在组件附加到 DOM 或销毁时调用。

这里我们有一个名为 <Clock> 的简单类组件,它显示当前时间

class Clock extends Component {

  constructor() {
    super();
    this.state = { time: Date.now() };
  }

  // Lifecycle: Called whenever our component is created
  componentDidMount() {
    // update time every second
    this.timer = setInterval(() => {
      this.setState({ time: Date.now() });
    }, 1000);
  }

  // Lifecycle: 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>;
  }
}
在 REPL 中运行

生命周期方法

为了让时钟的时间每秒更新一次,我们需要知道 <Clock> 何时被挂载到 DOM。如果你使用过 HTML5 自定义元素,这类似于 attachedCallbackdetachedCallback 生命周期方法。如果为组件定义了以下生命周期方法,Preact 将调用这些方法

生命周期方法何时调用
componentWillMount()(已弃用)在组件挂载到 DOM 之前
componentDidMount()在组件挂载到 DOM 之后
componentWillUnmount()在从 DOM 中移除之前
componentWillReceiveProps(nextProps, nextContext)在接受新属性之前(已弃用)
getDerivedStateFromProps(nextProps, prevState)shouldComponentUpdate 之前。返回对象以更新状态或 null 以跳过更新。谨慎使用。
shouldComponentUpdate(nextProps, nextState, nextContext)render() 之前。返回 false 以跳过渲染
componentWillUpdate(nextProps, nextState, nextContext)render() 之前(已弃用)
getSnapshotBeforeUpdate(prevProps, prevState)render() 之前调用。返回值传递给 componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)render() 之后

以下是它们相互关系的可视化概述(最初发布在 Dan Abramov 的推文中

Diagram of component lifecycle methods

错误边界

错误边界是一个组件,它实现 componentDidCatch() 或静态方法 getDerivedStateFromError()(或两者)。这些是特殊方法,允许你捕获渲染期间发生的任何错误,通常用于提供更友好的错误消息或其他后备内容,以及保存用于记录的信息。需要注意的是,错误边界无法捕获所有错误,并且在事件处理程序或异步代码(如 fetch() 调用)中引发的错误需要单独处理。

当捕获到错误时,我们可以使用这些方法对任何错误做出反应并显示一个友好的错误消息或任何其他后备内容。

class ErrorBoundary extends Component {
  constructor() {
    super();
    this.state = { errored: false };
  }

  static getDerivedStateFromError(error) {
    return { errored: true };
  }

  componentDidCatch(error, errorInfo) {
    errorReportingService(error, errorInfo);
  }

  render(props, state) {
    if (state.errored) {
      return <p>Something went badly wrong</p>;
    }
    return props.children;
  }
}
在 REPL 中运行

片段

Fragment 允许你一次返回多个元素。它们解决了 JSX 的限制,即每个“块”必须具有一个根元素。你经常会在列表、表格或 CSS flexbox(其中任何中间元素都会影响样式)中遇到它们。

import { Fragment, render } from 'preact';

function TodoItems() {
  return (
    <Fragment>
      <li>A</li>
      <li>B</li>
      <li>C</li>
    </Fragment>
  )
}

const App = (
  <ul>
    <TodoItems />
    <li>D</li>
  </ul>
);

render(App, container);
// Renders:
// <ul>
//   <li>A</li>
//   <li>B</li>
//   <li>C</li>
//   <li>D</li>
// </ul>
在 REPL 中运行

请注意,大多数现代转译器允许你使用更短的 Fragment 语法。较短的语法更常见,也是你通常会遇到的语法。

// This:
const Foo = <Fragment>foo</Fragment>;
// ...is the same as this:
const Bar = <>foo</>;

你还可以从组件中返回数组

function Columns() {
  return [
    <td>Hello</td>,
    <td>World</td>
  ];
}

如果你在循环中创建 Fragment,请不要忘记添加键

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Without a key, Preact has to guess which elements have
        // changed when re-rendering.
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}