コンポーネント

コンポーネントはPreactの基本的な構成要素です。 コンポーネントは小さな構成要素から複雑なUIを作る際に中心的な役割を果たします。 コンポーネントはレンダリングされたアウトプットに対してステートを付与する役割があります。

Preactには2種類のコンポーネントがあります。このガイドではそれについて説明します。



関数コンポーネント

関数コンポーネントは第1引数にpropsを取る単純な関数です。 JSXで動作させるために関数名は大文字から始める必要があります

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

// 使い方
const App = <MyComponent name="John Doe" />;

// レンダリング結果: <div>My name is John Doe.</div>
render(App, document.body);

注意: 以前のバージョンでは「ステートレスコンポーネント」として知られていましたが、現在はフックを使用することでステートを持つことができます。

クラスコンポーネント

クラスコンポーネントはステートとライフサイクルメソッドを持つことができます。 ライフサイクルメソッドは特別なメソッドです。例えば、コンポーネントがDOMにマウントされた時やDOMから削除された時に実行されます。

以下はという現在の時刻を表示するシンプルなクラスコンポーネントです。

class Clock extends Component {

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

  // ライフサイクルメソッド: コンポーネントがDOMにマウントされた時に実行
  componentDidMount() {
    // 1秒ごとに時刻を更新
    this.timer = setInterval(() => {
      this.setState({ time: Date.now() });
    }, 1000);
  }

  // ライフサイクルメソッド: コンポーネントがDOMから削除される直前に実行
  componentWillUnmount() {
    // レンダリングが不可能なので停止
    clearInterval(this.timer);
  }

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

ライフサイクルメソッド

現在の時計の時刻を1秒ごとに更新するためにいつ<Clock>がDOMにマウントされるかを知る必要があります。 もし、HTML5 Custom Elementsを使ったことがあれば、それはHTML5 Custom ElementsのライフサイクルメソッドであるattachedCallbackdetachedCallbackに似ています。 Preactはコンポーネントに以下のライフサイクルメソッドが定義されている場合、それを実行します。

ライフサイクルメソッド 実行されるタイミング
componentWillMount() (非推奨) コンポーネントがDOMにマウントされる前
componentDidMount() コンポーネントがDOMにマウントされた後
componentWillUnmount() DOMから削除される前
componentWillReceiveProps(nextProps, nextState) (非推奨) 新しいpropsを受け取る前
getDerivedStateFromProps(nextProps) shouldComponentUpdateの直前。注意して使って下さい。
shouldComponentUpdate(nextProps, nextState) render()の前。falseを返したらrenderをスキップする。
componentWillUpdate(nextProps, nextState) (非推奨) render()の前。
getSnapshotBeforeUpdate(prevProps, prevState) render()が実行される直前。戻り値はcomponentDidUpdateに渡される。
componentDidUpdate(prevProps, prevState, snapshot) render()の後

この図を見てこれらが互いにどのように関係しているのか確認しましょう。

componentDidCatch

注目すべきライフサイクルメソッドが1つあります。それはcomponentDidCatchです。 レンダリング中に発生したエラーを扱うことができる点が特別です。 それにはライフサイクルフック中に発生したエラーも含まれます。 しかし、例えば、fetch()を実行した後の非同期にスローされるようなエラーは扱うことができません。

エラーが発生した場合、このライフサイクルメソッドを使ってエラーの対応をしたりエラーメッセージを表示したり代替のコンテンツを表示することができます。

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

  componentDidCatch(error) {
    this.setState({ errored: true });
  }

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

Fragments

Fragmentはrender関数と関数コンポーネントが一度に複数の要素を返すことを可能にします。 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);
// レンダリング結果:
// <ul>
//   <li>A</li>
//   <li>B</li>
//   <li>C</li>
//   <li>D</li>
// </ul>

最新のトランスパイラのほとんどはFragmentsの短いシンタックスを使うことができ、こちらのほうが一般的です。

const Foo = <Fragment>foo</Fragment>;
// 上記は下記と同じ
const Bar = <>foo</>;

render関数と関数コンポーネントは配列を返すこともできます。

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

ループ中にFragmentsを生成する場合はkey属性を付与することを忘れないで下さい。

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
	// キーがない場合、Preactは再レンダリング時、変更された要素を特定できません。
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

Built by a bunch of lovely people ubitools.com