プロパティ

概要

LitElementは、宣言されたプロパティとそれに対応する属性を管理します。 既定として:

LitElementで使うプロパティは全て宣言しておく必要があることを忘れないでください 上記のプロパティの機能を使うには、 プロパティを宣言する必要があります。

プロパティオプション

プロパティ宣言は、次の形式のオブジェクトです:

{ optionName1: optionValue1, optionName2: optionValue2, ... }

以下のオプションが利用可能です:

すべてのプロパティ宣言オプションは、静的プロパティゲッターまたはTypeScriptデコレータで指定できます。

プロパティの宣言

propertiesゲッターを実装するか、TypeScriptデコレータで要素のプロパティを宣言してください:

// プロパティゲッター
static get properties() {
  return { 
    prop1: { type: String }
  };
}
// TypeScriptデコレータ
export class MyElement extends LitElement {
  @property( { type : String }  ) prop1 = '';

静的ゲッターでプロパティを宣言

propertiesでプロパティを宣言するには:

static get properties() { 
  return { 
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean }
  };
}

プロパティを使う時には、コンストラクタでプロパティの値を初期化してください

constructor() {
  // 必ず最初にsuper()を呼びます
  super();
  this.prop1 = 'Hello World';
  ...
}

コンストラクタで super()を最初に呼び出すことを忘れないでください。さもないと要素はレンダリングされません。

例: propertiesでプロパティを宣言

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { return {
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean },
    prop4: { type: Array },
    prop5: { type: Object }
  };}

  constructor() {
    super();
    this.prop1 = 'Hello World';
    this.prop2 = 5;
    this.prop3 = false;
    this.prop4 = [1,2,3];
    this.prop5 = { subprop1: 'prop 5 subprop1 value' }
  }

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>
      <p>prop4[0]:</p>${this.prop4[0]}</p>
      <p>prop5.subprop1: ${this.prop5.subprop1}</p>
    `;
  }
}

customElements.define('my-element', MyElement);

TypeScriptデコレータを使う

TypeScriptデコレータでプロパティを宣言することもできます:

@property({type : String})  prop1 = 'Hello World';

例: TypeScriptデコレータによるプロパティ宣言

import { LitElement, html, customElement, property } from 'lit-element';

@customElement('my-element')
export class MyElement extends LitElement {
  @property({type : String})  prop1 = 'Hello World';
  @property({type : Number})  prop2 = 5;
  @property({type : Boolean}) prop3 = true;
  @property({type : Array})   prop4 = [1,2,3];
  @property({type : Object})  prop5 = { subprop1: 'prop 5 subprop1 value' };

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>
      <p>prop4[0]:</p>${this.prop4[0]}</p>
      <p>prop5.subprop1: ${this.prop5.subprop1}</p>
    `;
  }
}
customElements.define('my-element', MyElement);

プロパティの初期化

コンストラクタでプロパティを初期化

プロパティを使う時には、コンストラクタでプロパティの値を初期化します:

static get properties() { return { /* Property declarations */ }; } 

constructor() {
  // 必ず最初にsuper()を呼びます
  super();

  // プロパティの初期化
  this.prop1 = 'Hello World';
}

コンストラクタで super()を最初に呼び出すことを忘れないでください。さもないと要素はレンダリングされません。

例: コンストラクタでプロパティを初期化

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { return {
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean },
    prop4: { type: Array },
    prop5: { type: Object }
  };}

  constructor() {
    super();
    this.prop1 = 'Hello World';
    this.prop2 = 5;
    this.prop3 = true;
    this.prop4 = [1,2,3];
    this.prop5 = { stuff: 'hi', otherStuff: 'wow' };
  }

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>

      <p>prop4: ${this.prop4.map((item, index) =>
        html`<span>[${index}]:${item}&nbsp;</span>`)}
      </p>

      <p>prop5:
        ${Object.keys(this.prop5).map(item =>
          html`<span>${item}: ${this.prop5[item]}&nbsp;</span>`)}
      </p>
    `;
  }
}
customElements.define('my-element', MyElement);

TypeScriptデコレータでプロパティを初期化

TypeScriptでは @propertyデコレータでプロパティの値を初期化できます:

@property({ type : String }) prop1 = 'Hello World';

例: TypeScriptデコレータを使ってプロパティの値を初期化する

import { LitElement, html, customElement, property } from 'lit-element';

@customElement('my-element')
export class MyElement extends LitElement {
  // Declare and initialize properties
  @property({type : String})  prop1 = 'Hello World';
  @property({type : Number})  prop2 = 5;
  @property({type : Boolean}) prop3 = true;
  @property({type : Array})   prop4 = [1,2,3];
  @property({type : Object})  prop5 = { subprop1: 'hi', thing: 'fasdfsf' };

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>

      <p>prop4: ${this.prop4.map((item, index) =>
        html`<span>[${index}]:${item}&nbsp;</span>`)}
      </p>

      <p>prop5:
        ${Object.keys(this.prop5).map(item =>
          html`<span>${item}: ${this.prop5[item]}&nbsp;</span>`)}
      </p>
    `;
  }
}

