一、开篇:为什么自定义组件是ArkUI的核心竞争力?
作为经历过移动开发从原生到跨平台变革的开发者,我深刻体会到:组件化程度决定了UI框架的生产力上限。
在ArkUI体系中,自定义组件不仅是代码复用的载体,更是实现「设计系统落地」和「业务逻辑封装」的关键基础设施。
本文将以登录组件为例,揭示从「能用」到「好用」再到「复用」的组件进化三阶段,涵盖状态管理、验证体系、样式系统等核心技术点。
二、基础版:从0到1实现登录组件
2.1 组件骨架搭建
// 基础登录组件(v1.0)
@Component
export struct LoginComponent {
// 内部状态:控制表单数据和验证信息
@State userName: string = "";
@State password: string = "";
@State errorMessage: string = "";
build() {
Column() {
// 标题区
Text("欢迎登录")
.fontSize(40)
.margin(20)
.fontWeight(FontWeight.Bold);
// 账号输入
Row() {
Text("账号:").fontSize(16);
TextInput({ placeholder: "请输入账号" })
.onChange(v => this.userName = v);
}.margin(10);
// 密码输入
Row() {
Text("密码:").fontSize(16);
TextInput({ placeholder: "请输入密码" })
.type(InputType.Password)
.onChange(v => this.password = v);
}.margin(10);
// 登录按钮
Button("登录")
.onClick(() => this.handleLogin())
.margin(10)
.height(44);
}
.padding(20)
.width("100%");
}
// 登录处理(硬编码逻辑)
private handleLogin() {
if (!this.userName || !this.password) {
this.errorMessage = "账号或密码不能为空";
return;
}
// 模拟登录请求
console.log("登录请求:", this.userName, this.password);
}
}
2.2 致命缺陷分析(生产环境不可用的三大原因)
- 样式固化:标题字体、输入框宽度等硬编码,无法适配不同设计规范
- 验证薄弱:仅做非空校验,且错误提示缺乏视觉反馈
- 逻辑耦合:登录处理直接写在组件内,无法复用至不同业务场景
三、进化版:可配置化组件的核心改造
3.1 组件属性系统设计(@Prop的正确打开方式)
// 可配置化登录组件(v2.0)
@Component
export struct LoginComponent {
// 基础配置(通过@Prop暴露)
@Prop title: string = "欢迎登录"; // 标题文案
@Prop inputWidth: string = "80%"; // 输入框宽度
@Prop primaryColor: string = "#2B7AFF"; // 品牌主色
@Prop space: number = 20; // 组件内间距
// 表单状态(内部维护)
@State userName: string = "";
@State password: string = "";
@State errorMessage: string = "";
// 样式对象(提取公共样式)
private titleStyle = {
fontSize: 40,
fontWeight: FontWeight.Bold,
margin: { top: 20, bottom: 20 },
color: this.primaryColor
};
private inputRowStyle = {
space: 10,
margin: { top: 10 }
};
3.2 表单验证体系升级(三重防护机制)
① 实时输入校验(即时反馈)
TextInput({ placeholder: "请输入密码" })
.type(InputType.Password)
.onChange(v => {
this.password = v;
// 密码强度实时校验(示例:至少6位)
if (v.length < 6 && v !== "") {
this.errorMessage = "密码需至少6位";
} else {
this.errorMessage = "";
}
});
② 提交前校验(完整规则)
private validateForm(): boolean {
const trimmedUser = this.userName.trim();
const trimmedPwd = this.password.trim();
// 账号格式校验(示例:支持手机号/邮箱,可扩展正则)
const isUserValid = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(trimmedUser) ||
/^1[3-9]\d{9}$/.test(trimmedUser);
if (!trimmedUser) {
this.errorMessage = "请输入账号";
return false;
} else if (!isUserValid) {
this.errorMessage = "账号格式不正确";
return false;
} else if (trimmedPwd.length < 6) {
this.errorMessage = "密码需至少6位";
return false;
}
return true;
}
③ 视觉反馈优化
// 错误提示组件(条件渲染)
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.textColor("#FF4D4F")
.margin({ top: 5, start: 20 })
.align(TextAlign.Start);
}
3.3 业务逻辑解耦(回调机制设计)
// 定义回调接口
interface LoginHandler {
(user: string, password: string): void;
}
// 暴露可覆盖的登录回调
export struct LoginComponent {
// 提供默认实现(方便快速试用)
onLogin: LoginHandler = (user, pwd) => {
console.log("默认登录逻辑:", user, pwd);
// 可在此添加Toast提示等通用逻辑
};
private handleLogin() {
if (this.validateForm()) {
this.onLogin(this.userName, this.password); // 触发回调
}
}
}
// 父组件调用(示例:集成真实登录API)
LoginComponent({
onLogin: (user, pwd) => {
// 调用真实登录接口
http.request({
url: "/api/login",
method: "POST",
data: { username: user, password: pwd }
}).then(res => {
if (res.success) {
router.pushUrl("pages/home"); // 路由跳转
} else {
this.errorMessage = "登录失败,请重试";
}
});
}
})
四、生产级组件的终极形态(可复用性设计三板斧)
4.1 可复用代码片段(@Reusable的正确用法)
// 提取公共输入行组件
@Reusable
private buildInputRow(
label: string,
placeholder: string,
type: InputType,
value: string,
onChange: (v: string) => void
) {
return Row({ space: this.inputRowStyle.space })
.margin(this.inputRowStyle.margin) {
Text(label)
.fontSize(16)
.width("20%"); // 固定标签宽度,提升布局稳定性
TextInput({ placeholder })
.type(type)
.width(this.inputWidth)
.value(value)
.onChange(onChange);
};
}
// 在build中重复使用
this.buildInputRow("账号:", "请输入账号/手机号/邮箱", InputType.Normal, this.userName, v => this.userName = v);
this.buildInputRow("密码:", "请输入登录密码", InputType.Password, this.password, v => this.password = v);
4.2 样式系统设计(主题化支持)
// 定义样式接口
interface LoginStyle {
titleSize?: number;
inputLabelColor?: string;
buttonBgColor?: string;
errorTextSize?: number;
}
// 支持传入完整样式对象
export struct LoginComponent {
@Prop style: LoginStyle = {
titleSize: 40,
inputLabelColor: "#333",
buttonBgColor: "#2B7AFF",
errorTextSize: 14
};
// 使用样式对象
Text(this.title)
.fontSize(this.style.titleSize)
.color(this.primaryColor);
}
4.3 性能与可维护性优化
- 状态最小化:仅将驱动UI的变量标记为@State,其他使用普通变量
- 方法私有化:通过private修饰内部方法(如validateForm),明确组件边界
- 类型安全:为所有@Prop和回调函数添加TypeScript类型注解,提前暴露接口错误
五、组件扩展路线图
5.1 近期可实现功能(1-2周落地)
| 功能点 | 技术实现 | 价值收益 |
|---|---|---|
| 加载状态 | 添加@State isLoading,控制按钮禁用/loading动画 | 防止重复提交,提升交互体验 |
| 记住密码 | 结合LocalStorage实现历史账号存储 | 高频用户登录效率提升40%+ |
| 自定义插槽 | 使用@Slot允许插入第三方登录按钮 | 支持多登录方式扩展 |
5.2 中长期规划(组件库级能力)
- 主题系统:通过全局样式对象实现暗黑/亮色主题一键切换
- 国际化支持:接收i18n对象动态渲染多语言文案(title/placeholder等)
- 无障碍适配:添加accessibilityLabel、键盘导航支持等A11Y特性
- 单元测试:针对表单验证逻辑编写ETS单测,保障修改安全性
六、开发者经验谈:高质量组件的设计黄金法则
- 单一职责原则:组件只做一件事(登录组件不处理路由跳转)
- 接口稳定性:通过@Prop默认值和可选参数,确保旧版本兼容性
- 渐进式增强:基础版本提供最小可用集,复杂功能通过扩展参数实现
- 文档即代码:为每个@Prop添加JSDoc注释,说明用途、类型、默认值
作为团队技术负责人,我建议在组件开发中引入「组件评审机制」,重点审查:
- 可配置参数是否超过10个(过多需考虑分组)
- 内部状态是否完全自包含(不依赖外部上下文)
- 回调函数是否提供默认实现(降低使用门槛)
七、总结:从组件思维到架构思维的跨越
本文通过登录组件的进化过程,展示了ArkUI自定义组件的核心开发范式:
- 基础实现:掌握@Component、@State、build()的核心用法
- 可配置化:通过@Prop暴露样式和行为参数,实现「一次开发,多处使用」
- 逻辑解耦:利用回调机制分离UI组件与业务逻辑,提升可测试性
- 生态构建:规划扩展点,为未来接入设计系统、国际化、无障碍等能力预留接口
记住:优秀的组件不是写出来的,而是进化出来的。
如果大家想考取鸿蒙开发者认证的,欢迎加入我的专属考试链接中:developer.huawei.com/consumer/cn…
从第一个项目的「能用」版本开始,持续收集使用反馈,逐步抽象公共逻辑,最终形成团队级的组件资产。
这不仅是代码复用的过程,更是技术沉淀和团队效能提升的重要实践。
我是Feri。关注我,获取更多鸿蒙开发、程序员成长干货。让我们在技术进阶的路上,少走弯路,快速成长!