当前位置: 首页 > article >正文

HarmonyOS:@Reusable装饰器:组件复用

@Reusable装饰器装饰任意自定义组件时,表示该自定义组件可以复用。

说明
从API version 10开始,对@Reusable进行支持,支持在ArkTS中使用。

一、概述

@Reusable适用自定义组件,与@Component结合使用,标记为@Reusable的自定义组件从组件树上被移除时,组件和其对应的JSView对象都会被放入复用缓存中,后续创建新自定义组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。

二、限制条件

@Reusable装饰器仅用于自定义组件。

// 编译报错,仅用于自定义组件
 @Reusable
 @Builder
 function buildCreativeLoadingDialog(closedClick: () => void) {
   Crash()
 }

ComponentContent不支持传入@Reusable装饰器装饰的自定义组件。

示例效果图

在这里插入图片描述

示例代码

import { ComponentContent } from "@kit.ArkUI"

// @Reusable装饰器仅用于自定义组件。
// 编译报错,仅用于自定义组件
// @Reusable // 注释掉编译不报错
@Builder
function buildCreativeLoadingDialog(closedClick: () => void) {
  Crash()
}

// 如果注释掉就可以正常弹出弹窗,如果加上@Reusable就直接crash
@Reusable
@Component
export struct Crash {
  build() {
    Column() {
      Text("Crash")
        .fontSize(12)
        .lineHeight(18)
        .fontColor(Color.Blue)
        .margin({
          left: 6
        })
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Builder
function buildCreativeLoadingDialog2(closedClick: () => void) {
  Crash2()
}

@Component
export struct Crash2 {
  build() {
    Column() {
      Text("Crash2 弹框提示, 没有使用@Reusable装饰器")
        .fontSize(20)
        .lineHeight(18)
        .fontColor(Color.Blue)
        .margin({
          left: 6,
          top: 100
        })
        .padding(26)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .width(300)
        .height(200)
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
  }
}


@Entry
@Component
struct TestReusable {
  @State message: string = '@Reusable装饰器:组件复用';
  private uicontext = this.getUIContext()

  build() {
    RelativeContainer() {
      Text(this.message)
        .id('Index')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onClick(() => {
          // ComponentContent底层是buildNode,buildNode不支持传入@Reusable注解的自定义组件
          let contentNode = new ComponentContent(this.uicontext, wrapBuilder(buildCreativeLoadingDialog), () => {

          });
          this.uicontext.getPromptAction().openCustomDialog(contentNode);
			
		  // 传入不使用@Reusable注解的自定义组件
          // let contentNode2 = new ComponentContent(this.uicontext, wrapBuilder(buildCreativeLoadingDialog2), () => {
          // });
          // this.uicontext.getPromptAction().openCustomDialog(contentNode2);
        })
    }
    .height('100%')
    .width('100%')
  }
}

传入不使用@Reusable注解的自定义组件效果图

在这里插入图片描述

@Reusable装饰器不支持嵌套使用。

效果图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

ReusableMessage.ets代码

export class ReusableMessage {
  value: string | undefined

  constructor(value: string) {
    this.value = value
  }
}

TestReusable2.ets代码

import { ReusableMessage } from "./ReusableMessage"

@Entry
@Component
struct TestReusable2 {
  @State switch: boolean = true;
  @State childswitch: boolean = false;

  build() {
    Column() {
      Button('click')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.switch = !this.switch;
        })

      if (this.switch) {
        // 父自定义组件已经添加@Reusable(出于描述方便,以下子组件代指自定义组件,父组件代指父自定义组件)
        Parent({ message: new ReusableMessage('Parent') })
          .reuseId('Parent')
      }
    }
    .height("100%")
    .width('100%')
  }
}

@Reusable
@Component
struct Parent {
  @State message: ReusableMessage = new ReusableMessage('AboutToReuse');
  @State switchchild: boolean = true;

  aboutToRecycle(): void {
    console.info("aboutToRecycle===Parent ====Child==");
  }

  aboutToReuse(params: Record<string, ESObject>) {
    this.message = params.message as ReusableMessage;
    console.info("aboutToReuse==Parent====Child==" + this.message.value);
  }

  build() {
    Column() {
      Button('click child')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.switchchild = !this.switchchild;
        })

      // 子自定义组件
      if (this.switchchild) {
        // 父自定义组件已经添加@Reusable
        HasReusableChild({ message: new ReusableMessage('From ChildReuse') });
      }
      Text(this.message.value)
        .fontSize(30)
    }
    .borderWidth(1)
    .height(100)
  }
}

// 可复用的自定义组件的子树中存在可复用的自定义组件,如果子组件标记@Reuable,会导致同一颗子树下复用率变低,因此不建议子组件加上@Reusable
@Reusable
@Component
export struct HasReusableChild {
  @State message: ReusableMessage = new ReusableMessage('AboutToReuse');

  // 子组件有@Reusable,单独刷新子组件,执行了子组件的aboutToReuse,父组件是不复用的
  aboutToAppear(): void {
    console.info("aboutToAppear=== HasReusableChild ====Child==");
  }

  aboutToDisappear(): void {
    console.info("aboutToDisappear=== HasReusableChild ====Child==");
  }

  aboutToRecycle(): void {
    console.info("aboutToRecycle=== HasReusableChild ====Child==");
  }

  // 正常复用,父组件刷新,引发子组件aboutToReuse方法的执行
  aboutToReuse(params: Record<string, ESObject>) {
    this.message = params.message as ReusableMessage;
    console.info("aboutToReuse== HasReusableChild ====Child==" + this.message.value);
  }

  build() {
    Column() {
      Text(this.message.value)
        .fontSize(12)
        .lineHeight(18)
        .fontColor(Color.Blue)
        .margin({
          left: 6
        })
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

三、使用场景

  • 列表滚动:当应用需要展示大量数据的列表,并且用户进行滚动操作时,频繁创建和销毁列表项的视图可能导致卡顿和性能问题。在这种情况下,使用列表组件的组件复用机制可以重用已经创建的列表项视图,提高滚动的流畅度。
  • 动态布局更新:如果应用中的界面需要频繁地进行布局更新,例如根据用户的操作或数据变化动态改变视图结构和样式,重复创建和销毁视图可能导致频繁的布局计算,影响帧率。在这种情况下,使用组件复用可以避免不必要的视图创建和布局计算,提高性能。
  • 频繁创建和销毁数据项的视图场景下。使用组件复用可以重用已创建的视图,只更新数据的内容,减少视图的创建和销毁,能有效提高性能。

四、使用场景举例

4.1 动态布局更新
  • 示例代码将Child自定义组件标记为复用组件,通过Button点击更新Child,触发Child复用;
  • @Reusable:自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力;
  • aboutToReuse:当一个可复用的自定义组件从复用缓存中重新加入到节点树时,触发aboutToReuse生命周期回调,并将组件的构造参数传递给aboutToReuse。

TestReusable3.ets代码

import { ReusableMessage } from "./ReusableMessage"

@Entry
@Component
struct TestReusable3 {
  @State switch: boolean = true;
  build() {
    Column() {
      Button('Hello')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.switch = !this.switch;
        })
      if (this.switch) {
        Child({ message: new ReusableMessage('Child') })
          // 如果只有一个复用的组件,可以不用设置reuseId
          .reuseId('Child')
      }
    }
    .height("100%")
    .width('100%')
  }
}

@Reusable
@Component
struct Child {
  @State message: ReusableMessage = new ReusableMessage('AboutToReuse');

  aboutToReuse(params: Record<string, ESObject>) {
    console.info("Recycle ====Child==");
    this.message = params.message as ReusableMessage;
  }

  build() {
    Column() {
      Text(this.message.value)
        .fontSize(30)
    }
    .borderWidth(1)
    .height(100)
  }
}

效果图

在这里插入图片描述

4.2 列表滚动配合LazyForEach使用
  • 示例代码将CardView自定义组件标记为复用组件,List上下滑动,触发CardView复用;
  • @Reusable:自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力;
  • 变量item的被@State修饰,才能更新,非@State修饰变量存在无法更新问题。

TestReusable4.ets代码

class MyDataSource implements IDataSource {
  private dataArray: string[] =
    ["示例代码将CardView自定义组件标记为复用组件,List上下滑动,触发CardView复用;",
      "自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力;",
      "变量item的被@State修饰,才能更新,非@State修饰变量存在无法更新问题。"];
  private listener: DataChangeListener | undefined;

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): string {
    return this.dataArray[index];
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
  }

  public reloadListener(): void {
    this.listener?.onDataReloaded();
  }

  public registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener;
  }

  public unregisterDataChangeListener(listener: DataChangeListener): void {
    this.listener = undefined;
  }
}

@Entry
@Component
struct TestReusable4 {
  private data: MyDataSource = new MyDataSource();

  build() {
    Column() {
      List() {
        LazyForEach(this.data, (item: string) => {
          ListItem() {
            CardView({ item: item })
          }
        }, (item: string) => item)
      }
    }
  }
}

// 复用组件
@Reusable
@Component
export struct CardView {
  @State item: string = '';

  aboutToReuse(params: Record<string, Object>): void {
    this.item = params.item as string;
  }

  build() {
    Column({ space: 10 }) {
      Text(this.item)
        .fontSize(20)
    }
    .borderWidth(1)
    .height(100)
  }
}

效果图

在这里插入图片描述

4.3 if使用场景
  • 示例代码将OneMoment自定义组件标记为复用组件,List上下滑动,触发OneMoment复用;
  • 可以使用reuseId为复用组件分配复用组,相同reuseId的组件会在同一个复用组中复用,如果只有一个复用的组件,可以不用设置reuseId;
  • 通过reuseId来标识需要复用的组件,省去重复执行if的删除重创逻辑,提高组件复用的效率和性能。
@Entry
@Component
struct withoutReuseId {
  aboutToAppear(): void {
    getFriendMomentFromRawfile();
  }
 
  build() {
    Column() {
      TopBar()
      List({ space: ListConstants.LIST_SPACE }) {
        LazyForEach(momentData, (moment: FriendMoment) => {
          ListItem() {
            OneMoment({moment: moment})
              // 使用reuseId进行组件复用的控制
              .reuseId((moment.image !== '') ? 'withImage' : 'noImage')
          }
        }, (moment: FriendMoment) => moment.id)
      }
      .cachedCount(Constants.CACHED_COUNT)
    }
  }
}
 
@Reusable
@Component
export struct OneMoment {
  @Prop moment: FriendMoment;
 
  build() {
    Column() {
      ...
      Text(this.moment.text)
 
      if (this.moment.image !== '') {
        Flex({ wrap: FlexWrap.Wrap }) {
          Image($r(this.moment.image))
          Image($r(this.moment.image))
          Image($r(this.moment.image))
          Image($r(this.moment.image))
        }
      }
      ...
    }
  }
}
4.4 Foreach使用场景
  • 示例点击update,数据刷新成功,但是滑动列表,组件复用无法使用,Foreach的折叠展开属性的原因;
  • 点击clear,再次update,复用成功;符合一帧内重复创建多个已被销毁的自定义组件;
// xxx.ets
class MyDataSource implements IDataSource {
  private dataArray: string[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): string {
    return this.dataArray[index];
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
  }

  public registerDataChangeListener(listener: DataChangeListener): void {
  }

  public unregisterDataChangeListener(listener: DataChangeListener): void {
  }
}

@Entry
@Component
struct Index {
  private data: MyDataSource = new MyDataSource();
  private data02: MyDataSource = new MyDataSource();
  @State isShow: boolean = true;
  @State dataSource: ListItemObject[] = [];

  aboutToAppear() {
    for (let i = 0; i < 100; i++) {
      this.data.pushData(i.toString())
    }

    for (let i = 30; i < 80; i++) {
      this.data02.pushData(i.toString())
    }
  }

