ウェブサイト検索

node.js - TypeScriptのゲッターをメモ化する方法


デコレーターを使ってTypeScriptのゲッターをメモ化するために次のアプローチを使用していますが、より良い方法があるか知りたいです。以下のように、npmの人気のあるmemoizeeパッケージを使用しています:

import { memoize } from '@app/decorators/memoize'

export class MyComponent {

  @memoize()
  private static memoizeEyeSrc(clickCount, maxEyeClickCount, botEyesDir) {
    return clickCount < maxEyeClickCount ? botEyesDir + '/bot-eye-tiny.png' : botEyesDir + '/bot-eye-black-tiny.png'
  }

  get leftEyeSrc() {
    return MyComponent.memoizeEyeSrc(this.eyes.left.clickCount, this.maxEyeClickCount, this.botEyesDir)
  }
}

そしてメモ化デコレーターは次の通りです:

// decorated method must be pure
import * as memoizee from 'memoizee'

export const memoize = (): MethodDecorator => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const func = descriptor.value
    descriptor.value = memoizee(func)
    return descriptor
  }
}

MyComponentで2つの別々の関数を使わずにこれを行い、デコレーターをTypeScriptのゲッターに直接追加する方法はありますか?

ここでの考慮すべき点の一つは、デコレートされた関数は純粋でなければならないということですが、これを満たさない回答でもかまわないので、この問題にどのように取り組むかについて一般的な興味があります。

回答 #1

デコレーターは、プロトタイプメソッドとゲッターの両方をサポートするように拡張できます:

export const memoize = (): MethodDecorator => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    if ('value' in descriptor) {
      const func = descriptor.value;
      descriptor.value = memoizee(func);
    } else if ('get' in descriptor) {
      const func = descriptor.get;
      descriptor.get = memoizee(func);
    }
    return descriptor;
  }
}

そして、ゲッターに直接使用されます:

  @memoize()
  get leftEyeSrc() {
    ...
  }

回答 #2

@estusの回答に基づいて、これが私が最終的に思いついたものです:

@memoize(['this.eyes.left.clickCount'])
get leftEyeSrc() {
  return this.eyes.left.clickCount < this.maxEyeClickCount ? this.botEyesDir + '/bot-eye-tiny.png' : this.botEyesDir + '/bot-eye-black-tiny.png'
}

そしてメモ化デコレーターは次の通りです:

// decorated method must be pure when not applied to a getter

import { get } from 'lodash'
import * as memoizee from 'memoizee'

// noinspection JSUnusedGlobalSymbols
const options = {
  normalizer(args) {
    return args[0]
  }
}

const memoizedFuncs = {}

export const memoize = (props: string[] = []): MethodDecorator => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    props = props.map(prop => prop.replace(/^this\./, ''))
    if ('value' in descriptor) {
      const valueFunc = descriptor.value
      descriptor.value = memoizee(valueFunc)
    } else if ('get' in descriptor) {
      const getFunc = descriptor.get
      // args is used here solely for determining the memoize cache - see the options object
      memoizedFuncs[propertyKey] = memoizee((args: string[], that) => {
        const func = getFunc.bind(that)
        return func()
      }, options)
      descriptor.get = function() {
        const args: string[] = props.map(prop => get(this, prop))
        return memoizedFuncs[propertyKey](args, this)
      }
    }
    return descriptor
  }
}

これにより、どのプロパティがメモ化キャッシュに使用されるかを決定する文字列の配列を渡すことができます(この場合、可変なのは1つのプロパティのみ - clickCount - で、他の2つは固定です)。

memoizee のオプションは、memoizee((args: string[], that) => {...}) の最初の配列引数のみがメモ化の目的で使用されることを示しています。

このコードがどれほど美しいか、まだ理解しようとしています!

関連記事