Skip to content

生命周期与状态管理 ​

Widget 是配置,State 保存变化 ​

StatelessWidget 和 StatefulWidget 都是不可变配置。前者并不意味着「只构建一次」;父级更新或依赖变化都可能让它重新构建。StatefulWidget 把需要跨多次构建保留的可变信息交给 State。

构建函数负责根据当前配置与状态返回 UI。避免在 build 中发请求、创建需要释放的控制器或修改状态,因为它可能频繁执行。

生命周期速查 ​

方法何时执行典型用途
initState每个 State 初始化时一次创建控制器、启动不依赖继承查找的初始化
didChangeDependencies初始化后及依赖变化时响应通过 context 建立的继承依赖
build首次展示及后续重建用当前状态描述界面
didUpdateWidget同位置复用且配置更新时比较新旧参数,更新订阅或数据源
deactivate暂时从树中移除时处理与树位置相关的连接,之后可能重新插入
disposeState 永久移除时取消订阅并释放控制器

异步返回后,使用当前 State 前检查 mounted;使用跨异步间隔保存的 BuildContext 前检查对应的 context.mounted。同时尽可能取消无用任务:挂载检查不会停止网络请求,也不能解决旧请求覆盖新请求的问题。

控制器由谁创建,通常就由谁释放。父级传入的控制器,不应由子组件擅自销毁。生命周期行为参见 State API。

实例:局部搜索状态 ​

下面是独立页面。添加 import 'package:flutter/material.dart';,放入 Flutter 应用中,并设为 MaterialApp 的 home。

dart
class SearchNotesPage extends StatefulWidget {
  const SearchNotesPage({super.key});

  @override
  State<SearchNotesPage> createState() => _SearchNotesPageState();
}

class _SearchNotesPageState extends State<SearchNotesPage> {
  final _search = TextEditingController();
  static const _titles = ['Flutter 布局', 'Dart 空安全', 'Flutter 状态'];
  String _keyword = '';

  @override
  void dispose() {
    _search.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final visible = _titles
        .where((title) => title.toLowerCase().contains(_keyword))
        .toList();

    return Scaffold(
      appBar: AppBar(title: const Text('搜索笔记')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: TextField(
              controller: _search,
              decoration: const InputDecoration(labelText: '关键字'),
              onChanged: (value) {
                setState(() => _keyword = value.trim().toLowerCase());
              },
            ),
          ),
          Expanded(
            child: visible.isEmpty
                ? const Center(child: Text('没有匹配的笔记'))
                : ListView.builder(
                    itemCount: visible.length,
                    itemBuilder: (context, index) => ListTile(
                      title: Text(visible[index]),
                    ),
                  ),
          ),
        ],
      ),
    );
  }
}

setState 的回调应同步完成状态赋值,异步操作放在回调外。只有一个页面关心搜索词时,将状态留在页面内即可;列表可以从原始数据和关键字推导出来,无需再保存第二份可变列表。

共享状态如何选择 ​

范围可以从什么方案开始关键边界
单个组件setState更新范围靠近变化处
单个可观察值ValueNotifier + ValueListenableBuilder集合更新要留意通知语义,优先赋予新值
父子协作构造参数传值、回调传事件父级持有唯一状态来源
跨层依赖InheritedWidget 或状态管理库明确哪些组件订阅哪些数据
复杂业务ViewModel / Controller + RepositoryUI 状态和数据访问分开,便于测试

ValueListenableBuilder 会监听值的变化并重新构建相关区域,参见 API 文档。是否引入 Provider、Riverpod 或 Bloc,应结合团队已有方案、测试方式与复杂度决定,不必在入门页同时引入所有库。

状态分层可参考 官方应用架构指南:视图呈现状态、处理用户意图,数据访问交给独立对象。分层的目的,是让同一条业务规则只有一个维护位置。

Key 与状态复用 ​

列表会插入、删除、重排时,为有状态的条目提供稳定的 ValueKey(note.id),帮助框架对应逻辑上同一条数据。不要使用会随顺序变化的索引充当业务身份。

GlobalKey 用于确实需要全局身份或访问特定 State 的场景,不是普通状态共享的替代品。三棵树如何配合复用,见 框架原理。

练习与验收 ​

输入 flutter 应命中两条,输入不存在的内容应出现空状态,清空后恢复全部列表。反复进入和退出页面,确认没有控制器或订阅重复创建后未释放的问题。

持续记录,持续理解。