Open36

decorator

kajirikajirikajirikajiri
kajirikajirikajirikajiri

Decorators are a proposal for extending JavaScript classes which is widely adopted among developers in transpiler environments, with broad interest in standardization. TC39 has been iterating on decorators proposals for over five years. This document describes a new proposal for decorators based on elements from all past proposals.

デコレーターはJSのクラスを拡張するための提案
トランスパイラ環境の開発者の間で広く採用されており、標準化への関心も高まっている。
TC39では5年以上にわたって提案されてきた

kajirikajirikajirikajiri

Decorators are functions called on classes, class elements, or other JavaScript syntax forms during definition.

クラスやクラス要素など、JSの文法形式の定義時に呼び出される関数。

定義時に呼び出される。
関数である。

kajirikajirikajirikajiri
@defineElement("my-class")
class C extends HTMLElement {
  @reactive accessor clicked = false;
}

Decorators have three primary capabilities:
They can replace the value that is being decorated with a matching value that has the same semantics. (e.g. a decorator can replace a method with another method, a field with another field, a class with another class, and so on).

デコレーションされる値を同じセマンティクスを持つ値に置き換えることができる?。
semantics→Diodictによると言葉や文章の意味を扱う言語学の1分野。
semantic→Diodictによると言葉や文章の意味を扱うものを指します。
同じ意味を扱うものって感じかな。

例によると、メソッドを別のメソッドに、フィールドを別のフィールドに、クラスを別のクラスに置き換えることができる。ふーん?

kajirikajirikajirikajiri

They can associate metadata with the value that is being decorated. This metadata can then be read externally and used for metaprogramming and introspection.

メタデータを関連づけることができる。
メタデータを外部から読み取ることができる。
メタデータを外部から読み取ることができ、メタプログラミングやイントロスペクションに使用することができる。
introspection→Diodictによると、自分自身の考えや思想、感情を調べること。
関数が何ができるかをデコレータで調べるって感じかな。

kajirikajirikajirikajiri

They can provide access to the value that is being decorated, via metadata. For public values, they can do this via the name of the value. For private values, they receive accessor functions which they can then choose to share.

メタデータを介して値へのアクセスを提供
パブリックな値なら値の名前を介して。
プライベートな値ならアクセサ関数を共有して。

kajirikajirikajirikajiri

Essentially, decorators can be used to metaprogram and add functionality to a value, without fundamentally changing its external behavior.

値の外部の動作を根本的に変更することなく、その値にメタプログラムを施し、機能を追加するために使用することができる

kajirikajirikajirikajiri

This proposal differs from previous iterations where decorators could replace the decorated value with a completely different type of value. The requirement for decorators to only replace a value with one that has the same semantics as the original value fulfills two major design goals:

これまでの提案では、デコレーターがデコレートされた値を全く別のタイプの値に置き換えることができたのか。
今回は、元の値と同じセマンティクスを持つ値にしか置き換えられない。

kajirikajirikajirikajiri

It should be easy both to use decorators and to write your own decorators. Previous iterations such as the static decorators proposal were complicated for authors and implementers in particular. In this proposal, decorators are plain functions, and are accessible and easy to write.

使うのも、書くのも簡単でなければなりません?ありがとう。
単純な関数であり、アクセス可能で簡単に書くことができる。確かにそう見える。

kajirikajirikajirikajiri

Decorators should affect the thing they're decorating, and avoid confusing/non-local effects. Previously, decorators could change the decorated value in unpredictable ways, and also add completely new values which were unrelated. This was problematic both for runtimes, since it meant decorated values could not be analyzed statically, and for developers, since decorated values could turn into completely different types of values without any indicator to the user.

これまでのデコレータはデコレーションされた値を予想外の方法で変更したり、関連性のない新しい値を追加したりできた?マジかよ怖い。
ランタイムにとっては値を静的に分析できないという問題があるのか。なるほど。開発者にとっては気付かれることなく全く別の種類の値に変化してしまう問題。なるほど。

