T-CREATOR

Preact チートシート【保存版】:JSX/Props/Events/Ref の書き方早見表

Preact チートシート【保存版】:JSX/Props/Events/Ref の書き方早見表

Preact は React の軽量な代替フレームワークとして、多くの開発者に選ばれています。React とほぼ同じ API を持ちながら、わずか 3KB というサイズで高速に動作するのが特徴です。しかし、実際にコードを書く際に「あの書き方、どうだったかな?」と迷うことはありませんか?

本記事では、Preact の基本的な書き方を網羅したチートシートとして、JSX、Props、Events、Ref の各機能について、すぐに使えるコード例とともに解説します。この記事を保存しておけば、開発中に迷ったときにすぐに参照できるでしょう。

早見表

開発中にすぐ参照できるよう、主要な書き方を早見表にまとめました。

JSX 早見表

#項目書き方
1基本要素<div>...<​/​div><div>Hello<​/​div>
2classNameclassName="..."<div className="container">
3インラインスタイルstyle={{...}}<div style={{ color: 'red' }}>
4三項演算子{条件 ? A : B}{isActive ? <p>ON<​/​p> : <p>OFF<​/​p>}
5論理 AND{条件 && 要素}{show && <p>表示<​/​p>}
6リスト{配列.map(...)}{items.map(item => <li key={item.id}>{item.name}<​/​li>)}
7Fragment<Fragment>...<​/​Fragment><Fragment><h1>Title<​/​h1><p>Text<​/​p><​/​Fragment>
8Fragment 短縮<>...<​/​><><h1>Title<​/​h1><p>Text<​/​p><​/​>

Props 早見表

#項目書き方
1基本的な Props(props: Type)const Child = (props: ChildProps) => {...}
2分割代入({ prop1, prop2 }: Type)const Button = ({ label, disabled }: ButtonProps) => {...}
3デフォルト値({ prop = 値 }: Type)const Counter = ({ count = 0 }: Props) => {...}
4オプショナルprop?: typeinterface Props { label?: string; }
5Childrenchildren: ComponentChildrenconst Card = ({ children }: { children: ComponentChildren }) => {...}
6関数 PropsonEvent: (arg: Type) => voidinterface Props { onClick: (id: number) => void; }
7スプレッド展開{...props}<input {...inputProps} ​/​>
8Union 型prop: 'a' | 'b' | 'c'status: 'success' | 'error' | 'warning'

Events 早見表

#イベント書き方
1クリックonClick={handler}<button onClick={handleClick}>Click<​/​button>
2フォーム送信onSubmit={handler}<form onSubmit={handleSubmit}>
3入力変更onInput={handler}<input onInput={(e) => setValue(e.target.value)} ​/​>
4ChangeonChange={handler}<select onChange={handleChange}>
5キーボードonKeyDown={handler}<input onKeyDown={(e) => { if (e.key === 'Enter') {...} }} ​/​>
6マウス EnteronMouseEnter={handler}<div onMouseEnter={handleEnter}>
7マウス LeaveonMouseLeave={handler}<div onMouseLeave={handleLeave}>
8マウス MoveonMouseMove={handler}<div onMouseMove={(e) => console.log(e.clientX)}>
9preventDefaulte.preventDefault()const handler = (e) => { e.preventDefault(); ... }
10stopPropagatione.stopPropagation()const handler = (e) => { e.stopPropagation(); ... }

Ref 早見表

#項目書き方
1Ref 作成useRef<Type>(初期値)const inputRef = useRef<HTMLInputElement>(null);
2Ref 設定ref={refオブジェクト}<input ref={inputRef} ​/​>
3DOM アクセスref.currentinputRef.current?.focus()
4コールバック Refref={(el) => {...}}<div ref={(el) => console.log(el)}>
5forwardRefforwardRef<Type, Props>((props, ref) => {...})const Input = forwardRef<HTMLInputElement, Props>((props, ref) => <input ref={ref} ​/​>)
6useImperativeHandleuseImperativeHandle(ref, () => ({...}))useImperativeHandle(ref, () => ({ focus: () => {...} }))
7複数 Ref(配列)useRef<Type[]>([])const refs = useRef<(HTMLElement | null)[]>([]);
8null チェックif (ref.current) {...}if (inputRef.current) { inputRef.current.focus(); }

インポート早見表

