TypeScript を使用する

TypeScript は、JavaScript コードベースに型定義を追加するためによく使用される方法です。 TypeScript はJSX を標準でサポートしており、@types/react@types/react-dom をプロジェクトに追加することで、React Web の完全なサポートを得ることができます。

インストール

すべての本番環境に対応した React フレームワークは TypeScript の使用をサポートしています。インストールについては、フレームワーク固有のガイドに従ってください。

既存の React プロジェクトに TypeScript を追加する

React の型定義の最新バージョンをインストールするには

ターミナル
npm install @types/react @types/react-dom

tsconfig.json に以下のコンパイラオプションを設定する必要があります。

  1. libdom を含める必要があります(注:lib オプションが指定されていない場合、dom はデフォルトで含まれます)。
  2. jsx は、有効なオプションのいずれかに設定する必要があります。ほとんどのアプリケーションでは、preserve で十分です。ライブラリを公開する場合は、選択する値について jsx のドキュメント を参照してください。

React コンポーネントと TypeScript

注記

JSX を含むすべてのファイルは、.tsx ファイル拡張子を使用する必要があります。これは TypeScript 固有の拡張子であり、このファイルに JSX が含まれていることを TypeScript に伝えます。

React で TypeScript を記述することは、React で JavaScript を記述することと非常によく似ています。コンポーネントを扱う場合の主な違いは、コンポーネントの props に型を提供できることです。これらの型は、正確性のチェックとエディタでのインラインドキュメントの提供に使用できます。

クイックスタートガイドの MyButton コンポーネント を例に、ボタンの title を記述する型を追加してみましょう。

function MyButton({ title }: { title: string }) {
  return (
    <button>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a button" />
    </div>
  );
}

注記

これらのサンドボックスはTypeScriptコードを扱うことができますが、型チェッカーを実行しません。つまり、学習のためにTypeScriptサンドボックスを修正することはできますが、型エラーや警告は表示されません。型チェックを行うには、TypeScript Playgroundを使用するか、より本格的なオンラインサンドボックスを使用してください。

このインライン構文は、コンポーネントに型を提供する最も簡単な方法ですが、記述するフィールドがいくつかになると扱いにくくなることがあります。代わりに、interfaceまたはtypeを使用して、コンポーネントのプロパティを記述することができます。

interface MyButtonProps {
  /** The text to display inside the button */
  title: string;
  /** Whether the button can be interacted with */
  disabled: boolean;
}

function MyButton({ title, disabled }: MyButtonProps) {
  return (
    <button disabled={disabled}>{title}</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton title="I'm a disabled button" disabled={true}/>
    </div>
  );
}

コンポーネントのプロパティを記述する型は、必要に応じてシンプルにも複雑にもできますが、typeまたはinterfaceで記述されたオブジェクト型である必要があります。TypeScriptがオブジェクトをどのように記述するかについては、オブジェクト型で学ぶことができますが、共用体型を使用して、いくつかの異なる型のいずれかになることができるプロパティを記述したり、より高度なユースケースのために型から型を作成するガイドを使用したりすることもできます。

フックの例 (略:SVG)

@types/reactからの型定義には、組み込みフックの型が含まれているため、追加の設定なしでコンポーネントでそれらを使用できます。それらは、コンポーネントに記述するコードを考慮して構築されているため、多くの場合推論型を取得し、理想的には型を提供するという詳細を処理する必要はありません。

ただし、フックに型を提供する方法の例をいくつか見てみましょう。

useState (略:SVG)

useStateフックは、初期状態として渡された値を再利用して、値の型を決定します。例えば

// Infer the type as "boolean"
const [enabled, setEnabled] = useState(false);

これは、enabledboolean 型を割り当て、setEnabledboolean 引数または boolean を返す関数を受け入れる関数になります。状態の型を明示的に指定したい場合は、useState 呼び出しに型引数を指定することで行うことができます。

// Explicitly set the type to "boolean"
const [enabled, setEnabled] = useState<boolean>(false);

これはこの場合それほど役に立ちませんが、型を指定したい一般的なケースは、共用体型がある場合です。たとえば、ここでの status は、いくつかの異なる文字列のいずれかになります。

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

