快速掌握Flutter——Dart语法指南

发表时间: 2021-03-22 08:30

温馨提示:本文只罗列 Dart 中比较重要、奇特的语法,所以不合适没有其他语言基础的人学习!!

一、Dart 的基本语法

1、程序入口

  • Dart 的入口也是 main 函数,且没有返回值。
  • 传递给 main 的命令行参数,会存放在 List<String> args 中。
  • 定义字符串可以使用单引号或双引号。
  • 每行语句必须使用分号结尾。
main(List<String> args) {  print("Hello World");}

2、声明变量

  • 明确声明:变量类型 变量名称 = 赋值;
  • 类型推导:var/dynamic/const/final 变量名称 = 赋值;
// 1. 明确的声明String name = "lqr";// 2. 类型推导(var/final/const)// 类型推导的方式虽然没有明确的指定变量的类型,但是变量是有自己明确的类型的// 2.1 var声明变量var age = 18;// age = "abc"; // IDE报错:A value of type 'String' can't be assigned to a variable of type 'int'.// 2.2 final声明常量final height = 1.88;// height = 2.00; // IDE报错:The final variable 'height' can only be set once.// 2.3 const声明常量const address = "广州市";// address = "北京市"; // IDE报错:Constant variables can't be assigned a value.// 2.4 final和const的区别// const必须赋值 常量值(编译期间需要有一个确定的值)// final可以通过计算/函数获取一个值(运行期间来确定一个值)// const date1 = DateTime.now(); // 写法错误final date2 = DateTime.now();// 2.5 Object和dynamic的区别// 分别使用Object 和 dynamic,让父类引用指向子类对象// Object调用方法时,编译时会报错Object obj = "lqr";print(obj.substring(1)); // IDE报错:The method 'substring' isn't defined for the type 'Object'.// dynamic调用方法时,编译时不报错,但是运行时会存在安全隐患// dynamic是明确声明(var是类型推导)dynamic obj = "lqr";print(obj.substring(1)); // qr

3、[Dart 没有]非零即真

js 中存在非零即真、非空即真的特性,但在 Dart 中没有!!Dart 要求 if() 语句必须传入一个 bool 类型:

// Dart中没有非零即真,也没有非空即真var flag = "true";if (flag) { // IDE报错:Conditions must have a static type of 'bool'.  print("执行代码");}

注意:Dart 不支持非空即真或者非 0 即真,必须有明确的 bool 类型

4、字符串类型

Dart 有三种定义字符串的方式:

  • 单引号(''):与双引号相同。
  • 双引号(""):与单引号相同。
  • 三引号(""""""):可以定义多行字符串,不需要借助 \n。
// 1. 定义字符串var str1 = 'abc';var str2 = "abc";var str3 = """abccbanba""";// 小技巧:如果字符串中存在双引号,又不想转义的话,可以使用单引号来定义该字符串,反之使用双引号。print('a"b"c'); // a"b"cprint("a'b'c"); // a'b'c

Dart 支持字符串插值:

// 2. 字符串和表达式拼接var name = "lqr";var age = 19;var height = 1.88;// 强调:${变量}可以省略{},${表达式}不能省略{}// var message1 = "my name is ${name}, age is ${age}, height is ${height}";var message1 = "my name is $name, age is $age, height is $height"; // my name is lqr, age is 19, height is 1.88var message2 = "name is $name, type is ${name.runtimeType}"; // name is lqr, type is Stringprint(message1);print(message2);

5、集合类型

Dart 集合类型有:

  • 列表 List:[ele1, ele2, ele2, ele1]
  • 集合 Set:{ele1, ele2, ele3}
  • 映射 Map:{"key1" : value1, "key2" : value2}
// 1. 列表List:[];var names = ["abc", "cba", "nba", "cba"];names.add("mba");// names.remove(value);// names.removeAt(index);// 2. 集合Set:{};var movies = {"星际穿越", "大话西游", "盗梦空间"};names = Set<String>.from(names).toList(); // 可以用Set来去重print(names);// 3. 映射Mapvar info = {  "name": "lqr",  "age": 18,};

二、Dart 的函数使用

1、函数的基本使用

Dart 的函数定义:

返回值 函数的名称(参数列表) {  函数体  return 返回值}

例子:

main(List<String> args) {  print(sum(20, 30));}int sum(int num1, int num2) {  return num1 + num2;}// 返回值的类型可以省略(开发中不推荐)// sum(int num1, int num2) {//   return num1 + num2;// }

