TypeScript Tip #8

Apr 15, 2022

5 mins read

背景

今回もmattさんのTypeScriptTipsをまとめていく。
TypeScript Tip #8


概要

Reactのジェネリクスを使用して、動的で柔軟なコンポーネントを作成することができる。


本題

以下のコードはReactのサンプルのコードである。
Tableコンポーネントのpropsに存在するitemsはオブジェクトの配列になっている。
オブジェクトはプロパティidのみを保持している。

import React from 'react'

interface TableProps {
    items: { id: string }[]
    renderItem: (item: { id: string }) => React.ReactNode
}

export const Table = (props: TableProps) => {
    return null
}

const Component = () => {
    return (
        <Table
            items={[
                {
                    id: '1',
                }
            ]}
            renderItem={(item) => <div>{ item.id }</div>}
        ></Table>
    )
}

上記の場合、propsitemsにオブジェクトのid以外のプロパティを定義したい場合、TablePropsにも追加をしてやる必要がある。 以下はそのサンプルコード

import React from 'react'

interface TableProps {
    items: { id: string, name: string }[]
    // 🔼プロパティnameを追加している
    renderItem: (item: { id: string, name: string }) => React.ReactNode
    // 🔼プロパティnameを追加している
}

export const Table = (props: TableProps) => {
    return null
}

const Component = () => {
    return (
        <Table
            items={[
                {
                    id: '1',
                    name: 'issei'
                    // 🔼propsにnameを追加している
                }
            ]}
            renderItem={(item) => <div>{ item.id }</div>}
        ></Table>
    )
}

プロパティを追加する度に型定義を追加してやる必要があるため、これをジェネリクスを利用して定義を動的に変更させる。 ジェネリクスを利用したサンプルコードは以下。

import React from 'react'

interface TableProps<TItem> {
    items: TItem[]
    renderItem: (item: TItem) => React.ReactNode
}

export function Table<TItem>(props: TableProps<TItem>) {
    return null
}

const Component = () => {
    return (
        <Table
            items={[
                {
                    id: '1',
                    name: 'Matt'
                }
            ]}
            renderItem={(item) => <div>{ item.id }</div>}
        ></Table>
    )
}
  1. TablePropsの型定義でジェネリクスを定義している。
    今回の例でいうと、itemsの型推論した型をジェネリクスに格納し、型全体で使用できる様にしている。
    renderItem関数内の引数にもジェネリクスで定義した型を定義している。
interface TableProps<TItem> {
    items: TItem[]
    renderItem: (item: TItem) => React.ReactNode
}
  1. 上記で定義したジェネクスも関数全体で使用できるように定義しておく。
export function Table<TItem>(props: TableProps<TItem>) {
  return null
}

// or

const Table = <TItem extends unknown>(props: TableProps<TItem>) => {
    return null;
};

  1. 上記の型を定義してやることで、propsの値を型推論してくれて、itemsのプロパティを追加しても動的に型推論してくれる。
const Component = () => {
  return (
    <Table
      items={[
          {
            id: '1',
            name: 'Matt'
            // 🔼nameを追加しても動的に型推論することができる
          }
        ]}
      renderItem={(item) => <div>{ item.id }</div>}
    ></Table>
    // 型定義内容
    //  function Table<{
    //    id: string;
    //    name: string;
    //   }>(props: TableProps<{
    //    id: string;
    //    name: string;
    //   }>): null
    //  )
}

まとめ

かなり汎用性が高くなるため、使用することがあれば使用していきたい


参考文献

TypeScript Tip #8