kajirikajirikajirikajiri

In this proposal, decorators can be applied to the following existing types of values:
Classes
Class fields (public, private, and static)
Class methods (public, private, and static)
Class accessors (public, private, and static)

これらが対象なのか

kajirikajirikajirikajiri

In addition, this proposal introduces a new type of class element that can be decorated:
Class auto accessors, defined by applying the accessor keyword to a class field. These have a getter and setter, unlike fields, which default to getting and setting the value on a private storage slot (equivalent to a private class field):

class Example {
  @reactive accessor myBool = false;
}

クラスにauto accessorsを導入。
accessorによって定義される。
こいつは、どっち?クラス変数?インスタンス変数?まあいいや。

kajirikajirikajirikajiri

This new element type can be used independently, and has its own semantics separate from usage with decorators. The reason it is included in this proposal is primarily because there are a number of use cases for decorators which require its semantics, since decorators can only replace an element with a corresponding element that has the same semantics. These use cases are common in the existing decorators ecosystem, demonstrating a need for the capabilities they provide.

関係ないよな?と思ったけどなるほど、必要なんだ。

kajirikajirikajirikajiri

Finally, there is an additional syntax which can be used when decorating a value that allows the decorator to run additional initialization code for that value:

@init:customElement('my-element')
class Example {
  @init:eventHandler('click')
  onClick() {
    // ...
  }
}

This syntax can be used with any decorator type, and is used in cases where additional setup steps are necessary.

最後に特殊そうなのがきたな。

kajirikajirikajirikajiri

The three steps of decorator evaluation:
Decorator expressions (the thing after the @) are evaluated interspersed with computed property names.
Decorators are called (as functions) during class definition, after the methods have been evaluated but before the constructor and prototype have been put together.
Decorators are applied (mutating the constructor and prototype) all at once, after all of them have been called.

The semantics here generally follow the consensus at the May 2016 TC39 meeting in Munich.

挟まれて評価されますってなんだ。
クラス定義中に関数として呼び出される。
メソッドが評価された後、コンストラクタとプロトタイプがまとめられる前に呼び出される??
コンストラクタとプロトタイプってまとめられるの?クラスをそこまで理解してない。マージみたいなことしてるのか?
すべてのデコレータが呼び出された後に、一度に適用されるのね。ok

kajirikajirikajirikajiri

Decorators are evaluated as expressions, being ordered along with computed property names. This goes left to right, top to bottom. The result of decorators is stored in the equivalent of local variables to be later called after the class definition initially finishes executing.

よかった左から右、上から下だった。普通だった。
クラス定義の初期実行終了後?うーん?クラスの評価順がわからない

kajirikajirikajirikajiri

When decorators are called, they receive two parameters:
The value being decorated, or undefined in the case of class fields which are a special case.
A context object containing metadata about the value being decorated
Using TypeScript interfaces for brevity and clarity, this is the general shape of the API:

type Decorator = (value: Input, context: {
  kind: string;
  name: string | symbol;
  access?: {
    get?(): unknown;
    set?(value: unknown): void;
  };
  isPrivate?: boolean;
  isStatic?: boolean;
  addInitializer?(initializer: () => void): void;
  getMetadata(key: symbol);
  setMetadata(key: symbol, value: unknown);
}) => Output | void;
kajirikajirikajirikajiri

Input and Output here represent the values passed to and returned from a given decorator. Each type of decorator has a different input and output, and these are covered below in more detail. All decorators can choose to return nothing, which defaults to using the original, undecorated value.

kajirikajirikajirikajiri

kind: The kind of decorated value. This can be used to assert that the decorator is used correctly, or to have different behavior for different types of values. It is one of the following values.
"class"
"method"
"getter"
"setter"
"field"
"auto-accessor"
name: The name of the value, or in the case of private elements the description of it (e.g. the readable name).
access: An object containing methods to access the value. This is only available for private class elements, since public class elements can be accessed externally by knowing the name of the element. These methods also get the final value of the private element on the instance, not the current value passed to the decorator. This is important for most use cases involving access, such as type validators or serializers. See the section on Access below for more details.
isStatic: Whether or not the value is a static class element. Only applies to class elements.
isPrivate: Whether or not the value is a private class element. Only applies to class elements.
addInitializer: This is available if the decorator was called as an @init: decorator, which is discussed in more detail below.
setMetadata: Allows the user to define some metadata to be associated with this property. This metadata can then be accessed on the class via Symbol.metadata. See the section on Metadata below for more details.

大体さっきまでに出てきた。

kajirikajirikajirikajiri

Decorators are applied after all decorators have been called. The intermediate steps of the decorator application algorithm are not observable--the newly constructed class is not made available until after all method and non-static field decorators have been applied.
The class decorator is called only after all method and field decorators are called and applied.
Finally, static fields are executed and applied.

うーんやっぱりクラスの評価順がわかったほうがいいね。
メソッド、フィールドデコレータ→クラスデコレータ→スタティックフィールドの順らしい。

kajirikajirikajirikajiri

This decorators proposal uses the syntax of the previous Stage 2 decorators proposal. This means that:
Decorator expressions are restricted to a chain of variables, property access with . but not [], and calls (). To use an arbitrary expression as a decorator, @(expression) is an escape hatch.
Class expressions may be decorated, not just class declarations.
Class decorators come after export and default.
There is no special syntax for defining decorators; any function can be applied as a decorator.

@(expression)できるの?それ大丈夫?

なにclass expressionってclass declarationsならわかるけど。

exportとdefaultの後に?そこに書くの?

kajirikajirikajirikajiri
type ClassMethodDecorator = (value: Function, context: {
  kind: "method";
  name: string | symbol;
  access?: { get(): unknown };
  isStatic: boolean;
  isPrivate: boolean;
  addInitializer?(initializer: () => void): void;
  getMetadata(key: symbol);
  setMetadata(key: symbol, value: unknown);
}) => Function | void;

Class method decorators receive the method that is being decorated as the first value, and can optionally return a new method to replace it. If a new method is returned, it will replace the original on the prototype (or on the class itself in the case of static methods). If any other type of value is returned, an error will be thrown.

okok

kajirikajirikajirikajiri

By default, method decorators do not receive access to the instances of the class, and cannot be used to add functionality that requires it. An example of such a decorator is the @bound decorator, which would bind the method to the instance of the class. In order to add instance initialization logic, users must use the @init: modifier (see below for more details).

なんとなくok

kajirikajirikajirikajiri

An example of a method decorator is the @logged decorator. This decorator receives the original function, and returns a new function that wraps the original and logs before and after it is called.

function logged(value, { kind, name }) {
  if (kind === "method") {
    return function (...args) {
      console.log(`starting ${name} with arguments ${args.join(", ")}`);
      const ret = value.call(this, ...args);
      console.log(`ending ${name}`);
      return ret;
    };
  }
}

class C {
  @logged
  m(arg) {}
}

new C().m(1);
// starting m with arguments 1
// ending m

え、あーそういうことなのね。なんか色々繋がったぞ。TypeORMのアレとか、関数をimportして使ってただけか。

kajirikajirikajirikajiri

This example roughly "desugars" to the following (i.e., could be transpiled as such):

class C {
  m(arg) {
    this.x = arg;
  }
}

C.prototype.m = logged(C.prototype.m, {
  kind: "method",
  name: "m",
  isStatic: false,
  isPrivate: false,
  setMetadata() { /**/ }
}) ?? C.prototype.m;

これはさっきまでに出てきた

kajirikajirikajirikajiri
type ClassGetterDecorator = (value: Function, context: {
  kind: "getter";
  name: string | symbol;
  access?: { get(): unknown };
  isStatic: boolean;
  isPrivate: boolean;
  addInitializer?(initializer: () => void): void;
  setMetadata(key: symbol, value: unknown);
}) => Function | void;