または、「状態を構築するための原則」で推奨されているように、関連する状態をオブジェクトとしてグループ化し、オブジェクト型を介してさまざまな可能性を記述できます。

type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success', data: any }
| { status: 'error', error: Error };

const [requestState, setRequestState] = useState<RequestState>({ status: 'idle' });

useReducer

useReducer Hook は、reducer 関数と初期状態を受け取る、より複雑な Hook です。reducer 関数の型は、初期状態から推論されます。必要に応じて、useReducer 呼び出しに型引数を指定して状態の型を提供できますが、代わりに初期状態で型を設定する方が良い場合が多いです。

import {useReducer} from 'react';

interface State {
   count: number 
};

type CounterAction =
  | { type: "reset" }
  | { type: "setCount"; value: State["count"] }

const initialState: State = { count: 0 };

function stateReducer(state: State, action: CounterAction): State {
  switch (action.type) {
    case "reset":
      return initialState;
    case "setCount":
      return { ...state, count: action.value };
    default:
      throw new Error("Unknown action");
  }
}

export default function App() {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  const addFive = () => dispatch({ type: "setCount", value: state.count + 5 });
  const reset = () => dispatch({ type: "reset" });

  return (
    <div>
      <h1>Welcome to my counter</h1>

      <p>Count: {state.count}</p>
      <button onClick={addFive}>Add 5</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

いくつかの重要な場所でTypeScriptを使用しています。

  • interface State は、reducer の状態の形状を記述します。
  • type CounterAction は、reducer にディスパッチできるさまざまなアクションを記述します。
  • const initialState: State は、初期状態の型を提供し、デフォルトで useReducer によって使用される型も提供します。
  • stateReducer(state: State, action: CounterAction): State は、reducer 関数の引数と戻り値の型を設定します。

initialState で型を設定する代わりに、より明示的な方法として、useReducer に型引数を指定する方法があります。

import { stateReducer, State } from './your-reducer-implementation';

const initialState = { count: 0 };

export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}

useContext

useContext Hook は、コンポーネントを介して props を渡すことなく、コンポーネントツリーにデータを下方に渡すための手法です。これは、プロバイダーコンポーネントを作成し、多くの場合、子コンポーネントで値を使用するための Hook を作成することによって使用されます。

コンテキストによって提供される値の型は、createContext 呼び出しに渡される値から推論されます。

import { createContext, useContext, useState } from 'react';

type Theme = "light" | "dark" | "system";
const ThemeContext = createContext<Theme>("system");

const useGetTheme = () => useContext(ThemeContext);

export default function MyApp() {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext.Provider value={theme}>
      <MyComponent />
    </ThemeContext.Provider>
  )
}

function MyComponent() {
  const theme = useGetTheme();

  return (
    <div>
      <p>Current theme: {theme}</p>
    </div>
  )
}

このテクニックは、意味のあるデフォルト値がある場合に有効です。ただし、デフォルト値がない場合があり、そのような場合、null をデフォルト値として使用することは合理的です。しかし、タイプシステムがコードを理解できるようにするには、createContextContextShape | null を明示的に設定する必要があります。

これは、コンテキストコンシューマーの型で | null を排除する必要があるという問題を引き起こします。Hook がランタイムチェックを実行して、存在しない場合はエラーをスローすることをお勧めします。

import { createContext, useContext, useState, useMemo } from 'react';

// This is a simpler example, but you can imagine a more complex object here
type ComplexObject = {
kind: string
};

// The context is created with `| null` in the type, to accurately reflect the default value.
const Context = createContext<ComplexObject | null>(null);

// The `| null` will be removed via the check in the Hook.
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject must be used within a Provider") }
return object;
}

export default function MyApp() {
const object = useMemo(() => ({ kind: "complex" }), []);

return (
<Context.Provider value={object}>
<MyComponent />
</Context.Provider>
)
}

function MyComponent() {
const object = useGetComplexObject();

return (
<div>
<p>Current object: {object.kind}</p>
</div>
)
}

useMemo

useMemo Hook は、関数呼び出しからメモ化された値を作成/再アクセスし、2 番目のパラメーターとして渡された依存関係が変更された場合にのみ関数を再実行します。 Hook を呼び出した結果は、最初のパラメーターの関数の戻り値から推測されます。 Hook に型引数を指定することで、より明示的にすることができます。

