Flutter 状态管理
StatefulWidget
Flutter 中的组件,按状态划分:
StatelessWidget(无状态组件)
StatefulWidget(状态组件)
状态组件是包含可变状态的组件,状态的特点:
(1)、当组件构建完成后,可同步读取
(2)、可以在组件的生命周期中改变
按状态作用域划分
组件内私有状态(StatefulWidget)
跨组件状态共享(InheritedWidget, Provider)
全局状态(Redux, fish-redux, Mobx......)
状态组件的组成
StatefulWidget(组件本身不可变 @immutable)
State(将变化的状态放到 State 中维护)
import 'package:flutter/material.dart'; class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('StatefulWidget'), ), body: const MyState(), ); } } class MyState extends StatefulWidget { const MyState({Key? key}) : super(key: key); @override State<MyState> createState() => _MyStateState(); } class _MyStateState extends State<MyState> { int _num = 0; // 执行加的方法 void _increment() { setState(() { _num++; }); } // 减的方法 void _decrement() { setState(() { if (_num == 0) return; _num--; }); } @override Widget build(BuildContext context) { return Center( child: Column( children: [ // 减 ElevatedButton( onPressed: _decrement, child: const Icon(Icons.minimize), ), // 数字 Padding( padding: const EdgeInsets.all(20), child: Text('$_num'), ), // 加 ElevatedButton( onPressed: _increment, child: const Icon(Icons.add), ) ], ), ); } }
生命周期
initState() 组件对象插入到元素树中时,通常根据后台接口的返回数据对状态进行初始化
didChangeDependencies() 当前状态对象的依赖改变时
build() 组件渲染时
setState() 组件对象的内部状态变更时
didUpdateWidget() 组件配置更新时
deactivate() 组件对象在元素树中暂时移除时
dispose() 组件对象在元素树中永远移除时
// ignore_for_file: avoid_print import 'package:flutter/material.dart'; class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('LifeCycle'), ), body: const MyState(), ); } } class MyState extends StatefulWidget { const MyState({Key? key}) : super(key: key); @override State<MyState> createState() => _MyStateState(); } class _MyStateState extends State<MyState> { int _num = 0; @override void initState() { // TODO: implement initState super.initState(); print('initState, 执行initState方法,_num: $_num'); // 通常根据后台接口返回的数据对状态进行初始化 _num = 1; } @override void didChangeDependencies() { // TODO: implement didChangeDependencies super.didChangeDependencies(); print('didChangeDependencies, 当前状态对象的依赖改变了, _num: $_num'); } @override void didUpdateWidget(covariant MyState oldWidget) { // TODO: implement didUpdateWidget super.didUpdateWidget(oldWidget); print('didUpdateWidget, 组件配置更新了,_num: $_num'); } @override void deactivate() { // TODO: implement deactivate super.deactivate(); print('deactivate, 组件对象在元素树中暂时移除了,_num: $_num'); } @override void dispose() { // TODO: implement dispose super.dispose(); print('dispose, 组件对象在元素树中永远移除了,_num: $_num'); } // 执行加的方法 void _increment() { setState(() { print('setState, 组件对象的内部状态变更了,_num: $_num'); _num++; }); } // 减的方法 void _decrement() { setState(() { print('setState, 组件对象的内部状态变更了,_num: $_num'); if (_num == 0) return; _num--; }); } @override Widget build(BuildContext context) { print('build, 组件渲染时,_num: $_num'); return Center( child: Column( children: [ // 减 ElevatedButton( onPressed: _decrement, child: const Icon(Icons.minimize), ), // 数字 Padding( padding: const EdgeInsets.all(20), child: Text('$_num'), ), // 加 ElevatedButton( onPressed: _increment, child: const Icon(Icons.add), ) ], ), ); } }
InheritedWidget
What:
提供沿树向下,共享数据的功能
即子组件可以获取父组件(InheritedWidget 的子类)的数据
Why:
依赖构造函数传递数据的方式不能满足业务需求。如图,组件D和组件G共享的数据是通过组件A取数据以后逐级向下透传的,也就是说数据需要逐级传递,比较麻烦,而且不是所有组件都需要相关数据
所以,需要一个新的,更好的跨组件数据传输方案。图中,所有的组件都可以获取顶层父组件 InheritedWidget 的数据,可以直接获取,不需要依赖其它组件传递
How:
BuildContext.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()
构造函数
InheritedWidget({Key? key, required Widget child})
InhertiedWidget抽象类包含一个常量构造器,继承该抽象类的子类可以提供一个常量构造器,在使用这些子类时可以用 const 表达式来调用
child:Widget,表示在组件树中挂载在该子类下的组件
key:Key?,表示要传递的数据
import 'package:flutter/material.dart'; class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('InheritedWidget'), ), body: const CalculatorState(), ); } } // 组件一:挂载在 InheritedWidget 下,它提供了一个可共享的数据 num class CalculatorState extends StatefulWidget { const CalculatorState({Key? key}) : super(key: key); @override State<CalculatorState> createState() => _CalculatorStateState(); } class _CalculatorStateState extends State<CalculatorState> { int _num = 0; // 执行加的方法 void _increment() { setState(() { _num++; }); } // 减的方法 void _decrement() { setState(() { if (_num == 0) return; _num--; }); } @override Widget build(BuildContext context) { // 要调用 of 方法,of 方法中的参数 context 必须是 InheritedWidget 的后代 // 也就是说,组件必须挂在 InheritedWidget 下面 return ShareDataWidget( // 提供 ShareDataWidget 必须的两个属性 num 和 child num: _num, child: Center( child: Column( children: [ // 减 ElevatedButton( onPressed: _decrement, child: const Icon(Icons.minimize), ), // 数字 const Padding( padding: EdgeInsets.all(20), // 跨组件访问数据 child: MyCounter(), ), // 加 ElevatedButton( onPressed: _increment, child: const Icon(Icons.add), ) ], ), ), ); } } // 组件二:调用 of() 方法,获取 InheritedWidget 共享的数据 num class MyCounter extends StatefulWidget { const MyCounter({Key? key}) : super(key: key); @override State<MyCounter> createState() => _MyCounterState(); } class _MyCounterState extends State<MyCounter> { @override Widget build(BuildContext context) { // 使用 InheritedWidget 中的共享数据 if (ShareDataWidget.of(context)?.num == null) { return const Text('0'); } return Text(ShareDataWidget.of(context)!.num.toString()); } } // 数据共享组件 class ShareDataWidget extends InheritedWidget { const ShareDataWidget({ Key? key, required this.num, required this.child, }) : super(key: key, child: child); final int num; final Widget child; // 静态方法 of 用于调用 BuildContext.dependOnInheritedWidgetOfExactType // 这样做的目的是为了让类可以定义自己的回调逻辑,即便该类没有出现在 context 上下文中 static ShareDataWidget? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>(); } @override bool updateShouldNotify(ShareDataWidget oldWidget) { return true; } }
Provider
Provider 是对 InheritedWidget 的封装,使其使用更容易使用且易于复用
文档:https://pub.flutter-io.cn/packages/provider
安装:flutter pub add provider
引用:import 'package:provider/provider.dart';
优点:
(1)、简化资源的分配与处置
(2)、懒加载
(3)、创建新类时减少大量的模板代码
(4)、支持 DevTools
实现原理
ChangeNotifier:class,可以封装应用程序的状态,用于向监听器发送通知,如果被定义为 ChangeNotifier,就可以订阅它的状态变化。对于简单的程序,一个 ChangeNotifier 就可以满足需求;对于复杂的应用,由于会有多个模型,所以可能会有多个 ChangeNotifier
ChangeNotifierProvider:widget,用于监听 ChangeNotifier 的实例并将其暴露给子孙节点,并且当 ChangeNotifier.notifyListeners 被调用时,会重新构建。虽然数据是单独存放在 ChangeNotifier 中,但是面对组件时,直接提供数据的是 Provider
如果你想提供更多状态,可以使用 MultiProvider:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: const MyApp(),
),
);
}
区别于 InheritedWidget 把状态存放在根节点上,Provider 会把状态和根节点做分割处理,状态会被提取出来保存在 ChangeNotifier 组件中,使用数据时,需要把数据注册到根节点 ChangeNotifierProdiver 上,后续的逻辑基本相同。
图中的 Producer (生产者)组件修改了数据以后,ChangeNotifierProvier (观察者)就会做两件事情,一是通知 ChangeNotifier(被观察者)更新 data,二是通知所有的 Listener(听众,即消费者)更新UI界面。也就是说,Provider 会把整个应用分成三个部分:
(1)、数据部分
(2)、监控数据的部分
(3)、UI界面
整个过程也可以类比 MVVM 的状态管理模式
使用步骤:
(1)、创建数据模型
(2)、创建 Provider
(3)、在子组件中使用数据模型
示例:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Step 1 // 1、创建数据模型 class LikesModel extends ChangeNotifier { // 模拟一个私有变量(实际需要把这个类放在一个单独的文件中) int _counter = 0; // 模拟一个私有的getter int get counter => _counter; void incrementCounter() { // 累加 _counter++; // 通知 UI 更新(监听了这个模型的组件重新构建) notifyListeners(); } } // Step 2 class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 2、创建 Provider (注册数据模型) return ChangeNotifierProvider( create: (BuildContext context) => LikesModel(), child: Scaffold( appBar: AppBar( title: const Text('Provider'), ), body: const MyHomePage(), ), ); } } // Step 3 class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(10), child: Column( children: [ // 3、在子组件中使用数据模型 Text( '点赞了:${context.watch<LikesModel>().counter} 次', style: const TextStyle( color: Colors.red, fontSize: 26, ), ), TextButton( onPressed: () => context.read<LikesModel>().incrementCounter(), child: const Icon(Icons.thumb_up), ), ], ), ); } }
读取值
可以使用 BuildContext 上的扩展属性(由 provider 注入)
(1)、context.watch<T>(),widget 能够监听到 T 类型的 provider 发生的改变
(2)、context.read<T>(),直接返回 T,不会监听改变
(3)、context.select<T, R>(R cb(T value)),允许 widget 只监听 T 上的一部分内容的改变
需要注意的是,context.read<T>() 方法不会在值变化时让 widget 重新构建,并且不能在 StatelessWidget.build 和 State.build 内调用。换句话说,它可以在除了这两个方法以外的任意位置调用
Text( // 此处如果使用 context.read 来读取 counter,将监听不到状态更改,视图不会更新 '点赞了:${context.watch<LikesModel>().counter} 次', style: const TextStyle( color: Colors.red, fontSize: 26, ), ),
这些方法会从传入的 BuildContext 关联的 widget 开始,向上查找 widget 树,并返回查找到的层级最近的 T 类型的 provider(未找到时将抛出错误)。例如,把 Step3 中的 MyHomePage 直接作为 Step2 中的 body 组件,就会报如下错误
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Step 1 // 1、创建数据模型 class LikesModel extends ChangeNotifier { // 模拟一个私有变量(实际需要把这个类放在一个单独的文件中) int _counter = 0; // 模拟一个私有的getter int get counter => _counter; void incrementCounter() { // 累加 _counter++; // 通知 UI 更新(监听了这个模型的组件重新构建) notifyListeners(); } } // Step 2 class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 2、创建 Provider (注册数据模型) return ChangeNotifierProvider( create: (BuildContext context) => LikesModel(), child: Scaffold( appBar: AppBar( title: const Text('Provider'), ), // todo: 注意看这里,会产生异常 body: Container( padding: const EdgeInsets.all(10), child: Column( children: [ // 3、在子组件中使用数据模型 Text( '点赞了:${context.watch<LikesModel>().counter} 次', style: const TextStyle( color: Colors.red, fontSize: 26, ), ), TextButton( onPressed: () => context.read<LikesModel>().incrementCounter(), child: const Icon(Icons.thumb_up), ), ], ), ), ), ); } }
ProviderNotFoundException (Error: Could not find the correct Provider<LikesModel> above this Home Widget
大概意思是说,Home组件的 BuildContext 不包含 Provider<LikesModel>,产生这个错误的原因是使用的 BuildContext 是当前正在读取数据的 provider 的祖先元素,当你创建好一个 provider 以后立即读取其数据就会报这个错。
解决方法:
(1)、将上下文传递给与构建方法分离。参考示例中的 Step2、Step3 的写法,拆成两个组件
(2)、将上下文包装在 Builder 中
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Step 1 // 1、创建数据模型 class LikesModel extends ChangeNotifier { // 模拟一个私有变量(实际需要把这个类放在一个单独的文件中) int _counter = 0; // 模拟一个私有的getter int get counter => _counter; void incrementCounter() { // 累加 _counter++; // 通知 UI 更新(监听了这个模型的组件重新构建) notifyListeners(); } } // Step 2 class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 2、创建 Provider (注册数据模型) return ChangeNotifierProvider( create: (BuildContext context) => LikesModel(), child: Scaffold( appBar: AppBar( title: const Text('Provider'), ), // todo: 修改点:将上下文包装在 Builder 中 body: Builder(builder: (BuildContext context) { return Container( padding: const EdgeInsets.all(10), child: Column( children: [ // 3、在子组件中使用数据模型 Text( '点赞了:${context.watch<LikesModel>().counter} 次', style: const TextStyle( color: Colors.red, fontSize: 26, ), ), TextButton( onPressed: () => context.read<LikesModel>().incrementCounter(), child: const Icon(Icons.thumb_up), ), ], ), ); }), ), ); } }
DataTable
DataTable 是 Flutter 中的表格
columns:声明表头列表
DataColumn:表头单元格
rows:声明数据列表
DataRow:一行数据
DataCell:数据单元格
import 'package:flutter/material.dart'; class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('DataTable'), ), body: ListView( children: const [UserTable()], ), ); } } class UserTable extends StatefulWidget { const UserTable({Key? key}) : super(key: key); @override State<UserTable> createState() => _UserTableState(); } class _UserTableState extends State<UserTable> { @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 1), child: SingleChildScrollView( child: DataTable( // 表头 columns: const [ // 表头单元格 DataColumn(label: Text('姓名')), DataColumn(label: Text('年龄')), DataColumn(label: Text('性别')), DataColumn(label: Text('简介')), ], // 表格主体 rows: const [ // 行 DataRow(cells: [ // 列 DataCell(Text('张三')), DataCell(Text('18')), DataCell(Text('男')), DataCell(Text('一首张三的歌')), ]), DataRow(cells: [ DataCell(Text('二丫')), DataCell(Text('22')), DataCell(Text('女')), DataCell(Text('没有简介')), ]), ], ), ), ); } }
import 'package:flutter/material.dart'; class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('DataTable'), ), body: const UserTable(), ); } } class User { String name; int age; String gender; String intro; bool selected; User(this.name, this.age, this.gender, this.intro, {this.selected = false}); } class UserTable extends StatefulWidget { const UserTable({Key? key}) : super(key: key); @override State<UserTable> createState() => _UserTableState(); } class _UserTableState extends State<UserTable> { List<User> data = [ User('美国队长', 200, '男', '最会甩锅的男人'), User('钢铁侠', 72, '男', 'i love you more than 4000'), User('蜘蛛侠', 18, '男', '彼得帕克和玛莉简拍照时被五彩毒蜘蛛咬了', selected: true), User('黑寡妇', 36, '女', '把你的洗澡水给杜兰特喝一点吧'), ]; bool _sortAscending = true; List<DataRow> _getUserRows() { List<DataRow> dataRows = []; for (int i = 0; i < data.length; i++) { dataRows.add( DataRow( selected: data[i].selected, onSelectChanged: (selected) { // ignore: avoid_print setState(() { data[i].selected = selected!; }); }, cells: [ DataCell(Text(data[i].name)), DataCell(Text('${data[i].age}')), DataCell(Text(data[i].gender)), DataCell(Text(data[i].intro)), ], ), ); } return dataRows; } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 1), child: SingleChildScrollView( scrollDirection: Axis.horizontal, // 表格 child: DataTable( // 需要排序的列 sortColumnIndex: 1, // 排序方式,false:降序,true:升序 sortAscending: _sortAscending, // 行高 dataRowHeight: 100, // 每一行的首位两列的表格边缘和父容器间的外边距 horizontalMargin: 10, // 单元格的水平外边距 columnSpacing: 100, // 表头 columns: [ const DataColumn(label: Text('姓名')), DataColumn( label: const Text('年龄'), // 该列是否表示数值,如是,则该列内容右对齐 numeric: true, // 排序:ascending false:降序,true:升序 onSort: (int columnIndex, bool ascending) { setState(() { _sortAscending = ascending; if (ascending) { // compareTo:负数表示a小于b,正数表示a大于b data.sort((a, b) => a.age.compareTo(b.age)); } else { data.sort((a, b) => b.age.compareTo(a.age)); } }); }, ), const DataColumn(label: Text('性别')), const DataColumn(label: Text('介绍')), ], // 表格主体 rows: _getUserRows(), ), ), ); } }