#項目インポート文
1h 関数import { h } from 'preact';
2Fragmentimport { Fragment } from 'preact';
3ComponentChildrenimport { ComponentChildren } from 'preact';
4useStateimport { useState } from 'preact​/​hooks';
5useRefimport { useRef } from 'preact​/​hooks';
6useEffectimport { useEffect } from 'preact​/​hooks';
7forwardRefimport { forwardRef } from 'preact​/​compat';
8useImperativeHandleimport { useImperativeHandle } from 'preact​/​compat';

背景

Preact の特徴と位置づけ

Preact は React の API をベースに設計された軽量フレームワークです。React と同じような書き方ができるため、React の知識があればすぐに始められます。

以下の図は、Preact と React の関係性を示しています。

mermaidflowchart LR
  react["React<br/>(120KB+)"]
  preact["Preact<br/>(3KB)"]
  preact_compat["preact/compat<br/>互換レイヤー"]

  react -.互換性.-> preact_compat
  preact_compat --> preact

  style preact fill:#673ab8,color:#fff
  style react fill:#61dafb,color:#000

Preact の主な利点は次のとおりです。

#特徴詳細
1軽量性わずか 3KB のサイズで、高速な読み込みを実現
2React 互換React とほぼ同じ API で、学習コストが低い
3パフォーマンス最適化された仮想 DOM で高速レンダリング
4シンプル余計な機能を削ぎ落とし、本質的な機能に集中

Preact の基本構成要素

Preact でアプリケーションを構築する際、主に以下の 4 つの要素を理解する必要があります。

mermaidflowchart TB
  component["コンポーネント"]
  jsx["JSX<br/>UI の記述"]
  props["Props<br/>データの受け渡し"]
  events["Events<br/>ユーザー操作"]
  ref["Ref<br/>DOM アクセス"]

  component --> jsx
  component --> props
  component --> events
  component --> ref

  style component fill:#673ab8,color:#fff

これらの要素を組み合わせることで、インタラクティブな UI を構築できます。本記事では、これら 4 つの要素の具体的な書き方を順に解説していきますね。

課題

Preact 開発における典型的な課題

Preact を使い始めた開発者が直面する課題は、主に以下の 3 点に集約されます。

1. JSX の記法バリエーション

JSX には条件分岐、ループ、フラグメントなど、複数の記法があります。どの場面でどの書き方を使うべきか、混乱しやすいポイントです。

2. Props の型安全性と受け渡し

TypeScript を使う場合、Props の型定義や、デフォルト値の設定、分割代入の使い方など、適切なパターンを知っておく必要があります。

3. イベントハンドリングと DOM 操作

イベントハンドラの書き方、カスタムイベントの実装、直接 DOM を操作する Ref の使い方など、実装パターンが多岐にわたるため、迷いやすいでしょう。

以下の図は、これらの課題の関係性を示しています。

mermaidflowchart TB
  start["Preact 開発開始"]
  issue1["JSX の記法<br/>どう書くべき?"]
  issue2["Props の型定義<br/>どう設計する?"]
  issue3["Event / Ref<br/>どう実装する?"]
  result["開発の停滞<br/>検索時間の増加"]

  start --> issue1
  start --> issue2
  start --> issue3

  issue1 --> result
  issue2 --> result
  issue3 --> result

  style result fill:#ff5252,color:#fff

これらの課題を解決するには、各機能の書き方を体系的に整理し、すぐに参照できる形でまとめておくことが重要です。

解決策

チートシート形式での体系的整理

本記事では、Preact の主要な書き方を以下の 4 つのカテゴリに分類し、それぞれ具体的なコード例とともに解説します。

mermaidflowchart LR
  cheatsheet["Preact<br/>チートシート"]
  jsx_section["JSX セクション<br/>UI 記述"]
  props_section["Props セクション<br/>データ受け渡し"]
  events_section["Events セクション<br/>イベント処理"]
  ref_section["Ref セクション<br/>DOM 操作"]

  cheatsheet --> jsx_section
  cheatsheet --> props_section
  cheatsheet --> events_section
  cheatsheet --> ref_section

  style cheatsheet fill:#673ab8,color:#fff

各セクションは独立しているため、必要な部分だけを参照できます。それでは、順番に見ていきましょう。

具体例

JSX の書き方

JSX は Preact で UI を記述するための構文です。HTML に似た記法で、JavaScript の中に直接マークアップを書くことができます。

基本的な JSX

最もシンプルな JSX の書き方です。

typescriptimport { h } from 'preact';

// シンプルな要素
const SimpleElement = () => {
  return <div>Hello, Preact!</div>;
};

h 関数をインポートすることで、JSX を使えるようになります。h は Hyperscript の略で、仮想 DOM を生成する関数です。

属性とプロパティ

HTML 属性や DOM プロパティを JSX で指定する方法です。

typescript// 属性の設定
const ElementWithAttributes = () => {
  return (
    <div
      className='container' // class  className
      id='main'
      data-test='value' // data 属性
    >
      <input
        type='text'
        placeholder='Enter text'
        disabled={false} // 真偽値属性
      />
    </div>
  );
};

注意点として、HTML の class 属性は JSX では className として記述します。これは JavaScript の予約語との衝突を避けるためです。

スタイルの適用

インラインスタイルを適用する場合、オブジェクト形式で記述します。

typescript// インラインスタイル
const StyledElement = () => {
  const styles = {
    color: 'blue',
    fontSize: '16px', // キャメルケースで記述
    backgroundColor: '#f0f0f0',
  };

  return <div style={styles}>スタイル付きテキスト</div>;
};

CSS プロパティ名はキャメルケース(例:backgroundColor)で記述し、値は文字列で指定します。

条件付きレンダリング

条件によって表示内容を切り替える方法です。

typescript// 三項演算子
const ConditionalRender = ({
  isLoggedIn,
}: {
  isLoggedIn: boolean;
}) => {
  return (
    <div>
      {isLoggedIn ? (
        <p>ログイン済み</p>
      ) : (
        <p>ログインしてください</p>
      )}
    </div>
  );
};

三項演算子を使うことで、簡潔に条件分岐を表現できます。

typescript// 論理 AND 演算子
const ConditionalRenderAnd = ({
  showMessage,
}: {
  showMessage: boolean;
}) => {
  return (
    <div>{showMessage && <p>メッセージを表示</p>}</div>
  );
};

&& 演算子を使えば、条件が真のときのみ要素を表示できますね。

リストのレンダリング

配列データをループして表示する方法です。

typescript// map を使ったリスト
const ListRender = () => {
  const items = ['Apple', 'Banana', 'Orange'];

  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li> // key 属性は必須
      ))}
    </ul>
  );
};

map メソッドで配列をループし、各要素に key 属性を付与します。key は Preact が要素を識別するために使われます。

typescript// オブジェクト配列のリスト
interface User {
  id: number;
  name: string;
}

const UserList = ({ users }: { users: User[] }) => {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {' '}
          // ユニークな ID を key に{user.name}
        </li>
      ))}
    </ul>
  );
};

オブジェクトの場合、ユニークな ID を key に使用するのがベストプラクティスです。

フラグメント

複数の要素をグループ化する際、不要な DOM ノードを作らない方法です。

typescriptimport { Fragment } from 'preact';

// Fragment を使った記法
const FragmentExample = () => {
  return (
    <Fragment>
      <h1>タイトル</h1>
      <p>本文</p>
    </Fragment>
  );
};

Fragment を使うと、余計な div などを追加せずに複数要素を返せます。

typescript// 短縮記法
const FragmentShorthand = () => {
  return (
    <>
      <h1>タイトル</h1>
      <p>本文</p>
    </>
  );
};

<>...<​/​> という短縮記法も使えますが、key 属性が必要な場合は Fragment を使います。

JSX セクションのまとめ(図で理解できる要点)

  • JSX は h 関数をベースに仮想 DOM を生成する
  • 条件分岐やループは JavaScript の式として記述できる
  • Fragment を使えば不要な DOM ノードを削減できる

Props の書き方

Props はコンポーネント間でデータを受け渡すための仕組みです。親コンポーネントから子コンポーネントへ、一方向にデータが流れます。

基本的な Props

最もシンプルな Props の受け渡し方法です。

typescript// 親コンポーネント
const Parent = () => {
  return <Child message='Hello from parent' />;
};

// 子コンポーネント
interface ChildProps {
  message: string;
}

const Child = (props: ChildProps) => {
  return <div>{props.message}</div>;
};

TypeScript を使う場合、Props の型を定義することで型安全性が高まります。

分割代入を使った Props

Props を分割代入で受け取ることで、コードがより簡潔になります。

