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

鸿蒙公共通用组件封装实战指南:从基础到进阶

一、鸿蒙组件封装核心原则

1.1 高内聚低耦合设计

在鸿蒙应用开发中,高内聚低耦合是组件封装的关键准则,它能极大提升代码的可维护性与复用性。

从原子化拆分的角度来看,我们要把复杂的 UI 界面拆分为基础组件和复合组件。像按钮、输入框这类基础组件,它们功能单一,只专注于自身的核心功能实现,比如按钮负责点击交互,输入框负责文本输入。而表单、列表等复合组件,则是由多个基础组件组合而成,实现更复杂的业务功能。以一个登录界面为例,它由用户名输入框、密码输入框和登录按钮这些基础组件组成,我们将它们组合成一个登录表单复合组件。这样的拆分方式,让每个组件都职责明确,当需要修改按钮样式或者输入框的验证逻辑时,只需要在对应的基础组件中进行修改,不会影响到其他组件,有效降低了组件之间的耦合度,同时也提高了代码的内聚性 。

状态管理在鸿蒙组件中至关重要,@State 和 @Link 是实现数据驱动 UI 更新的核心。@State 用于声明组件内部的私有状态变量,当这个状态变量发生变化时,会自动触发 UI 的刷新。例如在一个计数器组件中,我们用 @State 修饰一个 count 变量,当用户点击按钮使 count 值增加时,UI 上显示的数字会随之更新。@Link 则实现了父子组件之间的双向数据绑定,让父子组件可以共享状态变量。比如在一个父子组件交互的场景中,父组件有一个文本状态变量,子组件通过 @Link 绑定这个变量后,子组件中对这个文本的修改会同步到父组件,反之亦然,实现了数据的实时双向同步,进一步增强了组件之间的协同能力 ,同时也保证了数据的一致性。

合理控制组件的生命周期是确保应用性能和资源有效利用的关键。aboutToAppear 钩子在组件即将显示时触发,我们可以在这里进行数据初始化、资源预加载等操作。比如在一个新闻详情页面组件中,在 aboutToAppear 时可以去加载新闻的具体内容数据,这样当页面显示时,用户能快速看到新闻内容,提升用户体验。onDidBuild 钩子在组件构建完成后触发,此时我们可以进行一些与 UI 相关的操作,比如获取组件的尺寸信息等。通过合理运用这些生命周期钩子,我们可以更好地管理组件的资源和行为,让组件在不同的生命周期阶段都能高效运行,减少资源浪费和性能瓶颈 。

二、UI 组件封装实战

2.1 基础组件封装案例

在鸿蒙应用开发中,按钮组件是最常用的基础组件之一,其封装过程充分体现了基础组件封装的要点。

我们先创建一个 Button 组件,用 @Component 装饰器和 struct 关键字定义组件结构。在这个组件中,通过 @Prop 装饰器来接收外部传入的属性参数,比如按钮的文本内容 text、按钮的颜色 backColor、文本颜色 textColor 等。例如:

@Component
struct MyButton {@Prop text: string;@Prop backColor: Color = Color.Blue;@Prop textColor: Color = Color.White;build() {Button(this.text).backgroundColor(this.backColor).fontColor(this.textColor).width('100%').height('50vp')}
}

这样,在其他页面使用这个按钮组件时,只需要传入相应的属性值即可。比如:

MyButton({text: '点击我',backColor: Color.Red
})

在这个过程中,我们还可以对按钮的样式进行抽离和复用。利用 @Extend 装饰器定义一个函数,来设置按钮的公共样式,像按钮的圆角、边框等。例如:

  • 和@Styles不同,@Extend仅支持在全局定义,不支持在组件内部定义。
@Extend(Button)
function buttonStyle() {.borderWidth(2).borderRadius(16).borderColor(Color.Gray)
}

然后在按钮组件的 build 方法中应用这个样式函数,使代码更加简洁和易于维护。

在处理按钮的点击事件时,我们通过 @Prop 接收一个点击回调函数 onClick,将业务逻辑的处理交给使用组件的地方。比如在一个登录页面中,点击登录按钮时需要进行账号密码验证和登录操作,就可以在传入的点击回调函数中实现这些逻辑,而按钮组件本身只专注于按钮的 UI 展示和点击交互的基础功能 。

2.2 复合组件封装技巧

表单组件是典型的复合组件,它由多个基础组件组成,实现了复杂的业务功能,其封装技巧对于提升开发效率和代码质量至关重要。

以登录表单为例,它包含用户名输入框、密码输入框和登录按钮。我们先创建一个 LoginForm 组件,在这个组件内部组合这些基础组件。在构建过程中,合理使用布局容器,如 Column 和 Row 来排列组件,使其符合用户界面设计规范。例如:

