Skip to main content

加速开发,在 Flutter 中使用函数组件 FC

Flutter 已经更新到第3个大版本,并带来更丰富的特性更接近原生的性能。然而不管是新手入门还是轻车熟路的应用搭建,我们的第一个组件都从继承 StatefulWidget 或 StatelessWidget 开始。

React 风格的开发替代方案

若经历过 Flutter 项目开发的过程,会明显感觉到,相比 React 组件开发生态,Flutter 的组件开发过程相当繁琐。

目前社区已经有部分方案提供类似 React 的函数式组件开发风格。比较出名的有 functional_widget、flutter_hooks。

functional_widget

functional_widget 能让我们直接写函数组件,并作为 Widget 构造出对象。然而这个过程需要借助 build_runner 进行代码生成,一番折腾后,和手写 Widget 并没有节省多少时间。

@widget
Widget myText(BuildContext context) {
  return const Text("FC");
}

flutter_hooks

flutter_hooks 则引用 React 的钩子设计,通过单线程的特性,封装出门面基类 HookWidget 和附属钩子。继承自 HookWidget ,便可在 build 阶段使用钩子。这个库由社区活跃开发者所作,即使从未发布主版本但仍然收获 2k+ star,是较推荐的 React 风格工具库。

class MyText extends HookWidget {
  Widget build(BuildContext context) {
    final counter = useState(0);
    // counter.value++;
    return Text("counter: ${counter.value}");
  }
}

可以看到 HookWidget 省去了代码生成环节也减少了相比原始开发的工作量,并提供了接近 React FC 的开发过程。

Dart 3 Records 解构语法

通过速览上方 2 个 Flutter 库,它们提供的接口仍然没有最大程度地呈现出 React 的开发风格,其中 Dart 语言的语法支持也是很重要的原因,毕竟 Dart 与 JavaScript 是不同的语言。

Dart 3 Records 语法的出现给 Flutter 函数式组件开发提供了一个机会。在之前,为了减少 DTO class 泛滥,我们一般用 Tuple 包来组建临时 DTO 实例。

// 第 1 页,取 10 条
final tuple = Tuple2<int, int>(1, 10);
tuple.item1; // 1
tuple.item2; // 10

而在 Dart 3 中,引擎提供了直接语法支持,甚至能够使用解构语法。

final tuple = (1, 10);
tuple.$1; // 1
tuple.$2; // 10

// 解构
final (page, limit) = tuple;

解构语法在 React 钩子编程很重要,一般用解构实现一行使用钩子并得到需要的部分。Dart 3 以前为了解构需要分开出多行代码赋值,大幅增加代码冗余。而现在可以这样了:

// useState 钩子,但是 in Dart 3
final (state, setState) = useState(0);

这就是 flutter_fc 的灵感,借助解构,能提供更接近 React 的开发体验。上方的例子已经在 Dart 实现了。

使用 flutter_fc 加快开发速度

flutter_fc 是一个支持函数式组件开发的工具库,它提供非常接近 React 的钩子和接口,让我们像写 React FC 那样编写 Flutter 组件,减少代码量,加速项目开发。

接下来呈现的使用方法,相信对于 React 开发者能迅速理解。

Eg1: Counter FC

一个计数器组件。

final Counter = defineFC((props) {
  final (counter, setCounter) = useState(0);
  return ElevatedButton(
    onPressed: () => setCounter(counter + 1),
    child: Text("Counter: $counter"),
  );
});

void main() {
  runApp(MaterialApp(home: Counter()));
}

就这么简单,不需要代码生成,不需要继承别的。只要关心输入 props 和输出组件就行了。

Eg2: 使用 Side Effects 管理受控

调用 useEffect() 部署一个副作用,依赖变化时才会触发执行。

final Counter = defineFC((int? value) {
  final (counter, setCounter) = useState(0);
  
  useEffect(() {
    if (value != null) setCounter(value);
  }, [value]);

  return ElevatedButton(
    onPressed: () => setCounter(counter + 1),
    child: Text("Counter: $counter"),
  );
});

Eg3: 使用 useMemo 减少重绘

使用 useMemo 确保只有需要的数据变化时才执行重计算。

final Base64Image = defineFC((String? base64String) {
  assert(base64String != null, "data can not be null");
  final buffer = useMemo(
    () => base64Decode(base64String), 
    [base64String],
  );
  return Image.memory(buffer);
});

组合钩子

flutter_fc 存储钩子的方法和 React Fiber 类似,和 flutter_hooks 也有相同之处,所以不管在 flutter_fcflutter_hooks,都应避免在控制流程中使用钩子,避免调用混乱。

编写钩子函数,构建自己的钩子。

// useUpdate 返回一个方法,调用时请求刷新重绘
Function() useUpdate() {
  final (_, setState) = useState([]);
  return () => setState([]);
}
// 从 Element 树取一个 Store 锚实例
Store useStore() {
  final context = useBuildContext();
  return Provider.of<Store>(context);
}

FC 和 Widget 共存

flutter_fc 旨在解决 Flutter 项目中的代码冗余和解耦问题,同时提供类似 React 的接口为函数提供副作用,开发者只需关注函数内输入输出。对于功能较复杂的组件、需要使用 mixin 实现的组件,应使用组件继承方式开发。

() => Text(“请支持 flutter_fc”)

flutter_fc 借助 Dart 3 的语法支持,给函数式组件开发开辟了一条 Flutter 端组件开发的思路。如果 flutter_fc 节省了你的开发时间,或正在使用它构建项目,请支持 flutter_fc

参考

  • React
  • Dart 3