发现项目中有很多重复构建的地方需要优化

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

实现构建与重建优化

本次做了如下优化:合并主题/语言/字体为单一 Listenable、主题只创建一次、底部 Tab 懒加载、首页翻页不整页 setState、发现页搜索防抖。


在这里插入图片描述

一、目标与方案

  • 目标

    1. 切换主题/语言/字体时,减少从 MaterialApp 起的整树重建,提升帧率与体感。
    2. 主题数据不随每次 build 重新创建,降低 GC 与重算开销。
    3. 底部四个 Tab 仅在首次进入时创建对应页面,降低首屏内存与无关请求。
    4. 首页 PageView 翻页时仅更新必要状态,不触发整页 rebuild。
    5. 发现页搜索输入时通过防抖减少全表过滤与 setState 频率,避免输入卡顿。
  • 大致方向

    • main.dart 中引入 AppSettings(themeMode、locale、fontScale)与单一 ValueNotifier<AppSettings>,只包一层 ValueListenableBuilder;亮/暗主题改为顶层 final,只创建一次。
    • RootPageList<Widget?> _tabPages 按需创建 Tab 页面,在 _onTabTapped_ensureTabCreated(i),首屏仅创建 HomePage
    • HomePageValueNotifier<int> _pageIndexNotifier 保存当前页码,onPageChanged 只写 _pageIndexNotifier.value = index,不调用 setState
    • SearchPage_onSearchChanged 做 250ms 防抖,再执行过滤与 setState

二、修改的文件

文件 说明
lib/main.dart 新增 AppSettingsappSettingsNotifier;主题/暗主题改为顶层 finalMyApp 仅一层 ValueListenableBuilder<AppSettings>
lib/pages/profile_page.dart 设置项与弹窗改为监听/更新 appSettingsNotifier(主题、语言、字体);移除对 appThemeModeNotifier / appLocaleNotifier / appFontScaleNotifier 的引用。
lib/pages/root_page.dart 底部 Tab 懒加载:_tabPages + _ensureTabCreated,仅在首次进入某 Tab 时创建对应页面。
lib/pages/home_page.dart 翻页时用 _pageIndexNotifier.value = index 替代 setState(() => _currentIndex = index),并在 disposedispose 该 notifier。
lib/pages/search_page.dart 搜索防抖:Timer? _searchDebounce_onSearchChanged 内先 cancelTimer(250ms, ...) 执行过滤与 setState;dispose_searchDebounce?.cancel()

三、优化说明

3.1 主题/语言/字体合并为单一 Listenable

  • AppSettings:不可变类,包含 themeModelocalefontScale,提供 copyWith({ themeMode, locale, fontScale })。其中 locale 通过「省略参数」与「显式传 null」区分「不修改」与「设为跟随系统」。
  • appSettingsNotifier:全局 ValueNotifier<AppSettings>,在 main() 中从 SettingsService 读入初始值后创建。
  • MyApp.build:仅一层 ValueListenableBuilder<AppSettings>,内部直接使用 settings.themeModesettings.localesettings.fontScale 配置 MaterialAppMediaQuery.textScaler
  • 设置页:所有原先读写 appThemeModeNotifier / appLocaleNotifier / appFontScaleNotifier 的地方改为读写 appSettingsNotifier.value(读字段、写 copyWith),并持久化到 SettingsService 不变。

3.2 主题数据只创建一次

  • _lightTheme_darkTheme 由 getter 改为顶层 final 常量,应用生命周期内只创建一次,避免每次 build 重算。

3.3 底部 Tab 懒加载

  • 结构List<Widget?> _tabPages 长度为 4,初始均为 null
  • 创建时机initState 中调用 _ensureTabCreated(0),保证首屏首页存在;用户点击 Tab i 时在 _onTabTapped(i) 内先 _ensureTabCreated(i)setState,保证当前 Tab 对应页面已创建。
  • 展示AnimatedSwitcher 的 child 为 _tabPages[_currentIndex]!。已创建过的 Tab 会一直保留,切换回来不再新建。

3.4 首页翻页不整页 setState

  • 状态:用 ValueNotifier<int> _pageIndexNotifier 替代原来的 _currentIndex
  • onPageChanged:仅执行 _pageIndexNotifier.value = index 以及原有的「接近末尾时 _loadMore」逻辑,不再 setState。页码指示器在每页的 _buildImagePage 中由 itemBuilderindex 直接得到,无需依赖当前页码状态。
  • 网格切单列:点击网格项时先 _pageIndexNotifier.value = index,再 setState(() => _isGridView = false)animateToPagedispose 中调用 _pageIndexNotifier.dispose()

