美文网首页Happy Flutter
Dart语法摘要(四)

Dart语法摘要(四)

作者: tongxyj | 来源:发表于2020-11-25 15:21 被阅读0次

    学Flutter就和学iOS一样,先学基本语言语法的使用,再学习搭UI,iOS是OC和Swift,Flutter么就是Dart,语言学多了语法都差球不多,特别是这些新的语言,很多东西都是相似的,但是长时间不用容易忘记,与其每次去网上瞎jb找,不如自己总结一些,以后看自己写的东西就行了。文中个别结论是我自己总结出来的,不能保证准确性,看到的同学仅供参考。

    本章提纲:
    1.异步
    2.泛型
    3.库的使用

    异步

    Dart 库中包含许多返回 Future 或 Stream 对象的函数。这些函数在设置完耗时任务后, 就立即返回了,不会等待耗任务完成。 使用 asyncawait 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。

    Future

    Future的字面意思就是在未来的某一个时刻让你拿到结果。
    Future有两种状态:

    • 状态一:未完成状态(uncompleted)
      执行Future内部的操作时(在上面的案例中就是具体的网络请求过程,我们使用了延迟来模拟),我们称这个过程为未完成状态
    • 状态二:完成状态(completed)
      当Future内部的操作执行完成,通常会返回一个值,或者抛出一个异常。
      这两种情况,我们都称Future为完成状态。
    mport "dart:io";
    
    main(List<String> args) {
      print("main function start");
      print(getNetworkData());
      print("main function end");
    }
    
    Future<String> getNetworkData() {
      return Future<String>(() {
        sleep(Duration(seconds: 3));
        return "network data";
      });
    }
    
    // 打印
    // main function start
    // Instance of 'Future<String>'
    // main function end
    

    使用.then来拿到Future的回调或者使用.catchError(error)来捕获异常,whenComplete指所有任务完成后的回调函数,类似于finally:

    import "dart:io";
    
    main(List<String> args) {
      print("main function start");
      var future = getNetworkData();
      future.then((value) {
        print(value);
      }).catchError((error) { // 捕获出现异常时的情况
        print(error);
      }).whenComplete(() {
        print("whenComplete")//所有任务完成后的回调函数;
      });
      print(future);
      print("main function end");
    }
    
    Future<String> getNetworkData() {
      return Future<String>(() {
        sleep(Duration(seconds: 3));
        future.then((value) {
          print(value);
        });
        // 不再返回结果,而是出现异常
        // return "network data";
        // throw Exception("网络请求出现错误");
      });
    }
    

    Future.then链式调用避免了回调地狱问题,这个感觉就跟OC里的Block的链式调用很相似:

    void main() {
      login('tong', '1234556').then((id){
        print('登录成功,用户id为${id}');
        return getUserInfo(id);
      }).then((userInfo){
        print('获取用户信息成功,结果为${userInfo}');
        return saveUserInfo(userInfo);
      }).then((data){
        print('保存用户信息成功');
      });
    

    Future.value(value)可以直接获取一个完成的Future,该Future会直接调用then的回调函数:

    main(List<String> args) {
      print("main function start");
    
      Future.value("哈哈哈").then((value) {
        print(value);
      });
    
      print("main function end");
    }
    // 打印
    // main function start
    // main function end
    // 哈哈哈
    

    这块为啥直接获取到完成的Future,哈哈哈还是最后才打印呢,因为.then回调中的事件被加到eventLoop中了,简单说下自己对这个eventLoop的理解吧,这玩意儿就和iOS里的runLoop差不多,本质上都是一套事件循环机制,只不过iOS里的runLoop有主线程和子线程之分,但在Dart里我看文章里讲的似乎只有一条线程,Dart中的异步操作也并不像iOS中可以去新开一条线程去执行一些耗时操作,这里的Future其实是个非阻塞调用的感觉,就是说我当前线程要执行一些耗时操作,那我把调用完这个返回Future的耗时操作方法以后就不管了,“主线程”不会在这边等待结果,继续执行下边的操作,至于结果什么时候回来,我就通过.then去挂一个监听,等回调回来的时候,.then里面的方法会被加到当前这个eventLoop的队列里等待执行,所以上面的哈哈哈就是被放在eventLoop里,当main function end打印完后就打印哈哈哈,大概就这么个意思,这点像我之前接触了iOS的多线程机制还不太能理解单线程是怎么执行异步操作的,其实这个单线程也就是个“伪单线程”,只不过耗时操作在哪个线程执行交给CPU去调度了,我们只需要关注什么时候拿到结果就OK了,看完这块其实也加深了对OC中async和sync的理解,async严格意义上就是个非阻塞调用方式,只不过在OC里这种非阻塞调用的方式可以创建一条新的线程而已,目前是这么理解的,可能不一定对,后面讲到Dart其实也可以自己去创建一个新的线程,只不过他这个不单单是一条线程,他叫Isolate,包含了线程和evetLoop,后面再讲讲。

    await、async

    使用 await 可以让当前操作等待后面异步函数的执行结果,使用 await 时代码必须在使用 async 标记的函数中,使用async标记的函数,必须返回一个Future对象,这个对象并不需要我们手动包装,直接返回正常的数据,返回值会默认被包装在一个Future对象中,这个特性好像Swift哪个语法点里也有,我记得可以自动包装来着,完了看Swift的时候再看看吧。

    Future<String> getNetworkData() async {
      var result = await Future.delayed(Duration(seconds: 3), () {
        return "network data";
      });
    
      return "请求到的数据:" + result;//自动包装成Future
    }
    

    虽然异步函数可能会执行耗时的操作, 但它不会等待这些操作。 相反,异步函数只有在遇到第一个 await 表达式时才会执行。 也就是说,它返回一个 Future 对象, 仅在await表达式完成后才恢复执行。

    Dart关于异步的基本用法目前就这些,还有些高级用法后面用到的时候再看吧。

    try / catch / finally

    和Java中的try/catch类型,在OC也有捕获异常的机制,就是平时开发的时候没怎么用过:

    try {
      breedMoreLlamas();
    } on OutOfLlamasException {
      // 一个具体异常
      buyMoreLlamas();
    } on Exception catch (e) {
      // 任意一个异常
      print('Unknown exception: $e');
    } catch (e) {
      // 非具体类型
      print('Something really unknown: $e');
    } finally {
       print('Something complete');
    }
    

    泛型

    OC应该是从Swift2.0的时候开始为了更好的兼容Swift,引入了泛型,但我比较lowb,对OC泛型的使用还是仅仅停留在声明集合类型对象的时候,其他用法涉猎不多,慢慢的接触Swift和Dart的时候才接触到泛型的其他使用场景。

    为什么使用泛型

    在类型安全上通常需要泛型支持, 它的好处不仅仅是保证代码的正常运行:

    • 正确指定泛型类型可以提高代码质量。
    • 使用泛型可以减少重复的代码。

    常见的用法就是用泛型来限制集合存储元素的类型,这样编译器可以进行静态类型检查,从数组中取出的对象也不再是Dart中应该是Dynamic或者Object类型,类似于OC里的id类型:

    var names = List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    names.add(42); // 错误
    

    另外一个使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以继续使用检查模式和静态分析工具提供的代码分析功能。 例如,假设你创建了一个用于缓存对象的接口:

    abstract class ObjectCache {
      Object getByKey(String key);
      void setByKey(String key, Object value);
    }
    

    后来发现需要一个相同功能的字符串类型接口,因此又创建了另一个接口:

    abstract class StringCache {
      String getByKey(String key);
      void setByKey(String key, String value);
    }
    

    后来,又发现需要一个相同功能的数字类型接口 … 这里你应该明白了。
    泛型可以省去创建所有这些接口的麻烦。 通过创建一个带有泛型参数的接口,来代替上述接口:

    abstract class Cache<T> {
      T getByKey(String key);
      void setByKey(String key, T value);
    }
    

    泛型类型的构造函数

    稍微回顾一下Swift中使用泛型创建一个数组:

    var shoppingList: [String] = ["Eggs", "Milk"]
    

    Dart中的写法:

    var names = <String>['Seth', 'Kathy', 'Lars'];
     List<String> names1 = ['Seth', 'Kathy', 'Lars'];
    

    运行时中的泛型集合

    Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 例如, 在运行时检测集合的类型:

    var names = List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    print(names is List<String>); // true
    

    提示: 相反,Java中的泛型会被 擦除 ,也就是说在运行时泛型类型参数的信息是不存在的。 在Java中,可以测试对象是否为 List 类型, 但无法测试它是否为 List<String> 。

    限制泛型类型

    使用泛型类型的时候,可以使用 extends 实现参数类型的限制,参数必须是extends类或其子类类型。

    class Location<T extends num> {
      T x;
      T y;
    
      Location(this.x, this.y);
    }
    main(List<String> args) {
      Location l2 = Location<int>(10, 20);
      print(l2.x.runtimeType);
        
      // 错误的写法, 类型必须继承自num
      Location l3 = Location<String>('aaa', 'bbb');
    }
    

    使用泛型函数

    最初,Dart仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。具体用法到时候在网上再查查,这玩意儿对我来说就是个新鲜事物。

    main(List<String> args) {
      var names = ['why', 'kobe'];
      var first = getFirst(names);
      print('$first ${first.runtimeType}'); // why String
    }
    
    T getFirst<T>(List<T> ts) {
      T tmp = ts[0];
      return tmp;
    }
    

    这里的 first (<T>) 泛型可以在如下地方使用参数 T :

    • 函数的返回值类型 (T).
    • 参数的类型 (List<T>).
    • 局部变量的类型 (T tmp).

    库的使用

    Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明。import语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库,语法如下:

    import 'dart:html';
    

    使用相对路径导入的库,通常指自己项目中定义的其他dart文件:

    import 'lib/student/student.dart';
    

    Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package:

    //Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
    import 'package:flutter/material.dart';
    

    库文件中内容的显示和隐藏
    如果希望只导入库中某些内容,或者刻意隐藏库里面某些内容,可以使用show和hide关键字:
    show关键字:可以显示某个成员(屏蔽其他)
    hide关键字:可以隐藏某个成员(显示其他)

    import 'lib/student/student.dart' show Student, Person;
    
    import 'lib/student/student.dart' hide Person;
    

    当库中内容和当前文件中的名字冲突的时候,可以使用as关键字来使用命名空间:

    import 'lib/student/student.dart' as Stu;
    
    Stu.Student s = Stu.Student();
    

    库的定义

    library关键字
    通常在定义库时,我们可以使用library关键字给库起一个名字:

    library math;
    

    export关键字
    将不同的库分散在不同的dart文件中,通过export关键字暴露给外界使用:

    library utils;
    
    export "mathUtils.dart";
    export "dateUtils.dart";
    
    //使用时
    //import "lib/utils.dart";
    

    Dart主要语法基本告一段落了,现在去项目里看代码的时候应该会好很多了。
    参考资料:
    coderwhy-Flutter之搞定Dart系列
    Dart编程语言中文网

    相关文章

      网友评论

        本文标题:Dart语法摘要(四)

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