迹忆客 专注技术分享

当前位置:主页 > 学无止境 >

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

作者:迹忆客 最近更新:2022/08/28 浏览次数:

介绍

在撰写本文时,已经有许多作为外部软件包提供的解决方案。 因为 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)的东西。 它还必须能够处理不同服务、页面(屏幕)、存储库等的状态……如果有一些用于 initdispose 的钩子也很好。 并且不要忘记不要有任何必须放置在应用程序根目录上的父部件(如 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 个可选参数。 类型参数是状态类的类型和数据的类型(在“计数器”示例的情况下,它们是 CounterStateint)。 所需的参数是状态实例(静态属性 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 counter 示例


最后的想法

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

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

Flutter - 如何创建一个简单但有效的进度条

发布时间:2022/08/29 浏览次数:289 分类:学无止境

想象一下,我们有一个要导入数据库的大文件或一个需要一些时间才能完成的 API 请求。 我们不能只单击操作按钮并开始该过程,而不向用户指示某些东西在幕后工作。 这可能会产生错

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便