當年React Native 正火的時候,我擼了一個一席的客戶端,最近抽空把我本身的項目用Flutter 寫一下,項目地址戳這裏,走過路過隨手給個star🌟,不勝感激; 如下是做爲前端對Flutter 的一些見解和經驗的總結;html
我在上手寫Flutter 的時候,其實一開始並無學習Dart,以爲有點相似TypeScript,Dart 很好上手,只在遇到一些不熟悉的問題時纔去翻閱Dart文檔,說一下一些不同的概念:前端
變量聲明java
vargit
在JavaScript 和Dart 中,它均可以接受任意類型,但Dart中var的變量一旦賦值,類型便會肯定,則不能再改變其類型;github
var a;
a = 'hello'; // a 已經肯定爲String類型
a = 1; // 報錯,類型不能更改
複製代碼
dynamic & Objectjson
javaScript中沒有dynamic 變量聲明,與var 不一樣,這兩個都支持聲明後改變變量類型,但Object 聲明的變量只能使用Object所擁有的屬性和方法,而dynamic 則支持全部屬性canvas
final & const安全
從字面上能夠看出這兩個都是聲明常量,可是const 變量是編譯時常量,而final 變量則在第一次使用時初始化;bash
異步支持網絡
在Javascript 和Dart中都有相同用法的async、await,但沒有Promise,取而代之的是Future,但沒有resolve 和reject
構造函數 在Dart 中,子類不會繼承父類的命名構造函數。若是不顯式提供子類的構造函數,系統就提供默認的構造函數。同時,寫法也變得更簡潔;
class Point {
num x;
num y;
Point(this.x, this.y);// 這句等同於
/* Point(num x, num y) { this.x = x; this.y = y; } */
}
複製代碼
箭頭函數
在Javascript 中,箭頭函數是做爲一個影響this 做用域等的存在,但在Dart 中則是做爲縮寫語法的存在,二者的概念是不一樣的,應該區分清楚;
首先咱們來看看一樣的佈局,使用HTML + CSS 和Flutter 的寫法區別
在Flutter 中,一切UI 都基於Widget,在上圖中,Container 即是一個Widget,靠style 來設置樣式(也可使用Theme,後文中細講),子類嵌套在child 中,。
class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
複製代碼
實際上這種寫法有點相似虛擬Dom,以樹形嵌套來編寫,可是這種寫法我的以爲維護起來很要命,若是沒有足夠細分組件的話,可讀性也會變得不好,實際上,Flutter 的issues 中也有關於類JSX 寫法的討論,對這種寫法的吐槽,最近在掘金沸點上看到一張很貼切的圖:
關於Widget 能夠參考Flutter 中文網的Widget 目錄,具體的我就不展開寫了,下面講講一些不常見的須要注意的問題:
Expanded
不能用在不肯定或者無限高度Widget(如SingleChildScrollView
) 中
BuildContext
的概念
BuildContext
其實是當前Widget 所建立的Element對象,在獲取組件尺寸,就須要用到MediaQuery.of(context).size
,路由跳轉時,也要用到Navigator.of(context)
,比較詳細的展開和理解說明能夠參考深刻理解BuildContext 這篇文章;
Widget 的狀態管理
這裏要介紹一下InheritedWidget
,InheritedWidget
是一個特殊的Widget,你能夠將其做爲另外一個子樹的父級放在Widgets樹中。該子樹的全部子Widget 都能與該InheritedWidget
公開的數據進行交互,從而實現了Widget 間的通訊;更多狀態管理的方式能夠參考 深刻探索 flutter 中的狀態管理方式
在Flutter 中,樣式並無抽離出來,而是以各類(混亂甚至有點怪異)組合的方式來使用,設置文本要用TextStyle
,設置邊框背景等要用decoration
,感興趣的能夠看看樣式的一些用法對比;
這裏要吐槽一下樣式的管理,在Flutter 中,可使用Theme
來共享樣式,可是單個Widget 的樣式除了DefaultTextStyle
設置默認文本樣式外沒得繼承,仍是要本身一個個寫,這裏就推進了對組件進行細化(否則懶得重複寫),主題有如下使用方式
全局主題
new MaterialApp(
title: title,
theme: new ThemeData(
brightness: Brightness.dark,
),
);
複製代碼
局部主題
new Theme(
data: new ThemeData(
accentColor: Colors.yellow,
),
child: new Text('Hello World'),
);
複製代碼
拓展主題
若是你不想覆蓋全部的樣式,能夠繼承App的主題,只覆蓋部分樣式,使用copyWith方法。
new Theme(
data: Theme.of(context).copyWith(accentColor: Colors.yellow),
child: new Text('extend theme'),
);
複製代碼
獲取主題
Theme.of(context)
會查找Widget 樹,並返回最近的一個Theme對象。若是父層級上有Theme對象,則返回這個Theme,若是沒有,就返回App的Theme。建立好主題,只要在Widget的構造方法裏面經過Theme.of(context) 方法來調用。
new Container(
color: Theme.of(context).accentColor,
chile: new Text(
'Text with a background color',
style: Theme.of(context).textTheme.title,
),
);
複製代碼
用過React 的都知道無狀態組件和有狀態組件,在Flutter中,StatelessWidget
即是無狀態組件,它不依賴於除了傳入的數據之外任何其餘數據,意味着改變傳入其構造函數的參數是改變其顯示的惟一方式。而StatefulWidget
則是有狀態組件,可是跟React有一點不一樣,在React 中,組件的render
和state 是在一塊兒的,而Flutter 中,StatefulWidget
須要重寫createStae()
,返回一個State,而build
方法須要放在State 中,至於爲何不放在StatefulWidget 呢?有兩點緣由:
狀態訪問問題
因爲build
方法在state 每次改變時都會調用,在StatefulWidget
有不少狀態時,build
方法須要傳入一個State 參數,那麼,只能將State的全部狀態公開才能在State類外部訪問,但公開狀態後,狀態將再也不具備私密性,這樣對狀態的修改將變得不可控;
Widget build(BuildContext context, State state){
//state.a etc...
...
}
複製代碼
繼承StatefulWidget問題
當第一個狀況發生後,若是有個子Widget 繼承自一個引入了抽象方法build(BuildContext context)
的父Widget,那麼子Widget 在實現這個build
時都須要傳入一個state,此時父Widget 就必須將本身的state 傳入給子Widget,這樣就十分不合理,由於父Widget 的state 只與自身邏輯有關,且傳遞給子Widget 還需另外的傳遞機制,所以,應該將build
方法放在State 中。
class ChildWidgert extends ParentWidget{
@override
Widget build(BuildContext context, State state){
super.build(context, _parentWidgetState)
}
}
複製代碼
Flutter 的生命週期以下圖:
說一些經常使用的:initState
這個函數至關於在React 中的構造函數中初始化State,能夠在這一步進行數據請求加載
didUpdateWidget
當調用了 setState
改變Widget 狀態時,Flutter 會建立一個新的 Widget 來綁定這個 State 並在此方法中傳遞舊 Widget ,若是你想比對新舊 Widget 而且對 State 作一些調整,或者某些 Widget 上涉及到 controller 的變動時,就能夠在此回調方法中移除舊的 controller 並建立新的 controller;
@override
void didUpdateWidget(AVCycleLess oldWidget){
super.didUpdateWidget(oldWidget);
}
複製代碼
dispose
當Widget 被釋放(如路由切換),Widget 中存在一些監聽或持久化的變量,你就須要在 dispose 中進行釋放。
當咱們進入頁面進行一些耗時的操做,好比請求數據、初始化某些設置等時,咱們一般須要顯示一個加載頁面,通常作法都是判斷數據狀態來切換顯示的組件,而在Flutter 中則有FutureBuilder
這種便利的解決方案,這裏展開篇幅會很長,能夠參考FutureBuilder的使用方法和注意事項
在Flutter 中,路由分爲靜態路由和動態路由,靜態路由沒法傳遞參數,因此在須要傳遞參數的狀況下只能使用動態路由;
靜態路由在新建App 時定義,使用Navigator.of(context).pushNamed('/router/a');
進行切換,pushNamed 返回一個Future,能夠接收來自下一個頁面的返回值。
return new MaterialApp(
home: new Text('hello'),
routes: <String, WidgetBuilder> {
'/router/a': (_) => new APage(),
'/router/b': (_) => new BPage(),
},
);
// then 說明
// 當前頁面
Navigator.of(context).pushNamed('/router/b').then((value) {
// value 爲下一個頁面的返回值
});
// b 頁面
Navigator.of(context).pop('some data');
複製代碼
動態路由使用push
方法,傳入一個route 對象,在builder 中建立一個新頁面對象,若是須要自定義動畫效果,只須要使用PageRouteBuilder
替換MaterialPageRoute
,在transitionsBuilder
中定義動畫便可。
Navigator.of(context).push(new MaterialPageRoute(builder: (_) {
return new NewPage(data: 'some data');
}));
複製代碼
在Flutter 中,網絡請求是由HttpClient
進行的,但其操做十分麻煩,因此有Dio 這麼一個優秀的請求庫來簡化咱們的工做,須要注意的是,當App 只有一個數據源時,Dio 應該使用單例模式
當咱們獲取到數據時,一般咱們都會拿到一個json,在JavaScript 中,咱們能夠很任意地直接使用點操做符來獲取數據中的字段,可是在Dart中,你須要引入dart:convert
,並使用JSON.decode(json)
,但它返回的是一個Map<String, dynamic>
,意味着咱們直到運行時才知道值的類型,也就失去了大部分靜態類型語言特性:類型安全、自動補全和最重要的編譯時異常。
但這樣一來,咱們的代碼可能會變得很是容易出錯。咱們一般須要編寫模型類來序列化JSON,官方推薦了json_serializable
(相關操做看這裏) 來輔助咱們生成庫序列化JSON,經過這種方式,咱們就能夠直接用點操做符來操做數據了。
若是仍是嫌麻煩,能夠試試JSONFormat4Flutter這一工具(我還沒用過,看着很不錯的樣子。)
在Vue 中,咱們只須要使用@click
之類的方法便可監聽事件,而React 中則是onClick
之類的方法,但在Flutter 中,咱們須要將須要監聽事件的元素包裹在GestureDetector
中,使用onTap
等方法來處理事件,對事件的行爲表現,咱們能夠經過設置behavior
來控制,
enum HitTestBehavior {
deferToChild, // 子widget會一個接一個的進行命中測試,若是子Widget中有測試經過的,則當前Widget經過,這就意味着,若是指針事件做用於子Widget上時,其父(祖先)Widget也確定能夠收到該事件。
opaque,// 在命中測試時,將當前Widget當成不透明處理(即便自己是透明的),最終的效果至關於當前Widget的整個區域都是點擊區域
translucent,// 當點擊Widget透明區域時,能夠對自身邊界內及底部可視區域都進行命中測試,這意味着點擊頂部widget透明區域時,頂部widget和底部widget均可以接收到事件
}
複製代碼
在Flutter 中,若是須要使用Canvas,咱們須要繼承CustomPainter 並重寫paint方法來繪製自定義圖形。在使用Canvas時,咱們須要知道三個概念:
canvas
畫布對象,包括了各類繪製方法,用來繪製各類圖形
size
當前繪製區域的大小
paint
畫筆,用來控制畫出來的各類屬性,如顏色、描邊及抗鋸齒等;
使用例子以下:
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Offset.zero & size, Paint()
..isAntiAlias = true // 抗鋸齒
..style = PaintingStyle.fill // 填充,stroke則爲使用描邊
..color = Color(0xFF000000) // yanse
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false; // 強制不重繪,提升性能
}
複製代碼
說到mixin ,相信Vue 和React 的使用者都很熟悉,雖然React中mixin已 被高階函數或Decorator取代,但在Flutter 中,mixin 仍是得以保留。 它使用with
來引入一個mixin,定義的方式以下:
class A {
int a = 1;
void b(){
print('c');
}
}
class B with A{
}
B b = new B();
print(b.a);
b.b();
複製代碼
不過,mixin 在 Dart 中是有如下使用條件的:
在使用Tab 時,切換Tab後,每一個Tab 都會被銷燬而後重建,因而會屢次調用initState,那有沒有相似Vue 中的<keep-alive>
組件同樣的存在呢?答案是有的,那就是AutomaticKeepAliveClientMixin
。只須要繼承這個mixin並實現wantKeepAlive
方法便可。但widget在不顯示以後也不會被銷燬仍然保存在內存中,因此慎重使用這個方法
class APageState extends State<APage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
// ...
}
複製代碼
以上只是我這10天斷斷續續作出第一個粗糙的Flutter App所學到的東西,有些是查資料過程當中看到的一些知識點,並無用在項目中,還有不少細緻的或者沒遇到過的東西值得探討,等之後遇到了有機會再講講。