zl程序教程

您现在的位置是:首页 >  后端

当前栏目

用一种更有条理的方法写Flutter代码——使用Flutter Hooks与函数式组件

方法flutter组件代码 函数 一种 Hooks 使用
2023-09-11 14:16:46 时间

今天在学习Flutter时了解到flutter_hooksfunctional_widget两个第三方库,大为震撼。下面花两段稍微讲一下这两个库是啥,当然要深入了解还得是去看官方文档:

flutter_hooks把React中的Hook概念引入到了Flutter中,消除了Flutter的StatefulWidget和State类,所有状态和变量都改用类似React Hook的useXxx方式管理(可以说基本上把Flutter原本的语法翻新了个遍)。flutter_hooks的优势在于可以重用状态逻辑代码,关于flutter_hooks可以参考这篇文章

functional_widget可以通过一个@swidget注解将Flutter中函数式组件转化为一个类组件。它最大的益处可能就是类组件能够被const修饰,因而可以启动Flutter的const优化。而且函数式组件比类组件更加简洁、直观。这里我的想法是functional_widget可以用于写组件的样式代码。

经过一段时间的摸索,我研究出了一套这样的Flutter的代码,结合了flutter_hooks和functional_widget的优势(阅读前需要先了解flutter_hooks和functional_widget):

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

part 'hook_demo.g.dart';

//-----视图代码-----
class HookDemo extends HookWidget{
  const HookDemo({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = Counter.use(0);
    return Column(
        children: [
          const $Title(Text("A COUNTER DEMO",style: $TitleStyle,)),
          SubTitle("Counter Value: ${counter.value}"),
          $Line(ElevatedButton(onPressed: counter.inc, child: const Text("Add"))),
          $Line(ElevatedButton(onPressed: counter.dec, child: const Text("Minus"))),
        ]
    );
  }
}

//-----方法代码-----
class Counter{
  static use(initialValue){
    return Counter()
      .._count=useState(initialValue);
  }

  late final ValueNotifier<int> _count;
  get value => _count.value;
  inc(){
    _count.value ++;
  }
  dec(){
    _count.value --;
  }
}

//-----样式代码-----
@swidget
Widget $line(e)=>Padding(
  padding: const EdgeInsets.symmetric(vertical: 5),
  child: e,
);

@swidget
Widget $title(e)=>Padding(
  padding: const EdgeInsets.symmetric(vertical: 5),
  child: Container(
    color: Colors.amber,
    width: 230,
    child: Column(
      children: [
        e, Container(height: 2,color: Colors.grey)
      ],
    ),
  )
);

@swidget
Widget subTitle(text)=>Text(text,style: const TextStyle(color: Colors.blue),);

const $TitleStyle = TextStyle(fontSize: 25,fontWeight: FontWeight.bold);

代码的执行结果:

没错,这依旧是一个计数器的demo!但是代码的书写形式却与一般的Flutter相去甚远。

这样写代码有什么优势呢?我们来看看这段代码的特点:

①有意识地对视图、方法和样式代码进行了分离

可以发现,这一段代码呈现的结构是这样的:

//-----视图代码-----
class 组件名 extends HookWidget{
  @override
  Widget build(BuildContext context) {
    return 视图内容;
  }
}
//-----方法代码-----
class 模块名{
    模块中的方法、变量
}
//-----样式代码-----
@swidget
Widget $样式名(e)=>函数式组件

视图、方法和样式的代码被划分到三个部分(如果熟悉Vue的话,会发现这就是Vue的SFC的组织方式)。这样的效果是,可以减少视图代码中的组件嵌套。因为Flutter中的样式代码是用嵌套Widget的形式实现的,但这里我们把诸如Padding、ClipPath、GestureDetector这样的样式代码都放在了第三段部分,而把Row、Column、ListView这样的视图代码放在第一段部分,可以说是彻彻底底地减少了视图代码的嵌套问题。

②使用Flutter Hook,可以实现逻辑复用

我们熟悉(且讨厌的)的setState和StatefulWidget不见了,转而变成了下面两段代码:

//----视图代码----
Widget build(BuildContext context) {
    final counter = Counter.use(0);
    ...
}

//-----方法代码-----
class Counter{
  static use(initialValue){
    return Counter()
      .._count=useState(initialValue);
  }

  late final ValueNotifier<int> _count;
  get value => _count.value;
  inc(){
    _count.value ++;
  }
  dec(){
    _count.value --;
  }
}

在Counter这个类中,我们定义了所有与计数器功能相关的代码,并且使用use函数来初始化。

在use函数中,用flutter_hooks的useState初始化了状态变量(useState前的两个点用到了Dart的级联符号语法)。

而在视图代码中,我们只需要用Counter.use就能初始化计数器的状态逻辑,并且可以通过counter.value访问、counter.inc改变它的状态。注意,这里因为使用了flutter_hooks,所以不再需要调用setState了!

这样做的一个好处是可以把每个功能都集中到类似于Counter这样的类中,例如,如果我们希望再添加一个Log功能,就可以再写一个Log类,并且在视图中调用Log.use(...)。另外一个好处是状态逻辑可以被复用,如Counter也可以用在其他的组件中,只需要使用Counter.use就可以启用一个计数器逻辑。

③保持了Flutter的const优化

Flutter能够对类组件进行const优化。这里因为使用了@swidget这个注解,可以将$title这样的函数组件(更准确来说,是一个样式函数,它接受一个子组件e,并返回包装后的组件,一般用$开头来命名样式函数),转化为一个类组件$Title(functional_widget会将转化后的类名首字母大写)。然后,在视图代码中调用$Title(....)时,由于它是一个类,所以可以被const修饰,从而可以启动Flutter的const优化。

总的来说,Flutter本身的语法虽然很古板,甚至还有令人生厌的“嵌套地狱”,但是结合flutter_hooksfunctional_widget这样的第三方库,可以优化其代码结构,让代码更有条理。