HarmonyOS Development Guide: ArkUI Component Encapsulation Patterns
Overview
During application development with HarmonyOS, encapsulating ArkUI components becomes essential for business reuse. Based on practical migration scenarios, three typical component encapsulation patterns have emerged:
- Public Component Encapsulation: Wrapping system components to provide unified styling according to UX guidelines, such as login buttons or dialog buttons for shared component libraries.
- Dialog Component Encapsulation: Internal encapsulation of dialog content with controllers, allowing callers to control visibility through state variables.
- Component Factory Pattern: Factory classes that expose all components, enabling callers to retrieve specific components via parameters.
This guide details these scenarios and their implementation approaches.
Public Component Encapsulation
Scenario Description
Different business scenarios often require ArkUI components with identical functionality and styling. For instance, a login page submit button and a shopping page checkout button might share the same visual design. The common approach involves extracting shared logic and encapsulating it as a custom component in a shared library.
Taking the Button component as an example, when multiple business scenarios need buttons with consistent styling, the generic logic gets wrapped into a custom component called PrimaryAction. This component defines common fontSize and fontColor properties. When integrating this component into a shared library as an extended Button, developers must enumerate all Button attributes to enable chain-style property access while maintaining Button's native capabilities.
@Component
struct PrimaryAction {
@Prop label: string = '';
@Prop active: boolean = true;
// ... enumerate all Button-specific properties
build() {
Button(this.label)
.fontSize(14)
.fontColor('#FFFFFF')
.active(this.active)
.backgroundColor('#007AFF')
// ... assign other Button-specific properties
}
}
When using the PrimaryAction component, modifications to display content and interaction effects require passing parameters:
@Component
struct MainScreen {
build() {
PrimaryAction({ label: 'Submit', active: true })
}
}
Limitations of This Approach
The parameter-based encapsulation method presents several challenges:
- Inconsistent API with System Components: System components use chain-style property assignment, while this approach requires property configuration through parameters.
- Excessive Parameters: Using all system component properties necessitates enumerating each as a paraemter in the custom component, creating verbose usage.
- Maintenance Overhead: When system component properties change, custom components require corresponding updates.
Implementation Solution
To address these limitations, ArkTS provides the attributeModifier property for each system component. This approach separates property configuration into a separate AttributeModifier interface implementation class, enabling property extension through custom classes.
Solution: Exposing AttributeModifier via Shared Components
Using the system Button component as an example, the implementation steps are:
- Create a reusable custom component in the shared library that accepts external
attributeModifierparameters:
@Component
export struct ActionButton {
@Prop label: string = '';
@Prop modifier: AttributeModifier<ButtonAttribute> | null = null;
build() {
Button(this.label)
.attributeModifier(this.modifier)
}
}
- Callers implement the
AttributeModifierinterface to customize properties:
class CustomButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.fontSize(16)
.fontColor('#FFFFFF')
.backgroundColor('#34C759')
.borderRadius(8)
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#248A3D')
}
}
@Component
struct UserInterface {
private buttonModifier = new CustomButtonModifier();
build() {
ActionButton({
label: 'Confirm',
modifier: this.buttonModifier
})
}
}
This approach provides several advantages:
- Consistent API: Maintains chain-style property assignment similar to native system components
- Flexible Customization: Callers can modify properties without modifying the encapsulated component
- Separation of Concerns: Component logic and styling remain independent
- Better Maintainability: Property changes in system components don't require updates to custom components