Dart 入门

986 阅读9分钟

Dart 介绍

Dart 在 2011 年由 Google 推出,目标是替代 JavaScript 在 Web 上的应用,Chrome 曾内置 Dart VM,但 Dart 并没有流行起来。

2015 年 Google 推出 Flutter,采用 Dart 作为开发语言。为什么 Flutter 使用 Dart 而不是另一种“更好”的语言,既有技术的考量,也有版权风险的考虑。

Dart 是一种静态类型语言,同时支持 JIT 和 AOT,开发 Flutter 时可以提供良好的热重载,编译后运行效率也比较高。

Dart 语法和其他静态类型语言很想,类似 Java,C# 或 TypeScript。

资源网站

学习 Dart 和 Flutter 建议直接看官网,考虑到网络访问速度,在国内也可以使用官网中文版。下面列举官网和对应的中文站。

阅读官网文档,基本就可以入门了,继续学习,可以参考 Github awesome-flutter

Dart-SDK 安装

接下来,我们按照官网教程,安装 Dart-SDK。

先下载 Dart-SDK zip 文件,最新地址

下载后解压到本地,注意不要放到 C:\Program Files 等需要管理员权限的目录。然后将 dart-sdk\bin 的本地路径添加到系统路径中。

执行 dart -h 可以看到以下输出,这是用到了 dart cli 工具,后文会详细介绍。

VSCode 插件安装和环境配置

虽然 Google 官方建议使用 Android Studio 来开发,但是 Android Studio 启动很慢,体验不太好。对前端开发来说,还是 VSCode 比较熟悉。

使用 VSCode 开发 Dart,需要安装 Dart 插件。

安装之后,新建一个空目录,新建文件 main.dart,输入以下代码,然后按 F5 就会自动创建 launch.json 文件并运行。

void main() {
  print('Hello, Live Dart!');
}

如果只是入门阶段想先试试 Dart 的语法,可以在 launch.json 文件中,可以将 "program": "main.dart" 改为 "program": "workspaceRoot/{workspaceRoot}/{relativeFile}",这样就可以在任意 dart 文件中按 F5 开始调试了。

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Dart & Flutter",
            "request": "launch",
            "type": "dart",
            "program": "${workspaceRoot}/${relativeFile}"
        }
    ]
}

Dart 语法与类型

Dart 和 C/C++ 类似,必须要有一个 main 函数作为入口,这和 JS 不一样。Dart 有以下内置类型

  • Numbers (int, double)
  • Strings (String)
  • Booleans (bool)
  • Lists (对应 JS 中的 Array)
  • Sets (Set)
  • Maps (Map)
  • Runes (用于在 Characters API 中进行字符替换,实际开发中很少用到)
  • Symbols (JS 中也有 Symbol)
  • The value null (Null)

和 JS 类似,在Dart 语言中,一切皆是对象,Object 是所有类型的基类。所有对象都是 toString() 等 Object 内的基础方法。

但和 JS 不同的是,Dart 是强类型的语言,同时又具体类型推断。

相比 JS,Dart 还有空类型安全,变量默认是不允许为空的,如果需要允许为空,必须显式标明,比如 int? a 在类型后面加个问号,代表变量允许为空,这和 C# 类似。

在 Dart 中可以使用 int double String bool List Set Map 定义变量,还可以使用 var const final dynamic Object 来定义变量。

var const final dynamic Object

上文说除了可以用内置类型或自定义类型来定义变量之外,还可以用以上关键字来定义。那各自有什么区别呢?

  var a = 1;
  a = '2';  // 此处会报错,因为 a 初始化时已经赋值为 1,是 int 类型,不能再赋值字符串 '2'
  final b = 'final';
  // b = 'change';

  const c = 'const';
  // c = 'd';

  Object d = 'Object';
  d = 123;
  d = [1];

  Object e = DemoA();
  // e.say();

  dynamic f = DemoA();
  f.say();

var dynamic Object 的区别