typescript// 分割代入
interface ButtonProps {
  label: string;
  disabled?: boolean; // オプショナル
}

const Button = ({ label, disabled }: ButtonProps) => {
  return <button disabled={disabled}>{label}</button>;
};

関数の引数で直接分割代入すると、props.label ではなく label と書けるため、可読性が向上します。

デフォルト値の設定

Props にデフォルト値を設定する方法です。

typescript// デフォルト値(方法1:分割代入)
interface CounterProps {
  initialCount?: number;
}

const Counter = ({ initialCount = 0 }: CounterProps) => {
  return <div>Count: {initialCount}</div>;
};

分割代入の際に = でデフォルト値を指定できます。

typescript// デフォルト値(方法2:defaultProps)
const Counter2 = (props: CounterProps) => {
  return <div>Count: {props.initialCount}</div>;
};

Counter2.defaultProps = {
  initialCount: 0,
};

defaultProps を使う方法もありますが、TypeScript では分割代入の方が型推論が効きやすいでしょう。

Children Props

子要素を Props として受け取る方法です。

typescriptimport { ComponentChildren } from 'preact';

// children を受け取る
interface CardProps {
  title: string;
  children: ComponentChildren;
}

const Card = ({ title, children }: CardProps) => {
  return (
    <div className='card'>
      <h2>{title}</h2>
      <div className='content'>{children}</div>
    </div>
  );
};

children は特別な Props で、コンポーネントタグの間に書かれた内容が渡されます。

typescript// Card の使用例
const App = () => {
  return (
    <Card title='タイトル'>
      <p>カードの内容</p>
      <p>複数の要素も OK</p>
    </Card>
  );
};

このように、柔軟なコンポーネント設計ができますね。

関数を Props として渡す

イベントハンドラなどの関数を Props として渡す方法です。

typescript// 関数を Props として定義
interface SearchBoxProps {
  onSearch: (query: string) => void;
}

const SearchBox = ({ onSearch }: SearchBoxProps) => {
  const handleSubmit = (e: Event) => {
    e.preventDefault();
    const input = (e.target as HTMLFormElement).query;
    onSearch(input.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type='text' name='query' />
      <button type='submit'>検索</button>
    </form>
  );
};

関数の型は (引数の型) => 戻り値の型 という形式で定義します。

typescript// 親コンポーネントでの使用
const ParentComponent = () => {
  const handleSearch = (query: string) => {
    console.log('検索:', query);
  };

  return <SearchBox onSearch={handleSearch} />;
};

親から子へコールバック関数を渡すことで、子から親へデータを伝えられます。

スプレッド演算子での Props 展開

複数の Props をまとめて渡す方法です。

typescript// スプレッド演算子
interface InputProps {
  type: string;
  placeholder: string;
  required: boolean;
}

const Input = (props: InputProps) => {
  return <input {...props} />; // すべての Props を展開
};

// 使用例
const Form = () => {
  const inputProps: InputProps = {
    type: 'email',
    placeholder: 'メールアドレス',
    required: true,
  };

  return <Input {...inputProps} />;
};

{...props} とすることで、オブジェクトのすべてのプロパティを展開して渡せます。

Props のバリデーション

Props の型チェックをより厳密に行う方法です。

typescript// Union 型
interface StatusProps {
  status: 'success' | 'error' | 'warning';
}

const StatusMessage = ({ status }: StatusProps) => {
  const colors = {
    success: 'green',
    error: 'red',
    warning: 'orange',
  };

  return (
    <div style={{ color: colors[status] }}>
      Status: {status}
    </div>
  );
};

Union 型を使うことで、特定の値のみを許可できます。

Props セクションのまとめ(図で理解できる要点)

  • Props は親から子への一方向データフローを実現する
  • TypeScript の型定義で Props の安全性を高められる
  • 分割代入やデフォルト値を使うことでコードが簡潔になる

Events の書き方

Preact では、DOM イベントをリスナーとして登録し、ユーザーの操作に応答します。

基本的なイベントハンドリング

クリックイベントなど、基本的なイベントの処理方法です。

typescript// onClick イベント
const ClickButton = () => {
  const handleClick = () => {
    console.log('クリックされました');
  };

  return <button onClick={handleClick}>クリック</button>;
};

イベントハンドラは on + イベント名(キャメルケース) という形式で指定します。

typescript// インラインでイベントハンドラを定義
const InlineEventButton = () => {
  return (
    <button
      onClick={() => console.log('インラインクリック')}
    >
      クリック
    </button>
  );
};

シンプルな処理であれば、インラインで定義することもできますね。

イベントオブジェクト

イベントハンドラには自動的にイベントオブジェクトが渡されます。

typescript// イベントオブジェクトの利用
const EventObjectExample = () => {
  const handleClick = (e: MouseEvent) => {
    console.log('クリック位置:', e.clientX, e.clientY);
    // デフォルト動作を防ぐ
    e.preventDefault();
  };

  return (
    <a href='#' onClick={handleClick}>
      リンク
    </a>
  );
};

preventDefault() でデフォルト動作を防いだり、stopPropagation() でイベントの伝播を止めたりできます。

フォームイベント

フォームの送信や入力変更を処理する方法です。

typescriptimport { useState } from 'preact/hooks';

// フォーム送信
const FormSubmit = () => {
  const [value, setValue] = useState('');

  const handleSubmit = (e: Event) => {
    e.preventDefault(); // ページリロードを防ぐ
    console.log('送信:', value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type='text'
        value={value}
        onInput={(e) =>
          setValue((e.target as HTMLInputElement).value)
        }
      />
      <button type='submit'>送信</button>
    </form>
  );
};

onSubmit でフォーム送信を、onInput で入力変更を処理します。

typescript// Change イベント
const ChangeEvent = () => {
  const [selected, setSelected] = useState('');

  const handleChange = (e: Event) => {
    const value = (e.target as HTMLSelectElement).value;
    setSelected(value);
  };

  return (
    <select onChange={handleChange} value={selected}>
      <option value=''>選択してください</option>
      <option value='1'>オプション1</option>
      <option value='2'>オプション2</option>
    </select>
  );
};

onChange はセレクトボックスやチェックボックスでよく使われます。

キーボードイベント

キーボード操作を処理する方法です。

typescript// キーボードイベント
const KeyboardEvent = () => {
  const handleKeyDown = (e: KeyboardEvent) => {
    if (e.key === 'Enter') {
      console.log('Enter が押されました');
    } else if (e.key === 'Escape') {
      console.log('Escape が押されました');
    }
  };

  return (
    <input
      type='text'
      onKeyDown={handleKeyDown}
      placeholder='キーを押してみてください'
    />
  );
};

e.key でどのキーが押されたかを判定できます。

マウスイベント

マウスの動きや状態を処理する方法です。

typescript// マウスイベント
const MouseEvents = () => {
  const handleMouseEnter = () => {
    console.log('マウスが入りました');
  };

  const handleMouseLeave = () => {
    console.log('マウスが出ました');
  };

  const handleMouseMove = (e: MouseEvent) => {
    console.log('マウス位置:', e.clientX, e.clientY);
  };

  return (
    <div
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      onMouseMove={handleMouseMove}
      style={{
        width: '200px',
        height: '200px',
        border: '1px solid black',
      }}
    >
      ホバーエリア
    </div>
  );
};

onMouseEnteronMouseLeaveonMouseMove などのイベントでマウス操作を検出できます。

カスタムイベント

コンポーネント間でカスタムイベントを発火する方法です。

typescript// カスタムイベントを発火する子コンポーネント
interface CustomEventChildProps {
  onCustomEvent: (data: { message: string }) => void;
}

const CustomEventChild = ({
  onCustomEvent,
}: CustomEventChildProps) => {
  const handleClick = () => {
    // カスタムイベントを発火
    onCustomEvent({ message: 'カスタムデータ' });
  };

  return (
    <button onClick={handleClick}>イベント発火</button>
  );
};

Props として関数を受け取り、その関数を呼び出すことでカスタムイベントを実装できます。

typescript// 親コンポーネントでカスタムイベントを受け取る
const CustomEventParent = () => {
  const handleCustomEvent = (data: { message: string }) => {
    console.log('受信:', data.message);
  };

  return (
    <CustomEventChild onCustomEvent={handleCustomEvent} />
  );
};

この仕組みにより、子から親へのデータ送信が可能になりますね。

イベントの委譲

複数の要素のイベントを親要素でまとめて処理する方法です。

typescript// イベント委譲
const EventDelegation = () => {
  const handleClick = (e: MouseEvent) => {
    const target = e.target as HTMLElement;

    // クリックされた要素が button の場合
    if (target.tagName === 'BUTTON') {
      console.log(
        'ボタンがクリックされました:',
        target.textContent
      );
    }
  };

  return (
    <div onClick={handleClick}>
      <button>ボタン1</button>
      <button>ボタン2</button>
      <button>ボタン3</button>
    </div>
  );
};

親要素でイベントを受け取ることで、各子要素にハンドラを設定する必要がなくなります。

Events セクションのまとめ(図で理解できる要点)

  • イベントハンドラは on + イベント名 の形式で指定する
  • イベントオブジェクトを使って詳細な情報にアクセスできる
  • カスタムイベントは Props の関数を通じて実装する

Ref の書き方

Ref は DOM 要素やコンポーネントインスタンスへの直接参照を保持する仕組みです。

基本的な Ref の使い方

useRef フックを使って Ref を作成します。

typescriptimport { useRef } from 'preact/hooks';

// 基本的な Ref
const BasicRef = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    // Ref 経由で DOM にアクセス
    if (inputRef.current) {
      inputRef.current.focus(); // フォーカスを当てる
    }
  };

  return (
    <div>
      <input ref={inputRef} type='text' />
      <button onClick={handleClick}>フォーカス</button>
    </div>
  );
};

