移动应用 活跃维护

flutter_hooks

rrousselGit/flutter_hooks

Flutter 的 React hooks。Hooks 是一种管理 Widget 生命周期的新型对象。它们用于增加小部件之间的代码共享,并作为 StatefulWidget 的完整替代方案。

3333
Stars 标星
197
Forks 分支
32
Watchers 关注
33
Open Issues
Dart
主要语言
MIT
开源协议
738 KB
仓库大小
4 个月前
最后推送
一键安装扩展 / 插件指令
dsh plugin --profile web add github:rrousselGit/flutter_hooks
git clone https://github.com/rrousselGit/flutter_hooks.git
git clone git@github.com:rrousselGit/flutter_hooks.git
README.md master

机器翻译正文由机器翻译自项目原始文档(英文),排版经程序统一处理,可能存在偏差,请以原项目仓库为准。

Flutter Hooks

React hooks 的 Flutter 实现: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889

Hooks 是一种管理 Widget 生命周期的新型对象。它们存在的唯一原因是:通过删除重复项来增加组件之间的代码共享。

动机

StatefulWidget 存在一个大问题:很难重用比如 initState 或 dispose 的逻辑。一个明显的例子是 AnimationController:

class Example extends StatefulWidget {
  const Example({super.key, required this.duration});

  final Duration duration;

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  @override
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller.duration = widget.duration;
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

所有希望使用 AnimationController 的小部件都必须几乎从零重新实现所有这些逻辑,这当然是不希望的。

Dart 的 mixin 可以部分解决这个问题,但它们存在其他问题:

  • 给定的 mixin 在每个类中只能使用一次。
  • Mixin 和类共享同一个对象。这意味着如果两个 mixin 定义了相同名称的变量,结果可能会从编译失败到未知行为不等。

这个库提出了第三种解决方案:

class Example extends HookWidget {
  const Example({super.key, required this.duration});

  final Duration duration;

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(duration: duration);
    return Container();
  }
}

这个代码在功能上与前面的示例等效。它仍然会处理 AnimationController,并且在 Example.duration 改变时仍会更新其 duration。但你可能会想:

所有逻辑都去哪儿了?

这些逻辑已经被移到了 useAnimationController 中,这是一个直接包含在此库中的函数(参见 现有钩子)——这就是我们所说的 Hook。

钩子是一种具有一些特定性的全新对象类型:

  • 它们只能在混入 Hooks 的组件的 build 方法中使用。

  • 同一个钩子可以任意次数重复使用。以下代码定义了两个独立的 AnimationController,并且在组件重建时它们能够被正确保留。

    Widget build(BuildContext context) {
    final controller = useAnimationController();
    final controller2 = useAnimationController();
    return Container();
    }
  • Hooks 完全独立于彼此以及 widget。这意味着它们可以轻松地提取到一个 package 中,并在 pub 上发布供他人使用。

原理

类似于 State,hooks 存储在 Widget 的 Element 中。然而,它不是只有一个 State,而是 Element 存储一个 List<Hook>。然后,为了使用 Hook,必须调用 Hook.use。

由 use 返回的 hook 是基于调用次数的。第一次调用返回第一个 hook;第二次调用返回第二个 hook,第三次调用返回第三个 hook,如此类推。

如果这个想法仍然不清楚,hooks 的一个简单实现可能如下所示:

class HookElement extends Element {
  List<HookState> _hooks;
  int _hookIndex;

  T use<T>(Hook<T> hook) => _hooks[_hookIndex++].build(this);

  @override
  performRebuild() {
    _hookIndex = 0;
    super.performRebuild();
  }
}

关于 hooks 是如何实现的,下面有一篇很棒的文章讲解了它在 React 中的实现方式:https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e

规则

由于 hooks 是通过它们的索引获取的,因此必须遵守一些规则:

始终使用 use 前缀你的 hooks:

Widget build(BuildContext context) {
  // starts with `use`, good name
  useMyHook();
  // doesn't start with `use`, could confuse people into thinking that this isn't a hook
  myHook();
  // ....
}

无条件地调用钩子

Widget build(BuildContext context) {
  useMyHook();
  // ....
}

不要将 use 包裹到条件中

Widget build(BuildContext context) {
  if (condition) {
    useMyHook();
  }
  // ....
}

