【Flutter 工程】003-钩子函数:Flutter Hooks

【Flutter 工程】003-钩子函数:Flutter Hooks

一、概述

1、前言

Hooks 是 React 框架中引入的一项特性,用来分离状态逻辑和视图逻辑。如今,这个概念已经不仅限于 React,其他前端框架也在学习和借鉴。在 Flutter 开发中,业务逻辑和视图逻辑的耦合一直是一个比较突出的痛点,这也是各大前端框架常遇到的一个共性难题。为了解决这个问题,前端社区提出了许多方案,如MVP、MVVM、React 的Mixin、高阶组件(HOC),以及Hooks。在Flutter中,开发者可能对Mixin比较熟悉。但是,Mixin的应用也存在一定的局限性:

  • Mixin 之间可能互相依赖,导致依赖关系混乱;
  • Mixin 之间可能产生冲突,难以识别和解决;
  • Mixin的可复用性有限,不利于组件解耦。

基于此,本文将介绍 Hooks 的概念和应用,看是否能够避免 Mixin 的这些限制,实现更好的状态逻辑和视图逻辑的解耦。Hooks 为我们提供了一种无需修改组件结构的方式来复用状态逻辑。我们可以通过 Hooks 将复杂的状态逻辑抽离出来,这有助于提高组件的内聚性,实现高度可复用的状态逻辑。

2、Flutter Hooks 概述

Flutter Hooks是一个用于 Flutter 应用程序的第三方包,它提供了一种优雅且方便的方式来管理 Flutter 小部件的状态和生命周期

在Flutter中,通常使用 StatefulWidget 来管理具有可变状态的小部件。然而,使用 StatefulWidget 可能会导致代码冗长,因为需要创建一个单独的State类来管理状态,并且需要在小部件和状态之间进行额外的通信

Flutter Hooks通过使用钩子(hooks)的概念,提供了一种更简洁的方式来管理小部件的状态。钩子是一些函数,可以在小部件函数内部调用,它们提供了一种轻量级的状态管理机制。

使用Flutter Hooks,您可以在无需创建 StatefulWidget 的情况下管理状态。它提供了一些常用的钩子函数,例如useState(用于管理状态)、useEffect(用于处理生命周期)、useMemo(用于记忆计算结果)和useCallback(用于记忆回调函数)。这些钩子函数使得管理小部件的状态和副作用变得更加简单和直观。

除了内置的钩子函数,Flutter Hooks还提供了自定义钩子函数的能力,以便您可以根据应用程序的需求创建自己的钩子。

总的来说,Flutter Hooks是一个强大而灵活的库,可以帮助开发者更好地组织和管理Flutter应用程序中的小部件状态和生命周期。它可以提高代码的可读性和可维护性,并且减少了使用传统的 StatefulWidget 时的样板代码量。

二、useState 基本使用

0、计数器官方 demo

去掉了注释!

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

1、安装 flutter_hooks

flutter pub add flutter_hooks

2、代码改造

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

// 关注:继承自 HookWidget
class MyHomePage extends HookWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  Widget build(BuildContext context) {
    // 关注:使用 useState
    final counter = useState(0);
    print("执行时间:" + DateTime.now().toString() + "  counter.value = " + counter.value.toString());
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            // 关注:使用 counter.value
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // 关注:使用 counter.value ++
        onPressed: () => counter.value++,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

3、运行结果

image-20230526130958068

4、神奇的事情

hook 的实现原理这里暂不探讨,可参考文章:https://juejin.cn/post/7220295071060541495

  • counter 的值更新了,也重新执行了 build 方法,但是 counter 变量的值并没有被重新初始化,而是实现了复用!

    Syncing files to device Windows...
    flutter: 执行时间:2023-05-26 13:14:56.030115  counter.value = 14
    Reloaded 1 of 668 libraries in 168ms (compile: 23 ms, reload: 62 ms, reassemble: 49 ms).
    flutter: 执行时间:2023-05-26 13:15:12.404470  counter.value = 15
    
  • 没有 State 代码,HookWidget 是继承 StatelessWidget

    abstract class HookWidget extends StatelessWidget {
     /// Initializes [key] for subclasses.
     const HookWidget({Key? key}) : super(key: key);
    
     
     _StatelessHookElement createElement() => _StatelessHookElement(this);
    }
    

三、使用 HookBuilder 实现更小范围的组件更新

1、简单分析

上述示例中,发生变化的仅仅是 count 的值,需要更新的也只是一个 Text 组件的文本,但由于 count 的值的更新却导致整个页面的重建,这是不合理的!

传统的解决方案是将 Text 单独封装,但这徒增了很多代码!flutter_hooks 提供了 HookBuilder 来实现这个需求!

2、使用 HookBuilder 实现更小范围的组件更新

代码改造

这里做一个简单示例来展示基本用法,不纠结极客风格的代码!

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

// 关注:继承自 HookWidget
class MyHomePage extends HookWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  Widget build(BuildContext context) {
    print("外层 build 执行时间:" + DateTime.now().toString());
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      // 关注:使用 HookBuilder
      body: HookBuilder(
        builder: (context) {
          print("内层 build 执行时间:" + DateTime.now().toString());
          final counter = useState(0);
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text(
                  'You have pushed the button this many times:',
                ),
                // 关注:使用 counter.value
                Text(
                  '${counter.value}',
                  style: Theme.of(context).textTheme.headlineMedium,
                ),
                FloatingActionButton(
                  // 关注:使用 counter.value ++
                  onPressed: () => counter.value++,
                  tooltip: 'Increment',
                  child: const Icon(Icons.add),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

运行结果

image-20230526134057742

控制台

Syncing files to device Windows...
flutter: 外层 build 执行时间:2023-05-26 13:40:46.152943
flutter: 内层 build 执行时间:2023-05-26 13:40:46.182019
flutter: 内层 build 执行时间:2023-05-26 13:40:49.101309
flutter: 内层 build 执行时间:2023-05-26 13:40:49.297099
flutter: 内层 build 执行时间:2023-05-26 13:40:49.644307
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值