Flutter 最佳實踐

艾維碼發表於2021-07-19

「本文已參與好文召集令活動,點選檢視:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!

開發人員不管使用哪種程式語言,都需要在提高程式碼可讀性、可維護性、健壯性等方面努力。Flutter 同樣需要一些最佳實踐,讓我們也能編寫一致、穩健、快速的程式碼,同時也方便我們閱讀和檢視程式碼。Effective Dart提供了詳細的指南,但是相信大家很難耐心的看完並能記住。本文結合長期開發以來的經驗,列舉幾個最常見的示例。

命名規則

  • UpperCamelCase 大駝峰命名法,每個單詞首字母大寫。
  • lowerCamelCase 小駝峰命名法,第一個單詞首字母小寫,其他單詞首字母大寫。
  • lowercase_with_underscores 小寫下劃線命名法,單詞全部小寫,單詞間用下劃線_連線。
  1. 變數、常量、方法名、引數使用小駝峰命名,特別注意常量也是小駝峰命名:

    String name;
    int phoneNumber = 123456789;
    const songPrice = 9.99;
    void sum(int songPrice){
      //....
    }
    複製程式碼
  2. 類名、建構函式名、擴充套件類名、定義型別、列舉名使用大駝峰命名:

    extension MyFancyList<T> on List<T> { ... }
    class Foo {
      const Foo([Object? arg]);
    }
    typedef Predicate<T> = bool Function(T value);
    enum Status {
       none,
       running,
       stopped,
       paused
    }
    複製程式碼
  3. Packages 、 Libraries 、檔名、導包別名使用小寫下劃線命名:

    library peg_parser.source_scanner;
    import 'dart:math' as math;
    import 'package:angular_components/angular_components'
        as angular_components;
        
    複製程式碼

導包使用相對路徑

一個專案同時使用相對路徑和絕對路徑,那麼看著導包就比較混亂,為了避免這種情況,應該在資料夾中使用相對路徑。因為 ide 還沒有那麼智慧,更改資料夾名稱或者移動資料夾,導包路徑會出錯。

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


// Do
import '../../../utils/dialog_utils.dart';
複製程式碼

為變數指定型別,可空型別不用初始化為空

Dart 支援型別推斷,但是為了更好的閱讀性,一目瞭然的知道型別,我們需要指定型別:

//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;
複製程式碼

而對於可空的型別,不需要顯示初始化為空,因為預設就是空值:

// Do
String name;

// Don't
String name = null;
複製程式碼

使用操作符和級聯操作符

使用??而不是判空表示式進行空檢查,縮短程式碼:

// Do
a = x ?? y;
// Don't
a = x == null ?  y : x;

// Do
a = x?.y;
// Don't
a = x == null ? null : x.y;

// Do
List<String>? strs;
if (strs?.isNotEmpty ?? false) {
//...
}
// Don't
List<String>? strs;
if (strs!=null&&strs.isNotEmpty) {
//...
}
複製程式碼

使用展開操作符展開列表項:

//Do
var y = [4,5,6];
var x = [1,2,...y];

//Don't
var y = [4,5,6];
var x = [1,2];
x.addAll(y);
複製程式碼

如果在同一個物件上執行一系列操作,應該使用級聯表示式:

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

// Don't
var path = Path();
path.lineTo(0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0);
path.close();  
複製程式碼

使用 lambda 表示式:

//Do
get width => right - left;
Widget getProgressBar() => CircularProgressIndicator(
      valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
    );
    
    
//Don't
get width {
  return right - left;
}
Widget getProgressBar() {
  return CircularProgressIndicator(
    valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
  );
}

//-------------------------------------------------------------

List<String> names=[]

// Do
names.forEach(print);

// Don’t
names.forEach((name) {
  print(name);
});

複製程式碼

使用原始字串,可以避免轉義反斜線和美元符號:

//Do
var s = r'This is demo string \ and $';

//Don't
var s = 'This is demo string \\ and \$';
複製程式碼

使用插值構建字串,而不是使用+來拼接字串,可以使字串更清潔、更短。

// Do
var description = 'Hello, $name! You are ${year - birth} years old.';

//Don’t
var description = 'Hello, ' + name + '! You are ' + (year - birth).toString() + ' years old.';
複製程式碼

使用操作符is,避免使用強轉,如果型別不一致,強轉可能會丟擲異常:

//Do
if (item is Animal){
 item.name = 'Lion';
} 
  
//Don't
(item as Animal).name = 'Lion';
複製程式碼

避免使用print()

列印日誌太多的時候,Android 會丟棄一些,並且 print()會造成日誌洩露,所以使用debugPrint()dart:developer:log()

使用async/await處理非同步任務

非同步程式碼很難讀取和除錯。async``await語法提高了可讀性,避免了地獄回撥和巢狀,使非同步程式碼變得和同步程式碼一樣:

// Do
Future<int> countActiveUser() async {
  try {
    var users = await getActiveUser();
     
    return users?.length ?? 0;
  
  } catch (e) {
    log.error(e);
    return 0;
  }
}

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

  }).catchError((e) {
    log.error(e);
    return 0;
  });
}
複製程式碼

使用 Const 和抽取巢狀控制元件

如果在呼叫setState時不需要改變狀態的控制元件,需要宣告為 Const 型別,避免重建:

Container(
      padding: const EdgeInsets.only(top: 10),
      color: Colors.black,
      child: const Center(
        child: const Text(
          "No Data found",
          style: const TextStyle(fontSize: 30, fontWeight: FontWeight.w800),
        ),
      ),
    );
複製程式碼

如果巢狀控制元件過多,抽離為單獨的小部件,不要抽離為方法:

抽成方法會重建,具體情況從渲染流程解說Flutter老鳥也常犯的錯誤——多次重建

// Do
class _NonsenseWidget extends StatelessWidget {
  const _NonsenseWidget();
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        Text('我是第一行'),
        Text('我是第二行'),
        Text('我是第三行'),
      ],
    );
  }
}

// Don’t
Row _buildRow() {
    return Row(
            children: <Widget>[
              Text('我是第一行'),
              Text('我是第二行'),
              Text('我是第三行'),
            ],
          );
  }
複製程式碼