Flutter基础--Dart

Flutter使用Dart开发,具有良好的性能和优秀的跨平台性,codepen.io目前已经支持Flutter。

diagram-layercake

Flutter vs React Native vs NativeScript vs Ionic vs PWAs | Maximilian Schwarzmüller, Udemy 干货|Flutter 原理与闲鱼深度实践

Dart是谷歌Fuchsia OS钦定的UI语言,专门针对UI开发进行过优化,Flutter很好地为Dart进行了背书。

Dart versus Node js fastest programs

Dart的一些特性:

  • 跨平台且支持多端(dart VM、dart2native、dart2js)
  • 支持AoT(Ahead of Time)编译
  • 支持垃圾回收
  • 支持热重载(JIT编译)
  • 单线程(event-loop)
  • 面向对象
  • 强类型(strong-typed)语言,带类型推断功能
  • 支持泛型

http://dartpad.dev/ 可以在线体验dart。不过更推荐使用 repl.it,功能更强大,比如支持引入第三方类库。

基础

变量与常量

用 var 关键字声明变量

var greeting = 'hello, dart'; // type inference
String hi = '你好'; // use specific type
int awesome = 666;
dynamic typeVariableVar = 'variable';
typeVariableVar = 233; // assign int to typeVariableVar is OK, cause it's type is dynamic
awesome = '666'; // Error: A value of type 'String' can't be assigned to a variable of type 'int'

用 const 关键字声明编译时常量

const HTTP_OK = 200;
const int errorCode = -1;

用 final 关键字声明不可重新赋值变量

final now = DateTime.now();
final numbers = <int>[1, 2, 3];
numbers.add(4); // cannot ressign numbers to other value, but can add value to it
numbers[0] = 0; // this is OK too, cause value in final collection is not final
numbers = [5, 6, 7]; // Error: Can't assign to the final variable 'numbers'

// value in const collection is also const
const otherNumbers = <dynamic>[233, 666, 'awsl'];
otherNumbers[0] = 886; // Uncaught Error: Unsupported operation
  • 对dart而言一切类型皆对象,所以变量保存的是对象的引用。
  • 未初始化的变量默认值为null

变量作用域

和其他绝大部分语言一样 dart 变量的作用域是静态作用域。关于全局作用域、动态作用域和静态作用域推荐阅读 《代码之髓》第七章进一步了解。

闭包(Closure)

函数内部可以包含函数所在作用域内的变量,即使函数在其作用域以外被调用。

基础数据类型

  • num
    • int
    • double (IEEE 754)
var a = 1;
var b = 0xD800;
var c = 1.0;
var d = 1.0e10;
double e = 1; // Dart 2.1版本起 int类型字面量自动转换为double,所以flutter内很多组件属性类型是double但使用时可以自己传int
var one = int.parse('1');
int.parse('666'); // 666
int.parse('36', radix: 16); // 54 parse with radix
int.parse('0x29A'); // 666
double.parse('0.9'); // 0.9
123.456.toStringAsFixed(2) == '123.46');
123.456.toStringAsPrecision(2); // '1.2e+2'
123.456.toStringAsPrecision(4); // '123.5'
double.parse('1.23e+2'); // 123.0
  • string
var str1 = 'hello';
var str2 = "hi";
var name = 'world';
var strWithVar = 'hello, $name';
var strWithExpr = 'hello, ${name.toUpperCase()}';
var strConcat = 'String '
    'concatenation'
    " works even over line breaks.";

var strConcat = 'The + operator ' + 'works, as well.';
var multilineStr = """a string break into
multi-line like this.""";
var rawStr = r'In a raw string, not even \n gets special treatment.';
var emojiStr = '😁';
assert(emojiStr.length == 2); // true
var unicodeStr = '\u{1f601}'; // 😁
var surrogateUnicodeStr = '\ud83d\ude00'; // 😁
for (var char in emojiStr.runes) {
  print(char.toRadixString(16)); // 1f600
}
  • 为了兼容 JavaScript, Dart字符串没有采用UTF-8而采用了UTF-16编码。https://github.com/dart-lang/sdk/issues/36316

  • String 由 Runes(Unicode码点值)构成,要遍历字符串内的字符需要对runes进行遍历。

  • 不能通过String构成函数创建字符串,只能通过字面量创建字符串