3.5 发现页搜索防抖

  • 防抖Timer? _searchDebounce,常量 _searchDebounceDuration = 250ms
  • 逻辑_onSearchChanged(q) 内先 _searchDebounce?.cancel(),再 _searchDebounce = Timer(_searchDebounceDuration, () { ... }),在回调里做 lower = q.trim().toLowerCase()、过滤 _allWorkssetState 更新 _filteredWorks;回调内先 if (!mounted) return
  • dispose_searchDebounce?.cancel(),避免泄漏与 setState 后使用。

四、部分代码

4.1 AppSettings 与单一 Notifier(main.dart)

class AppSettings {
  const AppSettings({
    required this.themeMode,
    required this.locale,
    required this.fontScale,
  });

  final ThemeMode themeMode;
  final Locale? locale;
  final double fontScale;

  AppSettings copyWith({
    ThemeMode? themeMode,
    Object? locale = _omitLocale,
    double? fontScale,
  }) {
    return AppSettings(
      themeMode: themeMode ?? this.themeMode,
      locale: identical(locale, _omitLocale) ? this.locale : locale as Locale?,
      fontScale: fontScale ?? this.fontScale,
    );
  }
}
const Object _omitLocale = Object();

late final ValueNotifier<AppSettings> appSettingsNotifier;
// main(): appSettingsNotifier = ValueNotifier<AppSettings>(AppSettings(...));

4.2 底部 Tab 懒加载(root_page.dart)

final List<Widget?> _tabPages = [null, null, null, null];

void _ensureTabCreated(int index) {
  if (_tabPages[index] != null) return;
  switch (index) {
    case 0: _tabPages[index] = const HomePage(); break;
    case 1: _tabPages[index] = const SearchPage(); break;
    case 2: _tabPages[index] = const MessagesPage(); break;
    case 3: _tabPages[index] = const ProfilePage(); break;
    default: _tabPages[index] = const HomePage();
  }
}
// initState: _ensureTabCreated(0);
// _onTabTapped(i): _ensureTabCreated(i); setState(...);
// child: _tabPages[_currentIndex]!

4.3 首页翻页仅更新 notifier(home_page.dart)

final ValueNotifier<int> _pageIndexNotifier = ValueNotifier<int>(0);

onPageChanged: (index) {
  _pageIndexNotifier.value = index;
  if (!_isGridView && index >= _displayImages.length - 2 && _hasMore && !_isLoadingMore) {
    _loadMore();
  }
},
// dispose: _pageIndexNotifier.dispose();

4.4 发现页搜索防抖(search_page.dart)

Timer? _searchDebounce;
static const Duration _searchDebounceDuration = Duration(milliseconds: 250);

void _onSearchChanged(String q) {
  _searchDebounce?.cancel();
  _searchDebounce = Timer(_searchDebounceDuration, () {
    if (!mounted) return;
    setState(() {
      final lower = q.trim().toLowerCase();
      // ... 过滤 _allWorks → _filteredWorks
    });
  });
}
// dispose: _searchDebounce?.cancel();

五、注意事项

  • AppSettings.copyWithlocale 使用 Object? locale = _omitLocale,调用方传 null 表示「跟随系统」,不传表示保持当前;设置页切换语言时使用 copyWith(locale: newLocale)
  • Tab 懒加载:首次进入发现/消息/我的时才会执行对应页的 initState(如请求、初始化),首屏仅首页会发起首页相关请求。
  • 防抖:防抖仅减少输入过程中的 setState 次数,列表很大时若单次过滤仍耗时,可后续考虑将过滤放入 compute(isolate)。

六、小结

通过合并主题/语言/字体为单一 AppSettings + 一层 ValueListenableBuilder、主题常量只创建一次、底部 Tab 按需创建、首页翻页仅更新 ValueNotifier、发现页搜索 250ms 防抖,完成了「二、构建与重建」中高优先级优化,缩小了设置切换与翻页时的重建范围,降低了首屏与输入时的开销,体感更顺滑。

结束语

感谢阅读本帖,如对贴中内容有意见和建议的,欢迎与我联系交流,也欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