关于热重载

由于 hooks 是从它们的索引获取的,人们可能认为在重构时热重载会破坏应用程序。

但不用担心,HookWidget 会覆盖默认的热重载行为以与 hooks 一起工作。不过,在某些情况下,Hook 的状态可能会被重置。

考虑以下 hook 列表:

useA();
useB(0);
useC();

然后考虑在执行热重载之后,我们编辑了 HookB 的参数:

useA();
useB(42);
useC();

这里一切正常,所有钩子都保持其状态。

现在假设我们移除了 HookB。我们现在有:

useA();
useC();

在这种情况下,HookA 保持其状态,但 HookC 会被硬重置。这是因为,当在重构后执行热重载时,所有在第一行受影响之后的 hook 都会被销毁。因此,由于 HookC 放置在 HookB 之后,它将被销毁。

如何创建一个 hook

有两种方式创建 hook:

  • 一个函数

    函数是编写 hook 最常见的方式。由于 hook 本质上是可组合的,一个函数能够组合其他 hook 来创建一个更复杂的自定义 hook。按惯例,这些函数将以 use 作为前缀。

    以下代码定义了一个自定义 hook,该 hook 创建一个变量,并在每次变量值变化时将其值记录到控制台中:

    ValueNotifier<T> useLoggedState<T>([T initialData]) {
    final result = useState<T>(initialData);
    useValueChanged(result.value, (_, __) {
      print(result.value);
    });
    return result;
    }
  • 一个类

    当一个 Hook 变得过于复杂时,可以将其转换为扩展 Hook 的类——然后可以使用 Hook.use 来使用它。作为一个类,这个 Hook 将看起来非常类似于 State 类,并且可以访问小部件的生命周期和方法,例如 initHook、dispose 和 setState。

    通常的好做法是将类隐藏在一个函数下,如下所示:

    Result useMyHook() {
    return use(const _TimeAlive());
    }

    以下代码定义了一个钩子,该钩子在 State 被释放时打印其存活的总时间。

    class _TimeAlive extends Hook<void> {
    const _TimeAlive();
    
    @override
    _TimeAliveState createState() => _TimeAliveState();
    }
    
    class _TimeAliveState extends HookState<void, _TimeAlive> {
    DateTime start;
    
    @override
    void initHook() {
      super.initHook();
      start = DateTime.now();
    }
    
    @override
    void build(BuildContext context) {}
    
    @override
    void dispose() {
      print(DateTime.now().difference(start));
      super.dispose();
    }
    }

现有钩子

Flutter_Hooks 已经提供了一系列可重用的 hooks,这些 hooks 被分为不同类型:

原始类型(Primitives)

一组低级 hooks,可与组件的小部件生命周期进行交互

名称 描述
useEffect 用于副作用,并可选择性地取消它们。
useState 创建一个变量并订阅它。
useMemoized 缓存复杂对象的实例。
useRef 创建一个包含单一可变属性的对象。
useCallback 缓存函数实例。
useContext 获取建筑物 HookWidget 的 BuildContext 。
useValueChanged 监视一个值,并在其值变化时触发回调。

对象绑定(Object-binding)

这一类 hooks 用于通过 hooks 操作现有的 Flutter/Dart 对象。它们会处理对象的创建/更新/销毁。

dart:async 相关的钩子:

名称 描述
useStream 订阅一个 Stream 并返回其当前状态作为 AsyncSnapshot 。
useStreamController 创建一个自动处理销毁的 StreamController 。
useOnStreamChange 订阅一个 Stream,注册处理程序,并返回 StreamSubscription 。
useFuture 订阅一个 Future 并返回其当前状态作为 AsyncSnapshot 。

与动画相关的钩子:

名称 描述
useSingleTickerProvider 创建一个单次使用的 TickerProvider 。
useAnimationController 创建一个 AnimationController ,它将被自动销毁。
useAnimation 订阅一个 Animation 并返回其值。

与 Listenable 相关的钩子:

名称 描述
useListenable 订阅一个 Listenable 并在监听器被调用时将小部件标记为需要重新构建。
useListenableSelector 类似于 useListenable ,但允许过滤 UI 重建。
useValueNotifier 创建一个 ValueNotifier ,它将被自动销毁。
useValueListenable 订阅一个 ValueListenable 并返回其值。
useOnListenableChange 向一个 Listenable 添加指定的监听器回调,该监听器将在适当时自动移除。

