Flutter — 简单的状态管理,无需任何外部包

介绍

在撰写本文时,已经有许多作为外部软件包提供的解决方案。 因为 Flutter 提供了选择架构的自由,所以我们有机会使用我们认为适合自己的任何状态管理。 但正如已经多次说过的“强大的力量带来了巨大的责任”,这就是为什么这个话题如此敏感并且有许多不同的解决方案。 另外,我需要说没有坏包,因为每个包都有一个很好的用例。 目前 Flutter 团队自己推荐的关于这个主题的包列表是:

  • Provider
  • Riverpod
  • setState
  • inheritedWidget & inheritedModel
  • Redux
  • Fish-Redux
  • BLoC/Rx
  • GetIt
  • MobX
  • Flutter Commands
  • Binder
  • GetX
  • states_rebuilder
  • Triple Pattern

这里我们不会详细介绍它们中的任何一个。 如果大家想了解更多信息,可以在此处访问 Flutter 文档并阅读它们。


有点不同的解决方案

我想尝试的是构建一些简单且独立于任何其他包(纯dart和 flutter)的东西。 它还必须能够处理不同服务、页面(屏幕)、存储库等的状态……如果有一些用于 init 和 dispose 的钩子也很好。 并且不要忘记不要有任何必须放置在应用程序根目录上的父部件(如 runApp 的子部件)。 所以,最后,我们决定使用流来广播状态,而 StreamBuilder 可以在前端部分需要时拦截它们。 这应该足以解耦 UI 和业务逻辑。 这就是我的状态管理基本逻辑的样子:

import 'dart:async';

class BaseState<T> {
  late T _state;
  late StreamController<T> _streamController;

  BaseState(T initState, [bool autoDispose = false]) {
    _streamController = StreamController<T>.broadcast();
    addToSink(initState);
    init();
    _streamController.onCancel = () {
      if (autoDispose == true) {
        close();
      }
    };
  }

  T get state => _state;
  set state(T newState) {
    addToSink(newState);
  }

  void addToSink(T state) {
    _state = state;
    _streamController.sink.add(_state);
  }

  void error(Object err) {
    _streamController.addError(err);
    throw err;
  }

  Stream<T> get stream => _streamController.stream;

  void close() {
    _streamController.close();
    dispose();
  }

  void init() {}
  void dispose() {}
}

我们需要的只是不到 50 行代码。 我们有一个所有状态都可以扩展的基本类。 “T”类型是我们要为其保留状态的数据类型。 在下面的“计数器”示例中,这是 int。 在构造函数中,我们还触发了可以从父类重写的 init() 方法,并且我们准备了仅当 autoDispose 设置为 true 时才能触发的 close() 方法。 如果需要,可以覆盖方法 dispose() 并用于清理任何资源。 我们的状态有一个 getter 和一个 setter。 setter 调用“addToSink”方法,将新状态添加到流中,准备好向所有监听方广播。 如果遇到问题,我们可以调用“error”方法,这样我们就可以直接在 UI 上指示它,这要归功于“basestate”小部件。

import 'package:flutter/widgets.dart';
import './basestate.logic.dart';

class BaseStateWidget<T extends BaseState, DataType> extends StatelessWidget {
  final T state;
  final Widget Function(DataType) builder;
  final Widget Function(DataType)? onClose;
  final Widget Function(dynamic)? onError;

  const BaseStateWidget({
    required this.state,
    required this.builder,
    this.onClose,
    this.onError,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      initialData: state.state,
      stream: state.stream,
      builder: (context, snapshot) {
        if (snapshot.hasError == true) {
          return onError?.call(snapshot.error) ?? const SizedBox();
        } else if (snapshot.hasData) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.waiting:
            case ConnectionState.active:
              return builder(snapshot.data as DataType);
            case ConnectionState.done:
              return onClose?.call(snapshot.data as DataType) ?? const SizedBox();
          }
        }
        return const SizedBox();
      },
    );
    //return builder(context, d);
  }
}

小部件非常简单。 它有 2 个类型参数、2 个必需参数和 2 个可选参数。 类型参数是状态类的类型和数据的类型(在“计数器”示例的情况下,它们是 CounterState 和 int)。 所需的参数是状态实例(静态属性 CounterState.instance)和构建器函数,它们将负责根据我们提供的状态构建不同的 UI。 可选参数 onError 可用于在屏幕上显示错误,onClose 可用于更新 UI,指示将不再有状态。


标志性的反例

import 'package:flutter/material.dart';
import 'basestate/basestate.logic.dart';
import 'basestate/basestate.widget.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: {
        '/': (context) => const SamplePage(), 
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Login"),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: BaseStateWidget<CounterState, int>(
                  state: CounterState.instance,
                  builder: (state) {
                    return Text(state.toString());
                  },
                ),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  OutlinedButton(
                    child: const Icon(Icons.exposure_minus_1),
                    onPressed: CounterState.instance.minus,
                  ),
                  OutlinedButton(
                    child: const Icon(Icons.plus_one),
                    onPressed: CounterState.instance.plus,
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class CounterState extends BaseState<int> {
  static CounterState instance = CounterState(0);

  CounterState(int init) : super(init);

  void plus() {
    state = state + 1;
  }

  void minus() {
    state = state - 1;
  }
}

Flutter — 简单的状态管理,无需任何外部包


最后的想法

不用说,flutter 中的状态管理是一个非常复杂的话题,在每个项目开始时都要考虑很多。 那里的每个软件包都有其优点和缺点,并且没有最终正确的解决方案。 我们在这里展示的绝不是生产就绪的代码。 它更多的是一个概念。

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Flutter — 简单的状态管理,无需任何外部包