为啥Flutter Hooks没有受到太多关注和青睐?

2020 年 8 月 06 日

为啥Flutter Hooks没有受到太多关注和青睐?

了解Flutter Hooks并不需要React的相关知识。


Flutter Hooks虽然面世已经有一段时间了,但是迄今为止它并没有受到太多关注和青睐。我很奇怪为什么会是这个样子,毕竟它真的很好用!在本文中,我会试着告诉大家如何使用 Flutter Hooks 来减少样板代码,并基本上摆脱你现在用的几乎所有有状态小部件(StatefulWidget),让大家知道 Hooks 用起来是多么简单利落!


什么是 Hooks,它又是从何而来的?总不会是无名氏发明的吧?


其实 Hooks 最初是源于 React,但这里我并不会谈什么 React,因为我没用过它,以后也应该不会用的。换句话说了解 Flutter Hooks 并不需要 React 的相关知识。


Hooks 是一种与多个小部件共享同一代码的方法,这些代码往往是在有状态小部件之间重复或难以共享的代码。这里我的总结是:“Hooks 是 UI 逻辑的管理者”。


接下来我会介绍自己在应用中使用最多的 Hooks,及其有状态小部件的等效形式,方便你对比两者并理解前者带来的实际收益。


Memoized Hook:


这种 Hook(记忆化 Hook)是在小部件的生命周期中缓存对象实例的一种简单方法。用它可以轻松在页面上创建 BLoC、MobX 存储或通知程序对象。


下面是有状态小部件的版本:


class MyHomePage extends StatefulWidget {  @override  _MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {  final store = MyStore();    _MyHomePageState();

@override Widget build(BuildContext context) { return Container(); }}
复制代码


然后是 Hook 的等效版本:


class MyHomePage extends HookWidget {  @override  Widget build(BuildContext context) {    final store = useMemoized(() => MyStore());    return Container();  }}
复制代码


这两个示例都在小部件的生命周期内创建了一个 MyStore 实例,效果也是一样的。


这里 Flutter Hooks 的优势并不大,但一般来说,当你希望初始化对象以加载数据的时候,用 Hooks 也是可以做到的。现在让我们看看 useEffect


Effect Hook:


如前所述,我们要加载数据,为此一般会在 initState 上调用一个方法。


有状态小部件的版本:


class MyHomePage extends StatefulWidget {  @override  _MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {  final store = MyStore();    _MyHomePageState();@override  void initState() {    store.loadData();    super.initState();  }@override  Widget build(BuildContext context) {    return Container();  }}
复制代码


然后是等效的 Hook 版本:


class MyHomePage extends HookWidget {  @override  Widget build(BuildContext context) {    final store = useMemoized(() => MyStore());    useEffect(() {      store.loadData();    }, const []);    return Container();  }}
复制代码


这里使用 useEffect 模拟 initState,并且在小部件的生命周期内仅被调用一次。如果需要,你还可以返回一个在放弃小部件时将调用的函数,如下所示:


useEffect(() {  store.loadData();  return store.dispose;}, const []);
复制代码


看起来不错吧?const[]表示在未放弃(dispose)小部件之前,请勿调用 effect。你可以提供一组参数,当其中一个参数更改时将调用 effect。


下面来看看另一个关于动画的例子。


动画 Hooks:


下面是一个简单的示例,效果是在点击按钮时旋转一个框体:


import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(), ); }}

class MyHomePage extends StatefulWidget { MyHomePage({Key key}) : super(key: key);

@override _MyHomePageState createState() => new _MyHomePageState();}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { AnimationController controller;

_MyHomePageState();

@override void initState() { controller = AnimationController(vsync: this, duration: Duration(milliseconds: 800)); super.initState(); }

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

@override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ RotationTransition( turns: controller, child: ColoredBox( color: Colors.red, child: SizedBox( width: 200, height: 200, ), ), ), FlatButton( onPressed: () { if (controller.isCompleted) { controller.reset(); } controller.animateTo(controller.value + .25); }, child: Text( 'Rotate', style: TextStyle(color: Colors.red), ), ), ], ),
复制代码


使用有状态小部件完成的基本旋转动画


下面是 Hook 的等效版本:


import 'package:flutter/material.dart';import 'package:flutter_hooks/flutter_hooks.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(), ); }}

class MyHomePage extends HookWidget { @override Widget build(BuildContext context) { final controller = useAnimationController(duration: Duration(milliseconds: 800)); return Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ RotationTransition( turns: controller, child: ColoredBox( color: Colors.red, child: SizedBox( width: 200, height: 200, ), ), ), FlatButton( onPressed: () { if (controller.isCompleted) { controller.reset(); } controller.animateTo(controller.value + .25); }, child: Text( 'Rotate', style: TextStyle(color: Colors.red), ), ), ], ), ); }
复制代码


我们可以看到,Hooks 为我们管理了控制器的生命周期,我们无需放弃控制器,也无需像有状态小部件中那样提供 ticker provider。


Hooks 允许你创建自己的 Hooks,这意味着如果你找不到内置的 Hooks,则只需创建自己的版本即可。


下面我们看看如何创建一个管理 TabController 的 Hook。


定制 Hooks:


flutter_hooks 包提供了两种自定义 Hooks 的方法,只需使用一个函数或创建一个自定义类即可。


首先,我们来看一个实现为函数的自定义 Hook:


