Hooks, meet Flutter.受React Hooks和Dan Abramov的文章《Making sense of React Hooks》的启发,Dash Overflow的开发者们决定将Hooks引入Flutter。
Flutter小组件的行为与React组件类似,因为React组件中的许多生命周期都存在于Flutter小组件中。根据创作者在其GitHub页面上的介绍。
钩子是一种新的对象,管理Widget的生命周期。它们的存在只有一个原因:通过消除重复的代码,增加小部件之间的代码共享。
flutter_hooks 库提供了一种强大而简洁的方式来管理Widget的生命周期,通过增加Widget之间的代码共享和减少代码中的重复。
内置的Flutter Hooks包括。
useEffectuseStateuseMemoizeduseRefuseCallbackuseContextuseValueChanged
在这篇文章中,我们将重点介绍其中的三个Hooks。
useState钩子管理应用程序中的本地状态useEffect钩子从服务器获取数据,并将获取的数据设置为本地状态useMemoized钩子将繁重的函数备忘录化,以便在应用程序中实现最佳性能。
我们还将学习如何从flutter_hooks 创建和使用自定义Hooks。
现在,让我们看看如何安装下面的flutter_hooks 库。
<h2″>安装flutter_hooks 库
要使用来自flutter_hooks 库的Flutter Hooks,我们必须在Flutter项目中的终端运行以下命令来安装它。
flutter pub add flutter_hooks
这样就在pubspec.yaml 文件中的dependencies 部分添加了flutter_hooks: VERSION_NUMER_HERE 。
此外,我们还可以在pubspec.yaml 文件中的dependencies 部分中添加flutter_hooks 。
dependencies:
flutter:
sdk: flutter
flutter_hooks:
保存文件后,Flutter安装了这个依赖关系。接下来,导入flutter_hooks 库。
import 'package:flutter_hooks/flutter_hooks.dart';
现在我们就可以开始了!
useState 钩子
就像React中的useState ,Flutter中的useState 帮助我们在widget中创建和管理状态。
useState 钩子被调用时,我们要在小组件中本地管理状态。这个状态作为一个参数传递给useState Hook。这个状态是初始状态,因为它可以在widget的生命周期内改变。
final state = useState(0);
在这里,0 传递给useState 并成为初始状态。
现在,让我们看看我们如何在widget中使用它。我们必须首先将Flutter的counter 例子转换为使用useState 。
这里是Flutter原来的counter 的例子。
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
注意,使用StatefulWidget ,有时会使在widget中本地维护状态变得复杂。我们还必须引入另一个扩展了State 类的类,为一个StatefulWidget 创建两个类。
然而,使用Hooks,我们只用一个类来维护我们的代码,这比StatefulWidget 更容易维护。
下面是Hook的等同例子。
class MyHomePage extends HookWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
final _counter = useState(0);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter.value',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _counter.value++,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Hook的例子比其同时代的要短。然而,在小组件中使用Flutter Hooks之前,小组件必须扩展HookWidget ,这是由flutter_hooks 库提供的。
useState 通过在build 方法中调用0 ,我们将返回值存储在_counter 。这个_counter 是一个ValueNotifier 的实例。
现在,该状态被存储在ValueNotifier 的.value 属性中。所以,_counter 状态的值被存储在_counter.value 。
useState 订阅了.value 属性中的状态,当.value 的值被修改时,useState 钩子会重建小组件以显示新的值。
在FloatingActionButton ,如果按钮被按下,_counter.value 会增加。这使得状态增加了1 ,并且useState 重建了MyHomePage 小组件以显示新的值。
useEffect 钩子
Flutter中的useEffect Hook与React的useEffect Hook相同。Hook将一个函数回调作为参数,并在widget中运行副作用。
useEffect( () {
// side effects code here.
//subscription to a stream, opening a WebSocket connection, or performing HTTP requests
});
副作用可以包括流订阅,打开WebSocket连接,或执行HTTP请求。它们也是在Hook里面完成的,所以我们可以在一个widget被处理掉的时候取消它们。
该函数回调必须返回一个函数,并在小部件被处置时被调用。然后我们可以在widget被从UI和widget树中移除之前,在该函数中取消订阅或其他清理工作。其他清理工作包括
- 取消对一个流的订阅
- 取消轮询
- 清除超时
- 取消活动的HTTP连接
- 取消WebSockets连接
这可以防止小组件中的开放连接--如HTTP、WebSocket连接、开放的流和开放的订阅--在打开它们的小组件被销毁且不再出现在小组件树中后仍继续存在。
useEffect( () {
// side effects code here.
// - Unsubscribing from a stream.
// - Cancelling polling
// - Clearing timeouts
// - Cancelling active HTTP connections.
// - Cancelling WebSockets conncetions.
return () {
// clean up code
}
});
useEffect 中的函数回调被同步调用,这意味着每次小组件渲染或重新渲染时都会调用它。
keys 的参数useEffect
这个Hook也有一个可选的第二个参数,名为keys 。keys 参数是一个值列表,它决定了useEffect Hook中的函数回调是否会被调用。
useEffect 将keys 的当前值与之前的值进行比较。如果数值不同,useEffect 运行函数回调。如果keys 中只有一个值保持不变,则不调用该函数回调。
useEffect( () {
// side effects code here.
return () {
// clean up code
}
}, [keys]);
useMemoized 钩子
useMemoized Hook就像React中的useMemo :它记忆/缓存由构建器函数创建的复杂对象的实例。
这个函数传递给useMemoized Hook,然后useMemoized 调用并存储该函数的结果。如果一个widget重新渲染的函数没有被调用,useMemoized ,其之前的结果就会返回。
keys 的参数为useMemoized
类似于useEffect ,useMemoized 钩子有第二个可选的参数,称为keys 。
const result = useMemoized(() {}, [keys]);
这个keys 参数是一个依赖性的列表,它决定了当widget重新渲染时,传递给useMemoized 的函数是否执行。
当一个widget重建时,useMemoized 检查它的keys ,看以前的值是否改变。如果至少有一个值改变了,useMemoized Hook中的函数回调将被调用,result 渲染函数调用结果。
如果自从上次检查后,没有一个值发生变化,useMemoized ,跳过调用函数,并使用其最后的值。
自定义钩子
flutter_hooks 使我们能够通过两种方法创建我们自己的自定义Hooks:一个函数或类。
当创建自定义Hooks时,有两个规则需要遵循。
- 使用
use作为前缀,告诉开发者该函数是一个Hook,而不是一个普通的函数 - 不要有条件地渲染Hooks,只需有条件地渲染Hook的结果。
使用函数和类方法,我们将创建一个自定义的Hook,打印一个带有调试值的数值,就像React的useDebugValue Hook一样。
让我们从函数方法开始。
函数方法
要从函数方法开始,我们必须使用任何一个内置的Hooks在里面创建一个方法。
ValueNotifier<T> useDebugValue([T initialState],debugLabel) {
final state = useState(initialState);
print(debugLabel + ": " + initialState);
return state;
}
在上面的代码中,使用内置的useState Hook在函数中保持状态,并打印出状态的debugLabel 和值。
然后我们可以返回state 。所以,使用debugLabel ,当小部件第一次被安装到小部件树上,以及修改状态值时,状态的标签会在控制台中打印出来。
接下来,让我们看看如何使用我们创建的useDebugValue Hook,在挂载和重建widget时打印debutLabel 字符串和相应的状态。
final counter = useDebugValue(0, "Counter");
final score = useDebugValue(10, "Score");
// Counter: 0
// Score: 10
类方法
现在,让我们使用一个类来重新创建useDebugValue 自定义Hook。这是通过创建一个extends 一个Hook 的类来实现的。
ValueNotifier<T> useDebugValue<T>(T initialData, debugLabel) {
return use(_StateHook(initialData: initialData, debugLabel));
}
class _StateHook<T> extends Hook<ValueNotifier<T>> {
const _StateHook({required this.initialData, this.debugLabel});
final T debugLabel;
final T initialData;
@override
_StateHookState<T> createState() => _StateHookState();
}
class _StateHookState<T> extends HookState<ValueNotifier<T>, _StateHook<T>> {
late final _state = ValueNotifier<T>(hook.initialData)
..addListener(_listener);
@override
void dispose() {
_state.dispose();
}
@override
ValueNotifier<T> build(BuildContext context) {
print(this.debugLabel + ": " + _state.value);
return _state;
}
void _listener() {
setState(() {});
}
}
在上面的代码中,我们有useDebugValue 函数,它是我们的自定义Hook。它接受参数,例如Hook管理的initialData 初始状态值,以及状态的标签,debugLabel 。
_StateHook 类是编写我们的Hook逻辑的地方。当use 函数被调用并传入_StateHook 类实例时,它将_StateHook 类注册到Flutter运行时。然后我们就可以作为一个Hook调用useDebugLabel 。
因此,无论何时使用类方法创建一个Hook,该类必须扩展一个Hook类。你也可以用Hook.use() 来代替use() 。
结论
flutter_hooks 为我们构建Flutter小部件的方式带来了重大改变,它有助于将代码库的大小减少到一个相当小的规模。
正如我们所看到的,flutter_hooks ,使开发人员能够摒弃像StatefulWidget ,使他们能够编写干净和可维护的代码,易于分享和测试。
The postHow to use Flutter Hooksappeared first onLogRocket Blog.