var 是 Dart 建议使用的方式,var 定义变量,会进行类型推断。如果定义类型的时候有初始值,那就是静态类型,不能重复赋值不同类型的值。如果定义类型时没有初始值,那就是动态类型,可以重复赋值不同类型的值。

dynamic 类似 JS 中的 any,是显式定义一个动态类型,就是明确告诉编译器不用做类型检查,可以重复赋值不同类型的值。

Object 则是因为所有类型的基类都是 Object,所以可以用 Object 来定义任意类型的变量。

final 和 const 的区别

final 是只能赋值一次的,运行时是不可变的,而 const 则是定义就需要给定常量,编译时就是不可变的。

const 类型的变量,同时也是 final 类型。

字符串格式化

Dart 中的函数、运算符、流程控制、异常和泛型等基础语法,和 JS/TS 没什么太大区别,不过多描述。 我们谈一谈字符串格式化,Dart 在这方面更加简单。

在 JS 中,字符串格式,需要用反引号,加上 $ 和花括号。

var str = `I like ${fruit}`

但是在 Dart 中,不需要用反引号,直接使用 $ 即可,在不会引起歧义的情况下,也不需要花括号。 如果不需要转义呢?可以在字符串前面增加一个 r,代表 raw 纯文本。

另外,Dart 中还可以直接拼接多个字符串,不需要使用 +,也可以像 Python 一样,使用三个单引号定义多行文字。

  String s1 = '这些文字'
      '其实'
      '是一行';
  print(s1);

  String s2 = '''
三个引号
就是多行
文字
''';
  print(s2);

  String s3 = '拼接简单 $s1'; // 不必花括号
  print(s3);

  String fruit = 'Apple';
  String s4 = 'I have many ${fruit}s'; // 使用花括号避免歧义
  print(s4);

  String s5 = r'I have many ${fruit}s'; // 前面加 r 代表 raw
  print(s5);

Dart 中类的概念和 TS 差异不大,有一些有趣的特性。

构造函数中使用 this

在其他的语言中,我们经常在构造函数中写 this.val = val 这样的代码,显得比较繁琐

class Demo {
  var val;
  Demo(val){
    this.val = val
  }
}

而 Dart 提供了方便地语法糖,直接在参数列表中使用 this,有些时候构造函数甚至不需要函数体定义了。

class Person {
  double height;
  Person(this.height);

  say() {
    print('I am person');
  }
}

接口和实现

在 Dart 中没有 interface 关键字,也就是说没有接口的定义。但是 Dart 中的类都具备一个隐式接口。比如说,定义一个 Person 类,那同时也就具备了一个 Person 接口。

如此一来,不需要像其他语言那样,区分抽象类和接口了。如果以后你去面试,遇到面试官问你二者的区别,你可以说 Dart 中没有这个区别,显出自己知识面广了。

当一个类 extends Person,代表继承父类,若 implements Person,则代表实现接口。

同时,一个类可以实现多个接口,用逗号隔开即可。

class Boy extends Person {
  Boy(double height) : super(height);

  @override
  say() {
    print('i am boy, i am $height');
  }

  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: '
        '${invocation.memberName}');
  }
}

abstract class Traffic {
  move();
}

// 可以实现多个接口
class Robot implements Person, Traffic {
  double height = 2;

  @override
  say() {
    print('i am robot');
  }

  @override
  move() {
    print('i can move');
  }
}

noSuchMethod

在 Dart 类中,可以定义 noSuchMethod 方法来提供安全性。在 JS 中,调用类不存在的方法,常常会造成 Bug。

Dart 是静态语言,一般情况下,变量定义是强类型的,noSuchMethod 用不上,因为在编译阶段就能发现调用了不存在的方法。

但是,别忘了 Dart 也支持动态类型,比如 var 初始化未赋值,或者显示使用了 dynamic,那编译阶段就不会作任何检查。

这个时候,若调用不存在的函数,则会统一执行 noSuchMethod,可以添加日志,同时避免崩溃。

