【2026最新】鸿蒙NEXT状态管理实战:培训班管理系统数据流转全攻略
鸿蒙开发中状态混乱、UI不刷新、数据不同步?本文用15分钟带你彻底搞懂@State、@Prop、@Link、@Provide/Consume四大核心装饰器,附完整培训班管理系统实战代码和踩坑记录,让你的鸿蒙App数据流转从此丝滑!
一、学员信息状态管理
1.1 @State装饰器详解
@State装饰器用于管理组件内部状态,当状态变化时,UI会自动刷新。
// components/student/StudentManager.ets
@Component
struct StudentManager {
@State studentList: Student[] = [];
@State selectedStudent: Student | null = null;
@State isLoading: boolean = false;
@State searchKeyword: string = '';
async aboutToAppear() {
await this.loadStudents();
}
async loadStudents() {
this.isLoading = true;
try {
this.studentList = await StudentService.getAllStudents();
} catch (error) {
console.error('加载学员列表失败:', error);
} finally {
this.isLoading = false;
}
}
async searchStudents(keyword: string) {
this.searchKeyword = keyword;
if (keyword.length === 0) {
await this.loadStudents();
return;
}
this.isLoading = true;
try {
this.studentList = await StudentService.searchStudents(keyword);
} catch (error) {
console.error('搜索学员失败:', error);
} finally {
this.isLoading = false;
}
}
build() {
Column() {
// 搜索栏
SearchBar({
searchText: $searchKeyword,
searchCallback: (keyword: string) => {
this.searchStudents(keyword);
}
})
// 学员列表
if (this.isLoading) {
LoadingView()
} else {
List({ space: 12 }) {
ForEach(this.studentList, (student: Student) => {
ListItem() {
StudentCard({
student: student,
isSelected: this.selectedStudent?.id === student.id,
clickCallback: (selected: Student) => {
this.selectedStudent = selected;
}
})
}
}, (student: Student) => student.id)
}
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
}
}
1.2 @ObjectLink装饰器详解
@ObjectLink装饰器用于监听对象内部属性的变化,适用于复杂对象的状态管理。
// model/StudentObserved.ets
@Observed
class StudentObserved implements Student {
id: string = '';
name: string = '';
phone: string = '';
email: string = '';
gender: number = 0;
birthday: string = '';
address: string = '';
avatar: string = '';
age: number = 0;
courses: string[] = [];
status: number = 0;
createTime: string = '';
updateTime: string = '';
constructor(student?: Student) {
if (student) {
Object.assign(this, student);
}
}
updateName(name: string) {
this.name = name;
this.updateTime = Utils.formatDate(new Date());
}
updatePhone(phone: string) {
this.phone = phone;
this.updateTime = Utils.formatDate(new Date());
}
addCourse(courseId: string) {
if (!this.courses.includes(courseId)) {
this.courses.push(courseId);
this.updateTime = Utils.formatDate(new Date());
}
}
removeCourse(courseId: string) {
const index = this.courses.indexOf(courseId);
if (index > -1) {
this.courses.splice(index, 1);
this.updateTime = Utils.formatDate(new Date());
}
}
}
// components/student/StudentDetailCard.ets
@Component
export struct StudentDetailCard {
@ObjectLink student: StudentObserved;
editCallback: () => void = () => {};
build() {
Column() {
// 头像区域
Stack() {
Image(this.student.avatar || $r('app.media.ic_default_avatar'))
.width(80)
.height(80)
.borderRadius(40)
// 编辑按钮
Image($r('app.media.ic_edit'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
.backgroundColor('#007DFF')
.borderRadius(12)
.position({ x: 56, y: 56 })
.onClick(() => {
this.editCallback();
})
}
.width(80)
.height(80)
// 姓名
Text(this.student.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ top: 12 })
// 状态标签
Text(this.student.status === 0 ? '在读' : '已毕业')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor(this.student.status === 0 ? '#52C41A' : '#999999')
.borderRadius(4)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.margin({ top: 8 })
// 信息列表
Column() {
this.InfoItem('手机号', this.student.phone)
this.InfoItem('邮箱', this.student.email)
this.InfoItem('性别', this.student.gender === 1 ? '男' : '女')
this.InfoItem('年龄', `${this.student.age}岁`)
this.InfoItem('地址', this.student.address)
this.InfoItem('已报课程', `${this.student.courses.length}门`)
}
.width('100%')
.margin({ top: 16 })
.padding(16)
.backgroundColor('#F8F8F8')
.borderRadius(8)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
@Builder
InfoItem(label: string, value: string) {
Row() {
Text(label)
.fontSize(14)
.fontColor('#666666')
.width(80)
Text(value)
.fontSize(14)
.fontColor('#333333')
.layoutWeight(1)
}
.width('100%')
.height(40)
.borderWidth({ bottom: 1 })
.borderColor('#F0F0F0')
}
}
二、课程数据状态流转
2.1 @Prop装饰器详解
@Prop装饰器用于父子组件之间的单向数据传递,父组件数据变化会同步到子组件。
// components/course/CourseList.ets
@Component
export struct CourseList {
@State courseList: Course[] = [];
@State selectedFilter: string = 'all';
build() {
Column() {
// 筛选标签
this.FilterTabs()
// 课程列表
List({ space: 12 }) {
ForEach(this.courseList, (course: Course) => {
ListItem() {
CourseCard({
course: course,
isEnrolled: this.isCourseEnrolled(course.id),
enrollCallback: (courseId: string) => {
this.handleEnroll(courseId);
}
})
}
}, (course: Course) => course.id)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
@Builder
FilterTabs() {
Row() {
this.FilterTab('all', '全部')
this.FilterTab('not_started', '未开始')
this.FilterTab('in_progress', '进行中')
this.FilterTab('ended', '已结束')
}
.width('100%')
.height(48)
.backgroundColor('#FFFFFF')
.justifyContent(FlexAlign.SpaceAround)
}
@Builder
FilterTab(filter: string, label: string) {
Column() {
Text(label)
.fontSize(14)
.fontColor(this.selectedFilter === filter ? '#007DFF' : '#666666')
.fontWeight(this.selectedFilter === filter ? FontWeight.Bold : FontWeight.Normal)
// 选中指示器
if (this.selectedFilter === filter) {
Divider()
.width(24)
.height(2)
.color('#007DFF')
.margin({ top: 4 })
}
}
.onClick(() => {
this.selectedFilter = filter;
this.filterCourses();
})
}
filterCourses() {
// 根据筛选条件过滤课程
}
isCourseEnrolled(courseId: string): boolean {
// 检查是否已报名
return false;
}
handleEnroll(courseId: string) {
// 处理报名逻辑
}
}
// components/course/CourseCard.ets
@Component
export struct CourseCard {
@Prop course: Course;
@Prop isEnrolled: boolean = false;
enrollCallback: (courseId: string) => () => void = () => () => {};
build() {
Column() {
// 课程封面
Image(this.course.coverImage || $r('app.media.ic_default_course'))
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 })
// 课程信息
Column() {
// 课程名称
Text(this.course.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 课程描述
Text(this.course.description)
.fontSize(12)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 4 })
// 课程信息行
Row() {
// 授课老师
Row() {
Image($r('app.media.ic_teacher'))
.width(16)
.height(16)
.fillColor('#999999')
Text(this.course.teacher)
.fontSize(12)
.fontColor('#999999')
.margin({ left: 4 })
}
// 报名人数
Row() {
Image($r('app.media.ic_users'))
.width(16)
.height(16)
.fillColor('#999999')
Text(`${this.course.currentStudents}/${this.course.maxStudents}`)
.fontSize(12)
.fontColor('#999999')
.margin({ left: 4 })
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ top: 8 })
// 价格和状态
Row() {
Text(`¥${this.course.price}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FF6B6B')
if (this.isEnrolled) {
Text('已报名')
.fontSize(12)
.fontColor('#52C41A')
.backgroundColor('#F6FFED')
.borderRadius(4)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
} else {
Button('报名')
.width(64)
.height(28)
.fontSize(12)
.backgroundColor('#007DFF')
.borderRadius(14)
.onClick(() => {
this.enrollCallback(this.course.id);
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
.margin({ top: 8 })
}
.padding(12)
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.shadow({
radius: 4,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
}
}
三、报名流程状态控制
3.1 @Link装饰器详解
@Link装饰器用于父子组件之间的双向数据绑定,子组件修改会同步到父组件。
// components/enrollment/EnrollmentForm.ets
@Component
export struct EnrollmentForm {
@Link selectedStudentId: string;
@Link selectedCourseId: string;
@Link remark: string;
@Link isSubmitting: boolean;
private studentList: Student[] = [];
private courseList: Course[] = [];
async aboutToAppear() {
await this.loadData();
}
async loadData() {
this.studentList = await StudentService.getAllStudents();
this.courseList = await CourseService.getAllCourses();
}
build() {
Column() {
// 选择学员
this.SelectorSection('选择学员', this.studentList, this.selectedStudentId, (id: string) => {
this.selectedStudentId = id;
})
// 选择课程
this.SelectorSection('选择课程', this.courseList, this.selectedCourseId, (id: string) => {
this.selectedCourseId = id;
})
// 备注输入
Column() {
Text('备注信息')
.fontSize(14)
.fontColor('#666666')
.alignSelf(ItemAlign.Start)
TextInput({ placeholder: '请输入备注信息(选填)' })
.width('100%')
.height(80)
.backgroundColor('#F8F8F8')
.borderRadius(8)
.margin({ top: 8 })
.onChange((value: string) => {
this.remark = value;
})
}
.width('100%')
.margin({ top: 16 })
}
.width('100%')
.padding(16)
}
@Builder
SelectorSection<T extends { id: string; name: string }>(
title: string,
list: T[],
selectedId: string,
onSelect: (id: string) => void
) {
Column() {
Text(title)
.fontSize(14)
.fontColor('#666666')
.alignSelf(ItemAlign.Start)
List() {
ForEach(list, (item: T) => {
ListItem() {
Row() {
Text(item.name)
.fontSize(14)
.fontColor(selectedId === item.id ? '#007DFF' : '#333333')
.fontWeight(selectedId === item.id ? FontWeight.Bold : FontWeight.Normal)
if (selectedId === item.id) {
Image($r('app.media.ic_check'))
.width(16)
.height(16)
.fillColor('#007DFF')
}
}
.width('100%')
.height(48)
.padding({ left: 12, right: 12 })
.backgroundColor(selectedId === item.id ? '#E6F7FF' : '#FFFFFF')
.borderRadius(8)
.margin({ top: 4 })
.onClick(() => {
onSelect(item.id);
})
}
}, (item: T) => item.id)
}
.width('100%')
.height(200)
.margin({ top: 8 })
}
.width('100%')
.margin({ top: 16 })
}
}
// pages/EnrollmentPage.ets
@Entry
@Component
struct EnrollmentPage {
@State selectedStudentId: string = '';
@State selectedCourseId: string = '';
@State remark: string = '';
@State isSubmitting: boolean = false;
build() {
Column() {
// 标题栏
TitleBar({ title: '学员报名', showBack: true })
// 表单
EnrollmentForm({
selectedStudentId: $selectedStudentId,
selectedCourseId: $selectedCourseId,
remark: $remark,
isSubmitting: $isSubmitting
})
// 提交按钮
Button(this.isSubmitting ? '提交中...' : '确认报名')
.width('90%')
.height(48)
.backgroundColor(this.canSubmit() ? '#007DFF' : '#CCCCCC')
.borderRadius(24)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24, top: 'auto' })
.enabled(this.canSubmit() && !this.isSubmitting)
.onClick(() => {
this.submitEnrollment();
})
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
canSubmit(): boolean {
return this.selectedStudentId.length > 0 && this.selectedCourseId.length > 0;
}
async submitEnrollment() {
if (!this.canSubmit()) return;
this.isSubmitting = true;
try {
const enrollment: Enrollment = {
id: Utils.generateId(),
studentId: this.selectedStudentId,
courseId: this.selectedCourseId,
enrollTime: Utils.formatDate(new Date()),
status: 0,
remark: this.remark,
createTime: Utils.formatDate(new Date()),
updateTime: Utils.formatDate(new Date())
};
await EnrollmentService.createEnrollment(enrollment);
Utils.showToast('报名成功');
router.back();
} catch (error) {
Utils.showToast('报名失败,请重试');
} finally {
this.isSubmitting = false;
}
}
}
四、@State、@Prop、@Link实战应用
4.1 状态管理最佳实践
// viewmodel/StudentViewModel.ets
export class StudentViewModel {
@State studentList: Student[] = [];
@State selectedStudent: Student | null = null;
@State isLoading: boolean = false;
@State errorMessage: string = '';
private studentService: StudentService;
constructor() {
this.studentService = new StudentService();
}
async loadStudents(): Promise<void> {
this.isLoading = true;
this.errorMessage = '';
try {
this.studentList = await this.studentService.getAllStudents();
} catch (error) {
this.errorMessage = '加载学员列表失败,请重试';
console.error('加载学员列表失败:', error);
} finally {
this.isLoading = false;
}
}
async searchStudents(keyword: string): Promise<void> {
if (keyword.length === 0) {
await this.loadStudents();
return;
}
this.isLoading = true;
this.errorMessage = '';
try {
this.studentList = await this.studentService.searchStudents(keyword);
} catch (error) {
this.errorMessage = '搜索失败,请重试';
console.error('搜索学员失败:', error);
} finally {
this.isLoading = false;
}
}
async deleteStudent(studentId: string): Promise<boolean> {
try {
await this.studentService.deleteStudent(studentId);
this.studentList = this.studentList.filter(s => s.id !== studentId);
if (this.selectedStudent?.id === studentId) {
this.selectedStudent = null;
}
Utils.showToast('删除成功');
return true;
} catch (error) {
Utils.showToast('删除失败,请重试');
return false;
}
}
selectStudent(student: Student): void {
this.selectedStudent = student;
}
clearSelection(): void {
this.selectedStudent = null;
}
}
4.2 状态管理流程图
┌─────────────────────────────────────────────────────────────┐
│ 状态管理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ @State │───▶│ @Prop │───▶│ @Link │ │
│ │ 组件内部 │ │ 父传子 │ │ 双向绑定 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ UI刷新 │ │ UI刷新 │ │ UI刷新 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
五、跨组件数据共享最佳实践
5.1 @Provide/@Consume装饰器详解
@Provide/@Consume装饰器用于跨组件层级的数据共享,无需逐层传递。
// context/StudentContext.ets
@Observed
export class StudentContext {
studentList: Student[] = [];
selectedStudent: Student | null = null;
isLoading: boolean = false;
updateStudentList(list: Student[]) {
this.studentList = list;
}
selectStudent(student: Student) {
this.selectedStudent = student;
}
clearSelection() {
this.selectedStudent = null;
}
}
// pages/StudentManagement.ets
import { StudentContext } from '../context/StudentContext';
@Entry
@Component
struct StudentManagement {
@Provide('studentContext') context: StudentContext = new StudentContext();
build() {
Column() {
// 标题栏
TitleBar({ title: '学员管理' })
// 内容区域
Row() {
// 左侧列表
StudentListPanel()
.width('40%')
.height('100%')
// 右侧详情
StudentDetailPanel()
.layoutWeight(1)
.height('100%')
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
// components/student/StudentListPanel.ets
@Component
export struct StudentListPanel {
@Consume('studentContext') context: StudentContext;
build() {
Column() {
// 搜索栏
SearchBar({ searchText: '', searchCallback: (keyword: string) => {
this.searchStudents(keyword);
}})
// 学员列表
if (this.context.isLoading) {
LoadingView()
} else {
List({ space: 8 }) {
ForEach(this.context.studentList, (student: Student) => {
ListItem() {
StudentCard({
student: student,
isSelected: this.context.selectedStudent?.id === student.id,
clickCallback: (selected: Student) => {
this.context.selectStudent(selected);
}
})
}
}, (student: Student) => student.id)
}
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
}
async searchStudents(keyword: string) {
// 搜索逻辑
}
}
// components/student/StudentDetailPanel.ets
@Component
export struct StudentDetailPanel {
@Consume('studentContext') context: StudentContext;
build() {
Column() {
if (this.context.selectedStudent) {
// 显示学员详情
StudentDetailCard({
student: this.context.selectedStudent,
editCallback: () => {
this.editStudent();
}
})
} else {
// 空状态
EmptyView({ message: '请选择学员查看详情' })
}
}
.width('100%')
.height('100%')
.padding(16)
}
editStudent() {
// 编辑学员逻辑
}
}
5.2 状态管理架构图
┌─────────────────────────────────────────────────────────────┐
│ 状态管理架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ @Provide │ │
│ │ (根组件) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ @Consume │ │ @Consume │ │ @Consume │ │
│ │ (列表组件) │ │ (详情组件) │ │ (编辑组件) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ @State │ │ @State │ │ @State │ │
│ │ (局部状态) │ │ (局部状态) │ │ (局部状态) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
六、踩坑记录
6.1 @State状态不更新
问题:@State装饰的数组,修改元素后UI不刷新。
原因:直接修改数组元素不会触发UI更新。
解决方案:
// 错误方式
this.list[0].name = '新名称';
// 正确方式
this.list = [...this.list];
this.list[0].name = '新名称';
6.2 @Prop单向数据流
问题:子组件修改@Prop属性后,父组件数据未同步。
原因:@Prop是单向数据流,子组件修改不会影响父组件。
解决方案:
// 使用@Link实现双向绑定
@Link selectedId: string;
// 或者使用回调函数
selectCallback: (id: string) => void = () => {};
6.3 @Provide/@Consume作用域
问题:@Provide/@Consume在不同页面之间不生效。
原因:@Provide/@Consume只在同一组件树内有效。
解决方案:
// 使用AppStorage实现全局状态
AppStorage.SetOrCreate('globalState', new GlobalState());
// 在组件中使用
@StorageLink('globalState') globalState: GlobalState;
七、总结与预告
本文要点回顾
-
@State装饰器:管理组件内部状态,触发UI刷新
-
@ObjectLink装饰器:监听对象内部属性变化
-
@Prop装饰器:父子组件单向数据传递
-
@Link装饰器:父子组件双向数据绑定
-
@Provide/@Consume装饰器:跨组件层级数据共享
-
最佳实践:状态管理架构设计和常见问题解决
下期预告
下期我们将深入讲解数据持久化篇,包括:
-
学员信息本地存储
-
课程数据管理
-
网络请求与API对接
-
数据缓存策略
互动引导
如果本文对你有帮助,请点赞、收藏、关注!有任何问题欢迎在评论区留言,我会及时回复。
系列文章导航:
-
第1篇:项目架构篇
-
第2篇:UI界面篇
-
第3篇:状态管理篇(本文)
-
第4篇:数据持久化篇
-
第5篇:性能优化篇
更多推荐



所有评论(0)