Help
Support Us

组件

组件为渲染结果添加状态,更是 Preact 的基石和构建复杂界面的基础。

我们将在此教程中展示 Preact 中的两种组件。



函数组件

函数组件是第一个参数为 props 的普通函数,其名称必须以大写字母开头才能在 JSX 中使用。

function MyComponent(props) {
  return <div>我的名字叫{props.name}</div>;
}

// 用法
const App = <MyComponent name="张三" />;

// 渲染结果:<div>我的名字叫张三。</div>
render(App, document.body);
Run in REPL

请注意,在先前的版本中我们将其称之为“无状态组件”,但有了钩子组件后就不是了。

类组件

类组件可以拥有状态及生命周期方法,后者是当组件添加到 DOM 或销毁时调用的特殊方法。

下面是一个显示当前时间的简单类组件 <Clock>

class Clock extends Component {

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

  // 生命周期:在组件创建时调用
  componentDidMount() {
    // 每秒钟更新一次时间
    this.timer = setInterval(() => {
      this.setState({ time: Date.now() });
    }, 1000);
  }

  // 生命周期:在组件销毁时调用
  componentWillUnmount() {
    // 在无法渲染时停止时钟
    clearInterval(this.timer);
  }

  render() {
    let time = new Date(this.state.time).toLocaleTimeString();
    return <span>{time}</span>;
  }
}
Run in REPL

生命周期方法

为了让时钟能每秒钟更新一次事件,我们需要知道 <Clock> 什么时候会被挂载到 DOM 上。如果您用过 HTML5 自定义元素的话,您就会发现这和 attachedCallbackdetachedCallback 生命周期方法很像。Preact 会自动为组件调用下列列表中存在的生命周期方法 :

生命周期方法 被调用时间
componentWillMount() (已弃用) 组件将被挂载到 DOM 前调用
componentDidMount() 组件被挂载到 DOM 后调用
componentWillUnmount() 组件将从 DOM 移除前调用
componentWillReceiveProps(nextProps, nextState) (已弃用) 在传递进新属性前调用
getDerivedStateFromProps(nextProps) shouldComponentUpdate 前调用,请小心使用!
shouldComponentUpdate(nextProps, nextState) render() 前调用,返回 false 来跳过渲染
componentWillUpdate(nextProps, nextState) (已弃用)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;
  }
}
Run in REPL

片段 (Fragment)

Fragment 允许一次返回多个元素,解决了 JSX 每个“代码块”只能有一个根元素的限制。你将会经常在列表、表格、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);
// 渲染结果:
// <ul>
//   <li>A</li>
//   <li>B</li>
//   <li>C</li>
//   <li>D</li>
// </ul>
Run in REPL

请注意,大部分现代转译器支持 Fragments 的简写语法,这种语法更为常见:

// 如下代码
const Foo = <Fragment>foo</Fragment>;
// ...与下列代码等同:
const Bar = <>foo</>;

您也可以从组件中返回数组:

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

若要在循环中创建 Fragments,别忘了为其添加键:

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有键值的话,Preact 需要猜测哪些元素在重渲染时存在变化。
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}