美文网首页Flutter
[Flutter]flutter基础之Dart语言基础(四)

[Flutter]flutter基础之Dart语言基础(四)

作者: 陌问MW | 来源:发表于2020-02-25 17:01 被阅读0次

    一、运算符重载

    Dart 支持运算符重载,使用 operator 关键字,语法格式为:operator 运算符 ,如下,重载加法运算符:

    main(){
      Vector v1 = Vector(1, 2);
      Vector v2 = Vector(2, 3);
      Vector v3 = v1    + v2;
      print("${v3.x} ${v3.y}");   //输出 3 5
    }
    
    class Vector {
      int x, y;
      Vector(this.x, this.y);
    
      Vector operator +(Vector v) {
        return Vector(x+v.x, y+v.y);
      }
    }
    

    一、模块的使用

    有点规模的工程一般都会有很多个文件和模块组成,甚至简单的程序也不会是单个文件组成的。在 Dart 中,每一个 .dart 文件就是一个模块,称为库。可以将不同的功能分装在不同的库中,以便在不同的文件中使用同一库的功能函数,减少代码量,可以使用 import 关键字进行文件导入。

    在 Dart 中,库(模块)类型分为三种:自定义库、系统库(Dart 提供的库文件)、第三方库。

    1. 引入自定义库

    引入自定义库(自己创建的 .dart 文件)的方式如下,如在当前文件夹新建一个 common 文件夹为公共类文件夹,在文件夹中新建 common.dart 文件,内容如下:

    void description(){
      print("描述内容");
    }
    

    main 方法所在文件引入 common.dart 文件并使用方式如下:

    import 'common/common.dart';
    
    main(){
      description();
    }
    

    通过 import 导入文件后,即可使用文件中的方法等内容。当然也可以在自定义库内新建其他 class 等。引入的格式为 import '路径/文件名.dart'

    2. 引入系统库

    系统库是由 Dart 事先提供的库,包含了一些文件或其他内容的处理方法,如 iomath 等文件,引入方式如下:

    import 'dart:math';
    
    main(){
      num maxNumber = max(10, 20);
      print(maxNumber);
    }
    

    max 为 系统 math 库提供的比较两个数字并返回较大数字的函数,引入库后可直接使用。引入格式为:import 'dart:文件名'

    3. 引入第三方库

    第三方库为其他开发者或公司等写好的具有一定功能的函数库,可以在 Dart 提供的官方平台寻找需要的库,地址为:https://pub.dev 。比如我们前面使用过的 meta.dart 文件,可以在网站中直接搜 meta 即可。结果如下图:

    2020224_12_15.png

    点击 Installing ,进入安装说明页,如下图:

    2020224_12_15.jpg

    本页列出了包的安装方式,可以通过命令行和配置文件方式引入,这里介绍下配置文件引入的方法。对于 Flutter 项目,创建工程后在文件的顶层目录会存在一个名为 pubspec.yaml 的配置文件。在没有此文件的工程中可以手动在顶级目录添加此文件,里面包含一些包的依赖和其他信息,具体可以参考:https://dart.dev/tools/pub/pubspec 。只需将按照上图的步骤1,将包名和对应的版本号添加到 dependencies 即可。编译器会自动下载所需依赖包,开发者只需在工程文件中,通过 package 方式引入即可,如没有自动下载依赖,可以查看官方的具体信息,进入项目所在文件目录,使用 pub get 命令下载即可。 使用如下方式导入:

    import 'package:meta/meta.dart';
    
    4. 私有属性

    对于私有属性,前面文章简单介绍过。在 Dart 中,没有关键字 publicprotectedprivate 。如果标识符以下划线(_)开头,则表示该标识符私有属性,只能在定义标识符的库中使用(定义标识符的当前.dart 文件)。

    5. 指定库前缀

    当引入的不同库中存在相同的类名、方法名或其他相同名称时,有时会出现冲突的问题,在 Dart 中,通过指定一个或多个库前缀的方式解决,使用 as 关键字。如 common.dartcommon1.dart 中同时存在名为 description 的方法,内容分别如下:

    //common.dart
    void description(){
      print("common的描述内容");
    }
    
    //common1.dart
    void description(){
      print("common1的描述内容");
    }
    

    main 方法所在文件进行引入:

    import 'common/common.dart';
    import 'common/common1.dart';
    
    main(){
      // description();  此时调用会抛出两个库文件存在同一方法的异常
    }
    

    此时,需要至少指定其中一个库的前缀或部分引入的方式解决问题,使用前缀如下:

    import 'common/common.dart';
    import 'common/common1.dart' as com1;
    
    main(){
      description();  
      com1.description();
    }
    

    这里为 common1.dart 指定了 com1 前缀,调用方法或其他内容时,需要加上前缀进行调用即可。类似于其他语言的命名空间概念。

    6. 部分引入库

    当一个库中包含很多解决问题的方法时,引入库但只会用到其中很少一部分内容或不想用某些方法内容,可以使用 showhide 关键字进行指定。在 common.dart 文件中添加方法 printInfocommon.dart 内容如下:

    void description(){
      print("common的描述内容");
    }
    
    void printInfo() {
      print("打印指定信息");
    }
    

    使用 show 关键字指定只导入 printInfo 方法如下:

    import 'common/common.dart' show printInfo;
    
    main(){
      // description();    //调用description会抛出异常
      printInfo();    //输出 打印指定信息
    }
    

    使用 hide 关键字指定不导入部分如下:

    import 'common/common.dart' hide printInfo;
    
    main(){
      description();   
      // printInfo();  //调用会报未定义错误 
    }
    

    show 多个可用逗号做分隔。另外,Dart 中还支持惰性加载库,使用关键字 deferred ,但是 Flutter 并不支持延迟,所以不多做介绍,有兴趣的朋友可以自行了解。

    二、泛型

    第一篇文章介绍过泛型的基本使用,使用泛型是为了解决类型安全问题以及重复代码的问题。泛型,也就是通用类型,在前面的文章中使用过泛型。例如,当创建一个只能包含字符串的列表(List)时,如不通过泛型执行数据类型,则创建的列表为动态类型,如下:

    main(){
      List lst = List();
      print(lst.runtimeType);  //输出 List<dynamic>
    }
    

    如果列表是为了特定的问题而创建的只能包含字符串的类型(可能需要对列表内的数据进行特定的字符串处理),此时如果数组内添加了非字符串类型数据,调用字符串处理方法就会出现异常,所以需要使用泛型对添加的数据类型做限定,如下:

    main(){
      List lst = List<String>();
      print(lst.runtimeType);   //输出 List<String>
    }
    

    此时如果列表添加其他类型数据就会抛出异常,如法进行添加。

    也可以使用泛型来减少重复代码,如下:

    main(){
      var student1 = StudentInt(1);
      var student2 = StudentString("hike");
    
      student1.study();             //输出 1 号学生在学习
      student2.study();             //输出 hike 在学习
    }
    
    class StudentInt {
      int stuId;
      StudentInt(this.stuId);
    
      void study(){
        print("$stuId 号学生在学习");
      }
    }
    
    class StudentString {
      String name;
      StudentString(this.name);
    
      void study(){
        print("$name 在学习");
      }
    }
    

    上述代码,分别为通过学号和学生姓名来创建学生,创建了两个类,通过泛型修改如下:

    main(){
      var student1 = Student(1);
      var student2 = Student("hike");
    
      student1.study();             //输出 1 号学生在学习
      student2.study();             //输出 hike 在学习
    }
    
    class Student<T> {
      T stuIdentifier;
      Student(this.stuIdentifier);
    
      void study(){
        if(stuIdentifier is num) {
          print("$stuIdentifier 号学生在学习");
        }else if(stuIdentifier is String) {
          print("$stuIdentifier 在学习");
        }
      }
    }
    

    通过泛型 <T> 可以有效减少代码的重复量,通过传入不同的参数类型区分不同的执行结果。其中 T 为标识符,为类型占位,类型为传入数据的数据类型,在运行时确定。

    泛型的另一个用途是用来约束范围,可以用来用在继承类类型的限制,如下:

    main(){;
      var student = Coach<Student>(new Student("学生 在讲话"));
      student.coachMethod();                                //学生在讲话
    }
    
    class Coach<T extends Person> {
      T coachData;
      Coach(this.coachData);
    
      coachMethod() {
        coachData.say();
      }
    }
    
    class Student extends Person {
      Student(String name) : super(name);
    }
    
    class Teacher extends Person {
      Teacher(name) : super(name);
    }
    
    class Person {
      String profession;
      Person(this.profession);
      void say(){
        print("$profession 在讲话");
      }
    }
    

    这里定义了辅导类(Coach),泛型约束了传入的类必须是继承自 Person 类,main 中的定义方法又约束了具体的类的类型,如果传入其他类进行初始化操作则会报错。

    也可以在函数中使用泛型,如下:

    main(){;
      print(lastString([1, 2, 3]));
    }
    
    T lastString<T>(List<T> lst) {
      if(lst.length <= 0){
        print("长度有误");
        return null;
      }
      return lst.first;
    }
    

    T lastString<T>(List<T> lst) 中,第一个 T 代表返回值类型,第二个 <T> 表示可以在函数的多个地方使用类型为 T 的参数,如果不加此参数会报错,第三个为列表中的参数类型。

    三、异步编程

    在程序设计中,为了不影响用户的使用,都会对耗时的任务做异步处理,以防止对用户界面或功能造成假死、卡顿等现象。在 Dart 中, 使用 asyncawait 来做异步编程,也可以使用 Future API。

    1. async 与 await

    await 必须在 async 函数中使用。比如,定义如下函数:

    getNetworkData() {
     //  var data = await "获取的网络数据"; //次处代码会报错,await 必须使用在 async 函数中
    }
    

    对上述代码做如下修改:

    main(){;
      getNetworkData();
      print("执行完毕");
    }
    
    getNetworkData() async {
      var data = await "获取的网络数据";
      print("数据:$data");
    }
    

    输出结果:

    执行完毕
    数据:获取的网络数据
    

    如果去掉 await 关键字修饰,做如下修改:

    main(){;
      getNetworkData();
      print("执行完毕");
    }
    
    getNetworkData() async {
      var data = "获取的网络数据";
      print("数据:$data");
    }
    

    输出结果:

    数据:获取的网络数据
    执行完毕
    

    可以发现当去掉 await 修饰后,函数的执行过程即为调用过程,顺序执行(即便在 getNetworkData 中有耗时操作 )。所以 asyncawait 需要一起使用,需要之后执行的处理使用 await 修饰。异步操作,不会等待操作执行完毕,而是继续执行后面的任务。

    可以同时使用多个 await ,如下:

    getNetworkData() async {
      var data =  await "获取的网络数据";
      print("数据:$data");
    
      var proData = await "处理数据";
      print("处理:$proData");
    }
    
    2. 回调

    当从网络获取到数据后,需要对数据进行本地处理后进行UI渲染,Dart 中可以使用回调函数的处理方式处理此问题,如下:

    main(){;
      getNetworkData(renderUI);
      print("执行完毕");
    }
    
    getNetworkData(methodCallBack) async {
      var data =  await "获取的网络数据";
      methodCallBack(data);
    }
    
    renderUI(var data){
      print("$data 进行UI渲染");
    }
    
    2. Future API

    对于被 async 修饰的异步函数,其返回值类型为 Future ,无论是否显式执行返回值类型。对于没有返回值的异步函数,应使用 Feture<void> 作为返回值类型。如下:

    Future<void> getNetworkData() async {
      var data =  await "获取的网络数据";
      print("数据:$data");
    }
    

    使用 Futurethen 方式处理函数回调,如下:

    main(){
      var future = getNetworkData();
      future.then((data){;
        renderUI(data);
      });
    }
    
    getNetworkData() async {
      var data =  await "获取的网络数据";
      return data;
    }
    
    renderUI(var data){
      print("$data 进行UI渲染");
    }
    

    官方建议,如果能使用 await 执行异步操作尽量使用此方式。Future 的 then 方法主要用于当有多个异步任务有依赖关系时使用。

    关于更多 Future API的使用,可见官方文档:https://dart.dev/guides/libraries/library-tour#dartasync---asynchronous-programming

    3. 流处理

    如果数据以流的方式进行处理,有两种方式,一种是使用 asyncawait for 处理,另外一种是使用 stream API。详细见官方文档:https://dart.dev/guides/libraries/library-tour#stream

    四、可调用类

    可调用类的作用是使类的实例可以像函数一样使用,需要在类中实现 call()方法,如下:

    main(){
      var student = Student();
      var des = student("hike", 11);
      print(des);
    }
    
    class Student {
      call(String name, int age) {
        return "$age 的学生 $name 在学习!";
      }
    }
    

    五. Typedefs

    在当前的 Dart 中,可以为函数指定别名(其他类型暂时不可以),使用 typedef 关键字。Dart 中的此功能是一种函数匹配,就官网的例子来说看似有点鸡肋,看如下例子:

    main(){
      var mAdd = MethodAdd(addFunction);
      var result= mAdd.func(1, 2);
      print(result);     //输出 3
      print(mAdd.func is Function);  //输出 true
    }
    
    class MethodAdd {
      Function func;
      MethodAdd(this.func);
    }
    
    num addFunction(num a, num b){
      return a + b;
    }
    

    定义一个 Function 类型参数作为类 MethodAdd 的构造函数参数,传入 addFunction 方法做加法运算并得出结果。使用 typedef 定义别名方式如下:

    typedef Add = num Function(num a, num b);
    
    main(){
      var mAdd = MethodAdd(addFunction);
      var result= mAdd.func(1, 2);
      print(result);                            //输出 3
      print(mAdd.func is Function); //输出 true
      print(mAdd.func is Add);          //输出 true
    }
    
    class MethodAdd {
      Add func;
      MethodAdd(this.func);
    }
    
    num addFunction(num a, num b){
      return a + b;
    }
    

    上述代码最终的区别就在于最后对类型的具体判断,未使用 typedef 定义别名时,仅能判断传入的是一个函数,使用别名后,因为定义了具体类型(这里是加法),可以具体判断到加法,传入其他类型,也是如此,也就是提供了一种类型检查机制。

    六、Metadata

    Dart 中,使用元数据来提供有关代码的其他信息。元数据注解使用字符 @ 开头,其后是对编译时常量的引用。Dart 中提供的批注有:@deprecated@override@proxy

    @override 前面说过,用来指示其标识的代码为重写父类的代码。

    @deprecated 表示被标识的代码被弃用,应使用其他替代代码,但依然可以执行,直到下次版本更新。

    main(){
      Student student = Student();
      student.studyData();
      student.study();
    }
    
    class Student extends Person{
      @override
      void eat() {
        print("学生吃");
      }
    
      @deprecated
      void studyData() {
        study(); //studyData 将被弃用,使用study方法代替
      }
    
      void study() {
        print("学习");
      }
    }
    
    class Person {
      void eat(){
        print("吃");
      }
    }
    

    也可以自定义元数据注解,新建一个名为 customAnnotation.dart 的文件,创建自定义注解并不一定需要单独创建一个文件,这里只是个例子,如一个注解只在本类使用,在类中创建即可(不知道是否有意义,但是这样做是可以的)。如果需要创建一个共享的,在多个列中都使用的注解,则单独创建一个 .dart 文件。我们创建的文件内容如下:

    library studyDescription;
    
    /**
     * 这是对学习的描述
    */
    const StudyDes stuDes = StudyDes();
    
    class StudyDes {
      const StudyDes();
    }
    

    这是一个用来描述学习的注解(只是例子,没有任何意义,这是根据官方注解的样子仿写的)。第一行使用 library 指令声明这是一个正式的 Dart 库(无此声明也可以),名字为 studyDescription 。注释格式为文档注释,创建的注解为 stuDes 。定义注解的类的构造函数必须为常量构造函数,关于常量构造函数的说明上文有过说明。 使用方式如下:

    import 'customAnnotation.dart';
    
    main(){
      Student student = Student();
      student.study();
    }
    
    class Student{
      @stuDes
      void study() {
        print("学习");
      }
    }
    

    在编译器中,当鼠标放在 import 'customAnnotation.dart'; 上静止不动时,会显示 library 定义的正式库的名字,鼠标放在 stuDes 上会提示注解的注释信息。这个注解是无参数的,也可以定义有参数的,与定义类的方式没什么区别,这里不再赘述。

    元数据可以出现在库(library),类(class),typedef,类型参数(type parameter),构造函数(constructor),工厂(factory),函数(function),字段(field),参数(parameter)或变量声明(variable declaration)之前,也可以出现在导入或导出指令之前。您可以在运行时使用反射来检索元数据

    至此,Dart 基础语法部分完毕。

    相关文章

      网友评论

        本文标题:[Flutter]flutter基础之Dart语言基础(四)

        本文链接:https://www.haomeiwen.com/subject/tudxchtx.html