useRef で作成した Ref オブジェクトを、要素の ref 属性に渡します。実際の DOM 要素には ref.current でアクセスできます。

Ref で DOM を操作する

Ref を使って DOM を直接操作する例です。

typescript// DOM 操作
const DOMManipulation = () => {
  const divRef = useRef<HTMLDivElement>(null);

  const changeColor = () => {
    if (divRef.current) {
      divRef.current.style.backgroundColor = 'lightblue';
    }
  };

  const changeText = () => {
    if (divRef.current) {
      divRef.current.textContent =
        'テキストが変更されました';
    }
  };

  return (
    <div>
      <div ref={divRef}>元のテキスト</div>
      <button onClick={changeColor}>色を変更</button>
      <button onClick={changeText}>テキストを変更</button>
    </div>
  );
};

通常は State を使って UI を更新しますが、アニメーションやサードパーティライブラリとの統合など、直接 DOM 操作が必要な場面で Ref が活躍します。

複数の Ref を管理する

配列やオブジェクトで複数の Ref を管理する方法です。

typescript// 複数の Ref(配列)
const MultipleRefs = () => {
  const itemRefs = useRef<(HTMLLIElement | null)[]>([]);

  const focusItem = (index: number) => {
    if (itemRefs.current[index]) {
      itemRefs.current[index]?.scrollIntoView({
        behavior: 'smooth',
      });
    }
  };

  const items = [
    'アイテム1',
    'アイテム2',
    'アイテム3',
    'アイテム4',
  ];

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li
            key={index}
            ref={(el) => (itemRefs.current[index] = el)}
          >
            {item}
          </li>
        ))}
      </ul>
      <button onClick={() => focusItem(2)}>
        3番目にスクロール
      </button>
    </div>
  );
};

ref 属性にコールバック関数を渡すことで、動的に Ref を設定できます。

Ref のコールバック形式

Ref が設定・解除されるタイミングで処理を実行する方法です。

typescript// Ref コールバック
const RefCallback = () => {
  const setRef = (element: HTMLInputElement | null) => {
    if (element) {
      console.log('Ref が設定されました');
      element.focus();
    } else {
      console.log('Ref が解除されました');
    }
  };

  return <input ref={setRef} type='text' />;
};

コールバック形式の Ref は、要素がマウントされたときと、アンマウントされたときに呼び出されます。

親コンポーネントから子の Ref にアクセス

forwardRef を使って、親から子コンポーネントの DOM にアクセスする方法です。

typescriptimport { forwardRef } from 'preact/compat';
import { Ref } from 'preact';

// forwardRef で Ref を転送
interface CustomInputProps {
  placeholder?: string;
}

const CustomInput = forwardRef<
  HTMLInputElement,
  CustomInputProps
>(({ placeholder }, ref) => {
  return (
    <input
      ref={ref}
      type='text'
      placeholder={placeholder}
    />
  );
});

forwardRef でラップすることで、親から渡された Ref を子の DOM 要素に転送できます。

typescript// 親コンポーネントで使用
const ParentWithForwardRef = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <CustomInput
        ref={inputRef}
        placeholder='テキスト入力'
      />
      <button onClick={handleFocus}>フォーカス</button>
    </div>
  );
};

