☰
OpenHarmony上Flutter实战:添加房间功能与Provider状态管理
2026/10/9 5:58:23 网站建设 项目流程

做OpenHarmony应用开发有一段时间了,最大的感受就是生态里的现成组件太少,很多在Android和iOS上很轻松的事到了这边都得从头折腾。这次团队接到一个“家具购买记录App”的项目,需求很明确:用户把家里买的每一件家具登记下来,包括价格、购买日期、保修期、卖家信息,然后按房间维度去归类查看——客厅花了多少钱,卧室买了哪些东西,一目了然。我第一反应就是用Flutter来跑,而不是一头扎进ArkTS里从零写UI。原因很简单:Flutter的组件库成熟、布局语法我用得熟、状态管理方案也比ArkTS那边顺手得多。

这个项目的核心功能之一就是“添加房间”。别看它只是一个简单的表单,真正动手做的时候才发现,它牵扯到数据模型设计、表单校验、本地持久化、组件通信、页面联动一堆问题。这篇文章就围绕这个功能,把从技术选型到代码落地的完整过程记录下来。适合正在做Flutter跨端开发、尤其是准备在OpenHarmony设备上跑Flutter应用的朋友参考,也适合想搞清楚Provider到底怎么用在真实项目里的同学。

1. 项目背景与技术选型分析

1.1 为什么选Flutter而不是ArkTS写OpenHarmony应用

OpenHarmony官方主推的UI框架是ArkTS,搭配ArkUI声明式语法,和Flutter在形态上确实有点像。但如果你像我一样长期用Flutter,对比下来体会非常明显:ArkTS的开发资料少、第三方库更少、编辑器插件和调试工具都还在快速迭代期。遇到一个TabBar联动或者复杂表单校验的问题,搜半天可能都找不到一个能直接抄的答案。

Flutter的优势在于它有一套完整的渲染引擎,用的是自绘UI的方式,不完全依赖系统原生的控件树。把Flutter适配到OpenHarmony上跑,UI层面基本能做到一次编写到处运行,不需要为不同平台分别维护界面代码。而且Flutter的包管理用的是pub.dev,生态里光是表单、状态管理、数据库、图表这类常用库就比OpenHarmony现成的方案多一个数量级。对于业务类的工具App,缩短开发周期是实打实的价值。

1.2 家具购买记录App的整体需求与“添加房间”的定位

这个App的整体结构是典型的“分层记账”思路:最顶层是房间列表,每个房间下面挂家具购买记录。房间是一个维度概念,先有房间,然后才能在房间里添加家具。所以“添加房间”是用户进入App后最先接触的功能之一,它的体验好不好直接影响用户对整体产品的第一印象。

当时产品经理给的需求很简单:用户可以新建一个房间,填房间名称,选一个图标,偶尔会填一下预算。但这个看似简单的功能,背后至少有四个技术点要解决:

第一,房间数据要怎么建模,字段预留到哪个程度,才能让后续“按房间统计总花销”“按房间查看保修清单”这些功能不被数据模型卡住。

第二,表单校验要怎么做,名称不能为空、不能超过十个字、不能重复,这些规则要在用户点保存的那一刻同时生效。

第三,数据写到哪里。本地存储还是内存?App退出后再打开,房间数据不能丢。

第四,添加完房间之后,主页的房间列表要立刻刷新,房间数统计卡片也要跟着变。这就是组件通信和状态管理的问题了。

我建议任何人在动手写这类功能之前,先把这四个问题在脑子里过一遍,不要急着写代码。我在实际项目中见过太多人一上来就写一个StatefulWidget,用setState更新数据,写到后面页面一多就收不住了。

1.3 开发环境与版本选型

我在这个项目里用的环境是Windows 11 + DevEco Studio,Flutter版本是3.16系列,OpenHarmony SDK用的API 10。如果你用的是更新的版本,操作步骤差别不会太大,但要注意API Level不同,部分权限声明和组件初始化方式会有调整。

有一个很关键的点:要在OpenHarmony设备上跑Flutter应用,不是装一个普通Flutter SDK就能直接打包的。OpenHarmony本身没有兼容Flutter的原生运行时,需要配合专门的OpenHarmony Flutter SDK包,它里面包含了适配层代码,让Flutter引擎能跑在OHOS的Ability框架之上。装好之后,工程结构会同时存在一个Flutter模块和OpenHarmony的Ability模块,编译时会先把Flutter代码编译成so文件,再通过一个壳工程打包成hap。这个流程第一次跑通时有点绕,但跑一次成功之后后面就顺利了。

2. 环境搭建与工程结构设计

2.1 Flutter在OpenHarmony上的环境准备

我把环境预装的坑在这简单梳理一下,照着做能省不少时间。首先你需要装好DevEco Studio和DevEco的Command Line Tools,这个在OpenHarmony开发里是基础,类似Android开发里的Android Studio和SDK Manager。

然后拉取Flutter for OpenHarmony版本的SDK并配置到环境变量里。配置完成后,在命令行执行:

flutter doctor

到这里要注意,doctor的输出里如果提示Android toolchain报错,不用太紧张。因为我们在OpenHarmony上开发,不依赖Android SDK,只要能看到Flutter本身和Dart SDK正常就能继续。我实际验证下来,只要没出现大面积红色报错,就可以初始化工程。

初始化工程用flutter create即可。扛完初始化之后,记得在工程根目录打开pubspec.yaml,加入OpenHarmony适配需要的依赖。这里我不展开列具体的版本号,因为不同时期适配包的版本差异比较大,建议你以拉取的SDK里附带的示例工程为参照,把示例工程里的依赖清单复制过来,这是最稳的做法。

2.2 创建工程并跑通“一个Flutter页面在OpenHarmony设备上显示”

很多人遇到的第一个问题是:工程建好了,代码也写了,但不知道怎么跑到OpenHarmony设备上。这里的关键步骤有三个:

第一步,确认开发者的设备已开启开发者模式,并且用USB连接到电脑。设备上需要安装hap包,最直接的方式是用DevEco Studio安装。

第二步,修改工程的签名配置。OpenHarmony设备上安装应用,和设备建立信任关系是必须的。如果没有配置签名,编译的时候会报“signature verification failed”,打包出的hap传不到设备上。这一步如果搞不定,后面就跑不起来,建议第一次做的时候先创建一个OpenHarmony工程,按里面配置证书的方法把签名文件生成好,再回过来用在Flutter工程上。

第三步,运行flutter build hap,如果构建成功,再用DevEco Studio打开壳工程安装到设备。我第一次操作的时候花了一个下午,一直在各种版本报错里绕,后来发现就是签名文件没配对。

2.3 项目目录结构与状态管理方案选型

工程跑通之后,就要考虑代码怎么组织了。这个项目我用了最简单的分层结构:

lib/ models/ # 数据模型 providers/ # 状态管理 views/ # 页面 widgets/ # 可复用组件 utils/ # 工具函数 services/ # 数据持久化、平台通道等

选择Provider作为状态管理方案,是我有意识决定的。原因有两条:第一,项目体量不大,房间、家具、统计这几个模块的状态都是树状关联关系,用Provider这种基于InheritedWidget的机制已经足够,不需要引入Redux或者Bloc这样的事件流框架增加复杂度。第二,Flutter官方文档里Provider的例子非常多,团队其他成员接手时学习成本低。后面我还会花一整节来讲Provider在这个项目里具体是怎么用的,因为很多人在小项目上用了Provider,但遇到跨页面联动的时候还是容易犯错误。

3. “添加房间”功能的核心实现

3.1 数据模型设计:Room字段怎么定

写代码之前先把模型定义清楚。我定义的Room模型不是只有名称和图标那么简单,因为后续要支撑房间内家具统计,我给它加了一个budget字段用于预算管理,用int类型,单位是分,避免浮点数计算的精度问题。

class Room { final String id; final String name; final String iconCode; // 存储IconData的codePoint final int budget; // 预算,单位分 final DateTime createdAt; Room({ required this.id, required this.name, required this.iconCode, required this.budget, required this.createdAt, }); Map<String, dynamic> toJson() => { 'id': id, 'name': name, 'iconCode': iconCode, 'budget': budget, 'createdAt': createdAt.millisecondsSinceEpoch, }; factory Room.fromJson(Map<String, dynamic> json) => Room( id: json['id'] as String, name: json['name'] as String, iconCode: json['iconCode'] as String, budget: json['budget'] as int, createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int), ); }

id用什么生成?千万不要自增整数然后存本地,后面做数据同步或者备份恢复时会非常痛苦。我直接用DateTime.now().microsecondsSinceEpoch加上一个随机因子转成字符串,保证唯一性。iconCode我存字符串是因为FontFamily中的图标字体在不同版本Flutter上编出来的codePoint可能不同,以字符串形式存更安全。

3.2 表单交互设计:底部弹窗还是整页表单

添加房间的入口是主页右上角的“添加”按钮。点击后有两种交互方案:一种是跳转新页面,另一种是弹出ModalBottomSheet。我选择了底部弹窗,理由是单个房间的表单字段少,一个弹窗正好放下,用户不需要来回跳转,视觉上也能保持上下文连贯。

弹窗的内部布局:

showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => const AddRoomSheet(), );

