Flutter--Dart语法小记

1,548 阅读7分钟

可选参数

Dart方法有两种类型的参数:必需的和可选的。必需的我们一般都知道,这里主要分析可选参数。

如果同时包含可选参数和必需参数,必需的参数在参数列表前面, 可选数在后面。

可选参数可以有一个默认值,当默认值在调用者没有指定值时使用。这一点和kotlin的语法很像。其次可选参数具体可分为:

  • Optional named parameters(可选命名参数)
  • Optional positional parameters(可选位置参数)

可选命名参数

在方法参数中,使用"{}"包围的参数属于可选命名参数,比如

  void _buildThree(int num, {String name, int range}) {
    
  }

可以为可选参数添加默认值,比如:

  void _buildThree(int num, {String name, int range = 10}) {

  }

调用包含可选命名参数的方法时,需要使用paramName:value的形式指定为哪个可选参数赋值,比如:

    _buildThree(10,range: 1);

可选位置参数

在方法参数中,使用"[]"包围的参数属于可选位置参数,比如

  void _buildHouse(int num, [String where, int range]) {

  }
  
  void _buildHouseAndDefaultValue(int num, [String where = 'Shanghai', int range]) {

  }

调用包含可选位置参数的方法时,无需使用paramName:value的形式,因为 可选位置参数是位置,如果想指定某个位置上的参数值,则必须前面位置的已经有值,即使前面的值存在默认值。 比如:

    _buildHouse(10,10); //不可行的
    
    _buildHouse(10,'shenzhen',10); //可行的
    
    _buildHouseAndDefaultValue(10,10); //不可行的
    
    _buildHouseAndDefaultValue(10,'shenzhen',10); //可行的
    
    

这里特意使用两个不同类型的可选参数作为示例,如果前后可选参数为相同类型,则会出现异常结果,并且只有在发生后才会注意到。所以这一点要特别注意。比如以下示例,假如本意是想赋值给age,但结果将会差强人意

void _buildHouse(int num, [int range , int age]) {
  print('range :  $range  age : $age');
}

void main() {
  _buildHouse(10,10);
}

输出:

range :  10  age : null

Mixins与with关键字

Dart和Java一样只支持单继承。而且Dart中没有和Java一样提供Interface字段去声明一个接口,但是也有抽象类。

如果想使用和Java接口一样的功能可以使用Mixinsimplements两种方式,分别解释下两种方式:

  • Mixins : 指能够将另一个或多个类的功能添加到您自己的类中,而无需继承这些类。
  • implements : 将一个类作为接口使用
class A {
  void a() {
    print('a');
  }
}

class B implements A {
  @override
  void a() {
    print('override a');
  }
}

class C {
  void c() {
    print('c');
  }
}

class E {
  String e = 'eeee';
}

class D extends A with C, E {
  void c() {
    print('c is D');
  }

  void d() {
    c();
  }
}

首先看B implements A,所以此时A相对于B来说就是一个接口,所以他要实现B中的方法。换句话说,Dart每个类都是接口

然后看D extends A with C ,D继承于A,由于单继承特性,这个时候D不能再使用extends关键字继承其他类,但是可以使用with关键字折叠其他类以实现代码重用。当属性和方法重复时,以当前类为准。 比如上面例子调用Dc()方法打印的是 c is D

Typedefs

在学习Flutter的过程中用到了ValueChange这个方法,是用typedef修饰的一个方法。查阅了一番资料,记录下对Typedefs的理解。

先看看官网的解释,我并没有看明白这个到底有什么用,可以做什么事。

说下自己的个人理解。首先需要明确的一点是在Dart中,方法是一等对象。可以把方法当做参数调用另外一个方法。 typedef 本质上为 一个方法签名提供了一个别名。官网上介绍TypeDef的时候有一句话说道:"If we change the code to use explicit names and retain type information" ,使用typedef会保留方法的类型信息。以官网的例子说明

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}

当把 f 赋值给 compare 的时候, 类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型)。我们不能使用coll.compare is int f(Object a, Object b)判断,我们只知道该类型是一个 Function。而使用typedef,相当于给方法贴了一个类型标签

我在Effective Dart中看到一条规则说到: 如果需要的只是一个回调函数,使用方法即可。 如果你定义了一个类,里面只有一个名字无意义的函数, 例如 call 或者 invoke, 这种情况最好用方法替代;

//good
typedef Predicate<E> = bool Function(E element);

//bad
abstract class Predicate<E> {
  bool test(E element);
}

前面提到的ValueChange这个方法就是作为回调函数使用,具体细节可以参考The parent widget manages the widget’s state

Generics泛型

Dart中关于泛型的使用和Java相差不大,但是重要的一点区别是:Java 中泛型信息是编译时的,泛型类型信息在运行时被擦除。而Dart 的泛型类型是固化的,在运行时有也 可以判断具体的类型。 比如以下代码再Java中是行不通的,但在Dart中是可行的

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

注意 is 表达式只是判断集合的类型,而不是集合里面具体对象的类型。

Import

import 必须参数为库 的 URI。 对于内置的库,URI 使用特殊的 dart: scheme。 对于其他的库,你可以使用文件系统路径或者 package: schemepackage: scheme 指定的库通过包管理器来提供, 例如 pub 工具

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

如果导入的多个库具有冲突的标识符,可以使用库的前缀来区分

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // 使用lib1的Element.
lib2.Element element2 = new lib2.Element(); // 使用lib2的Element..

如果只使用库的一部分功能,则可以选择需要导入的内容。

// 只导入foo.
import 'package:lib1/lib1.dart' show foo;

// 导入除了foo以外的
import 'package:lib2/lib2.dart' hide foo;

Lazily loading a library

Deferred loading可以让应用在需要的时候再 加载库。 下面是一些使用延迟加载库的场景:

  • 减少 APP 的启动时间。
  • 执行 A/B 测试,例如 尝试各种算法的 不同实现。
  • 加载很少使用的功能,例如可选的屏幕和对话框。

要延迟加载一个库,需要先使用 deferred as 来 导入:

import 'package:deferred/hello.dart' deferred as hello;

使用的时候,使用库标识符调用 loadLibrary() 函数来加载库。注意:调用 loadLibrary() 该库只是载入一次

使用延迟加载库的时候,请注意一下问题:

  1. 延迟加载库的常量在导入的时候是不可用的。 只有当库加载完毕的时候,库中常量才可以使用。
  2. 在导入文件的时候无法使用延迟库中的类型。 如果你需要使用类型,则考虑把接口类型移动到另外一个库中, 让两个库都分别导入这个接口库。
  3. Dart 隐含的把 loadLibrary() 函数导入到使用 deferred as 的命名空间 中。 loadLibrary() 方法返回一个 Future

注解

Dart默认包含所有代码都可以使用的三个注解: @deprecated@override、 和 @proxy

  • 使用 @deprecated 注解表示函数被启用
  • 使用 @override 注解来 表明你的函数是想覆写超类的一个函数
  • 使用 @proxy 注解来避免警告信息

可以自定义元数据注解,与Java不同的是无需使用 @interface 关键字进行定义,只需使用普通的 class 方式声明,如

library todo;

class todo {
  final String who;
  final String what;

  const todo(this.who, this.what);
}
import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}