Flutter情感表达插件love的使用
Flutter情感表达插件love的使用
Love

Why
love 具有 ReactiveX、Redux 和 RxFeedback 的基因。因此它:
- 统一 - 一个就是一切,一切就是一个 (System<State, Event>)
- 声明式 - 系统首先声明,在调用 run 后开始执行
- 可预测 - 单向数据流
- 灵活 - 随着复杂应用可以很好地扩展
- 优雅 - 代码对人类来说易于阅读和编写
- 可测试 - 系统可以轻松进行测试
目录
库
- love - 仅 Dart 的状态管理库
- flutter_love - 提供处理常见场景的 Flutter 小部件
- flutter_love_provider - 提供基于 love 和 provider 的解决方案的 Flutter 小部件
计数器示例
// typedef CounterState = int;
abstract class CounterEvent {}
class Increment implements CounterEvent {}
class Decrement implements CounterEvent {}
void main() async {
final counterSystem = System<int, CounterEvent>
.create(initialState: 0)
.on<Increment>(
reduce: (state, event) => state + 1,
effect: (state, event, dispatch) async {
await Future<void>.delayed(const Duration(seconds: 3));
dispatch(Decrement());
},
)
.on<Decrement>(
reduce: (state, event) => state - 1,
)
.log()
.reactState(
effect: (state, dispatch) {
print('Simulate persistence save call with state: $state');
},
)
.onRun(effect: (initialState, dispatch) {
dispatch(Increment());
return null;
},);
final disposer = counterSystem.run();
await Future<void>.delayed(const Duration(seconds: 6));
disposer();
}
输出:
System<int, CounterEvent> Run
System<int, CounterEvent> Update {
event: null
oldState: null
state: 0
}
System<int, CounterEvent> Update {
event: Instance of 'Increment'
oldState: 0
state: 1
}
System<int, CounterEvent> Update {
event: Instance of 'Decrement'
oldState: 1
state: 0
}
System<int, CounterEvent> Dispose
我们希望这段代码能够自解释。如果你能猜出这段代码的作用,那就太好了!
此示例首先声明了一个计数器系统,状态是计数,事件是 increment
和 decrement
。然后我们运行系统来记录输出,6 秒后停止系统。
当前代码并不十分优雅,我们有更好的方法来实现相同的功能。当我们掌握新技能时,我们将逐步重构代码。我们保持这种写法,因为它是一个很好的起点,用于演示如何工作。
核心
它是如何工作的?

