LitElement(四)属性

1、概述

LitElement管理您声明的属性及其对应的属性。默认情况下,LitElement将:

  • 确保在任何声明的属性更改时进行元素更新。
  • 捕获已声明属性的实例值。在浏览器注册自定义元素定义之前设置的所有属性值。
  • 使用每个属性的小写名称设置观察到的(未引用的)属性。
  • 处理声明为字符串,数字,布尔值,数组和对象类型的属性的属性转换。
  • 使用直接比较(oldValue !== newValue)测试属性更改。
  • 应用超类声明的所有属性选项和访问器。

属性选项

属性声明是具有以下格式的对象:

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

也可以是以下选项:

  • converter: Convert between properties and attributes.
  • type: Use LitElement’s default attribute converter.
  • attribute: Configure observed attributes.
  • reflect: Configure reflected attributes.
  • noAccessor: Whether to set up a default property accessor.
  • hasChanged: Specify what constitutes a property change.

可以在静态属性getter中或使用TypeScript装饰器指定所有属性声明选项。

2、声明属性

通过实现静态 properties 或使用装饰器(注解)来声明元素的属性,就像C的声明与定义分开的写法:

// properties getter
static get properties() {
  return { 
    prop1: { type: String }
  };
}
// Decorators (requires TypeScript or Babel)
export class MyElement extends LitElement {
  @property( { type : String }  ) prop1 = '';

2.1 在静态properties getter中声明属性

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

注意:如果实现静态属性获取器,请在元素构造函数中初始化属性值。

constructor() {
  // Always call super() first
  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 = 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]: ${this.prop4[0]}</p>
      <p>prop5.subprop1: ${this.prop5.subprop1}</p>
    `;
  }
}

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

 2.2 用注解声明属性

翻译为装饰器,但我更喜欢称之为注解(Java框架),一般用在TS文件中

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

装饰器是JavaScript的一项建议功能,因此您需要使用BabelTranslate编译器(例如TypeScript编译器)来使用装饰器。

如果您使用的是Babel,则需要使用@babel/plugin-proposal-decorators插件

如果你使用TypeScript,你需要激活 experimentalDecorators 编译选项(例如:在tsconfig.json配置文件中设置"experimentalDecorators": true),不推荐也不必要激活emitDecoratorMetadata选项

用注解声明属性的例子:

my-element.ts

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]: ${this.prop4[0]}</p>
      <p>prop5.subprop1: ${this.prop5.subprop1}</p>
    `;
  }
}

index.ts

import './my-element.ts';

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
  <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <title>lit-element code sample</title>
  
</head>

  <body>
    <my-element></my-element>
  </body>

</html>

3、初始化属性值

3.1 在构造方法中初始化属性值

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

constructor() {
  // Always call super() first
  super();

  // Initialize properties 
  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);

3.2 使用TypeScript 注解初始化属性值

在TypeScript中使用@property注解来初始化属性值

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

即可以直接在声明的同时初始化,将 static properties getter()方法和 constructor()方法所作的事情合而为一

