Mutable是上篇文章MoLc组件中的一个类,由于和其他组件没有耦合度,所以单拎出来和大家分享一下。
个人并不没有使用过GetX,但社区关于GetX的文章太多了,一不小心就看了一两个。有一篇文章的评论说道,GetX的精髓在于dart单线程特性结合一个proxy的静态变量实现了控制反转。利用这点,我们来复刻一个GetX。
要做到如同GetX一样的响应式刷新,即赋值即刷新,首先需要设计一个GetX中Rx类型和.obs。
响应式数据
先上代码:
class Mutable<T> {
late Queue<T> _data;
T get value {
return _data.last;
}
set value(T value) {
if (value == _data.last) return;
_data.add(value);
}
Mutable(T value) : _data = Queue.from([value]);
}
GetX有一个特性是值不变的话不会刷新,这里就想到肯定需要进行对比新老数据的过程,于是存放新老数据的数据结构选用了Queue,然后在set value时加入队列,在get value时取最新的last。这样就实现了一个基本的数据结构。
基本类型转换
这里以String为例:
extension MutableBoolExt on String {
Mutable<String> get mt => Mutable(this);
}
为String提供一个.mt扩展函数就可以将String转化为Mutable<String>。像这样:
final abc = 'abcText'.mt;
但此时你没办法用abs调用String的函数和操作符, 还需要将String源码复制出来写成Mutable<String>的扩展。
extension MutableStringFunExt on Mutable<String?> {
String operator +(String val) => (value ?? '') + val;
bool? endsWith(String other) {
return value?.endsWith(other);
}
bool? startsWith(Pattern pattern, [int index = 0]) {
return value?.startsWith(pattern, index);
}
bool? get isEmpty => value?.isEmpty;
bool? get isNotEmpty => value?.isNotEmpty;
...
}
代理刷新
然后给Mutable数据加上刷新来实现控制反转。也就是实现GetX中的Obx()。
abstract class RefreshDelegate {
static MapEntry<int, RefreshCallback>? _delegate;
Map<int, RefreshCallback>? _refreshMap;
}
先解释一下上面的两个变量, _delegate是用来临时存放widget的刷新函数,_refreshMap是用来存放所有用到该Mutable值的Widget的刷新函数。看下面代码就清楚了:
class MutableWidget extends StatefulWidget {
final WidgetBuilder builder;
const MutableWidget(this.builder);
@override
State<StatefulWidget> createState() => _MutableState();
}
class _MutableState extends State<MutableWidget> {
bool refresh() {
if (mounted) {
setState(() {});
}
return mounted;
}
@override
Widget build(BuildContext context) {
RefreshDelegate._delegate = MapEntry(hashCode, refresh);
return widget.builder(context);
}
}
MutableWidget是用来包裹Mutable,也就是GetX中的Obx。
让Mutable继承RefreshDelegate, 并在get中将静态变量_delegate存到刷新map里
class Mutable<T> extends RefreshDelegate {
...
T get value {
final entry = RefreshDelegate._delegate!;
_refreshMap ??= SplayTreeMap();
_refreshMap![entry.key] = entry.value;
return _data.last;
}
set value(T value) {
if (value == _data.last) return;
if (_data.length == _MUTABLE_SIZE) {
_data.removeFirst();
}
_data.add(value);
final removeSet = Set();
_refreshMap?.forEach((k, v) {
if (!v.call()) removeSet.add(k);
});
removeSet.forEach((e) {
_refreshMap?.remove(e);
});
}
...
}
所以整个流程就是,MutableWidget在build的时候,会先将它自身的刷新函数赋值给静态变量_delegate。接下来build中用到的Mutable值会在get时将_delegate也就是刷新函数存入刷新函数Map中。最后在给Mutable赋值也就是set的时候,在刷新map中取出所有的刷新函数并刷新。如果刷新失败,也就是MutableWidget已经被dispose,那么在刷新map中移除该刷新。
使用
大功告成之后我们看下基本使用:
class ExampleWidget extends StatelessWidget {
final abc = 100.mt;
@override
Widget build(BuildContext context) {
return MutableWidget(
(context) => Row(
children: [
Text('${abc.value}'),
TextButton(
onPressed: () {
abc.value += 1;
},
child: Text('+1'),
),
],
),
);
}
}
和GetX的.obs和Obx()类似,关注点几乎只有.mt和MutableWidget()。MutableWidget内依赖Mutable值的小组件都是响应式刷新的,支持MutableWidget嵌套,也支持一个Mutable值被多个MutableWidget引用。
源码
下面是开箱即用的源码,只有一个类,如果你只是想要实现颗粒度刷新和响应式刷新,可以直接拿到项目中使用,而不必依赖臃肿的GetX。
import 'dart:collection';
import 'package:flutter/widgets.dart';
typedef RefreshCallback = bool Function();
const int _MUTABLE_SIZE = 3;
class Mutable<T> extends RefreshDelegate {
late Queue<T> _data;
T get value {
final entry = RefreshDelegate._delegate!;
_refreshMap ??= SplayTreeMap();
_refreshMap![entry.key] = entry.value;
return _data.last;
}
set value(T value) {
if (value == _data.last) return;
if (_data.length == _MUTABLE_SIZE) {
_data.removeFirst();
}
_data.add(value);
final removeSet = Set();
_refreshMap?.forEach((k, v) {
if (!v.call()) removeSet.add(k);
});
removeSet.forEach((e) {
_refreshMap?.remove(e);
});
}
Mutable(T value) : _data = Queue.from([value]);
@override
String toString() {
return value.toString();
}
}
class MutableWidget extends StatefulWidget {
final WidgetBuilder builder;
const MutableWidget(this.builder);
@override
State<StatefulWidget> createState() => _MutableState();
}
class _MutableState extends State<MutableWidget> {
bool refresh() {
if (mounted) {
setState(() {});
}
return mounted;
}
@override
Widget build(BuildContext context) {
RefreshDelegate._delegate = MapEntry(hashCode, refresh);
return widget.builder(context);
}
}
abstract class RefreshDelegate {
static MapEntry<int, RefreshCallback>? _delegate;
Map<int, RefreshCallback>? _refreshMap;
}
extension MutableStringExt on String {
Mutable<String> get mt => Mutable(this);
}
extension MutableBoolExt on bool {
Mutable<bool> get mt => Mutable(this);
}
extension MutableIntExt on int {
Mutable<int> get mt => Mutable(this);
}
extension MutableDoubleExt on double {
Mutable<double> get mt => Mutable(this);
}
extension MutableNumExt on num {
Mutable<num> get mt => Mutable(this);
}
extension MutableListExtension<T> on List<T> {
Mutable<List<T>> get mt => Mutable(this);
}
extension MutableT<T> on T {
Mutable<T> get mt => Mutable<T>(this);
}
extension MutableStringFunExt on Mutable<String?> {
String operator +(String val) => (value ?? '') + val;
int? compareTo(String other) {
return value?.compareTo(other);
}
bool? endsWith(String other) {
return value?.endsWith(other);
}
bool? startsWith(Pattern pattern, [int index = 0]) {
return value?.startsWith(pattern, index);
}
int? indexOf(Pattern pattern, [int start = 0]) {
return value?.indexOf(pattern, start);
}
int? lastIndexOf(Pattern pattern, [int? start]) {
return value?.lastIndexOf(pattern, start);
}
bool? get isEmpty => value?.isEmpty;
bool? get isNotEmpty => value?.isNotEmpty;
String? substring(int startIndex, [int? endIndex]) {
return value?.substring(startIndex, endIndex);
}
String? trim() {
return value?.trim();
}
String? trimLeft() {
return value?.trimLeft();
}
String? trimRight() {
return value?.trimRight();
}
String? padLeft(int width, [String padding = ' ']) {
return value?.padLeft(width, padding);
}
String? padRight(int width, [String padding = ' ']) {
return value?.padRight(width, padding);
}
bool? contains(Pattern other, [int startIndex = 0]) {
return value?.contains(other, startIndex);
}
String? replaceAll(Pattern from, String replace) {
return value?.replaceAll(from, replace);
}
List<String>? split(Pattern pattern) {
return value?.split(pattern);
}
List<int>? get codeUnits => value?.codeUnits;
Runes? get runes => value?.runes;
String? toLowerCase() {
return value?.toLowerCase();
}
String? toUpperCase() {
return value?.toUpperCase();
}
Iterable<Match>? allMatches(String string, [int start = 0]) {
return value?.allMatches(string, start);
}
Match? matchAsPrefix(String string, [int start = 0]) {
return value?.matchAsPrefix(string, start);
}
}
extension MutableBoolFunExt on Mutable<bool?> {
bool get isTrue => value ?? false;
bool get isFalse => !isTrue;
bool operator &(bool other) => other && (value ?? false);
bool operator |(bool other) => other || (value ?? false);
bool operator ^(bool other) => !other == value;
Mutable<bool?> toggle() {
if (value != null) {
value = !value!;
}
return this;
}
}
extension MutableNumFunExt<T extends num> on Mutable<T?> {
num? operator *(num other) {
if (value != null) {
return value! * other;
}
}
num? operator %(num other) {
if (value != null) {
return value! % other;
}
}
double? operator /(num other) {
if (value != null) {
return value! / other;
}
}
int? operator ~/(num other) {
if (value != null) {
return value! ~/ other;
}
}
num? operator -() {
if (value != null) {
return -value!;
}
}
num? remainder(num other) => value?.remainder(other);
bool? operator <(num other) {
if (value != null) {
return value! < other;
}
}
bool? operator <=(num other) {
if (value != null) {
return value! <= other;
}
}
bool? operator >(num other) {
if (value != null) {
return value! > other;
}
}
bool? operator >=(num other) {
if (value != null) {
return value! >= other;
}
}
bool? get isNaN => value?.isNaN;
bool? get isNegative => value?.isNegative;
bool? get isInfinite => value?.isInfinite;
bool? get isFinite => value?.isFinite;
num? abs() => value?.abs();
num? get sign => value?.sign;
int? round() => value?.round();
int? floor() => value?.floor();
int? ceil() => value?.ceil();
int? truncate() => value?.truncate();
double? roundToDouble() => value?.roundToDouble();
double? floorToDouble() => value?.floorToDouble();
double? ceilToDouble() => value?.ceilToDouble();
double? truncateToDouble() => value?.truncateToDouble();
num? clamp(num lowerLimit, num upperLimit) =>
value?.clamp(lowerLimit, upperLimit);
int? toInt() => value?.toInt();
double? toDouble() => value?.toDouble();
String? toStringAsFixed(int fractionDigits) =>
value?.toStringAsFixed(fractionDigits);
String? toStringAsExponential([int? fractionDigits]) =>
value?.toStringAsExponential(fractionDigits);
String? toStringAsPrecision(int precision) =>
value?.toStringAsPrecision(precision);
}