@Component
struct LoginForm {@State username: string = '';@State password: string = '';handleLogin() {// 登录逻辑处理console.log('用户名:', this.username, '密码:', this.password);}build() {Column() {TextInput({ placeholder: '请输入用户名' }).onChange((value) => this.username = value);TextInput({ placeholder: '请输入密码' }).onChange((value) => this.password = value);MyButton({text: '登录',backColor: Color.Blue,onClick: this.handleLogin.bind(this)});}}
}

在这个封装过程中,我们通过 @State 修饰组件内部的状态变量 username 和 password,用于存储用户输入的信息。当用户在输入框中输入内容时,通过 onChange 事件更新对应的状态变量。而登录按钮的点击事件则绑定到组件内部的 handleLogin 方法,在这个方法中可以实现具体的登录业务逻辑,比如调用后端接口进行验证等 。

为了提高表单组件的通用性,我们还可以将一些属性进行参数化。比如表单的提示信息、按钮的文本等,都可以通过 @Prop 从外部传入,这样在不同的业务场景下,只需要传入不同的参数,就可以复用这个表单组件。同时,对于表单的验证逻辑,我们可以单独封装成一个函数,在 handleLogin 方法中调用,进一步提高代码的可维护性和复用性。

三、动态属性封装方案

3.1 AttributeModifier 进阶应用

在鸿蒙组件封装中,AttributeModifier 为实现动态属性提供了强大的支持,它能有效解决静态注册属性在处理属性动态化时的不足,使组件的样式和属性设置更加灵活。

我们先了解一下 AttributeModifier 的基本接口。它是一个接口,开发者需要实现其中的 applyXxxAttribute 方法来实现对应场景的属性设置。这里的 Xxx 表示多态的场景,包括默认态(Normal)、按压态(Pressed)、焦点态(Focused)、禁用态(Disabled)、选择态(Selected) 。T 是组件的属性类型,在回调中可以获取到属性对象,通过该对象来设置属性。例如,对于一个 Button 组件,我们可以这样定义一个实现 AttributeModifier 接口的类:

class MyButtonModifier implements AttributeModifier<ButtonAttribute> {isDark: boolean = false;applyNormalAttribute(instance: ButtonAttribute): void {if (this.isDark) {instance.backgroundColor('#707070');} else {instance.backgroundColor('#17A98D').borderColor('#707070').borderWidth(2);}}
}

在这个例子中,通过 isDark 变量来控制按钮的背景颜色等样式。当 isDark 为 true 时,按钮背景色为 #707070;为 false 时,背景色为 #17A98D,同时设置边框颜色和宽度 。

在实际应用中,我们可以在组件中使用这个 Modifier。比如:

@Entry
@Component
struct AttributeDemo {@State modifier: MyButtonModifier = new MyButtonModifier(true);build() {Row() {Column() {Button("Button").attributeModifier(this.modifier).onClick(() => {this.modifier.isDark = !this.modifier.isDark;});}.width('100%');}.height('100%');}
}

这样,当用户点击按钮时,会切换 isDark 的值,从而动态改变按钮的样式。而且,一个 Modifier 实例对象可以在多个组件上使用,一个组件上多次使用 applyNormalAttribute 设置不同的 Modifier 实例,每次状态变量刷新均会按顺序执行这些实例的方法属性设置,遵循属性覆盖原则,即后设置的属性生效 。

3.2 插槽机制实现灵活布局

插槽机制在鸿蒙组件封装中是实现灵活布局和内容定制的关键技术,它允许父组件向子组件传递任意的 UI 内容,极大地增强了组件的通用性和可扩展性。

在鸿蒙中,我们可以通过 @BuilderParam 装饰器来实现插槽功能。@BuilderParam 装饰器用于标记指向 @Builder 方法的变量,在初始化自定义组件时,可以通过对这个属性赋值来为组件添加特定的功能,其作用类似于 Vue 中的 slot 占位符 。

以一个卡片组件为例,我们来看插槽的具体实现。首先定义子组件 Card:

@Component
export struct Card {@Builderslot() {Text('暂无数据').margin(30);}private title: ResourceStr='标题';@BuilderParam component: () => void = this.slot;build() {Column() {Row() {Text(this.title).fontColor('#333333').fontSize(18).fontWeight(700).lineHeight(26);}.justifyContent(FlexAlign.Start).width('100%');this.component();}.backgroundColor(Color.White).width('100%').padding({left: 16,right: 16,top: 20,bottom: 20}).borderRadius(12).margin({ bottom: 12 });}
}

在这个组件中,我们定义了一个 @Builder 修饰的 slot 方法,作为默认的插槽内容。同时,通过 @BuilderParam 修饰的 component 属性来接收父组件传入的自定义内容 。

在父组件中使用这个卡片组件时,可以这样传入自定义内容:

@Builder
function overBuilder() {Text('外部组件').margin(30);
}@Preview
@Component
@Entry
export struct Index {@BuildercomponentBuilder() {Text('内部组件').margin(30);}build() {Column() {Card({ title: '默认插槽' });Card({ title: '使用内部的组件插槽', component: this.componentBuilder });Card({ title: '使用外部的组件插槽', component: overBuilder });Card({ title: '直接传值' }) {Text('直接传值').margin(30);}}.height('100%').padding({left: 16,right: 16,top: 20,bottom: 20}).backgroundColor('#f0f3f6');}
}

这里展示了多种使用插槽的方式,包括使用默认插槽、传入内部定义的 @Builder 方法作为插槽内容、传入外部定义的 @Builder 方法作为插槽内容,以及直接在使用组件时通过尾随闭包传入内容 。

当需要多个插槽时,同样可以通过定义多个 @BuilderParam 属性来实现。例如,修改 Card 组件为支持两个插槽:

@Component
export struct Card {@Builderslot() {Text('暂无数据').margin(30);}@BuilderdefaultRightSlot() {Text('详情');}private title: ResourceStr = '标题';@BuilderParam component: () => void = this.slot;@BuilderParam rightSlot: () => void = this.defaultRightSlot;build() {Column() {Row() {Text(this.title).fontColor('#333333').fontSize(18).fontWeight(700).lineHeight(26);this.rightSlot();}.justifyContent(FlexAlign.SpaceBetween).width('100%');this.component();}.backgroundColor(Color.White).width('100%').padding({left: 16,right: 16,top: 20,bottom: 20}).borderRadius(12).margin({ bottom: 12 });}
}

在父组件中使用时:

@Builder
function overBuilder() {Text('外部组件').margin(30);
}@Preview
@Component
@Entry
export struct Index {@BuildercomponentBuilder() {Text('内部组件').margin(30);}@BuilderrightBuilder() {Text('右边的插槽').margin(30);}build() {Column() {Card({ title: '默认插槽' });Card({ title: '通过参数传递', component: this.componentBuilder, rightSlot: this.rightBuilder });}.height('100%').padding({left: 16,right: 16,top: 20,bottom: 20}).backgroundColor('#f0f3f6');}
}

这样就实现了一个支持多个插槽的组件,父组件可以根据需求灵活地向子组件传递不同位置的内容,满足复杂的布局和业务需求 。

四、多端适配封装策略

4.1 响应式布局方案

在鸿蒙应用开发中,响应式布局是实现多端适配的关键技术,它能确保应用在不同设备上都能呈现出良好的用户体验 。

鸿蒙系统提供了丰富的响应式布局能力和工具。其中,栅格断点系统是重要的一环,它类似于 Web 设计中的栅格布局,将应用窗口在宽度维度上分成不同的区间,即断点。通过设置这些断点,应用可以在从小屏到大屏的不同设备上自动调整布局结构。比如,我们可以将断点设置为超小(xs,对应智能穿戴设备)、小(sm,对应手机)、中(md,对应平板)、大(lg,对应智慧屏与 PC)等不同范围。在不同的断点区间,我们可以为组件设置不同的布局参数。例如,在一个商品展示页面中,在手机(sm 断点)上,商品图片和描述可能采用上下排列的方式,图片占据较大的屏幕比例;而在平板(md 断点)上,商品图片和描述可以采用左右排列的方式,并且可以展示更多的商品信息 。

媒体查询也是实现响应式布局的重要手段。它类似于 Web CSS 中的媒体查询,允许开发者根据设备的特性,如屏幕尺寸、分辨率、横竖屏状态等编写条件语句来应用不同的布局规则或样式。具体实现时,我们首先导入媒体查询模块:import mediaquery from '@ohos.mediaquery';。然后通过matchMediaSync接口设置媒体查询条件,保存返回的条件监听句柄listener。例如监听横屏事件:let listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(orientation: landscape)');。接着给条件监听句柄listener绑定回调函数onPortrait,当listener检测设备状态变化时执行回调函数,在回调函数内,根据不同设备状态更改页面布局或者实现业务逻辑 。比如当检测到设备为横屏时,我们可以调整页面中组件的排列方式,使其更适合横屏显示。

鸿蒙内置的一些组件也支持响应式布局,像 Tabs、Swiper、Grid、List、GridRow 等。以 Tabs 组件为例,通过barPosition、vertical等属性的不同组合可以实现不同屏幕的适配。在大屏设备上,我们可以将 Tabs 组件的barPosition设置为Start,使其页签栏显示在侧边;而在小屏设备上,将barPosition设置为End,使页签栏显示在底部,更方便用户操作 。通过合理运用这些响应式布局技术和组件,我们可以打造出在各种设备上都能完美适配的鸿蒙应用。

4.2 分布式能力封装

分布式能力是鸿蒙系统的一大特色,它允许应用在多个设备之间无缝协作,实现数据共享、任务迁移等功能 。在组件封装中,合理利用分布式能力可以极大地提升应用的用户体验和功能扩展性 。

在设备协同与任务分配方面,我们利用鸿蒙系统的分布式软总线(Distributed Soft Bus, DSB)技术实现设备的自动发现与连接。在一个智能家居控制应用中,手机作为控制终端,需要与家中的智能灯泡、智能空调等设备连接。我们可以通过 DSB 技术,让手机自动搜索并连接这些智能设备。然后,将复杂的任务分解为多个子任务,并根据设备的性能和资源状况进行合理分配。例如,在视频处理应用中,将视频解码任务分配给性能较强的设备,将音频处理任务分配给对音频处理有优势的设备,通过鸿蒙系统的分布式任务调度服务(Distributed Task Scheduler, DTS),实现任务的动态调度与监控,确保整个视频处理过程高效运行 。

在实现分布式数据共享时,鸿蒙系统提供了分布式数据管理服务(Distributed Data Management, DDM)。我们首先要设计一个统一的数据模型,确保不同设备上的数据结构一致。比如在一个团队协作应用中,任务列表、文档等数据在手机、平板、电脑等设备上都要保持相同的数据结构。然后根据应用场景,选择合适的共享策略。可以采用 “主从共享” 模式,其中一个设备作为主设备,负责数据的更新和共享,其他设备作为从设备,接收主设备的数据更新。同时,要考虑多设备同时修改数据时可能出现的数据冲突问题,设计冲突解决机制,如采用 “最后写入优先” 策略,或者通过用户交互来解决冲突,保证数据的一致性和准确性 。通过这些分布式能力的封装和应用,我们可以开发出具有强大跨设备协作能力的鸿蒙应用。

五、封装最佳实践

5.1 性能优化技巧

在鸿蒙组件封装过程中,性能优化是至关重要的环节,它直接影响着应用的响应速度和用户体验。下面介绍几种有效的性能优化技巧。

延迟渲染是处理大数据列表时提升性能的关键手段。在鸿蒙开发中,我们可以使用 LazyForEach 来实现这一功能。LazyForEach 会根据屏幕可视区能够容纳显示的组件数量按需加载数据,并根据加载的数据量创建组件,挂载在组件树上,构建出一棵短小的组件树 。例如,在一个新闻列表应用中,可能会有大量的新闻数据。如果使用普通的 ForEach 一次性加载所有新闻数据并渲染,当数据量较大时,会导致页面启动时间过长,内存占用过高。而使用 LazyForEach,只有当用户滑动到相应位置时,才会加载并渲染对应位置的新闻数据,大大减少了页面首次启动时一次性加载数据的时间消耗,减少了内存峰值,显著提升了页面的能效比和用户体验 。同时,为了避免在快速滑动长列表时出现白块现象,我们可以结合 List 容器的 cachedCount 属性一起使用,设置列表中 ListItem/ListItemGroup 的预加载数量,提前加载部分屏幕可视区外的数据,保证列表滑动的流畅性 。

资源复用对于管理图片等资源、降低内存开销起着重要作用。在鸿蒙中,我们可以通过 PixelMapPool 来管理图片资源。PixelMapPool 是一个用于缓存 PixelMap 对象的资源池,它可以减少图片解码和创建 PixelMap 对象的开销 。以一个图片展示应用为例,当用户浏览大量图片时,如果每次都重新解码和创建 PixelMap 对象,会消耗大量的内存和时间。通过 PixelMapPool,我们可以将已经解码和创建的 PixelMap 对象缓存起来,当需要再次显示相同图片时,直接从资源池中获取,而不需要重新进行解码和创建操作,大大提高了图片加载的效率,同时也降低了内存的占用 。在使用 PixelMapPool 时,我们需要合理设置资源池的大小,根据应用的实际需求和设备的内存情况,确保既能充分利用资源复用的优势,又不会因为资源池过大而占用过多的内存。

内存监控是确保组件性能稳定的重要保障。在鸿蒙开发中,我们可以使用 Profiler 工具来分析组件的内存占用情况。DevEco Profiler 是集成在 DevEco Studio 中的一款原生鸿蒙应用性能优化工具,它提供了针对鸿蒙应用内存问题的场景化分析模板 SnapshotInsight 与 Allocation Insight,可以用于分析 ArkTS 以及 Native 内存 。例如,在开发一个视频编辑应用时,通过 Profiler 工具,我们可以实时监控组件在不同操作下的内存变化,如导入视频、添加特效、剪辑视频等操作。如果发现某个操作导致内存占用过高且持续增长,就可以通过分析工具进一步查看内存占用的详细情况,定位到具体的内存泄漏点或者内存使用不合理的地方,然后针对性地进行优化,如及时释放不再使用的资源、优化数据结构等,确保应用在运行过程中内存使用的合理性和稳定性 。通过定期使用 Profiler 工具对组件进行内存分析,可以及时发现潜在的内存问题,避免在应用上线后出现因内存问题导致的卡顿、崩溃等现象,提升应用的质量和用户满意度。

5.2 版本兼容方案

随着鸿蒙系统的不断发展和更新,确保组件在不同版本系统上的兼容性是非常重要的。

我们可以采用条件编译的方式来处理不同版本系统的差异。鸿蒙开发中,通过 #ifdef 和 #endif 等预处理指令,我们可以根据系统版本号来编写特定的代码。例如,在鸿蒙系统的某个版本中,对某个组件的 API 进行了更新,新的 API 提供了更强大的功能,但旧版本系统不支持。这时我们可以使用条件编译:

#ifdef HARMONYOS_XX
// 这里编写针对新版本系统的代码,使用新的API
#else
// 这里编写针对旧版本系统的兼容代码,使用旧的API或者其他替代方案
#endif

这样,在编译时,编译器会根据当前的系统版本号,选择相应的代码进行编译,从而确保组件在不同版本系统上都能正常运行 。

定期对组件进行版本兼容性测试也是必不可少的。我们需要在不同版本的鸿蒙系统设备上进行全面的测试,包括功能测试、性能测试、界面显示测试等。例如,在开发一个电商应用的商品展示组件时,我们要在搭载不同版本鸿蒙系统的手机、平板等设备上进行测试,检查组件在不同系统版本下商品图片的加载是否正常、商品信息的显示是否完整、价格计算和优惠展示是否准确等 。通过测试,及时发现并解决可能出现的兼容性问题,如某个版本系统下组件样式错乱、交互功能异常等。同时,要关注鸿蒙系统官方发布的版本更新说明和兼容性指南,及时调整组件的代码,以适应新的系统特性和变化,保证组件在各个版本系统上都能为用户提供一致的、高质量的使用体验 。

六、常见问题解决方案

6.1 组件生命周期冲突

在鸿蒙应用开发中,子组件生命周期与页面生命周期不同步是一个常见的问题,这可能会导致数据加载、资源释放等操作出现异常,影响应用的稳定性和用户体验。

比如在一个包含视频播放子组件的页面中,当页面切换时,如果子组件的生命周期没有与页面生命周期同步,可能会出现视频在后台继续播放、资源未及时释放等问题。具体来说,当页面隐藏时,子组件的资源如视频解码资源等可能没有及时释放,导致内存占用过高;当页面再次显示时,子组件可能没有正确地重新初始化,出现播放异常等情况 。

为了解决这个问题,我们可以通过 @Prop 传递状态变量,在 onPageShow/onPageHide 中控制子组件行为。在父组件中,定义一个 @State 修饰的状态变量,比如 isPageVisible,在 onPageShow 钩子中设置 isPageVisible 为 true,在 onPageHide 钩子中设置为 false 。然后通过 @Prop 将这个状态变量传递给子组件,子组件通过监听这个变量的变化来执行相应的操作。例如,在子组件中,通过 @Watch 修饰符监听 isPageVisible 变量的变化,当 isPageVisible 为 false 时,暂停视频播放并释放相关资源;当 isPageVisible 为 true 时,重新初始化视频播放相关的设置并恢复播放 。代码示例如下:

// 父组件
@Entry
@Component
struct ParentComponent {@State isPageVisible: boolean = true;onPageShow() {this.isPageVisible = true;}onPageHide() {this.isPageVisible = false;}build() {Column() {VideoComponent({ isVisible: this.isPageVisible });}}
}// 子组件
@Component
struct VideoComponent {@Prop@Watch('handleVisibilityChange')isVisible: boolean;handleVisibilityChange() {if (this.isVisible) {// 初始化视频播放} else {// 暂停视频播放,释放资源}}build() {// 视频组件的构建逻辑}
}

通过这种方式,我们可以有效地解决子组件生命周期与页面生命周期不同步的问题,确保组件在页面不同状态下都能正确地执行相应的操作,提高应用的稳定性和性能 。

6.2 样式覆盖问题

在鸿蒙应用开发中,自定义组件样式被父组件覆盖是一个常见的样式冲突问题,这会导致组件无法呈现出预期的外观,影响应用的视觉效果和用户体验 。

例如,我们在一个自定义的按钮组件中设置了独特的背景颜色、字体大小和边框样式等,当将这个按钮组件放置在一个具有全局样式的父组件中时,父组件的样式可能会覆盖掉按钮组件的自定义样式,使得按钮看起来与预期的样式不一致 。具体表现可能是按钮的背景颜色变成了父组件设置的颜色,字体大小也不符合按钮组件的设计,边框样式也被改变或消失 。

为了解决这个问题,我们可以使用!important 标记或自定义 CSS 作用域。使用!important 标记时,在自定义组件的样式属性后面加上!important,这样可以提高该样式的优先级,使其不会被父组件的样式轻易覆盖。例如:

/* 自定义按钮组件的样式 */
.my - button {background - color: blue!important;font - size: 16px!important;border: 1px solid black!important;
}

通过这种方式,即使父组件有其他样式影响到这个按钮,按钮也会保持我们设置的样式 。

另一种方法是使用自定义 CSS 作用域。我们可以为自定义组件创建一个独立的 CSS 类,并在组件内部使用这个类来定义样式,这样可以避免与父组件的样式发生冲突 。例如,在自定义按钮组件的模板中添加一个独特的类名:

<button class="my - custom - button">点击我</button>

然后在 CSS 文件中定义这个类的样式:

.my - custom - button {background - color: green;font - size: 14px;border: 2px solid gray;
}

通过这种方式,自定义组件的样式被限制在这个特定的类名作用域内,不会受到父组件其他样式的干扰 。在实际应用中,我们可以根据具体的项目需求和代码结构,选择合适的方法来解决样式覆盖问题,确保自定义组件能够按照预期的样式进行展示 。

七、组件库工程化实践

7.1 项目结构规范

在鸿蒙组件库开发中,建立清晰合理的项目结构规范是保障开发效率和代码质量的基础。

一般来说,我们会将组件库项目分为多个层级。在根目录下,主要包含 src 目录用于存放源代码,test 目录用于放置测试代码,以及一些项目配置文件如 package.json、config.json 等。在 src 目录中,进一步细分 components 目录,用于存放各个组件的实现代码,每个组件都有自己独立的文件夹,文件夹内包含组件的.ts 文件(用于定义组件逻辑)、.css 文件(用于设置组件样式)以及可能的.ets 文件(ArkTS 文件,用于更复杂的组件逻辑和 UI 构建) 。例如,对于 Button 组件,其结构如下:

src├── components│ ├── Button│ │ ├── Button.ts│ │ ├── Button.css│ │ └── Button.ets

一般来说不会写那么复杂 

这种结构使得每个组件都有独立的命名空间,方便管理和维护。同时,我们还可以设置 utils 目录,用于存放一些公共的工具函数,比如数据格式化函数、网络请求封装函数等 。例如,在处理日期格式时,我们可以在 utils 目录下创建一个 dateUtils.ts 文件,里面定义各种日期格式化的函数,供各个组件使用:

// dateUtils.tsexport function formatDate(date: Date, format: string): string {// 具体的日期格式化逻辑}

assets 目录用于存放静态资源,如图标、图片等。比如在开发一个图片展示组件时,相关的图片资源就可以存放在 assets 目录下,通过相对路径引用,确保组件在不同环境下都能正确加载资源 。

7.2 自动化测试方案

自动化测试是保障组件库质量的重要手段,它可以帮助我们在开发过程中及时发现问题,提高组件的稳定性和可靠性。

在鸿蒙组件库中,我们可以使用 arkxtest 自动化测试框架,它支持 JS/TS 语言的单元测试框架(JsUnit)及 UI 测试框架(UiTest) 。对于单元测试,我们可以针对组件的各个功能模块编写测试用例,验证组件在不同输入条件下的输出是否符合预期。比如对于一个加法运算的工具函数,我们可以编写如下测试用例:

import { describe, it, expect } from '@ohos.hypium';// 假设add是在utils目录下的mathUtils.ts中定义的加法函数
import { add } from '../utils/mathUtils';describe('Math Utils Tests', () => {it('should add two numbers correctly', () => {const result = add(2, 3);expect(result).assertEqual(5);});
});

在这个测试用例中,我们使用 describe 定义了一个测试套件,用 it 定义了一个具体的测试用例,通过 expect 断言来验证 add 函数的返回值是否正确 。

对于 UI 测试,我们可以利用 UiTest 提供的查找和操作界面控件的能力,模拟用户的操作行为,验证组件的 UI 交互是否正常。比如对于一个按钮组件,我们可以编写测试用例来模拟点击按钮,然后验证按钮点击后的状态变化或者相关的业务逻辑是否执行 。例如:

import { describe, it, expect } from '@ohos.hypium';
import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
import { Driver, ON } from '@ohos.UiTest';
import Want from '@ohos.app.ability.Want';
import UIAbility from '@ohos.app.ability.UIAbility';const delegator = abilityDelegatorRegistry.getAbilityDelegator();
const bundleName = abilityDelegatorRegistry.getArguments().bundleName;describe('Button Component UI Tests', () => {it('should change state after click', async () => {const want: Want = {bundleName: bundleName,abilityName: 'MainAbility'};await delegator.startAbility(want);let driver = Driver.create();await driver.delayMs(1000);let button = await driver.findComponent(ON.text('Click Me'));await button.click();// 这里可以根据按钮点击后的预期状态进行断言,比如按钮的背景颜色变化等// 假设按钮点击后背景颜色变为红色const newBackgroundColor = await button.getBackgroundColor();expect(newBackgroundColor).assertEqual('#FF0000');});
});

通过这种方式,我们可以全面地对组件库进行自动化测试,确保组件在各种场景下都能正常工作 。

八、总结与展望

通过系统化的组件封装,可使鸿蒙应用开发效率提升 40% 以上。未来随着鸿蒙生态扩展,建议重点关注:

  1. 分布式组件状态同步:随着鸿蒙系统分布式能力的不断发展,如何实现分布式组件间的状态高效同步,确保数据在不同设备上的一致性,将是一个重要的研究方向。比如在多设备协同办公应用中,不同设备上的文档编辑组件需要实时同步文档的编辑状态和内容,这就需要更高效的分布式状态同步机制。
  1. 跨设备 UI 自动适配:随着越来越多的设备接入鸿蒙生态,包括各种智能穿戴设备、智慧屏等,实现跨设备 UI 的自动适配,使应用在不同尺寸、不同分辨率的设备上都能呈现出最佳的用户界面,是提升用户体验的关键。例如,开发一款支持手机、平板、智慧屏的视频播放应用,需要根据不同设备的屏幕特性,自动调整视频播放界面的布局和组件大小,以适应不同的观看场景。
  1. 基于 AI 的智能组件生成:借助 AI 技术,根据业务需求自动生成组件代码和布局,能够进一步提高开发效率,降低开发门槛。比如,通过自然语言描述业务需求,AI 自动生成对应的按钮组件、表单组件等,这将极大地改变鸿蒙应用的开发模式,让开发者能够更专注于业务逻辑的实现。

相关文章:

鸿蒙公共通用组件封装实战指南:从基础到进阶

一、鸿蒙组件封装核心原则 1.1 高内聚低耦合设计 在鸿蒙应用开发中&#xff0c;高内聚低耦合是组件封装的关键准则&#xff0c;它能极大提升代码的可维护性与复用性。 从原子化拆分的角度来看&#xff0c;我们要把复杂的 UI 界面拆分为基础组件和复合组件。像按钮、输入框这…...

IntelliJ 配置(二)配置相关类库(2)LineMarkerProvider

一、介绍 LineMarkerProvider 是 IntelliJ 平台插件开发中的一个接口&#xff0c;它的作用是在编辑器左侧的“行标记区域”&#xff08;就是代码行号左边那一栏&#xff09;添加各种图标、标记或导航链接。比如Java 类中看到的&#xff1a; 小绿色三角形&#xff08;可以点击运…...

红宝书第四十二讲:Angular核心特性精讲:依赖注入 RxJS整合

红宝书第四十二讲&#xff1a;Angular核心特性精讲&#xff1a;依赖注入 & RxJS整合 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、依赖注入&#xff08;Dependency Injection&#xff09;&#xff1a;快…...

AD917X系列JESD204B MODE7使用

MODE7特殊在F8&#xff0c;M4使用2个复数通道 CH0_NCO10MHz CH1_NCO30MHZ DP_NCO50MHz DDS1偏移20MHz DDS2偏移40MHz...

软考高级系统架构设计师-第11章 系统架构设计

【本章学习建议】 根据考试大纲&#xff0c;本章不仅考查系统架构设计师单选题&#xff0c;预计考12分左右&#xff0c;而且案例分析和论文写作也是必考&#xff0c;对应第二版教材第7章&#xff0c;属于重点学习的章节。 软考高级系统架构设计师VIP课程https://edu.csdn.net/…...

中和农信的“三农”服务密码:科技+标准化助力乡村振兴

作为中国农村市场最大的专注服务农村小微客户的“三农”综合服务机构&#xff0c;中和农信凭借多年积累的农村服务经验&#xff0c;成功从单一小额信贷机构转型为覆盖金融、生产、生活及生态服务的综合型“三农”服务平台。近期&#xff0c;中和农信在由中保保险资产登记交易系…...

【Redis】布隆过滤器应对缓存穿透的go调用实现

布隆过滤器 https://pkg.go.dev/github.com/bits-and-blooms/bloom/v3 作用&#xff1a; 判断一个元素是不是在集合中 工作原理&#xff1a; 一个位数组&#xff08;bit array&#xff09;&#xff0c;初始全为0。多个哈希函数&#xff0c;运算输入&#xff0c;从而映射到位数…...

MyBatis-Plus笔记(下)

注解 tablename注解 - 描述&#xff1a;表名注解&#xff0c;标识实体类对应的表 - 使用位置&#xff1a;实体类 代码举例&#xff1a; TableName//可以不加&#xff0c;使用实体类的名字作为表名&#xff01;忽略大小写 //BaseMapper->User实体类-》实体类名-》表名数据…...

【项目管理】第14章 项目沟通管理-- 知识点整理

项目管理-相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 项目管理知识域 知识点: (项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域) 对应:第6章-第19章 第6章 项目管理概论 4分第13章 项目资源管理 3-4分第7章 项目…...

3个关键数据解密:首航上市如何重构ebay电商新能源供应链?

3个关键数据解密&#xff1a;首航上市如何重构eBay电商新能源供应链&#xff1f; 在跨境电商圈&#xff0c;一个新玩家的崛起往往意味着新的格局变动。2024年&#xff0c;伴随一家名为“首航”的新能源企业在港股成功上市&#xff0c;整个eBay类目的供应链悄然掀起新一轮洗牌。…...

《华为云Node.js部署:从开发环境到生产上线的完整指南》

目录 引言第一步&#xff1a; 重置密码第二步&#xff1a;连接到服务器第三步&#xff1a;安装必要软件第四步&#xff1a;创建项目目录第五步&#xff1a;将代码上传到服务器1、安装 FileZilla2、打开FileZilla&#xff0c;连接到您的服务器&#xff1a;3、连接后&#xff0c;…...

【网络原理】TCP/IP协议五层模型

目录 一. 协议的分层 二. OSI七层网络协议 三. TCP/IP五层网络协议 四. 网络设备所在分层 五. 封装 六. 分用 七. 传输中的封装和分用 八. 数据单位术语 一. 协议的分层 常见的分层为两种OSI七层模型和TCP/IP五层模型 为什么要协议分层&#xff1f; 在网络通信中&…...

Asp.Net Core学习随笔

学习自BLBL杨中科老师 依赖注入(Dependency Injection) 依赖注入是实现控制反转(Inversion Of Control 即IOC)的一种方式(还有一种叫服务定位器的实现,但是不如依赖注入好用),软件开发中实现解耦常用的方式. 比如吃饭 ​1. 传统写法&#xff08;没有DI&#xff0c;紧耦合&a…...

基于PHP的酒店网上订房系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 酒店服务是旅游行业的一个重要组成部分&#xff0c;它的作用已经从过去的单一的住宿、结算帐务向全面、高水平的服务型酒店转变。酒店的服务工作贯穿于整个酒店的市场营销、预定、入住、退房、结账等环节&#xff0c;酒店要提高整体工作水平&#xff0c;简化工作程序&…...

《MySQL从入门到精通》

文章目录 《MySQL从入门到精通》1. 基础-SQL通用语法及分类2. 基础-SQL-DDL-数据库操作3. 基础-SQL-DDL-表操作-创建&查询4. 基础-SQL-DDL-数据类型及案例4.1 数值类型4.2 字符串类型4.3 时间和日期类型 5. 基础-SQL-DDL-表操作-修改&删除5.1 DDL-表操作-修改5.2 DDL-表…...

MySQL聚合查询

聚合查询 group by...

生信初学者教程(三十四):文章的方法

文章目录 介绍数据收集和整理数据整合差异基因分析功能富集分析免疫浸润分析候选标记物识别诊断ROC曲线单细胞分析统计方法介绍 在数据分析进行的同时,我们可以逐步撰写方法部分,确保其与结果紧密相连。一旦结果部分完成,方法部分应根据结果的逻辑顺序进行分类和组织。在描…...

算力云平台部署—SadTalker的AI数字人视频

选择算力 部署选择 选择镜像 机器管理 控制台 通过平台工具进入服务器 认识管理系统 打开命令行 进入目录 stable-diffusion-webui# cd 增加执行权限 chmod x ./webui.sh 运行命令 bash ./webui.sh sudo apt install -y python3 python3-venv git 安装软件 Creating the …...

iPhone相册导出到电脑的完整指南

iPhone相册导出到电脑的完整指南 本文介绍通过数据线连接实现iPhone照片视频传输到电脑的标准操作方法&#xff0c;适用于需要备份移动设备影像资料的用户。 环境准备 使用原装Lightning或USB-C数据线连接设备与电脑需在电脑端安装设备管理工具&#xff08;如克魔助手&#…...

【数据结构】励志大厂版·初阶(复习+刷题):复杂度

前引&#xff1a;从此篇文章开始&#xff0c;小编带给大家的是数据结构初阶的刷题讲解 &#xff0c;此类文章将简略的包含相关知识&#xff0c;详细的思路拆分讲解&#xff0c;分析每一题的难点、易错点&#xff0c;看见题目如何分析&#xff0c;以上就是小编预备的内容&#x…...

Nginx底层架构(非常清晰)

目录 前言&#xff1a; 场景带入&#xff1a; HTTP服务器是什么&#xff1f; 反向代理是什么&#xff1f; 模块化网关能力&#xff1a; 1.配置能力&#xff1a; 2.单线程&#xff1a; 3.多worker进程 4.共享内存&#xff1a; 5.proxy cache 6.master进程 最后&…...

Golang|Channel 相关用法理解

文章目录 用 channel 作为并发小容器channel 的遍历channel 导致的死锁问题用 channel 传递信号用 channel 并行处理文件用channel 限制接口的并发请求量用 channel 限制协程的总数量 用 channel 作为并发小容器 注意这里的 ok 如果为 false&#xff0c;表示此时不仅channel为空…...

智能合约安全审计平台——以太坊虚拟机安全沙箱

目录 以太坊虚拟机安全沙箱 —— 理论、设计与实战1. 引言2. 理论背景与安全原理2.1 以太坊虚拟机(EVM)概述2.2 安全沙箱的基本概念2.3 安全证明与形式化验证3. 系统架构与模块设计3.1 模块功能说明3.2 模块之间的数据流与安全性4. 安全性与密码学考量4.1 密码学保障在沙箱中…...

趣说区块链隐私智能合约Shielder 实现原理

目录 核心理念 Deposit Withdraw Shielder 是 Aleph Zero 上的智能合约,它利用 zk-SNARK 技术实现隐私支付以及与 DeFi 的隐私交互。这与常规区块链的完全透明性形成鲜明对比,常规区块链允许追踪单个用户与链上合约以及其他用户的所有交互。Shielder 通过使第三方链观察者…...

TCPIP详解 卷1协议 五 Internet协议

5.1——Internet协议 IP是TCP/IP协议族中的核心协议。所有TCP、UDP、ICMP和IGMP数据都通过IP数据报传输。IP 提供了一种尽力而为、无连接的数据报交付服务。“尽力而为”的含义是不保证 IP 数据报能成功到达目的地。任何可靠性必须由上层&#xff08;例如TCP&#xff09;提供。…...

基于Oracle ADG通过dblink创建物化视图同步数据到目标库

基于Oracle ADG通过dblink创建物化视图同步数据到目标库 环境说明&#xff1a;源端环境Oracle ADG一主一备&#xff0c;版本11.2.0.4&#xff0c;目标端版本11.2.0.4&#xff0c;测试通过dblink方式在目标库创建物化视图同步ADG备库的数据。 PROD --> STANDBY – > TAR…...

openGauss新特性 | 自动参数化执行计划缓存

目录 自动化参数执行计划缓存简介 SQL参数化及约束条件 一般常量参数化示例 总结 自动化参数执行计划缓存简介 执行计划缓存用于减少执行计划的生成次数。openGauss数据库会缓存之前生成的执行计划&#xff0c;以便在下次执行该SQL时直接使用&#xff0c;可…...

qt中的正则表达式

问题&#xff1a; 1.在文本中把dog替换成cat&#xff0c;但可能会把dog1替换成cat1&#xff0c;如果原本不想替换dog1&#xff0c;就会出现问题 2文本中想获取某种以.txt为结尾的多有文本&#xff0c;普通的不能使用 3如果需要找到在不同的系统中寻找换行符&#xff0c;可以…...

开源项目 | 17款云原生安全相关的扫描和平台类开源工具

“ 随着云计算技术的不断发展&#xff0c;越来越多的企业开始将应用程序和数据存储到云上。然而&#xff0c;云安全问题也随之而来&#xff0c;因此&#xff0c;开源云原生安全工具的需求也越来越大。在本文中&#xff0c;我们将介绍一些流行的开源云原生安全工具&#xff0c;以…...

力扣面试150题—旋转图像和矩阵置零

Day21 题目描述 思路 矩阵转置 在将列反转 1 2 3 4 5 6 7 8 9 转置 1 4 7 2 5 8 3 6 9 反转 7 4 1 8 5 2 9 6 3 class Solution {public void rotate(int[][] matrix) { //分为两步 矩阵转置&#xff0c;将列倒序 int x0; int nmatrix.length; //转…...

ScholarCopilot:“学术副驾驶“

这里写目录标题 引言&#xff1a;学术写作的痛点与 AI 的曙光ScholarCopilot 的核心武器库&#xff1a;智能生成与精准引用智能文本生成&#xff1a;不止于“下一句”智能引用管理&#xff1a;让引用恰到好处 揭秘背后机制&#xff1a;检索与生成的动态协同快速上手&#xff1a…...

Node.js项目开启多进程的2种方案

当node项目只部署一个单进程单实例时,遇到异常发生后程序会崩溃,此时杀掉进程在重启单这段时间会导致服务不能正常使用,这显然会影响用户体验。 所以需要以多进程的模式去部署应用,这样当某一个进程发生异常重启时,此时有其他请求被接受后,其他进程依旧可以对外提供服务…...

论文导读 | 基于GPU的子图匹配算法

摘要 大规模图上的子图匹配在社交网络挖掘&#xff0c;生物信息学&#xff0c;知识图谱等领域具有关键作用。近年来随着以GPU为代表的新硬件的发展&#xff0c;研究人员开始尝试在GPU上实现这一NP难的任务。GPU提供了大量的计算单元和高速的显存带宽&#xff0c;可以显著提升算…...

中天科技旗下的中天智能装备有限公司,在立库方面有哪些优势?

中天科技旗下的中天智能装备有限公司在立库方面优势显著&#xff0c;主要体现在产品与方案、技术研发、项目经验和服务质量管控等多个维度&#xff0c;能够为客户提供全方位、高品质的立库相关服务。 产品与解决方案优势 多种立库解决方案&#xff1a;提供托盘式立库、料箱式立…...

HTML5+CSS前端开发【保姆级教学】+超链接标签

一、引入&#xff1a; Hello&#xff01;&#xff0c;各位编程猿们&#xff01;一个页面可以跳转到其他页面&#xff0c;去访问其他资源&#xff0c;使得我们的文档更加的灵动&#xff0c;那我们如何实现不同页面的跳转呢&#xff1f;本期主要介绍超链接标签 那么什么是超链接…...

【游戏安全】文本校验类风险

文本风险定义: 在游戏中除了动画,声音参与和玩家的交互之外,游戏中的文本也属于和玩家交互中一项重要的元素。由玩家操作触发任何不同于游戏自身逻辑设定,进而破坏游戏平衡的文本内容都可以称之为文本类风险漏洞。(这个定义自己瞎写的…) 文本风险危害(漏洞举例): …...

快速排序及其应用

快速排序及其应用 标准写法改成稳定版本求第k小值O(n)做法快排的另一种写法 标准写法 #include <bits/stdc.h>using namespace std;using ll long long;int a[] {8, 5, 18, 11, 7, 2, 21, 15, 3, 8};void quickSort(int l, int r) {if (l > r) return ; // 元素个数…...

南柯电子|新能源汽车EMC电磁兼容性测试整改:突破行业规范之路

随着新能源汽车产业的蓬勃发展&#xff0c;车辆电子化、智能化程度不断提高&#xff0c;电磁兼容性&#xff08;EMC&#xff09;问题日益凸显。作为衡量汽车电子系统稳定性的关键指标&#xff0c;EMC性能不仅影响车辆功能安全&#xff0c;更关乎道路交通的整体安全性。 一、EM…...

LabVIEW 程序持续优化

LabVIEW 以其独特的图形化编程方式&#xff0c;在工业自动化、测试测量、数据分析等众多领域发挥着关键作用。为了让 LabVIEW 程序始终保持高效、稳定&#xff0c;并契合不断变化的实际需求&#xff0c;持续改进必不可少。下面将从多个关键维度&#xff0c;为大家细致地介绍通用…...

裂缝检测数据集,支持yolo,coco json,pasical voc xml,darknet格式的标注,1673张原始训练集图片,正确识别率99.4%

数据集详情: 裂缝检测数据集,支持yolo,coco json,pasical voc xml,darknet格式的标注,1673张原始训练集图片,正确识别率99.4% 2394总图像 数据集分割 训练集占比 70% 1673图片 有效集20% 477图片 测试集...

Webrtc让浏览器实现无服务器中转的安全私密聊天

私密聊天平台的应用介绍 在当今数字时代&#xff0c;隐私和安全成为人们日益关注的焦点。许多人发现&#xff0c;他们的聊天记录、个人信息甚至行为习惯都可能被第三方平台记录、分析甚至滥用。无论是出于保护个人隐私的需要&#xff0c;还是希望实现真正的点对点直接通信&…...

数据结构-限定性线性表 - 栈与队列

栈和队列是数据结构中非常重要的两种限定性线性表&#xff0c;它们在实际应用中有着广泛的用途。这篇文章将深入讲解栈和队列的概念、抽象数据类型、实现方式、应用场景以及性能分析&#xff0c;并通过代码示例帮助大家更好地理解和实践。 一、栈的概念与抽象数据类型 1.1 栈…...

接口的集成测试步骤

一、集成测试是什么 ‌接口的集成测试‌是指在软件开发过程中&#xff0c;将各个模块或组件按照设计要求组合在一起&#xff0c;并测试它们之间的接口是否能够正确交互和协同工作的过程。集成测试是软件开发中的一个重要阶段&#xff0c;通常在单元测试之后进行&#xff0c;目的…...

Python 实现的运筹优化系统数学建模详解(多目标规划模型)

一、引言 在数学建模的广阔领域中&#xff0c;多目标规划模型占据着极为重要的地位。它致力于在复杂的实际场景里&#xff0c;同时优化多个相互冲突的目标&#xff0c;寻求一组决策变量&#xff0c;让多个目标函数在满足特定约束条件下达到某种平衡。这种模型广泛应用于生产调度…...

AJAX原理与XMLHttpRequest

目录 一、XMLHttpRequest使用步骤 基本语法 步骤 1&#xff1a;创建 XHR 对象 步骤 2&#xff1a;调用 open() 方法 步骤 3&#xff1a;监听 loadend 事件 步骤 4&#xff1a;调用 send() 方法 二、完整示例 1. GET 请求&#xff08;带查询参数&#xff09; 2. POST 请…...

css中的3d使用:深入理解 CSS Perspective 与 Transform-Style

在前端开发的奇妙世界中&#xff0c;CSS 不仅负责页面的布局和样式&#xff0c;还能赋予元素生动的动态效果。要实现引人入胜的 3D 变换&#xff0c;perspective 和 transform-style 这两个属性扮演着至关重要的角色。本文将带您深入了解这两个属性&#xff0c;揭开它们如何协同…...

在 JMeter 中,Active Threads Over Time 是一个非常有用的监听器(Listener)

在 JMeter 中,Active Threads Over Time 是一个非常有用的监听器(Listener),它可以帮助你实时观察测试过程中活跃线程数(并发用户数)的变化趋势,从而分析系统的并发处理能力和负载情况。 1. Active Threads Over Time 的作用 实时监控并发用户数:显示测试过程中活跃线程…...

未来七轴机器人会占据主流?深度解析具身智能方向当前六轴机器人和七轴机器人的区别,七轴力控机器人发展会加快吗?

六轴机器人和七轴机器人在设计、功能和应用场景上存在明显区别。六轴机器人是工业机器人的传统架构&#xff0c;而七轴机器人则在多自由度和灵活性方面进行了增强。 本文将在理解这两者的区别以及为何六轴机器人仍然是市场主流&#xff0c;从多个方面进行深入解读六轴和七轴区…...

spark-SOL简介

Spark-SQL简介 一&#xff0e;Spark-SQL是什么 Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块 二&#xff0e;Hive and SparkSQL SparkSQL 的前身是 Shark&#xff0c;Shark是给熟悉 RDBMS 但又不理解 MapReduce 的技术人员提供的快速上手的工具 …...

【今日三题】经此一役小红所向无敌(模拟) / 连续子数组最大和(动态规划) / 非对称之美(贪心)

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;每日两三题 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 经此一役小红所向无敌(模拟)连续子数组最大和(动态规划)非对称之美(贪心) 经此一役小红所向无敌(模拟) 经此一役小红所向无…...