第十六讲 状态管理基础

0 阅读3分钟

前言:

状态管理是一个重点,可以看看。之前也有很多案例用过了已经。

一、定位

本讲聚焦 Flutter 中最基础也最核心的状态管理能力

  1. setState 核心:用于单个 StatefulWidget 的局部状态更新,仅触发当前 Widget 的 build 方法,适合管理组件内部状态(如按钮点击、表单输入)。
  2. InheritedWidget 核心:Flutter 原生的跨组件状态共享方案,通过「状态缓存+依赖收集」实现精准重建,适合共享跨层级 Widget 需要访问的状态(如用户信息、主题配置)。
  3. 组合使用原则:用 setState 管理局部临时状态,用 InheritedWidget 封装共享状态,通过「不可变状态+copyWith」实现状态安全更新,兼顾性能与可维护性。

1.1 setState 局部刷新原理

image.png

1.2 InheritedWidget 状态共享原理

image.png

1.3 整体状态管理基础架构

image.png

二、核心知识点详解

2.1 setState 局部刷新

2.1.1 核心概念

setState 是 StatefulWidget 中最基础的状态更新方法,本质是通过标记当前 Element 为脏状态,触发该 Widget 的 build 方法重新执行,实现局部 UI 刷新。

2.1.2 核心属性/方法说明
名称类型作用
setState(VoidCallback fn)方法接收一个无返回值的回调函数,在回调中更新状态,触发 build 重建
mounted布尔属性标记当前 State 是否挂载到 Widget 树中,更新状态前建议检查(避免空指针)
2.1.3 基础案例
import 'package:flutter/material.dart';

// setState 基础使用案例
class SetStateDemo extends StatefulWidget {
  const SetStateDemo({super.key});

  @override
  State<SetStateDemo> createState() => _SetStateDemoState();
}

class _SetStateDemoState extends State<SetStateDemo> {
  // 局部状态:计数器
  int _counter = 0;

  // 状态更新方法
  void _incrementCounter() {
    // 核心:调用setState更新状态
    setState(() {
      _counter++; // 仅更新此状态,触发build重建
    });
  }

