「本文已參與好文召集令活動,點選檢視:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」
開發人員不管使用哪種程式語言,都需要在提高程式碼可讀性、可維護性、健壯性等方面努力。Flutter 同樣需要一些最佳實踐,讓我們也能編寫一致、穩健、快速的程式碼,同時也方便我們閱讀和檢視程式碼。Effective Dart提供了詳細的指南,但是相信大家很難耐心的看完並能記住。本文結合長期開發以來的經驗,列舉幾個最常見的示例。
命名規則
UpperCamelCase
大駝峰命名法,每個單詞首字母大寫。lowerCamelCase
小駝峰命名法,第一個單詞首字母小寫,其他單詞首字母大寫。lowercase_with_underscores
小寫下劃線命名法,單詞全部小寫,單詞間用下劃線_
連線。
-
變數、常量、方法名、引數使用小駝峰命名,特別注意常量也是小駝峰命名:
String name; int phoneNumber = 123456789; const songPrice = 9.99; void sum(int songPrice){ //.... } 複製程式碼
-
類名、建構函式名、擴充套件類名、定義型別、列舉名使用大駝峰命名:
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 } 複製程式碼
-
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('我是第三行'),
],
);
}
複製程式碼