flutter 状态管理

本文最后更新于:2022年4月22日 上午

flutter 状态管理

InheritedWidget

通过 InheritedWidget可以实现状态管理,多个组件共享数据

1. 创建状态

步骤

  1. 继承 InheritedWidget
  2. 重写 updateShouldNotify方法
  3. 编写 XXX.of()方法
  4. 编写需要共享的数据,且在构造函数中接收 child和 需要共享状态的参数
// 编写状态
class UserinfoState extends InheritedWidget {
  final String username; // 共享的数据

  UserinfoState({required Widget child, required this.username}) : super(child: child);

  static UserinfoState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType()!;
  }

  @override
  bool updateShouldNotify(UserinfoState oldWidget) {
    // 当两个值不一致的返回 true ,会执行 didChangeDependencies() 方法
    return oldWidget.username != username;
  }
}

2. 在共同的父组件共享状态

在父组件中使用状态 Widget,且将 状态的值传入,将需要使用状态的子组件置于 child

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  String username = 'flutter';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: UserinfoState(
          username: username,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [Text('需要共享的值:$username'), Child1(), Child2()],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => {setState(() => (username = username == 'flutter' ? 'dart' : 'flutter'))},
        child: Icon(Icons.change_circle_outlined),
      ),
    );
  }
}

3. 子组件消费数据

通过 XXX.of(context) 来取到状态


class Child1 extends StatelessWidget {
  const Child1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final username = UserinfoState.of(context).username;

    return Container(color: Colors.red, child: Text('child1: $username'));
  }
}

class Child2 extends StatelessWidget {
  const Child2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final username = UserinfoState.of(context).username;

    return Container(color: Colors.green, child: Text('child2: $username'));
  }
}

Provider

Providerflutter官方推出的一个状态管理库,也有 reduxmobxbloc等状态管理库,但是在官方推出了状态管理库后,基本上都转向了官方库。但是在 getX推出后,情况发现一些改变,总之,Provider是学习 flutter值得学习的一个库。

添加到项目依赖

flutter pub add provider

1. 创建全局容器

runApp 中 使用 MultiProvider

main() => runApp(
      // 全局只有一个状态
      // ChangeNotifierProvider(
      //   create: (ctx) => CounterStore(),
      //   child: MyApp(),
      // ),

      // 全局拥有多个需要共享的状态 MultiProvider
      MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (ctx) => CounterStore()),
          ChangeNotifierProvider(create: (ctx) => TokenStore()),
        ],
        child: MyApp(),
      ),
    );

2. 创建状态

  1. 继承自 ChangeNotifier
  2. 编写 get/set
  3. set中值被修改后调用 notifyListeners()
// 全局状态1 编写需要共享的状态
class CounterStore extends ChangeNotifier {
  int _counter = 0;

  set counter(int newCounter) {
    _counter = newCounter;
    notifyListeners(); // 设置的时候需要发送通知
  }

  // 该名称为使用时的名称,
  int get counter => _counter;
}

// 全局状态2
class TokenStore extends ChangeNotifier {
  String _token = ''; // 内部的

  String get token => _token;

  set token(newToken) {
    _token = newToken;
    notifyListeners();
  }

  // 修改token
  void changeToken(String newToken) => token = newToken;
}

3. 消费数据(状态)

Provier消费数据的方式在多次更新更新后有很多种

context.watch

context.watch<T>()widget 能够监听到 T 类型的 provider 发生的改变。

class Child2 extends StatelessWidget {
  const Child2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
        // 获取全局状态
        child: Text('token: ${context.watch<TokenStore>().token}')
    );
  }
}

context.read

该方法在消费状态的时候不会对状态产生依赖

但是该方法不能在 StatelessWidgetStatefulWidgetbuild方法中使用,除此之外的任何地方都可以

@override
void initState() {
  super.initState();

  // 获取全局方式
  // 在 生命周期 组件构建完成的下一帧 请求数据存入全局 store,如果直接直接存会报错,文档有对应说明
  Future.microtask(() => context.read<TokenStore>().changeToken('newToken 12532535'));

}

context.select

该方法可以将状态 进行一些转换

runApp(
 Builder(builder: (context) {
   final title = context.select<Movie?, String>((movie) => movie?.title);

   if (title == null) Text('no Movie found');
   return Text(title);
 }),
);

Provider.of(context)

通过指定泛型 Provider.of<XxxState>(context)的方式来消费数据,

当可选参数 listen: falsecontext.read的作用相似

当可选参数 listen: true或不传时和 context.watch的作用相似

class Child1 extends StatelessWidget {
  const Child1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 获取全局状态
    final counter = Provider.of<CounterStore>(context, listen: false).counter; // 获取 全局的状态

    return Container(color: Colors.red, child: Text('child1: $counter'));
  }
}

以上几种都是较为简单的使用方式,且代码阅读性强

Consumer

可优化子组件的 build

child: Consumer<CounterStore>(
  builder: (context, value, child) {
    return FloatingActionButton(
      onPressed: () => value.counter += 1,
      child: child, // 不会重新构建 icon
    );
  },
  child: Icon(Icons.change_circle_outlined)
)

Consumerx

如果对多个数据有依赖可以通过 Consumer[2-6]的方式来添加多个依赖,至多多个,同事,builder方法中会依次传入对应的状态

child: Consumer2<CounterStore, TokenStore>(
  builder: (context, value, value2, child) {
    return FloatingActionButton(
      onPressed: () => value.counter += 1,
      child: child, // 不会重新构建 icon
    );
  },
  child: Icon(Icons.change_circle_outlined)
),

Selector

可显示指定是否重新build,且可以对状态进行转换

通过 shouldRebuild 函数的返回值来决定是否重新执行 builder

child: Selector<CounterStore, CounterStore>(
  selector: (p0, p1) => p1,
  shouldRebuild: (prev, next) => false, // 状态更新时不重新构建依赖
  builder: (context, value, child) {
    print('重新 构建'); // 只有第一次会打印,后续更新状态不会打印
    return FloatingActionButton(
      onPressed: () => value.counter += 1,
      child: Icon(Icons.change_circle_outlined), // 不会重新构建 icon
    );
  },
),

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。