dart入門指南

来源:https://www.cnblogs.com/floor/archive/2019/12/20/12070991.html
-Advertisement-
Play Games

本文將簡單介紹flutter的語言特性、基礎語法,以及在日常開發中非常實用的如何請求數據、如何處理非同步、如何序列化與反序列化json等技能。 ...


dart-logo

近來,flutter的熱度在上升。flutter應用的主要開發語言是dart, 因此,欲練flutter, 必先瞭解dart.

dart是由google開發的編程語言,可用於開發移動應用,桌面應用,h5應用,後端服務。

本文將簡單介紹dart的語言特性、基礎語法,以及在日常開發中非常實用的如何請求數據、如何處理非同步、如何序列化與反序列化json等技能。

文章比較長,熟悉的部分各位看官可快速瀏覽,文末也給出了小小福利,供大家參考。疏漏之處請見諒,錯誤之處請指正。


語言特性

面向對象

dart是一門純粹的面向對象語言,在dart中一切皆對象。

  • 函數也是對象,函數能被賦值給變數,也可以作為函數的參數或返回值
  • 基礎類型是對象,字面值也是對象,比如可以像下邊這樣使用
1.toString();
  • 支持介面、抽象類、泛型、多態

支持頂級函數和變數

與java不同的是,java的變數和方法只能在類裡邊,而dart可以有不在類里的方法和變數。

帶有面向過程類語言的特點,比如像c。

強類型弱類型均支持

在dart中你可以顯式的指定變數的類型,也可以不指定類型,由dart推斷類型。指定類型的話編譯器可以做靜態類型檢查。

在類型方面dart既有弱類型語言(像js)也有強類型(像java)的特點。

兼具解釋性語言和編譯型語言特點

對開發web應用來講:dart會被轉譯成js

對app、服務端、桌面應用講:

  • dart提供了開發模式,通過vm解釋執行,支持熱更新,具有解釋型語言特點
  • 也提供了生產模式,通過編譯成機器碼執行,具有編譯型語言特點

小結

dart看起來是希望融合多種主流語言的特性,在dart中能夠看到多種語言的影子。

基礎語法

程式入口

dart的程式執行入口是main函數,這跟c很像,main函數一個頂級函數。

返回值通常是void, 寫為int, double等也不會報錯,不過沒有意義。

void main() {
  print('Hello, World!');
}

變數和常量

  • dart中的變數或常量必須先定義後使用
  • 未被初始化的變數和常量預設值為null

變數

  1. var 變數名[=值];
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)變數
}
  1. 數據類型 變數名[=值];

這種方式可以顯式聲明變數類型,以便做一些靜態檢查。

void main() {
  // 聲明並初始化
  int var1 = 1;
  print(var1); // 1

  // 先聲明後初始化
  int var2;
  var2 = 1;
  print(var2); // 1

//  var2 = '1'; 這是錯誤的,類型不匹配
}
  1. dynamic 變數名[=值]

這個dynamic意思是動態類型,這種變數可以隨便給他賦什麼類型的值

void main() {
  dynamic var1 = 1;
  var1 = '2'; // 動態類型可以賦任意類型的值
  print(var1); // 2
}

常量

  1. final [數據類型] 常量名=值

在運行時確定其值, 可以作為類的普通成員變數

  1. const [數據類型] 常量名=值

必須在編譯時確定其值,只能作為類的靜態成員變數,不能作為普通成員變數

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; 錯誤,必須在編譯時可以確定值
}

數據類型

  1. num、int、double
  • int類型和double類型都是num類型
  • 都是占8byte
  • 是對象,有一些方法和屬性
  • 沒有無符號類型
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
}
  1. String

字面值表示法

  • 單行字面值表示方法:'xx'或"xx"
  • 多行字面值表示方法: '''xxx'''或"""xxx"""
  • 忽略轉譯符: r'xx\nxx'
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
}
  1. bool

布爾值,字面值只能是true或false, 不能為其他

void main() {
  bool bool1 = true;
  print(bool1); // true
}
  1. List

類似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); // []
}
  1. Set

表示集合,無重覆值,加重覆值不會報錯,但是進不去,無序。

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
}
  1. Map

表示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
}
  1. enum

表示枚舉,是一種特殊的類,官方文檔沒有將其納入到基礎類型,這裡為了方便理解放到這裡。

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
}

數據類型轉換

  • java中有自動類型轉換機制,例如可以將int型自動轉為double;dart中類型不匹配時必須強轉,沒有自動類型轉換
  • 其他類型轉String或數字之間相互轉換 toXxx()方法
  • String轉數字使用數字類(int,double,num)的parse()方法
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();
}

操作符

操作符與其他語言基本相同,這裡說明幾個特殊的,其他簡單羅列。

  1. 算術運算符

+、-、*、/、%、++、--

比較特殊的是~/, 表示整除

void main() {
  int int1 = 3;
  print(int1 ~/ 2); // 1
}
  1. 比較運算符

==、!=、>、<、<=、>=

  1. 賦值運算符

=、-=、~/=等等

比較特殊的是 變數名??=值, 表示如果左邊變數是null則賦值,否則不賦值

void main() {
  int a;
  a ??= 1;
  print(a); // 1

  int b = 2;
  b ??= 1;
  print(b); // 2
}
  1. 邏輯運算符

!、||、&&

  1. 位運算符

&、|、^、~expr、<<、>>

  1. 條件表達式

condition ? expr1 : expr2

  1. 級聯運算符

這是比較特殊的運算符,類似於jquery中的連續的.操作, 上一次操作還是返回當前對象,因此可以繼續調用其方法。大多語言沒有這種操作。

class User {
  say() {
    print("say");
  }

  run() {
    print("run");
  }
}

void main() {
  User user = User();
  user..say()..run();
  // say
  // run
}
  1. 類型運算符

這是比較特殊的運算符,用於判斷類型和強轉,類似java裡邊(User) instance這種

  1. instance as className

用於將某個類型轉換為其他類型,必須滿足父子關係,否則報錯。

  1. import 'xxx' as variable;

用於指定導入庫的別名,相當於將庫里導出來的東西掛到一個map上,可以解決重名問題。

  1. instance is classNmae

用於判斷一個對象是否是某個類的實例

  1. instance is! classNmae

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的方法
}

流程式控制制語句

流程式控制制與其他語言相同,這裡簡單列舉

  • if .. else if..else
  • for
  • while、do..while
  • break、continue
  • switch .. case
  • assert

assert表示斷言,判斷一個表達式的真假,若為假則拋出一個異常

模塊化

dart支持模塊化編程,可以導入dart內置庫,其他三方庫,也可以將自己的代碼拆分成不同模塊。

  1. 導入
  • 導入內置庫
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的按需載入。

  1. 創建庫

內容較多,此處略過,筆者也尚未研究,可參考官網

  1. dart內置庫介紹
  • dart:core

dart核心內庫,預設導入,類似java的java.lang包。提供內置數據類型等。

  • dart:async

非同步支持庫,大名鼎鼎的Future就在這裡邊。

  • dart:math

複雜數學計算相關

  • dart:convert

json序列化、反序列化,字元集轉換相關。

  • dart:html

web應用相關api

  • dart:io

io相關,與發請求相關的HttpClient在這裡邊

函數

  • 函數可以在頂層,也就是不屬於任何一個類
  • 函數可以是類的成員方法
  • 可以有返回值也可以沒有
  • 函數是對象,可以賦值給變數,可以作為參數或返回值
  • 參數可以是常見的位置參數,也可以是命名參數,命名參數傳遞時不必考慮順序,命名參數大多數語言沒有,聽說是從oc借鑒過來的
  • 只有一條語句的函數可以使用箭頭函數
  • 可以使用typedef定義特定的函數類型
  • 函數參數可以指定預設值
// 沒有返回值的函數
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);
}

註釋

  1. 單行註釋
// 我是一行註釋
  1. 多行註釋
/**
 * 多行註釋
 * 多行註釋
*/
  1. 文檔註釋
/// 文檔註釋
/// 文檔註釋

面向對象

類的定義與組成

類使用class關鍵字定義

一個類可以由如下部分組成:

  • 普通成員變數
  • 靜態成員變數
  • 普通方法
  • 靜態方法
  • 構造函數,不聲明的話預設有無參構造
  • 命名構造函數
  • set,get方法

說明

  • 不支持重載
  • 靜態成員不用實例化即可訪問,只能通過類名訪問,不像java可以通過實例訪問靜態成員變數
  • 靜態成員方法中不能使用this,且只能訪問靜態成員變數
  • 不寫set,get方法會有預設的set,get方法
  • 不支持public、private等訪問修飾符,一般約定_開頭的變數為私有的,但是只是約定,實際還是可以訪問到
// 使用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關鍵字
  • 只支持單繼承
  • 子類可以獲得父類的普通成員方法和普通成員變數,無法得到靜態成員,構造方法也繼承不到
  • 可以重寫父類的方法,可以使用super訪問父類
  • 如果父類沒有無參構造的話,子類必須顯示調用父類的構造,否則報錯
// 使用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);
}

抽象類

要點:

  • 在class前加abstract關鍵字定義抽象類
  • 抽象類不能被實例化,只能被繼承(後面會講到,也可以被當成介面實現)
  • 抽象類可以有抽象方法(不含方法體的方法),也可以有普通方法和成員變數
  • 繼承抽象類的類,要麼重寫其所有抽象方法,要麼該類也要被被定義為抽象類,要麼增加一個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);
}

異常

要點:

  • 無需顯式指定拋出哪些異常,這與java不同,java需要使用throws 異常類
  • 不是必須對異常處理,java如果有throws那麼必須捕獲
  • 有Error、Exception及其子類作為異常類,也可自定義
  • 拋出異常時可以拋出異常類,也可以拋出非null的任何對象
  • 可以使用try catch 處理異常,也可以再次向上拋出
// 拋出一個異常實例
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是類似的。

  1. Future的創建

可以使用Future的構造方法創建Future

  • Future Future(FutureOr computation()) 可以非同步的執行computation函數,其結果就是Future的結果
  • Future Future.delayed(Duration duration, [ FutureOr computation() ]) 延遲去執行裡邊的函數
  • Future Future.error(Object error, [ StackTrace stackTrace ]) 延遲拋出一個錯誤
  • Future.sync(FutureOr computation()) 立即執行
  • Future Future.microtask(FutureOr computation()) 以微任務方式執行
  • Future Future.value([FutureOr value ]) 參數是一個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對比參見後文

  1. Future 結果的獲取
  • 如果一個函數返回Future, 那麼可以使用async await 獲取其結果,可以使用try catch 捕獲異常
  • 可以使用.then獲取正常結果,.catchError獲取拋出的異常
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
  // 按順序執行
}
  1. Future執行順序比較

通過測試得出:

  • microtask 和async是寫在前邊的先執行
  • 直接使用構造函數的在最後執行
  • delayed在microtask和async之後執行

關於這幾者的比較,有興趣的同學可以自行研究。

網路請求

常用的髮網絡請求的方法有兩種:

  • 一是使用dart:io下的HttpClient
  • 二是使用package:http/http.dart

HttpClient

使用步驟:

  • 創建HttpClient
  • [可選]配置HttpClient,比如設置超時時間,代理等
  • 獲取HttpClientRequest
  • [可選]設置httpClientRequest,如header
  • 獲取httpClientResponse
  • 處理響應結果
  • 關閉連接
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);
}

還有關於上傳文件的內容,本文篇幅過長了,後面再寫一個。

http

我們發現使用原生的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是一種非常靈活的數據交換格式。

json序列化與反序列化通俗講就是將 語言定義的非字元串類型(比如java中的實體類,dart中的Map)轉換為json字元串,便於傳輸和存儲,這是序列化;反序列化則是這個逆過程,方便操作數據。

在java中一般使用阿裡的fastjson做json的序列化與反序列化;

在js使用JSON.stringify()、JSON.parse()做序列化與反序列化。

在dart中json序列化與反序列化有兩種方式:

  • 一是 使用內置庫dart:convert的jsonDecode和jsonEncode
  • 二是 使用package:json_serializable,這個庫跟下問所說的第二種方法差不多,只不過他會自動幫你生成下邊會講到的那些樣板代碼,這裡不進行詳細介紹了。

使用dart:convert

  1. 不關註類型,直接用動態類型,這種方式會導致無法錯類型檢查,可能不安全,好處是簡單,不用寫實體類
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);
}
  1. 使用類型轉換,在實體類定義一些轉換規則,好處是可以有靜態類型檢查,缺點是比較麻煩
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入坑快樂!~

最後獻上文章開始提到的小福利。圖片較小,可下載源文件查看,該思維導圖源文件下載地址參見思維導圖, 如果可以的話,順手幫忙點個星星啊☺

dart基礎語法思維導圖

參考文獻

  • https://flutter.dev/docs/development/data-and-backend/json#manual-encoding
  • https://dart.dev/guides/language/language-tour

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 情況概述 今天登陸在騰訊雲伺服器上搭建的 MySQL 資料庫,發現資料庫被黑了,黑客提示十分明顯。 MySQL 中只剩下兩個資料庫,一個是information_schema,另一個是黑客創建的PLEASE_READ,其中有一張info表,內容如下: Info: Your DB is Backed ...
  • #MYSQL單節點的mysql遠遠不能滿於生成,以防止生產伺服器宕機,磁碟空間溢滿等種種原因,需要有一個備用資料庫, 這時候主從庫是不錯的選擇,在是資料庫集群中也起到了很大的作用 #MySQL 主從複製概念: MySQL 主從複製是指數據可以從一個MySQL資料庫伺服器主節點複製到一個或多個從節點。 ...
  • 在Python3中操作MySQL資料庫 在Python3中使用mysql資料庫需要安裝pymysql庫 操作MySQL 導包 第一步:打開資料庫連接 第二步:創建游標 第三步:操作資料庫 1、創建表 2、查詢數據 1. Python查詢Mysql使用 fetchone() 方法獲取單條數據, 使用f ...
  • MySQL密碼正確卻無法本地登錄 報錯如下: ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES) 解決方法:1、在啟動mysql的參數中加入跳過密碼問題方式,如下:vim /etc/m ...
  • 用的Toad for Oracle 12.1 編輯,Oracle 10g ...
  • 1 、 下載對用的版本信息 地址是:https://dev.mysql.com/downloads/mysql/ 2 、 解壓到目錄 D:\tools\mysql\mysql-5.7.21-winx64 3、新建my.txt文件 重命名 my.ini 內容如下: 我本機目錄是雙斜杠 \\ 單斜杠在安 ...
  • 文檔:基於windows server 2016和sqlserver 2...鏈接:http://note.youdao.com/noteshare?id=4f07c1c3f7d0e32b7631d7d867385bbf&sub=941FBCFD5E2F4A8086FBDD40A214C526 ...
  • http://note.youdao.com/noteshare?id=a61c4a6ff2b76e5305430eb66eb116e2&sub=4B4B6E8D0E2849F9B0DFB67D43D12A1C ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...