Flutter-Dart入门,常用细节

文章目录

  • 背景
  • 入门
  • 变量
    • var
    • dynamic 和 Object
    • 数据类型
      • 集合
      • 空安全null safety
      • late延时/惰性初始化
      • 类型推导var、dynamic、object
      • Final 和 Const关键词
      • Assert(断言)
        • 补充
  • 函数
    • 函数声明
    • 参数
    • 默认构造函数
    • 有参构造函数和命名式构造函数
    • 私有属性、私有方法
    • 继承
    • Mixin
  • 异步支持
    • Future
    • Future 的常用函数
      • Future.wait
    • Async/Await
  • Stream
    • 单订阅流
    • 广播流
      • 使用 StreamController 生成 Stream
  • Dart的不足
    • 与Java对比
    • 与js对比
  • 总结

背景

Dart 诞生于 2011 年,但是在 2017 之前并不是很受欢迎。 在 2017 年 Google 宣布了跨平台移动应用开发的 Flutter beta 版之后,Dart 的受欢迎程度一直在上升。
它被用于web、服务器、移动应用和物联网等领域的开发。

  • 扩展Dart Vs JavaScript

入门

  • 中文官方文档
  • 英文官方文档,比较全

变量

var

Dart 本身是一个强类型语言,任何变量都是有确定类型的,在 Dart 中,当用var声明一个变量后,Dart 在编译时会根据第一次赋值数据的类型来推断其类型,编译结束后其类型就已经被确定。

var t = "flutter";
t=10;/// 报错

dynamic 和 Object

Object 是 Dart 所有对象的根基类,dynamic与Object声明的变量都可以赋值任意对象,且后期可以改变赋值的类型,这和 var 是不同的。
具体不同,后面会介绍。

dynamic a = "123";
Object b = "123";

数据类型

dart2.12加入空安全。
在dart中的一切皆是对象,包括数字、布尔值、函数等,它们和Java一样都继承于Object,所以它们的默认值也就是null(dart2.12之前默认值是null,2.12之后必须赋初值)。在dart主要有: 布尔类型bool、数字类型num(数字类型又分为int,double,并且两者父类都是num)、字符串类型String、集合类型(List, Set, Map)。

结构:【类型】【变量名】= 【赋值】

/// 变量-数据类型
void variable() {
  var t = "hi world";
  bool isBool = false;
  String name = "flutter";
  int age = 1;
  double height = -12.1;

  /// 扩展,直接使用数学函数
  // print("height ${height.abs()}");

  //打印类型
  print(t.runtimeType);
  print(isBool.runtimeType);
  print(name.runtimeType);
  print(age.runtimeType);
  print(height.runtimeType);
}

运行结果:

String
bool
String
int
double

细节一:num类型的变量,自带了数学函数Math功能,不像其他语言

double height = -12.1;
print("height ${height.abs()}");
/// 输出12.1

集合

new 关键词是可选的,创建对象,不需要显示声明关键字new。

/// 集合操作
void collection() {
  List<String> colorList = ['red', 'yellow', 'blue', 'green'];
  /// 语法提示,不需要使用new关键字
  // List colorList2 = new List.empty();

  /// forEach箭头函数遍历
  colorList.forEach((String color) => print(color));

  /// map函数的使用
  print(colorList.map((color) => '1+$color').join(","));

  /// 直接使用{}形式初始化
  Set<String> colorSet = {'red', 'yellow', 'blue', 'green'};
  /// for-in遍历
  for (var color in colorSet) {
    print(color);
  }

  /// map初始化
  Map<String, int> colorMap = {'white': 0xffffffff, 'black': 0xff000000};
  print(colorMap.containsKey('green')); //false
  print(colorMap.containsValue(0xff000000)); //true

}
  • 打印set和map 显示的是_InternalLinkedHashMap,网上有对这个说明。
  • foreach-vs-for-in关于dart的lint不推荐使用foreach,这里有个简单的例子做说明

空安全null safety

从dart 2.12开始,支持空安全。就是在类型后面加个?

  • 官方文档健全的空安全
  1. 确保正确性
  2. 在编译时就能确定数据类型,避免为null的情况
/// 空安全
void nullSafety(){
  /// 默认是null
  String? nullStr;
  int? nullInt;
  bool? nullBool;
  Map<String, int>? nullMap;

  print("init nullStr = $nullStr");

  /// 报错,Error: Non-nullable variable 'str' must be assigned before it can be used.
  String str;
  // str = "init";
  print("is $str");

}