例子:

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>
    `;
  }
}

3.3 在标签中初始化属性值

ndex.html

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

就像普通的html写法一样

4、配置属性

 4.1 properties 和 attributes的转换

虽然元素 properties 可以是任何类型,但是 attributes 始终是字符串。这会影响非字符串属性的  observed attributes 和 reflected attributes

要 observe 一个属性(set a property from an attribute),属性值必须由string类型转换为匹配的类型

要 reflect 一个属性(set an attribute from a property),属性值必须被转化为string

4.1.1 使用默认转换

在处理 StringNumber, BooleanArray, and Object  属性类型时,LitElement有一个默认的转换规则

要使用默认的转换规则,需要在你的属性声明中指明 type 选项

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

以下信息显示默认转换器如何处理每种类型的转换。

attribute 转换到 property :

  • 对于Strings类型, 直接使用,无需转换
  • 对于Numbers类型,调用 Number(attributeValue) 构造函数将
  • 对于Boolean 类型,非null转化为true,null转化为 false
  • 对于Arrays 和 Objects,调用 JSON.parse(attributeValue)

 从 property 转换到 attribute  :

  •  对于Strings,如果 property 是
    • null,删除该 attribute
    • undefined,不做改变
    • 否则直接 attribute 作为 property 值
  • 对于Numbers,同上
  • 对于 Booleans,property 值
    • 为真则创建属性
    • 为假则删除属性
  • 对于 Objects and Arrays  ,
    • 如果为 null 或 undefined 则删除属性
    • 否则 JSON.stringify(propertyValue) 转换

 使用默认转换的例子:

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([...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 = [...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);

4.1.2 配置自定义转换

您可以使用converter选项在属性声明中指定自定义属性转换器:

myProp: { 
  converter: // Custom property converter
} 

converter可以是一个对象或函数,如果是一个对象,它有2个关键字 fromAttribute 和 toAttribute

prop1: { 
  converter: { 
    fromAttribute: (value, type) => { 
      // `value` is a string
      // Convert it to a value of type `type` and return it
    },
    toAttribute: (value, type) => { 
      // `value` is of type `type` 
      // Convert it to a string and return it
    }
  }
}

如果是一个函数,它被用来替代 fromAttribute

myProp: { 
  converter: (value, type) => { 
    // `value` is a string
    // Convert it to a value of type `type` and return it
  }
} 

如果没有为反射的属性提供toAttribute函数,则该属性将设置为property 值,而无需进行转换。

在更新期间:

  • If toAttribute returns null, the attribute is removed.

  • If toAttribute returns undefined, the attribute is not changed.

自定义转换的例子:

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);

4.1.3 配置观察属性

只要 observed attribute 发生更改,就会触发自定义元素API回调 attributeChangedCallback。默认情况下,每当某个属性触发此回调时,LitElement就会使用属性的 fromAttribute函数从该attribute 设置property值。

默认情况下,LitElement为所有声明的属性创建一个相应的观察属性。被观察属性的名称是属性名称,小写:

// observed attribute name is "myprop"
myProp: { type: Number }

要使用其他名称创建观察到的属性,请将attribute设置为字符串:

// Observed attribute will be called my-prop
myProp: { attribute: 'my-prop' }

为了防止为从property创建observed attribute,请将attribute设置为false。该property 不会从标记中的attributes 初始化,并且attribute 更改不会对其产生影响。

// No observed attribute for this property
myProp: { attribute: false }

配置observed attributes的例子

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);
设置了 theProp: { attribute: false }, 所以当 attribute更改时,其值不会发生改变

4.1.4 配置反射属性

您可以配置property ,以便每当property 更改时,其值都会反射到其 observed attribute 中。例如:

// Value of property "myProp" will reflect to attribute "myprop"
myProp: { reflect: true }

property更改后,LitElement使用属性转换器中的toAttribute函数从新property值中设置attribute值

  • If toAttribute returns null, the attribute is removed.

  • If toAttribute returns undefined, the attribute is not changed.

  • If toAttribute itself is undefined, the property value is set to the attribute value without conversion.

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);

4.2 配置属性访问器

默认情况下,LitElement为所有声明的属性生成一个属性访问器。每当您设置属性时,都会调用访问器:

// Declare a property
static get properties() { return { myProp: { type: String } }; }
...
// Later, set the property
this.myProp = 'hi'; // invokes myProp's generated property accessor

生成的访问器会自动调用requestUpdate,如果尚未开始,则启动更新。

4.2.1 创建自己的属性访问器

也就是Java类常用的自定义属性的get set方法

要指定属性的获取和设置的工作方式,可以定义自己的属性访问器。例如:

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

set myProp(value) {
  const oldValue = this.myProp;
  // Implement setter logic here... 
  this.requestUpdate('myProp', oldValue);
} 
get myProp() { ... }

...

// Later, set the property
this.myProp = 'hi'; // Invokes your accessor

如果您的类为属性定义了自己的访问器,则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);

如果要将自己的属性访问器与@property装饰器一起使用,则可以通过将装饰器放在getter上来实现此目的:

_myProp: string = '';

  @property({ type: String })
  public get myProp(): string {
    return this._myProp;
  }
  public set myProp(value: string) {
    const oldValue = this.myProp;
    this._myProp = value;
    this.requestUpdate('myProp', oldValue);
  }

4.2.2 防止LitElement生成属性访问器

在极少数情况下,子类可能需要为其父类上存在的属性更改或添加属性选项

为了防止LitElement生成覆盖父类的已定义访问器的属性访问器(防止覆盖父类属性get set 方法),请在属性声明中将noAccessor设置为true

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

定义自己的访问器时,无需设置Accessor。

例如,

父类

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

export class SuperElement 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('super-element', SuperElement);

子类

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

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

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

4.3 配置属性更改

所有声明的属性都有一个函数hasChanged,只要设置了属性,就会调用该函数。

hasChanged比较属性的旧值和新值,并评估属性是否已更改。如果hasChanged返回true,则如果尚未安排元素更新,则LitElement将开始元素更新。有关更新如何工作的更多信息,请参见Element更新生命周期文档。

  • hasChanged returns true if newVal !== oldVal.
  • hasChanged returns false if both the new and old values are NaN.

要为属性定制hasChanged,请将其指定为属性选项,也就是自定义属性更改的比较规则,相当于Java重写equals方法

myProp: { hasChanged(newVal, oldVal) {
  // compare newVal and oldVal
  // return `true` if an update should proceed
}}

例子:

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

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

      /**
       * Compare myProp's new value with its old value.
       *
       * Only consider myProp to have changed if newVal is larger than
       * oldVal.
       */
      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}">get new value</button>
    `;
  }

  updated(){
    console.log('updated');
  }

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

}
customElements.define('my-element', MyElement);
posted @ 2019-12-05 23:25  姬无华  阅读(825)  评论(1编辑  收藏  举报