var str1 = 's';
var str2 = 's';
assert(str1 == str2); // true
  • bool

只有true和false是bool类型,空字符串、null都不是bool

'' == false // false
// Uncaught Error: Assertion failed: "boolean expression must not be null"
var nullVar;
if (!nullVar) {
  print();
}

''.isEmpty == true // true
nullVar == null // true
  • List 有序数组
var arr = [1, 2, 3];
var constList = const [1, 2, 3];
var newList = [0, ...arr];
var nullArr;
var newArr = [0, ...?nullArr];

// control flow collection
var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
  • Set 无序集合
Set<String> countries = {};
var names = <String>{};
var languages = { 'chinese', 'english', 'japanese', 'german' };

Dart 2.3 开始,set支持展开运算符(…)和空感知展开运算符(…?)

  • Map 映射
var chineseNumbers = {
  0: '零',
  1'一',
  2: '二'
};
var animals = Map(); // Dart 2开始创建对象时 new 操作符可选
animals['lion'] = '狮子';
animals['tiger'] = '老虎';

键和值都可以是任意类型 {} 字面量的类型默认是Map,所以用 {} 字面量初始化Set类型变量时需要注意 Dart 2.3 开始,set支持展开运算符(…)和空感知展开运算符(…?)

  • Symbol

用于代表操作符或者标识符

You might never need to use symbols, but they’re invaluable for APIs that refer to identifiers by name, because minification changes identifier names but not identifier symbols

https://github.com/dart-lang/sdk/blob/9a618e5661665b8d687a28e6b1ec25e9177ec2d7/tests/corelib_2/symbol_operator_test.dart

https://dart.dev/guides/language/sound-dart

枚举(Enum)

和typescript的enum比起来,功能比较简单

enum Color { red, green, blue }

List<Color> colors = Color.values;
assert(colors[0] == Color.red);

assert(Color.red.index == 0);

print(Color.red.toString().split('.').last); // red

函数

first-class Object. 函数是一个对象,所以可以当作普通变量使用。

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; // => expr 等效于 { return expr; } 只有当函数体只包含一条表达式时才能使用

函数参数分为必选和可选两种类型,参数可以是具名参数也可以是不具名参数

void add(int op1, int op2) => op1 + op2;
// parameters wrapped by [] is optional positional parameters
String greeting(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}
void sayHi({ String name }) {}
void sayHello({ @required String name }) {}
void enableFlags({bool bold = false, bool hidden = false}) {...}

// cascade, perform multiple operations on the members of a single object
querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);

// anonymous function
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List parameter for arguments.

运算符(operator)

dart支持运算符重载(override),如Duration类就重写了加减运算符。

DescriptionOperator
unary postfixexpr++    expr–    ()    []    .    ?.
unary prefix-expr    !expr    ~expr    ++expr    –expr      await expr
multiplicative*    /    %  ~/
additive+    -
shift«    »    »>
bitwise AND&
bitwise XOR^
bitwise OR
relational and type test>=    >    <=    <    as    is    is!
equality==    !=
logical AND&&
logical OR
if null??
conditionalexpr1 ? expr2 : expr3
cascade..
assignment=    *=    /=   +=   -=   &=   ^=  ??= etc.

可以看到除了常规的一些运算符外,dart还支持

// ?.(conditional member access)
// ??(if null)
// ??=(if null assignment)
// ..(cascade)
// ...(spread operator)
// ...?(null-aware spread operator)
// as(typecast)
true is bool // is(positive typecheck)
3 is! bool // is!(negative typecheck)

可以被重载的运算符

<+
>/^[]=
<=~/&~
>=*«==
%»

!= 运算符是语法糖,e1 != e2 实际上等于 !(e1 == e2),所以不可重载。

== 运算符用于判断对象的相等性,可以通过覆盖 == 运算符的实现来修改 == 的行为。但是对 == 运算符的覆盖必须遵循等价关系(equivalence relation):

Total: It must return a boolean for all arguments. It should never throw or return null.

Reflexive: For all objects o, o == o must be true.

Symmetric: For all objects o1 and o2, o1 == o2 and o2 == o1 must either both be true, or both be false.

