reactjs - なぜ TypeScript ではオブジェクトリテラル `{a}` の型アサーションがインターフェース `{a, b}` には機能するのに、`{a?, b}` には機能しないのか
なぜ次のアサーションは動作するのですか:
interface AllRequired {
a: string;
b: string;
}
let all = {a: "foo"} as AllRequired; // No error
しかし、この主張はエラーを出します:
interface SomeOptional {
a?: string;
b: string;
}
let some = {a: "foo"} as SomeOptional; // Error: Property 'b' missing
私が見える唯一の違いは、インターフェースのプロパティの1つをオプション(?)にすることです。すべてのプロパティがオプションでない場合、部分的なオブジェクトをインターフェースにアサートできるようですが、インターフェースのプロパティのいずれかがオプションになると、もう部分的なオブジェクトをアサートできなくなります。これは私にはあまり理解できず、この動作の説明も見つけられませんでした。これは一体どういうことなのでしょうか?
文脈として:私は、React の setState() が部分的な state オブジェクトを受け取る一方で、TypeScript ではまだ部分的な型(partial types)が存在せず、state インターフェースに対してこれを正しく機能させることができない問題に対処しようとして、この挙動に遭遇しました。回避策として setState({a: "a"} as MyState) を考え出し、インターフェース MyState のフィールドが すべて必須である限りは動作することがわかりましたが、一部のプロパティがオプションになると失敗します。(すべてのプロパティをオプションにすることは回避策になりますが、私の場合これは非常に望ましくありません。)
回答 #1
型アサーションは、ある型とその型のサブタイプの間でのみ使用できます。
次の変数を宣言したとしましょう:
declare var foo: number | string;
declare var bar: number;
注:number は number | string の サブタイプ であり、これは number 型に一致する任意の値(例:3)も number | string に一致することを意味します。したがって、これらの型の間で型アサーションを使用することが許可されています:
bar = foo as number; /* convert to subtype */
foo = bar as number | string; /* convert to supertype (assertion not necessary but allowed) */
同様に、{ a: string, b: string } は { a: string } のサブタイプです。{ a: string, b: string } に一致する任意の値(例えば { a: "A", b: "B" })も、a プロパティが string 型であるため { a: string } に一致します。
対照的に、{ a?: string, b: string } と { a: string } のどちらも相手のサブタイプではありません。ある値(例えば { b: "B" })は前者にのみ一致し、別の値(例えば { a: "A" })は後者にのみ一致します。
もし本当に関連のない型の間で変換する必要がある場合は、共通のスーパタイプ(例えば any)を中間として使用することで回避できます:
let foo = ({ a: "A" } as any) as { a?: string, b: string };
仕様書の関連部分は4.16 型アサーションです:
形式が
e の型アサーション式では、e は T によって文脈的に型付けされ(セクション 4.23)、e の結果の型は T に代入可能である必要があるか、または T が e の結果の型の拡張された形式に代入可能である必要があり、そうでない場合はコンパイル時エラーが発生します。
回答 #2
まあ、これはあくまで仮説ですが、それを裏付けるコードがあります。
最初の例では、AllRequired にキャストすることで、基本的にコンパイラーに対して自分が何をしているのか分かっていて、後で all.b の値を設定するつもりだと言っているようなものです。
2番目の例では、同じことをしていると思っていますが、コンパイラはおそらく「彼がオプションのプロパティを設定しているなら、完全なオブジェクトを設定しているに違いない」と考えており、この完全なオブジェクトから some.b の値が欠けている理由に混乱しています。
これは少し突飛に聞こえるかもしれませんが、こちらです:
let some2 = (all.a ? {a: "foo"} : {}) as SomeOptional
条件を使うとエラーはなくなります。なぜなら、その場合(おそらく)some2.aは本当にオプションだからです。
プレイグラウンドで試してみてください。
