Actions API 修订版
摘要
#在 Flutter 中,Intent
是一个对象,通常使用 Shortcuts
组件绑定到键盘快捷键组合。Intent
可以绑定到 Action
,后者可以更新应用程序的状态或执行其他操作。在使用此 API 的过程中,我们发现设计中存在一些缺点,因此我们更新了 Actions API,使其更易于使用和理解。
在之前的 Actions API 设计中,操作是从 LocalKey
映射到每次调用 invoke
方法时都会创建一个新的 Action
的 ActionFactory
。在当前 API 中,操作是从 Intent
的类型映射到 Action
实例(使用 Map<Type, Action>
),并且不会为每次调用重新创建它们。
上下文
#最初的 Actions API 设计面向从组件调用操作,并使这些操作在组件的上下文中执行。团队一直在使用操作,并发现该设计中存在一些需要解决的限制。
无法从组件层次结构外部调用操作。此类示例包括处理命令脚本、某些撤消架构和某些控制器架构。
从快捷键到
Intent
,然后到Action
的映射并不总是很清楚,因为数据结构映射了 LogicalKeySet =>Intent,然后LocalKey
=>ActionFactory
。新的映射仍然是LogicalKeySet
到Intent
,但随后它将Type
(Intent
类型)映射到Action
,这更直接且更易于阅读,因为意图的类型是在映射中编写的。如果操作的键绑定位于组件层次结构的另一部分,则
Intent
并不总是能够访问决定是否应启用意图/操作所需的状态。
为了解决这些问题,我们对 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。
预定义操作的操作映射
#要更新 Flutter 中预定义操作(如 ActivateAction
和 SelectAction
)的 Actions
组件中的操作映射,请执行以下操作
- 更新
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);
/// The [LocalKey] that uniquely identifies this action to an [Intent].
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(),
)
);
}
}
迁移后代码
// You may need to create new Intent subclasses if you used
// a bare LocalKey before.
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
#要更新使用意图参数或保存状态的操作,您需要修改 invoke
方法的参数。在下面的示例中,代码将参数的值保存在意图中作为操作实例的一部分。这是因为在旧设计中,每次执行操作时都会创建一个新的操作实例,并且 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);
/// The [LocalKey] that uniquely identifies this action to an [Intent].
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 的最新稳定版本。页面上次更新于 2024-04-04。 查看源代码 或 报告问题.