Transitive: For all objects o1, o2, and o3, if o1 == o2 and o2 == o3 are true, then o1 == o3 must be true.

覆盖 == 运算符时需要同时覆盖 hashCode 属性,相等的对象必须就有相同的 hashCode,但具有相同hashCode的对象不一定相等。

A hash code is a single integer which represents the state of the object that affects [operator ==] comparisons.

All objects have hash codes. Hash codes must be the same for objects that are equal to each other according to [operator ==].

The hash code of an object should only change if the object changes in a way that affects equality.

Objects that are not equal are allowed to have the same hash code.

class Duration implements Comparable<Duration> {
  ...

  /**
    * Adds this Duration and [other] and
    * returns the sum as a new Duration object.
    */
  Duration operator +(Duration other) {
    return Duration._microseconds(_duration + other._duration);
  }

  /**
    * Subtracts [other] from this Duration and
    * returns the difference as a new Duration object.
    */
  Duration operator -(Duration other) {
    return Duration._microseconds(_duration - other._duration);
  }

  ...
}

控制流(control flow)

和其他常见语言支持常规的控制流语句差不多,switch语句稍微有点区别。

// if..else
if (isChinese()) {
  print('你好');
} else if (isJapanese()) {
  print('こんにちは');
} else {
  print('hello');
}

// for
var numbers = [0, 1, 2, 3];
for (var i = 0; i < numbers.length; i++) {
  print(numbers[i]);
}

for (var number in numbers) {
  print(number);
}

// while
var counter = 10;
while (counter > 0) {
  counter--;
  print(counter);
}

// do-while
var counter = 0;
do {
  counter++;
  print(counter);
} while (counter < 10);

// break
var counter = 10;
for (var i = 0; i < counter; i++) {
  if (i == 5) {
    break;
  }
  print(i); // prints 0 - 4
}

// continue
var counter = 10;
for (var i = 0; i < counter; i++) {
  if (i == 5) {
    continue;
  }
  print(i); // prints 0 - 4 6 - 9
}
// switch..case
var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    handleClose();
    break;
  case 'OPEN':
    handleOpen();
    break;
  case 'UNKOWN': // empty case clause is permitted
  case 'PENDING':
    handlePending();
    continue done; // continue with labeled case clause
  done:
  case 'DONE':
    handleDone();
    break;
  default:
    executeUnknown();
}

// assert
assert(text != null);
assert(text != null, 'text should be initialized');

异常处理

  • throw
throw SocketException("Socket is closed for reading");
throw ArgumentError('Index inside surrogate pair: $index');
throw UnsupportedError("Directory_SetCurrent");
  • try..on
  • try..catch
  • try..on..catch
  • finally

需要注意使用 on 语句捕获异常需要指明具体的异常类型(Exception是接口)

// handle one exception
try {
  handleOrder();
} on FormatException {
  handleException();
}

// handle multiple exceptions
try {
  handleOrder();
} on Exception catch(e) {
  print('unknown exception: $e');
} catch (e, s) {
  print('yet another unknown exception: $e');
  print('Stack trace:\n $s'); // s stands for stacktrace
} finally {
  cleanUp();
}

类(Class)

支持基于mixin模式的类继承 支持通过extension方法在不修改原类的情况下对类进行扩展 支持常量构造函数

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b))

构造函数

构造函数分两类:

  • 默认构造函数(和类同名的无参数函数,不声明构造函数时创建的构造函数)
  • 具名构造函数
// 声明了构造函数 Point 类就不存在默认构造函数
class Point {
  double x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromMap(Map data) {
    x = data['x'];
    y = data['y'];
  }

  // Redirect constructor, which just delegate to anthor constructor
  Point.alongXAxis(double x) : this(x, 0);
}

// const objects that never change
class ImmutablePoint {
  static final ImmutablePoint origin =
  const ImmutablePoint(0, 0);

  // all instance variables must set to final
  final double x, y;

  // const constructor
  const ImmutablePoint(this.x, this.y);
}

use factory constructor when:

  • the constructor is expensive, so you want to return an existing instance - if possible - instead of creating a new one;
  • you only ever want to create one instance of a class (the singleton pattern);
  • you want to return a subclass instance instead of the class itself.

