SlideShare a Scribd company logo
1 of 24
TypeScriptのDecoratorについて
基礎からDecoratorによるバリデーション作成まで
アジェンダ
● デコレータとは
● クラスデコレータ
○ コンストラクタ関数とは
● デコレータファクトリーとは
● 各種デコレータについて
○ class/property/accessor/method/parameter
○ PropertyDescriptor
● デコレータによる値の変更
● デコレータによるバリデーション
デコレータとは
● デコレータはメタプログラミングに役に立つ機能
● メタプログラミングとは
○ ユーザが直接触ったり見たりする機能には使われず、
開発者が使いやすい道具を提供することに向いている
○ クラスやクラスのメソッドが正しく使われることを保証
○ 表向きには見えない変換処理を行う
● デコレータは充てる場所によって受け取れる引数などが変わる
クラスデコレータ
● デコレータは最終的にはただの関数
● 引数は1つ
○ コンストラクタ関数(詳細は次で)
● デコレータはクラスが定義された時に実行される
○ インスタンス化のタイミングではない
○ インスタンス化のコードを削除してもlogは表
示される
コンストラクタ関数とは
● そもそもconstructorとは
○ よくclassに書かれてるあれ
○ インスタンス(実体)を作成する関数のこと
○ 初期化も行う
● でも、JSってClassないよね?
○ JSで「class hoge」と記述しているのはシンタックスシュガー
○ 内部的にはfunction(画像はES6以前の記述方式)
○ new Person()とすることでインスタンスがthisにセットされる
■ 暗黙的に this = {};
■ 暗黙的に return this;
● コンストラクタ関数は技術的には通常の関数
○ ただし、[[Construct]]という隠しメソッドを持つオブジェクト
● つまり、new式を使用して新規オブジェクト
(インスタンス)を作成する関数
デコレータファクトリーとは
● デコレータを何かに充てる時に、デコレータをカスタマイズできるようにす
るもの
デコレータファクトリー:基礎編
● 任意の引数を必要な数だけLogger
の引数として指定できる
● 引数として渡した値をLogger関数
内で使用できる
● デコレータの内部で行うことをカ
スタマイズできる
デコレータファクトリー2:応用編1
デコレータファクトリー2:応用編2
複数のデコレータ
● クラスには複数のデコレータを追加できる
○ 例:@Logger()
@WithTemplate()
複数デコレータ:実行順番
「デコレータ関数」
classに近い方から「下から上へ」
「内側の匿名関数」
記述された順通り、「上から下へ」
「実行順番」
外側Logger >
外側WithTemplate >
WithTemplateの匿名関数 >
Loggerの匿名関数
デコレータを追加できる場所
● Product class 内の全て追加できる
○ class
○ property
○ accessor
○ method
○ parameter
プロパティデコレータ
● 2つの引数を受け取る
1. target
a. インスタンスプロパティへデコレータを設定した場合
i. このクラスのプロトタイプ
b. スタティックプロパティへデコレータを設定した場合
i. コンストラクター
2. propertyName
a. string | Symbol
アクセサデコレータ(getter/setter)
● 3つの引数を受け取る
1. target
a. インスタンスプロパティへデコレータを設定した
場合
i. このクラスのプロトタイプ
b. スタティックプロパティへデコレータを設定した
場合
i. コンストラクター
2. AccessorName
a. アクセサのネーム
3. PropertyDescriptor
a. TypeScriptに組み込まれている型
PropertyDescriptor
メソッドデコレータ
● 3つの引数を受け取る
1. target
a. インスタンスプロパティへデコレータを設定した
場合
i. このクラスのプロトタイプ
b. スタティックプロパティへデコレータを設定した
場合
i. コンストラクター
2. MethodName
a. メソッドのネーム
3. PropertyDescriptor
a. TypeScriptに組み込まれている型
パラメータデコレータ
● 3つの引数を受け取る
1. target
a. インスタンスプロパティへデコレータを設定した
場合
i. このクラスのプロトタイプ
b. スタティックプロパティへデコレータを設定した
場合
i. コンストラクター
2. MethodName
a. メソッドのネーム(パラメータ名ではない)
3. position
a. パラメータの位置(ゼロ始まり)
クラスデコレータによるクラスの変更
● クラスに対して追加されたデコレータは、
コンストラクタ関数を返すことができる
● コンストラクタ関数を返すか、シンプル
にclassを返す
● デコレータ関数が受け取ったコンストラ
クタ関数を継承することができる
● 型の定義
○ 内側のデコレータ関数をGenerics型
にして、classによる制約をつける
○ newキーワードを使ってインスタン
スを作れるもの、という記述
その他のデコレータの返却値
● メソッドデコレータやアクセサデコレータも値を返すことができる
○ 新しいPropertyDescriptorの設定を返すことができる
● プロパティデコレータとパラメータデコレータ
○ 値を返すことはできるが、サポートされていないため何も起きない
メソッドデコレータによるPropertyDescriptorの変更1
● logの結果はundefined
● なぜ?
○ addEventListenerにメソッドを渡し
ているため
○ この例でthisはbuttonを参照してい
る
● 一般的にはbindメソッドを使って回避す
る
○ printer.showMessage.bind(printer)
メソッドデコレータによるPropertyDescriptorの変更2
● get(getter)はアクセスする前にロジックを
実行できる
● PropertyDescriptor オブジェクトのvalue
は設定せずにgetからvalueを返す
● get内で書かれているthisの参照先
○ このデコレータの対象メソッドが所
属しているオブジェクト
デコレータによるバリデーション1
デコレータによるバリデーション2
まとめ
● デコレータについて
○ デコレータはメタプログラミングに役に立つ機能
○ デコレータの種類で受け取れる引数が変わる
● クラスデコレータ
○ 受け取れる引数はコンストラクタ関数
○ デコレータはクラスが定義された時に実行される
● デコレータファクトリーとは
○ デコレータをカスタマイズできるようにするもの
○ 受け取った引数を匿名関数で返す
● 各種デコレータについて
○ class/property/accessor/method/parameter
○ PropertyDescriptor
● デコレータによる値の変更
● デコレータによるバリデーション

More Related Content

What's hot

Goの時刻に関するテスト
Goの時刻に関するテストGoの時刻に関するテスト
Goの時刻に関するテストKentaro Kawano
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことBIGLOBE Inc.
 
JJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めた
JJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めたJJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めた
JJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めたKoichi Sakata
 
TDD のこころ
TDD のこころTDD のこころ
TDD のこころTakuto Wada
 
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話JustSystems Corporation
 
Bapp Storeを調べてみたよ!
Bapp Storeを調べてみたよ!Bapp Storeを調べてみたよ!
Bapp Storeを調べてみたよ!zaki4649
 
GraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることGraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることShingo Fukui
 
Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発Kenjiro Kubota
 
SolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみようSolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみようShinsuke Sugaya
 
マイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCマイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCdisc99_
 
マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018
マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018
マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018Yusuke Suzuki
 
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24Shin Ohno
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!mosa siru
 
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
Amazon Cognito使って認証したい?それならSpring Security使いましょう!Amazon Cognito使って認証したい?それならSpring Security使いましょう!
Amazon Cognito使って認証したい?それならSpring Security使いましょう!Ryosuke Uchitate
 
「多要素認証」と言っても色々あるんです
「多要素認証」と言っても色々あるんです「多要素認証」と言っても色々あるんです
「多要素認証」と言っても色々あるんですIIJ
 
SQLインジェクション再考
SQLインジェクション再考SQLインジェクション再考
SQLインジェクション再考Hiroshi Tokumaru
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話
DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話
DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話JustSystems Corporation
 
設計と実装で 抑えておきたい サービスクラスと例外
設計と実装で 抑えておきたい サービスクラスと例外設計と実装で 抑えておきたい サービスクラスと例外
設計と実装で 抑えておきたい サービスクラスと例外Takuya Sato
 
塹壕よりLivetとMVVM
塹壕よりLivetとMVVM塹壕よりLivetとMVVM
塹壕よりLivetとMVVMHiroshi Maekawa
 

What's hot (20)

Goの時刻に関するテスト
Goの時刻に関するテストGoの時刻に関するテスト
Goの時刻に関するテスト
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
 
JJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めた
JJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めたJJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めた
JJUG CCC 2017 Spring Seasar2からSpringへ移行した俺たちのアプリケーションがマイクロサービスアーキテクチャへ歩み始めた
 
TDD のこころ
TDD のこころTDD のこころ
TDD のこころ
 
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
 
Bapp Storeを調べてみたよ!
Bapp Storeを調べてみたよ!Bapp Storeを調べてみたよ!
Bapp Storeを調べてみたよ!
 
GraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ることGraphQLのsubscriptionで出来ること
GraphQLのsubscriptionで出来ること
 
Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発Laravelでfacadeを使わない開発
Laravelでfacadeを使わない開発
 
SolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみようSolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみよう
 
マイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCマイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPC
 
マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018
マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018
マイクロサービス化デザインパターン - #AWSDevDay Tokyo 2018
 
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
Amazon Cognito使って認証したい?それならSpring Security使いましょう!Amazon Cognito使って認証したい?それならSpring Security使いましょう!
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
 
「多要素認証」と言っても色々あるんです
「多要素認証」と言っても色々あるんです「多要素認証」と言っても色々あるんです
「多要素認証」と言っても色々あるんです
 
SQLインジェクション再考
SQLインジェクション再考SQLインジェクション再考
SQLインジェクション再考
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話
DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話
DDDとクリーンアーキテクチャでサーバーアプリケーションを作っている話
 
設計と実装で 抑えておきたい サービスクラスと例外
設計と実装で 抑えておきたい サービスクラスと例外設計と実装で 抑えておきたい サービスクラスと例外
設計と実装で 抑えておきたい サービスクラスと例外
 
塹壕よりLivetとMVVM
塹壕よりLivetとMVVM塹壕よりLivetとMVVM
塹壕よりLivetとMVVM
 

More from tak

可読性について リーダブルコード Part5(優れたテストコード2)
可読性について リーダブルコード Part5(優れたテストコード2)可読性について リーダブルコード Part5(優れたテストコード2)
可読性について リーダブルコード Part5(優れたテストコード2)tak
 
可読性について リーダブルコード Part4(優れたテストコード1)
可読性について リーダブルコード Part4(優れたテストコード1)可読性について リーダブルコード Part4(優れたテストコード1)
可読性について リーダブルコード Part4(優れたテストコード1)tak
 
可読性について リーダブルコード Part3(コードの再構築)
可読性について リーダブルコード Part3(コードの再構築)可読性について リーダブルコード Part3(コードの再構築)
可読性について リーダブルコード Part3(コードの再構築)tak
 
可読性について リーダブルコード Part2(ループとロジックの単純化)
可読性について リーダブルコード Part2(ループとロジックの単純化)可読性について リーダブルコード Part2(ループとロジックの単純化)
可読性について リーダブルコード Part2(ループとロジックの単純化)tak
 
可読性について リーダブルコード part1(表面上の改善)
可読性について リーダブルコード part1(表面上の改善)可読性について リーダブルコード part1(表面上の改善)
可読性について リーダブルコード part1(表面上の改善)tak
 
DiI/DIコンテナを一から学んでみた
DiI/DIコンテナを一から学んでみたDiI/DIコンテナを一から学んでみた
DiI/DIコンテナを一から学んでみたtak
 
Rust + web assemblyやってみた
Rust + web assemblyやってみたRust + web assemblyやってみた
Rust + web assemblyやってみたtak
 
第ⅴ部:clean architecture アーキテクチャ Part8
第ⅴ部:clean architecture アーキテクチャ Part8第ⅴ部:clean architecture アーキテクチャ Part8
第ⅴ部:clean architecture アーキテクチャ Part8tak
 
第ⅴ部:clean architecture アーキテクチャ Part7
第ⅴ部:clean architecture アーキテクチャ Part7第ⅴ部:clean architecture アーキテクチャ Part7
第ⅴ部:clean architecture アーキテクチャ Part7tak
 
第ⅴ部:clean architecture アーキテクチャ Part6
第ⅴ部:clean architecture アーキテクチャ Part6第ⅴ部:clean architecture アーキテクチャ Part6
第ⅴ部:clean architecture アーキテクチャ Part6tak
 
第ⅴ部:clean architecture アーキテクチャ Part5
第ⅴ部:clean architecture アーキテクチャ Part5第ⅴ部:clean architecture アーキテクチャ Part5
第ⅴ部:clean architecture アーキテクチャ Part5tak
 
第ⅴ部:clean architecture アーキテクチャ Part4
第ⅴ部:clean architecture アーキテクチャ Part4第ⅴ部:clean architecture アーキテクチャ Part4
第ⅴ部:clean architecture アーキテクチャ Part4tak
 
第ⅴ部:clean architecture アーキテクチャ Part3
第ⅴ部:clean architecture アーキテクチャ Part3第ⅴ部:clean architecture アーキテクチャ Part3
第ⅴ部:clean architecture アーキテクチャ Part3tak
 
第ⅴ部:clean architecture アーキテクチャ Part2
第ⅴ部:clean architecture アーキテクチャ Part2第ⅴ部:clean architecture アーキテクチャ Part2
第ⅴ部:clean architecture アーキテクチャ Part2tak
 