isScrollControlled设置为true非常重要。如果不设置,弹窗默认高度只占屏幕的一小段,键盘弹起来时会直接把弹窗顶飞出去,输入框被键盘盖住,点不到“保存”按钮。设置之后,弹窗可以自己调整高度,再配合AnimatedPadding监听MediaQuery.of(context).viewInsets.bottom,让底部内容跟随键盘抬起,体验才好。

表单内部用了TextFormField配合Form和GlobalKey 来做校验。房间名称的校验规则是必填、最长10个字、不能和其他房间重名。这里有个细节:重名校验不能只判断当前列表里的数据,如果用户修改了已有房间的名称,也要把自身排除出去,否则会永远提示“房间已存在”。这个逻辑我放在Validator里做了一个参数传入:

validator: (value) { if (value == null || value.trim().isEmpty) { return '房间名称不能为空'; } if (value.trim().length > 10) { return '房间名称不能超过10个字'; } if (roomProvider.isNameTaken(value.trim(), excludeId: editingRoom?.id)) { return '已存在同名房间'; } return null; }

预算字段我用了一个简单的数字输入框,配合inputFormatters只允许输入数字,单位是元,提交时再换算成分。不需要做太复杂的联动计算,够用就行。

3.3 持久化方案:从SharedPreferences到SQLite的渐进

写数据这一步我一开始用的是shared_preferences,直接把房间列表序列化成JSON字符串存起来。这种方式实现快,适合原型验证,但实际用下来有两个问题:

第一,JSON字符串更新时要先读整份、改完再整份写回,随着房间增多和家具记录增多,性能会越来越差。第二,后续要按房间聚合统计家具金额或者按日期排序筛选,用JSON存储就只能在内存里全量过滤,代码写起来别扭。

所以这个项目我最终选用了sqflite来做真正的持久化。针对OpenHarmony环境,我实际用的是sqlite3的FFI方式封装,表和索引提前建好,写入和查询都走SQL,后续做统计功能时会省非常多的事。

class DatabaseHelper { Database? _database; Future<Database> get database async { _database ??= await _initDb(); return _database!; } Future<Database> _initDb() async { final dbPath = await getDatabasesPath(); return openDatabase( p.join(dbPath, 'furniture_app.db'), version: 1, onCreate: (db, version) async { await db.execute(''' CREATE TABLE rooms ( id TEXT PRIMARY KEY, name TEXT NOT NULL, icon_code TEXT, budget INTEGER, created_at INTEGER ) '''); await db.execute(''' CREATE TABLE furniture_items ( id TEXT PRIMARY KEY, room_id TEXT NOT NULL, name TEXT NOT NULL, price INTEGER, purchase_date INTEGER, warranty_end INTEGER, seller TEXT, note TEXT ) '''); }, ); } Future<void> insertRoom(Room room) async { final db = await database; await db.insert('rooms', room.toJson()); } }

简单总结一下:如果这个App只是演示demo,用shared_preferences完全没问题;但只要产品打算真的用起来,数据结构有扩展可能,就建议直接在第一步上SQLite。回头改数据层的成本比一开始多写几十行SQL高得多。

3.4 防重复提交与保存按钮的细节处理

保存按钮的点击处理有一个很容易忽略的问题:用户连点两次,会插入两条相同的房间记录。解决办法是提交前先检查一个isSubmitting状态,提交过程中按钮置灰,并且用一个try-finally包裹,确保finally里恢复状态。

