reactjs - 動的/計算されたキーを持つ Pick 型
最新の @types/react(v15.0.6)は、setState のために TypeScript 2.1 に追加された機能、具体的には Pick<S, K> を利用しています。これは良いことです。なぜなら、更新前は型定義が setState が this.state を置き換えるのではなくマージしていることを「認識していなかった」ため、更新後は型定義が正しくなったからです。
また、Pick を使用すると、setState 関数は許可される入力に関して非常に厳格になります。コンポーネント定義(React.Component の2番目のジェネリック)で定義されていないプロパティを state に追加することはもはや不可能です。
しかし、動的な更新ハンドラーを定義することはより難しいです。例えば:
import * as React from 'react';
interface Person {
name: string;
age: number|undefined;
}
export default class PersonComponent extends React.Component<void, Person> {
constructor(props:any) {
super(props);
this.state = {
name: '',
age: undefined
};
this.handleUpdate = this.handleUpdate.bind(this);
}
handleUpdate (e:React.SyntheticEvent<HTMLInputElement>) {
const key = e.currentTarget.name as keyof Person;
const value = e.currentTarget.value;
this.setState({ [key]: value });
}
render() {
return (
<form>
<input type="text" name="name" value={this.state.name} onChange={this.handleUpdate} />
<input type="text" name="age" value={this.state.age} onChange={this.handleUpdate} />
</form>
);
}
}
setState 関数は次のエラーを投げます
[ts] Argument of type '{ [x: string]: string; }' is not assignable
to parameter of type 'Pick<Person, "name" | "age">'.
Property 'name' is missing in type '{ [x: string]: string; }'.
たとえ key の型が "name" | "age" であっても。
これに対する解決策は、別々の updateName と updateAge 関数を持つこと以外に見つかりません。Pick を動的なキー値で使用する方法を知っている人はいますか?
回答 #1
さらに調査を行った後、上記のコードで何が起こっているのかについて、もう少し文脈を提供することができます。
例えば const name='Bob' のように何かをすると、変数 name の型は 'Bob' であり、string ではありません。しかし、const を let に置き換えると(let name='Bob')、変数 name の型は string になります。
この概念は「ワイドニング」と呼ばれます。基本的に、これは型システムができるだけ明示的であろうとすることを意味します。const は再代入できないため、TypeScript は正確な型を推論できます。let 文は再代入可能です。したがって、TypeScript は上の例の name の型を string と推論します。
同じことが const key=e.currentTarget.name as keyof Person でも起きています。key は (ユニオン) 型 "name"|"age" となり、これはまさに私たちが望む型です。しかし 式 this.setState({ [key]: value }); では変数 key が (不正確に) string 型に拡張されてしまいます。
要点; TypeScriptにバグがあるようです。私はその問題をGithubのリポジトリに投稿し、TypeScriptチームが問題を調査中です。:)
一時的な回避策として、次のようにすることができます:
this.setState({ [key as any]: value });
回答 #2
セバスチャンの答えはもはや私には機能しません(ある時点では機能していましたが)。現在、Typescriptのコアで解決されるまで(セバスチャンの答えに記載された問題の通りに)次のことを行っています。
handleUpdate (e:React.SyntheticEvent<HTMLInputElement>) {
const newState = {};
newState[e.currentTarget.name] = e.currentTarget.value;
this.setState(newState);
}
ダサいけど、うまくいく