第ⅴ部:clean architecture アーキテクチャ Part1
第ⅴ部:clean architecture アーキテクチャ Part1第ⅴ部:clean architecture アーキテクチャ Part1
第ⅴ部:clean architecture アーキテクチャ Part1tak
 
第ⅳ部:Clean architecture コンポーネントの原則
第ⅳ部:Clean architecture コンポーネントの原則第ⅳ部:Clean architecture コンポーネントの原則
第ⅳ部:Clean architecture コンポーネントの原則tak
 
第ⅲ部:Clean architecture 設計の原則
第ⅲ部:Clean architecture 設計の原則第ⅲ部:Clean architecture 設計の原則
第ⅲ部:Clean architecture 設計の原則tak
 
第ⅱ部:Clean architecture 構成要素から始めよ
第ⅱ部:Clean architecture 構成要素から始めよ第ⅱ部:Clean architecture 構成要素から始めよ
第ⅱ部:Clean architecture 構成要素から始めよtak
 
第ⅰ部:Clean Architecture イントロダクション
第ⅰ部:Clean Architecture イントロダクション第ⅰ部:Clean Architecture イントロダクション
第ⅰ部:Clean Architecture イントロダクションtak
 

More from tak (19)

可読性について リーダブルコード Part5(優れたテストコード2)
可読性について リーダブルコード Part5(優れたテストコード2)可読性について リーダブルコード Part5(優れたテストコード2)
可読性について リーダブルコード Part5(優れたテストコード2)
 
可読性について リーダブルコード Part4(優れたテストコード1)
可読性について リーダブルコード Part4(優れたテストコード1)可読性について リーダブルコード Part4(優れたテストコード1)
可読性について リーダブルコード Part4(優れたテストコード1)
 
可読性について リーダブルコード Part3(コードの再構築)
可読性について リーダブルコード Part3(コードの再構築)可読性について リーダブルコード Part3(コードの再構築)
可読性について リーダブルコード Part3(コードの再構築)
 
可読性について リーダブルコード Part2(ループとロジックの単純化)
可読性について リーダブルコード Part2(ループとロジックの単純化)可読性について リーダブルコード Part2(ループとロジックの単純化)
可読性について リーダブルコード Part2(ループとロジックの単純化)
 
可読性について リーダブルコード part1(表面上の改善)
可読性について リーダブルコード part1(表面上の改善)可読性について リーダブルコード part1(表面上の改善)
可読性について リーダブルコード part1(表面上の改善)
 
DiI/DIコンテナを一から学んでみた
DiI/DIコンテナを一から学んでみたDiI/DIコンテナを一から学んでみた
DiI/DIコンテナを一から学んでみた
 
Rust + web assemblyやってみた
Rust + web assemblyやってみたRust + web assemblyやってみた
Rust + web assemblyやってみた
 
第ⅴ部:clean architecture アーキテクチャ Part8
第ⅴ部:clean architecture アーキテクチャ Part8第ⅴ部:clean architecture アーキテクチャ Part8
第ⅴ部:clean architecture アーキテクチャ Part8
 
第ⅴ部:clean architecture アーキテクチャ Part7
第ⅴ部:clean architecture アーキテクチャ Part7第ⅴ部:clean architecture アーキテクチャ Part7
第ⅴ部:clean architecture アーキテクチャ Part7
 
第ⅴ部:clean architecture アーキテクチャ Part6
第ⅴ部:clean architecture アーキテクチャ Part6第ⅴ部:clean architecture アーキテクチャ Part6
第ⅴ部:clean architecture アーキテクチャ Part6
 
第ⅴ部:clean architecture アーキテクチャ Part5
第ⅴ部:clean architecture アーキテクチャ Part5第ⅴ部:clean architecture アーキテクチャ Part5
第ⅴ部:clean architecture アーキテクチャ Part5
 
第ⅴ部:clean architecture アーキテクチャ Part4
第ⅴ部:clean architecture アーキテクチャ Part4第ⅴ部:clean architecture アーキテクチャ Part4
第ⅴ部:clean architecture アーキテクチャ Part4
 
第ⅴ部:clean architecture アーキテクチャ Part3
第ⅴ部:clean architecture アーキテクチャ Part3第ⅴ部:clean architecture アーキテクチャ Part3
第ⅴ部:clean architecture アーキテクチャ Part3
 
