今天在学习Flutter时了解到flutter_hooks和functional_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_hooks和functional_widget这样的第三方库,可以优化其代码结构,让代码更有条理。