发布时间:2019年1月5日 - 5分钟阅读
Flutter hooks已经有一段时间了,但从那时起,它们并没有得到很多人的喜爱和关注。我很想知道为什么,因为它们很棒!
在这篇文章中,我将尝试告诉你如何减少锅炉模板,并基本上删除你今天使用的所有StatefulWidget,同时也告诉你钩子是如何简单而又酷的使用!
首先,什么是钩子,钩子是怎么来的?有人知道吗? 好吧,它们最初来自于React(参见medium.com/@dan_abramo…!
钩子是一种与多个widget共享相同代码的方式,这些代码通常是重复的,或者很难在有状态的widget之间共享。我对它们的描述是 "钩子就是UI逻辑管理"。
我给大家介绍一下我在我的应用中使用最多的钩子和它们对应的stateful widgets,让大家对比一下两者,看看到底有什么收获。
Memoized钩子:
这个钩子是在widget的生命周期中缓存一个对象实例的简单方法。在创建你的BLoC、MobX商店或屏幕的通知器对象时相当方便。 这里是有状态的widget版本:
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();
}
}
现在是钩子版本。
class MyHomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final store = useMemoized(() => MyStore());
return Container();
}
}
这两个例子都是在做同样的工作,只是在widget的生命周期内创建一个MyStore的实例。
这里的收益并不大,但一般来说,你想初始化你的对象来加载数据,例如,Hooks也为你提供了保障。现在我们来看看useEffect吧!
Effect钩子:
就像我说的,我们要加载数据,为了做到这一点,我们通常会调用initState的方法。
有状态的widget版本:
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();
}
}
钩子版本:
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 []的意思是,在widget没有被处置之前,不要调用效果。你可以提供一个参数数组,当一个参数发生变化时,效果就会被调用。
让我们再看一个有动画的例子!
动画钩子:
这是一个简单的例子,当一个按钮被点击时,如何旋转一个盒子。
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),
),
),
],
),
);
}
}
基本的旋转动画与有状态的widget
而这里相当于钩子:
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),
),
),
],
),
);
}
}
我们可以看到,钩子为我们管理控制器的生命周期,不需要处理它,不需要像有状态部件那样提供行情提供者。
钩子允许你创建你自己的钩子,这意味着如果你没有找到内置的东西,只需要创建你的钩子。
让我们看看如何创建一个钩子来管理TabController。
自定义钩子:
flutter_hooks包提供了两种方法来做自定义钩子,通过简单的使用函数或者创建一个自定义类。
首先,让我们看看用函数实现的自定义钩子:
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提供者由一个现有的hook来处理,叫做useSingleTickerProvider。这个很简单,当我们的自定义钩子将被使用时,长度和初始索引都必须被提供。
你看,一组键被传递给useSingleTickerProvider。这是为了确保每当改变任何一个键时,行情提供者都会重新创建。例如,如果tab的数量发生了变化,我们需要缓存TabController。
我们需要缓存TabController,以便在widget的生命周期中拥有一次,这就是为什么我们要useMemoized。在这里,我们将tickerProvider作为第二个参数传递给我们,以便在ticker发生变化时重新创建控制器,即当长度或initialIndex更新时。同样,这一切都是自动的!
为了处置TabController,正如我们前面看到的,我们依靠**useEffect()**函数来返回控制器的处置方法。
注意,如果提供一个新的TabController作为第二个参数,该方法也会被调用。
自定义钩子类呢?
由于钩子函数非常容易使用,我还不需要把它作为一个类来实现,但让我们看看如何做。
每当你的钩子的复杂性增加时,你应该把它作为一个类来实现,事实上,包的文档也推荐这样做。
这里是我们的TabController钩子作为一个自定义类:
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;
}
}
你可以看到钩子的工作方式就像一个有状态的部件!你有一个状态类,一个HookState类,它可以访问你的自定义Hook类的字段(这里是hook.length)。你有一个状态类,一个HookState类,它可以访问你的自定义Hook类的字段(这里是hook.length)。而hookState的build方法则构建了你的hook的结果。所以还是很容易做到的!
钩子提供的不仅仅是这些快捷方式。例如,它可以通过管理FocusNode或TextEditingController来帮助你处理表单。跳到官方文档中去阅读更多的内容。
我喜欢钩子,并在我所有的项目中使用它们。我通常将它们与Provider和MobX搭配使用。
你可以在pub上找到hooks,pub.dev/packages/fl…
如果你想知道更多关于钩子的信息,或者如何将它们与其他流行的包(如Provider和MobX)一起使用,请在评论中告诉我。 回头见,伙计们
通过( www.DeepL.com/Translator )(免费版)翻译