こんにちは。
最近 TypeScript でそこそこ大規模な WebApp を書いていて、日々こけつまろびつしている私です。
ここ数日で似たような疑問が複数個所から聞こえてきたので、メモがてら記事に起こしておきます。
Q. 型定義された値に対して、定義にないプロパティを含むオブジェクトを代入したい
提起
普通に型定義を含んで定義された変数に対して、その型に無いプロパティを含むオブジェクトを代入しようとした場合はエラーになりますね。
interface IHoge {
str: string;
}
const hoge: IHoge = {
str: 'str',
arg: 'arg', // <= [TS2322] オブジェクト リテラルは既知のプロパティのみ指定できます。'arg' は型 'IHoge' に存在しません。
};
これに対して、型に定義されたプロパティを持ち、かつそれ以外の任意のプロパティを持つオブジェクトを代入したい場合にはどうするか。
公式回答
https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks
ここには、明示的に「何でも追加できるよ」というのを無理やり定義する方法が書かれていますね。
interface IHoge {
str: string;
[prop: string]: any;
}
const hoge: IHoge = {
str: 'str',
arg: 'arg',
};
なるほど。
更に追加で、「ちょっと驚きのテクかもしれないけど、いったんオブジェクト作ってから代入すればいいよ。(超訳)」
とも書かれています。
interface IHoge {
str: string;
}
const hoge = {
str: 'str',
arg: 'arg',
};
const hoge2: IHoge = hoge; // <= arg が含まれてるけどエラーにならない (!!)
変数の初期化時には余計なプロパティの確認をするけど、代入時には確認しないという理解で良いのでしょうか。
言語の定義まで踏み込んでの理解はできていません。
回答の変形
ちょっと変形でこんな書き方もできますね。
interface IHoge {
str: string;
}
const hoge = <IHoge> {
str: 'str',
arg: 'arg',
};
更に踏み込むとこういう書き方も。
const hoge = <{str: string}> {
str: 'str',
arg: 'arg',
}; // <= hoge には 「少なくとも str は入ってるよ」と伝えたい
この書き方に何の意味があるんじゃい。
と思いましたが、原プログラマの意図を伝えるには意味がありそうです。
const hoge = <{str: string}> {
str: 'str',
arg: 'arg',
};
console.log(hoge.arg); // <= [TS2339] プロパティ 'arg' は型 '{ str: string; }' に存在しません。
あるのに!
どういう時にこの知識使うの?
とても specific に graphql-request の options に値を追加したい際にこの問題に遭遇しました。
呼び出し側のコード
import {GraphQLClient} from 'graphql-request';
const request = new GraphQLClient('http://hogehoge', {method: 'get', proxy: 'anywhere'});
// <= [TS2345] オブジェクト リテラルは既知のプロパティのみ指定できます。'proxy' は型 'Options' に存在しません。
内部で呼ばれている fetch に対して proxy を渡したかったのですが、options の引数定義に用意されていなかったんですよね。
ちょっと悩んだ挙句、とりあえず下記のようにして回避した。
というお話でした。
import {GraphQLClient} from 'graphql-request';
const options = {method: 'get', proxy: 'anywhere'};
const request = new GraphQLClient('http://hogehoge', options);
何かの参考になれば。