在 LWC 中, 如果一个属性的值发生了变化, 并且该属性在 tempalte 中有使用, 或者声明在一个属性的 getter 方法中, 该组件会重新显示新的值. 如果一个属性被分配给一个对象或一个数组, 框架会观察该对象或数组内部的一些变化, 比如当你分配了一个新值.
当一个组件重新渲染时, 模板中使用的表达式被重新评估, renderedCallback()
生命周期钩子会被执行.
我们来看接下来的一个例子:
当你在 FirstName/LastName 输入一个值时, 该组件将其转换为大写字母并渲染出来.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<lightning-card title="HelloExpressions" icon-name="custom:custom14">
<div class="slds-m-around_medium">
<lightning-input
name="firstName"
label="First Name"
onchange={handleChange}
></lightning-input>
<lightning-input
name="lastName"
label="Last Name"
onchange={handleChange}
></lightning-input>
<p class="slds-m-top_medium">
Uppercased Full Name: {uppercasedFullName}
</p>
</div>
</lightning-card>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { LightningElement } from 'lwc';
export default class TrackExample extends LightningElement {
firstName = '';
lastName = '';
handleChange(event) {
const field = event.target.name;
if (field === 'firstName') {
this.firstName = event.target.value;
} else if (field === 'lastName') {
this.lastName = event.target.value;
}
}
get uppercasedFullName() {
return `${this.firstName} ${this.lastName}`.trim().toUpperCase();
}
}
在 Spring'20
之前,为了让组件在用户输入时重新渲染,你必须用 @track
来修饰这些属性.
1
2
@track firstName = '';
@track lastName = '';
在 lwc 中, Fields 是 reactivity. Expandos
是在运行时添加到对象的属性, 不是Reactivity.
Expandos 是什么意思? 在javascript中,任何对象都是一个 expando object
(可扩展对象).它的意思是, 只要你试图访问一个属性1, 它就会被自动创建.
1
2
var myObj = {}; // 空的对象
myObj.myProp = 'value';
当你给 myProp
赋值的时候, myProp
这个属性就被动态地创建了, 尽管它之前并不存在的. 所以 expando
的能力是写, 而不是访问. Javascript对象允许你向一个对象写入新的属性, 而不需要像其他一些语言那样预先定义该属性.
Reactivity 的一些考量
尽管 fields 是反应式的, 但LWC引擎以一种比较浅层的方式跟踪属性值的变化. 当一个新的值被分配给属性时, 通过使用 ===
来比较值的身份来检测变化. 这对于像数字或布尔这样的原始类型很有效.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { LightningElement } from 'lwc';
export default class ReactivityExample extends LightningElement {
bool = true;
number = 42;
obj = { name: 'John' };
checkMutation() {
this.bool = false; // 检测到变化
this.number = 42; // 没有检测到变化: 之前的值等于新分配的值
this.number = 43; // 检测到变化
this.obj.name = 'Bob'; // 检测到变化
this.obj = { name: 'John' }; // 检测到变化 - 用相同的值重新定义对象,创建一个新的对象.
this.obj = { ...this.obj, title: 'CEO' } // 检测到变化
}
}
当操作复杂的类型如对象和数组时, 你必须创建一个新的对象, 并将其分配给property, 以便检测到值的变化.
为了避免在处理复杂对象时出现这样的问题,可以使用 @track
来深入追踪对属性值的监测.
追踪对象和数组内部的变化
为了观察一个对象的属性或一个数组的元素的变化,用 @track
来修饰这参数.
当一个 property 用 @track 修饰时,Lightning Web Components 会跟踪其内部值的变化.
LWC 引擎以递归方式观察对普通对象和数组的检测,包括嵌套对象,嵌套数组以及对象和数组的混合.循环引用也会被处理.
然而,LWC 引擎并不监测对复杂对象变化,例如 继承自 Object 的对象, 类实例, Date, Set 或 Map.
观察一个对象的属性
为了告诉框架观察一个对象的属性变化, 用 @track 来修饰这个属性. 如前面所讲, 在不使用 @track
的情况下, 框架会观察为字段分配新值的更改. 如果新值不是 === 之前的值, 组件将重新渲染.
例如, 让我们稍微改变一下代码, 声明fullName字段, 它包含一个有两个属性的对象, firstName和lastName. 框架观察到的变化是给fullName分配一个新的值.
1
fullName = { firstName : '', lastName : ''};
这段代码为 fullName 属性分配了一个新的值,因此该组件重新渲染.
1
2
// rerenders.
this.fullName = { firstName : 'Peter', lastName : 'Dong'};
然而,如果我们给对象的一个属性分配一个新的值,这个组件就不会重新渲染,因为这些属性没有被监测到.
1
2
// doesn't rerender.
this.fullName.firstName = 'Leo';
为了告诉框架监测对象属性的变化,用 @track
来修饰 fullName
字段.现在,如果我们改变任何一个属性,该组件就会重新渲染.
1
2
3
// rerenders.
@track fullName = { firstName : '', lastName : ''};
this.fullName.firstName = 'John';
监测一个数组的元素
@track的另一个用例是告诉框架监测一个数组元素的变化. 如果你不使用 @track
,框架会监测为字段分配新值的变化.
1
arr = ['a','b'];
当你给arr分配一个新的值时,该组件会重新渲染.
1
this.arr = ['x','y','z'];
然而,如果我们更新或添加数组中的一个元素,该组件就不会重新渲染.
1
2
this.arr[0] = 'x';
this.arr.push('c');
要告诉框架监测数组元素的变化,可以用 @track
来修饰 arr 字段.此外,框架不会为数组元素的更新自动转换为字符串.要返回更新的字符串,请使用getter将数组的元素用join()转换为字符串.
1
2
3
4
5
6
7
8
@track arr = ['a','b'];
get computedArray() { return this.arr.join(','); }
update() {
this.arr[0] = 'x';
this.arr.push('c');
}
监测复杂对象
让我们看看一个有日期类型属性x, template上有几个按钮可以改变x的内部状态. 这个例子中 new Date()创建了一个对象, 因为它不是一个普通的JavaScript对象, 所以内部状态的变化不会被LWC引擎监测到,即使代码使用了@track
.
1
2
3
4
5
6
7
8
9
10
11
12
import { LightningElement, track } from 'lwc';
export default class TrackDate extends LightningElement {
@track x = new Date();
initDate() {
this.x = new Date();
}
updateDate() {
this.x.setHours(7); // No mutation detected
}
}
与我们之前的例子类似,template有几个按钮可以改变 x 的内部状态。
1
2
3
4
5
<template>
<p>Date: {x}</p>
<button onclick={initDate}>Init</button>
<button onclick={updateDate}>Update</button>
</template>
当你点击 Init 按钮时,变化被监测到,模板被重新渲染。Lightning Web Components 监测到x正指向一个新的Date对象。然而,当你点击更新时,模板并没有重新渲染。Lightning Web Components没有监测到Date对象的值的变化。
为了确保模板在值发生变化时被重新渲染,克隆现有的日期并更新其值。
1
2
3
4
5
6
updateDate() {
const cloned = new Date(this.x.getTime());
cloned.setHours(7);
this.x = cloned;
}