// The type of visibleTodos is inferred from the return value of filterTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);

useCallback

useCallback は、2 番目のパラメーターに渡された依存関係が同じである限り、関数の安定した参照を提供します。useMemo と同様に、関数の型は最初のパラメーターの関数の戻り値から推測され、Hook に型引数を指定することで、より明示的にすることができます。

const handleClick = useCallback(() => {
// ...
}, [todos]);

TypeScript strict モードで作業する場合、useCallback はコールバックのパラメーターに型を追加する必要があります。これは、コールバックの型が関数の戻り値から推測されるためであり、パラメーターがないと型を完全に理解できないためです。

コードスタイルの好みに応じて、React タイプの *EventHandler 関数を使用して、コールバックを定義すると同時にイベントハンドラの型を指定できます。

import { useState, useCallback } from 'react';

export default function Form() {
const [value, setValue] = useState("Change me");

const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])

return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}

便利な型

@types/react パッケージには、非常に広範な型セットが付属しています。React と TypeScript の相互作用方法に慣れたら、読んでみる価値があります。DefinitelyTyped の React のフォルダーにあります。ここでは、より一般的な型のいくつかを取り上げます。

DOM イベント

React で DOM イベントを扱う場合、イベントの型は多くの場合、イベントハンドラーから推測できます。ただし、イベントハンドラーに渡される関数を抽出する場合、イベントの型を明示的に設定する必要があります。

import { useState } from 'react';

export default function Form() {
  const [value, setValue] = useState("Change me");

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setValue(event.currentTarget.value);
  }

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Value: {value}</p>
    </>
  );
}

Reactの型定義では、多くの種類のイベントが提供されています。完全なリストは、DOMで最も一般的なイベントに基づいたこちらにあります。

探している型を特定するには、まず使用しているイベントハンドラのホバー情報を確認します。そこにイベントの型が表示されます。

このリストに含まれていないイベントを使用する必要がある場合は、すべてのイベントの基本型であるReact.SyntheticEvent型を使用できます。

子要素 {/* SVGアイコンの内容は変更なし */}

コンポーネントの子要素を記述するには、2つの一般的な方法があります。1つ目は、JSXで子要素として渡すことができるすべての型の和集合であるReact.ReactNode型を使用することです。

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

これは、子要素の非常に広範な定義です。2つ目は、文字列や数値などのJavaScriptプリミティブではなく、JSX要素のみであるReact.ReactElement型を使用することです。

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

なお、TypeScriptを使用して、子要素が特定の型のJSX要素であることを記述することはできません。そのため、型システムを使用して、<li>子要素のみを受け入れるコンポーネントを記述することはできません。

このTypeScriptプレイグラウンドで、型チェッカーを使用したReact.ReactNodeReact.ReactElementの両方の例を確認できます。

スタイルプロップ {/* SVGアイコンの内容は変更なし */}

Reactでインラインスタイルを使用する場合、styleプロップに渡されるオブジェクトを記述するために、React.CSSPropertiesを使用できます。この型は、すべての有効なCSSプロパティの和集合であり、styleプロップに有効なCSSプロパティを渡していることを確認し、エディターでオートコンプリートを取得するのに適した方法です。

interface MyComponentProps {
style: React.CSSProperties;
}

さらに学ぶ {/* SVGアイコンの内容は変更なし */}

このガイドでは、ReactでTypeScriptを使用するための基本について説明しましたが、学ぶべきことはまだたくさんあります。ドキュメントの個々のAPIページには、TypeScriptでそれらを使用する方法に関する、より詳細なドキュメントが含まれている場合があります。

以下のリソースをお勧めします。

  • TypeScriptハンドブックは、TypeScriptの公式ドキュメントであり、主要な言語機能のほとんどを網羅しています。

  • TypeScriptリリースノートでは、新機能について詳しく説明しています。

  • React TypeScriptチートシートは、コミュニティによって管理されている、ReactでTypeScriptを使用するためのチートシートであり、多くの有用なエッジケースを網羅し、このドキュメントよりも幅広い情報を提供しています。

  • TypeScriptコミュニティDiscordは、質問したり、TypeScriptとReactの問題について助けを求めたりするのに最適な場所です。