如果使用String str 申明变量,但又不初始化,在使用的时候就会报错误。

late延时/惰性初始化

正式使用它之前必须得保证初始化过了,否则会报错

  1. 可能不需要该变量,并且初始化它的成本很高。
  2. 这个变量可能从网络获取。
void main() {
  var awesome = Awesome();
  print('awesome: ${awesome.isAwesome}');

  lateVoid();
}

/// 可以先不初始化
late String description;
void lateVoid() {
  description = 'Flutter!';
  print(description);
}

类型推导var、dynamic、object

前面介绍了dart中常用的变量,还可以使用var、dynamic、object用于定义变量,通过这种方式定义变量不需要指定变量类型。

Flutter-Dart入门,常用细节_第1张图片
补充:从图上可以知道object是类型的基类。

使用var、dynamic、object,可以不用指明变量类型。他们区别是什么:
var,编译时会检测类型,如果类型推断出是string类型,再赋值int,会报错。
dynamic,类型是可变的,先复制string再复制int是可以的,编译时不会揣测数据类型,但是运行时会推断。
Object,类型是可变的,先复制string再复制int是可以的,编译时会检测类型。

void varAndDynamic(){

  /// var定义的类型是不可变的,类型推断出是string类型,再赋值int,会报错
  //var
  var str = "flutter";
  print(str.runtimeType); //String
  print(str); //hello world
  // str = 1;

  //dynamic
  /// 编译时不会揣测数据类型,但是运行时会推断
  dynamic mic = "flutter";//编译时不会揣测数据类型,但是运行时会推断
  print(mic.runtimeType);//String
  print(mic);//hello world
  /// 但是这样的坏处就是会让dart的语法检查失效,所以有可能会造成混乱而不报错,所以不要直接使用dynamic
  mic.foo();
  /// 通过它定义的变量会关闭类型检查,这段代码静态类型检查不会报错,但是运行时会crash,因为mic并没有foo()方法,
  /// 所以建议大家在编程时不要直接使用dynamic
  mic=1;
  print(mic.runtimeType);//int 说明类型是可变的
  print(mic);//1

  //Object
  Object object = "flutter";
  print(object.runtimeType);//String
  print(object);//hello world
  object=1;
  print(object.runtimeType);//int 说明类型是可变的
  print(object);//1
  //object.foo();静态类型检查会运行报错会报错
  object.foo();

}

Final 和 Const关键词

如果你不想更改一个变量,可以使用关键字 final 或者 const 修饰变量。一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。

final:其值在初始化后不可改变;
const:只能被设一次值,在声明处赋值,且值必须为编译时常量;用于修饰常量。

/// final和const
void finalAndConst(){

  /// 可以用做静态常量
  const int age = 20;

  /// final不能改变
  final String name = 'Flutter';
  /// 以下不能改变
  // name = "zzb";
  print("name $name");

  final int count = 2 * 2;

  /// final只用来修饰变量,但无法限制变量的实例内部发生改变
  final baz = [1];
  // baz=[1,2,3,4]; //出错 此调用修改了变量的实例 即:[1] 和[1,2,3,4]是不同的对象
  baz[0] = 2; //正常执行,只修改了变量引用对象的成员变量的值
  print(baz);


  /// 对比不同点,打印时间
  final String finalTimeStamp = DateTime.now().toString();
  const String constTimeStamp = DateTime.now().toString();//出错,编译时就要确认内容
  
  print("finalTimeStamp $finalTimeStamp");
}

Assert(断言)

在dart中如果条件表达式结果不满足条件(表达式为false),则可以使用 assert 语句中断代码的执行。特别是在Flutter源码中随处可见都是assert断言的使用。注意: 断言只在检查模式(debug调试)下运行有效,如果在生产模式运行,则断言不会执行。

assert(text != null);//text为null,就会中断后续代码执行
assert(urlString.startsWith('https'));
补充

官方不推荐使用forEach方法进行,建议使用for…in替换。

void collection() {
  List<String> colorList = ['red', 'yellow', 'blue', 'green'];

  /// forEach箭头函数遍历
  colorList.forEach((String color) => print(color));
 }

lint语法检测:给出的提示
foreach与fon-in方法pk forEach方法在回调方法出现的弊端。

函数

Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。

