如何使用Flutter钩子

1,342 阅读7分钟

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包括。

  • useEffect
  • useState
  • useMemoized
  • useRef
  • useCallback
  • useContext
  • useValueChanged

在这篇文章中,我们将重点介绍其中的三个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也有一个可选的第二个参数,名为keyskeys 参数是一个值列表,它决定了useEffect Hook中的函数回调是否会被调用。

useEffectkeys 的当前值与之前的值进行比较。如果数值不同,useEffect 运行函数回调。如果keys 中只有一个值保持不变,则不调用该函数回调。

useEffect( () {
    // side effects code here.
    return () {
        // clean up code
    }
}, [keys]);

useMemoized 钩子

useMemoized Hook就像React中的useMemo :它记忆/缓存由构建器函数创建的复杂对象的实例。

这个函数传递给useMemoized Hook,然后useMemoized 调用并存储该函数的结果。如果一个widget重新渲染的函数没有被调用,useMemoized ,其之前的结果就会返回。

keys 的参数为useMemoized

类似于useEffectuseMemoized 钩子有第二个可选的参数,称为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.