Constant constructors don’t always create constants

https://stackoverflow.com/questions/53886304/understanding-factory-constructor-code-example-dart

当声明了带参数且与类同名的函数时类就不具备默认构造函数了。当父类不存在默认构造函数时,子类初始化时需要先显式调用父类构造函数。

If the superclass doesn’t have an unnamed, no-argument constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any).

在构造函数调用前和父类构造函数调用后可以进行一些初始化操作(Initializer list),每一步用逗号分隔。如flutter内常见的组件初始化参数校验和赋值:

// 调用AppBar构造函数前会先对参数进行校验、设置初始值、调用父类构造函数等一系列操作
AppBar({
  Key key,
  this.leading,
  this.automaticallyImplyLeading = true,
  this.title,
  this.actions,
  this.flexibleSpace,
  this.bottom,
  this.elevation,
  this.shape,
  this.backgroundColor,
  this.brightness,
  this.iconTheme,
  this.actionsIconTheme,
  this.textTheme,
  this.primary = true,
  this.centerTitle,
  this.excludeHeaderSemantics = false,
  this.titleSpacing = NavigationToolbar.kMiddleSpacing,
  this.toolbarOpacity = 1.0,
  this.bottomOpacity = 1.0,
}) : assert(automaticallyImplyLeading != null),
    assert(elevation == null || elevation >= 0.0),
    assert(primary != null),
    assert(titleSpacing != null),
    assert(toolbarOpacity != null),
    assert(bottomOpacity != null),
    preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
    super(key: key);

this

this即代表当前对象实例

静态变量与静态方法

即类变量和类方法,属于类不属于任何类的实例

class Point {
  static const size = 16;

  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

assert(Queue.size == 16);
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
print(distance);

Getter/Setter

实例变量默认包含对应的getter/setter方法

class People {
  final String firstname;
  final String lastname;

  People({
    @required this.firstname,
    @required this.lastname,
  });

  String get fullname {
    return '$firstname $lastname';
  }
}

var noone = People('john', 'doe');
print(noone.fullname); // 'john doe'

Callable

if you wanna call object instance like a function, implements call() method.

class CallableClass {
  String call(String name) => 'hello $name!';
}

var cc = CallableClass();
var greeting = cc('john');
print(greeting);

继承(extends)

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
}

class SmartTelevision extends Television {
  @override
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
}

Mixin

https://en.wikipedia.org/wiki/Mixin

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

和Mixin模式类似的还有Trait模式(rust采用),二者的比较参见:

https://stackoverflow.com/questions/925609/mixins-vs-traits

扩展(Extension)

Dart 2.7 添加了扩展方法支持。通过扩展方法可以在不修改类库的情况下对其进行扩展。

int.parse('42');

// lib/string_apis.dart
// extend String class add toInt method
extension String2Number on String {
  int toInt() {
    return int.parse(this);
  }
}

import 'string_apis.dart';
print('42'.toInt());

https://dart.dev/guides/language/extension-methods

抽象类(Abstract Class)

abstract class Human {
  void read(); // Abstract method.
}

接口(interface)

dart没有单独的interface接口,类可以充当接口

class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String name) => 'Hello, $name. I am $_name.';
}

// An implementation of the Person interface.
class Chinese implements Person, Comparable {
  int age;
  get _name => '';

  String greet(String name) => '$name 你好';

  static int compare(Comparable a, Comparable b) => a.age - b.age;
}

泛型(Generics)

// for type safty
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var moreNames = <String>['Tom', 'Joe'];
names.add(42); // Error

// reduce duplication
abstract class NumberCache {
  num getByKey(String key);
  void setByKey(String key, Object value);
}
abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}
// When T is String it works the same as StringCache
// When T is num it works the same as NumberCache
abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

// key of map limited to int, value of map limited to Page object
var pages = Map<int, Page>();

// only object that implements Comparable interface allowed
var ranking = List<T implements Comparable>();

// only instances of Animal and it's sub class allowed
var ranking = List<T extends Animal>();

包(Package)

  • 所有dart程序都是一个库
  • 库以包(pacakge)的形式进行分发
  • 私有变量和方法以下划线开头
  • 通过import语句引用库
  • 编译到js代码时支持懒加载(lazy loading)