函数声明

返回类型,函数名,入参

bool isNull(int? num) {
  return num == null;
}

如果函数体内只包含一个表达式,你可以使用简写语法:

bool isNull2 (int? num) => num == null;

Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断,不建议使用

isNull3(int? num) {
  return num == null;
}

参数

有两种形式的参数:必要参数 和 可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。可选参数可以是 命名的位置的

/// 必选参数
void upload(String filePath, String fileName, int? fileSize) {
}

/// 命名位置参数
void upload2(String filePath, String fileName, {int? fileSize}) {
}

/// 可选位置参数
void upload3(String filePath, String fileName, [int? fileSize]) {
}

/// 测试以上方法
/// 测试以上方法
void testParam(){
  /// 必选参数
  upload("/user", "name", 0);
  /// 命名位置参数,两种用法
  upload2("/user", "name");
  upload2("/user", "name", fileSize: 10);

  /// 可选位置参数
  upload3("/user", "name");
  upload3("/user", "name", 10);
  

}

可选命名参数在Flutter中使用非常多。注意,不能同时使用可选的位置参数和可选的命名参数。
命名位置参数,最大的好处是能知道,传递参数的含义,再配上关键词required,required表示这个参数是必须的。

/// required使用
void upload4({required String filePath, required String fileName, int? fileSize}) {
}
/// required使用
  upload4(filePath: "/user", fileName: "name");
  upload4(filePath: "/user", fileName: "name", fileSize: 10);

对象的 成员 由函数和数据(即 方法 和 实例变量)组成。

默认构造函数

如果你没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。

/// 默认构造函数
class Point{
  int x = 0;
  int y = 0;
}
Point point = Point();

有参构造函数和命名式构造函数

需要注意的是:使用了有参构造函数和命名式构造函数,那默认的构造函数就无效了。无法直接使用
Point point = Point();

构造函数:声明一个与类名一样的函数即可声明一个构造函数。
命名式构造函数:可以为一个类声明多个命名式构造函数来表达更明确的意图:

class Point {
  int x = 0;
  int y = 0;
  int? z = 0;

  /// constructor,构造函数,有参数构造函数
  Point(int x, int y) {
    this.x = x;
    this.y = y;
  }

  /// 简化,构造函数
  // Point(this.x, this.y);

  /// 命名式构造函数,明确知道意图
  Point.origin() {
    x = 5;
    y = 5;
  }

  /// 常用命名式构造函数,把json的数据转成对象
  Point.fromJson(Map<String, dynamic> json) {
    x = json['x'];
    y = json['y'];
  }

  void printPoint() {
    print("x = $x y = $y");
  }

  /// 距离
  int distanceTo() {
    return 0;
  }
}

/// 测试构造函数
void testPoint() {
  /// 无法直接使用
  // Point point = Point();

  /// 构造函数
  Point point = Point(2, 2);
  point.printPoint();

  /// 命名式构造函数
  Point point2 = Point.origin();
  point2.printPoint();

  /// 命名式构造函数
  Point point3 = Point.fromJson({'x': 1, 'y': 2});
  point3.printPoint();

  /// 可以为空的对象,调用方法前可以使用运算符?. 来判断是否有该方法
  /// 可以避免因为左边表达式为 null 而导致的问题
  Point? point4;
  point4?.printPoint();
}

私有属性、私有方法

  1. Dart和其他面向对象语言不一样,Data中没有 public private protected这些访问修饰符合,但是我们可以使用_把一个属性或者方法定义成私有。

  2. 私有方法必须要抽离在单独文件中,否则不生效。

class PrivateClass{
  /// 私有变量
  int _i = 1;
  int j = 2;

  PrivateClass();

  void printContent() {
    print("打印变量_i = $_i j = $j");
  }

  /// 私有方法,
  void _printContent2(){
    print("打印变量_i = $_i j = $j");
  }
}

PrivateClass privateClass = PrivateClass();
  privateClass.printContent();

  /// 修改公共属性
  privateClass.j = 3;
  privateClass.printContent();

  /// 无法修改私有属性,报错The setter '_i' isn't defined for the type 'PrivateClass'.
  privateClass._i = 3;

  /// 无法使用私有方法,The method '_printContent2' isn't defined for the type 'PrivateClass'.
  privateClass._printContent2();

继承

如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数。
1.子类使用extends关键词来继承父类

