##記事のデータ構造を表すクラスがある
class Article {
public title: string = '' // タイトル
public body: string = '' // 内容
public caretPosition: number = 0 // キャレット位置
}
こんな感じでの記事の「タイトル」「内容」「キャレット位置」をフィールドに持つクラスがあり、ローカルで記事の内容を保持するために利用し、APIでサーバにもそのまま送信したいとします。
##「キャレット位置」はサーバに送りたく無い
しかし、ここでcaretPosition
については、ローカルでの処理にしか使わないフィールドなのでサーバには送りたく無かったとします。
これをそのままfetchで送ってしまうと、当然のことながらサーバに対してcaretPosition
が含まれたオブジェクトが送られてしまうので、どうにかしてcaretPosition
を取り除いてから送信する必要があります。
##素直なやり方
const article = new Article()
const articleRemote = {
title: article.title,
body: article.body
}
// fetchにarticleRemoteを渡す
必要フィールドのみを詰め直す処理を別途記述するとどうでしょうか。
クラス定義とは遠い場所での処理になるため、クラス定義の変更に対して追随し忘れたりが発生しそうです。
##デコレータとメタデータを使って処理
class Article {
public title: string = ''
public body: string = ''
@LocalOnly
public caretPosition: number = 0
}
こんな感じでクラス定義にサーバに送りたく無いフィールドに対してデコレータで指定できると便利です。
##デコレータを機能するようにする
では実際にLocalOnlyデコレータが機能するようにしてみましょう。
npm install reflect-metadata
でreflect-metadataを導入しておきましょう。
import 'reflect-metadata'
const symbolLocalOnly = Symbol('LocalOnly')
// デコレータ定義。対象フィールドにLocalOnlyのメタデータを付与する。
function LocalOnly(target: any, props: string) {
Reflect.defineMetadata(symbolLocalOnly, true, target, props)
}
class Article {
public title: string = ''
public body: string = ''
@LocalOnly
public caretPosition: number = 0
}
// LocalOnlyフィールド削除関数(実際は再帰的にオブジェクトを辿るようにする)
function removeLocalOnly(data: any): any {
const ret: any = {}
Object.keys(data).forEach(key => {
// LocalOnlyのメタデータが付与されていないものだけを処理
if (!Reflect.hasMetadata(symbolLocalOnly, data, key)) {
ret[key] = data[key]
}
})
return ret
}
これにより
const article = new Article()
const articleRemote = removeLocalOnly(article)
// fetchにarticleRemoteを渡す
と詰め直す処理を書かずに、クラス定義に指定をまとめることができます。
##まとめ
ローカル用とサーバ送信用の微妙に構造の異なるデータを取り扱うケースは結構あると思います。
そんな時はデコレータを利用して不要なフィールドを指定できると直感的だよというお話でした。