第ⅴ部:clean architecture アーキテクチャ Part2
第ⅴ部:clean architecture アーキテクチャ Part2第ⅴ部:clean architecture アーキテクチャ Part2
第ⅴ部:clean architecture アーキテクチャ Part2
 
第ⅴ部:clean architecture アーキテクチャ Part1
第ⅴ部:clean architecture アーキテクチャ Part1第ⅴ部:clean architecture アーキテクチャ Part1
第ⅴ部:clean architecture アーキテクチャ Part1
 
第ⅳ部:Clean architecture コンポーネントの原則
第ⅳ部:Clean architecture コンポーネントの原則第ⅳ部:Clean architecture コンポーネントの原則
第ⅳ部:Clean architecture コンポーネントの原則
 
第ⅲ部:Clean architecture 設計の原則
第ⅲ部:Clean architecture 設計の原則第ⅲ部:Clean architecture 設計の原則
第ⅲ部:Clean architecture 設計の原則
 
第ⅱ部:Clean architecture 構成要素から始めよ
第ⅱ部:Clean architecture 構成要素から始めよ第ⅱ部:Clean architecture 構成要素から始めよ
第ⅱ部:Clean architecture 構成要素から始めよ
 
第ⅰ部:Clean Architecture イントロダクション
第ⅰ部:Clean Architecture イントロダクション第ⅰ部:Clean Architecture イントロダクション
第ⅰ部:Clean Architecture イントロダクション
 

TypeScriptのdecoratorについて