HTMLの属性からプロパティを初期化

また、マークアップ内の属性からプロパティの値を初期化することもできます:

index.html

<my-element 
  mystring="hello world"
  mynumber="5"
  mybool
  myobj='{"stuff":"hi"}'
  myarray='[1,2,3,4]'></my-element>

属性から初期化する方法の詳細については、属性の監視およびプロパティと属性間の変換を参照してください。

属性をカスタマイズする

プロパティと属性間の変換

要素のプロパティは任意の型にできますが、属性は常に文字列です。これは、非文字列プロパティの属性の監視属性への反映に影響を与えます:

既定のコンバータを使う

LitElementには、 String NumberBoolean ArrayObjectプロパティの型を扱う既定のコンバータがあります。

既定のコンバータを使用するには、プロパティ宣言で typeオプションを指定してください:

// Use LitElement's default converter 
prop1: { type: String },
prop2: { type: Number },
prop3: { type: Boolean },
prop4: { type: Array },
prop5: { type: Object }

以下では、既定のコンバータが型の変換をどのように処理するかを示しています。

属性からプロパティに変換する

プロパティから属性への変換

例: 既定のコンバータを使用する

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { return {
    prop1: { type: String, reflect: true },
    prop2: { type: Number, reflect: true },
    prop3: { type: Boolean, reflect: true },
    prop4: { type: Array, reflect: true },
    prop5: { type: Object, reflect: true }
  };}

  constructor() {
    super();
    this.prop1 = '';
    this.prop2 = 0;
    this.prop3 = false;
    this.prop4 = [];
    this.prop5 = { };
  }

  attributeChangedCallback(name, oldval, newval) {
    console.log('attribute change: ', name, newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>prop1 ${this.prop1}</p>
      <p>prop2 ${this.prop2}</p>
      <p>prop3 ${this.prop3}</p>

      <p>prop4: ${this.prop4.map((item, index) =>
        html`<span>[${index}]:${item}&nbsp;</span>`)}
      </p>

      <p>prop5:
        ${Object.keys(this.prop5).map(item =>
          html`<span>${item}: ${this.prop5[item]}&nbsp;</span>`)}
      </p>

      <button @click="${this.changeProperties}">change properties</button>
      <button @click="${this.changeAttributes}">change attributes</button>
    `;
  }

  changeAttributes() {
    let randy = Math.floor(Math.random()*10);
    let myBool = this.getAttribute('prop3');

    this.setAttribute('prop1', randy.toString);
    this.setAttribute('prop2', randy.toString);
    this.setAttribute('prop3', myBool? '' : null);
    this.setAttribute('prop4',
      JSON.stringify(Object.assign([], [...this.prop4], randy)));
    this.setAttribute('prop5',
      JSON.stringify(Object.assign({}, this.prop5, {[randy]: randy})));
    this.requestUpdate();
  }

  changeProperties() {
    let randy = Math.floor(Math.random()*10);
    let myBool = this.prop3;

    this.prop1 = randy.toString();
    this.prop2 = randy;
    this.prop3 = !myBool;
    this.prop4 = Object.assign([], [...this.prop4], randy);
    this.prop5 = Object.assign({}, this.prop5, {[randy]: randy});
  }

  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
  }

}

customElements.define('my-element', MyElement);

カスタムコンバータを設定する

converterオプションを使ってプロパティ宣言でカスタムプロパティコンバーターを指定することができます:

myProp: { 
  converter: // カスタムコンバータ
} 

converterはオブジェクトまたは関数です。それがオブジェクトの場合、 fromAttribute toAttributeのためのキーを持つことができます:

prop1: { 
  converter: { 
    fromAttribute: (value, type) => { 
      // `value`は文字列として
      // `type`で定義された型として変換して返す
    },
    toAttribute: (value, type) => { 
      // `value`が`type`で定義された型として
      // 文字列として変換して返す
    }
  }
}

converterが関数の場合、 fromAttributeの代わりに使用されます。:

myProp: { 
  converter: (value, type) => { 
    // `value`は文字列として
    // `type`で定義された型として変換して返す
  }
} 

反映された属性に対して toAttribute関数が指定されていない場合、属性は変換されずにプロパティの値が設定されます。

更新時に動作としては:

例: カスタムコンバータを設定する

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { return {
    myProp: {
      reflect: true,
      converter: {
        toAttribute(value) {
          console.log('myProp\'s toAttribute.');
          console.log('Processing:', value, typeof(value));
          let retVal = String(value);
          console.log('Returning:', retVal, typeof(retVal));
          return retVal;
        },

        fromAttribute(value) {
          console.log('myProp\'s fromAttribute.');
          console.log('Processing:', value, typeof(value));
          let retVal = Number(value);
          console.log('Returning:', retVal, typeof(retVal));
          return retVal;
        }
      }
    },

    theProp: {
      reflect: true,
      converter(value) {
        console.log('theProp\'s converter.');
        console.log('Processing:', value, typeof(value));

        let retVal = Number(value);
        console.log('Returning:', retVal, typeof(retVal));
        return retVal;
      }},
  };}

  constructor() {
    super();
    this.myProp = 'myProp';
    this.theProp = 'theProp';
  }

  attributeChangedCallback(name, oldval, newval) {
    // console.log('attribute change: ', name, newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>myProp ${this.myProp}</p>
      <p>theProp ${this.theProp}</p>

      <button @click="${this.changeProperties}">change properties</button>
      <button @click="${this.changeAttributes}">change attributes</button>
    `;
  }

  changeAttributes() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.setAttribute('myprop', 'myprop ' + randomString);
    this.setAttribute('theprop', 'theprop ' + randomString);
    this.requestUpdate();
  }

  changeProperties() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.myProp='myProp ' + randomString;
    this.theProp='theProp ' + randomString;
  }
}
customElements.define('my-element', MyElement);