状态
状态是某个时刻的数据快照。
例如,计数器的状态是计数:
// typedef CounterState = int;
事件
事件是对已发生事情的描述。
例如,计数器的事件是 increment
和 decrement
,它们描述了发生了什么:
abstract class CounterEvent {}
class Increment implements CounterEvent {}
class Decrement implements CounterEvent {}
减少
减少是一个函数,描述了当事件发生时状态如何更新。
typedef Reduce<State, Event> = State Function(State state, Event event);
计数器示例:
...
.on<Increment>(
reduce: (state, event) => state + 1,
)
.on<Decrement>(
reduce: (state, event) => state - 1,
)
...
如果发生 increment
事件,我们增加计数;如果发生 decrement
事件,我们减少计数。
我们可以使它更简洁:
...
- .on<Increment>(
- reduce: (state, event) => state + 1,
- )
- .on<Decrement>(
- reduce: (state, event) => state - 1,
- )
+ .on<Increment>(
+ reduce: (state, event) => state + 1,
+ )
+ .on<Decrement>(
+ reduce: (state, event) => state - 1,
+ )
...
这对我们来说更优雅地读取和编写代码。
注意:减少是一个纯函数,其唯一目的是用当前状态和事件计算出新的状态。此函数中没有副作用。
那么如何处理副作用呢?
效果
效果是一个导致外部可观察效果的函数。
typedef Effect<State, Event> = void Function(State state, State? oldState, Event? event, Dispatch<Event> dispatch);
副作用:
- 呈现
- 日志
- 网络
- 持久化
- 分析
- 蓝牙
- 定时器
- …
以下是 日志效果
和 模拟效果
:
...
.log()
.reactState(
effect: (state, dispatch) {
print('Simulate persistence save call with state: $state');
},
)
...
那么异步的东西,比如 网络效果
或 定时器效果
如何处理:
...
.log()
.reactState(
effect: (state, dispatch) {
print('Simulate persistence save call with state: $state');
},
)
+ .on<Increment>(
+ effect: (state, event, dispatch) async {
+ await Future<void>.delayed(const Duration(seconds: 3));
+ dispatch(Decrement());
+ },
+ )
...
我们添加了一个 定时器效果
,当发生 increment
事件时,我们将在 3 秒后分发一个 decrement
事件以恢复计数。
我们还可以添加 持久化效果
:
...
.on<Increment>(
effect: (state, event, dispatch) async {
await Future<void>.delayed(const Duration(seconds: 3));
dispatch(Decrement());
},
)
+ .reactState(
+ effect: (state, dispatch) {
+ if (event != null && oldState != state) {
+ print('Simulate persistence save call with state: $state');
+ }
+ },
+ ),
...
此持久化保存功能将在状态更改时被调用,但初始状态将被跳过,因为大多数情况下初始状态是从持久层恢复的,无需再次保存。
运行
我们已经声明了我们的 counterSystem
:
final counterSystem = System<int, CounterEvent>
...;
在调用 run
之前,它不会做任何事情:
final disposer = counterSystem.run();
当调用 run
时,会返回一个 disposer
。稍后我们可以使用这个 disposer
来停止系统:
// 在 6 秒后停止系统
await Future<void>.delayed(const Duration(seconds: 6));
disposer();
效果细节
由于效果在这里扮演着重要角色,让我们深入研究一下。
效果触发
我们添加了 定时器效果
和 持久化效果
。现在,与其思考这是一个什么样的效果,不如关注这些效果是如何 触发 的:
...
.on<Increment>(
effect: (state, event, dispatch) async {
await Future<void>.delayed(const Duration(seconds: 3));
dispatch(Decrement());
},
)
.reactState(
effect: (state, dispatch) {
print('Simulate persistence save call with state: $state');
},
),
...
不难发现第一个 定时器效果
是在 increment
事件发生时触发的,第二个 持久化效果
是通过反应到状态变化而触发的。
这里,我们有两种 效果触发:
- 事件触发
- 状态触发
事件触发
事件触发会在事件满足某些条件时触发效果。
我们有一系列前缀为 on
的操作符(方法)来更好地实现这一点:
...
- .on<Increment>(
- effect: (state, event, dispatch) async {
- await Future<void>.delayed(const Duration(seconds: 3));
- dispatch(Decrement());
- },
- )
+ .on<Increment>(
+ effect: (state, event, dispatch) async {
+ await Future<void>.delayed(const Duration(seconds: 3));
+ dispatch(Decrement());
+ },
+ )
...
我们甚至可以在共享相同条件的情况下将 effect
移动到 reduce
旁边:
...
.on<Increment>(
reduce: (state, event) => state + 1,
+ effect: (state, event, dispatch) async {
+ await Future<void>.delayed(const Duration(seconds: 3));
+ dispatch(Decrement());
+ },
)
.on<Decrement>(
reduce: (state, event) => state - 1,
)
...
- .on<Increment>(
- effect: (state, event, dispatch) async {
- await Future<void>.delayed(const Duration(seconds: 3));
- dispatch(Decrement());
- },
- )
...
有一些特殊情况。例如,我们想在系统启动时分发事件:
...
.onRun(effect: (initialState, dispatch) {
dispatch(Increment());
return null;
},);
我们还可以使用 onRun
操作符来处理不同的情况。了解更多请参阅 API 文档:
- on
- onRun
- onDispose
状态触发
状态触发会通过反应到状态变化来触发效果。
我们有一系列前缀为 react
的操作符来实现这一点:
...
- .reactState(
- effect: (state, dispatch) {
- print('Simulate persistence save call with state: $state');
- },
- ),
+ .reactState(
+ effect: (state, dispatch) {
+ print('Simulate persistence save call with state: $state');
+ },
+ ),
...
此效果将在状态变化时反应并触发保存调用。由于它对整个状态(而非部分值)的变化作出反应,我们可以使用方便的操作符 reactState
,这样就不需要值映射函数:
- .reactState(
- effect: (state, dispatch) {
- print('Simulate persistence save call with state: $state');
- },
- ),
+ .reactState(
+ effect: (state, dispatch) {
+ print('Simulate persistence save call with state: $state');
+ },
+ ),
还有一个重要的效果使用了这种触发方式。你能猜出是什么吗?
是的,这是 呈现效果。使用声明式 UI 库如 Flutter 或 React 时,构建(渲染)是由反应到状态变化触发的。 我们稍后将讨论这个问题。
还有其他 react*
操作符用于不同情况。了解更多请访问 API 文档:
- react
- reactLatest
- reactState
呈现效果(与 Flutter)
我们提到 呈现效果 是由反应到状态变化触发的:
.reactState(
effect: (state, dispatch) {
print('Simulate presentation effect (build, render) with state: $state');
},
)
由于 Flutter 充满了小部件,我们如何让 react* 操作符
与小部件一起工作?
这是可能的吗:
// 下面只是想象,只在我们心中工作
.reactState(
effect: (state, dispatch) {
return TextButton(
onPressed: () => dispatch(Increment()),
child: Text('$state'),
);
},
)
是的,我们可以引入 <React*>
小部件,它们是 <react*> 操作符
和小部件的组合:
Widget build(BuildContext context) {
return ReactState<int, CounterEvent>(
system: counterSystem,
builder: (context, state, dispatch) {
return TextButton(
onPressed: () => dispatch(Increment()),
child: Text('$state'),
);
}
);
}
很高兴看到 Flutter 和 React 的合作 ^_^。
了解更多请访问 flutter_love。
日志效果
我们介绍了如何添加 日志效果
:
...
.log()
...
输出:
System<int, CounterEvent> Run
System<int, CounterEvent> Update {
event: null
oldState: null
state: 0
}
System<int, CounterEvent> Update {
event: Instance of 'Increment'
oldState: 0
state: 1
}
System<int, CounterEvent> Update {
event: Instance of 'Decrement'
oldState: 1
state: 0
}
System<int, CounterEvent> Dispose
我们看到,日志
操作符可以用更少的代码完成更多的工作,它不仅记录 更新
,还记录系统 运行
和 销毁
,这对于调试可能很有帮助。
日志
是一种 场景聚焦操作符,它可以随着详细解决方案的需求扩展日志。如果我们反复编写类似的代码来解决类似的问题,那么我们可以提取操作符以便重用解决方案。日志
就是这样的一个操作符之一。
其他操作符
还有一些操作符可以帮助我们实现目标。我们将介绍其中一些。
ignoreEvent
根据当前状态和候选事件忽略事件。
futureSystem
.ignoreEvent(
when: (state, event) => event is TriggerLoadData && state.loading
)
...
上述代码表明,如果系统已经在加载状态,则接下来的 TriggerLoadData
事件将被忽略。
debounceOn
对某些事件应用 去抖逻辑。
searchSystem
...
.on<UpdateKeyword>(
reduce: (state, event) => state.copyWith(keyword: event.keyword)
)
.debounceOn<UpdateKeyword>(
duration: const Duration(seconds: 1)
)
...
上述代码表明,如果 UpdateKeyword
事件以高频(快速输入)分发,系统将丢弃这些事件以减少不必要的分发,只有当分发事件稳定时才会传递事件。
附录
代码审查
我们已经重构了很多代码。让我们回顾一下,以增强肌肉记忆。
旧代码:
final counterSystem = System<int, CounterEvent>
.create(initialState: 0)
.on<Increment>(
reduce: (state, event) => state + 1,
effect: (state, event, dispatch) async {
await Future<void>.delayed(const Duration(seconds: 3));
dispatch(Decrement());
},
)
.on<Decrement>(
reduce: (state, event) => state - 1,
)
.log()
.reactState(
effect: (state, dispatch) {
print('Simulate persistence save call with state: $state');
},
)
.onRun(effect: (initialState, dispatch) {
dispatch(Increment());
return null;
},);
新代码:
final counterSystem = System<int, CounterEvent>
.create(initialState: 0)
.on<Increment>(
reduce: (state, event) => state + 1,
effect: (state, event, dispatch) async {
await Future<void>.delayed(const Duration(seconds: 3));
dispatch(Decrement());
},
)
.on<Decrement>(
reduce: (state, event) => state - 1,
)
.log()
.reactState(
effect: (state, dispatch) {
print('Simulate persistence save call with state: $state');
},
)
.onRun(effect: (initialState, dispatch) {
dispatch(Increment());
return null;
},);
测试
测试可以简单地进行:
- 创建系统
- 注入模拟事件和模拟效果
- 记录状态
- 运行系统
- 验证记录的状态
test('CounterSystem', () async {
final List<State> states = [];
final counterSystem = System<int, CounterEvent>
.create(initialState: 0)
.on<Increment>(
reduce: (state, event) => state + 1,
)
.on<Decrement>(
reduce: (state, event) => state - 1,
);
final disposer = counterSystem.run(
effect: (state, oldState, event, dispatch) async {
states.add(state);
if (event == null) {
// 注入模拟事件
dispatch(Increment());
await Future<void>.delayed(const Duration(milliseconds: 20));
dispatch(Decrement());
}
},
);
await Future<void>.delayed(const Duration(milliseconds: 60));
disposer();
expect(states, [
0, // 初始状态
1,
0,
]);
});
更多关于Flutter情感表达插件love的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter情感表达插件love的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中集成和使用love
插件来实现情感表达功能的一个简单代码案例。love
插件可能不是一个实际存在的官方插件名称,但假设它提供了一些类似心形动画或情感图标的功能,我将基于这个假设来展示代码。
首先,你需要确保你的Flutter项目已经设置好,并且已经添加了love
插件(或类似的情感表达插件)到你的pubspec.yaml
文件中。由于love
插件可能不存在,我将使用一个假设的插件名称flutter_love_expression
作为示例。
1. 添加依赖
在你的pubspec.yaml
文件中添加依赖:
dependencies:
flutter:
sdk: flutter
flutter_love_expression: ^1.0.0 # 假设版本号
然后运行flutter pub get
来安装依赖。
2. 导入插件
在你的Dart文件中导入插件:
import 'package:flutter/material.dart';
import 'package:flutter_love_expression/flutter_love_expression.dart'; // 假设的导入路径
3. 使用插件
下面是一个简单的示例,展示如何在你的Flutter应用中使用flutter_love_expression
插件来显示一个心形动画或图标:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Love Expression Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Love Expression Demo'),
),
body: Center(
child: LoveExpressionButton(),
),
),
);
}
}
class LoveExpressionButton extends StatefulWidget {
@override
_LoveExpressionButtonState createState() => _LoveExpressionButtonState();
}
class _LoveExpressionButtonState extends State<LoveExpressionButton> {
bool isLoved = false;
void _expressLove() {
setState(() {
isLoved = !isLoved;
});
// 假设插件有一个showLoveAnimation方法来显示心形动画
if (isLoved) {
LoveExpression.showLoveAnimation(); // 假设的方法调用
} else {
// 如果有隐藏动画的方法,也可以在这里调用
// LoveExpression.hideLoveAnimation();
}
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _expressLove,
child: Text(isLoved ? 'Loved!' : 'Express Love'),
style: ElevatedButton.styleFrom(
primary: isLoved ? Colors.red : Colors.blue,
),
);
}
}
注意:上面的代码示例是基于假设的flutter_love_expression
插件的功能。实际的插件可能有不同的API和用法。你需要查阅该插件的官方文档来了解如何正确使用它。
如果love
插件确实存在,并且有不同的API,你需要根据该插件的实际文档来修改上述代码。通常,插件的README文件或官方文档会提供详细的用法示例和API参考。