Lit 组件接收输入并将其状态存储为 JavaScript 类字段或属性。响应式属性是可以在更改时触发响应式更新周期、重新渲染组件以及可选地读取或写入属性的属性。
Lit 管理您的反应属性及其相应的属性。尤其是:
- 反应性更新。Lit 为每个反应属性生成一个 getter/setter 对。当反应性属性发生变化时,组件会安排更新。
- 属性处理。默认情况下,Lit 会设置属性对应的被观察属性,并在属性发生变化时更新属性。属性值也可以选择性地反映回属性。
- 超类属性。Lit 自动应用超类声明的属性选项。除非您想更改选项,否则您不需要重新声明属性。
- 元素升级。如果在元素已经在 DOM 中之后定义了 Lit 组件,则 Lit 会处理升级逻辑,确保在升级之前设置在元素上的任何属性在元素升级时触发正确的反应性副作用。
属性选项
attribute
:
属性是否与属性关联,或者关联属性的自定义名称。默认值:真。如果attribute为 false,converter则忽略reflect和type选项。
converter
:
用于在属性和属性之间转换的自定义转换器。如果未指定,请使用默认属性转换器。
hasChanged
:
每当设置属性时调用的函数以确定属性是否已更改,并应触发更新。如果未指定,LitElement 将使用严格的不等式检查 ( newValue !== oldValue) 来确定属性值是否已更改。
noAccessor
:
设置为 true 以避免生成默认属性访问器。这个选项很少需要。默认值:false。
reflect
:
属性值是否反映回关联的属性。默认值:false。
state
:
设置为 true 以将属性声明为内部反应状态。内部反应状态触发更新,如公共反应属性,但 Lit 不会为其生成属性,用户不应从组件外部访问它。相当于使用@state装饰器。默认值:false。
type
:
在将字符串值的属性转换为属性时,Lit 的默认属性转换器会将字符串解析为给定的类型,反之亦然,将属性反映为属性时。如果converter设置,此字段将传递给转换器。如果type未指定,则默认转换器将其视为type: String.
公共属性 @property
class MyElement extends LitElement {
@property()
name: string;
}
组件不应更改其自己的公共属性,除非响应用户输入
内部状态 @state()
@state()
private _counter = 0;
内部反应状态是指不属于组件公共 API 的反应属性。这些状态属性没有相应的属性,并且不打算从组件外部使用。内部反应状态应该由组件本身设置。
不应从组件外部引用内部反应状态。在 TypeScript 中,这些属性应标记为私有或受保护。我们还建议使用前导下划线 ( _) 之类的约定来标识 JavaScript 用户的私有或受保护属性。
您可以为内部反应状态指定的唯一选项是hasChanged函数。
声明属性
properties使用装饰器或静态字段声明元素的公共反应属性。在任何一种情况下,您都可以传递一个选项对象来配置属性的功能。
使用@property带有类字段声明的装饰器来声明反应性属性。
class MyElement extends LitElement {
@property({type: String})
mode: string;
@property({attribute: false})
data = {};
}
@property
装饰器的参数是一个选项对象。省略参数等效于为所有选项指定默认值。
在静态properties类字段中声明属性
class MyElement extends LitElement {
static properties = {
mode: {type: String},
data: {attribute: false},
};
constructor() {
super();
this.data = {};
}
}
声明属性时避免类字段的问题
类字段与反应属性的交互存在问题。类字段在元素实例上定义。反应性属性被定义为元素原型上的访问器。根据 JavaScript 的规则,实例属性优先于并有效地隐藏了原型属性。这意味着当使用类字段时,反应式属性访问器不起作用。设置属性后,元素不会更新。
在JavaScript 中,声明反应属性时不能使用类字段。相反,必须在元素构造函数中初始化属性:
constructor() {
super();
this.data = {};
}
可以理解为类字段在构造器上定义后是类上的一个属性;而声明属性,这个属性是响应式的,是组件上的一个属性。
属性更新
更新流程:
- 调用属性的设置器。
- setter 调用组件的requestUpdate方法。
- 比较属性的旧值和新值。
- 默认情况下,Lit 使用严格的不等式测试来确定值是否已更改(即newValue !== oldValue)。
- 如果属性具有hasChanged函数,则使用属性的旧值和新值调用它。
- 如果检测到属性更改,则会异步安排更新。如果已安排更新,则仅执行一次更新。
- 调用组件的update方法,将更改的属性反映到属性并重新呈现组件的模板。
调用组件的update方法,将更改的属性反映到属性并重新呈现组件的模板。
这个应该相当于vue里的监听
注: 请注意,如果您更改对象或数组属性,它不会触发更新,因为对象本身没有更改
更改对象和数组属性
改变对象或数组不会更改对象引用,因此不会触发更新。解决方式有两种:
- 不可变的数据模式。将对象和数组视为不可变的。例如,要从 中删除一个项目myArray,请构造一个新数组:
this.myArray = this.myArray.filter((_, i) => i !== indexToRemove);
- 手动触发更新。改变数据并requestUpdate()直接触发更新。例如:
this.myArray.splice(indexToRemove, 1);
this.requestUpdate();
属性
默认情况下,Lit 会为每个公共响应属性设置一个观察到的属性,并在属性更改时更新该属性。属性值也可以选择性地被反映(写回属性)。
虽然元素属性可以是任何类型,但属性始终是字符串。这会影响非字符串属性的观察属性和反射属性:
- 要观察属性(从属性设置属性),必须将属性值从字符串转换为与属性类型匹配。
- 要反映属性(从属性设置属性),必须将属性值转换为字符串。
设置属性名称
默认情况下,Lit 为所有公共响应属性创建相应的观察属性。观察到的属性的名称是属性名称,小写:
// 在组件上,属性就是 "myvalue"
@property({ type: Number })
myValue = 0;
要创建具有不同名称的观察属性,请设置attribute为字符串:
// 属性名将是 my-name,而不是myname
@property({ attribute: 'my-name' })
myName = 'Ogden';
要防止为属性创建观察到的属性,请设置attribute为false。该属性不会从标记中的属性初始化,并且属性更改不会影响它。
@property({ attribute: false })
myData = {};
大体意思就是这个属性将不再是相应式的,属性改变,模板不会更新
@customElement('base-app')
export class BaseApp extends LitElement {
@property({ type: String }) aName = 'Lit'
// 渲染组件
render() {
return html`
<button @click="${this._changeName}">改变</button>
<simple-greeting name="${this.aName}"></simple-greeting>
`;
}
private _changeName() {
this.aName = 'World!';
}
}
当将组件simple-greeting
的 name
属性设置为 @property({ type: String, attribute: false })
再次点击按钮,内容将不会被改变
注: 内部反应状态永远不会有关联的属性。内部反应状态即@state()
定义的属性
使用默认转换器
Lit 具有处理String、Number、Boolean、Array和Object属性类型的默认转换器。
要使用默认转换器,type请在属性声明中指定选项
@property({ type: Number })
count = 0;
使用自定义转换器
可以使用以下converter选项在属性声明中指定自定义属性转换器,converter可以是对象或函数。
额,这个没太理解,以后清楚了再补上。
启用属性反射
您可以配置一个属性,以便每当它发生变化时,它的值都会反映到其对应的属性中。这样保证当属性的值发生变化后,模板能够实时更新。默认值是false,该属性可以结合css和js
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
@property({type: Boolean, reflect: true})
active: boolean = false;
static styles = css`
:host {
display: inline-block;
}
:host([active]) {
border: 1px solid red;
}`;
render() {
return html`
<span>Active: ${this.active}</span>
<button @click="${() => this.active = !this.active}">Toggle active</button>
`;
}
}
注:要注意与attribute
属性的区别。attribute
是父组件与子组件之间的响应,reflect
是组件内部的响应
属性通常应被视为来自其所有者的元素的输入,而不是受元素本身的控制,因此应谨慎地将属性反映到属性。
就像vue中的props
属性,一般都是通过emit
来进行更新,而不是组件内部之间修改。