  build() {
    Column() {
      Row() {
        Button('clear').onClick(() => {
          for (let i = 1; i < 50; i++) {
            let obj = new ListItemObject();
            obj.id = i;
            obj.uuid = Math.random().toString();
            obj.isExpand = false;
            this.dataSource.pop();
          }
        }).height(40)

        Button('update').onClick(() => {
          for (let i = 1; i < 50; i++) {
            let obj = new ListItemObject();
            obj.id = i;
            obj.uuid = Math.random().toString();
            obj.isExpand = false
            this.dataSource.push(obj);
          }
        }).height(40)
      }

      List({ space: 10 }) {
        ForEach(this.dataSource, (item: ListItemObject) => {
          ListItem() {
            ListItemView({
              obj: item
            })
          }
        }, (item: ListItemObject) => {
          return item.uuid.toString()
        })

      }.cachedCount(0)
      .width('100%')
      .height('100%')
    }
  }
}

@Reusable
@Component
struct ListItemView {
  @ObjectLink obj: ListItemObject;
  @State item: string = '';

  aboutToAppear(): void {
    // 点击 update,首次进入,上下滑动,由于Foreach折叠展开属性,无法复用
    console.log("=====abouTo===Appear=====ListItemView==创建了==" + this.item)
  }

  aboutToReuse(params: ESObject) {
    this.item = params.item;
    // 点击 clear,再次update,复用成功
    // 符合一帧内重复创建多个已被销毁的自定义组件
    console.log("=====aboutTo===Reuse====ListItemView==复用了==" + this.item)
  }

  build() {
    Column({ space: 10 }) {
      Text(`${this.obj.id}.标题`)
        .fontSize(16)
        .fontColor('#000000')
        .padding({
          top: 20,
          bottom: 20,
        })

      if (this.obj.isExpand) {
        Text('')
          .fontSize(14)
          .fontColor('#999999')
      }
    }
    .width('100%')
    .borderRadius(10)
    .backgroundColor(Color.White)
    .padding(15)
    .onClick(() => {
      this.obj.isExpand = !this.obj.isExpand;
    })
  }
}

@Observed
class ListItemObject {
  uuid: string = "";
  id: number = 0;
  isExpand: boolean = false;
}
4.5 Grid使用场景
  • 示例中使用@Reusable装饰器修饰GridItem中的自定义组件ReusableChildComponent,即表示其具备组件复用的能力;
  • 使用aboutToReuse是为了让Grid在滑动时从复用缓存中加入到组件树之前触发,用于更新组件的状态变量以展示正确的内容;
  • 需要注意的是无需在aboutToReuse中对@Link、@StorageLink、@ObjectLink、@Consume等自动更新值的状态变量进行更新,可能触发不必要的组件刷新。
// MyDataSource类实现IDataSource接口
class MyDataSource implements IDataSource {
  private dataArray: number[] = [];

  public pushData(data: number): void {
    this.dataArray.push(data);
  }

  // 数据源的数据总量
  public totalCount(): number {
    return this.dataArray.length;
  }

  // 返回指定索引位置的数据
  public getData(index: number): number {
    return this.dataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
  }
}

@Entry
@Component
struct MyComponent {
  // 数据源
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 1; i < 1000; i++) {
      this.data.pushData(i);
    }
  }

  build() {
    Column({ space: 5 }) {
      Grid() {
        LazyForEach(this.data, (item: number) => {
          GridItem() {
            // 使用可复用自定义组件
            ReusableChildComponent({ item: item })
          }
        }, (item: string) => item)
      }
      .cachedCount(2) // 设置GridItem的缓存数量
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .margin(10)
      .height(500)
      .backgroundColor(0xFAEEE0)
    }
  }
}

// 自定义组件被@Reusable装饰器修饰,即标志其具备组件复用的能力
@Reusable
@Component
struct ReusableChildComponent {
  @State item: number = 0;

  // aboutToReuse从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容
  // aboutToReuse参数类型已不支持any,这里使用Record指定明确的数据类型。Record用于构造一个对象类型,其属性键为Keys,属性值为Type
  aboutToReuse(params: Record<string, number>) {
    this.item = params.item;
  }