Future<void> _submit() async { if (_isSubmitting) return; setState(() => _isSubmitting = true); try { if (_formKey.currentState!.validate()) { final room = Room( id: generateId(), name: _nameController.text.trim(), iconCode: _selectedIcon, budget: int.parse(_budgetController.text.isEmpty ? '0' : _budgetController.text) * 100, createdAt: DateTime.now(), ); await _roomProvider.addRoom(room); if (!mounted) return; Navigator.pop(context); } } finally { if (mounted) { setState(() => _isSubmitting = false); } } }

此外,在弹窗提交成功关闭之前,页面底部可以短暂地显示一个三个字的文字提示“已添加”,不需要很夸张的动画,但能让用户感觉到操作确实生效了,这个体验细节也值得保留。

4. 通过Provider实现组件通信与页面联动

4.1 Provider基础用法回顾:ChangeNotifier怎么用

如果你现在去搜“flutter provider 怎么用”,能看到大量文章在讲ChangeNotifier、Provider.of、Consumer这些概念,但很多例子都是计数器那种玩具场景,放到真实业务里就不知道怎么落地。这里我结合添加房间的场景,把完整的实现串一遍。

先定义一个RoomProvider,继承ChangeNotifier:

class RoomProvider extends ChangeNotifier { final List<Room> _rooms = []; bool _isLoading = true; List<Room> get rooms => List.unmodifiable(_rooms); Future<void> loadRooms() async { final roomsFromDb = await DatabaseHelper().getAllRooms(); _rooms ..clear() ..addAll(roomsFromDb); _isLoading = false; notifyListeners(); } Future<void> addRoom(Room room) async { await DatabaseHelper().insertRoom(room); _rooms.add(room); notifyListeners(); } int get totalBudget { return _rooms.fold(0, (sum, room) => sum + room.budget); } }

关键点在于:所有修改数据的地方都经过Provider的方法,方法内部先落下数据库、再更新内存列表、最后调用notifyListeners通知界面刷新。有人可能会问,为什么不直接在页面里写了数据库再手动setState?当然也可以,但那样子页面逻辑和数据逻辑就耦合了,后面加一个“从云端恢复数据”的功能时,你得满屏找setState的位置。

4.2 ChangeNotifierProvider的挂载与Consumer的精准监听

Root级的Provider挂载,我直接放在MaterialApp外面:

void main() { runApp( ChangeNotifierProvider( create: (_) => RoomProvider()..loadRooms(), child: const FurnitureApp(), ), ); }

然后主页面的房间列表用Consumer来包裹:

Consumer<RoomProvider>( builder: (context, provider, child) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (provider.rooms.isEmpty) { return const EmptyRoomPlaceholder(); } return ListView.builder( itemCount: provider.rooms.length, itemBuilder: (context, index) => RoomCard(room: provider.rooms[index]), ); }, )

Consumer的builder里面,第一个参数是BuildContext,第二个参数是当前Provider实例,第三个参数child是可选的优化项。如果列表项本身不依赖Provider数据,可以用child参数把不变的子组件提升到builder外面创建,这样Provider数据变化时Flutter会直接复用那个child实例,减少重建范围。这个优化在大列表场景效果很明显。

4.3 跨页面联动:添加房间后统计卡片自动更新的完整流程

当用户从底部弹窗提交新房间,整个数据流是这样串起来的:

第一,AddRoomSheet里的_submit方法构造Room对象。

第二,调用roomProvider.addRoom,此时RoomProvider里的_rooms列表长度加一,notifyListeners触发所有监听者的回调,包括主页的Consumer和统计卡片的Consumer。

第三,弹窗自身通过Navigator.pop关闭。

第四,主页的ListView因为数据源变化自动在末尾多渲染一个RoomCard,同时统计卡片上“房间总数”“总预算”跟着变化。

这里有一个细节:调用addRoom之后不要再去做任何“手动刷新页面”的操作,不要告诉ListView去reload,不要重新调loadRooms,一切都交给notifyListeners。我在CodeReview中经常看到有人把Provider和手动刷新混着用,于是数据被刷新了两次,轻则性能浪费,重则出现闪烁或列表跳动。

如果你有多个页面同时监听同一个Provider,比如主页显示房间列表,另一个统计页显示房间数量,它们的刷新是有序的吗?答案是:Flutter的框架会调度这些监听回调,但执行顺序不保证和注册顺序一致。所以在写依赖关系时,千万不要依赖先后顺序,而是要把数据准备好在Provider内部处理好。