属性を監視する

監視された属性は、更新されるたびに、カスタム要素のAPIコールバックattributeChangedCallbackされます。 既定では、属性がこのコールバックを呼び出すたびに、LitElementはプロパティの fromAttribute関数を使用して属性からプロパティの値を設定します。 詳細については、プロパティと属性の変換を参照してください。

既定では、LitElementは、宣言されたすべてのプロパティに対して、対応する監視設定を作成します。監視している属性の名前はプロパティ名で、小文字となります:

// 監視している属性名は "myprop"
myProp: { type: Number }

監視している属性に別名をつけるには、 attributeを設定します:

// 監視している属性名が "my-prop" となる
myProp: { attribute: 'my-prop' }

プロパティに対して属性の監視がされないようにするには、 attributefalseに設定します。このプロパティはマークアップの属性から初期化されず、属性の変更は影響を受けません。

// このプロパティの属性と紐付きません
myProp: { attribute: false }

監視された属性を使用して、マークアップを介してプロパティの初期値を提供することができます。 HTMLの属性からプロパティを初期化を参照してください。

例: 属性の監視を設定する

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { return {
    myProp: { attribute: true },
    theProp: { attribute: false },
    otherProp: { attribute: 'other-prop' },
  };}

  constructor() {
    super();
    this.myProp = 'myProp';
    this.theProp = 'theProp';
    this.otherProp = 'otherProp';
  }

  attributeChangedCallback(name, oldval, newval) {
    console.log('attribute change: ', name, newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>myProp ${this.myProp}</p>
      <p>theProp ${this.theProp}</p>
      <p>otherProp ${this.otherProp}</p>

      <button @click="${this.changeAttributes}">change attributes</button>
    `;
  }

  changeAttributes() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.setAttribute('myprop', 'myprop ' + randomString);
    this.setAttribute('theprop', 'theprop ' + randomString);
    this.setAttribute('other-prop', 'other-prop ' + randomString);
    this.requestUpdate();
  }

  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
  }
}
customElements.define('my-element', MyElement);

属性への反映を設定する

変更するたびにその値が 監視している属性に反映されるようにプロパティを設定できます。 例えば:

// プロパティ"myProp"の値は属性"myprop"に反映されます。
myProp: { reflect: true }

プロパティが変更されると、LitElementはプロパティのコンバータ toAttribute関数を使用して、新しいプロパティの値から属性値を設定します。

LitElementは更新中に反映状態を追跡します LitElementはプロパティと監視している属性との間で引き起される無限ループを回避するために、状態を見ています。

例: 属性への反映を設定する

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { return {
    myProp: { reflect: true }
  };}

  constructor() {
    super();
    this.myProp='myProp';
  }

  attributeChangedCallback(name, oldval, newval) {
    console.log('attribute change: ', newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>${this.myProp}</p>

      <button @click="${this.changeProperty}">change property</button>
    `;
  }

  changeProperty() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.myProp='myProp ' + randomString;
  }

}
customElements.define('my-element', MyElement);

