Actions API 修订
摘要
#在 Flutter 中,Intent
是一个对象,通常使用 Shortcuts
小部件绑定到键盘快捷键组合。Intent
可以绑定到 Action
,后者可以更新应用程序的状态或执行其他操作。在使用此 API 的过程中,我们发现设计中存在一些缺点,因此我们更新了 Actions API,使其更易于使用和理解。
在之前的 Actions API 设计中,操作是从 LocalKey
映射到 ActionFactory
,后者每次调用 invoke
方法时都会创建一个新的 Action
。在当前的 API 中,操作是从 Intent
的类型映射到 Action
实例(使用 Map<Type, Action>
),并且不会为每次调用都重新创建它们。
上下文
#最初的 Actions API 设计面向从窗口小部件调用操作,并让这些操作在窗口小部件的上下文中执行。团队一直在使用操作,并发现该设计中存在一些需要解决的限制:
操作无法从窗口小部件层次结构外部调用。这方面的例子包括处理命令脚本、一些撤销架构和一些控制器架构。
从快捷键到
Intent
,然后到Action
的映射并不总是很清晰,因为数据结构将 LogicalKeySet 映射到 Intent,然后将LocalKey
映射到ActionFactory
。新的映射仍然是LogicalKeySet
到Intent
,但随后它将Type
(Intent
类型)映射到Action
,这更直接、更易读,因为 Intent 的类型写在映射中。如果操作的键绑定位于窗口小部件层次结构的另一部分,则
Intent
并不总是能够访问决定是否应启用 intent/action 的必要状态。
为了解决这些问题,我们对 API 做了一些重大更改。操作的映射变得更直观,启用接口已移至 Action
类。从 Action
的 invoke
方法及其构造函数中删除了一些不必要的参数,并允许操作从其 invoke 方法返回结果。操作被制作成泛型,接受它们处理的 Intent
类型,不再使用 LocalKeys
来标识要运行的操作,而是使用 Intent
的类型。
这些更改中的大部分是在 修改 Action API 和 使 Action.enabled 成为 isEnabled(Intent intent) 而不是 的 PR 中完成的,并在设计文档 中进行了详细说明。
更改说明
#以下是为解决上述问题所做的更改:
- 提供给
Actions
小部件的Map<LocalKey, ActionFactory>
现在是Map<Type, Action<Intent>>
(类型是要传递给 Action 的 Intent 的类型)。 isEnabled
方法已从Intent
类移动到Action
类。- 删除了
Action.invoke
和Actions.invoke
方法的FocusNode
参数。 - 调用操作不再创建
Action
的新实例。 - 从
Intent
构造函数中删除了LocalKey
参数。 - 从
CallbackAction
中删除了LocalKey
参数。 Action
类现在是泛型 (Action<T extends Intent>
),以提高类型安全性。CallbackAction
使用的OnInvokeCallback
不再接受FocusNode
参数。ActionDispatcher.invokeAction
签名已更改为不接受可选的FocusNode
,而是接受可选的BuildContext
。- 删除了
Action
子类中的LocalKey
静态常量(按约定命名的键)。 Action.invoke
和ActionDispatcher.invokeAction
方法现在将调用操作的结果作为Object
返回。- 现在可以监听
Action
类的状态更改。 ActionFactory
typedef 已被删除,因为它不再使用。
示例分析器错误
#以下是一些示例分析器错误,这些错误可能是由于 Actions API 的过时使用造成的。错误的具体细节可能会有所不同,并且这些更改还可能导致其他错误。
error: MyActionDispatcher.invokeAction' ('bool Function(Action<Intent>, Intent, {FocusNode focusNode})') isn't a valid override of 'ActionDispatcher.invokeAction' ('Object Function(Action<Intent>, Intent, [BuildContext])'). (invalid_override at [main] lib/main.dart:74)
error: MyAction.invoke' ('void Function(FocusNode, Intent)') isn't a valid override of 'Action.invoke' ('Object Function(Intent)'). (invalid_override at [main] lib/main.dart:231)
error: The method 'isEnabled' isn't defined for the type 'Intent'. (undefined_method at [main] lib/main.dart:97)
error: The argument type 'Null Function(FocusNode, Intent)' can't be assigned to the parameter type 'Object Function(Intent)'. (argument_type_not_assignable at [main] lib/main.dart:176)
error: The getter 'key' isn't defined for the type 'NextFocusAction'. (undefined_getter at [main] lib/main.dart:294)
error: The argument type 'Map<LocalKey, dynamic>' can't be assigned to the parameter type 'Map<Type, Action<Intent>>'. (argument_type_not_assignable at [main] lib/main.dart:418)
迁移指南
#需要进行重大更改才能将现有代码更新到新的 API。
预定义操作的操作映射
#要更新 Actions
小部件中 Flutter 预定义操作(如 ActivateAction
和 SelectAction
)的操作映射,请执行以下操作:
- 更新
actions
参数的参数类型 - 在
Shortcuts
映射中使用特定Intent
类的实例,而不是Intent(TheAction.key)
实例。
迁移前的代码:
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): Intent(ActivateAction.key),
},
child: Actions(
actions: <LocalKey, ActionFactory>{
Activate.key: () => ActivateAction(),
},
child: Container(),
)
);
}
}
迁移后的代码:
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): ActivateIntent,
},
child: Actions(
actions: <Type, Action<Intent>>{
ActivateIntent: ActivateAction(),
},
child: Container(),
)
);
}
}
自定义操作
#要迁移自定义操作,请消除已定义的 LocalKeys
,并将其替换为 Intent
子类,以及更改 Actions
小部件的 actions
参数的参数类型。
迁移前的代码:
class MyAction extends Action {
MyAction() : super(key);
/// 将此操作唯一标识给 [Intent] 的 [LocalKey]。
static const LocalKey key = ValueKey<Type>(RequestFocusAction);
@override
void invoke(FocusNode node, MyIntent intent) {
// ...
}
}
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): Intent(MyAction.key),
},
child: Actions(
actions: <LocalKey, ActionFactory>{
MyAction.key: () => MyAction(),
},
child: Container(),
)
);
}
}
迁移后的代码:
// 如果之前使用的是裸 `LocalKey`,则可能需要创建新的 `Intent` 子类。
class MyIntent extends Intent {
const MyIntent();
}
class MyAction extends Action<MyIntent> {
@override
Object invoke(MyIntent intent) {
// ...
}
}
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): MyIntent,
},
child: Actions(
actions: <Type, Action<Intent>>{
MyIntent: MyAction(),
},
child: Container(),
)
);
}
}
带参数的自定义 Actions
和 Intents
#要更新使用 intent 参数或保存状态的操作,需要修改 invoke
方法的参数。在下面的示例中,代码将参数的值保存在 intent 中,作为操作实例的一部分。这是因为在旧设计中,每次执行操作时都会创建一个新的实例,并且生成的 action 可以由 ActionDispatcher
保留以记录状态。
在下面的迁移后代码示例中,新的 MyAction
将状态作为调用 invoke
的结果返回,因为不会为每次调用创建新的实例。此状态将返回给 Actions.invoke
或 ActionDispatcher.invokeAction
的调用者,具体取决于操作的调用方式。
迁移前的代码:
class MyIntent extends Intent {
const MyIntent({this.argument});
final int argument;
}
class MyAction extends Action {
MyAction() : super(key);
/// 将此操作唯一标识给 [Intent] 的 [LocalKey]。
static const LocalKey key = ValueKey<Type>(RequestFocusAction);
int state;
@override
void invoke(FocusNode node, MyIntent intent) {
// ...
state = intent.argument;
}
}
迁移后的代码:
class MyIntent extends Intent {
const MyIntent({this.argument});
final int argument;
}
class MyAction extends Action<MyIntent> {
@override
int invoke(Intent intent) {
// ...
return intent.argument;
}
}
时间线
#包含在版本中:1.18
稳定版本中:1.20
参考
#API 文档:
相关问题:
相关 PR:
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。