响应式属性

Lit 组件接收输入并将它们的状态存储为 JavaScript 类字段或属性。响应式属性是指当更改时可以触发响应式更新周期的属性,从而重新渲染组件,并且可以选择读写属性。

Lit 管理您的响应式属性及其对应的属性。特别是

  • 响应式更新。Lit 为每个响应式属性生成一对 getter/setter。当响应式属性发生更改时,组件会安排更新。
  • 属性处理。默认情况下,Lit 会设置一个与属性相对应的观察属性,并在属性发生更改时更新属性。属性值也可以选择反射回属性。
  • 超类属性。Lit 会自动应用超类声明的属性选项。您无需重新声明属性,除非您想更改选项。
  • 元素升级。如果在元素已存在于 DOM 中后定义了 Lit 组件,Lit 会处理升级逻辑,确保在元素升级之前设置在元素上的任何属性在元素升级时触发正确的响应式副作用。

公共属性是组件公共 API 的一部分。一般来说,公共属性,尤其是公共响应式属性,应该被视为输入

组件不应该更改自己的公共属性,除非响应用户输入。例如,菜单组件可能具有一个公共的 selected 属性,该属性可以由元素的所有者初始化为给定值,但在用户选择项目时由组件本身更新。在这些情况下,组件应该分派一个事件来指示组件的所有者 selected 属性已更改。有关更多详细信息,请参见 分派事件

Lit 还支持内部响应式状态。内部响应式状态是指不是组件 API 一部分的响应式属性。这些属性没有对应的属性,并且通常在 TypeScript 中标记为 protected 或 private。

组件操作自己的内部响应式状态。在某些情况下,内部响应式状态可能从公共属性初始化,例如,如果用户可见属性和内部状态之间存在昂贵的转换。

与公共响应式属性一样,更新内部响应式状态会触发更新周期。有关更多信息,请参见 内部响应式状态

使用装饰器或静态 properties 字段来声明元素的公共响应式属性。

在任何一种情况下,您都可以传递一个选项对象来配置属性的功能。

@property 装饰器与类字段声明一起使用以声明响应式属性。

@property 装饰器的参数是 选项对象。省略参数等同于为所有选项指定默认值。

使用装饰器。装饰器是 JavaScript 的一个提议功能,因此您需要使用像 Babel 或 TypeScript 编译器这样的编译器来使用装饰器。有关详细信息,请参见 启用装饰器

要在静态 properties 类字段中声明属性

一个空的选项对象等同于为所有选项指定默认值。

类字段 与响应式属性存在问题交互。类字段在元素实例上定义,而响应式属性在元素原型上定义为访问器。根据 JavaScript 的规则,实例属性优先于原型属性并有效地隐藏了原型属性。这意味着当使用类字段时,响应式属性访问器不起作用,因此设置属性不会触发元素更新。

JavaScript 中,在声明响应式属性时不能使用类字段。相反,必须在元素构造函数中初始化属性

或者,您可以使用 带有 Babel 的标准装饰器 来声明响应式属性。

对于 TypeScript,您可以使用类字段来声明响应式属性,只要您使用以下模式之一

  • 在字段上添加 declare 关键字,并将字段的初始化程序放在构造函数中。

选项对象可以具有以下属性

attribute

属性是否与属性相关联,或者关联属性的自定义名称。默认值:true。如果 attribute 为 false,则 converterreflecttype 选项将被忽略。有关更多信息,请参见 设置属性名称

converter

用于在属性和属性之间进行转换的 自定义转换器。如果未指定,请使用 默认属性转换器

hasChanged

每次设置属性时都会调用一个函数,以确定属性是否已更改,以及是否应该触发更新。如果未指定,LitElement 使用严格不等式检查 (newValue !== oldValue) 来确定属性值是否已更改。有关更多信息,请参见 自定义变更检测

noAccessor

设置为 true 以避免生成默认属性访问器。此选项很少需要。默认值:false。有关更多信息,请参见 阻止 Lit 生成属性访问器

reflect

属性值是否反射回关联属性。默认值:false。有关更多信息,请参见 启用属性反射

state

设置为 true 以将属性声明为内部响应式状态。内部响应式状态会像公共响应式属性一样触发更新,但 Lit 不会为其生成属性,用户也不应该从组件外部访问它。等同于使用 @state 装饰器。默认值:false。有关更多信息,请参见 内部响应式状态

type

当将字符串值属性转换为属性时,Lit 的默认属性转换器会将字符串解析为给定的类型,反之亦然,当将属性反射回属性时。如果设置了 converter,则此字段将传递给转换器。如果 type 未指定,则默认转换器将其视为 type: String。参见 使用默认转换器

当使用 TypeScript 时,此字段通常应该与为字段声明的 TypeScript 类型匹配。但是,type 选项由 Lit 的运行时用于字符串序列化/反序列化,不应与类型检查机制混淆。

省略选项对象或指定一个空的选项对象等同于为所有选项指定默认值。

内部响应式状态是指不是组件公共 API 一部分的响应式属性。这些状态属性没有对应的属性,也不打算从组件外部使用。内部响应式状态应该由组件本身设置。

使用 @state 装饰器声明内部响应式状态

使用静态 properties 类字段,您可以通过使用 state: true 选项声明内部响应式状态。

不应从组件外部引用内部响应式状态。在 TypeScript 中,这些属性应该标记为 private 或 protected。我们还建议使用像前导下划线 (_) 这样的约定来标识 JavaScript 用户的私有或受保护属性。

内部响应式状态的工作原理与公共响应式属性相同,区别在于没有与该属性关联的属性。您可以为内部响应式状态指定的唯一选项是hasChanged函数。

@state装饰器还可以作为代码压缩器的提示,表明该属性名称可以在压缩过程中更改。

属性更改可以触发一个响应式更新周期,这会导致组件重新渲染其模板。

当属性更改时,将执行以下顺序

  1. 调用属性的设置器。
  2. 设置器调用组件的requestUpdate方法。
  3. 比较属性的旧值和新值。
    • 默认情况下,Lit 使用严格不等测试来确定值是否已更改(即newValue !== oldValue)。
    • 如果属性具有hasChanged函数,则会使用属性的旧值和新值来调用它。
  4. 如果检测到属性更改,则异步安排更新。如果已经安排了更新,则只会执行一次更新。
  5. 调用组件的update方法,将更改的属性反映到属性并重新渲染组件的模板。

请注意,如果您修改对象或数组属性,它不会触发更新,因为对象本身没有更改。有关更多信息,请参见修改对象和数组属性

有多种方法可以挂钩并修改响应式更新周期。有关更多信息,请参见响应式更新周期

有关属性更改检测的更多信息,请参见自定义更改检测

修改对象或数组不会更改对象引用,因此不会触发更新。您可以通过以下两种方式之一处理对象和数组属性

  • 不可变数据模式。将对象和数组视为不可变的。例如,要从myArray中删除一个项,请构造一个新数组

    虽然此示例很简单,但使用像Immer这样的库来管理不可变数据通常很有帮助。这可以帮助避免在设置深层嵌套对象时出现棘手的样板代码。

  • 手动触发更新。修改数据并调用requestUpdate()以直接触发更新。例如

    当没有参数调用时,requestUpdate()会安排更新,而不会调用hasChanged()函数。但是请注意,requestUpdate()只会导致当前组件更新。也就是说,如果一个组件使用上面显示的代码,并且该组件将this.myArray传递给子组件,那么子组件将检测到数组引用没有改变,因此不会更新。

通常,对大多数应用程序而言,使用自上而下的数据流和不可变对象是最好的。它确保每个需要渲染新值的组件都会渲染(并且尽可能高效地渲染,因为数据树中没有更改的部分不会导致依赖它们的组件更新)。

直接修改数据并调用requestUpdate()应该被认为是一种高级用例。在这种情况下,您(或其他一些系统)需要识别使用修改数据的全部组件,并在每个组件上调用requestUpdate()。当这些组件分布在整个应用程序中时,这将难以管理。如果不健壮地执行此操作,则意味着您可能会修改在应用程序的两个部分中渲染的对象,但只有一部分更新。

在简单情况下,如果您知道特定数据仅在单个组件中使用,那么如果您愿意,直接修改数据并调用requestUpdate()应该是安全的。

虽然属性非常适合接收 JavaScript 数据作为输入,但属性是 HTML 允许从标记配置元素的标准方式,无需使用 JavaScript 来设置属性。为其响应式属性提供属性属性接口是 Lit 组件在各种环境中都非常有用的关键方式,包括那些在没有客户端模板引擎的情况下渲染的环境,例如从 CMS 提供的静态 HTML 页面。

默认情况下,Lit 为每个公共响应式属性设置一个观察到的属性,并在属性更改时更新属性。属性值也可以选择反映(写入回属性)。

虽然元素属性可以是任何类型,但属性始终是字符串。这会影响非字符串属性的观察到的属性反映的属性

  • 观察属性(从属性设置属性),必须将属性值从字符串转换为匹配属性类型。

  • 反映属性(从属性设置属性),必须将属性值转换为字符串。

暴露属性的布尔属性应默认为 false。有关更多信息,请参见布尔属性

默认情况下,Lit 为所有公共响应式属性创建一个相应的观察到的属性。观察到的属性的名称是属性名称,小写

要创建具有不同名称的观察到的属性,请将attribute设置为字符串

要防止为属性创建观察到的属性,请将attribute设置为false。属性不会从标记中的属性初始化,并且属性更改不会影响它。