2、函数的可选参数

Dart 的函数参数有两类:

  • 必选参数:必须传
  • 可选参数:可不传
main(List<String> args) {  sayHello1("lqr");}// 必选参数:必须传void sayHello1(String name) {  print(name);}
  • 可选参数有 2 种:
    • 位置可选参数:[int age, double height]
    • 命名可选参数:{int age, double height}
main(List<String> args) {  sayHello2("lqr");  sayHello2("lqr", 18, 1.88);  sayHello3("lqr", age: 18, height: 1.88);  sayHello3("lqr", height: 1.88, age: 18);  sayHello3("lqr", height: 1.88);}// 位置可选参数:[int age, double height]// 实参和形参在进行匹配时,是根据位置来匹配void sayHello2(String name, [int age, double height]) {}// 命名可选参数:{int age, double height}// 实参和形参在进行匹配时,是根据变量名来匹配void sayHello3(String name, {int age, double height}) {}

3、函数的默认参数

Dart 中没有函数重载!!!Dart 中没有函数重载!!!Dart 中没有函数重载!!!但是,Dart 函数支持为可选参数设置默认值:

void sayHello1(String name) {  print(name);}// Dart中没有函数的重载// void sayHello1(){ // IDE报错:The name 'sayHello1' is already defined.//   ...// }// 注意:只有可选参数才可以有默认值,必传参数没有默认值void sayHello4(String name, [int age = 20, double height = 1.7]) {}void sayHello5(String name, {int age = 20, double height = 1.7}) {}

注意:只有可选参数才能设置默认值。

4、函数是一等公民

函数是一等公民 意味着可以将函数赋值给一个变量,也可以将函数作为另外一个函数的参数或返回值来使用。

