jackmiwamiwa devblog

フロントエンドをメインに行ったことをまとめていくサイトになります。

TypeScirptで配列・オブジェクトの値をEnums化して型を安全にする

以下の記事を参考にTypeScriptでの配列の値を記載する際にEnums化を行ったため、その時のメモになります。

blog.mitsuruog.info

行いたかったこと

react-hook-formで以下のようなことをする際にテキストだと型がうまく反応しなかったため、反応するようにしたい

const inputNameList = {
 group: ['input1', 'input2']
}
<!-- 以下の「input1」の部分を「inputNameList」以外の名前の場合にエラーにしたい -->
<input {...register("input1")} />

<!-- OKパターン(inputNameListの中にinput1はあるので、OK) -->
<input {...register("input1")} />
<!-- NGパターン(inputNameListの中にinput3はないので、NG) -->
<input {...register("input3")} />

今回実装したコード

オブジェクトのkey部分を使用したい場合

一覧の定義・使い方

// 定義の内容
const inputNameList = {
  group1: ['data1', 'data2', 'data3'],
};
// 使い方
/**
 * inputNameList.group1 // 'group1'のテキスト
 */

関数の定義

/**
 * Function to create an enum based on an array of names
 * @template { string[] } T Type of list of names in object first level
 * @template { string } S  List name type of object first level names
 * @param { T } keys List of names in object first level
 */
export function convertArrayToEnum<T extends unknown[], S extends string>(keys: T) {
  const result = {} as { [key in S]: S };
  keys.forEach((k: S) => {
    result[k] = k;
  });
  return result;
}

実際の使い方

// 一覧の定義
const inputNameList = {
  group1: ['data1', 'data2', 'data3'],
  group2: ['test1'],
};
// オブジェクトのkey部分を配列にて、取得
// 結果: ['group1', 'group2']
const objectList = Object.keys(inputNameList) as (keyof typeof inputNameList)[];
// 各種型の定義
// 結果: ('group1' | 'group2')[]
type InputGroupKeys = typeof validateGroupKeys;
// 結果: 'group1' | 'group2'
type InputGroupKeysNumber = typeof validateGroupKeys[number]
// enum化
/** 結果
 * const enum = {
 *    group1: "group1";
 *    group2: "group1";
 * }
 */
const enum = convertArrayToEnum<InputGroupKeys, InputGroupKeysNumber>(objectList);
// 実際に使用
enum.group1 //「group1」のテキスト

オブジェクトの配列部分を取得したい場合

一覧の定義・使い方

// 定義の内容
const inputNameList = {
  group1: ['data1', 'data2', 'data3'],
};
// 使い方
/**
 * group1.data1 // 'data1'のテキスト
 * group1.data2 // 'data2'のテキスト
 * group1.data3 // 'data3'のテキスト
 */

関数の定義

/**
 * Function to create an enum based on the name of an array in a property
 * @template { string[] } T Type of list of names in object first level
 * @template { string } S List name type of object first level names
 * @template { Object<string> } K  List name type of object first level names
 * @param { Object<S, string[]> } group group list
 * @param { T } keys List of names in object first level
 */
export function convertObjectArrayToEnum<T extends unknown[], S extends string, K extends object>(
  group: { [key in S]: string[] },
  keys: T
) {
  type InputLabel = { [key in keyof K]: string };
  const result = {} as { [key in S]: InputLabel };
  keys.forEach((k: S) => {
    result[k] = {} as InputLabel;
    group[k].forEach((value: string) => {
      result[k][value] = value;
    });
  });
  return result;
}

実際の使い方

// 一覧の定義
const inputNameList = {
  group1: ['data1', 'data2', 'data3'],
  group2: ['test1'],
};
// 一覧の型の定義
export type State = {
  data1: string;
  data2: string;
  data3: number;
  test1: string;
};
// オブジェクトのkey部分を配列にて、取得
// 結果: ['group1', 'group2']
const objectList = Object.keys(inputNameList) as (keyof typeof inputNameList)[];
// 各種型の定義
// 結果: ('group1' | 'group2')[]
type InputGroupKeys = typeof validateGroupKeys;
// 結果: 'group1' | 'group2'
type InputGroupKeysNumber = typeof validateGroupKeys[number]
// enum化
const enum = convertObjectArrayToEnum<
  InputGroupKeys,
  InputGroupKeysNumber,
  State
>(inputNameList, objectList);
// 実際に使用
const {group1} = enum
// group1の中にdata1はあるので、OK
// <input {...register('data1')} />と結果は同じ
<input {...register(group1.data1)} />
// group1の中にdata100はないので、NG
<input {...register(group1.data100)} />

結論

今回作成した関数についてはこちらになります。

/**
 * Function to create an enum based on an array of names
 * @template { string[] } T Type of list of names in object first level
 * @template { string } S  List name type of object first level names
 * @param { T } keys List of names in object first level
 */
export function convertArrayToEnum<T extends unknown[], S extends string>(keys: T) {
  const result = {} as { [key in S]: S };
  keys.forEach((k: S) => {
    result[k] = k;
  });
  return result;
}

/**
 * Function to create an enum based on the name of an array in a property
 * @template { string[] } T Type of list of names in object first level
 * @template { string } S List name type of object first level names
 * @template { Object<string> } K  List name type of object first level names
 * @param { Object<S, string[]> } group group list
 * @param { T } keys List of names in object first level
 */
export function convertObjectArrayToEnum<T extends unknown[], S extends string, K extends object>(
  group: { [key in S]: string[] },
  keys: T
) {
  type InputLabel = { [key in keyof K]: string };
  const result = {} as { [key in S]: InputLabel };
  keys.forEach((k: S) => {
    result[k] = {} as InputLabel;
    group[k].forEach((value: string) => {
      result[k][value] = value;
    });
  });
  return result;
}