2.子类会继承父类里面可见的属性和方法,但是不会继承构造函数

3.子类能复写父类的方法 getter和setter

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }

  void eat() {
    print("eat");
  }

  void play(){
    print("play");
  }
}

class Teacher extends Person {
  Teacher.fromJson(Map data) : super.fromJson(data) {
    print('in Teacher');
  }

  /// 重写类成员
  @override
  void eat() {
    /// 调用父类方法
    super.play();
    print("eat fish");
  }
}

/// 测试继承
void testExtends() {
  Teacher teacher = Teacher.fromJson({});
  teacher.eat();
}

输出

in Person
in Teacher
play
eat fish

Mixin

Dart 是不支持多继承的,Mixin 是一种在多重继承中复用某个类中代码的方法模式,“组合”类可以访问mixin类的方法、变量而不必成为其子类。使用 with 关键字来使用 Mixin 模式。

定义一个 Person 类,实现吃饭、说话、走路和写代码功能,同时定义一个 Dog 类,实现吃饭、和走路功能:

/// 定义一个 Person 类,实现吃饭、说话、走路和写代码功能,同时定义一个 Dog 类,实现吃饭、和走路功
class Person {
  say() {
    print('say');
  }
}

mixin Eat {
  eat() {
    print('eat');
  }
}

mixin Walk {
  walk() {
    print('walk');
  }
}

mixin Code {
  code() {
    print('code');
  }
}

class Dog with Eat, Walk{}
class Man extends Person with Eat, Walk, Code{}

/// 测试mixin
void testMixin(){
  Man man = Man();
  man.say();//say
  man.code(); //code
  man.eat();//eat

  Dog dog = Dog();
  dog.eat(); //eat
}

打印结果

say
code
eat
eat

mixins是普通的类,我们可以从中扩展方法(或变量)而不扩展类。
Dart之Mixin详解,Flutter中大量使用了这种方式。

异步支持

Dart类库有非常多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。

async和await关键词支持了异步编程,允许您写出和同步代码很像的异步代码。

Future

Future与JavaScript中的Promise非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。

/// 测试future
void testFuture() {
  print("start future");
  Future.delayed(Duration(seconds: 2), () {
    throw ArgumentError("Error");
    // return "hi world!";
  }).then((data) {
    print(data);
  }).catchError((e){
    //执行失败会走到这里
    print("失败了 $e");
  });
  print("end future");
}

执行结果:

start future
end future
失败了 Invalid argument(s): Error

就可以说明Future是一个异步操作。

  • 官方文档介绍

Future 的常用函数

  1. Future.then()
    任务执行完成会进入这里,能够获得返回的执行结果。
  2. Future.catchError()
    有任务执行失败,可以在这里捕获异常。
  3. Future.whenComplete()
    当任务停止时,不断成功或失败,最后会执行这里。
  4. Future.wait()
    可以等待多个异步任务执行完成后,再调用 then()。
    只有有一个执行失败,就会进入 catchError()。
  5. Future.delayed()
    延迟执行一个延时任务。
  6. Future.sync()
    同步执行
  7. Future.microtask()
    创建一个在微任务队列里运行的Future

Future.wait

比如我们有一个界面,需要先分别从两个网络接口获取数据,获取成功后,我们需要将两个接口数据进行特定的处理后再显示到UI界面上。它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。

void testFutureWait(){

  Future<String> demo1() {
    return Future.value("1");
  }
  Future<String> demo2(){
    return Future.value("2");
  }
  Future<dynamic> demo3(){
    return Future.delayed(Duration(seconds: 4), () {
      // throw ArgumentError("Error");
      return "hi world!";
    });
  }

  /// 返回错误
  Future<String> demo4(){
    return Future.error("error");
  }

  /// 内部执行错误,被捕获了,wait是无法检测到
  Future<void> demo5(){
    return Future.delayed(Duration(seconds: 4), () {
      throw ArgumentError("Error");
      // return "hi world!";
    }).catchError((e){
      //执行失败会走到这里
      print("内部捕获 $e");
    });
    // return Future.error("error");
  }

  /// 正常流程
  List<dynamic> list1 = [demo1(), demo2(), demo3()];
  /// 包含一个错误
  List<dynamic> list2 = [demo1(), demo2(), demo3(), demo4()];

  Future.wait<dynamic>([...list2]).then((value) => print(value)).catchError((e){
    //执行失败会走到这里
    print("失败了 $e");
  });
}

Async/Await

使用 async 和 await 的代码是异步的,但是看起来有点像同步代码。async能把普通函数变成异步函数。
Dart中的async/await 和JavaScript中的async/await功能和用法是几乎一样。

/// 定义Future函数
Future<String> checkVersion() async{
  return "1.2";
}
//先分别定义各个异步任务
Future<String> login(String userName, String pwd){
	...
    //用户登录
};
Future<String> getUserInfo(String id){
	...
    //获取用户信息 
};
Future saveUserInfo(String userInfo){
	...
	// 保存用户信息 
}; 

接下来,执行整个任务流:

login("alice","******").then((id){
 //登录成功后通过,id获取用户信息    
 getUserInfo(id).then((userInfo){
    //获取用户信息后保存 
    saveUserInfo(userInfo).then((){
       //保存用户信息,接下来执行其它操作
        ...
    });
  });
})

如果业务逻辑中有大量异步依赖的情况,过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,也被叫做“回调地狱”。

使用async/await可以解决以上问题。

task() async {
   try{
    String id = await login("alice","******");
    String userInfo = await getUserInfo(id);
    await saveUserInfo(userInfo);
    //执行接下来的操作   
   } catch(e){
    //错误处理   
    print(e);   
   }  
}

  • async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用 then 方法添加回调函数。
  • await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。

Stream

官方文档-streams
Stream 是一系列异步事件的序列。上面的Future,是返回一个异步任务,那Stream就是可以串行返回多个异步任务。
Stream有两种类型,一种是点对点的单订阅流(Single-subscription),另一种则是广播流(Broadcast)。
主要作用是监听事件流,常作为事件总线使用。

单订阅流

单订阅流的特点是只允许存在一个监听器,即使该监听器被取消后,也不允许再次注册监听器。

广播流

这种流可以在同一时间设置多个不同的监听器同时监听,同时你也可以在取消上一个订阅后再次对其发起监听。

使用 StreamController 生成 Stream

创建Stream的方式有很多,跟Future类似

  1. 可以通过Stream提供的构造函数。
  2. Future可以使用async创建,那Stream就使用 async* 函数创建。
  3. 使用 StreamController 生成 Stream。Stream的一个帮助类,可用于整个 Stream 过程的控制。

这里介绍StreamController来创建Stream,这里就以广播流的形式来介绍一下。

void testStreamController(){
  /// 创建广播流
  /// onListen,有监听事件就回调
  /// onCancel,关闭stream事件流就回调
  StreamController sc = StreamController.broadcast(
      onListen: ()=>print("onListen"),
      onCancel: ()=>print("onCancel"),
  );

  /// 监听事件
  sc.stream.listen((String event){
    print("第一个监听 $event");
  });

  sc.stream.listen((String event){
    print("第二个监听 $event");
  });
  
  /// 放入事件
  sc.add('event1');
  // sc.add("event2");

  /// 关闭
  sc.close();
}

运行结果:

onListen
第一个监听 event1
第二个监听 event1
onCancel

Flutter中的EventBus的原理就是根据Stream实现。

Dart的不足

  1. Dart 不支持 protected,所以要么是 public,要么是 private,不能折中。
  2. 枚举不支持自定义值。只能从0开始。
  3. 场景应用少,还有待完善生态。

与Java对比

  1. 都是强类型语言。
  2. Java有public private protected这些访问修饰符,Dart的Class中属性默认public,若声明私有,只需在属性名前加_。
  3. Java用接口间接实现多继承,Dart提供更灵活的方式Mixin。
  4. 比java更适合函数式编程。

与js对比

  1. 都可以用在跨端实现上。
  2. js没用私有属性的概念;所有的属性都是公用的,Dart起码还可以使用_(下划线)表示私有。
  3. js弱语言,没有强制类型。仅在运行时发现错误。dart强类型。
  4. JavaScript具有庞大的社区和在线提供的出色框架。dart目前要考Flutter推广。

总结

主要介绍了dart的变量、函数、类、异步编程。今天课程主要是为打下个基础,学会这些内容基本就能满足基本的Flutter开发,学习以上内容后,推荐再去阅读官方的Dart语言规范和用法示例,能更好的配合团队使用Dart来开发项目。如下

  • 高效 Dart 语言指南
  • DART 总结(对比JAVA)

你可能感兴趣的:(Flutter,flutter)