type ClassSetterDecorator = (value: Function, context: {
  kind: "setter";
  name: string | symbol;
  access?: { set(value: unknown): void };
  isStatic: boolean;
  isPrivate: boolean;
  addInitializer?(initializer: () => void): void;
  getMetadata(key: symbol);
  setMetadata(key: symbol, value: unknown);
}) => Function | void;

こうなってるのか

kajirikajirikajirikajiri

Accessor decorators receive the original underlying getter/setter function as the first value, and can optionally return a new getter/setter function to replace it. Like method decorators, this new function is placed on the prototype in place of the original (or on the class for static accessors), and if any other type of value is returned, an error will be thrown.
Accessor decorators are applied separately to getters and setters. In the following example, @foo is applied only to get x() - set x() is undecorated:

sette,getterに両方適用されないのか。

class C {
  @foo
  get x() {
    // ...
  }

  set x(val) {
    // ...
  }
}
kajirikajirikajirikajiri

We can extend the @logged decorator we defined previously for methods to also handle accessors. The code is essentially the same, we just need to handle additional kinds.

function logged(value, { kind, name }) {
  if (kind === "method" || kind === "getter" || kind === "setter") {
    return function (...args) {
      console.log(`starting ${name} with arguments ${args.join(", ")}`);
      const ret = value.call(this, ...args);
      console.log(`ending ${name}`);
      return ret;
    };
  }
}

class C {
  @logged
  set x(arg) {}
}

new C().x = 1
// starting x with arguments 1
// ending x

ふむ

kajirikajirikajirikajiri

This example roughly "desugars" to the following (i.e., could be transpiled as such):

class C {
  set x(arg) {}
}

let { set } = Object.getOwnPropertyDescriptor(C.prototype, "x");
set = logged(set, {
  kind: "setter",
  name: "x",
  isStatic: false,
  isPrivate: false,
  getMetadata() { /**/ }
  setMetadata() { /**/ }
}) ?? set;

Object.defineProperty(C.prototype, "x", { set });

kajirikajirikajirikajiri

Unlike methods and accessors, class fields do not have a direct input value when being decorated. Instead, users can optionally return an initializer function which runs when the field is assigned, receiving the initial value of the field and returning a new initial value. If any other type of value besides a function is returned, an error will be thrown.
We can expand our @logged decorator to be able to handle class fields as well, logging when the field is assigned and what the value is.

デコレータ種類多いなー

kajirikajirikajirikajiri

The initializer function is called with the instance of the class as this, so field decorators can also be used to bootstrap registration relationships. For instance, you could register children on a parent class:

const CHILDREN = new WeakMap();

function registerChild(parent, child) {
  let children = CHILDREN.get(parent);

  if (children === undefined) {
    children = [];
    CHILDREN.set(parent, children);
  }

  children.push(child);
}

function getChildren(parent) {
  return CHILDREN.get(parent);
}

function register() {
  return function(value) {
    registerChild(this, value);

    return value;
  }
}

class Child {}
class OtherChild {}

class Parent {
  @register child1 = new Child();
  @register child2 = new OtherChild();
}

let parent = new Parent();
getChildren(parent); // [Child, OtherChild]

weekmapはあんまり使ってないんでわからないが、こういう使い方もできるのか。

kajirikajirikajirikajiri

Class auto-accessors are a new construct, defined by adding the accessor keyword in front of a class field:

class C {
  accessor x = 1;
}

Auto-accessors, unlike regular fields, define a getter and setter on the class prototype. This getter and setter default to getting and setting a value on a private slot. The above roughly desugars to:

class C {
  #x = 1;

  get x() {
    return this.#x;
  }

  set x(val) {
    this.#x = val;
  }
}

すごい簡潔

kajirikajirikajirikajiri

desugars, desugarsってなんだこれって思った。上のaccessorみたいなのをdesugarsっていうのか。syntax sugarっぽいことなんだろう。こういうふうにも書けますよ。みたいな

kajirikajirikajirikajiri

メタデータは便利そうだけど、頭がこんがらがる。使ってみてだな。