跟隨《Flutter實戰(zhàn)·第二版》學習鼻听,建議直接看原書
變量聲明
- var
Dart中的var變量一旦賦值焚刺,類型便會確定祟印,不能再改變其類型
var t = "hi world";
// 下面代碼在dart中會報錯温学,因為變量t的類型已經確定為String,
// 類型一旦確定后則不能再更改其類型钙畔。
t = 1000;
Dart 本身是一個強類型語言茫陆,任何變量都是有確定類型的,在 Dart 中擎析,當用var聲明一個變量后簿盅,Dart 在編譯時會根據第一次賦值數據的類型來推斷其類型,編譯結束后其類型就已經被確定
- dynamic和Object
Object 是 Dart 所有對象的根基類揍魂,也就是說在 Dart 中所有類型都是Object的子類(包括Function和Null)桨醋,所以任何類型的數據都可以賦值給Object聲明的對象。 dynamic與Object聲明的變量都可以賦值任意對象现斋,且后期可以改變賦值的類型喜最,這和 var 是不同的
dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代碼沒有問題
t = 1000;
x = 1000;
dynamic與Object不同的是dynamic聲明的對象編譯器會提供所有可能的組合,而Object聲明的對象只能使用 Object 的屬性與方法, 否則編譯器會報錯
dynamic a;
Object b = "";
main() {
a = "";
printLengths();
}
printLengths() {
// 正常
print(a.length);
// 報錯 The getter 'length' is not defined for the class 'Object'
print(b.length);
}
dynamic 的這個特點使得我們在使用它時需要格外注意庄蹋,這很容易引入一個運行時錯誤瞬内,比如下面代碼在編譯時不會報錯,而在運行時會報錯:
print(a.xx); // a是字符串蔓肯,沒有"xx"屬性遂鹊,編譯時不會報錯,運行時會報錯
- final和const
如果您從未打算更改一個變量蔗包,那么使用 final 或 const秉扑,不是var,也不是一個類型调限。 一個 final 變量只能被設置一次舟陆,兩者區(qū)別在于:const 變量是一個編譯時常量(編譯時直接替換為常量值),final變量在第一次使用時被初始化耻矮。被final或者const修飾的變量秦躯,變量類型可以省略
//可以省略String這個類型聲明
final str = "hi world";
//final String str = "hi world";
const str1 = "hi world";
//const String str1 = "hi world";
- 空安全(null-safety)
Dart 中一切都是對象,這意味著如果我們定義一個數字裆装,在初始化它之前如果我們使用了它踱承,假如沒有某種檢查機制倡缠,則不會報錯
test() {
int I;
print(i*8);
}
在 Dart 引入空安全之前,上面代碼在執(zhí)行前不會報錯茎活,但會觸發(fā)一個運行時錯誤昙沦,原因是 i 的值為 null 。但現在有了空安全载荔,則定義變量時我們可以指定變量是可空還是不可空盾饮。
int i = 8; //默認為不可空,必須在定義時初始化懒熙。
int? j; // 定義為可空類型丘损,對于可空變量,我們在使用前必須判空工扎。
// 如果我們預期變量不能為空徘钥,但在定義時不能確定其初始值,則可以加上late關鍵字定庵,
// 表示會稍后初始化吏饿,但是在正式使用它之前必須得保證初始化過了,否則會報錯
late int k;
k=9;
如果一個變量我們定義為可空類型蔬浙,在某些情況下即使我們給它賦值過了猪落,但是預處理器仍然有可能識別不出,這時我們就要顯式(通過在變量后面加一個”!“符號)告訴預處理器它已經不是null了畴博,比如:
class Test{
int? I;
Function? fun;
say(){
if(i!=null) {
print(i! * 8); //因為已經判過空笨忌,所以能走到這 i 必不為null,如果沒有顯式申明俱病,則 IDE 會報錯
}
if(fun!=null){
fun!(); // 同上
}
}
}
上面中如果函數變量可空時官疲,調用的時候可以用語法糖:
fun?.call() // fun 不為空時則會被調用
函數
Dart是一種真正的面向對象的語言,所以即使是函數也是對象亮隙,并且有一個類型Function途凫。這意味著函數可以賦值給變量或作為參數傳遞給其他函數,這是函數式編程的典型特征
1. 函數聲明
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
Dart函數聲明如果沒有顯式聲明返回值類型時會默認當做dynamic處理溢吻,注意维费,函數返回值沒有類型推斷:
typedef bool CALLBACK();
//不指定返回類型,此時默認為dynamic促王,不是bool
isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
void test(CALLBACK cb){
print(cb());
}
//報錯犀盟,isNoble不是bool類型
test(isNoble);
- 對于只包含一個表達式的函數,可以使用簡寫語法:
bool isNoble (int atomicNumber)=> true ;
- 函數作為變量
var say = (str) {
print(str);
};
say("hi world");
函數作為參數傳遞
void execute(var callback) {
callback();
}
execute( () => print("xxx"))可選的位置參數
包裝一組函數參數蝇狼,用[]標記為可選的位置參數阅畴,并放在參數列表的最后面:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
下面是一個不帶可選參數調用這個函數的例子:
say('Bob', 'Howdy'); //結果是: Bob says Howdy
下面是用第三個參數調用這個函數的例子:
say('Bob', 'Howdy', 'smoke signal'); //結果是:Bob says Howdy with a smoke signal
- 可選的命名參數
定義函數時,使用{param1, param2, …}迅耘,放在參數列表的最后面贱枣,用于指定命名參數监署。例如:
//設置[bold]和[hidden]標志
void enableFlags({bool bold, bool hidden}) {
// ...
}
調用函數時,可以使用指定命名參數冯事。例如:paramName: value
enableFlags(bold: true, hidden: false);
可選命名參數在Flutter中使用非常多焦匈。注意,不能同時使用可選的位置參數和可選的命名參數昵仅。
mixin
Dart是不支持多繼承的,但是它支持mixin累魔,簡單來講mixin可以"組合"多個類
定義一個 Person 類摔笤,實現吃飯、說話垦写、走路和寫代碼功能吕世,同時定義一個 Dog 類,實現吃飯梯投、和走路功能:
class Person {
say() {
print('say');
}
}
mixin Eat {
eat() {
print('eat');
}
}
mixin Walk {
walk() {
print('walk');
}
}
mixin Code {
code() {
print('key');
}
}
class Dog with Eat, Walk{}
class Man extends Person with Eat, Walk, Code{}
定義了幾個 mixin命辖,然后通過 with 關鍵字將它們組合成不同的類。
有一點需要注意:如果多個mixin 中有同名方法分蓖,with 時尔艇,會默認使用最后面的 mixin 的,mixin 方法中可以通過 super 關鍵字調用之前 mixin 或類中的方法
異步支持
Dart類庫有非常多的返回Future或者Stream對象的函數么鹤。 這些函數被稱為異步函數:它們只會在設置好一些耗時操作之后返回终娃,比如像 IO操作。而不是等到這個操作完成蒸甜。
async和await關鍵詞支持了異步編程棠耕,允許您寫出和同步代碼很像的異步代碼
Future
Future與JavaScript中的Promise非常相似,表示一個異步操作的最終完成(或失斈隆)及其結果值的表示窍荧。簡單來說,它就是用于處理異步操作的恨憎,異步處理成功了就執(zhí)行成功的操作蕊退,異步處理失敗了就捕獲錯誤或者停止后續(xù)操作。一個Future只會對應一個結果框咙,要么成功咕痛,要么失敗。
Future 的所有API的返回值仍然是一個Future對象喇嘱,所以可以很方便的進行鏈式調用
Future.then
為了方便示例茉贡,在本例中我們使用Future.delayed 創(chuàng)建了一個延時任務(實際場景會是一個真正的耗時任務,比如一次網絡請求)者铜,即2秒后返回結果字符串"hi world!"腔丧,然后我們在then中接收異步結果并打印結果
Future.delayed(Duration(seconds: 2),(){
return "hi world!";
}).then((data){
print(data);
});
Future.catchError
如果異步任務發(fā)生錯誤放椰,我們可以在catchError中捕獲錯誤,我們將上面示例改為:
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會走到這里
print("success");
}).catchError((e){
//執(zhí)行失敗會走到這里
print(e);
});
在本示例中愉粤,我們在異步任務中拋出了一個異常砾医,then的回調函數將不會被執(zhí)行,取而代之的是 catchError回調函數將被調用衣厘;但是如蚜,并不是只有 catchError回調才能捕獲錯誤,then方法還有一個可選參數onError影暴,我們也可以用它來捕獲異常
Future.delayed(Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
Future.whenComplete
有些時候错邦,我們會遇到無論異步任務執(zhí)行成功或失敗都需要做一些事的場景,比如在網絡請求前彈出加載對話框型宙,在請求結束后關閉對話框撬呢。這種場景,有兩種方法妆兑,第一種是分別在then或catch中關閉一下對話框魂拦,第二種就是使用Future的whenComplete回調,我們將上面示例改一下
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會走到這里
print(data);
}).catchError((e){
//執(zhí)行失敗會走到這里
print(e);
}).whenComplete((){
//無論成功或失敗都會走到這里
});
Future.wait
有些時候搁嗓,我們需要等待多個異步任務都執(zhí)行結束后才進行一些操作芯勘,比如我們有一個界面,需要先分別從兩個網絡接口獲取數據谱姓,獲取成功后借尿,我們需要將兩個接口數據進行特定的處理后再顯示到UI界面上,應該怎么做屉来?答案是Future.wait路翻,它接受一個Future數組參數,只有數組中所有Future都執(zhí)行成功后茄靠,才會觸發(fā)then的成功回調茂契,只要有一個Future執(zhí)行失敗,就會觸發(fā)錯誤回調慨绳。下面掉冶,我們通過模擬Future.delayed 來模擬兩個數據獲取的異步任務,等兩個異步任務都執(zhí)行成功時脐雪,將兩個異步任務的結果拼接打印出來厌小,代碼如下:
Future.wait([
// 2秒后返回結果
Future.delayed(Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回結果
Future.delayed(Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
執(zhí)行上面代碼,4秒后你會在控制臺中看到“hello world”战秋。
Async/await
Dart中的async/await 和JavaScript中的async/await功能和用法是一模一樣的璧亚,如果你已經了解JavaScript中的async/await的用法,可以直接跳過本節(jié)
回掉地獄(Callback Hell)
如果代碼中有大量異步邏輯脂信,并且出現大量異步任務依賴其它異步任務的結果時癣蟋,必然會出現Future.then回調中套回調情況透硝。舉個例子,比如現在有個需求場景是用戶先登錄疯搅,登錄成功后會獲得用戶ID濒生,然后通過用戶ID,再去請求用戶個人信息幔欧,獲取到用戶個人信息后罪治,為了使用方便,我們需要將其緩存在本地文件系統礁蔗,代碼如下:
//先分別定義各個異步任務
Future<String> login(String userName, String pwd){
...
//用戶登錄
};
Future<String> getUserInfo(String id){
...
//獲取用戶信息
};
Future saveUserInfo(String userInfo){
...
// 保存用戶信息
};
接下來规阀,執(zhí)行整個任務流:
login("alice","******").then((id){
//登錄成功后通過,id獲取用戶信息
getUserInfo(id).then((userInfo){
//獲取用戶信息后保存
saveUserInfo(userInfo).then((){
//保存用戶信息瘦麸,接下來執(zhí)行其它操作
...
});
});
})
可以感受一下,如果業(yè)務邏輯中有大量異步依賴的情況歧胁,將會出現上面這種在回調里面套回調的情況滋饲,過多的嵌套會導致的代碼可讀性下降以及出錯率提高,并且非常難維護喊巍,這個問題被形象的稱為回調地獄(Callback Hell)屠缭。回調地獄問題在之前 JavaScript 中非常突出崭参,也是 JavaScript 被吐槽最多的點呵曹,但隨著 ECMAScript 標準發(fā)布后,這個問題得到了非常好的解決何暮,而解決回調地獄的兩大神器正是 ECMAScript6 引入了Promise奄喂,以及ECMAScript7 中引入的async/await。 而在 Dart 中幾乎是完全平移了 JavaScript 中的這兩者:Future相當于Promise海洼,而async/await連名字都沒改跨新。
看看通過Future和async/await如何消除上面示例中的嵌套問題
使用Future消除Callback Hell
login("alice","******").then((id){
return getUserInfo(id);
}).then((userInfo){
return saveUserInfo(userInfo);
}).then((e){
//執(zhí)行接下來的操作
}).catchError((e){
//錯誤處理
print(e);
});
正如上文所述, “Future 的所有API的返回值仍然是一個Future對象坏逢,所以可以很方便的進行鏈式調用” 域帐,如果在then 中返回的是一個Future的話,該future會執(zhí)行是整,執(zhí)行結束后會觸發(fā)后面的then回調肖揣,這樣依次向下,就避免了層層嵌套浮入。
使用Async/await消除Callback Hell
通過Future回調中再返回Future的方式雖然能避免層層嵌套龙优,但是還是有一層回調,有沒有一種方式能夠讓我們可以像寫同步代碼那樣來執(zhí)行異步任務而不使用回調的方式舵盈?答案是肯定的陋率,這就要使用async/await了
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//執(zhí)行接下來的操作
} catch(e){
//錯誤處理
print(e);
}
}
- async用來表示函數是異步的球化,定義的函數會返回一個Future對象,可以使用 then 方法添加回調函數瓦糟。
- await 后面是一個Future筒愚,表示等待該異步任務完成,異步完成后才會往下走菩浙;await必須出現在 async 函數內部
可以看到巢掺,我們通過async/await將一個異步流用同步的代碼表示出來了
其實,無論是在 JavaScript 還是 Dart 中劲蜻,async/await 都只是一個語法糖陆淀,編譯器或解釋器最終都會將其轉化為一個 Promise(Future)的調用鏈
Stream
Stream 也是用于接收異步事件數據,和 Future 不同的是先嬉,它可以接收多個異步操作的結果(成功或失斣弧)。 也就是說疫蔓,在執(zhí)行異步任務時含懊,可以通過多次觸發(fā)成功或失敗事件來傳遞結果數據或錯誤異常。 Stream 常用于會多次讀取數據的異步任務場景衅胀,如網絡內容下載岔乔、文件讀寫等
Stream.fromFutures([
//1秒后返回結果
Future.delayed(Duration(seconds: 1),(){
return 'hello 1';
}),
//拋出一個異常
Future.delayed(Duration(seconds: 2), (){
throw AssertionError('Error');
}),
//3秒后返回結果
Future.delayed(Duration(seconds: 3), (){
return 'hello 3';
})
]).listen((data) {
print(data);
}, onError: (e){
print(e.message);
}, onDone: (){
});
上面的代碼會依次輸出:
flutter: hello 1
flutter: Error
flutter: hello 3
綜合起來看,Dart 既能進行服務端腳本滚躯、APP 開發(fā)雏门、Web 開發(fā),這就有優(yōu)勢了