第三板斧:合理管理状态变量
应该合理地使用状态变量,精准控制组件的更新范围(详细介绍可参考文章: 精准控制组件的更新范围),控制状态变量关联组件数量上限,控制对象级状态变量的成员变量关联组件数,减少系统的组件渲染负载,提升应用流畅度。
精准控制组件的更新范围
在复杂页面开发的场景下,精准控制组件更新的范围对提高应用运行性能尤为重要。我们应该避免状态变量的滥用引起的容器组件的刷新,进而影响帧率。
使用Stack包裹条件渲染组件,减小更新范围
用条件渲染的方式控制组件的显示,在条件发生变化时会引起父组件的重新渲染。如果在同个父组件下存在复杂组件,在高频操作场景会产生性能问题。因此,对于该场景,我们应该用父容器组件包裹可能会频繁条件变化的组件
反例代码如下:
@State isVisible: boolean;
@State realData: Data[] = [];
aboutToAppear() {
// 更新状态变量
this.realData = [...]
}
build() {
Column() {
if(this.isVisible) {
Text()
}
ForEach(this.realData,(item: Data) => {
Text(`${item.label}`)
})
}
}
正例代码如下:
@State isVisible: boolean;
@State realData: Data[] = [];
aboutToAppear() {
// 更新状态变量
this.realData = [...]
}
build() {
Column() {
Stack() {
if(this.isVisible) {
Text()
}
}
ForEach(this.realData,(item: Data) => {
Text(`${item.label}`)
})
}
}
减少不必要的参数层次传递
@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink三种方案的实现方式是逐级向下传递状态,当共享状态的组件间层级相差较大时,会出现状态层层传递的现象。对于没有使用该状态的中间组件而言,这是“额外的消耗”。因此,对于跨越多层的状态变量传递,使用@Provide+@Consume方案更为合理。
反例代码如下:
// 父组件
@Component
struct componentParent{
@State data: Data = {}
aboutToAppear() {
// 获取子组件数据
this.data = getData()
}
build() {
Column() {
componentSon({data: this.data})
}
}
}
// 子组件
@Component
struct componentSon{
// 获取传递参数
@Prop data: Data;
build() {
Column() {
Text(data.text)
componentGrandSon({data: this.data})
}
}
}
@Component
struct componentGrandSon{
// 获取传递参数
@Prop data: Data;
build() {
Column() {
Text(data.text)
}
}
}
正例代码如下:
// 父组件
@Component
struct componentParent{
@Provide('data') data: Data = {};
aboutToAppear() {
// 获取子组件数据
this.data = getData()
}
build() {
Column() {
componentSon({data: this.data})
}
}
}
// 子组件
@Component
struct componentSon{
// 获取传递参数
@Consume("data") data: Data;
build() {
Column() {
Text(data.text)
componentGrandSon({data: this.data})
}
}
}
@Component
struct componentGrandSon{
// 获取传递参数
@Consume("data") data: Data;
build() {
Column() {
Text(data.text)
}
}
}
避免滥用@Provide+@Consume
在父子组件关联的场景下,@Provide+@Consume开销要大于@State+@Prop/@Link,因此在该场景下推荐使用@State+@Prop/@Link的组合。
反例代码如下:
// 父组件
@Component
struct componentParent{
@Provide("data") data: Data = {};
aboutToAppear() {
// 获取子组件数据
this.data = getData();
}
build() {
Column() {
componentSon()
}
}
}
// 子组件
@Component
struct componentSon{
// 获取传递参数
@Consume("data") data: Data;
build() {
Column() {
Text(data.text)
}
}
}
正例代码如下:
// 父组件
@Component
struct componentParent{
@State data:Data = {};
aboutToAppear() {
// 获取子组件数据
this.data = getData();
}
build() {
Column() {
componentSon({data: this.data})
}
}
}
// 子组件
@Component
struct componentSon{
// 获取传递参数
@Prop data:Data;
build() {
Column() {
Text(data.text)
}
}
}
精准控制状态变量关联组件数量
应该控制状态变量关联的组件数量,如果一个状态关联过多的组件,当这个变量更新时会引起过多的组件重新绘制渲染,建议关联数量限制在20个以内(详细介绍可参考文章: 精准控制组件的更新范围)。
控制状态变量关联组件数量
反例代码如下:
@Observed
class Translate {
translateX: number = 20;
}
@Component
struct Title {
@ObjectLink translateObj: Translate;
build() {
Row() {
Image($r('app.media.icon'))
.translate({
x:this.translateObj.translateX // this.translateObj.translateX used in two component both in Row
})
Text("Title")
.translate({
x: this.translateObj.translateX
})
}
}
}
@Entry
@Component
struct Page {
@State translateObj: Translate = new Translate();
build() {
Column() {
Title({
translateObj: this.translateObj
})
Stack() {
}
.translate({
x:this.translateObj.translateX //this.translateObj.translateX used in two components both in Column
})
Button("move")
.translate({
x:this.translateObj.translateX
})
.onClick(() => {
animateTo({
duration: 50
},()=>{
this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
})
})
}
}
}
正例代码如下:
@Observed
class Translate {
translateX: number = 20;
}
@Component
struct Title {
build() {
Row() {
Image($r('app.media.icon'))
Text("Title")
}
}
}
@Entry
@Component
struct Page1 {
@State translateObj: Translate = new Translate();
build() {
Column() {
Title()
Stack() {
}
Button("move")
.onClick(() => {
animateTo({
duration: 50
},()=>{
this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
})
})
}
.translate({ // the component in Column shares the same property translate
x: this.translateObj.translateX
})
}
}
控制对象级状态变量成员数量
应该控制对象级状态变量的成员变量关联的组件数量。开发者封装一个数据结构类用于进行状态变量关联时,应该避免过多的成员变量关联大量ArkUI组件,这种情况下,当这个大对象的一个成员变量更新时,会导致所有关联这个大对象的组件都同时进行刷新,造成不必要的性能损耗,从而影响帧率。
反例代码如下:
@Observed
class AnimationParams {
translateX: number = 0;
translateY: number = 0;
alpha: number = 1;
rotationX: number = 0;
rotationY: number = 0;
centerX: number = 0;
centerY: number = 0;
angle: number = 0;
scaleX: number = 1;
scaleY: number = 1;
}
@Entry
@Component
struct Page {
@State animationParam: AnimationParams = new AnimationParams();
build() {
Column() {
Row() {
Image($r('app.media.startIcon'))
.translate({
x: this.animationParam.translateX,
y: this.animationParam.translateY
})
.rotate({
x: this.animationParam.rotationX,
y: this.animationParam.translateY,
centerX: this.animationParam.centerX,
centerY: this.animationParam.centerY,
angle: this.animationParam.angle
})
.opacity(this.animationParam.alpha)
.scale({
x: this.animationParam.scaleX,
y: this.animationParam.scaleY,
centerX: this.animationParam.centerX,
centerY: this.animationParam.centerY
})
.animation({
duration: 3000
})
}
Button('点击播放动画')
.onClick(() => {
this.animationParam.translateX = 300;
this.animationParam.translateY = 200;
this.animationParam.rotationX = 90;
this.animationParam.rotationY = 90;
this.animationParam.centerX = 20;
this.animationParam.centerY = 20;
this.animationParam.angle = 270;
this.animationParam.alpha = 0.5;
this.animationParam.scaleX = 3;
this.animationParam.scaleY = 3;
})
}
}
}
正例代码如下:
@Observed
class RotationAnimationParams {
rotationX: number = 0;
rotationY: number = 0;
centerX: number = 0;
centerY: number = 0;
angle: number = 0;
}
@Observed
class TranslateAnimationParams {
translateX: number = 0;
translateY: number = 0;
}
@Observed
class AlphaAnimationParams {
alpha: number = 1;
}
@Observed
class ScaleAnimationParams {
scaleX: number = 1;
scaleY: number = 1;
centerX: number = 0;
centerY: number = 0;
}
@Entry
@Component
struct Page {
@State rotationAnimation: RotationAnimationParams = new RotationAnimationParams();
@State translateAnimation: TranslateAnimationParams = new TranslateAnimationParams();
@State alphaAnimation: AlphaAnimationParams = new AlphaAnimationParams();
@State scaleAnimation: ScaleAnimationParams = new ScaleAnimationParams();
build() {
Column() {
Row() {
Image($r('app.media.startIcon'))
.translate({
x: this.translateAnimation.translateX,
y: this.translateAnimation.translateY
})
.rotate({
x: this.rotationAnimation.rotationX,
y: this.rotationAnimation.rotationY,
centerX: this.rotationAnimation.centerX,
centerY: this.rotationAnimation.centerY,
angle: this.rotationAnimation.angle
})
.opacity(this.alphaAnimation.alpha)
.scale({
x: this.scaleAnimation.scaleX,
y: this.scaleAnimation.scaleY,
centerX: this.scaleAnimation.centerX,
centerY: this.scaleAnimation.centerY
})
.animation({
duration: 3000
})
}
Button('点击播放动画')
.onClick(() => {
this.rotationAnimation.rotationX = 90;
this.rotationAnimation.rotationY = 90;
this.rotationAnimation.centerX = 20;
this.rotationAnimation.centerY = 20;
this.rotationAnimation.angle = 270;
this.translateAnimation.translateX = 300;
this.translateAnimation.translateY = 200;
this.alphaAnimation.alpha = 0.5;
this.scaleAnimation.scaleX = 3;
this.scaleAnimation.scaleY = 3;
this.scaleAnimation.centerX = 20;
this.scaleAnimation.centerY = 20;
})
}
}
}
避免不必要的创建和读取状态变量
避免不必要的创建和读取状态变量,减少性能损耗。
删除冗余的状态变量标记
状态变量的管理有一定的开销,应在合理场景使用,普通的变量用状态变量标记可能会导致性能劣化
反例代码如下:
@Component
struct component {
@State bgcolor: string | Color = '#ffffff';
@State selectColor: string | Color = '#007DFF';
build() {
}
}
正例代码如下:
@Component
struct component {
bgcolor: string | Color = '#ffffff';
selectColor: string | Color = '#007DFF';
build() {
}
}
避免在For/while等循环函数中重复读取状态变量
状态变量的读取耗时远大于普通变量的读取耗时,因此要避免重复读取状态变量,而是应该放在循环外面读取,例如在打印For/while循环中打印状态变量的日志信息
反例代码如下:
@Component
struct Page {
@State message: string = '';
build() {
Column() {
Button('点击打印日志')
.onClick(() => {
for (let i = 0; i < 10; i++) {
console.debug(this.message);
}
})
}
}
}
正例代码如下:
@Component
struct Page {
@State message: string = '';
build() {
Column() {
Button('点击打印日志')
.onClick(() => {
let logMessage: string = this.message;
for (let i = 0; i < 10; i++) {
console.debug(logMessage);
}
})
}
}
}