TypeScript ×React 子コンポーネントに渡した文字列をオブジェクトのkeyとして利用する

## 導入
親コンポーネントで子コンポーネントを呼び出すときに
propsで文字列を渡します。
(下記コード例では、App.tsx「<PointInput name="suica"～」等)

子コンポーネントのオブジェクトのあるプロパティを利用する際に
先ほど渡した文字列をこのオブジェクトのkeyとして使用して
値を取得します。
(下記コード例では、PointInput.tsx「value={state[name as keyof StateType]}」)

JavaScriptでは、ブランケット記法で書けば、特に問題はないです。
ただ、TypeScriptでは普通にブランケット記法で書くとエラーが発生します。

このエラーの解消方法です。

## 環境構築
create react appを利用します。
```
npx create-react-app . --template typescript
```

## コード例
```TypeScript:App.tsx
import { useState } from 'react';
import PointInput from './PointInput';

const App:React.FC = () => {

  const [points, setPoints] = useState({
    suica:0,
    toica:0,
    icoca:0
  });

  return (
    <>
      <PointInput name="suica" state={points} dispatch={setPoints}/>
      <PointInput name="toica" state={points} dispatch={setPoints}/> 
      <PointInput name="icoca" state={points} dispatch={setPoints}/> 
    </>
  );
}

export default App;
```

```TypeScript:PointInput.tsx
type StateType = {
    suica:number,
    toica:number,
    icoca:number
}

const PointInput = ({name,state,dispatch}:{name:string,state:StateType,dispatch:React.Dispatch<React.SetStateAction<StateType>>}) => {
  return (
    <div>
        {name}:<input 
            type="number"
            name={name}
            value={state[name as keyof StateType]}
            onChange={(e) => dispatch({...state, [e.target.name]:e.target.value})}
        />
    </div>
  )
}

export default PointInput
```
機能としては、inputタグを表示するPointInputコンポーネントが3つあり、
その入力値をpointsというstateで状態管理しています。

## 解説
- 解決法 その1
```:TypeScript:PointInput.tsx(一部)
value={state[name]}
```
このようにブランケット記法で普通に記述すると下記のエラーが発生します。
`
型 'string' の式を使用して型 'StateType' にインデックスを付けることはできないため、
要素は暗黙的に 'any' 型になります。
型 'string' のパラメーターを持つインデックス シグネチャが型 'StateType' に見つかりませんでした。ts(7053)
`

どうもTypeScriptが、nameという変数をstateオブジェクトのkeyとして
認識していないことがエラーの原因のようです。

型アサーションを使用して、型を指定します。
```:TypeScript:PointInput.tsx(一部)
value={state[name as keyof StateType]}
```

サバイバルTypeScriptより引用
keyofはオブジェクトの型からプロパティ名を型として返す型演算子です。
2つ以上のプロパティがあるオブジェクトの型にkeyofを使った場合は、
すべてのプロパティ名がユニオン型で返されます。


上記コード例では下記のようになります。
```TypeScript
type StateTypeKey = keyof StateType
// StateTypeKey = "suica"| "toica" | "icoca"
```


- 解決法 その2
子コンポーネントにnameをpropsとして渡した際、値が文字列であったため
子コンポーネントでstring型として型付けをしています。
そもそもここが根本的な問題です。

propsのnameを分割代入の型付けの際にkeyof StateTypeと指定します。
```TypeScript:PointInput.tsx(一部)
const PointInput = ({name,state,dispatch}:{name:keyof StateType,state:StateType,dispatch:React.Dispatch<React.SetStateAction<StateType>>}) => {
```


最終的なコード
```TypeScript:PointInput.tsx
type StateType = {
    suica:number,
    toica:number,
    icoca:number
}

const PointInput = ({name,state,dispatch}:{name:keyof StateType,state:StateType,dispatch:React.Dispatch<React.SetStateAction<StateType>>}) => {
  return (
    <div>
        {name}:<input 
            type="number"
            name={name}
            value={state[name]}
            onChange={(e) => dispatch({...state, [e.target.name]:e.target.value})}
        />
    </div>
  )
}

export default PointInput
```

文字列を渡したからstring型にしておけばいいや、ではないこと
string型にしてしまうとオブジェクトのkeyとして認識されないこと
を学びました。

## 最後に
- 私がたどった思考と解決の経緯に沿って、記述しているので、まどろっこしい書き方になっていますがどうぞご勘弁ください。
- 誤り、改善点があれば、ご教授いただければ幸いです。