ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

NestJSでよく見る@Decoratorって何なのかわからなかったからサクッと試してみた

f:id:rasukarusan:20210327224546p:plain
NestJSで@Controllerのような@を使った書き方をよくみる。よくわからず使っていたけど調べてみたらなんてことなかったので書き留めておく。

こういうやつ。

@Controller('app')
export class AppController {
  @Post()
  create(@Body() dto: CreateAppDto) {}
}

Decoratorっていうjsの機能らしい

DecoratorはJavaとかPythonでは普通に使われている概念らしく、jsではES2016から導入されたみたい。詳しくは下記。
Exploring EcmaScript Decorators. Iterators, generators and array… | by Addy Osmani | Google Developers | Medium

何に使えるのかって言うとメソッド実行時にログを出力したり、引数のチェックをしたり、依存関係の注入(DI)に使える。NestJSではDIとして使われていることが多い。@Injectableみたいなやつね。

サクッと試すための環境を整える

実際にコードを書いて試す前に環境を整える。vimのquickrunを使う前提。quickrun使わなくても普通にts-nodeコマンドで実行してもいいので、vimmerじゃない人は飛ばしちゃってOK。

ts-node test.ts

~/tsconfig.json

~/tsconfig.json

{
  "compilerOptions": {
    "target": "ES2016",
    "module": "commonjs",
    "lib": ["ES2016", "dom"],
    "alwaysStrict": true,
    "experimentalDecorators": true
  }
}

experimentalDecorators:trueが肝。これがないとDecoratorが使えない。alwaysStrict: trueで実行時にエラーが出るようにする。

~/test.ts

class Cat {
  say() {
    console.log('nya-!')
  }
}
const cat = new Cat()
cat.say() // nya-!

このファイルで色々実験していく

quickrunの設定

let g:quickrun_config={
  \'_': {
  \  'split': ''
  \},
\}
set splitbelow
" \rで保存して実行、画面分割を下に出す
nnoremap \r :cclose<CR>:write<CR>:QuickRun -mode n<CR>
xnoremap \r :<C-U>cclose<CR>:write<CR>gv:QuickRun -mode v<CR>

\rでquickrunを実行。

早速Decoratorを試してみる

まずは@XXXXXとして使えるようなDecorator関数を作る。今回は@readonlyとして使えるような関数を作る。

~/test.ts

function readonly(target: any, key: string, descriptor:PropertyDescriptor) {
  descriptor.writable = false
  return descriptor
}

注意したい点は引数にtarget, key, descriptorをとるということ。これだけ抑えていれば問題ない。
実際に使うときは下記のような感じ。

function readonly(target: any, key: string, descriptor:PropertyDescriptor) {
  descriptor.writable = false
  return descriptor
}

class Cat {
  @readonly
  say() {
    console.log('nya-!')
  }
}
const cat = new Cat()
cat.say()

この状態では何もエラーは出ないが、say()を書き換えようとすると、エラーを出してくれる。

function readonly(target: any, key: string, descriptor:PropertyDescriptor) {
  descriptor.writable = false
  return descriptor
}

class Cat {
  @readonly
  say() {
    console.log('nya-!')
  }
}
const cat = new Cat()
cat.say()

// 書き換え
cat.say = function() {
  console.log('wan!')
}

// cat.say = function() {
//        ^
// TypeError: Cannot assign to read only property 'say' of object '#<Cat>'

めちゃシンプル。全然難しいことなかった。

引数を取るようなDecoratorを作ってみる

たとえばNestJSでよく見る@Get('/')のようなDecoratorを作ってみる。

function Get(endpoint?: string) {
  return function(target: any, key: string, descriptor:PropertyDescriptor) {
    // ここでなにがしかをする
    console.log(endpoint)
  }
}

class Cat {
  @Get('/hoge')
  say() {
    console.log('nya-!')
  }
}

あくまでイメージなので全く機能はないが、こんな感じで引数付きのDecoratorを作ることができる。

クラスにあてるDecoratorの作り方

先程作った@readonlyはメソッドにあてるDecorator。クラスにDecoratorをあてる場合、引数が異なってくる。

function Controller(fnc:Function) {
  console.log(fnc) // [class Cat]
}

@Controller
class Cat {
}

同様にプロパティやパラメータにあてるDecorator関数も存在する。詳細は下記。

mae.chab.in

終わり

NestJSのDecoratorの使い方はめちゃくちゃ勉強になる。他の言語書くときも使えるような考え方だから思想を知っておくのは絶対良い。