近來,flutter
的熱度在上升。flutter應用的主要開發語言是dart
, 所以,欲練flutter, 必先了解dart
.html
dart是由google開發的編程語言,可用於開發移動應用,桌面應用,h5應用,後端服務。java
本文將簡單介紹dart
的語言特性、基礎語法,以及在平常開發中很是實用的如何請求數據、如何處理異步、如何序列化與反序列化json等技能。jquery
文章比較長,熟悉的部分各位看官可快速瀏覽,文末也給出了小小福利,供你們參考。疏漏之處請見諒,錯誤之處請指正。webpack
dart是一門純粹的面嚮對象語言,在dart中一切皆對象。git
1.toString();
與java不一樣的是,java的變量和方法只能在類裏邊,而dart能夠有不在類裏的方法和變量。github
帶有面向過程類語言的特色,好比像c。web
在dart中你能夠顯式的指定變量的類型,也能夠不指定類型,由dart推斷類型。指定類型的話編譯器能夠作靜態類型檢查。編程
在類型方面dart既有弱類型語言(像js)也有強類型(像java)的特色。json
對開發web應用來說:dart會被轉譯成js後端
對app、服務端、桌面應用講:
小結
dart看起來是但願融合多種主流語言的特性,在dart中可以看到多種語言的影子。
dart的程序執行入口是main函數,這跟c很像,main函數一個頂級函數。
返回值一般是void, 寫爲int, double等也不會報錯,不過沒有意義。
void main() { print('Hello, World!'); }
void main() { var var1 = '1'; print(var1); // 1 // var1 = 1; 這樣是錯誤的, 聲明且初始化會推斷出類型,後面不能賦值其餘類型的值 var var2; print(var2); // null var2 = 2; print(var2); // 2 var2 = '3'; print(var2); // 3 正確,聲明時不賦值則等同於聲明瞭一個動態類型(dynamic)變量 }
這種方式能夠顯式聲明變量類型,以便作一些靜態檢查。
void main() { // 聲明並初始化 int var1 = 1; print(var1); // 1 // 先聲明後初始化 int var2; var2 = 1; print(var2); // 1 // var2 = '1'; 這是錯誤的,類型不匹配 }
這個dynamic意思是動態類型,這種變量能夠隨便給他賦什麼類型的值
void main() { dynamic var1 = 1; var1 = '2'; // 動態類型能夠賦任意類型的值 print(var1); // 2 }
在運行時肯定其值, 能夠做爲類的普通成員變量
必須在編譯時肯定其值,只能做爲類的靜態成員變量,不能做爲普通成員變量
class User { final int var1=1; static const int var2=2; // const int var3 = 3; 錯誤,不能做爲類的普通成員變量 } void main() { int var1 = 1; int var2 = 2; final int const1 = 1; const int const2 = 2; // const1 = 2; 錯誤,不能再改變 // const2 = 1; 錯誤,不能再改變 // final int const3; 錯誤,必須被初始化 // const int const4; 錯誤,必須被初始化 final int const5 = var1 + var2; // const int cont6 = var1+var2; 錯誤,必須在編譯時能夠肯定值 }
void main() { int int1 = 1; double double1 = 1.3; num num1 = 2.0; // 如下引起運行時異常 // int1 = num1; // print(int1); print(int1.toString()); // 1 print(int1.isEven); // false print(int1.floor()); // 1 print(int1.isNaN); // false print(double1.toString()); // 1.3 print(double1.floor()); // 1 print(double1.isNaN); // false }
字面值表示法
void main() { String str1 = 'str1'; print(str1); // str1 String str2 = "str2"; print(str2); // str2 String str3 = '''a b c '''; print(str3); // a // b // c String str4 = "a\nb"; String str5 = r"a\nb"; print(str4); // a // b print(str5); // a\nb }
經常使用屬性和方法
String 的屬性和方法和其餘語言相似,這裏列舉幾個經常使用方法和屬性
void main() { String str1 = "我和個人祖國"; String str2 = ",一刻也不能分割"; String str3 = str1 + str2; print(str1.length); // 6 print(str1.substring(2)); // 個人祖國 List<String> list = str3.split(","); print(list); // [我和個人祖國, 一刻也不能分割] print(str1.startsWith("我")); // true print(str1.indexOf("我")); // 0 print(str1.lastIndexOf("我")); // 2 print(str1.replaceAll("我", "你")); // 你和你的祖國 print("a".compareTo("b")); // -1 print(str1.contains('祖國')); // true }
布爾值,字面值只能是true或false, 不能爲其餘
void main() { bool bool1 = true; print(bool1); // true }
相似js中的數組, 長度可變。
void main() { List<int> list = [1, 3, 4]; print(list.length); // 3 print(list); // [1, 3, 4] list.add(5); print(list); // [1, 3, 4, 5] list.addAll([6, 7]); print(list); // [1, 3, 4, 5, 6, 7] print(list.removeLast()); // 7 print(list); // [1, 3, 4, 5, 6] list.removeAt(0); print(list); // [3, 4, 5, 6] list.remove(3); print(list); // [4, 5, 6] list.sort((item1, item2) { return item2 - item1; }); print(list); // [6, 5, 4] list.forEach((item) { print(item); }); // 6 // 5 // 4 print(list.indexOf(4)); // 2 list.clear(); print(list); // [] }
表示集合,無重複值,加劇復值不會報錯,可是進不去,無序。
void main() { Set<int> set = {1, 3, 3, 4}; print(set); // {1, 3, 4} set.add(9); print(set); // {1, 3, 4, 9} print(set.contains(3)); // true print(set.length); // 4 set.remove(3); print(set); // {1, 4, 9} print(set.isEmpty); // true }
表示k-v結構數據,key不能重。
void main() { Map<String, int> map = { "a": 1, "b": 2, }; print(map); // {a: 1, b: 2} map['c'] = 3; print(map); // {a: 1, b: 2, c: 3} map.remove('a'); print(map); // {b: 2, c: 3} print(map.containsKey('a')); // false print(map.length); // 2 print(map.containsValue(3)); // true }
表示枚舉,是一種特殊的類,官方文檔沒有將其歸入到基礎類型,這裏爲了方便理解放到這裏。
enum Color { red, blue, yellow, } void main() { Color c = Color.red; print(Color.blue); // Color.blue print(Color.values); // [Color.red, Color.blue, Color.yellow] print(c); // Color.red }
void main() { int int1 = 1; // 錯誤,不能自動轉換 // double double1= int1; // double 和int的相互轉換 double double1 = int1.toDouble(); int1 = double1.toInt(); // num 是int 和 double的父類型, 能夠直接轉換 num num1 = double1; num1 = int1; // String 與double相互轉換 String str1 = double1.toString(); double1 = double.parse(str1); // String 與int相互轉換 String str2 = int1.toString(); int1 = int.parse(str2); // Map,Set,List用其toString方法能夠轉換爲String Map map = {"a": 1, "b": 2}; List list = [1, 2, 3]; Set set = {1, 2, 3}; String str3 = map.toString(); list.toString(); set.toString(); }
操做符與其餘語言基本相同,這裏說明幾個特殊的,其餘簡單羅列。
+、-、*、/、%、++、--
比較特殊的是~/
, 表示整除
void main() { int int1 = 3; print(int1 ~/ 2); // 1 }
==、!=、>、<、<=、>=
=、-=、~/=等等
比較特殊的是 變量名??=值
, 表示若是左邊變量是null則賦值,不然不賦值
void main() { int a; a ??= 1; print(a); // 1 int b = 2; b ??= 1; print(b); // 2 }
!、||、&&
&、|、^、~expr、<<、>>
condition ? expr1 : expr2
這是比較特殊的運算符,相似於jquery中的連續的.操做, 上一次操做仍是返回當前對象,所以能夠繼續調用其方法。大多語言沒有這種操做。
class User { say() { print("say"); } run() { print("run"); } } void main() { User user = User(); user..say()..run(); // say // run }
這是比較特殊的運算符,用於判斷類型和強轉,相似java裏邊(User) instance
這種
用於將某個類型轉換爲其餘類型,必須知足父子關係,不然報錯。
用於指定導入庫的別名,至關於將庫裏導出來的東西掛到一個map上,能夠解決重名問題。
用於判斷一個對象是不是某個類的實例
is 的結果取反
import 'dart:math' as math; // 這裏導入庫時使用as指定了別名 class Person { String name; Person(); say() { print("person say $name"); } } class User extends Person { String password; User(this.password); run() { print("user run"); } } void main() { print(math.max(4, 5)); // 指定別名的庫調用 User u = User("password"); print(u is User); // true u.name = "name"; u.run(); // user run Person p = u as Person; // 經過as將子類型強轉爲父類型 print(p is User); // true print(p is Person); // true print(p is List); // fasle p.say(); // person say name // p.run(); // 錯誤,已經被轉換成了Person, 不能再調用User的方法 }
流程控制與其餘語言相同,這裏簡單列舉
assert表示斷言,判斷一個表達式的真假,若爲假則拋出一個異常
dart支持模塊化編程,能夠導入dart內置庫,其餘三方庫,也能夠將本身的代碼拆分紅不一樣模塊。
import "dart:math";
須要在項目描述文件pubspec.yaml(相似js中的package.json或者java的pom.xml)中聲明你要依賴的庫,而後安裝,最後導入。
pubspec.yaml
dependencies: http: ^0.12.0+2
安裝
pub get
導入
import 'package:http/http.dart';
導入本地的文件後可使用其中定義的類,變量,函數等
import '../common/Utils.dart';
至關於將一個庫導出的東西掛到一個對象,能夠解決重名問題(上邊講as有提到)
import 'package:http/http.dart' as http;
能夠只導入指定內容,或者不導入某些內容
import 'package:lib1/lib1.dart' show foo;
import 'package:lib2/lib2.dart' hide foo;
能夠在運行時導入依賴,針對web應用,相似webpack的按需加載。
內容較多,此處略過,筆者也還沒有研究,可參考官網
dart核心內庫,默認導入,相似java的java.lang包。提供內置數據類型等。
異步支持庫,大名鼎鼎的Future就在這裏邊。
複雜數學計算相關
json序列化、反序列化,字符集轉換相關。
web應用相關api
io相關,與發請求相關的HttpClient在這裏邊
// 沒有返回值的函數 import 'package:flutter/foundation.dart'; void printInfo(String name, int age) { print('$name:$age'); } // 有返回值的函數 String getInfo(String name, int age) { return '$name:$age'; } // 函數做爲參數傳遞 void excuteFn(var function, String name, int age) { function(name, age); } // 返回一個函數 Function getPrintInfoFn(int age) { return (String name) { print('$name:$age'); }; } // 函數體只有一條語句,可使用箭頭函數 void printInfo2(String name, int age) => print('$name:$age'); // 可使用命名參數 void printInfo3({String name, int age}) { print('$name:$age'); } // 位置參數和命名參數混用,指定默認值 void printInfo4(String name, { int age}) { print('$name:$age'); } // 定義一種函數類型 typedef PrintFn = void Function(String name, int age); // 將特定類型的函數做爲參數傳遞 void excuteFn2(PrintFn function, String name, int age) { function(name, age); } class User { // 函數做爲類的成員方法 say() { print("hello"); } } void main() { printInfo("張三", 18); print(getInfo('李四', 18)); User user = User(); user.say(); excuteFn(printInfo, "王五", 18); Function fn1 = getPrintInfoFn(19); fn1("小明"); fn1("小花"); printInfo("小張", 18); printInfo2('小李', 20); printInfo3(name: "小華", age: 21); // 命名參數函數調用方式 printInfo4("AA"); printInfo4("aa", age: 30); excuteFn2(printInfo, "王五", 18); }
// 我是一行註釋
/** * 多行註釋 * 多行註釋 */
/// 文檔註釋 /// 文檔註釋
類使用class關鍵字定義
一個類能夠由以下部分組成:
說明
// 使用class關鍵字定義類 class Rectangle { // 兩個成員變量,變量名加_表示是私有的,只是一種約定,實質仍然能訪問到 double _height; double _width; // 靜態成員變量無需實例化便可經過類型訪問 static String name = "長方形"; Rectangle(double width, double height) { this._width = width; this._height = height; } // 非命名構造方法若只是爲成員變量賦值也能夠這樣寫,與上邊寫法等價 // Rectangle(this._width, this._height); // 命名構造方法 Rectangle.fromMap(Map<String, double> map) { this._width = map['width']; this._height = map['height']; } static String getName() { // return this._height.toString(); 錯誤, 靜態成員方法不能使用this,不能使用非靜態成員變量 return name; } double getArea() { return _height * _width; } // double getArea 已存在,不能這樣寫,不支持重載 // double getArea(double width, double height) { // // } // get set方法 double get width { print("調用 get width"); return _width; } set width(double value) { print("調用set width"); _width = value; } double get height => _height; set height(double value) { _height = value; } } void main() { print(Rectangle.name); print(Rectangle.getName()); Rectangle rectangle = Rectangle.fromMap({'width': 10, 'height': 20}); print(rectangle.getArea()); rectangle.width = 1; print(rectangle.width); }
要點:
// 使用extends繼承 class Square extends Rectangle { // 默認會訪問父類的無參構造,若是父類沒有無參構造須要這樣顯式指定調用哪一個構造 Square(double width):super(width, width); // 重寫父類的方法 @override double getArea() { // 使用super訪問父類的成員變量 print('重寫父類方法: ${super._width}'); // 使用super訪問父類的成員方法 return super.getArea(); } } void main() { // Square.getName(); 錯誤, 沒法繼承靜態成員 // Square.name; 錯誤,沒法繼承靜態成員 Square square = Square(10); print(square.getArea()); print(square._width); }
要點:
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
方法// 定義一個抽象類 abstract class Shape { // 抽象方法,沒有方法體 double getArea(); double getPerimeter(); } class Rectangle extends Shape { double _height; double _width; Rectangle(double width, double height) { this._width = width; this._height = height; } Rectangle.fromMap(Map<String, double> map) { this._width = map['width']; this._height = map['height']; } // 必須重寫抽象類的抽象方法,不然須要也要聲明爲抽象類 @override double getArea() { return _height * _width; } // 必須重寫抽象類的抽象方法 @override double getPerimeter() { return _height + _width; } } void main() { // Shape shape = Shape(); 錯誤,抽象類不能被實例化 Shape shape = Rectangle(10, 2); // 這樣是能夠的 print(shape.getArea()); print(shape.getPerimeter()); }
關於接口,dart的設計比較奇怪。
官方文檔對接口描述一下
每個類都顯式的聲明瞭一個包含其成員變量和成員方法及他實現的接口的接口。若是你想建立一個支持B類API的類,實現B類就行了。
也即定義一個類就至關於定義了一個接口,能夠直接去實現他,也能夠實現多個接口。extends只能繼承一個類,implements能夠實現多個類(抱歉恨不厚道的說了實現一個類,這裏應爲接口)。
在上一個例子中直接寫成下邊這樣也是能夠的
class Rectangle implements Shape
dart提供了對泛型的支持,泛型能夠增長程序的通用性,並可提供一些靜態檢查,減小了一些強制類型轉換的工做。
// 定義一個泛型類 class Cache<T> { Map<String, T> _cache = {}; T getByKey(String key) { return _cache[key]; } // 泛型方法 void setByKey(String key, T value) { _cache[key] = value; } } // 作類型約束,必須是num的子類 class NumCache<T extends num> { Map<String, T> _cache = {}; T getByKey(String key) { return _cache[key]; } void setByKey(String key, T value) { _cache[key] = value; } } void main() { Cache<String> cache = Cache(); cache.setByKey("a", "1"); // cache.setByKey("b", 2); 錯誤,類型不匹配 print(cache.getByKey("a") is String); // 類型被保持, 輸出true // NumCache<String> numCache = NumCache(); 錯誤String不是num的子類 NumCache<int> numCache = NumCache(); numCache.setByKey('a', 1); }
要點:
// 拋出一個異常實例 void testThrow() { throw Exception("異常發生"); } // 能夠拋出一個任意對象 void testThrow2() { throw "拋出字符串也能夠"; } void testThrow3() { try { testThrow(); } catch(e) { // 捕獲到異常不處理,再次向上拋出 rethrow; } } void main() { try { testThrow(); } catch(e, s) { // 這裏e是指拋出的異常類,s是堆棧信息 print(e is Exception); // true print(s); } try { testThrow2(); } catch(e) { print(e is Exception); // false print(e); // 拋出字符串也能夠 } testThrow3(); // 這裏有異常拋出,不捕獲也是能夠經過靜態檢查的 // 下面的語句沒法執行 print("未捕獲異常以後還能執行嗎,不能"); }
dart提供了對異步的支持,核心類包括Future和Stream,都位於dart:async
包下,這裏介紹很是經常使用的Future.
Future被用來表示在將來才能知道其值或錯誤的一個類,和js中的Promise是相似的。
可使用Future的構造方法建立Future
這幾個構造方法使用比較簡單,一下是簡單示例
// 使用非命名構造 Future<String> getFuture() { return Future<String>(() { return "hello"; }); } // Future.delayed Future<String> getFuture2() { return Future.delayed(Duration(seconds: 2), () { return "Future.delayed"; }); } // Future.error Future<String> getFuture3() { return Future.error("錯誤對象"); } // Future.microtask Future<String> getFuture4() { return Future.microtask(() { return "Future.microtask"; }); } // Future.sync Future<String> getFuture5() { return Future.sync(() { return "Future.sync"; }); } Future.value(getFuture4());
關於delayed, microtask,sync對比參見後文
import 'dart:async'; // 使用非命名構造 Future<String> getFuture() { return Future<String>(() { return "hello"; }); } void method1() { getFuture().then((res) { print(res); }).catchError((e) { print(e); }); print("後邊的先執行了"); } void method2() async { String str = await getFuture(); print(str); print('按順序執行'); } void main(){ method1(); method2(); // 輸出 // 後邊的先執行了 // hello // hello // 按順序執行 }
經過測試得出:
關於這幾者的比較,有興趣的同窗能夠自行研究。
經常使用的髮網絡請求的方法有兩種:
使用步驟:
import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; // 這裏封裝了一個發get請求的方法,將返回的json字符串反序列化 Future<Map<String, dynamic>> httpGet( {@required String url, Map<String, dynamic> headers = const {}, Map<String, dynamic> params = const {}, int timeout = 10}) async { // 建立HttpClient實例 HttpClient client = HttpClient(); // 我在本地啓動一個代理服務,這樣方便抓包,沒有代理服務能夠去掉 client.findProxy = (uri) { // 若是須要過濾uri,能夠手動判斷 return "PROXY 127.0.0.1:8888"; }; // 超時時間設置 client.connectionTimeout = Duration(seconds: timeout); // 經過url(包括協議 主機 路徑)建立Uri對象, 便於獲取url的各個部分 Uri uri = Uri.parse(url); // 使用getUrl建立 HttpClientRequest對象 HttpClientRequest httpClientRequest = await client.getUrl(Uri( scheme: uri.scheme, host: uri.host, path: uri.path, queryParameters: params)); // 加上自定義header headers.forEach((key, value) { httpClientRequest.headers.add(key, value); }); // 調用HttpClientRequest的close獲得HttpClientResponse HttpClientResponse httpClientResponse = await httpClientRequest.close(); // 使用特定字符集解碼 var str = await httpClientResponse.transform(utf8.decoder).join(); // 將字符串序列化爲Map, 這個將在後文詳解 Map<String, dynamic> map = jsonDecode(str); // 關閉鏈接 client.close(force: false); return map; } void main() async { Map<String, dynamic> map = await httpGet( url: 'http://t.weather.sojson.com/api/weather/city/101030100', headers: <String, dynamic>{ 'custom_header': 'customheader', }, params: <String, dynamic>{ "version": 'v1', 'cityid': '101120201', 'city': '青島' }); print(map); }
以上代碼簡單封裝了一個發get請求的方法,get請求的參數是帶在url上的,而post則不一樣,post的參數一般是在body中,下面演示如何發一個post請求, 數據格式採用json, 表單格式的相似,修改application便可。
void main() async { HttpClient client = HttpClient(); // 本地搭了一個服務,用於測試 HttpClientRequest httpClientRequest = await client.postUrl(Uri.parse('http://127.0.0.1:3000/users/save')); // 關鍵步驟,告訴服務器請求參數格式 httpClientRequest.headers.contentType = new ContentType("application", "json", charset: "utf-8"); // 關鍵步驟,寫數據到body httpClientRequest.write(jsonEncode({"name":'lily', 'age': 20})); HttpClientResponse httpClientResponse = await httpClientRequest.close(); var str = await httpClientResponse.transform(utf8.decoder).join(); Map<String, dynamic> map = jsonDecode(str); print(map); }
還有關於上傳文件的內容,本文篇幅過長了,後面再寫一個。
咱們發現使用原生的HttpClient來發請求仍是比較麻煩的,咱們可使用package:http/http.dart這個包能夠大大簡化開發。包安裝能夠參考上文模塊化部分
import 'package:http/http.dart' as http; void main() async { // 演示post請求 var url = 'http://127.0.0.1:3000/users/save'; var response = await http.post(url, body: {'name': 'lily', 'age': '20'}); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); // 演示get請求 var response2 = await http.get('http://127.0.0.1:3000?a=1',); print(response2.body); }
能夠發現使用package:http/http.dart
極其方便。
json是一種很是靈活的數據交換格式。
json序列化與反序列化通俗講就是將 語言定義的非字符串類型(好比java中的實體類,dart中的Map)轉換爲json字符串,便於傳輸和存儲,這是序列化;反序列化則是這個逆過程,方便操做數據。
在java中通常使用阿里的fastjson作json的序列化與反序列化;
在js使用JSON.stringify()、JSON.parse()作序列化與反序列化。
在dart中json序列化與反序列化有兩種方式:
import 'dart:convert'; void main() async { String jsonString = '[{"name":"name1","age":1},{"name":"name2","age":2}]'; List list = jsonDecode(jsonString); list.forEach((item) { print(item['name']); }); String jsonString2 = jsonEncode(list); print(jsonString2); }
import 'dart:convert'; class User { String name; int age; User(this.name, this.age); // 將map轉爲User User.fromJson(Map<String, dynamic> json) { name = json['name']; age = json['age']; } // 將User轉成map Map<String, dynamic> toJson() => { 'name': name, 'age': age, }; } void main() async { List<User> list = List(); list.add(User('name1', 1)); list.add(User('name2', 2)); // 這裏會自動調用toJson String str = jsonEncode(list); print(str.runtimeType); // String print(str); // [{"name":"name1","age":1},{"name":"name2","age":2}] // 這裏但願反序列化爲List<User> 可是還沒有找到方法, // 網上文檔基本就是翻譯翻譯官方文檔,官網也沒找到解決方案 List userList = jsonDecode(str); userList.forEach((item) { // 這裏不得不手動強轉 User u = User.fromJson(item); print(u.name); }); // name1 // name2 }
由於以前寫js的緣由,更傾向使用第一種方式,由於不用寫實體類;並且第二種方案即便轉換成特定類型,除了可使用.直接訪問到屬性,也沒太大意義。js沒有這種定義實體類的習慣,照樣玩耍。
本覺得三言兩語就能夠說完dart, 最後發現居然寫了1300多行,並且還省略了一些瑣碎的東西(好比dart的集合框架)。
祝你們flutter入坑快樂!~
最後獻上文章開始提到的小福利。圖片較小,不便查看,可下載源文件,該思惟導圖源文件下載地址參見思惟導圖, 若是能夠的話,順手幫忙點個星星啊☺