これにより、カプセル化されたコンポーネントでも柔軟に DOM 操作ができますね。

useImperativeHandle で公開メソッドを制御

子コンポーネントが親に公開するメソッドを制御する方法です。

typescriptimport {
  useImperativeHandle,
  forwardRef,
} from 'preact/compat';
import { useRef } from 'preact/hooks';

// 公開するメソッドの型
interface VideoPlayerRef {
  play: () => void;
  pause: () => void;
}

// useImperativeHandle の使用
const VideoPlayer = forwardRef<VideoPlayerRef, {}>(
  (props, ref) => {
    const videoRef = useRef<HTMLVideoElement>(null);

    // 親に公開するメソッドを定義
    useImperativeHandle(ref, () => ({
      play: () => {
        videoRef.current?.play();
      },
      pause: () => {
        videoRef.current?.pause();
      },
    }));

    return <video ref={videoRef} src='video.mp4' />;
  }
);

useImperativeHandle を使うことで、内部実装を隠蔽しつつ、必要なメソッドだけを公開できます。

typescript// 親コンポーネントでの使用
const VideoPlayerParent = () => {
  const playerRef = useRef<VideoPlayerRef>(null);

  return (
    <div>
      <VideoPlayer ref={playerRef} />
      <button onClick={() => playerRef.current?.play()}>
        再生
      </button>
      <button onClick={() => playerRef.current?.pause()}>
        停止
      </button>
    </div>
  );
};

カプセル化されたコンポーネントの設計に役立ちますね。

Ref の使用上の注意点

Ref を使う際の重要なポイントをまとめます。

#注意点説明
1State との使い分けUI の更新には State、DOM 操作には Ref を使う
2null チェックref.currentnull の可能性があるため、必ずチェックする
3レンダリング中の使用禁止Ref の値はレンダリング中に変更してはいけない
4依存配列に含めないuseEffect の依存配列に Ref を含めても再実行されない
typescript// Ref の適切な使用例
const ProperRefUsage = () => {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const [count, setCount] = useState(0);

  // ✅ イベントハンドラ内で使用
  const handleClick = () => {
    if (buttonRef.current) {
      buttonRef.current.style.backgroundColor = 'green';
    }
    setCount(count + 1);
  };

  // ❌ レンダリング中に使用してはいけない
  // if (buttonRef.current) {
  //   buttonRef.current.textContent = 'NG';
  // }

  return (
    <button ref={buttonRef} onClick={handleClick}>
      クリック数: {count}
    </button>
  );
};

Ref は強力な機能ですが、使いすぎると宣言的な UI の利点が失われるため、本当に必要な場面でのみ使いましょう。

Ref セクションのまとめ(図で理解できる要点)

  • Ref は DOM への直接アクセスを可能にする
  • forwardRef で親から子の DOM にアクセスできる
  • useImperativeHandle で公開メソッドを制御できる
  • State と Ref を適切に使い分けることが重要

まとめ

本記事では、Preact の基本的な書き方を JSX、Props、Events、Ref の 4 つのカテゴリに分けて解説しました。

各機能のポイント

JSX では、条件分岐やループ、フラグメントなど、UI を記述するための様々な記法を紹介しました。HTML に似た直感的な構文で、JavaScript の力を活用できるのが特徴です。

Props では、コンポーネント間のデータ受け渡し方法を学びました。TypeScript の型定義を活用することで、安全で保守性の高いコードが書けます。

Events では、ユーザー操作に応答するための様々なイベントハンドリング方法を解説しました。基本的なクリックイベントから、カスタムイベントまで、柔軟に対応できます。

Ref では、DOM への直接アクセス方法を紹介しました。State と Ref を適切に使い分けることで、宣言的な UI と命令的な操作のバランスを取ることができるでしょう。

チートシートとしての活用方法

本記事は保存版として、以下のような場面で参照してください。

#場面参照セクション
1条件付きレンダリングの書き方を忘れたJSX の書き方
2Props の型定義方法を確認したいProps の書き方
3イベントハンドラの記法を確認したいEvents の書き方
4DOM を直接操作したいRef の書き方

Preact の開発において、これらの基本的な書き方をマスターすることで、より効率的にアプリケーションを構築できます。本記事が皆さんの開発の助けになれば幸いです。

関連リンク