投稿日:2020年12月25日
TypeScript、JavaScriptではグローバルな関数のthisが暗黙的に割り当てられるために注意が必要です。TypeScriptの型指定の機能を使うことでこのthisの割り当てを危険性の低いものに変えることができます。
TypeScript(JavaScriptも同様)では関数のthisの割り当てにある特徴があります。
例えばまず次の様なユーティリティ関数を作成したとします。
function sendGreeting() {
console.log(`Hello ${this.getFullName()}`);
}
この関数はgetFullName()という関数を持つObjectにバインドされることを前提にthisを利用して作られています。
call()やapply()、bind()メソッドを使って以下の様に使用できます。
class User {
public readonly firstName: string;
public readonly lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
function sendGreeting() {
console.log(`Hello ${this.getFullName()}`);
}
let user = new User("mars", "quai");
// [ 1 ] callを使った呼び出し
sendGreeting.call(user);
// [ 2 ] applyを使った呼び出し
sendGreeting.apply(user);
// [ 3 ] bindを使った呼び出し
const userSendGreeting = sendGreeting.bind(user);
userSendGreeting();
thisはしっかり理解すれば、この様に便利に使うことができます。
基本的に自身が作った関数の使用法を理解して、毎回正しくObjectをバインドすれば問題ないのですが、
同じチームの誰か(あるいは数年後の自分)が次のようなコードを書いてしまうかも知れません。
sendGreeting(); // Error
このコードはgetFullName()が実装されたObjectがバインドされていないためエラーになるのですが...、
残念な事にそのエラーはトランスパイル後のコード実行時にやっと出力されるのです。
TypeScriptでは関数のthisに型の成約を加えることができます。
関数の第1引数にthisを指定して、そこで型の指定をすることができます。
// Interfaceを定義
interface Person {
firstName: string;
lastName: string;
getFullName(): string;
}
// Interfaceを継承
class User implements Person {
public readonly firstName: string;
public readonly lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
// this
function sendGreeting(this: Person) {
console.log(`Hello ${this.getFullName()}`);
}
ここではInterfaceとしてPersonを定義し、更にそのInterfaceをthisの型成約に追加しました。この引数のthisはバインドなどのthisを割り当てる場合にのみ使用され、通常の呼び出しで渡す引数では無視されます。
thisを明確に定義することで、編集時のエディタの機能、またはトランスパイル時にthisの違法な割り当てをエラーとして出力してくれます。
試しにsendGreeting()をthisの割り当てなしで呼んでみましょう。
sendGreeting(); // 型 'void' の 'this' コンテキストを型 'Person' のメソッドの 'this' に割り当てることはできません。ts(2684)
上の節でthisの明確な型指定のメリットを理解できたと思います。
さらに気になる点があるすると、コード上に暗黙的なthisと型指定をするthisが混在することでしょう。
tsconfig.jsonで"noImplicitThis": trueの設定を入れることでグローバル関数の様な暗黙的なthisの型推定を禁止することができます。
{
"compilerOptions": {
"noImplicitThis": true
}
}
function sendGreeting() {
console.log(`Hello ${this.getFullName()}`); // [Error] 'this' は型として注釈を持たないため、暗黙的に型 'any' になります。
}