デコレータとは
クラスを受け取ってデコレーションする関数のこと
function Logging(constructor: Function) {
console.log('Logging...');
console.log(constructor);
}
@Logging
class User {
name = 'Quill';
constructor() {
console.log('User was created')
}
}
const User1 = new User();
Logging...
[Function: User]
User was created
デコレータとして定義したLogging()は、クラスのインスタンスが生成される前に実行されている。
デコレータの引数の型はFunctionとなることに注意(クラスはコンストラクタ関数の等位構文)。
デコレータファクトリ
デコレータに引数を渡す場合、デコレータファクトリを使用する。
デコレータとして実行したい関数を無名関数としてリターンする。
function Logging(message: string) {
return function (constructor: Function) {
console.log(message);
console.log(constructor);
}
}
@Logging('Logging User')
class User {
name = 'Quill';
constructor() {
console.log('User was created')
}
}
const User1 = new User();
$ node dist/decolator.js
Logging User
[Function: User]
デコレータを使用し簡易版のフレームワークを作成
function Logging(message: string) {
return function (constructor: Function) {
console.log(message);
console.log(constructor);
}
}
function Component(template: string, selector: string) {
return function (constructor: { new(...args: any[]): { name: string } }) {
const mountedElement = document.querySelector(selector);
const instance = new constructor();
if (mountedElement) {
mountedElement.innerHTML = template;
mountedElement.querySelector('h1')!.textContent = instance.name;
}
}
}
@Component('<h1>{{ name }}</h1>', '#app')
@Logging('Logging User')
class User {
name = 'Quill';
constructor() {
console.log('User was created')
}
}
const User1 = new User();
重要なのは以下の部分
return function (constructor: { new(...args: any[]): { name: string } }) {
const mountedElement = document.querySelector(selector);
const instance = new constructor();
Userクラスのnameプロパティを使用するため、newでインスタンスを生成し定数instanceに代入している。
無名関数の第一引数constructorの型としてnew()が使用されているが、これはconstructorが「関数」なのか「コンストラクタ関数」なのかを判別するために使用している。関数はnewできないから、この場合はconstructor関数(クラス)だと指定している。
※jsのクラスはコンストラクタ関数の等位構文で、クラスを関数で表現している。
戻り値に新しいクラスを指定する
ジェネリクスを使用することで、Componentデコレータを使用するクラスのプロパティを型として使用する事ができる。
function Component(template: string, selector: string) {
return function <T extends { new(...args: any[]): { name: string } }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
const mountedElement = document.querySelector(selector);
const instance = new constructor();
if (mountedElement) {
mountedElement.innerHTML = template;
mountedElement.querySelector('h1')!.textContent = instance.name;
}
}
}
}
}
@Component('<h1>{{ name }}</h1>', '#app')
@Logging('Logging User')
class User {
name = 'Quill';
constructor(public age:number) {
console.log('User was created')
}
}
const User1 = new User(23);
プロパティデコレータ
クラス全体ではなく、クラスを部分的にデコレーションすることができる。
その際は引数を2つ取る。
クラスデコレータよりもプロパティデコレータのほうが先に実行される。
プロパティにstatic修飾子をつけた場合
function PropertyLogging(target: any, propertyKey: string) {
console.log('propertyLogging');
console.log(target);
console.log(propertyKey);
}
class User {
@PropertyLogging
static name2 = 'Quill';
constructor(public age:number) {
console.log('User was created')
}
}
const User1 = new User(23);
name2
class User {
constructor(age) {
this.age = age;
console.log('User was created');
}
}
プロパティにstatic修飾子をつけない場合
function PropertyLogging(target: any, propertyKey: string) {
console.log('propertyLogging');
console.log(target);
console.log(propertyKey);
}
class User {
@PropertyLogging
name2 = 'Quill';
constructor(public age:number) {
console.log('User was created')
}
}
const User1 = new User(23);
プロトタイプになる。
{constructor: ƒ}
constructor: class User
length: 1
name: "User"
prototype: {constructor: ƒ}
arguments: (...)
caller: (...)
[[FunctionLocation]]: decorator.js:37
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[2]
[[Prototype]]: Object
プロトタイプとは?
関数をconsole.dir()で確認するとオブジェクトであることが確認できる。
今更聞けないconsole.log()とconsole.dir()の本当の違い
ƒ PropertyLogging(target, propertyKey)
length: 2
name: "PropertyLogging"
prototype: {constructor: ƒ}
arguments: (...)
caller: (...)
[[FunctionLocation]]: decorator.js:30
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[2]
このプロトタイプが、プロパティデコレータの第一引数としてとるものとなる。
function PropertyLogging(target: any, propertyKey: string) {
console.log('propertyLogging');
console.log(target);
console.log(propertyKey);
}
class User {
@PropertyLogging
name2 = 'Quill';
constructor(public age:number) {
console.log('User was created')
}
greeting() {
console.log('hi');
}
}
const user1 = new User(23);
greeting()メソッドを追加してみる.
age,name2はひと目で確認できるが、greeting()はPrototypeの下にある。
コンストラクタ関数(クラス)のプロパティが、そのコンストラクタ関数(クラス)から生成されるインスタンスにコピーされる。そのコピーされるものをPrototypeという。
これはメモリを節約するため。
クラスから生成したインスタンスは、プロパティは違うがメソッドが一緒。
だからPrototypeの中に共通のメソッドを保存している。