// use dart sdk library
import 'dart:async';
// use third-party package
import 'package:flutter/material.dart';
// import same library code by relative path
import '../utils/helper.dart';

// partial import
import 'package:lib1/lib1.dart' show foo; // import foo only
import 'package:lib2/lib2.dart' hide foo; // import all others except foo

// use alias to resolve conflicts
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// lazy load for dart2js
import 'package:greetings/hello.dart' deferred as hello;

https://dart.dev/guides/libraries/create-library-packages

异步

Future

A Future is used to represent a potential value, or error, that will be available at some time in the future.

顾名思义,Future就是指未来某个时间点得到的结果。和JavaScript Promise一样,Future要么返回一个结果值,要么返回一个错误。

Future存在两种状态:

  • uncompleted
  • completed(with a value or error)
// onError is optional
Future<int> successor = future.then(
  (int value) {
    // Invoked when the future is completed with a value.
    return 42;  // The successor is completed with the value 42.
  },
  onError: (e) {
    // Invoked when the future is completed with an error.
    if (canHandle(e)) {
      return 499;  // The successor is completed with the value 499.
    } else {
      throw e;  // The successor is completed with the error e.
    }
  }
);

import 'dart:async';
import 'dart:io';

void main() {
  new File('file.txt').readAsString().then((String contents) {
    print(contents);
  });
}

Stream

可以类比工厂流水线,数据就好比原材料在流水线上流动,每个环节的工人对数据进行处理,最后得到完整的制品。

Dart里 Stream 是一个事件序列。通过对事件进行订阅(StreamSubscription)

A Stream provides a way to receive a sequence of events

https://en.wikipedia.org/wiki/Stream_(computing) https://dart.dev/tutorials/language/streams https://api.dart.dev/stable/2.8.3/dart-async/Stream-class.html

  • Single-subscription
  • Broadcast

按规定,Stream 在有订阅者时才开始运行

Generator

  • sync*
  • async*
Stream<int> timedCounter(Duration interval, [int maxCount]) async* {
  int i = 0;
  while (true) {
    await Future.delayed(interval);
    yield i++;
    if (i == maxCount) break;
  }
}

StreamController

take full control of stream by use StreamController

Stream<int> timedCounter(Duration interval, [int maxCount]) {
  StreamController<int> controller;
  Timer timer;
  int counter = 0;

  void tick(_) {
    counter++;
    controller.add(counter); // Ask stream to send counter values as event.
    if (counter == maxCount) {
      timer.cancel();
      controller.close(); // Ask stream to shut down and tell listeners.
    }
  }

  void startTimer() {
    timer = Timer.periodic(interval, tick);
  }

  void stopTimer() {
    if (timer != null) {
      timer.cancel();
      timer = null;
    }
  }

  controller = StreamController<int>(
      onListen: startTimer,
      onPause: stopTimer,
      onResume: startTimer,
      onCancel: stopTimer);

  return controller.stream;
}

await/async

Future<void> printOrderMessage() async {
  print('Awaiting user order...');
  var order = await fetchUserOrder();
  print('Your order is: $order');
}

Future<String> fetchUserOrder() {
  return Future.delayed(Duration(seconds: 2), () => 'Large Latte');
}

Future<void> main() async {
  countSeconds(4);
  await printOrderMessage();
}

void countSeconds(int s) {
  for (var i = 1; i <= s; i++) {
    Future.delayed(Duration(seconds: i), () => print(i));
  }
}

// output
Awaiting user order...
1
2
Your order is: Large Latte
3
4

Isolate

源码注释对Isolate的说明如下:

independent workers that are similar to threads but don’t share memory, communicating only via messages.

Each isolate has its own memory heap and event-loop, ensuring that no isolate’s state is accessible from any other isolate

dart codes runs in isolate

Meta programming

Annotation

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key key }) : super(key: key);

  // @override to mark subclass override parent's method
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

@Deprecated(
  'Use CalendarDatePicker instead. '
  'This feature was deprecated after v1.15.3.'
)
class YearPicker extends StatefulWidget {
  ...
}

class Foo extends StatelessWidget {
  const Foo({
    Key key,
    @required Widget child,
  }) : super(key: key, child: child);
}

Reflection