对比下面两个图片,使用 dynamci 什么 boy,并故意写错 say 方法,结果发现 say2 和 jump 都成了未定义方法。

say2 是故意写错的,符合预期,jump 为什么也成了未定义方法呢?因为 jump 是 Person 的扩展方法。

扩展方法

我之前在 C# 中了解过扩展方法的概念,觉得特别好用。扩展方法的语法,很符合“开闭原则”,很适合做扩展、解耦。

Dart 中定义扩展方法的方式如下:

// extendFunc.dart 使用 extension XXXExtend on XXX 定义扩展方法
import 'class.dart';

extension PersonExtend on Person {
  void jump() {
    print('i jump');
  }
}

// main.dart  引入 extendFunc.dart 后,扩展方法自动生效
import 'extendFunc.dart';

// boy 是 Person 的示例,可直接调用 jump
boy.jump();

扩展方法不可用于 dynamic 类型。

如果扩展方法之间有命名冲突,可以用 show hide 来显示隐藏,或者用 as 来设置别名,直接用别名调用对应的扩展方法。 不过,开发中遇到命名冲突的情况应该比较少。

Mixins

Mixin 是 Dart 中一个比较重要的特性,可以通过 Mixin 扩展类。通过 mixin 关键字定义扩展类,在其他类中通过 with 关键字应用 mixin。一个类可以添加多个 mixin,用逗号隔开即可,就像可以实现多个接口一样。

同时,和扩展方法不同,mixin 添加的方法,在 dynamic 类型中也可以使用。

// 以 mixin 替代 class
mixin Player {
  playFootball() {
    print('i like play football');
  }
}

// 定义 Boy 时使用 with,把 Player 添加进 Boy
class Boy extends Person with Player {
  Boy(double height) : super(height);

  @override
  say() {
    print('i am boy, i am $height');
  }
}

级联

Dart 内置核心库

就像 Nodejs 内置的 path,fs 等基础库一样,Dart-SDK 也提供了一些内置库,具体见Library | Dart

Dart 内置核心库 命名都是以 dart: 开头。dart:core 不需要引入,是默认引入的。

引入其他 dart 内置库,推荐放在最上面,然后依次引入第三方包、项目内文件。

import 'dart:math';
import 'package:logging/logging.dart';

import 'src/extenFunc.dart'

Dart 命令行工具

dart-sdk 除了作为 dart 的运行环境,同时也提供 cli 工具,下图是 dart-sdk 提供的命令。

我们常用的有

  • dart create xxx 创建项目
  • dart run xxx.dart 运行项目
  • dart pub add xxx 安装包
  • dart pub get xxx 下载包

Dart 包管理和 pubspec 文件

dart 和 flutter 都使用 pub.dev 进行包管理,类似 nodejs 的 npm 同样支持各种国内镜像。

每个包或项目都需要一个 pubspec.yaml 文件,对应 nodejs 中的 package.json。pubspec.yaml 文件的具体说明参考官方文档Pubspec 文件 | Dart

下面是官方 logging 包的 pubspec.yaml。

和 nodejs 中的 package.json 类似,pubspec.yaml 也包含 dependencies 和 dev_dependencies,还提供一个 dependency_overrides,可以覆盖第三方包及其依赖包,通常是改为项目中代码。

同样类似 npm,我们也可以使用 dart pub publish 发布自定义包。

name: logging
version: 1.1.0

description: >-
  Provides APIs for debugging and error logging, similar to loggers in other
  languages, such as the Closure JS Logger and java.util.logging.Logger.
repository: https://github.com/dart-lang/logging

environment:
  sdk: ">=2.12.0 <3.0.0"

dev_dependencies:
  lints: ^1.0.0
  test: ^1.16.0

总结

以上就是我学习 Dart 的一些入门经验。Dart 本身并不复杂,而且还有一些很有意思的新特性,熟悉 TS 的话,很容易上手。

更进一步学习 Dart,可以去看看官方库查找优秀的包,并在 Github 上查看源码学习。

更重要的是,通过实际开发 Flutter 来学习。