内部响应式状态永远不会有关联的属性。

可以将观察到的属性用于从标记中为属性提供初始值。例如

Lit 具有一个默认转换器,它处理StringNumberBooleanArrayObject属性类型。

要使用默认转换器,请在属性声明中指定type选项

如果您没有为属性指定类型自定义转换器,它会像您指定了type: String一样运行。

下面的表格显示了默认转换器如何处理每种类型的转换。

从属性到属性

类型转换
字符串如果元素具有相应的属性,则将属性设置为属性值。
数字如果元素具有相应的属性,则将属性设置为Number(attributeValue)
布尔值如果元素具有相应的属性,则将属性设置为 true。
如果没有,则将属性设置为 false。
ObjectArray如果元素具有相应的属性,则将属性值设置为JSON.parse(attributeValue)

对于除Boolean以外的任何情况,如果元素没有相应的属性,则属性保留其默认值,或者如果未设置默认值,则保留undefined

从属性到属性

类型转换
StringNumber如果属性已定义且不为空,则将属性设置为属性值。
如果属性为空或未定义,则删除属性。
布尔值如果属性为真值,则创建属性并将其值设置为一个空字符串。
如果属性为假值,则删除属性
ObjectArray如果属性已定义且不为空,则将属性设置为JSON.stringify(propertyValue)
如果属性为空或未定义,则删除属性。

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

converter可以是对象或函数。如果是对象,它可以具有fromAttributetoAttribute的键

如果converter是函数,它将代替fromAttribute使用

如果未为反映的属性提供toAttribute函数,则使用默认转换器将属性设置为属性值。

如果toAttribute返回nullundefined,则删除属性。

要使布尔属性可从属性配置,它必须默认为 false。如果它默认为 true,则无法从标记中将其设置为 false,因为属性的存在,无论是否有值,都等同于 true。这是 web 平台中属性的标准行为。

如果此行为不适合您的用例,则有以下两种选择

  • 更改属性名称,使其默认为 false。例如,web 平台使用disabled属性(默认为 false),而不是enabled

  • 使用字符串值或数字值属性代替。

您可以配置属性,以便只要它发生更改,其值就会反映到其相应的属性。反映的属性非常有用,因为属性对 CSS 可见,并且对 DOM API(如querySelector)可见。

例如

当属性更改时,Lit 会按照使用默认转换器提供自定义转换器中的描述设置相应的属性值。

属性通常应该被视为从其所有者到元素的输入,而不是由元素本身控制,因此应谨慎地将属性反映到属性。如今,它对于样式和可访问性等情况是必要的,但随着平台添加诸如:state伪选择器可访问性对象模型之类的功能,这种情况可能会发生变化,这些功能填补了这些空白。

不建议反映类型为对象或数组的属性。这会导致大型对象序列化到 DOM,从而导致性能下降。

Lit 在更新期间跟踪反射状态。您可能已经意识到,如果属性更改反映到属性,并且属性更改更新属性,则它有可能创建一个无限循环。但是,Lit 会跟踪属性和属性何时被专门设置,以防止这种情况发生

默认情况下,LitElement 为所有响应式属性生成一对 getter/setter。每当您设置属性时,都会调用 setter

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

要指定获取和设置属性的工作方式,您可以定义自己的 getter/setter 对。例如

要将自定义属性访问器与@property@state装饰器一起使用,请将装饰器放在 setter 上,如上所示。@property@state修饰的 setter 会调用requestUpdate()

在大多数情况下,您无需创建自定义属性访问器。 要从现有属性计算值,我们建议使用 willUpdate 回调,它允许您在更新周期内设置值,而不会触发额外的更新。要执行元素更新后的自定义操作,我们建议使用 updated 回调。自定义设置器可在罕见情况下使用,此时同步验证用户设置的任何值非常重要。

如果您的类为属性定义了自己的访问器,Lit 不会用生成的访问器覆盖它们。如果您的类未为属性定义访问器,即使超类已定义属性或访问器,Lit 也会生成它们。

在罕见情况下,子类可能需要更改或添加其超类上存在的属性的属性选项。

要防止 Lit 生成覆盖超类定义的访问器的属性访问器,请在属性声明中将 noAccessor 设置为 true

定义自己的访问器时,您无需设置 noAccessor

所有反应式属性都具有一个函数 hasChanged(),该函数在设置属性时被调用。

hasChanged 比较属性的旧值和新值,并评估属性是否已更改。如果 hasChanged() 返回 true,则 Lit 会启动元素更新(如果尚未计划)。有关更新的更多信息,请参阅 反应式更新周期

hasChanged() 的默认实现使用严格的不等式比较:如果 newVal !== oldVal,则 hasChanged() 返回 true

要自定义属性的 hasChanged(),请将其指定为属性选项

在以下示例中,hasChanged() 仅对奇数值返回 true。