🧶

Reactでよく使う型定義

2023/08/27に公開
2

はじめに

随時更新していきます。

onSubmitのtarget

form要素のonSubmitイベントのtargetは、name属性が型定義されていないので、型定義を追加する必要があります。

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault(); // デフォルトの挙動をキャンセル
  // name属性を指定しているinput要素の値を取得
  const target = e.target as typeof e.target & {
    email: { value: string };
    password: { value: string };
  };
  const email = target.email.value; // type: string
  const password = target.password.value; // type: string
};

return (
  <form onSubmit={handleSubmit}>
    <input type="email" name="email" />
    <input type="password" name="password" />
    <button type="submit">ログイン</button>
  </form>
)

Buttonのtype

aタグの子要素としてbutton要素を使うのは、HTMLの仕様上、許可されていません。
そのため、コンポーネント化した場合はhref属性をpropsとして受け取りつつ、hrefの有無でaタグかbutton要素を出し分ける必要があります。
アロー関数ではなく、function宣言で書くと、関数のオーバーロードを使って、型定義を簡潔に書くことができます。

:::details修正前

import {
  AnchorHTMLAttributes,
  ButtonHTMLAttributes,
  DetailedHTMLProps,
} from "react";

function Button(
  props: DetailedHTMLProps<
    ButtonHTMLAttributes<HTMLButtonElement>,
    HTMLButtonElement
  >,
): JSX.Element;
function Button(
  props: DetailedHTMLProps<
    AnchorHTMLAttributes<HTMLAnchorElement>,
    HTMLAnchorElement
  >,
): JSX.Element;
function Button(
  props:
    | DetailedHTMLProps<
        ButtonHTMLAttributes<HTMLButtonElement>,
        HTMLButtonElement
      >
    | DetailedHTMLProps<
        AnchorHTMLAttributes<HTMLAnchorElement>,
        HTMLAnchorElement
      >,
): JSX.Element {
  if ("href" in props) {
    return <a href={props.href} {...props} />;
  } else {
    const { ...buttonProps } = props as DetailedHTMLProps<
      ButtonHTMLAttributes<HTMLButtonElement>,
      HTMLButtonElement
    >;
    return <button type={buttonProps.type || "button"} {...buttonProps} />;
  }
}

export default Button;

:::

もっと簡潔に書けるとご指摘をいただきましたので、修正しました。
コメントありがとうございます!

ComponentPropsWithoutRefを使った場合

import type { ComponentPropsWithoutRef } from "react";

function Button(props: ComponentPropsWithoutRef<"a">): JSX.Element;
function Button(props: ComponentPropsWithoutRef<"button">): JSX.Element;
function Button(
  props: ComponentPropsWithoutRef<"button"> | ComponentPropsWithoutRef<"a">,
): JSX.Element {
  if ("href" in props) {
    return <a href={props.href} {...props} />;
  } else {
    const { ...buttonProps } = props as ComponentPropsWithoutRef<"button">;
    return <button type={buttonProps.type || "button"} {...buttonProps} />;
  }
}

export default Button;

JSX.IntrinsicElementsを使った場合

function Button(props: JSX.IntrinsicElements["a"]): JSX.Element;
function Button(props: JSX.IntrinsicElements["button"]): JSX.Element;
function Button(
  props: JSX.IntrinsicElements["button"] | JSX.IntrinsicElements["a"],
): JSX.Element {
  if ("href" in props) {
    return <a href={props.href} {...props} />;
  } else {
    const { ...buttonProps } = props as JSX.IntrinsicElements["button"];
    return <button type={buttonProps.type || "button"} {...buttonProps} />;
  }
}

export default Button;

useStateの型定義

useStateで初期値を []{} で初期化すると、型推論が効かないので、明示的に型定義する必要があります。

const [count, setCount] = useState(0); // type: number
const [text, setText] = useState(""); // type: string
const [flag, setFlag] = useState(false); // type: boolean
const [counts, setCounts] = useState<number[]>([]); // []で初期化すると、型推論が効かないので、明示的に型定義する

fetchの型定義

fetchのレスポンスをjson()メソッドを使って、json形式に変換した場合、型推論が効かないので、明示的に型定義する必要があります。

const [data, setData] = useState<DataType[]>([]); // []で初期化すると、型推論が効かないので、明示的に型定義する

useEffect(() => {
  fetch("https://example.com/api")
    .then((res) => res.json())
    .then((data) => setData(data));
}, []);

useSWRを使った場合

useSWRの場合は、以下のように型定義することで、型推論が効きます。

const { data, error } = useSWR<DataType[]>("https://example.com/api");

Next.jsのServer Componentsを使った場合

Next.jsのServer Componentsを使った場合は、以下のように型定義することで、型推論が効きます。

export default async function Page() {
    const data: DataType[] = await fetch("https://example.com/api").then((res) => res.json());
    return <Component data={data} />;
}

useRefの型定義

useRefフックは、Reactのレンダリングサイクルの間で値を保持するために使用されます。useRefは、引数として初期値を受け取り、その値を.currentプロパティに保持するオブジェクトを返します。

useRefの型定義は、ジェネリクスを使用して行います。

const ref = useRef<HTMLDivElement>(null);

useRefはHTMLDivElementのインスタンスを参照します。
ref.currentはHTMLDivElementかnullになります。

この先工事中🚧

(最終更新日: 2023-11-23)

GitHubで編集を提案
GMOメディアテックブログ

Discussion

Honey32Honey32

失礼します。

フォームの各要素の値取得

onSubmit でのフォームの各要素の値の取得は、FormData というクラスを使うことができます。ただ、FormData では各プロパティが string | File 型なので、 zod-form-data のようなスキーマライブラリが使えそうです。

import { zfd } from "zod-form-data";

const schema = zfd.formData({
  email: zfd.text(),
  password: zfg.text(),
});

// 中略

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    // e.target は型が特定できないので、currentTarget のほうが良さそう?
    const formData = new FormData(e.currentTarget);
    const parsedData = schema.parse(formData);

    window.alert(JSON.stringify(parsedData, null, 4));
  };

zod を普通に使うだけでも良さそう

https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#form-validation

DetailedHTMLProps よりも短く

import { type ComponentPropsWithoutRef } from "react";

type ButtonProps = ComponentPropsWithoutRef<"button">
// または 
type ButtonProps = JSX.IntrinsicElements["button"]

を使うと遠回りせず、かなり短く書けます。

Riya AmemiyaRiya Amemiya

コメントありがとうございます。
zodは外部ライブラリなのでこの記事では取り扱わない方針です。

ComponentPropsWithoutRefは初めて知りました。ありがとうございます。
修正しておきます。