配景
在应用开辟中,通常必要对ArkUI组件进行封装以便业务复用。结合目前鸿蒙化过程中的实际案例,紧张包含以下三种ArkUI组件封装复用的典型业务场景:
- 公用组件封装场景:公用组件封装紧张指对系统组件进行封装使用。好比公共组件库必要按照UX规范样式提供统一的系统组件样式供其他业务团队使用,如登录按钮、弹窗按钮。
- 弹窗组件封装场景:弹窗组件内部封装弹窗内容和弹窗控制器,调用方通过状态变量控制弹窗显隐。
- 组件工厂类封装场景:组件工厂类封装了全部的组件并将自身向外暴露,调用方通过传入不同的参数,从组件工厂类中获取对应的组件。
下面,本文将针对以上业务场景,具体说明各场景及其实现方案。
公用组件封装
场景形貌
在应用开辟过程中,不同的业务场景可能必要使用雷同功能和样式的ArkUI组件。比方,登录页面登录按钮和购物页面结算按钮可能样式雷同。该场景常用方法是抽取雷同样式的逻辑部门,并将其封装成一个自定义组件到公共组件库中。在业务场景开辟时,统一从公共组件库获取封装好的公用组件。
以Button组件为例,当多处业务场景必要使用雷同样式的Button组件时,将通用逻辑封装成一个MyButton自定义组件,并在通用逻辑中定制了公共的fontSize和fontColor属性。当必要把MyButton组件以Button扩展组件的情势集成到公共组件库中,提供给外部其他团队使用时,为了使它具备Button的所有基础能力并支持以链式调用的方式使用Button组件原生的属性接口,必要在MyButton组件内穷举所有的Button属性 。自定义组件的代码如下
- @Component
- struct MyButton {
- @Prop text: string = '';
- @Prop stateEffect: boolean = true;
- // ...穷举所有Button独有属性
-
- build() {
- Button(this.text)
- .fontSize(12)
- .fontColor('#FFFFFF')
- .stateEffect(this.stateEffect)// stateEffect属性的作用是控制默认点击动画
- .xxx //穷举Button其他独有属性赋值
- }
- }
复制代码 在使用MyButton 组件时,若需修改组件显示内容text和点击动画效果stateEffect时(其他Button独有的属性用法雷同),必要以参数的情势传入:
- @Component
- struct Index {
- build() {
- MyButton({ text: '点击带有动效', stateEffect: true, ... }) // 入参包含MyButton 组件中定义的全部 Button独有属性
- }
- }
复制代码 当前方案的缺点如下:
- 使用方式和系统组件不一致:系统组件通过链式调用的方式设置组件属性,该方案自定义组件必要以“参数列表”情势设置组件属性。
- 自定义组件入参过大:若必要使用系统组件的全量属性方法,则需在封装的自定义组件中以入参的情势穷举接收每个属性值。在使用自定义组件时,也需将全量的属性值以参数情势传入。
- 不利于后期维护:当自定义组件中的系统组件属性发生变更时,自定义组件也必要同步适配。
实现方案
为解决上述方案缺点,ArkTS为每个系统组件提供了attributeModifier属性方法。该方法将组件属性设置分离到系统提供的AttributeModifier接口实现类实例中,通过自定义Class类实现AttributeModifier接口对系统组件属性进行扩展。通过AttributeModifier实现公用组件有如下两种方案:
方案一:提供方对外提供封装好的自定义组件。
以封装系统组件Button为例,该方案实现步骤如下:
- 提供方在公共组件库中创建公用的自定义组件,该组件支持外部传入attributeModifier属性。
- //提供方自定义组件并导出
- @Component
- export struct MyButton {
- @Prop text: string = '';
- // 接受外部传入的AttributeModifier类实例
- @Prop modifier: AttributeModifier<ButtonAttribute> | null = null;
- build() {
- // AttributeModifier不支持入参为CustomBuilder或Lamda表达式的属性,且不支持事件和手势。此处text只能单独通过入参传递使用。
- Button(this.text)
- // 将入参的AttributeModifier类实例与系统组件绑定
- .attributeModifier(this.modifier)
- .fontSize(20)
- .width(200)
- .height(50)
- }
- }
复制代码
- 使用方自定义AttributeModifier接口实现类,并将该类实例作为参数传入提供方自定义组件。
- // 使用方自定义AttributeModifier接口实现类,此处指定泛型为Button组件的属性类ButtonAttribute
- class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
- // 私有定义Button组件特有属性
- private stateEffectValue: boolean = false;
- private buttonType: ButtonType = ButtonType.Normal;
- constructor() {
- }
- // 实现组件的普通状态下的样式方法,系统还提供了hover状态和其他状态下的样式方法
- applyNormalAttribute(instance: ButtonAttribute): void {
- instance.stateEffect(this.stateEffectValue);
- instance.type(this.buttonType);
- }
- stateEffect(enable: boolean): MyButtonModifier {
- this.stateEffectValue = enable
- return this;
- }
- // 自定义属性名和系统组件属性名一致,便于链式调用时的一致性
- type(buttonType: ButtonType): MyButtonModifier {
- this.buttonType = buttonType;
- return this;
- }
- }
- //使用方使用提供方的公用组件MyButton
- @Component
- struct Index {
- capsuleButtonModifier: MyButtonModifier = new MyButtonModifier().stateEffect(true).type(ButtonType.Capsule)
- circleButtonModifier: MyButtonModifier = new MyButtonModifier().stateEffect(true).type(ButtonType.Circle)
- build() {
- Row() {
- MyButton({ modifier: this.capsuleButtonModifier, text: 'Capsule Button' })
- .margin({ right: 20 })
- MyButton({ modifier: this.circleButtonModifier, text: 'Circle Button' })
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .height('100%')
- }
- }
复制代码 方案二:提供方对外提供AttributeModifier接口的实现类。
- 提供方创建AttributeModifier接口的实现类。
- // 提供方创建自定类Class类,实现系统AttributeModifier接口
- export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
- private buttonType: ButtonType = ButtonType.Normal;
- private stateEffectValue: boolean = false;
- constructor() {
- }
- applyNormalAttribute(instance: ButtonAttribute): void {
- instance.stateEffect(this.stateEffectValue);
- instance.type(this.buttonType);
- // 设置默认样式
- instance.width(200);
- instance.height(50);
- instance.fontSize(20)
- }
- stateEffect(enable: boolean): MyButtonModifier {
- this.stateEffectValue = enable;
- return this;
- }
- type(type: ButtonType): MyButtonModifier {
- this.buttonType = type;
- return this;
- }
- }
复制代码
- 使用方创建提供方的AttributeModifier实现类实例,并作为系统组件attributeModifier属性方法的参数传入。
- @Component
- struct Index {
- modifier = new MyButtonModifier()
- .stateEffect(true)
- .type(ButtonType.Capsule)
- build() {
- Row() {
- Button('Capsule Button')
- .attributeModifier(this.modifier)
- }
- .width('100%')
- .height('100%')
- }
- }
复制代码 对比两种方案,若必要抽取复用的公用组件为单一类型,如Button或Text,保举使用方案二。若必要抽取复用的组件为多个系统组件的组合,如组件中包含Image组件和Text组件,则保举使用方案一。
案例参考
若需抽取一个包含系统组件Image组件和Text组件的公用组件,效果展示如下:
图1 图片和文本组合组件效果
针对固定组合的组件封装采用方案一,实现上述效果的示例代码如下:
- 提供方封装自定义组件CustomImageText并导出。
- @Component
- export struct CustomImageText {
- @Prop imageAttribute: AttributeModifier<ImageAttribute>;
- @Prop textAttribute: AttributeModifier<TextAttribute>;
- @Prop imageSrc: PixelMap | ResourceStr | DrawableDescriptor;
- @Prop text: string;
- build() {
- Column() {
- Image(this.imageSrc)
- .attributeModifier(this.imageAttribute)
- Text(this.text)
- .attributeModifier(this.textAttribute)
- }
- }
- }
复制代码
- 使用方分别实现Image组件和Text组件的AttributeModifier接口实现类。
- // Image组件的AttributeModifier接口实现类
- class ImageModifier implements AttributeModifier<ImageAttribute> {
- private imageWidth: Length = 0;
- private imageHeight: Length = 0;
- constructor(width: Length, height: Length) {
- this.imageWidth = width;
- this.imageHeight = height;
- }
- width(width: Length) {
- this.imageWidth = width;
- return this;
- }
- height(height: Length) {
- this.imageHeight = height;
- return this;
- }
- applyNormalAttribute(instance: ImageAttribute): void {
- instance.width(this.imageWidth);
- instance.height(this.imageHeight);
- }
- }
- // Text组件的AttributeModifier接口实现类
- class TextModifier implements AttributeModifier<TextAttribute> {
- constructor() {
- }
- applyNormalAttribute(instance: TextAttribute): void {
- instance.fontSize(16)
- }
- }
复制代码
- 使用方创建Image组件和Text组件的AttributeModifier接口实现类实例,并作为提供方自定义组件CustomImageText的入参传入。
- @Component
- struct Index {
- imageAttribute: ImageModifier = new ImageModifier(100, 100);
- textAttribute: TextModifier = new TextModifier();
- build() {
- Row() {
- CustomImageText({
- imageAttribute: this.imageAttribute,
- textAttribute: this.textAttribute,
- imageSrc: $r('app.media.startIcon'),
- text: 'label'
- })
- }
- .justifyContent(FlexAlign.Center)
- .backgroundColor('F1F2F3')
- .width('100%')
- .height('100%')
- }
- }
复制代码 弹窗组件封装
使用Dialog弹窗组件场景
如下图所示,团队A是弹窗组件提供方,团队B是弹窗组件使用方。团队A实现了Dialog弹窗模块,并向外提供了相应接口。团队B必要在不同业务场景下展示Dialog弹窗模块中的相应的弹窗组件。比方,团队A封装了警告类型的弹窗组件Dialog并将获取组件的接口暴露给外部,团队B在业务中通过导入并使用该接口打开携带警告信息的弹窗。
图2 使用Dialog弹窗场景
方案先容
系统提供@CustomDialog装饰器用于自定义弹窗的实现。以使用方点击按钮后展示提供方的Dialog弹窗场景为例,若需实现下图效果,Dialog弹窗组件的封装和使用步骤如下:
图3 使用Dialog弹窗效果
- 提供方创建自定义弹窗组件MyCustomDialog。
- // 自定义弹窗需要使用@CustomDialog装饰器
- @CustomDialog
- struct MyCustomDialog {
- @Link visible: boolean;
- // 被@CustomDialog装饰器修饰的组件必须持有CustomDialogController类型属性参数
- controller: CustomDialogController;
- // 弹窗交互事件参数,点击确认和取消按钮时的回调函数
- onCancel?: () => void;
- onConfirm?: () => void;
- build() {
- Column({ space: 12 }) {
- Text("Custom dialog content!")
- .fontSize(20)
- Row() {
- Button("cancel")
- .onClick(() => {
- this.visible = false;
- this.onCancel?.();
- })
- .margin({ right: 20 })
- Button("confirm")
- .onClick(() => {
- this.visible = false;
- this.onConfirm?.();
- })
- }
- .justifyContent(FlexAlign.Center)
- }
- .padding(24)
- }
- }
复制代码
- 提供方创建自定义组件Dialog,该组件是对弹窗组件MyCustomDialog的二次封装,便于外部使用。
- @Component
- export struct Dialog {
- // 监听外部传入的visible变量,visible值发生变化时触发onChange回调函数
- @Watch("onChange") @Link visible: boolean;
- onCancel?: () => void;
- onConfirm?: () => void;
- // 通过CustomDialogController的builder参数绑定弹窗组件MyCustomDialog
- private controller = new CustomDialogController({
- builder: MyCustomDialog({
- visible: $visible,
- onCancel: this.onCancel,
- onConfirm: this.onConfirm
- })
- })
- // visible的值发生变化时触发,若visible最新值为true通过弹窗控制器打开弹窗,否则关闭弹窗
- onChange() {
- if (this.visible) {
- this.controller.open()
- } else {
- this.controller.close()
- }
- }
- build() {
- }
- }
复制代码- @Entry
- @Component
- export struct Index {
- // 外部定义visible变量作为弹窗组件入参,控制弹窗显隐
- @State visible: boolean = false;
- @State openDialogCount: number = 0;
- @State cancelDialogCount: number = 0;
- build() {
- Column({ space: 20 }) {
- Text(`open dialog count: ${this.openDialogCount}`)
- .fontSize(20)
- Text(`cancel dialog count: ${this.cancelDialogCount}`)
- .fontSize(20)
- Button(this.visible ? 'hide' : 'show')
- // 点击修改visible变量后,visible的值可以被Dialog组件监听并响应
- .onClick(() => this.visible = !this.visible)
- // 通过双向绑定visible变量,实现外部控制弹窗
- Dialog({
- visible: $visible,
- onCancel: () => this.cancelDialogCount++,
- onConfirm: () => this.openDialogCount++
- })
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .height('100%')
- }
- }
复制代码 说明
1. 被@CustomDialog修饰的struct内部必须有CustomDialogController类型的属性。
2. 二次封装的Dialog组件紧张通过控制器控制弹窗,不必要任何界面,因此内部的build函数内容为空。
组件工厂类封装
场景形貌
如下图所示,团队A实现了一个组件工厂类并供外部使用,该类封装了多个组件。业务团队B在不同业务需求开辟场景下,希望通过组件名从组件工厂类实例获取对应的组件。比方,B团队向工厂实例中里传入组件名参数"Radio",可以获取到对应的Radio组件模板。
图4 组件工厂场景
对于该场景,思量使用Map结构将封装的各个组件存入,使用时通过Map的key值获取相应组件。对于单个组件的通报,目前系统提供了@Builder装饰器,该装饰器使得装饰后的函数遵循自定义组件build()函数语法规则。当@Builder装饰的函数作为参数通报使用时,由于其@Builder属性不会被通报,导致通报后获取的函数在UI校验时,无法识别其UI方法特性。 针对该问题,系统提供了wrapBuilder函数,将@Builder方法传入wrapBuilder函数中可实现组件的通报使用。通过组件工厂的封装和通报,避免了在调用方的build()函数内使用多个if else展示不同组件的写法。,实现了简便的组件封装情势。
实现方案
组件工厂以Map结构存储各种组件,此中key为组件名,value为WrappedBuilder对象。该对象支持赋值和通报,是系统提供的wrapBuilder函数的返回值。组件工厂场景的实现紧张包含以下步骤:
- 在组件工厂实现方,将必要工厂化的组件通过全局@Builder方法封装。
- @Builder
- function MyRadio() {
- Radio({ value: '1', group: 'radioGroup' })
- Radio({ value: '0', group: 'radioGroup' })
- }
- @Builder
- function MyCheckbox() {
- CheckboxGroup({ group: 'checkboxGroup' }) {
- Checkbox({ name: '1', group: 'checkboxGroup' })
- Checkbox({ name: '0', group: 'checkboxGroup' })
- }
- }
复制代码
- 在组件工厂实现方,将封装好的全局@Builder方法使用wrapBuilder函数包裹,并将返回值作为组件工厂Map的value值存入。全部组件存入后,将组件工厂导出供外部使用。
- // 定义组件工厂Map
- let factoryMap: Map<string, object> = new Map();
- // 将需要工厂化的组件存入到组件工厂中
- factoryMap.set('Radio', wrapBuilder(MyRadio));
- factoryMap.set('Checkbox', wrapBuilder(MyCheckbox));
- // 导出组件工厂
- export { factoryMap }
复制代码
- 在使用方,引入组件工厂并通过key值获取对应的WrappedBuilder对象。
- // 导入组件工厂,路径需按照实际位置导入,此处仅做示例参考
- import { factoryMap } from './ComponentFactory';
- // 通过组件工厂Map的key值获取对应的WrappedBuilder对象
- let myRadio: WrappedBuilder<[]> = factoryMap.get('Radio') as WrappedBuilder<[]>;
- let myCheckbox: WrappedBuilder<[]> = factoryMap.get('Checkbox') as WrappedBuilder<[]>;
复制代码
- 在使用方的组件build方法中,通过调用WrappedBuilder对象的builder方法获取具体组件。
- @Component
- struct Index {
- build() {
- Column() {
- // myRadio和myCheckbox是从组件工厂中获取的WrappedBuilder对象
- myRadio.builder();
- myCheckbox.builder();
- }
- }
- }
复制代码 说明
使用wrapBuilder方法有以下限制:
- wrapBuilder方法只支持传入全局@Builder方法。
- wrapBuilder方法返回的WrappedBuilder对象的builder属性方法只能在struct内部使用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |