Dart 入門 & 與 ts 型別系統的異同

jserTang發表於2019-02-13

[toc]

一、語法

1. 變數宣告

1. var 關鍵字

可以使用 var 進行宣告一個常量

main() {
  var str = 'abc';
  str = '123';
  print(str);  // -> 123
}
複製程式碼

使用 var 關鍵詞進行宣告的時候,dart 會自動推斷出 當前變數的型別,如果在變數宣告的時候沒有進行賦值,那麼該型別就是動態的,類似於 TS 的 any。在型別推斷上跟 TypeScript 是一致的。

var str = 'abc';
str = 123;
print(str);  // -> Error: int 值不能分配給 String 型別的變數 

// 宣告變數時不賦值
var some;
some = 'abc';
str = 123;
print(some);  // -> 123
複製程式碼

2. final 關鍵字

final 宣告一個常量,只能被初始化一次。而且在初始化的時候必須賦值。其類似於 js 中的 const。final 的變數時執行時初始化

final str = 'abc';
str = 'def';
print(str);  // -> Setter not found: 'str'.

// or
final some;
print(some);  // -> must be initialized
複製程式碼
  • 與 js 中的 const 一樣,雖然不可以被重新賦值,但是允許修改其成員
final list = [1, 2];
list[0] = 3;
print(list);  // -> [3, 2]
複製程式碼

3. const 關鍵字

const 與 final 類似,也是表示一個常量,但其表示的是一個編譯時的常量,這個常量既可以是一個變數也可以是一個值。其特性與 final 相似,但是有很大差別,其在編譯時初始化。

  • const 宣告,其值不能接受一個變數
var some = 'abc';
final some2 = some;
print(some);  // -> abc  沒問題

var str = 'abc';
const str2 = str; 
print(str2);  // -> Error: Not a constant expression
複製程式碼
  • 與 final 不同的是,const 宣告的變數,其內部成員也不允許被更改值
const list = [1, 2];
list[0] = 3;
print(list);  // -> Cannot modify an unmodifiable list
複製程式碼
  • 可以用 const 建立一個常量值,其表示,這個值是不能更改得。但是這跟變數的賦值沒關係。
var list = const[1, 2];
list[0] = 3;  // Error: Cannot modify an unmodifiable list

list = [3, 4];
print(list);  // -> [3, 4]  list 不是常量,仍然可以被重新賦值
複製程式碼
  • 如果想定義一個常量,並且不允許其成員變化,可以配合 final 使用
final list = const[1, 2];
list[0] = 3; // -> Error: Cannot modify an unmodifiable list
list = [3, 4];  // -> Error: Setter not found: 'list'
複製程式碼

4. 顯式的宣告變數

dart 支援靜態型別,直接靜態宣告的話,即在宣告時候就規定了其型別,不能賦值非指定型別的值。

String str = 'abc';
str = '123';
str = 2333;  // Error: int 型別不能賦值給 String 型別的變數
複製程式碼

如果不使用靜態宣告,則預設的型別為 dynamic

var bar = 123;
print(bar is dynamic);  // true
複製程式碼

*注:關鍵字 is 用於型別判斷,返回 bool

5. 可以型別宣告與變數宣告關鍵字一起使用

final int number = 123;
number = 233;  // Error: Setter not found: 'number'.

const bool flag = true;
flag = false; // Error: Setter not found: 'flag'.

var String str = 'abc';
str = 'edg';
複製程式碼

6. 預設值

如果在定義變數的時候沒有初始化值,那麼他的初始值是 null。除了常量,因為常量定義時必須有初始值

var some;  // -> null
bool flag;  // -> null
int number;  // -> null
String str;  // -> null
Object obj;  // -> null
final namic;  // Error: must be initialized
複製程式碼

2. 資料型別

2.1 String

String str = 'abc';
複製程式碼
  • 模板字串。使用方法與 js 一樣
String name = '小明';
print('${name} 是個直男');  // -> 小明是個直男
複製程式碼
  • 多行字串

與 js 的反引號不同,dart 需要使用三個單引號或雙引號表示多行字串

String breakStr = '''
    這是一段,
    多行字串
'''
複製程式碼

2.2 numbers

數字型別有三種,int 、double 和 num

  • int 整形
int number = 123;
num = 1.1;  // Error:不能將 double 型別的值賦值給 int 型別
複製程式碼
  • double 浮點數
double number = 1.1;
number = 2;  // Error: 不能將 int 型別的值賦值給 double 型別
複製程式碼
  • num

int 與 doubled 都是 num 的子類,其相當於 int 與 double 的聯合型別。

num number = 1;
number = 2.33;   // 2.33
複製程式碼

2.3 bool

與其他程式語言一樣,她只有兩個值。true or false

bool flag = 1 == 1;
print(flag);  // -> true
複製程式碼
  • 與 js 不同的是,dart 在比較值的時候並不會對比較值自動進行型別轉換
bool flag = 1 == '1';
print(flag);  // -> false
複製程式碼
  • dart 的 boolean 的顯式的,只有 true 才會在判斷時為 true
var flag = 1;
if(flag) {
  print('真');
} else {
  print('假');
}
// Error: 不能將 int 型別的值賦值為 bool 變數
複製程式碼

上面的程式碼在 js 中不會有問題,但是在 dart 中會報錯,因為 flag 不是布林值

2.4 List

列表,類似於 js 中的 Array

  • 建立方式
    • 使用 new 關鍵字
    • 直接使用list字面量
var arr1 = new List();
print(arr1);  // -> [];

var arr2 = [1, 2, '3', {}];
print(arr2);  // -> [1, 2, '3', {}];
複製程式碼
  • 也可以像 ts 一樣,指定 list 的成員型別
List<int> arr = [1, 2];
arr = [1, 2, '3'];  // Error: String 不能分配給 int 型別的變數
複製程式碼
  • 內建方法
    • 增刪改查
var arr = new List<int>();

// 新增成員
arr.add(1);  // -> [1]

// 新增多個成員, 類似於 js 的 concat,但只能接受一個引數
arr.addAll([2, 3]); // -> [1,2,3]

// 獲取索引, 相應的還有 lastIndexOf
var index3 = arr.indexOf(3);
print(index3);  // -> 2

// 移除某個成員,只會移除匹配到的第一個成員
arr.remove(1);  // -> [2,3]

// 清空 List
arr.clear();    // -> []
複製程式碼
  • 將 list 轉化為 Map
List<int> arr = [1,2,3];
Map<int, int> arrMap = arr.asMap();
print(Map);  // -> {0: 1, 1: 2, 2: 3}
複製程式碼

需要注意的是,在將 list 轉化為 Map 的時候,list 必須指定成員型別

2.5 Map

鍵為唯一的鍵值對,鍵與值可以是任意型別。特性有點類似 js 的 Object

  • 建立,賦值與取值。都類似於 js 的 Object, 但是沒有.語法
var map = new Map();

map['a'] = 'a';
map[1] = 1;
map[null] = null;

print(map);  // -> {a: a, 1: 1, null: null}

// 訪問不存在的成員會返回 null
print(map['c']);  // -> null
複製程式碼
  • 指定鍵值的型別
Map<int, int> map = {
    1: 1,
    2: 2,
};

print(map);  // -> 2
複製程式碼
  • length
Map<int, int> map = {
    1: 1,
    2: 2,
};

print(map.length);  // -> 2
複製程式碼
  • 內建方法

    • 增刪改查
      Map<int, int> map = {
          1: 1,
          2: 2,
      };
      
      // 新增成員
      map.addAll({3: 3});  // -> {1: 1, 2: 2, 3: 3}
      
      // 移除某個成員
      map.remove(2); // -> {1: 1, 3: 3}
      
      // 清空成員
      map.clear();  // -> {}
      複製程式碼
    • 校驗是否有成員
      Map<String, int> map = {
        'a' :1,
      };
      
      // 是否有該 key
      bool hasKey = map.containsKey('a');
      
      // 是否有該 value
      bool hasValue = map.containsValue(2);
      print(hasKey);    // -> true
      print(hasValue);  // -> false
      
      // 是否是一個空物件
      print(map.isEmpty);  // -> false
      複製程式碼
      • 過濾
      var map = {
          1: 1,
          1: 1,
          3: 3
      };
      // 移除符合的某一條件的屬性
      var filterAttr = map.removeWhere((key, value){
          return value < 2;
      });
      print(map); // {3: 3}
      複製程式碼
  • 更多

  • map 的屬性是可保證順序的

js 非常坑的一點是,在遍歷一個物件的時候,並不能保證屬性的順序。但是 dart 可以。

js 中遍歷一個物件的時候

var obj = {
    3: 'a',
    2: 'b',
    1: 'c',
};
var arr = [];
for(var key in obj){
  arr.push(key);
}
console.log(arr);  // [1, 2, 3]
複製程式碼

上面的程式碼中,我遍歷了一個物件,想將其 key 依次 push 進一個陣列,我期望得到的是 [3, 2, 1],但是的得到的確是 [1, 2, 3], 因為 javascript 並不能保證 object 屬性的順序

dart 中遍歷一個 map

Map<int, String> map = {
	3: 'a',
	2: 'b',
	1: 'c',
};

List<int> arr = [];
map.forEach((key, value){
  arr.add(key);
});
print(arr);  // [3, 2, 1]
複製程式碼

map 會按照定義時的屬性的位置順序進行遍歷,正確的列印出 [1, 2, 3]

2.6 Function(方法)

Function 在 javascript 中是一等公民,既可以當做方法賦值給變數,也可以作為引數,也可以將例項的函式型別的屬性當做方法使用。Dart 的Function 與 它類似

  • 定義一個 方法
int sum(int x, int y) {
    return x + y;
}
sum(1, 2);  // 3
複製程式碼

上面是一個求和方法,接受兩個引數,規定必須是int 型別,其 returns 型別的宣告是放在最前面的,與 typeScript 不同,typeScript 是 在函式頭後面宣告

  • 箭頭函式

箭頭函式使用方法與 js 一樣

int sum(int x, int y) => x + y;
複製程式碼
  • 可選引數

與 ts 不同的是,dart 不是使用來標記可選引數,而是使用 []

int sum(int x, int y, [int z]) {
  if(z == null) {
    return x + y;
  }
  return x + y + z;
};

sum(1, 2);  // -> 3
sum(1, 2, 3)  // -> 6
複製程式碼
  • 預設引數值

預設引數與 js 使用方法一樣。在定義方法的時候直接引數 = value, 但是預設值只能加給可選引數

int sum(int x, int y, [int z = 3]) {
  return x + y;
};

sum(1, 2);  // -> 6
複製程式碼

如果給必選引數加預設值會報錯

int sum(int x, int y, int z = 3) {
  return x + y;
};

// Error:  Non-optional parameters can't have a default value.
複製程式碼
  • 可選命名引數

可以給引數指定名字,未指定名字的引數為未知引數。有些時候在使用可選引數的時候並不明確引數的定義,可以使用命名引數,在使用方法的時候必須加上可選引數的名字

int sum(int x, int y, {int z: 3}) {
  return x + y + z;
};

sum(1, 2, z: 3);  // -> 6
sum(1, 2, 3);  // -> Error: 應該有兩個位置引數,但發現了三個
複製程式碼
  • 匿名函式

匿名函式就是宣告時沒有進行命名的函式

List<int> arr = [1, 2, 3, 4, 5];
arr.forEach((v) {
  print(v);  // 1, 2, 3, 4, 5
});
複製程式碼

上面的程式碼將一個列印的匿名方法傳進forEach

  • 返回值

dart 的每一個函式都有返回值,如果沒有 return 返回值則自動 return 一個 null

add(int x) {}
var autoNull = add(1);
print(autoNull);   // null
複製程式碼

2.7 dynamic 型別

如果不確定該變數的值是什麼型別, 可以使用 dynamic,表示該型別的型別是動態的,類似於 TspeScript 中的 any。與 typeAcript 一樣,並不推薦使用它,因為錯誤的型別使用不會在編輯時報錯但是會在執行時報錯

dynamic some = 'abc';
some = some + 123;  // 編譯時不會報錯,但是執行時會報錯
複製程式碼

2.8 Obiect 型別

Obiect 表示任意型別,object之所以能夠被賦值為任意型別的原因,因為所有的型別都派生自 Obiect.

Obiect some = 'str';
some = 1;
some = true;
複製程式碼

3. class

每個物件都是一個類的例項,所有的類都繼承於 Object。 基於 Mixin 的繼承 意味著每個類(Object 除外) 都只有一個超類,一個類的程式碼可以在其他 多個類繼承中重複使用。class 不可以定義在 main 主函式中

class Person {
  String name = '湯姆';
  int age = 8;
}

main() {
    var tom = new Person();
    print(tom.name);  // '湯姆'
}
複製程式碼
  • 建構函式(constructor)

跟 js 不一樣,dart 的建構函式是一個與 class 同名的函式。

class Person {
  String name;
  int age;
  
  // 與 class 同名的建構函式
  Person(String name, int age){
    this.name = name;
    this.age = age;
  }
  void sayHi() {
    print('my name is ${this.name}, 今年${this.age}歲');
  }
}

main() {
  Person xiaohong = new Person('小紅', 8);
  xiaohong.sayHi();  // -> my name is 小紅, 今年8歲
}
複製程式碼
  • 建構函式不能被繼承,但是子類會預設的去呼叫父類的沒有引數的建構函式
class Person {
  String name;
  Person(){
    this.name = '我是誰';
  }
}

class People extends Person {
  int age;
  People() {
    this.age = 18;
  }
}


People xiaohong = new Person();
print(xiaohong.name);  // 預設呼叫了父類的建構函式,所以 name 屬性有值
print(xiaohong.age);
複製程式碼
  • 命名建構函式

在一個類裡面可以建立多個建構函式,其命名方式為建構函式名.xxx建立例項的時候,可以選擇不同的建構函式進行建立。

class Person {
  String name;
  String age;
  Person(this.name, this.age);
  Person.fromMap(Map<String, String> param) {
    this.name = param['name'];
    this.age = param['age'];
  }
  sayHi() {
    print('$name, 今年$age歲');
  }
}

main() {
  // 使用普通的建構函式建立例項
  Person xiaohong = new Person('小紅', '8');
  xiaohong.sayHi();  // 小紅, 今年8歲

  // 使用命名建構函式 fromMap 來建立例項
  Map<String, String> userInfo = {
    'name': '小明',
    'age': '6'
  };
  Person xiaoming = new Person.fromMap(userInfo);
  xiaoming.sayHi();  // 小明, 今年6歲
}
複製程式碼
  • 手動呼叫父類的建構函式

:super.父類的命名建構函式名 就可以手動呼叫父類的建構函式, 呼叫順序為先呼叫父類的函式再執行自己的建構函式

class Person {
  String name;
  Person.initName(){
    this.name = '我是誰';
    print(1);
  }
}

class People extends Person {
  int age;
  People():super.initName() {
    this.age = 0;
    print(2);
  }
  sayHi() {
    print('$name, 今年$age歲');
  }
}

main() {
  People xiaohong = new People();
  // -> 1
  // -> 2
  xiaohong.sayHi();  我是誰, 今年0歲
}
複製程式碼
  • 建構函式重定向

建構函式的宣告中可以呼叫其他的建構函式,此時,該建構函式沒有函式體

class Person {
  String name;
  Person.name(String name) {
    this.name = name;
  }
  // 建構函式 init 呼叫了建構函式 name
  Person.init(String name): this.name(name);

  sayHi() {
    print('我叫$name');
  }
}

main() {
  Person xiaohong = new Person.init('小紅');
  xiaohong.sayHi();  // 我叫小紅
}
複製程式碼
  • 工廠方法建構函式

如果一個建構函式並不總是返回一個新的物件,則使用 factory 來定義 這個建構函式。下面的類是為了建立 person 並避免建立重名的 person,建立新的物件後快取起來,如果建立重名的就從已有的物件裡面去取。

工廠方法建構函式裡不能訪問 this

class Person {
  String name;

  static final Map<String, Person> personMap = <String, Person>{};

  factory Person(String name) {
    if (personMap.containsKey(name)) {
      print('catch person');
      return personMap[name];
    } else {
      print('new person');
      Person newPerson = new Person.create(name);
      personMap[name] = newPerson;
      return newPerson;
    }
  }
  Person.create(String name) {
    this.name = name;
  }

  sayHi() {
    print('my name is ${name}');
  }
}

main() {
  Person xiaohong = new Person('小紅');  // -> new person
  xiaohong.sayHi();  // -> my name is 小紅
  
  Person xiaoming2 = new Person('小紅');  // -> catch person
  xiaoming2.sayHi();  // -> my name is 小紅
}
複製程式碼

上面建立的第二個小紅,其實就是取得第一個小紅,並沒有建立新物件。

  • 在 class 中訪問 this 的成員的時候,可以忽略 this.xxx, 當未宣告變數的時候會自動尋找 this 中有沒有該屬性
class Person {
  String name;
  int age;
  
  Person(String name, int age){
    this.name = name;
    this.age = age;
  }
  void sayHi() {
    // 這裡手動定義的 age 優先順序比 this.age 高
    var age = 99;
    // 沒有定義 name,會自動尋找 this.name
    print('my name is ${name}, 今年${age}歲');
  }
}

main() {
  var xiaohong = new Person('小紅', 8);
  xiaohong.sayHi();  // -> my name is 小紅, 今年99歲
}
複製程式碼
  • setters 與 getters

setters 與 getters 是用來設定與獲取屬性值的函式,一般情況下不需要手動設定,因為每個例項成員都有隱性的 getter 方法,非 final 的成員也會有隱性的 setter 方法。如果顯式的去宣告的話,使用 setget 關鍵字

set 與 get 函式中都不可以訪問自己,否則會死迴圈
set 函式中可以修改其他成員的值

下面的例子顯式的宣告瞭 Point 的 y 的 set 與 get 函式

class Point {
  int x;
  Point(this.x);
  int get y => x + 3;
  set y(int value) => x = value;
}

main() {
  Point point = new Point(5);
  point.y = 10;
  print(point.x);
  print(point.y);
}
複製程式碼
  • 抽象類與抽象函式

抽象類不能例項化,需要繼承他的子類去例項。抽象函式是隻有函式名但是沒有函式圖,需要子類去實現具體的功能

抽象函式用分號代替函式體
抽象函式必須在子類中被實現

abstract class Person {
  sayHi();
}

class Student extends Person {
  sayHi() {
    print("I'm a student");
  }
}

main() {
  Person student = new Student();
  student.sayHi();
}
複製程式碼
  • 介面

dart 沒有提供 interface 關鍵字用來宣告介面,
但是可以使用implements關鍵字將 class 當做介面使用

/// 一個抽象類 Person
abstract class Person {
  String name;
}

/// 使用 implements 關鍵字把 Person 當做介面使用
/// Teacher 必須含有 Person 的格式
class Teacher implements Person {
  String name;
}

main() {
  var xiaohong = new Teacher();
  print(xiaohong);  // -> Instance of 'Teacher'
}
複製程式碼

在使用介面的時候,並不是像 typescript 一樣必須嚴格一致,dart 裡只需要實現介面內的結構就不會報錯,可以有自己的成員。

/// 作為 Person 介面的實現,Teacher 多了個 age 成員,但是沒有問題
class Teacher implements Person {
  String name;
  int age;
}
複製程式碼

4. 異常

  • catch

可以使用 try catch 語句來捕獲異常

try {
    dynamic foo = true;
    print(foo++);   // 執行時錯誤
} catch (e) {
    print('錯誤型別: ${e.runtimeType}');  // -> 錯誤型別: NoSuchMethodError
} finally {
    print('無論有沒有異常都會執行');  
}
複製程式碼
  • 主動丟擲錯誤

可以使用 throw 主動丟擲錯誤,也可以在 catch 裡使用 rethrow 關鍵字繼續將捕獲到的錯誤丟擲

throw 丟擲的異常可以是任意型別

  try {
    throw '一個字串錯誤';  // 主動丟擲一個 String 型別的錯誤
  } catch (e) {
    print('錯誤型別: ${e.runtimeType}.');  // 錯誤型別: String.
    rethrow;   // 使用 rethrow 關鍵字繼續將錯誤丟擲
  }
複製程式碼
  • 根據錯誤型別分層處理

可以使用 on 宣告要在塊裡捕獲的錯誤型別,順序從上往下類似於 switch;

  try {
    throw '一個字串錯誤';
  } on String catch(e) {
    print('捕獲 String 型別異常: ${e.runtimeType}.');  // 捕獲 String 型別異常: String
  }catch (e) {
    print('捕獲所有型別異常: ${e.runtimeType}.');
  }
複製程式碼

5. 型別判斷

dart 可以使用 is 關鍵字進行型別判斷。比 typescript 與 javascript 的 typeof 更加方便、準確

var str = 'string';
print(str is String);  // true
複製程式碼

6. 泛型

泛型又稱為引數化型別,使用方法與 typescript 型別,在具體的型別後面加<型別>。dart 還可以用字面量的形式在之前面使用泛型進行註解

List<bool> arr = [1];
// Error: 引數型別'int'不能分配給引數型別'bool'

var array = <int>[1, 2, 3];
arr.array('4');
// Error: 引數型別'String'不能分配給引數型別'int'
複製程式碼

上面兩種泛型的使用方式都能對型別進行保護

  • dart 的泛型是固化的,在執行時的程式碼也可以進行正確的型別檢查;
var arr = new List<int>();
arr.addAll([1, 2, 3]);
print(names is List<int>);  // -> true
複製程式碼
  • 泛型函式

泛型函式可以在以下地方使用泛型

返回值
引數
函式內變數

T getFirstItem<T>(List<T> arr) {
    T firstItem = arr[0];
    return firstItem;
};

var first = getFirstItem<int>([1, 2, '3']);
print(arr);  // Error: 不能將引數型別 String 分配給 引數型別 int
}
複製程式碼

上面程式碼因為 getFirstItem 在使用的時候宣告泛型是 int,但是傳入的 List 內卻有 '4',所以報錯了

  • extends

可以使用 extends 關鍵字來縮小泛型的型別範圍,使用方式與 ts 一樣

7. typedef

可以給方法的型別起個名字,用來定義方法的樣子。類似於 ts 的 type,不過只能用於方法

typedef int Add(int x, int y);

main() {
  Add add = (x, y) => x + y;

  var sub1 = add(1, '2');  // Error: 不能將 String 型別的引數賦值給 int 型別的引數

  var sub2 =  add(1, 2);  // -> 3
  print(sub2 is int);     // -> true

}
複製程式碼

上面定義了一個名為Add的方法型別別名,其作為方法 add 的型別,因為 sub1 傳入的引數型別與 Add 定義的不符,所以報錯了。 因為 Add 定義了 add 方法的返回值是 int 型別,所以 (sub2 is int) 為 true

8. 模組化

7.1 pubspec.yaml

建立pubspec.yaml檔案來宣告該專案需要的依賴包,在 使用 put 命令拉取依賴包的時候會生成一個 .packages 檔案,該檔案將專案所依賴的每個程式包名稱對映到系統快取中的相應包

name: myApp
dependencies:
  js: ^ 0.3.0
  intl: ^ 0.12.4
複製程式碼

7.2 pub

dart 使用 pub 工具來管理 package 和 assets。在下載 Dart 的時候會帶有 pub不需額外下載

pub get  # 獲取依賴項

pub upgrade  # 更新依賴項

pub run [some script] [args]  # 執行某個指令碼
複製程式碼

pub 命令

7.3 import

引入內建的庫: dart: 庫名

import 'dart:io';
複製程式碼

可以使用 import 語句從 package 引入某個模組,要加 package 宣告

// 使用 import as 語句引入某個模組
import 'package:js/js.dart' as js;

// 引入 js 模組的一部分 - anonymous
import 'package:js/js.dart' show anonymous;
複製程式碼

引入庫除此之外的其他部分

// 引入 js 庫除 anonymous 的部分
import 'package:js/js.dart' hide anonymous;
複製程式碼

直接使用相對路徑引入自己的模組

import '../someLib.dart';
複製程式碼

7.4 按需載入

dart 直接提供了按需載入的語法,可以讓應用在需要的時候再載入庫。比如說一個頁面中可能有不同的狀態,不同的狀態下可能需要不同的依賴庫,這時候使用按需載入的話就能減少不必要的資源與效能浪費。這非常非常的方便!想想一下在寫 js 業務的時候,在一個有多種狀態的頁面需要把每一個狀態的依賴載入下來。

想要延遲載入一個庫,使用 deferred 關鍵字來匯入

import 'package:js/js.dart' deferred as JS;

複製程式碼

使用的時候,非同步的方式呼叫庫的 loadLibrary 函式就可以, 非同步語法與 js 相似

int flag = true;

getJs() async {
    await Js.loadLibrary();  // 引進 JS ku
    // 下面的程式碼可以使用 JS 庫了
}
複製程式碼

當延遲載入庫時,整個庫內的內容都是為載入過來的,包括庫內的型別宣告。所以如果想在使用庫前使用庫內的型別宣告,可以把這個庫的型別宣告抽出來單獨作為一個檔案。

7.5 建立一個自己的包

包檔案:person.dart

library person;  // 指定庫名,用來生成文件。

class Teacher {
  String name;
  Teacher(this.name) {
    print('我是 ${name} 老師');
  }
}

class Student {
  String name;
  Student(this.name) {
    print('我是學生 ${name}');
  }
}
複製程式碼

上面建立了一個 person.dart 檔案,裡面定義了兩個類 teacher 和 student,不需要匯出,使用的時候直接引進來就行。 library 一般不需要手動宣告,因為 dart 會自動生成唯一的庫標識,除非想要生成庫 文件。

import './common/person.dart' as person;

main() {
  var xiaohong = new person.Teacher('小紅');  // -> 我是 小紅 老師

  var xiaoming = new person.Student('小明');  // -> 我是學生 小明
}
複製程式碼

也可以使用 show 關鍵字只匯入某一個部分

import './common/person.dart' show Teacher;

main() {
  var xiaohong = new Teacher('小紅');  // -> 我是 小紅 老師
}
複製程式碼
  • part

有時候一個庫很大,或者一個庫裡需要有公用的變數或方法,可以使用 part 語法進行拆分。

還是上面 person 的例子,假如 Teacher 類與 Student 類都有一個 sayHi 的函式,而這兩個類的 sayHi 函式的邏輯是一樣的,這裡就可以將這個邏輯單獨抽出來放到一個獨立的檔案中去實現。

person 庫:

library person;

part 'commonSayHi.dart';  // 宣告sayHi的實現 - commonSayHi 的路徑

class Teacher {
  String name;
  Teacher(this.name);
  sayHi() {
    commonSayHi(this.name);
  }
}

class Student {
  String name;
  Student(this.name);
  sayHi() {
    commonSayHi(this.name);
  }
}
複製程式碼

commonSayHi:

part of 'person.dart';  // 宣告這是用於 person.dart 檔案的實現

commonSayHi (name) {
  print("Hello, I'm ${name}");
}
複製程式碼

使用:

import './common/person.dart' show Teacher;

main() {
  var xiaohong = new Teacher('小紅');
  xiaohong.sayHi();  // Hello, I'm 小紅
}
複製程式碼

?. 總結 - Dart 的語言特性

  • 強型別,靜態型別檢查

    • dart 支援靜態型別,但並不是強制的,如果不靜態宣告則默型別為 dynamic
  • 靜態作用域 & 擁有塊級作用域

dart 跟 js 一樣是靜態作用域。但是 Dart 有塊級作用域,在 {}外訪問不到{}內的程式碼。

{
    var a = 1;
}
 print(a);  // Error: Getter not found: 'a'
複製程式碼

上面列印的時候訪問不到 {} 內的變數,所以報錯了

相關文章