  build() {
    Column() {
      Image($r('app.media.icon'))
        .objectFit(ImageFit.Fill)
        .layoutWeight(1)
      Text(`图片${this.item}`)
        .fontSize(16)
        .textAlign(TextAlign.Center)
    }
    .width('100%')
    .height(120)
    .backgroundColor(0xF9CF93)
  }
}
4.6 WaterFlow使用场景
  • WaterFlow滑动场景存在FlowItem及其子组件的频繁创建和销毁,可以将FlowItem中的组件封装成自定义组件,并使用@Reusable装饰器修饰,使其具备组件复用能力;
  build() {
    Column({ space: 2 }) {
      WaterFlow() {
        LazyForEach(this.datasource, (item: number) => {
          FlowItem() {
            // 使用可复用自定义组件  
            ReusableFlowItem({ item: item })
          }
          .onAppear(() => {
            // 即将触底时提前增加数据  
            if (item + 20 == this.datasource.totalCount()) {
              for (let i = 0; i < 100; i++) {
                this.datasource.AddLastItem()
              }
            }
          })
          .width('100%')
          .height(this.itemHeightArray[item % 100])
          .backgroundColor(this.colors[item % 5])
        }, (item: string) => item)
      }
      .columnsTemplate("1fr 1fr")
      .columnsGap(10)
      .rowsGap(5)
      .backgroundColor(0xFAEEE0)
      .width('100%')
      .height('80%')
    }
  }
@Reusable
@Component
struct ReusableFlowItem {
  @State item: number = 0;
 
  // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容
  aboutToReuse(params) {
    this.item = params.item;
  }
 
  build() {
    Column() {
      Text("N" + this.item).fontSize(12).height('16')
      Image('res/waterFlowTest (' + this.item % 5 + ').jpg')
        .objectFit(ImageFit.Fill)
        .width('100%')
        .layoutWeight(1)
    }
  }
}
4.7 多种条目类型使用场景

标准型

  • 复用组件之间布局完全相同;
  • 示例同列表滚动中描述;

有限变化型

  • 复用组件之间有不同,但是类型有限;
  • 示例为复用组件显式设置两个reuseId与使用两个自定义组件进行复用;
class MyDataSource implements IDataSource {
  ...
}

@Entry
@Component
struct Index {
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 0; i < 1000; i++) {
      this.data.pushData(i);
    }
  }

  build() {
    Column() {
      List({ space: 10 }) {
        LazyForEach(this.data, (item: number) => {
          ListItem() {
            ReusableComponent({ item: item })
              .reuseId(item % 2 === 0 ? 'ReusableComponentOne' : 'ReusableComponentTwo')
          }
          .backgroundColor(Color.Orange)
          .width('100%')
        }, (item: number) => item.toString())
      }
      .cachedCount(2)
    }
  }
}

@Reusable
@Component
struct ReusableComponent {
  @State item: number = 0;

  aboutToReuse(params: ESObject) {
    this.item = params.item;
  }

  build() {
    Column() {
      if (this.item % 2 === 0) {
        Text(`Item ${this.item} ReusableComponentOne`)
          .fontSize(20)
          .margin({ left: 10 })
      } else {
        Text(`Item ${this.item} ReusableComponentTwo`)
          .fontSize(20)
          .margin({ left: 10 })
      }
    }.margin({ left: 10, right: 10 })
  }
}

组合型

  • 复用组件之间有不同,情况非常多,但是拥有共同的子组件;
    示例按照组合型的组件复用方式,将上述示例中的三种复用组件转变为Builder函数后,内部共同的子组件就处于同一个父组件MyComponent下;
  • 对这些子组件使用组件复用时,它们的缓存池也会在父组件上共享,节省组件创建时的消耗。
class MyDataSource implements IDataSource {
  ...
}

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 0; i < 1000; i++) {
      this.data.pushData(i.toString())
    }
  }

  @Builder
  itemBuilderOne(item: string) {
    Column() {
      ChildComponentA({ item: item })
      ChildComponentB({ item: item })
      ChildComponentC({ item: item })
    }
  }

  @Builder
  itemBuilderTwo(item: string) {
    Column() {
      ChildComponentA({ item: item })
      ChildComponentC({ item: item })
      ChildComponentD({ item: item })
    }
  }

  @Builder
  itemBuilderThree(item: string) {
    Column() {
      ChildComponentA({ item: item })
      ChildComponentB({ item: item })
      ChildComponentD({ item: item })
    }
  }

  build() {
    List({ space: 40 }) {
      LazyForEach(this.data, (item: string, index: number) => {
        ListItem() {
          if (index % 3 === 0) {
            this.itemBuilderOne(item)
          } else if (index % 5 === 0) {
            this.itemBuilderTwo(item)
          } else {
            this.itemBuilderThree(item)
          }
        }
        .backgroundColor('#cccccc')
        .width('100%')
        .onAppear(() => {
          console.log(`ListItem ${index} onAppear`);
        })
      }, (item: number) => item.toString())
    }
    .width('100%')
    .height('100%')
    .cachedCount(0)
  }
}