杂项钩子:

一系列没有特定主题的钩子。

名称 描述
useReducer useState 的替代方案,用于处理更复杂的状态。
usePrevious 返回上一个调用 [usePrevious] 的参数。
useTextEditingController Creates a TextEditingController.
useFocusNode Creates a FocusNode.
useTabController 创建并释放一个 TabController 。
useScrollController 创建并释放一个 ScrollController 。
usePageController 创建并释放一个 PageController 。
useFixedExtentScrollController 创建并释放一个 FixedExtentScrollController 。
useAppLifecycleState 返回当前的 AppLifecycleState ,并在变化时重建组件。
useOnAppLifecycleStateChange 监听 AppLifecycleState 变化,并在变化时触发回调。
useTransformationController 创建并释放一个 TransformationController 。
useIsMounted 钩子版本的 State.mounted 。
useAutomaticKeepAlive 相当于 AutomaticKeepAlive 小部件的 Hook 版本。
useOnPlatformBrightnessChange 监听平台 Brightness 变化并在变化时触发回调。
useSearchController 创建并释放一个 SearchController 。
useWidgetStatesController 创建并释放一个 WidgetStatesController 。
useExpansibleController Creates a ExpansibleController.
useDebounced 返回提供值的防抖版本,经过指定超时时间后触发小部件更新。
useDraggableScrollableController Creates a DraggableScrollableController.
useCarouselController 创建并释放一个 CarouselController 。
useTreeSliverController Creates a TreeSliverController.
useOverlayPortalController 创建并管理一个 OverlayPortalController 来控制覆盖内容的可见性。当不再需要时,控制器将被自动释放。
useSnapshotController 创建并管理一个 SnapshotController
useCupertinoController 创建并管理一个 CupertinoController

贡献

欢迎贡献!

如果您觉得缺少某个 Hook,请随时提交拉取请求。

要合并自定义 Hook,您需要执行以下操作:

  • 描述使用案例。

    打开一个问题解释我们为什么需要这个钩子,如何使用它,…… 这是很重要的,因为如果这个钩子对大量用户没有吸引力,它是不会被合并的。

    如果你的钩子被拒绝,不要担心!被拒绝并不意味着将来如果更多人对此感兴趣,它就无法被合并。与此同时,可以随意将你的钩子发布为 https://pub.dev 上的一个包。

  • 为你的钩子编写测试。

    除非经过充分测试,否则钩子不会被合并,以避免将来无意中破坏它。

  • 将其添加到 README 中并为其编写文档。

本站来源与版权声明
  • 本文标题:flutter_hooks - Flutter 的 React hooks
  • 本文链接:https://www.cn121.com/mobile/rrousselgit-flutter-hooks.html
  • 原项目:rrousselGit/flutter_hooks 版权归原作者 rrousselGit 及贡献者所有
  • 收录信息:本站于 2026-10-22 收录本项目,本页所列协议与仓库指标均为收录当时的状态;该日期之后原项目的版本更新与协议变更,本页不作同步。
  • 开源协议:收录时本项目采用 MIT(查看 LICENSE 原文),本站译文为其衍生内容;使用、修改、分发请以该仓库 LICENSE 原文为准。
  • 站点出处:本文首发于 OneTwoOne,收录自 GitHub 开源项目 rrousselGit/flutter_hooks。
  • 翻译说明:本页正文为人工智能生成内容——由机器翻译对原项目 README 初译、经程序校验排版,可能存在错漏,请以原项目文档为准。
  • 引用声明:商业转载、第三方聚合或 AI 检索训练引用时,请务必保留以上来源出处、本文永久链接,以及原项目的版权声明与许可信息。
  • 下架通道:若原项目此后变更或收紧了许可协议、或作者/权利人认为本站的收录方式(译文、排版适配、简介翻译等)超出其授权范围,请通过 xyd3302001@163.com 发送下架通知,并附上项目地址与本页链接。本站核实后将第一时间删除本页内容,或改为不复制原文的目录性收录;署名更正等其他要求可一并提出。