Flutter | bloc 之 state 使用优化

前尘镜

前言

bloc,既是架构设计,也是状态管理。

年初入坑 bloc,在公司的项目中也一直在使用,个人感受:分层合理、逻辑清晰、使用方便。

并且它还提供了一个简洁版:cubit。多一种选择,多一种体验。

但是在使用过程中,偶尔会感觉更新 state 有点麻烦,相信很多使用 bloc 的朋友有同感。本文即是对此问题的思考。

如果想直接看答案,滚动到文末。

官方的 state

回顾一下 bloc 更新 UI 的步骤:

  1. copy 一个新的 state
  2. emit(newState)

下面是 bloc 的一个官方 demo 中 state 的代码:

part of 'login_bloc.dart';

class LoginState extends Equatable {
  const LoginState({
    this.status = FormzStatus.pure,
    this.username = const Username.pure(),
    this.password = const Password.pure(),
  });

  final FormzStatus status;
  final Username username;
  final Password password;

  LoginState copyWith({
    FormzStatus? status,
    Username? username,
    Password? password,
  }) {
    return LoginState(
      status: status ?? this.status,
      username: username ?? this.username,
      password: password ?? this.password,
    );
  }

  @override
  List<Object> get props => [status, username, password];
}

官方的 copyWith 方法可以迅速生成一个 newStatenewState 的属性由 copyWith 的参数控制,这些参数都是可选的,如果不传,赋值原来的值,因此,当我们只想修改某一个属性的时候,就只需要传那一个参数。

大部分时候,使用起来还是很舒服的。

遇到的问题

刚刚说了,大部分时候,使用起来还是很舒服的。就是说,还是有不舒服的时候。

比如,我想给上文 statepassword 赋值 null,怎么办?

官方 demo 的 copyWith,参数传 null,相当于赋值原来的值:

password: password ?? this.password,

如果要赋值 null,只有把 password: password ?? this.password 改成 password: password。但这样一来,每次调用 copyWith 方法的时候都要给参数 password 赋值,即使不想改变它的值。

这有点蛋疼。

如果有一堆属性都需要赋值 null,那就非常蛋疼了。

寻找答案

我搜寻全网,只为找到更加优雅的写法。

正好看到有个博主分享了他对 state 问题的优化:

class MainState {
  int selectedIndex;
  bool isExtended;

  MainState clone() {
    return MainState()
      ..selectedIndex = selectedIndex
      ..isExtended = isExtended;
  }
}

这位博主的思路是:先 clone 一个 newState,再修改 newState 的值,最后 emit(newState)

貌似没什么问题,用起也很方便,同时解决了官方 copyWith 给 state 的属性赋值 null 特别蛋疼的问题。

但是,解决了一个问题,又创造了一个更大的问题。

来回顾一下 bloc 的核心思想:

  • bloc 的核心思想是什么?
  • stream
  • stream 是什么?
  • 什么流?
  • 单向数据流

那位博主的问题在于:
为了可以更灵活的操作 state,去掉了 state 中各个属性的 final 修饰符,这样就可以直接修改 newState 的属性,但问题也在此:当前 state 的属性也可以直接修改了

这种写法虽然解决了眼前的问题,但是它违背了 bloc 单向数据流的设计思想,也因此存在一个非常大的隐患:UI 与 data 不一致

按照 bloc 的设计初衷,state 的修改只能通过调用 emit(newState) 来实现,并且,state 改变了,UI 一定会跟着改变,UI 改变的前提一定是 state 改变,UI 与 data 一定同步

如果把 state 的各个属性的 final 去掉,可以直接修改 state 的属性而不需调用 emit(newState),state 变了 UI 可以不变,这就导致 UI 和 state 可以不同步,单向数据流因此瓦解。

官方原话:

Bloc试图通过调节何时可以发生状态更改并在整个应用程序中强制采用一种更改状态的方式来使状态更改可预测

去掉 state 属性的 final 修饰符,意味着状态更改不可预测

故,那位博主对 state 的优化表面上是优化了,实际上挖了个坑。

三种优化方案

虽然官方的 copyWith 不能完全满足我们,但是我们只需要在它的基础上稍加修改。

先仿照官方写一个最普通的 state:


class LoginPageState {
  const LoginPageState({
    this.phone,
    this.password,
  });

  final String? phone;
  final String? password;

  LoginPageState copyWith({
    String? phone,
    String? password,
  }) {
    return LoginPageState(
      phone: phone ?? this.phone,
      password: password ?? this.password,
    );
  }
}

现在有个需求:将 password 值改为 null

方案一:给 copyWith 添加一个参数来控制

LoginPageState copyWith({
  String? phone,
  String? password,
  bool resetPassword = false,
}) {
  return LoginPageState(
    phone: phone ?? this.phone,
    password: resetPassword == true ? null : password ?? this.password,
  );
}

在原来的逻辑上,多了一层控制:

  • resetPassword 如果为 false,走原来的逻辑;
  • resetPassword 如果为 truepassword 直接赋值 null

使用:

// 给 password 赋值 null
final newState = state.copyWith(resetPassword: true);
emit(newState);

方案二:不添加属性,运用函数式编程,将参数类型改为 ValueGetter

LoginPageState copyWith({
  String? phone,
  ValueGetter<String?>? password,
}) {
  return LoginPageState(
    phone: phone ?? this.phone,
    password: password != null ? password() : this.password,
  );
}

通过函数精准控制 password 的值:

// 给 password 赋值 null
final newState = state.copyWith(password: () => null);
emit(newState);

这就是函数式编程的实际运用。

PS: 看源码可知 ValueGetter 是一个有返回值的函数:

typedef ValueGetter<T> = T Function();

方案三:借助三方插件 freezed

详情:

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

祖安狂人学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值