@Reusable
@Component
struct ChildComponentA {
  @State item: string = '';

  aboutToReuse(params: ESObject) {
    console.log(`ChildComponentA ${params.item} Reuse ${this.item}`);
    this.item = params.item;
  }

  aboutToRecycle(): void {
    console.log(`ChildComponentA ${this.item} Recycle`);
  }

  build() {
    Column() {
      Text(`Item ${this.item} Child Component A`)
        .fontSize(20)
        .margin({ left: 10 })
        .fontColor(Color.Blue)
      Grid() {
        ForEach((new Array(20)).fill(''), (item: string,index: number) => {
          GridItem() {
            Image($r('app.media.startIcon'))
              .height(20)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr 1fr')
      .columnsGap(10)
      .width('90%')
      .height(160)
    }
    .margin({ left: 10, right: 10 })
    .backgroundColor(0xFAEEE0)
  }
}

@Reusable
@Component
struct ChildComponentB {
  @State item: string = '';

  aboutToReuse(params: ESObject) {
    this.item = params.item;
  }

  build() {
    Row() {
      Text(`Item ${this.item} Child Component B`)
        .fontSize(20)
        .margin({ left: 10 })
        .fontColor(Color.Red)
    }.margin({ left: 10, right: 10 })
  }
}

@Reusable
@Component
struct ChildComponentC {
  @State item: string = '';

  aboutToReuse(params: ESObject) {
    this.item = params.item;
  }

  build() {
    Row() {
      Text(`Item ${this.item} Child Component C`)
        .fontSize(20)
        .margin({ left: 10 })
        .fontColor(Color.Green)
    }.margin({ left: 10, right: 10 })
  }
}

@Reusable
@Component
struct ChildComponentD {
  @State item: string = '';

  aboutToReuse(params: ESObject) {
    this.item = params.item;
  }

  build() {
    Row() {
      Text(`Item ${this.item} Child Component D`)
        .fontSize(20)
        .margin({ left: 10 })
        .fontColor(Color.Orange)
    }.margin({ left: 10, right: 10 })
  }
}

http://www.kler.cn/a/470670.html

相关文章:

  • 【Linux系列】并发与顺序执行:在 Linux 脚本中的应用与选择
  • CS·GO搬砖流程详细版
  • 【题库】人工智能训练师练习题
  • 探索Whisper:从原理到实际应用的解析
  • stm32week3
  • 【C++】线程启动、结束与创建线程写法
  • 【C语言程序设计——函数】编写子函数求x的n次方(头歌实践教学平台习题)【合集】
  • 玩转大语言模型——ollama导入huggingface下载的模型
  • 【Flink CDC】Flink CDC的Schema Evolution表结构演变的源码分析和流程图
  • 【HarmonyOS-ArkTS语言】面向对象【合集】
  • 【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
  • 打靶记录23——Raven2
  • iOS 逆向学习 - iOS Architecture Cocoa Touch Layer
  • 计算机网络(第七版)知识点
  • Redis持久化详解
  • HTML5 弹跳动画(Bounce Animation)详解
  • 基于Vue的农业设备租赁系统设计与实现(LW+源码+讲解)
  • 【性能优化】如何提升页面加载速度?
  • Ubuntu 20.04安装gcc
  • Java高频面试之SE-08
  • 千兆宽带和全屋WiFi覆盖
  • jmeter 中 BeanShell 预处理程序、JSR223后置处理程序使用示例
  • 【Spring Boot】统一数据返回
  • 『SQLite』表连接查询
  • iPhone升级iOS18黑屏?2025最新修复办法分享
  • docker学习记录:commit,制作自己的镜像