Flutter中实现视图、功能和样式代码的分离(使用mixin与扩展函数)

齐健柏
2023-12-01

目录

1.引入apply函数

2.用Mixin分离Method和Style

3.总结:BMS模式


Flutter中,声明式的视图样式代码 和 命令式的业务功能代码 混合编写,造成了书写代码时结构混乱,使得代码的嵌套层数过深,稍不留神便会写出“嵌套地狱”。

我的想法是利用Dart语言mixin和扩展函数,使Flutter的视图、功能和样式分离开,就像Vue一样能够清晰分为template/script/style三部分。让结构更明晰,以及减少嵌套。

1.引入apply函数

首先,需要对所有窗口类(Widget)引入名为apply的扩展函数:

extension ApplyExt on Widget {
  Widget apply(Function style){
    return style.call(this);
  }
}

类似于Kotlin的apply。这里的apply函数接受一个style函数为参数。style是一个以Widget为入参,并返回一个Widget的函数。在style函数中会对窗口进行诸如Padding、SizedBox和GestureDetector之类的包装。

例如,一个用来装饰标题文本的style函数可能会是这样(为与一般的方法区别,style函数命名用$开头):

Widget $title(Widget e) => Padding(
    padding: const EdgeInsets.all(10),
    child: SizedBox(
      height: 40,
      child: e,
    ),
);

style函数恰如Web前端中CSS,apply也可类比为class绑定。把样式归结为函数,就可以把样式从视图中分离,并实现样式的复用:

class MyPage extends StatelessWidget {
  const MyPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text("first line").apply($title),
        Text("second line").apply($title),
        Text("third line").apply($title),
      ],
    );
  }
}

同样地,一个文件的样式也能以style函数的形式被其它文件复用,甚至可以保存一些类似css文件的常用style函数库!而这些style函数正如css一样可以用于任何widget,不限于某一种widget!

2.用Mixin分离Method和Style

我们还需要把功能和样式的代码部分从视图中分离。最起码,我们希望将视图(View)、功能(Method)和样式(Style)写在三个不同的“类”中。但在Dart中,使用混入(Mixin)会比使用类更合适。

Dart的Mixin能够将功能和样式的代码在视图所在的类之外的部分书写。

建两个Mixin,分别为Method和Style,将视图中用到的方法放到Method中,用到的样式函数放到Style,最后在视图所在的类中混入Method和Style即可。而视图所在的build函数中只需要写Column和Row这样的布局就行了!

例如,一个简单的计数器可以这样书写:

class CustomWidget extends StatefulWidget {
  const CustomWidget({Key? key}) : super(key: key);
  @override
  CustomWidgetState createState() => CustomWidgetState();
}

//build函数中只写布局和窗口,并且在State类中混入Method和Style
class CustomWidgetState extends State<CustomWidget> with Method,Style{
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text("点击下面的按钮").apply($title),
        IconButton(
            icon: const Icon(Icons.add),
            onPressed:(){setState((){increment();});},
            ).apply($button),
        Text(countText).apply($countText)
      ],
    );
  }
}

//Method中写用到的功能函数、变量
mixin Method{
  int _count = 0;
  increment(){
    _count++;
  }
  String get countText => "次数: $_count";
}

//Style中写样式函数,就像是写CSS一样
mixin Style{
  $title(e) => Padding(
      padding: const EdgeInsets.all(30),
      child: SizedBox(
        width: 300,
        child: Center(
          child: e,
        ),
      )
  );
  $button(e) => SizedBox(
    width: 100,
    height: 50,
    child: e,
  );
  $countText(e) => Padding(
    padding: const EdgeInsets.symmetric(
      vertical: 30,
    ),
    child: e,
  );
}

我们看到,整个Flutter代码被清晰地分成了三个部分(用首字母B-M-S作命名):

B:视图部分(Build函数)。所有的 行列布局 和 窗口 代码。

M:方法部分(Method混入)。视图中用到的方法、变量和计算变量。

S:样式部分(Style混入)。所有用来修饰窗口组件的样式函数。

这正好于Vue中<template>、<script>和<style>三个标签对应。也可以说,这是更具结构化的代码书写方式。

3.总结:BMS模式

B-M-S模式的益处在于(B-Build,M-Method,S-Style)

  • 极大程度上减小了代码嵌套。原本的视图被分为了三个部分。最为繁琐的样式已经被纳入Style混入组件中,且可以被apply函数复用。只需要在build函数中写视图中行与列的布局,和窗口的初始化,其余的样式都是用类似Web前端中class绑定的方式(apply函数)引入的。
  • 代码更加结构化。一个组件的构建被严格地划分为了视图、方法和样式。不必将函数功能代码混写到视图代码中。在设计组件时可以采取 视图→样式→方法 的逻辑顺序。
  • 没有引入框架或依赖,完全基于Dart原有的语法。唯一的改动是增加了一个不到5行的apply函数。因为是完全基于Dart语法,故可轻松与一般的代码写法结合与转换,也可随时引入Provider等框架。

然后,提供一个适用于Android Studio的实现该模式的代码模板:

class $NAME$ extends StatefulWidget {
  const $NAME$({Key? key}) : super(key: key);
 
  @override
  State<$NAME$> createState() => $SNAME$();
}
 
class $SNAME$ extends State<$NAME$> with Method,Style{
  @override
  Widget build(BuildContext context) {
    //build code here.
    return Container($END$);
  }
}
 
mixin Method on State<$NAME$>{
    $NAME$ get prop => widget;
    //method code here.
}
 
mixin Style on State<$NAME$>{
    $NAME$ get prop => widget;
    //style code here.
}
//SNAME : regularExpression(concat("_", NAME, "State"), "^__", "_")

最后

这是我对Flutter中代码分离的探索,目的是更舒适地书写Flutter代码!

如果大家有相关的问题,或者更好的建议,欢迎评论区补充!

关于BMS,你可能还关心:

如何让apply能传入多个style参数?如何动态绑定样式?

 类似资料: