浅谈 LWC 中的数据渲染

Posted by Peter Dong on July 21, 2022

在 LWC 中,如果一个属性的值发生了变化,并且该属性在 tempalte 中有使用,或者声明在一个属性的 getter 方法中,该组件会重新显示新的值。如果一个属性被分配给一个对象或一个数组,框架会观察该对象或数组内部的一些变化,比如当你分配了一个新值。

当一个组件重新渲染时,模板中使用的表达式被重新评估,renderedCallback() 生命周期钩子会被执行。

我们来看接下来的一个例子:

当你在 FirstName/LastName 输入一个值时,该组件将其转换为大写字母并渲染出来。

gif

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

Buy Me a Coffee