プロパティアクセサの設定

既定では、LitElementは宣言されたすべてのプロパティのプロパティアクセサを生成します。 アクセサは、プロパティを設定するたびに呼び出されます:

// プロパティの宣言
static get properties() { return { myProp: { type: String } }; }
...
// その後、値を設定する
this.myProp = 'hi'; // プロパティアクセサが呼び出される

生成されたアクセサは自動的に requestUpdateを呼び出し、まだ開始していなければ更新を開始します。

独自のプロパティアクセサを作成する

プロパティの取得と設定の動作を指定するには、独自のプロパティアクセサを作成します。例えば:

static get properties() { return { myProp: { type: String } }; }

set myProp(value) {
  const oldValue = this.myProp;
  // セッターのロジックをここで実装...
  this.requestUpdate('myProp', oldValue);
} 
get myProp() { ... }
...

// その後、プロパティを設定する
this.myProp = 'hi'; // アクセサを通して呼び出される

プロパティに独自のアクセサを作成する場合、LitElementはそれらのアクセサを上書きしません。 クラスがプロパティのアクセサを定義していない場合、たとえスーパークラスがそのプロパティまたはアクセサを定義していたとしても、LitElementはアクセサを生成します。

LitElementが生成するセッターは自動的に requestUpdateを呼び出します。あなた自身のセッターを書くのであれば、手動で requestUpdateを呼び出して、プロパティ名とその古い値を与えなければなりません。

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() { 
    return { prop: { type: Number } };
  }

  set prop(val) {
    let oldVal = this._prop;
    this._prop = Math.floor(val);
    this.requestUpdate('prop', oldVal);
  }

  get prop() { return this._prop; }

  constructor() {
    super();
    this._prop = 0;
  }

  render() {
    return html`
      <p>prop: ${this.prop}</p>
      <button @click="${() =>  { this.prop = Math.random()*10; }}">
        change prop
      </button>
    `;
  }
}
customElements.define('my-element', MyElement);

LitElementがプロパティアクセサを生成しないようにする

まれに、サブクラスがそのスーパークラスに存在するプロパティのプロパティオプションを変更または追加する必要がある場合があります。

LitElementがスーパークラスの定義済みアクセサを上書きするプロパティアクセサを生成しないようにするには、プロパティ宣言で noAccessorstrueに設定してください:

static get properties() { 
  return { myProp: { type: Number, noAccessor: true } }; 
}

あなた自身のアクセサを定義するときに noAccessorを設定する必要はありません。

サブクラス要素

import { SuperElement } from './super-element.js';

class SubElement extends SuperElement {  
  static get properties() { 
    return { prop: { reflectToAttribute: true, noAccessor: true } };
  }
}

customElements.define('sub-element', SubElement);

プロパティ変更をカスタマイズ

宣言されたすべてのプロパティには、プロパティが設定されるたびに呼び出される関数「hasChanged」があります。

hasChangedはプロパティの古い値と新しい値を比較し、プロパティが変更されたかどうかを評価します。 hasChangedがtrueを返した場合、LitElementは要素の更新を開始します。アップデートの仕組みの詳細については、Element update lifecycle documentationを参照してください。

既定では:

プロパティに対して hasChangedをカスタマイズするには、それをプロパティオプションとして指定します:

myProp: { hasChanged(newVal, oldVal) {
  // newValとoldValを比較する
  // 更新が進むべきであれば `true`を返します
}}

例: プロパティの変更を設定

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties(){ return {
    myProp: {
      type: Number,

      /**
       * myPropの新しい値と古い値を比較します。
       * 
       * newValがoldValより大きい場合にのみmyPropを変更したとみなしてください。
       */
      hasChanged(newVal, oldVal) {
        if (newVal > oldVal) {
          console.log(`${newVal} > ${oldVal}. hasChanged: true.`);
          return true;
        }
        else {
          console.log(`${newVal} <= ${oldVal}. hasChanged: false.`);
          return false;
        }
      }
    }};
  }

  constructor(){
    super();
    this.myProp = 1;
  }

  render(){
    return html`
      <p>${this.myProp}</p>
      <button @click="${this.getNewVal}">新しい値を取得</button>
    `;
  }

  updated(){
    console.log('更新されました');
  }

  getNewVal(){
    let newVal = Math.floor(Math.random()*10);
    this.myProp = newVal;
  }

}
customElements.define('my-element', MyElement);