如何编写地道的 Flutter 代码

背景

初入Flutter,都会带有之前开发经验的一种思维定式,如果改变这一现状,就必须搞清楚Flutter的声明式编程的编程思想,,这篇文章我将从编程范式、如何正确使用widget,以及Flutter的最佳实践与技巧三个方便解读,帮助大家写出地道的Flutter的代码。

两种编程范式

  • 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。是函数式编程的意思,更强调流程及步骤。强调的操作细节,即Step by Step,而不是对象之间的关系

  • 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。更强调语义的层面,而不是细节,不强调具体流程的细节,即更强调What,描述对象之间的关系,而不是具体的操作细节

这样说可能不太容易理解,我们就拿 Andorid 和 IOS 来举个栗子:

  1. iOS编程范式

很纯粹的命令式,new viewaddsubview

  1. 安卓编程范式:

算是半命令式,xml 声明了 UI,这是声明式的部分;但程序运行时如果要修改某个 view,仍是取到这个view,再去命令式地修改。

  1. Flutter编程范式:

Flutter 的 UI 框架吸取了 react 的理念,即 UI 是关于状态的函数。

如何正确的使用 widget

  1. use smallwidgets

  • 构建更加健壮/安全
  • 构建更高效,避免不必要的 bulid,提高性能
  • widget tree 更具有清晰干净
// like this
const MyPage extends StatelessWidget {
  @override
  Widget build(context) {
    return Column(
      children: const [    // <- I am now able to give a const list to the Column
        TopBar(),
        MyWidget1(),
        MyWidget2(),
      ],
    );
  }
}

  1. build 中减少复杂业务

    1. 避免在 build 使用过度变量的变量
 // Don’t
bool arg1, arg2, arg3

const MyPage extends StatelessWidget {
  @override
  Widget build(context) {
   if (arg1) {
     // do something
    } else if (arg2) {
     // do something
    } else if (arg3) {
     // do something
    } else {
     // do something
    }
  }
}

// Do
enum MyPageState { arg1 , arg2, arg3,  default,}

const MyPage extends StatelessWidget {
  @override
  Widget build(context) {
    switch (type) {
      case MyPageState.arg1:
       // do something
      case MyPageState.arg2:
       // do something
      case MyPageState.arg3:
       // do something
      case MyPageState.default:
       // do something
    }
  }
}

  1. 避免在 build 中做复杂/耗时的业务逻辑
  // Don’t

Future<bool?> isReady() async{
   final cache = await DefaultCacheManager().getFileFromCache(assetUrl);
   return cache?.file.path.isNotEmpty == true;
}

const MyPage extends StatelessWidget {
  @override
  Widget build(context) {
    if (isReady()) {
    // do something
    }
  }
}

// Do
bool get isReady {
  return cachePath?.isNotEmpty == true && isValid;
}

Future<void> preloadAsset() async {
  final cache = await DefaultCacheManager().getFileFromCache(assetUrl);
  cachePath = cache?.file.path;
}

const MyPage extends StatelessWidget {

  @override
  void initState() {
   super.initState();
   preloadAsset();
  }

  @override
  Widget build(context) {
    if (isReady) {
    // do something
    }
  }
}

  1. 合理使用StatefulWidget

通过区分场景适当选择StatefulWidget/StatelessWidget,避免滥用StatefulWidget

  1. 使用状态管理框架

  • Provider
  • Riverpod
  1. 函数判断优先级

    1. 子widget逻辑在子widget里判断
  // Don’t
const MyPage extends StatelessWidget {
  bool isShowTopBar;
  @override
  Widget build(context) {
    return Column(
      children: const [   
        if(isShowTopBar) TopBar(),
        MyWidget1(),
        MyWidget2(),
      ],
    );
  }
}

  // Do
const MyPage extends StatelessWidget {

  @override
  Widget build(context) {
    return Column(
      children: const [    
        TopBar(),
        MyWidget1(),
        MyWidget2(),
      ],
    );
  }
}

const TopBar extends StatelessWidget {

  @override
  Widget build(context) {
    return if(model != null) ? Column(
      children: const [
        MyWidget(),
      ],
    ):const SizedBox.shrink();;
  }
}

  1. 组合判断,把简单的放在前面

举一个例子:

image.png

为什么红色的要改为绿色的?

对简单的逻辑,我们要放前面,不要混在一起,看似兼容性更强实际降低了部分场景的性能。

Flutter 最佳实践和技巧

  1. 文件使用相对导入lib

当同时使用相对和绝对导入时,当同一个类从两种不同的方式导入时,可能会造成混淆。为了避免这种情况,我们应该在文件夹中使用相对路径lib/

// Don't
import 'package:demo/src/utils/dialog_utils.dart';

// Do
import '../../../utils/dialog_utils.dart';

  1. 指定类成员的类型

当成员的值类型已知时,尽可能避免使用var

//Don't
var item = 10;
final car = Car();
const timeOut = 2000;

//Do
int item = 10;
final Car bar = Car();
String name = 'john';
const int timeOut = 20;

为什么是已知类别,避免使用var?

对于全局变量、局部变量、静态属性或成员属性,通常可以通过初始值推断出来它们的类型,但是如果没有初始化值的话会导致类型推断失败。

// Don't
var parameters; 
if (node is Constructor) { 
  parameters = node.signature; 
} else if (node is Method) {
  parameters = node.parameters;
}

//Do
List<AstNode> parameters; 
if (node is Constructor) {
  parameters = node.signature; 
} else if (node is Method) {
  parameters = node.parameters; 
} 

  1. 避免使用as,使用is运算符

在 dart 中 as 更像是一个断言,如果值类型不匹配 as 会导致运行时异常。通常,as如果无法进行强制类型转换,则类型转换运算符会引发异常。为避免引发异常,可以使用is

//Don't
(item as Animal).name = 'Lion';

//Do
if (item is Animal)
  item.name = 'Lion';

  1. 使用???.

//Don't
v = a == null ? b : a;

//Do
v = a ?? b;

//Don't
v = a == null ? null : a.b;

//Do
v = a?.b;

  1. 使用级联运算符

如果我们想对同一个对象执行一系列操作,那么我们应该使用 Cascades(..) 运算符。

// Don't
var path = Path();
path.lineTo(0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0);
path.close();  

// Do
var path = Path()
..lineTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width, 0)
..close(); 

  1. 使用 async/await 过度使用futures回调

异步代码很难阅读和调试。同步代码async/await提高了可读性。

// Don’t
Future<int> countActiveUser() {
  return getActiveUser().then((users) {
    return users?.length ?? 0;
  }).catchError((e) {
    log.error(e);
    return 0;
  });
}

// Do
Future<int> countActiveUser() async {
  try {
    var users = await getActiveUser();

    return users?.length ?? 0;

  } catch (e) {
    log.error(e);
    return 0;
  }
}

  1. 在Widget中使用 Const

Widget在 setState 调用时不会改变时,我们应该将其定义为常量。这样避免不必要的build,从而提高性能。

  1. 使用 ListView.builder 获取长列表

在使用无限列表或非常大的列表时,通常建议使用ListView构建器以提高性能。

默认ListView构造函数一次构建整个列表。ListView.builder创建一个惰性列表,当用户向下滚动列表时,Flutter 会按需构建小部件。

参考

Flutter — Why you should use small widgets

Flutter: Best Practices and Tips

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容