main(List<String> args) {  // 1. 直接找到另外一个定义的函数传进去  test(bar); // bar函数被调用  // 2. 匿名函数 (参数列表){函数体};  test(() {    print("匿名函数被调用"); // 匿名函数被调用    return 10;  });  // 3. 箭头函数:条件,函数体只有一行代码  // 注意:Dart中的箭头函数与js中的箭头函数是两回事  test(() => print("箭头函数被调用")); // 箭头函数被调用}// 函数可以作为另外一个函数的参数void test(Function foo) {  var result = foo();}void bar() {  print("bar函数被调用");}

上述代码中,当将函数作为另一个函数的参数时,使用 Function 来声明这个参数类型,很明显有个问题,那就是无法对传入的函数做限制:

main(List<String> args) {  test((name) {    print(name); // lqr  });}// 封装test函数,要求:传入一个函数// 缺点:Function无法对传入函数做限制(比如:函数的参数列表、返回值)void test(Function foo) {  foo("lqr");}

这时,可以使用 函数签名 来代替 Function:

main(List<String> args) {  test((num1, num2) {    return num1 + num2;  });}// 可以限制 作为参数的函数的类型// 缺点:相当于在形参位置上写了一个函数声明(函数签名),整体看起来可读性差void test(int foo(int num1, int num2)) {  foo(20, 30);}

为了有更好的可读性,可以使用 typedef 来代替 函数签名:

main(List<String> args) {  test((num1, num2) {    return num1 + num2;  });  var demo1 = demo();  print(demo1(20, 30)); // 600}// 可以使用typedef定义一个函数类型typedef Calculate = int Function(int num1, int num2);void test(Calculate calc) {  calc(20, 30);}// 函数作为返回值Calculate demo() {  return (num1, num2) {    return num1 * num2;  };}

三、Dart 的特殊运算符

1、运算符

  • ??=:赋值运算符(变量会 null 时才执行赋值操作)
  • ??:条件运算符(有点像简化版的三元运算符)
// 1. ??=// 当原来的变量有值时,那么 ??= 不执行// 原来的变量为null时,那么将值赋值给这个变量var name = null;name ??= "lilei";print(name); // lilei// 2. ??// ??前面的数据有值,那么就使用??前面的数据// ??前面的数据为null,那么就使用后面的值var name = null;var temp = name ?? "lilei";print(temp); // lilei

2、级联语法(..)

Dart 可以使用级联语法 (..) 将任意对象的 变量赋值 或 方法调用 操作都变成 链式调用:

main(List<String> args) {  // var p = Person();  // p.name = "lqr";  // p.run();  // p.eat();  // 级联运算符  var p = Person()    ..name = "lqr"    ..eat()    ..run();}

3、for 循环的使用

Dart 支持 普通 for 循环,以及 for-in 循环:

// 1. 基础for循环for (var i = 0; i < 10; i++) {  print(i);}// 2. 遍历数组var names = ["why", "cba", "cba"];for (var i = 0; i < names.length; i++) {  print(names[i]);}for (var name in names) {  print(name);}

while 和 do-while 和其他语言一致,break 和 continue 用法也是一致

四、Dart 的面向对象

1、类的定义

Dart 中使用 class 关键字来定义类:

main(List<String> args) {  var p1 = new Person("lqr", 18);  // 从Dart2开始,new关键字可以省略  var p2 = Person("lqr", 18);}class Person {  String name;  int age;  // Person(String name, int age) {  //   this.name = name;  //   this.age = age;  // }  // 等同于上面的代码  Person(this.name, this.age);}

2、类的构造函数

Dart 不支持函数重载,所以,构造函数也一样不能重载。不过,可以使用 命名构造函数 来解决:

main(List<String> args) {  var p = Person("lqr", 18);  var p1 = new Person.withNameAgeHeight("lqr", 18, 1.88);  var p2 = Person.fromMap({    "name": "lqr",    "age": 18,    "height": 1.88,  });}class Person {  String name;  int age;  double height;  Person(this.name, this.age);  // Dart中没有函数重载  // Person(this.name, this.age, this.height); // IDE报错:The default constructor is already defined.  // 命名构造函数  Person.withNameAgeHeight(this.name, this.age, this.height);  Person.fromMap(Map<String, dynamic> map) {    this.name = map['name'];    this.age = map['age'];    this.height = map['height'];  }  @override  String toString() {    return "$name $age $height";  }}

3、类的初始化列表

如果类中的属性使用 final 修饰,那么在必须在构造函数中对 final 属性进行赋值,但要注意的是,不能在构造函数的函数体内对 final 属性赋值:

class Person {  final String name;  final int age;  // Person(this.name, this.age) {} // 必传参数  Person(this.name, {this.age = 10}) {} // (带默认值的)可选参数  // Person(this.name) { // IDE报错:All final variables must be initialized, but 'age' isn't.  //   this.age = 10; // IDE报错:'age' can't be used as a setter because it's final.  // }}

虽然 Dart 不支持在构造函数函数体中对 final 属性赋值,但是可以用 初始化列表 对其赋值:

初始化列表一般与命名可选参数配合使用,当外界调用构造函数时,可以对形参对应的属性进行校验,以及设置初始值。

class Person {  final String name;  final int age;  // function ():xxxxxx {} ,其中xxxxxx部分就是初始化列表  Person(this.name) : this.age = 10 {} // 初始化列表:可能使用 表达式 来为属性赋值  // Person(this.name, {this.age = 10}) {} // (带默认值的)可选参数:只能使用 赋值语句 来为属性赋值  // Person(this.name, {int age}) : this.age = age ?? 10 {} // 初始化列表:可以对形参进行校验,以及设置初始化值  // 初始化列表有多个时,用逗号隔开  // Person(this.name, {int age, double height}) : this.age = age ?? 10, this.height = height ?? 1.88 {}}

4、重定向构造函数

重定向构造函数,其实就是在一个构造函数中,去调用另一个构造函数:

class Person {  String name;  int age;  // 构造函数的重定向  Person(String name) : this._internal(name, 0);  Person._internal(this.name, this.age);}

5、常量构造函数

在某些情况下,我们希望 传入相同的值 能 返回同一个对象,这时,可以使用常量构造函数:

  • 常量构造函数:
    • 如果是将结果赋值给 const 修饰的标识符时,const 可以省略。
    • 在构造函数前加 const 进行修改。
    • 拥有常量构造函数的类中,所有的成员变量必须使用 final 修饰。
    • 为了可以通过常量构造函数创建出相同的对象,不再使用 new 关键字,而是使用 const 关键字。
main(List<String> args) {  // 用 const常量 去接收一个常量构造函数的结果,可以省略 const  // const p1 = const Person("lqr", 18);  const p1 = Person("lqr", 18);  const p2 = Person("lqr", 18);  print(identical(p1, p2)); // true  // 如果用 var变量 去接收一个常量构造函数的结果,则这时省略的不是 const,而是 new!!  var p11 = Person("lqr", 18); // ==> var p11 = new Person("lqr");  var p22 = Person("lqr", 18);  print(identical(p11, p22)); // false  // 必须所有的属性值相同,对象才是同一个  const p111 = Person("lqr", 18);  const p222 = Person("lqr", 20);  print(identical(p111, p222)); // false}class Person {  // 拥有常量构造方法的类中,所有的成员变量必须是final修饰  final String name;  final int age;  // 一个类中只能有一个常量构造方法,命名构造方法也不行  // const Person(this.name);  const Person(this.name, this.age);}

6、工厂构造函数

为了满足 传入相同的值,得到相同的对象 这个需求,除了可以使用常量构造函数外,还可以使用工厂构造函数,而且工厂构造函数更加灵活。

  • 工厂构造函数:在构造函数前加 factory 关键字进行修改。
  • 工厂构造函数与普通的构造函数的区别:
    • 普通的构造函数:会自动返回创建出来的对象,不能手动返回
    • 工厂构造函数最大的特点:可以手动的返回一个对象
main(List<String> args) {  final p1 = Person.withName("lqr");  final p2 = Person.withName("lqr");  print(identical(p1, p2));}class Person {  String name;  String color;  // static 修饰的属性是类属性,可以通过类名直接调用  static final Map<String, Person> _nameCache = {};  Person(this.name, this.color);  // 普通的构造函数:会自动返回创建出来的对象,不能手动返回  // 工厂构造函数最大的特点:可以手动的返回一个对象  factory Person.withName(String name) {    if (_nameCache.containsKey(name)) {      return _nameCache[name];    } else {      final p = Person(name, "default");      _nameCache[name] = p;      return p;    }  }}

7、类的 setter 和 getter

很多时候,我们并不希望外部可以直接访问对象字段,而是经过 setter 和 getter 中转,这样的好处是,可以在 setter 和 getter 中做一些额外的工作,Dart 支持为字段增加 setter 和 getter:

  • setter :前面使用 set 声明,并且需要用 () 接收参数。
  • getter :前面使用 get 声明,还需要声明返回值类型,但是不能有 () 接收参数。
  • setter 和 getter 像字段一样使用。
main(List<String> args) {  final p1 = Person();  // p1.setName("lqr"); // IDE报错:The method 'setName' isn't defined for the type 'Person'.  // setter 和 getter 像字段一样使用  p1.setName = "lqr";  print(p1.getName); // lqr}class Person {  String name;  // setter  // set setName(String name) {  //   this.name = name;  // }  set setName(String name) => this.name = name;  // getter:注意getter没有()  // String get getName {  //   return this.name;  // }  String get getName => this.name;}

严格意义上来说,Dart 中的 setter 和 getter 已经不能算是方法了吧。

8、类的继承

Dart 中的继承使用 extends 关键字,子类中使用 super 来访问父类。

注意:Dart 只支持单继承。

main(List<String> args) {}class Animal {  int age;  Animal(this.age);  void run() {    print("向前跑~");  }}class Person extends Animal {  String name;  // 必须在初始化列表中,调用父类的构造函数  Person(this.name, int age) : super(age);  @override  void run() {    // super.run();    print("向前走~");  }}// Dart只支持单继承// class SuperMan extends Runner, Flyer{ // IDE报错:Each class definition can hava at most one extends clause.// }

9、抽象类的使用

Dart 可以使用 abstract 关键字来定义抽象类:

  • 抽象类中的 抽象方法不需要使用 abstract 关键字修饰
  • 抽象类不能实例化
main(List<String> args) {  final s = Shape(); // IDE报错:Abstract classes can't be instantiated.}// 注意2:抽象类不能实例化abstract class Shape {  // 抽象方法,没有方法体,也不用 abstract 关键字修饰  int getArea();  String getInfo() {    return "形状";  }}// 注意1:继承自抽象类后,必须实现抽象类的抽象方法class Rectangle extends Shape {  @override  int getArea() {    return 100;  }}

注意:Dart 中抽象类不能实例化,但也有特殊情况,有工厂构造函数(factory)的抽象类可以实例化,比如:Map。

10、隐式接口

Dart 使用 implements 关键字来实现多个接口,但奇葩的是,Dart 中没有用来定义接口的关键字,默认情况下所有的类都是隐式接口,即可以 implements 任意类:

注意:要重写被 implements 的类中的所有方法!

main(List<String> args) {}// Dart中没有哪一个关键字是来定义接口的// 没有这些关键字interface/protocol// 默认情况下所有的类都是隐式接口class Runner {  void running() {    print("跑吧");  }}class Flyer {  void flying() {    print("飞吧");  }}// 当将一个类当做接口使用时,那么实现这个接口的类,必须实现这个接口中所有方法(不可以在这些方法里使用super)class SuperMan implements Runner, Flyer {  @override  void flying() {    // super.flying(); // IDE报错:The method 'flying' is always abstract in the supertype.  }  @override  void running() {}}

建议:用 abstract class 来声明接口,反正被 implements 的类的所有方法都要重写。

11、mixin 混入的使用

当遇到要复用 2 个类中方法的时候,要怎么办?

  • 如果使用接口方式则需要重新实现 2 个类中各自的方法,这样根本就不能算是复用;
  • 而 Dart 又只能单继承,最多只能复用 1 个类中方法;

Dart 提供了另一个方案:Mixin 混入。可以定义 2 个 mixin 类,通过 with 关键字把这 2 个 mixin 类混入到一个类 A 中,这时类 A 就拥有了它们的方法,从而达到复用 2 个类中方法的目的:

main(List<String> args) {  final superMan = SuperMan();  superMan.eating(); // 动作吃东西  superMan.running(); // running(调用的是混入类Runner中的running())  superMan.flying(); // flying}class Animal {  void eating() {    print("动作吃东西");  }  void running() {    print("动物running");  }}mixin Runner {  void running() {    print("running");  }}mixin Flyer {  void flying() {    print("flying");  }}// Runner中的running()与Animal中的running()冲突,这时会发生覆盖,即SuperMan中的running()调用的是Runner的running()class SuperMan extends Animal with Runner, Flyer {}

12、类属性和类方法

类中使用 static 关键字修饰的属性(或方法)就是类属性(或类方法),类属性和类方法可以通过类名直接调用:

main(List<String> args) {  Person.courseTime = "8:00";  print(Person.courseTime);  Person.gotoCourse();}class Person {  // 成员变量  String name;  // 静态属性(类属性)  static String courseTime;  // 对象方法  void eating() {    print("eating");  }  // 静态方法(类方法)  static void gotoCourse() {    print("去上课");  }}

13、枚举的使用

枚举使用 enum 关键字来定义,枚举有 2 个常见的属性:

  • index:用于表示每个枚举的索引,从 0 开始
  • values:包含每个枚举值的集合
// 枚举:enummain(List<String> args) {  final color = Colors.red;  switch (color) {    case Colors.red:      print("红色");      break;    case Colors.blue:      print("蓝色");      break;    case Colors.green:      print("绿色");      break;  }  print(Colors.values); // [Colors.red, Colors.blue, Colors.green]  print(Colors.red.index); // 0}enum Colors {  red,  blue,  green,}

14、泛型的使用

集合类型使用泛型:

// List使用时的泛型写法:var names1 = ["abc", "cba", "nba", 111]; // List<Object>var names2 = ["abc", "cba", "nba"]; // List<String>// var names3 = <String>["abc", "cba", "nba", 111]; // IDE报错:The element type 'int' can't be assigned to the list type 'String'.var names3 = <String>["abc", "cba", "nba"]; // List<String>List<String> names4 = ["abc", "cba", "nba"]; // List<String>// Map使用时的泛型写法:var info1 = {"name": "lqr", "age": 18}; // _InternalLinkedHashMap<String, Object>var info2 = <String, String>{'name': 'lqr', 'age': '18'}; // _InternalLinkedHashMap<String, String>Map<String, String> info3 = {'name': 'lqr', 'age': '18'}; // _InternalLinkedHashMap<String, String>

类定义使用泛型:

main(List<String> args) {  var point = Point<double>(1.23333, 1.9527); // Point<double>  final pointX = point.setAndGetX(5.55);  print(pointX); // 5.55}// class Point<T> // 泛型T是Object的子类class Point<T extends num> { // 泛型T是数字类型  T x;  T y;  Point(this.x, this.y);  T setAndGetX(T x) {    this.x = x;    return this.x;  }}

T extends num 用于限制泛型的类型必须是数字,不限制的话则默认是 Object。

五、Dart 中库的使用

  • Dart 中你可以导入一个库来使用它所提供的功能。
  • Dart 中任何一个 dart 文件都是一个库,即使你没有用关键字 library 声明。

库导入的语法:

import '库所在的 uri';

1、使用系统的库

系统库的 uri 一般以 dart: 开头,常见的系统库有:dart:io、dart:async、dart:math 等:

// import 'dart:io';// import 'dart:isolate';// import 'dart:async';// import 'dart:math';// 系统的库:import 'dart:库的名字';import 'dart:math';main(List<String> args) {  final num1 = 20;  final num2 = 30;  print(min(num1, num2));}

注意:dart:core 也是很常用的系统库,不过可以省略不写。

2、使用自定义库

自定义库就是自己项目中定义的其他 dart 文件,比如在 utils 目录下创建一个 math_utils.dart 文件,内容如下:

// utils 目录下的 math_utils.dart 文件内容:int sum(int num1, int num2) {  return num1 + num2;}int mul(int num1, int num2) {  return num1 * num2;}

这里的 math_utils.dart 文件就是一个自定义库,你可以在其他 dart 文件中导入:

// 导入自定义库可以是相对路径或绝对路径。import 'utils/math_utils.dart';main(List<String> args) {  print(sum(20, 30));

3、使用第三方库

那些存放在远程仓库上的库文件就是第三方库,如果项目中需要依赖第三方库,就需要在项目目录下创建一个 pubspec.yaml 文件,其中 dependencies 部分填写要依赖的第三方库的描述(库名: 版本):

name: myProjectNamedescription: my project namedependencies:  http: ^0.12.0+4environment:  sdk: ">=2.10.0 <3.0.0"

可以在 pub.dev 上查找第三方库的使用说明。

填写好依赖后,需要执行 dart pub get 或 flutter pub get 命令,让终端从服务器上拉取第三方对应的版本文件,之后就可以在代码中使用这个第三方库了:

import 'package:http/http.dart' as http;main(List<String> args) async {  var url = 'https://example.com/whatsit/create';  var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'});  print('Response status: ${response.statusCode}');  print('Response body: ${response.body}');  print(await http.read('https://example.com/foobar.txt'));}

4、库方法名冲突

比如:math_utils.dart 库中有 int sum(int num1, int num2) 方法,当前 dart 文件中又定义了 void sum(int num1, int num2) 方法,这 2 个方法名相同,但返回值不同:

import 'utils/math_utils.dart';main(List<String> args) {  print(sum(20, 30)); // IDE报错:This experssion has a type of 'void' so its value can't be used.}// void sum(num1, num2) // 参数类型可以不写,默认是 dynamicvoid sum(int num1, int num2) {  print(num1 + num2);}

这时,当前 dart 文件识别到的是自己的 sum()方法,而实际上我们想要使用的是 math_utils.dart 库中 sum()方法,这就发生了库方法名冲突,可以使用 as 关键字给库起别名来解决:

import 'utils/math_utils.dart' as mUtils;main(List<String> args) {  print(mUtils.sum(20, 30));}void sum(sum1, sum2) {  print(sum1 + sum2);}

5、库内容的显示和隐藏

默认 import 进来的库中所有内容都是可见的,但是很多时候我们希望只导入库中的某些内容,或者希望隐藏库中的某些内容,这时可以使用 show 和 hide 关键字:

  • show:显示某个成员( 屏蔽其他)
// import "utils/math_utils.dart" show sum, mul;import "utils/math_utils.dart" show sum;main(List<String> args) {  print(sum(20, 30));  print(mul(20, 30)); // IDE报错:The function 'mul' isn't defined.}
  • hide:隐藏某个成员(显示其他)
// import "utils/math_utils.dart" hide sum, mul;import "utils/math_utils.dart" hide sum;main(List<String> args) {  print(sum(20, 30)); // IDE报错:The function 'sum' isn't defined.  print(mul(20, 30));}

6、批量导入库文件

有些时候,可能需要在一个 dart 文件中,导入 n 个库,这还是能接受的:

import 'utils/math_utils.dart';import 'utils/date_utils.dart';import ...main(List<String> args) {  print(sum(20, 30));  print(dateFormat());}

但如果是 n 个 dart 文件都要导入相同的 m 个库呢?很明显,需要有个统一批量导入库文件的方案:可以创建一个新的 dart 文件,使用 export 关键字统一导库。

// utils 目录下的 utils.dart 文件内容:export 'math_utils.dart';export 'date_utils.dart';export ...

这时,其他 dart 文件中,只需要导入这个 utils.dart 就好了:

import 'utils/utils.dart';main(List<String> args) {  print(sum(20, 30));  print(dateFormat());  // math_utils.dart中的_min()无法被外界调用  // print(_min(20, 30)); // IDE报错:The function '_min' isn't defined.}

注意:Dart 语言中没有可见性修饰符,即没有 public、protected、private。如果某个成员属性或方法不想被外界(其他 dart 文件)调用,可以在名字前加个下划线 _。