4.4 Provider和setState的分工:什么时候不要用Provider

也不是所有状态都扔进Provider。像弹窗里输入框的文本、校验错误信息、保存按钮的loading状态,这些只在弹窗内部生效,关掉页面就销毁了,用Local State更合适,也就是StatefulWidget自己的setState。

Provider解决的问题是跨页面、跨组件的共享状态和通知机制。它的核心价值不是“省掉setState”,而是让数据变更和UI刷新之间的关系清晰化。你把房间列表放进Provider,任何页面只要消费了这个Provider,就自动获得最新数据,不需要一层一层地往子组件传回调。把弹窗内部的输入文字也放进Provider,那Provider就会越滚越大,最后变成一个什么都装的垃圾箱,调试时根本分不清是谁改了状态。

结合这个项目,我总结一个最简单的分工原则:这个状态被两个及以上的页面使用时,放Provider;只在当前页面生命周期内有效,放setState。按这个标准去套,基本不会出错。

5. 实战踩坑记录与排查技巧

5.1 热重载失效问题:添加Provider后必须hot restart

开发过程中我遇到一个非常迷惑的问题:在代码里加了ChangeNotifierProvider之后,按热重载(hot reload),页面直接报错,提示widget tree和element tree结构不匹配,完全无法使用。当时第一反应是代码写得有问题,反复看了半天没找到毛病,最后才发现是Flutter在OpenHarmony适配层上对热重载的支持还不完整。

如果你也在OpenHarmony上跑Flutter,记住一个经验:改动Provider、路由表、App根节点这类影响widget树整体结构的代码,直接用热重启(hot restart)而不是热重载。热重载适合改某个页面内部的文本、颜色、布局参数,它保留页面状态,效率高;热重启会重新执行main函数,状态清空,但结构变更的代码能正确加载。

5.2 在OpenHarmony设备上,图片和字体资源的加载路径

Flutter在OpenHarmony上的资源管理和标准Flutter不太一样。我在项目里添加了自定义图标字体作为房间图标的候选来源,结果发现在模拟器上显示正常,真机上图标全部变成方框。排查了很久,定位到问题是资源打包时字体文件的路径没有同步到资源目录的AssetManifest里。

解决方案是把要用的图标从自定义字体换成了Material Icons内置图标,再不行就直接用实体的PNG图标,按房间类型提供几套固定配色。给房间选图标这个功能,用Material Icons已经足够覆盖客厅、卧室、书房、厨房这些场景了,不必为它额外引入字体文件,这是我在这个项目里学会的取舍。

5.3 添加房间后列表未刷新的排查方法

我在开发过程中故意做过一次“错误演示”:在AddRoomSheet里调用了Provider的addRoom方法,但列表没反应。排查步骤是这样的:

第一步,检查Provider是否被正确挂载。如果页面的根Widget没有包ChangeNotifierProvider,Consumer会直接抛ProviderNotFoundException,所以如果能显示页面但没有刷新,说明Provider本身是在的。

第二步,检查是否调用了notifyListeners。RoomProvider的addRoom方法里确实调了,但我在一个分支路径里调完return,把notifyListeners漏掉了,结果列表不变。这个用debugPrint在addRoom里打印日志,立刻就能看出来。

第三步,检查页面的Consumer包裹范围。如果列表页面没有用Consumer包住ListView,而是直接在build里通过Provider.of(context)获取数据,那只有当前页面重新build时才会刷新。而Provider.of(context)在widget的build方法里调用,只要Provider通知,是会触发当前页面build的。问题往往出在列表的子组件里直接读取了Provider但没建立依赖关系。

第三步这个方法,用一句话概括:谁读数据,谁就要包在Consumer里或者使用Provider.of。不要在initState里读Provider数据,因为initState不参与后续的rebuild流程。

5.4 OpenHarmony适配中遇到的真实问题速查表

我在这个项目里前前后后踩过的坑远不止上面那几个,挑几个出现频率高的整理成了一张表,后续遇到类似问题可以快速翻:

问题现象可能原因解决方案
flutter build hap报错说找不到设备签名证书没有配置好在DevEco Studio里重新生成签名证书并关联到shell工程
真机上图标全部是方框自定义字体资源路径或打包异常改用Material Icons或本地PNG图标
键盘弹起时按钮被遮挡弹窗没有设置isScrollControlled打开isScrollControlled并配合viewInsets底部内边距适配
连续点击保存按钮出现重复房间没有防重复状态添加isSubmitting状态,等待异步完成后恢复
添加后列表不刷新Provider没有调用notifyListeners或Consumer包裹位置不对在状态修改后统一调用notifyListeners
热重载后报element tree错误结构调整后用了热重载改用热重启
数据存入数据库后App重启丢失没有初始化数据库或路径错误确认getDatabasesPath返回路径,打开数据库时设置onCreate建表
统计金额精度不对直接用double存储金额金额一律用int表示分,展示时再除以100

有两条额外经验值得单独说一下。

第一,在OpenHarmony上做真机调试时,闪退日志不一定能第一时间出现在Flutter的console里,有时候要在DevEco Studio的Log窗口里筛OHOS标签才能找到原生层的报错。尤其是跟sqlite相关的崩溃,Dart侧打印不出任何信息,但Log能给出具体是哪个so文件崩溃了。所以环境配置时,把DevEco Studio的日志过滤规则先配好,能节约大量排查时间。

第二,关于IllegalArgumentException这类资源找不到的报错,常见于依赖了不兼容OpenHarmony的第三方插件。有些插件虽然能在pub.dev上搜到,但内部实现是Android的Kotlin代码,没有做OpenHarmony平台适配,运行时就会出现平台通道调用为空的情况。选插件时优先看有没有在README里标明OpenHarmony支持,或者直接看项目里的ohos目录是否存在。

5.5 关于性能与渲染引擎Impeller的观察

Flutter 3.10以后开始推Impeller渲染引擎,替代之前的Skia。在OpenHarmony设备上,Impeller的表现和Android上不完全一致。一开始我用的是Flutter 3.13,默认没开Impeller,列表滚动时偶尔能感觉到掉帧。后来切换到Impeller之后,列表渲染确实明显顺滑,尤其是快速滚动和列表项插入动画,不会再出现那种“一顿一顿”的视觉反馈。

但Impeller也不是处处都好。在我用的设备上,切到Impeller之后启动时间多了一两百毫秒。启动时间在工具类App里不是核心指标,所以总体收益是正的。如果你想控制是否启用Impeller,可以在原生工程的配置文件里显式设置开关,但建议先保持默认,只有在真机出现性能问题时再去主动调整。

6. 后续功能扩展建议

做完“添加房间”功能之后,这个App后面还有很多路要走,这里简单说几个我计划中的方向,也给看这篇文章的朋友一些想法。

房间管理肯定会扩展“编辑房间”和“删除房间”。删除房间要注意关联数据,家具记录表里用room_id关联房间,删除时要么一并删除家具记录,要么先提示用户“该房间下有N件家具,确认删除吗”。我建议做软删除而不是物理删除,加一个deleted标志字段,这样未来做数据统计时还能回溯。

“全局搜索”也是一个很实用的功能,用户可能不记得家具放在哪个房间,但记得“去年买了一个乳胶床垫”,所以搜索框需要同时匹配房间名称和家具名称以及卖家字段。SQLite的LIKE查询在这里足够用,不需要上全文索引。

后端同步规划:等数据量增长之后,可以考虑接一个后端做多端备份。技术栈可以是Django或者Spring Boot,App端通过HTTP接口同步数据。有了稳定的数据模型和Provider状态管理,这一步主要就是写网络层和处理冲突策略,我目前倾向于“以本地数据为准,合并服务端增量”,避免复杂的冲突解法。

写在最后的个人体会

把“添加房间”这个功能完整做下来,我最大的心得是:在OpenHarmony上用Flutter开发,入门门槛不高,真正的成本在于生态适配和排查问题的经验积累。刚开始跑环境、配签名那几天确实让人烦躁,但一旦第一张图片在真机上显示出来,后面整个开发节奏就和普通Flutter项目没什么区别了。

如果你正准备在OpenHarmony上做Flutter应用,我的建议是先挑一个像“添加房间”这样的小功能走通全链路,从环境配置、数据持久化到状态管理全部跑一遍,再开始铺业务页面。另外,写之前一定先把数据流画清楚,谁产生数据谁消费数据,写在纸上比直接开Editor重要得多。这个项目的代码后续我会继续完善,等房间、家具、统计三个模块全都跑通之后,再整理一篇完整的技术复盘。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询