  @override
  Widget build(BuildContext context) {
    print("SetStateDemo 执行build"); // 验证仅局部刷新
    return Scaffold(
      appBar: AppBar(title: const Text("setState 局部刷新")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('你点击的次数:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

2.1.4 注意事项
  1. 仅局部刷新setState 只会触发当前 State 对应的 Widget 的 build 方法,不会刷新整个页面(性能优化点)

  2. 避免同步耗时操作:不要在 setState 回调中执行网络请求、大量计算等耗时操作(会阻塞 UI 线程)

  3. 状态更新必须在回调内:直接修改 _counter 而不调用 setState,或在回调外修改状态,都不会触发 UI 刷新

  4. 避免不必要的重建:如果状态变化不需要刷新 UI,不要调用 setState

  5. 检查 mounted:异步操作后更新状态时,务必检查 if (mounted),避免 Widget 已销毁仍更新状态:

    1.  // 延后两秒挂载
       void _asyncUpdate() async {
         await Future.delayed(const Duration(seconds: 2));
         if (mounted) { // 关键:检查是否仍挂载
           setState(() {
             _counter++;
           });
         }
       }
      

2.2 InheritedWidget 跨组件状态共享

2.2.1 核心概念

InheritedWidget 是 Flutter 提供的跨组件状态共享核心组件,允许上层 Widget 将状态数据向下传递,下层任意 Widget 可通过 BuildContext 高效获取,且当状态变化时,仅通知依赖该状态的子 Widget 重建。

2.2.2 核心属性/方法说明
名称类型作用
updateShouldNotify方法(返回bool)对比新旧 Widget 的状态,决定是否通知依赖组件重建
dependOnInheritedWidgetOfExactType<T>()方法通过 BuildContext 获取指定类型的 InheritedWidget,并建立依赖关系
getElementForInheritedWidgetOfExactType<T>()方法获取 InheritedWidget 对应的 Element,不建立依赖关系(状态变化不触发重建)
2.2.3 基础案例
import 'package:flutter/material.dart';

// 步骤1:定义共享状态模型
class UserInfo {
  final String name;
  final int age;

  UserInfo({required this.name, required this.age});

  // 不可变设计:状态更新时创建新对象
  UserInfo copyWith({String? name, int? age}) {
    return UserInfo(
      name: name ?? this.name,
      age: age ?? this.age,
    );
  }
}

// 步骤2:自定义InheritedWidget
class UserInheritedWidget extends InheritedWidget {
  // 共享状态
  final UserInfo userInfo;
  // 状态更新方法(供外部调用)
  final Function(UserInfo) updateUserInfo;

  const UserInheritedWidget({
    super.key,
    required this.userInfo,
    required this.updateUserInfo,
    required super.child,
  });

  // 步骤3:提供便捷的获取方法(简化子组件调用)
  static UserInheritedWidget of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType<UserInheritedWidget>();
    assert(result != null, 'No UserInheritedWidget found in context');
    return result!;
  }

  // 步骤4:判断是否需要通知依赖组件重建
  @override
  bool updateShouldNotify(UserInheritedWidget oldWidget) {
    // 状态变化时返回true,触发依赖组件重建
    return userInfo.name != oldWidget.userInfo.name || userInfo.age != oldWidget.userInfo.age;
  }
}

// 步骤5:使用共享状态的子组件1
class UserNameWidget extends StatelessWidget {
  const UserNameWidget({super.key});

  @override
  Widget build(BuildContext context) {
    print("UserNameWidget 执行build");
    // 获取共享状态
    final userInfo = UserInheritedWidget.of(context).userInfo;
    return Text(
      '用户名:${userInfo.name}',
      style: const TextStyle(fontSize: 20),
    );
  }
}

// 步骤6:使用共享状态的子组件2
class UserAgeWidget extends StatelessWidget {
  const UserAgeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    print("UserAgeWidget 执行build");
    // 获取共享状态
    final userInfo = UserInheritedWidget.of(context).userInfo;
    return Text(
      '年龄:${userInfo.age}',
      style: const TextStyle(fontSize: 20),
    );
  }
}

// 步骤7:顶层容器组件(管理状态+包裹InheritedWidget)
class InheritedWidgetDemo extends StatefulWidget {
  const InheritedWidgetDemo({super.key});

  @override
  State<InheritedWidgetDemo> createState() => _InheritedWidgetDemoState();
}

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  late UserInfo _userInfo;

  @override
  void initState() {
    super.initState();
    _userInfo = UserInfo(name: "张三", age: 20);
  }

  // 更新共享状态
  void _updateUser() {
    setState(() {
      _userInfo = _userInfo.copyWith(
        name: "李四",
        age: _userInfo.age + 1,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    print("InheritedWidgetDemo 执行build");
    return Scaffold(
      appBar: AppBar(title: const Text("InheritedWidget 状态共享")),
      body: UserInheritedWidget( // 包裹需要共享状态的Widget树
        userInfo: _userInfo,
        updateUserInfo: (newUserInfo) {
          setState(() {
            _userInfo = newUserInfo;
          });
        },
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              UserNameWidget(), // 子组件1:获取用户名
              SizedBox(height: 20),
              UserAgeWidget(), // 子组件2:获取年龄
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateUser,
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

2.2.4 注意事项
  1. 不可变状态设计:InheritedWidget 本身是不可变的,状态更新需创建新的 InheritedWidget 实例(通过 setState 实现)
  2. 精准重建:只有调用 dependOnInheritedWidgetOfExactType 的子组件会被通知重建,其他组件不受影响
  3. 避免过度共享:不要把所有状态都放在一个 InheritedWidget 中,按功能拆分(避免不必要的重建)
  4. 上下文范围:子组件必须在 InheritedWidget 的子树中,才能通过 context 获取到共享状态
  5. 依赖关系:如果不需要状态变化时重建,可使用 getElementForInheritedWidgetOfExactType 替代(无依赖)

三、综合应用案例

3.1 需求说明

实现一个「用户信息管理」页面:

  • 顶层通过 InheritedWidget 共享用户信息(姓名、年龄、积分)
  • 局部通过 setState 更新积分(局部刷新)
  • 跨组件更新用户姓名和年龄(共享状态刷新)
  • 不同层级的子组件都能访问并响应状态变化

3.2 完整代码

import 'package:flutter/material.dart';

// 1. 定义共享状态模型
class UserState {
  final String name;
  final int age;
  final int score;

  UserState({
    required this.name,
    required this.age,
    required this.score,
  });

  // 不可变更新:创建新对象
  UserState copyWith({
    String? name,
    int? age,
    int? score,
  }) {
    return UserState(
      name: name ?? this.name,
      age: age ?? this.age,
      score: score ?? this.score,
    );
  }
}

// 2. 自定义InheritedWidget
class UserStateProvider extends InheritedWidget {
  final UserState userState;
  final Function(UserState) updateUserState;

  const UserStateProvider({
    super.key,
    required this.userState,
    required this.updateUserState,
    required super.child,
  });

  // 便捷获取方法
  static UserStateProvider of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType<UserStateProvider>();
    assert(result != null, 'No UserStateProvider found in context');
    return result!;
  }

  // 判断是否需要通知依赖组件
  @override
  bool updateShouldNotify(UserStateProvider oldWidget) {
    return userState.name != oldWidget.userState.name ||
        userState.age != oldWidget.userState.age ||
        userState.score != oldWidget.userState.score;
  }
}

// 3. 局部状态组件:积分更新(仅刷新自身)
class ScoreCounter extends StatefulWidget {
  const ScoreCounter({super.key});

  @override
  State<ScoreCounter> createState() => _ScoreCounterState();
}

class _ScoreCounterState extends State<ScoreCounter> {
  void _addScore() {
    // 获取共享状态
    final provider = UserStateProvider.of(context);
    // 局部更新:仅更新积分(setState + 共享状态更新)
    setState(() {
      provider.updateUserState(
        provider.userState.copyWith(
          score: provider.userState.score + 10,
        ),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    print("ScoreCounter 执行build");
    final score = UserStateProvider.of(context).userState.score;
    return Column(
      children: [
        const Text(
          '当前积分',
          style: TextStyle(fontSize: 16, color: Colors.grey),
        ),
        Text(
          '$score',
          style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        ),
        ElevatedButton(
          onPressed: _addScore,
          child: const Text('增加10积分'),
        ),
      ],
    );
  }
}

// 4. 跨层级子组件:用户信息展示
class UserInfoDisplay extends StatelessWidget {
  const UserInfoDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    print("UserInfoDisplay 执行build");
    final userState = UserStateProvider.of(context).userState;
    return Card(
      margin: const EdgeInsets.all(20),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('姓名:${userState.name}', style: const TextStyle(fontSize: 18)),
            Text('年龄:${userState.age}', style: const TextStyle(fontSize: 18)),
            Text('积分:${userState.score}', style: const TextStyle(fontSize: 18)),
          ],
        ),
      ),
    );
  }
}

// 5. 状态更新组件:修改姓名和年龄
class UserEditWidget extends StatelessWidget {
  const UserEditWidget({super.key});

  @override
  Widget build(BuildContext context) {
    print("UserEditWidget 执行build");
    final provider = UserStateProvider.of(context);
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        ElevatedButton(
          onPressed: () {
            provider.updateUserState(
              provider.userState.copyWith(name: "新姓名-${DateTime.now().second}"),
            );
          },
          child: const Text('修改姓名'),
        ),
        ElevatedButton(
          onPressed: () {
            provider.updateUserState(
              provider.userState.copyWith(age: provider.userState.age + 1),
            );
          },
          child: const Text('年龄+1'),
        ),
      ],
    );
  }
}

// 6. 顶层容器:整合所有组件
class StateManagementCombinedDemo extends StatefulWidget {
  const StateManagementCombinedDemo({super.key});

  @override
  State<StateManagementCombinedDemo> createState() => _StateManagementCombinedDemoState();
}

class _StateManagementCombinedDemoState extends State<StateManagementCombinedDemo> {
  late UserState _userState;

  @override
  void initState() {
    super.initState();
    // 初始化状态
    _userState = UserState(name: "初始姓名", age: 20, score: 0);
  }

  // 更新共享状态的核心方法
  void _updateUserState(UserState newState) {
    setState(() {
      _userState = newState;
    });
  }

  @override
  Widget build(BuildContext context) {
    print("StateManagementCombinedDemo 执行build");
    return Scaffold(
      appBar: AppBar(
        title: const Text("状态管理综合案例"),
        centerTitle: true,
      ),
      body: UserStateProvider( // 包裹整个子树,提供共享状态
        userState: _userState,
        updateUserState: _updateUserState,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            UserInfoDisplay(), // 展示状态
            UserEditWidget(),  // 修改姓名/年龄
            SizedBox(height: 30),
            ScoreCounter(),    // 局部更新积分
          ],
        ),
      ),
    );
  }
}

// 7. 入口函数
void main() {
  runApp(const MaterialApp(
    home: StateManagementCombinedDemo(),
    debugShowCheckedModeBanner: false,
  ));
}

3.3 运行效果说明

  1. 初始页面显示:姓名=初始姓名、年龄=20、积分=0
  2. 点击「增加10积分」:仅 ScoreCounter 组件重建(局部刷新),积分数值增加
  3. 点击「修改姓名」:UserInfoDisplayUserEditWidget 重建(依赖共享状态),姓名更新为带秒数的新值
  4. 点击「年龄+1」:依赖组件重建,年龄数值+1
  5. 控制台打印的 build 日志可验证:只有状态变化相关的组件才会重建,实现高效刷新