Editor's Notes

  1. 今回はTypeScriptのDecoratorについて発表したいと思います。 以前にDI/DIコンテナについて発表させて頂いた際に、 少しだけ触れていましたが今回は深堀した形になります。
  2. まずデコレータとはメタプログラミングに役に立つ機能です。 メタプログラミングとは ユーザが直接触ったり見たりする機能には使われませんが、 開発者が使いやすい道具を提供することに向いています。 例えば、クラスやクラスのメソッドが正しく使われることを保証したり、 表向きには見えない変換処理を行なったりします。 JavaのSpringなどを触ったご経験がある方は、 アノテーションというとイメージしやすいと思います。 また、デコレータは充てる場所によって受け取れる引数などが変わります。
  3. まず初めにクラスデコレータについてです。 上にLogger関数を作っていますが、今回これがデコレータ関数となります。 この関数を直接使うのではなく代わりにクラスに対してデコレータとして追加します。 関数を大文字から始めていますが、 必須ではなく小文字から始めても問題ないです。 ただし、多くのライブラリのデコレータは大文字から始まっています。 デコレータは最終的にはただの関数です。 その関数を特定の方法で例えば今回はクラスに適応するということです。 デコレータ関数を定義した後は使用方法についてですが、 デコレータとして使用するには対象のクラスの前に@をつけます。 @はデコレータを認識するための特別な識別子で、この@の後ろに関数名を指定します。 デコレータは充てる場所によって受け取れる引数など変わると申しましたが、 今回のようにクラスへ充てるDecoratorの場合、受け取れる引数はコンストラクタ関数です。 コンストラクタ関数については次で解説いたします。 またデコレータが実行される順番ですが、 console.logの横に順番を振っている通り、 この画像のコードでは上から順番になります。 注目するポイントは、1番目の「ログ出力中」と 2番目の「コンストラクタ関数」の出力が Person class内のconstructorより前に表示されている点です。 これは非常に重要で何を意味しているかというと、 デコレータはJavaScriptがクラスの定義を見つけた時に実行されます。 インスタンス化のタイミングではありません。 例えばインスタンス化しているnewの行を 削除してもLoggerデコレータの1と2のコンソールログは出力されます。
  4. では、先程登場したコンストラクタ関数について解説いたします。 そもそもコンストラクターとは、 ですが、よくclassに書かれているあれになります。 コンストラクターとはインスタンス(実体)を作成する関数のことで、 初期化も行います。 ここで1つ疑問が浮かびます。 「でも、JSってClassないよね?」 という点です。 疑問に思われた方はその通りで、 JSでは「class hoge」と記述はできますが、 JSはプロトタイプオブジェクト指向言語のため、 内部的にはfunctionであくまでプロトタイプ構文を覆い包む シンタックスシュガーでしかありません。 ES6以前の記述方式は、 画像のようにfunctionで記述されており、 擬似的なクラスでしかありません。 この画像のthisを不思議に思われるかもしれませんが、 JSは変数や関数だけでなく、 数値や文字列などに至るまで全てがオブジェクトになっており、 JSではnewの式を実行すると暗黙的に新規のオブジェクトが作られ、 thisへ代入されます。 そして暗黙的にthisがreturnされます。 このようにコンストラクタ関数は技術的には通常の関数であり、 つまりは、new式を使用して新規オブジェクトを作成する関数であるといえます。
  5. 続いては、デコレータファクトリーについてです。 先程はクラスデコレータを作成することができましたが、 この他にもデコレータファクトリというものを定義することができます。 デコレータを何かに割り当てる時に、 デコレータをカスタマイズできるようにするものです。 では、先程のクラスデコレータをデコレータファクトリに変更してみます。
  6. まず先程のLogger関数に匿名関数を返すようにします。 そして、匿名関数の引数にコンストラクタ関数の引数を追加します。 この匿名関数の中には、 このデコレータで実行したい処理を書きます。 これで新しい関数を返す関数ができました。 従ってこれをデコレータとして適応する場合には 呼び出し時に関数として実行する必要があります。 @Loggerの後に()を付けますが、 これはもちろん内側の匿名関数ではなく外側のLogger関数を実行しています。 そして、Logger関数から返される新しい匿名関数を Personクラスのデコレータとして適応しています。 では、なぜこのデコレータファクトリをするのかというと、 例えば、任意の引数を必要な数だけ 1行目のLoggerの引数として指定することができます。 そして、呼び出し時に引数を渡せれるので、 引数として渡した値をLogger関数内でいかようにも使うことができます。 このようにデコレータファクトリを使うことで デコレータの内部で行うことをカスタマイズすることができます。
  7. 続いては、デコレータファクトリーの応用編ということで WithTemplateというクラスデコレータの 引数にtemplateとhookIdを渡してDOMへ表示させるデコレータを 作ってみます。 画像のようにWithTemplateデコレータは渡されたhookId、 今回はid=appのElementをgetElementByIdで取得して、 そのElementへ第一引数として渡されたH1タグを代入させることで ブラウザ上に表示させています。 結果は右のようにH1タグが表示されます。
  8. 続いてもデコレータファクトリーの応用編です。 WithTemplateデコレータの匿名関数の引数で受け取ったコンストラクタ関数を インスタンス化してクラスに存在しているname値を使用するパターンです。 まず匿名関数の引数にコンストラクターを受け取れるようにし、 インスタンス化をして適当にquerySelectorした箇所へ Person classのnameプロパティを代入しています。 するとクラスに存在しているプロパティの値が表示されます。 例題はあまり実務的ではありませんが、 このようにコンストラクタ関数からインスタンス化した プロパティの値を使用することもできます。
  9. 続いては、デコレータの基本的な概念についてです。 クラスには複数のデコレータが追加できます。 例えば、先ほどまで見ていたLoggerデコレータとWithTemplateデコレータを クラスの上に記述することができます。 ここでどちらのデコレータが先に実行されるかという疑問がでて来ます。
  10. 実行の順番ですが、 画像のように最初にデコレータファクトリーを定義して、 その下でクラスデコレータを充てているケースだとします。 これは、classに近い方から WithTemplate > Loggerのように「下から上に実行」されます。 ただし、これは内側の匿名関数が実行される順番なので、 外側のデコレータ関数はそれよりも前に実行されるため 通常のJavaScriptのルール通り記述された順番通りになります。 なぜなら@Logger()としているため、 内部のデコレータ関数ではなくLogger関数自体を実行しているからです。 つまり、 Loggerの外側 > WithTemplateの外側 > WithTemplateの匿名関数 > Loggerの匿名関数
  11. 続いては、デコレータを追加できる場所を見ていきます。 先程まではクラスデコレータに絞って解説してまいりましたが、 クラス以外にもデコレータは追加できます。 端的に申し上げると、Product class で記述している箇所の 全てがデコレータを追加できる場所です。 つまり、クラス/プロパティ/アクセサ/メソッド/パラメータです。
  12. 最初にプロパティデコレータについてです。 プロパティへ充てた場合のデコレータが受け取る引数ですが、 2つあります。 1つ目はtargetです。 targetに何が渡されるかは、 インスタンスプロパティかstaticプロパティかで変わります。 インスタンスプロパティへデコレータを設定した場合、 このクラスのプロトタイプが渡されます。 staticプロパティの場合はコンストラクタ関数が渡されます。 2つ目の引数は、単純にpropertyNameが渡されます。 これはシンプルにstringかもしれませんしSymbolである可能性があります。 この時点ではどちらかわかりません。 実際に、このログを出力すると右下のようになります。 まず最初のTextのみのログが出力されて、 targetはプロトタイプが渡されています。 そして、propertyNameは「title」が表示されています。 これらlogはインスタンス化していなくても出力されます。 なぜなら冒頭お伝えしたように、 デコレータはJavaScriptでクラス定義が登録された時に実行されるからです。 つまり、このプロパティを持っているコンストラクタ関数が JavaScriptで作られた時です。
  13. 続いてアクセサデコレータです。 3つの引数を受け取ります。 1つ目は、targetでプロパティデコレータと一緒です。 2つ目は、アクセサのnameを取ります。 3つ目は、プロパティディスクリプタです。 これはTypeScriptに組み込まれている型です。 出力結果は右下画像になります。
  14. 上から コンフィギュアブル イニューマラブル バリュー ライタブル get set になります。
  15. 続いてメソッドデコレータです。 受け取れる3つの引数はアクセサデコレータとほぼ一緒ですので、 解説を省略いたします。
  16. 続いてパラメータデコレータです。 パラメータデコレータはパラメータの前に付けます。 パラメータのデコレータは1つの引数に対してではなく、 全てのパラメータに対して追加でき、 それぞれのパラメータに独立して別々にデコレータを設定できます。 受け取れる引数は3つです。 1つ目はtargetです。 これも今までと同じです。 2つ目はMethodNameです。 パラメータの名前ではないため注意が必要です。 3つ目はパラメータの位置です。 ゼロ始まりです。 右下画像が実際のlogです。 以上でデコレータを追加できる箇所全てを学びました。
  17. クラスデコレータなどは値を返すことができます。 プロパティデコレータとパラメータデコレータも値を返すことはできますが、 サポートされていないため何も起きません。 デコレータファクトリーではなく、 内側の実際のデコレータ関数のことです。 どんな値を返せれるかはデコレータの種類によって変わります。 クラスに対して追加されたデコレータは、 コンストラクタ関数を返すことができます。 つまり、「デコレータ関数」が受け取った「コンストラクタ関数」を 別のものに置き換えることができます。 これは、コンストラクタ関数を返すか、 シンプルにclassを返します。 classはコンストラクタ関数のシンタックスシュガーなので、 ≒となります。 (syntax sugarとは構文を省略した記述方法) (JSではclass命令で定義されたクラスは内部的には関数) (今までFunctionオブジェクトで表現していたクラスをよりわかりやすくした) (class命令はプロトタイプベースのオブジェクト指向構文を覆うシンタックスシュガー) このclassは名前をつける必要がなく「return class」で記述できます。 そして、デコレータ関数が受け取ったコンストラクタ関数を 継承することができます。 なので、「return class extends constructor」とできます。 ここでやっていることは、内側のデコレータ関数で 新しいclassを返しています。 extendsは必須ではありませんが、 元のクラスの定義を残しておきたいので継承しています。 class extends constructor の中に新しい機能を追加できます。 継承したclassでコンストラクタ関数を追加した場合には、 superを呼び出す必要があります。 superを使ってthis.で親へアクセスできます。 今回行なっていることは、 デコレータが追加されたclassを新しいclass、新しいコンストラクタ関数で 置き換えるということを行なっています。 画像のように変更したので、 DOMへの表示ロジックはclassをインスタンス化した時に実行されます。 デコレータ関数はclassが定義された時に実行されるので タイミングが異なります。 ここで、型エラーが表示されるのですが、 Generics型で解消します。 まず、内側のデコレータ関数をGenerics型にします。 そして、originalConstructorの型をTにします。 このGenerics型はclassを受け取る必要があるので、 extendsで制約を付けます。 classを制約にする為には、{}(中括弧)を書いてオブジェクトを書きます。 その中にnewという名前のプロパティを書きます。 newは予約後ですが、TypeScriptに次のことを伝えます。 「これはオブジェクトですが、newキーワードを使ってインスタンスを作れるものです。つまり、コンストラクタ関数です。」 ということを伝えます。 このnew関数は任意の数の引数を受け取ります。 なので、Rest parametersを使います。 型はanyの配列にします。 これによって引数に対して非常に柔軟な型を定義しています。 このnew関数は何らかのオブジェクトを返します。 この引数を新しいclassのコンストラクタ関数の引数にも 定義する必要があります。 これによって全てのコンストラクタ関数の引数を受け取って、 元のコンストラクタ関数へ渡すことができます。 今回は、personクラスのインスタンス化をする時に、 personクラスに必要なconstructorの引数を渡せるようになります。 ここまでだと、nameプロパティが存在していることがTypeScriptにはわからないため、 Tが継承しているコンストラクタ関数が返すオブジェクトの型に定義します。 これはクラスがインスタンス化された時にしか実行されませんので、 インスタンス化するコードを削除するとDOMにも表示されません。
  18. クラスの他にはメソッドデコレータやアクセサデコレータも値を返すことができます。 これらは、新しいPropertyDescriptorの設定を返すことができます。 クラスデコレータ同様にデコレータ関数にreturn文を追加して、 PropertyDescriptorを返すことができます。 その場合、戻り値の型をPropertyDescriptorと明確に指定することが推奨されます。 戻り値のオブジェクトにPropertyDescriptorのプロパティを設定します。 プロパティデコレータとパラメータデコレータも値を返すことはできますが、 サポートされていないため何も起きません。
  19. では、メソッドデコレータによるPropertyDescriptorの変更例を見ていきます。 例題として、DOMのボタンを押した時に特定のメソッドが実行されるようにしてみます。 簡易的にbuttonをクリックしたら「クリックしました」とlogへ表示されるクラスを作成いたしました。 実際に画像の状態でbuttonをクリックするとどうなるかといいますと、 undefinedになってしまいます。 理由は、addEventListenerにメソッドを渡している為です。 showMessageメソッドの中のthisはイベントリスナーから呼ばれた時に、 違うオブジェクトを参照しています。 もしイベントリスナーではなく 直接printer.showMessageを実行した場合のthisはPrinterのオブジェクトを参照しています。 しかし、addEventListenerなどから呼び出された場合、 このthisはそのイベントの対象となった要素への参照になっています。 そうなってしまう理由は、 イベントリスナーはコールバック関数を実行する時に イベントの対象となった要素を関数にバインドするからです。 なので、この例でのthisはbuttonを参照しています。 一般的にはbindメソッドへ参照させたいオブジェクトを渡して、 printer.showMessage.bind(printer)のようにして回避します。 次では、showMessageへのデコレータを追加して、 メソッドのthisが常に自動的にそのメソッドが所属するclassオブジェクトを 参照するようにしてみます。
  20. まず最初に実行されるべきメソッドを取得しています。 そのメソッドはPropertyDescriptorから取得できます。 PropertyDescriptorのvalueに関数が設定されています。 const originarlMethod = descriptor.valueとして、 オリジナルの関数を取り出します。 そして、このデコレータが返すためのPropertyDescriptorを作ります。 getメソッドでは、 利用者がプロパティへアクセスする前に何らかのロジックを実行することができます。 ここでいうプロパティの値はデコレータ関数で受け取っている descriptorのvalueです。 デコレータのプロパティが参照された時にvalueを直接返すのではなく、 何らかの処理を行なってからvalueを返すことができます。 なので、PropertyDescriptor オブジェクトのvalueは設定せずに、 getからvalueを返します。 get内で書かれているthisはどこを参照しているかといいますと、 このgetメソッド(Getter)が所属している実際のオブジェクトから呼び出されため、 常にこのデコレータの対象メソッドが所属しているオブジェクトを参照します。 このthisはイベントリスナーによって上書きされることはありません。 getメソッドはプロパティの値とオブジェクトの間にあるレイヤーのようなものなので、 イベントリスナーからは変更されません。 そして、このデコレータ関数は新しいPropertyDescriptorを返します。 新しいPropertyDescriptorを返されたことで、 クリックした時に正しい参照先になりlogも表示されます。
  21. 続いては、デコレータによるバリデーションを実装します。 初めに、Courseクラスを作成して、 submitを押した時にそれぞれinputに入力されている値を元に、 Courseクラスをインスタンス化します。 ここにユーザーの入力値を検証するようにしたいと思います。
  22. interfaceのValidatorConfigはClassの名前に紐づいて、 どのプロパティにどんなバリデーションの種類が紐づいているのかを登録するものです。 registeredValidators で一度空のオブジェクトを作成します。 Requiredはプロパティデコレータで、 target.constructor.nameでClassの名前に紐付けて、 プロパティ名と required であることを紐付けます。 PositivedNumberも同様です。 validateメソッドではClassを受け取り、 RequiredもPositivedNumberもどちらも正しく入力できている時だけ isValidをtrueで返します。 また、最初の方でバリデーションをしようとするobjがない可能性があるので、 その場合はチェックが不要なのでtrueを返します。 これで、titleは入力必須かつ、priceは正の数字を入力する、 というバリデーションができました。