TabController useTabController({@required int length, int initialIndex = 0}) {  final tickerProvider = useSingleTickerProvider(keys: [length, initialIndex]);  final controller = useMemoized(() => TabController(length: length, vsync: tickerProvider, initialIndex: initialIndex), [tickerProvider]);

useEffect(() { return controller.dispose; }, [controller]);

return controller;
复制代码


这里我们拆开来看。要创建一个 TabController,我们需要一个 ticker provider,还需要 tab 的数量和当前 tab 的可选初始索引。


这里的 ticker provider 由一个称为 useSingleTickerProvider 的已有 Hook 搞定。这一步容易,在使用我们的自定义 Hook 时必须同时提供 length 和 initialIndex。


你会看到有一组 keys 被传递给了 useSingleTickerProvider。这是为了确保任意 key 被更改时都会重新创建 ticker provider。例如,当 tab 的数量变化时就会重新创建它。


我们需要缓存 TabController,使其在小部件生命周期中只有一次,所以我们要使用 useMemoized。在这里,我们将 tickerProvider 传递为第二个参数,以便在 ticker 更改时(也就是在 length initialIndex 更新时)重新创建控制器。这里依旧都是自动化的。


如前所见,要放弃 TabController,我们依靠 useEffect()函数返回控制器的 dispose 方法。


请注意,如果提供了新的 TabController 作为第二个参数,那么这个方法也会被调用的。


那么定制 Hook 类呢?


由于 Hook 函数非常易于使用,因此我不需要将其作为一个类来实现,不过这里还是展示一下具体的做法。


当你的 Hooks 的复杂度增长时,就应将其作为一个类来实现;实际上,这个包的文档就是这样建议的。


将我们的 TabController Hook 作为自定义类实现是这个样子:


TabController useTabController({@required int length, int initialIndex = 0}) {  return use(TabControllerHook(length, initialIndex));}class TabControllerHook extends Hook<TabController> {  final int length;  final int initialIndex;

const TabControllerHook(this.length, this.initialIndex);

@override HookState<TabController, TabControllerHook> createState() { return _TabControllerHookState(); }}

class _TabControllerHookState extends HookState<TabController, TabControllerHook> { @override build(BuildContext context) { final tickerProvider = useSingleTickerProvider(keys: [hook.length, hook.initialIndex]); final controller = useMemoized(() => TabController(length: hook.length, vsync: tickerProvider, initialIndex: hook.initialIndex), [tickerProvider]);

useEffect(() { return controller.dispose; }, [controller]);

return controller;
复制代码


这里你也看到了,一个 Hook 就像一个有状态小部件一样运行!你有一个有状态类,即 HookState 类,可以访问自定义 Hook 类的字段(此处为 hook.length)。而 hookState 的构建方法将构建你的 Hook 的结果。所以这些做起来还是很容易的。


Hooks 提供的不仅仅是这些捷径。例如,它可以管理 FocusNode 或 TextEditingController 来帮助你处理表单。可以访问官方文档以了解更多信息。


我喜欢 Hooks,并在我的所有项目中都使用它。我通常将它与 Provider 和 MobX 结合使用。


你可以在 pub 上找到Hooks,附带的文档都很完善。


参考链接


https://medium.com/flutter-community/flutter-hooks-say-goodbye-to-statefulwidget-and-reduce-boilerplate-code-8573d4720f9a


2020 年 8 月 06 日 14:241070

评论

发布
暂无评论
发现更多内容

开发框架文档体系化的思考

vivo互联网技术

框架开发

Docker网络学习第二篇-认识iptables

Lazy

Docker Linux 网络

6种快速统计代码执行时间的方法,真香!

王磊

Java

一张PDF了解JDK10 GC调优秘籍-附PDF下载

程序那些事

Java jdk JVM GC JDK10

秒懂云通信:通信圈黑话大盘点

巨侠说

云通信 通信云

Malagu 框架的认证与授权【借鉴 Spring Security 和 aws iam 的设计】

木香丘

身份认证 权限系统

配置 SharePoint Server for Reporting Services

JackWangGeek

SharePoint

Malagu 框架开发 React 应用新体验

木香丘

Serverless React 微前端 微应用 Malagu

MySQL性能优化(一):MySQL架构与核心问题

xcbeyond

MySQL MySQL性能优化

CAP原理

李白

Web经典B/S快速开发框架,强大后台+简洁UI一体化开发工具

力软.net/java开发平台

C# .net 软件开发 web开发

第六周总结

石印掌纹

【面试题系列】——Java基础

Noneplus

Java

猿灯塔:spring Boot Starter开发及源码刨析(五)

猿灯塔

spring 猿灯塔

昨天、今天、明天

escray

那些年,我在阿里当数据开发

DeeperMan

大数据

架构师训练营作业 -- Week 6

吴炳华

极客大学架构师训练营

【进收藏夹吃灰系列】——Java基础快速扫盲

Noneplus

Java

Docker网络学习第一篇:Linux虚拟网络

Lazy

Docker Linux 网络

30岁+程序员职场攻略:找到自己的“职业锚”乘风破浪

华为云开发者社区

程序员 AI 开发者 职场 程序员成长

“Python的单例模式有四种写法,你知道么?”——孔乙己

Young先生

Python 设计模式 单例模式

Doris 临时失效处理过程

石印掌纹

文档写作利器:Markdown

xcbeyond

markdown

实战技巧,Vue原来还可以这样写

前端有的玩

Java Vue 前端 技巧

SQL Server 报表服务

JackWangGeek

SharePoint

用Report Builder 创建报表

JackWangGeek

SharePoint

关于如何判断一个list是否为空的思考

Leetao

Python Python基础知识 列表

Java8——方法引用

Java旅途

java8 方法引用

纯CSS实现自定义单选框和复选框

爱嘤嘤嘤斯坦

CSS Java 编程语言 标签

5万字、97 张图总结操作系统核心知识点

cxuan

操作系统 计算机

从0开始设计Flutter独立APP | 第三篇: 一劳永逸解决全局BuildContext问题

渔子长

flutter 前端 跨平台 React

为啥Flutter Hooks没有受到太多关注和青睐?-InfoQ