近来,flutter
的热度在上升。flutter应用的主要开发语言是dart
, 所以,欲练flutter, 必先了解dart
.html
dart是由google开发的编程语言,可用于开发移动应用,桌面应用,h5应用,后端服务。java
本文将简单介绍dart
的语言特性、基础语法,以及在平常开发中很是实用的如何请求数据、如何处理异步、如何序列化与反序列化json等技能。jquery
文章比较长,熟悉的部分各位看官可快速浏览,文末也给出了小小福利,供你们参考。疏漏之处请见谅,错误之处请指正。webpack
dart是一门纯粹的面向对象语言,在dart中一切皆对象。git
1.toString();
与java不一样的是,java的变量和方法只能在类里边,而dart能够有不在类里的方法和变量。github
带有面向过程类语言的特色,好比像c。web
在dart中你能够显式的指定变量的类型,也能够不指定类型,由dart推断类型。指定类型的话编译器能够作静态类型检查。编程
在类型方面dart既有弱类型语言(像js)也有强类型(像java)的特色。json
对开发web应用来说:dart会被转译成js后端
对app、服务端、桌面应用讲:
小结
dart看起来是但愿融合多种主流语言的特性,在dart中可以看到多种语言的影子。
dart的程序执行入口是main函数,这跟c很像,main函数一个顶级函数。
返回值一般是void, 写为int, double等也不会报错,不过没有意义。
void main() { print('Hello, World!'); }
void main() { var var1 = '1'; print(var1); // 1 // var1 = 1; 这样是错误的, 声明且初始化会推断出类型,后面不能赋值其余类型的值 var var2; print(var2); // null var2 = 2; print(var2); // 2 var2 = '3'; print(var2); // 3 正确,声明时不赋值则等同于声明了一个动态类型(dynamic)变量 }
这种方式能够显式声明变量类型,以便作一些静态检查。
void main() { // 声明并初始化 int var1 = 1; print(var1); // 1 // 先声明后初始化 int var2; var2 = 1; print(var2); // 1 // var2 = '1'; 这是错误的,类型不匹配 }
这个dynamic意思是动态类型,这种变量能够随便给他赋什么类型的值
void main() { dynamic var1 = 1; var1 = '2'; // 动态类型能够赋任意类型的值 print(var1); // 2 }
在运行时肯定其值, 能够做为类的普通成员变量
必须在编译时肯定其值,只能做为类的静态成员变量,不能做为普通成员变量
class User { final int var1=1; static const int var2=2; // const int var3 = 3; 错误,不能做为类的普通成员变量 } void main() { int var1 = 1; int var2 = 2; final int const1 = 1; const int const2 = 2; // const1 = 2; 错误,不能再改变 // const2 = 1; 错误,不能再改变 // final int const3; 错误,必须被初始化 // const int const4; 错误,必须被初始化 final int const5 = var1 + var2; // const int cont6 = var1+var2; 错误,必须在编译时能够肯定值 }
void main() { int int1 = 1; double double1 = 1.3; num num1 = 2.0; // 如下引起运行时异常 // int1 = num1; // print(int1); print(int1.toString()); // 1 print(int1.isEven); // false print(int1.floor()); // 1 print(int1.isNaN); // false print(double1.toString()); // 1.3 print(double1.floor()); // 1 print(double1.isNaN); // false }
字面值表示法
void main() { String str1 = 'str1'; print(str1); // str1 String str2 = "str2"; print(str2); // str2 String str3 = '''a b c '''; print(str3); // a // b // c String str4 = "a\nb"; String str5 = r"a\nb"; print(str4); // a // b print(str5); // a\nb }
经常使用属性和方法
String 的属性和方法和其余语言相似,这里列举几个经常使用方法和属性
void main() { String str1 = "我和个人祖国"; String str2 = ",一刻也不能分割"; String str3 = str1 + str2; print(str1.length); // 6 print(str1.substring(2)); // 个人祖国 List<String> list = str3.split(","); print(list); // [我和个人祖国, 一刻也不能分割] print(str1.startsWith("我")); // true print(str1.indexOf("我")); // 0 print(str1.lastIndexOf("我")); // 2 print(str1.replaceAll("我", "你")); // 你和你的祖国 print("a".compareTo("b")); // -1 print(str1.contains('祖国')); // true }
布尔值,字面值只能是true或false, 不能为其余
void main() { bool bool1 = true; print(bool1); // true }
相似js中的数组, 长度可变。
void main() { List<int> list = [1, 3, 4]; print(list.length); // 3 print(list); // [1, 3, 4] list.add(5); print(list); // [1, 3, 4, 5] list.addAll([6, 7]); print(list); // [1, 3, 4, 5, 6, 7] print(list.removeLast()); // 7 print(list); // [1, 3, 4, 5, 6] list.removeAt(0); print(list); // [3, 4, 5, 6] list.remove(3); print(list); // [4, 5, 6] list.sort((item1, item2) { return item2 - item1; }); print(list); // [6, 5, 4] list.forEach((item) { print(item); }); // 6 // 5 // 4 print(list.indexOf(4)); // 2 list.clear(); print(list); // [] }
表示集合,无重复值,加剧复值不会报错,可是进不去,无序。
void main() { Set<int> set = {1, 3, 3, 4}; print(set); // {1, 3, 4} set.add(9); print(set); // {1, 3, 4, 9} print(set.contains(3)); // true print(set.length); // 4 set.remove(3); print(set); // {1, 4, 9} print(set.isEmpty); // true }
表示k-v结构数据,key不能重。
void main() { Map<String, int> map = { "a": 1, "b": 2, }; print(map); // {a: 1, b: 2} map['c'] = 3; print(map); // {a: 1, b: 2, c: 3} map.remove('a'); print(map); // {b: 2, c: 3} print(map.containsKey('a')); // false print(map.length); // 2 print(map.containsValue(3)); // true }
表示枚举,是一种特殊的类,官方文档没有将其归入到基础类型,这里为了方便理解放到这里。
enum Color { red, blue, yellow, } void main() { Color c = Color.red; print(Color.blue); // Color.blue print(Color.values); // [Color.red, Color.blue, Color.yellow] print(c); // Color.red }
void main() { int int1 = 1; // 错误,不能自动转换 // double double1= int1; // double 和int的相互转换 double double1 = int1.toDouble(); int1 = double1.toInt(); // num 是int 和 double的父类型, 能够直接转换 num num1 = double1; num1 = int1; // String 与double相互转换 String str1 = double1.toString(); double1 = double.parse(str1); // String 与int相互转换 String str2 = int1.toString(); int1 = int.parse(str2); // Map,Set,List用其toString方法能够转换为String Map map = {"a": 1, "b": 2}; List list = [1, 2, 3]; Set set = {1, 2, 3}; String str3 = map.toString(); list.toString(); set.toString(); }
操做符与其余语言基本相同,这里说明几个特殊的,其余简单罗列。
+、-、*、/、%、++、--
比较特殊的是~/
, 表示整除
void main() { int int1 = 3; print(int1 ~/ 2); // 1 }
==、!=、>、<、<=、>=
=、-=、~/=等等
比较特殊的是 变量名??=值
, 表示若是左边变量是null则赋值,不然不赋值
void main() { int a; a ??= 1; print(a); // 1 int b = 2; b ??= 1; print(b); // 2 }
!、||、&&
&、|、^、~expr、<<、>>
condition ? expr1 : expr2
这是比较特殊的运算符,相似于jquery中的连续的.操做, 上一次操做仍是返回当前对象,所以能够继续调用其方法。大多语言没有这种操做。
class User { say() { print("say"); } run() { print("run"); } } void main() { User user = User(); user..say()..run(); // say // run }
这是比较特殊的运算符,用于判断类型和强转,相似java里边(User) instance
这种
用于将某个类型转换为其余类型,必须知足父子关系,不然报错。
用于指定导入库的别名,至关于将库里导出来的东西挂到一个map上,能够解决重名问题。
用于判断一个对象是不是某个类的实例
is 的结果取反
import 'dart:math' as math; // 这里导入库时使用as指定了别名 class Person { String name; Person(); say() { print("person say $name"); } } class User extends Person { String password; User(this.password); run() { print("user run"); } } void main() { print(math.max(4, 5)); // 指定别名的库调用 User u = User("password"); print(u is User); // true u.name = "name"; u.run(); // user run Person p = u as Person; // 经过as将子类型强转为父类型 print(p is User); // true print(p is Person); // true print(p is List); // fasle p.say(); // person say name // p.run(); // 错误,已经被转换成了Person, 不能再调用User的方法 }
流程控制与其余语言相同,这里简单列举
assert表示断言,判断一个表达式的真假,若为假则抛出一个异常
dart支持模块化编程,能够导入dart内置库,其余三方库,也能够将本身的代码拆分红不一样模块。
import "dart:math";
须要在项目描述文件pubspec.yaml(相似js中的package.json或者java的pom.xml)中声明你要依赖的库,而后安装,最后导入。
pubspec.yaml
dependencies: http: ^0.12.0+2
安装
pub get
导入
import 'package:http/http.dart';
导入本地的文件后可使用其中定义的类,变量,函数等
import '../common/Utils.dart';
至关于将一个库导出的东西挂到一个对象,能够解决重名问题(上边讲as有提到)
import 'package:http/http.dart' as http;
能够只导入指定内容,或者不导入某些内容
import 'package:lib1/lib1.dart' show foo;
import 'package:lib2/lib2.dart' hide foo;
能够在运行时导入依赖,针对web应用,相似webpack的按需加载。
内容较多,此处略过,笔者也还没有研究,可参考官网
dart核心内库,默认导入,相似java的java.lang包。提供内置数据类型等。
异步支持库,大名鼎鼎的Future就在这里边。
复杂数学计算相关
json序列化、反序列化,字符集转换相关。
web应用相关api
io相关,与发请求相关的HttpClient在这里边
// 没有返回值的函数 import 'package:flutter/foundation.dart'; void printInfo(String name, int age) { print('$name:$age'); } // 有返回值的函数 String getInfo(String name, int age) { return '$name:$age'; } // 函数做为参数传递 void excuteFn(var function, String name, int age) { function(name, age); } // 返回一个函数 Function getPrintInfoFn(int age) { return (String name) { print('$name:$age'); }; } // 函数体只有一条语句,可使用箭头函数 void printInfo2(String name, int age) => print('$name:$age'); // 可使用命名参数 void printInfo3({String name, int age}) { print('$name:$age'); } // 位置参数和命名参数混用,指定默认值 void printInfo4(String name, { int age}) { print('$name:$age'); } // 定义一种函数类型 typedef PrintFn = void Function(String name, int age); // 将特定类型的函数做为参数传递 void excuteFn2(PrintFn function, String name, int age) { function(name, age); } class User { // 函数做为类的成员方法 say() { print("hello"); } } void main() { printInfo("张三", 18); print(getInfo('李四', 18)); User user = User(); user.say(); excuteFn(printInfo, "王五", 18); Function fn1 = getPrintInfoFn(19); fn1("小明"); fn1("小花"); printInfo("小张", 18); printInfo2('小李', 20); printInfo3(name: "小华", age: 21); // 命名参数函数调用方式 printInfo4("AA"); printInfo4("aa", age: 30); excuteFn2(printInfo, "王五", 18); }
// 我是一行注释
/** * 多行注释 * 多行注释 */
/// 文档注释 /// 文档注释
类使用class关键字定义
一个类能够由以下部分组成:
说明
// 使用class关键字定义类 class Rectangle { // 两个成员变量,变量名加_表示是私有的,只是一种约定,实质仍然能访问到 double _height; double _width; // 静态成员变量无需实例化便可经过类型访问 static String name = "长方形"; Rectangle(double width, double height) { this._width = width; this._height = height; } // 非命名构造方法若只是为成员变量赋值也能够这样写,与上边写法等价 // Rectangle(this._width, this._height); // 命名构造方法 Rectangle.fromMap(Map<String, double> map) { this._width = map['width']; this._height = map['height']; } static String getName() { // return this._height.toString(); 错误, 静态成员方法不能使用this,不能使用非静态成员变量 return name; } double getArea() { return _height * _width; } // double getArea 已存在,不能这样写,不支持重载 // double getArea(double width, double height) { // // } // get set方法 double get width { print("调用 get width"); return _width; } set width(double value) { print("调用set width"); _width = value; } double get height => _height; set height(double value) { _height = value; } } void main() { print(Rectangle.name); print(Rectangle.getName()); Rectangle rectangle = Rectangle.fromMap({'width': 10, 'height': 20}); print(rectangle.getArea()); rectangle.width = 1; print(rectangle.width); }
要点:
// 使用extends继承 class Square extends Rectangle { // 默认会访问父类的无参构造,若是父类没有无参构造须要这样显式指定调用哪一个构造 Square(double width):super(width, width); // 重写父类的方法 @override double getArea() { // 使用super访问父类的成员变量 print('重写父类方法: ${super._width}'); // 使用super访问父类的成员方法 return super.getArea(); } } void main() { // Square.getName(); 错误, 没法继承静态成员 // Square.name; 错误,没法继承静态成员 Square square = Square(10); print(square.getArea()); print(square._width); }
要点:
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
方法// 定义一个抽象类 abstract class Shape { // 抽象方法,没有方法体 double getArea(); double getPerimeter(); } class Rectangle extends Shape { double _height; double _width; Rectangle(double width, double height) { this._width = width; this._height = height; } Rectangle.fromMap(Map<String, double> map) { this._width = map['width']; this._height = map['height']; } // 必须重写抽象类的抽象方法,不然须要也要声明为抽象类 @override double getArea() { return _height * _width; } // 必须重写抽象类的抽象方法 @override double getPerimeter() { return _height + _width; } } void main() { // Shape shape = Shape(); 错误,抽象类不能被实例化 Shape shape = Rectangle(10, 2); // 这样是能够的 print(shape.getArea()); print(shape.getPerimeter()); }
关于接口,dart的设计比较奇怪。
官方文档对接口描述一下
每个类都显式的声明了一个包含其成员变量和成员方法及他实现的接口的接口。若是你想建立一个支持B类API的类,实现B类就行了。
也即定义一个类就至关于定义了一个接口,能够直接去实现他,也能够实现多个接口。extends只能继承一个类,implements能够实现多个类(抱歉恨不厚道的说了实现一个类,这里应为接口)。
在上一个例子中直接写成下边这样也是能够的
class Rectangle implements Shape
dart提供了对泛型的支持,泛型能够增长程序的通用性,并可提供一些静态检查,减小了一些强制类型转换的工做。
// 定义一个泛型类 class Cache<T> { Map<String, T> _cache = {}; T getByKey(String key) { return _cache[key]; } // 泛型方法 void setByKey(String key, T value) { _cache[key] = value; } } // 作类型约束,必须是num的子类 class NumCache<T extends num> { Map<String, T> _cache = {}; T getByKey(String key) { return _cache[key]; } void setByKey(String key, T value) { _cache[key] = value; } } void main() { Cache<String> cache = Cache(); cache.setByKey("a", "1"); // cache.setByKey("b", 2); 错误,类型不匹配 print(cache.getByKey("a") is String); // 类型被保持, 输出true // NumCache<String> numCache = NumCache(); 错误String不是num的子类 NumCache<int> numCache = NumCache(); numCache.setByKey('a', 1); }
要点:
// 抛出一个异常实例 void testThrow() { throw Exception("异常发生"); } // 能够抛出一个任意对象 void testThrow2() { throw "抛出字符串也能够"; } void testThrow3() { try { testThrow(); } catch(e) { // 捕获到异常不处理,再次向上抛出 rethrow; } } void main() { try { testThrow(); } catch(e, s) { // 这里e是指抛出的异常类,s是堆栈信息 print(e is Exception); // true print(s); } try { testThrow2(); } catch(e) { print(e is Exception); // false print(e); // 抛出字符串也能够 } testThrow3(); // 这里有异常抛出,不捕获也是能够经过静态检查的 // 下面的语句没法执行 print("未捕获异常以后还能执行吗,不能"); }
dart提供了对异步的支持,核心类包括Future和Stream,都位于dart:async
包下,这里介绍很是经常使用的Future.
Future被用来表示在将来才能知道其值或错误的一个类,和js中的Promise是相似的。
可使用Future的构造方法建立Future
这几个构造方法使用比较简单,一下是简单示例
// 使用非命名构造 Future<String> getFuture() { return Future<String>(() { return "hello"; }); } // Future.delayed Future<String> getFuture2() { return Future.delayed(Duration(seconds: 2), () { return "Future.delayed"; }); } // Future.error Future<String> getFuture3() { return Future.error("错误对象"); } // Future.microtask Future<String> getFuture4() { return Future.microtask(() { return "Future.microtask"; }); } // Future.sync Future<String> getFuture5() { return Future.sync(() { return "Future.sync"; }); } Future.value(getFuture4());
关于delayed, microtask,sync对比参见后文
import 'dart:async'; // 使用非命名构造 Future<String> getFuture() { return Future<String>(() { return "hello"; }); } void method1() { getFuture().then((res) { print(res); }).catchError((e) { print(e); }); print("后边的先执行了"); } void method2() async { String str = await getFuture(); print(str); print('按顺序执行'); } void main(){ method1(); method2(); // 输出 // 后边的先执行了 // hello // hello // 按顺序执行 }
经过测试得出:
关于这几者的比较,有兴趣的同窗能够自行研究。
经常使用的发网络请求的方法有两种:
使用步骤:
import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; // 这里封装了一个发get请求的方法,将返回的json字符串反序列化 Future<Map<String, dynamic>> httpGet( {@required String url, Map<String, dynamic> headers = const {}, Map<String, dynamic> params = const {}, int timeout = 10}) async { // 建立HttpClient实例 HttpClient client = HttpClient(); // 我在本地启动一个代理服务,这样方便抓包,没有代理服务能够去掉 client.findProxy = (uri) { // 若是须要过滤uri,能够手动判断 return "PROXY 127.0.0.1:8888"; }; // 超时时间设置 client.connectionTimeout = Duration(seconds: timeout); // 经过url(包括协议 主机 路径)建立Uri对象, 便于获取url的各个部分 Uri uri = Uri.parse(url); // 使用getUrl建立 HttpClientRequest对象 HttpClientRequest httpClientRequest = await client.getUrl(Uri( scheme: uri.scheme, host: uri.host, path: uri.path, queryParameters: params)); // 加上自定义header headers.forEach((key, value) { httpClientRequest.headers.add(key, value); }); // 调用HttpClientRequest的close获得HttpClientResponse HttpClientResponse httpClientResponse = await httpClientRequest.close(); // 使用特定字符集解码 var str = await httpClientResponse.transform(utf8.decoder).join(); // 将字符串序列化为Map, 这个将在后文详解 Map<String, dynamic> map = jsonDecode(str); // 关闭链接 client.close(force: false); return map; } void main() async { Map<String, dynamic> map = await httpGet( url: 'http://t.weather.sojson.com/api/weather/city/101030100', headers: <String, dynamic>{ 'custom_header': 'customheader', }, params: <String, dynamic>{ "version": 'v1', 'cityid': '101120201', 'city': '青岛' }); print(map); }
以上代码简单封装了一个发get请求的方法,get请求的参数是带在url上的,而post则不一样,post的参数一般是在body中,下面演示如何发一个post请求, 数据格式采用json, 表单格式的相似,修改application便可。
void main() async { HttpClient client = HttpClient(); // 本地搭了一个服务,用于测试 HttpClientRequest httpClientRequest = await client.postUrl(Uri.parse('http://127.0.0.1:3000/users/save')); // 关键步骤,告诉服务器请求参数格式 httpClientRequest.headers.contentType = new ContentType("application", "json", charset: "utf-8"); // 关键步骤,写数据到body httpClientRequest.write(jsonEncode({"name":'lily', 'age': 20})); HttpClientResponse httpClientResponse = await httpClientRequest.close(); var str = await httpClientResponse.transform(utf8.decoder).join(); Map<String, dynamic> map = jsonDecode(str); print(map); }
还有关于上传文件的内容,本文篇幅过长了,后面再写一个。
咱们发现使用原生的HttpClient来发请求仍是比较麻烦的,咱们可使用package:http/http.dart这个包能够大大简化开发。包安装能够参考上文模块化部分
import 'package:http/http.dart' as http; void main() async { // 演示post请求 var url = 'http://127.0.0.1:3000/users/save'; var response = await http.post(url, body: {'name': 'lily', 'age': '20'}); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); // 演示get请求 var response2 = await http.get('http://127.0.0.1:3000?a=1',); print(response2.body); }
能够发现使用package:http/http.dart
极其方便。
json是一种很是灵活的数据交换格式。
json序列化与反序列化通俗讲就是将 语言定义的非字符串类型(好比java中的实体类,dart中的Map)转换为json字符串,便于传输和存储,这是序列化;反序列化则是这个逆过程,方便操做数据。
在java中通常使用阿里的fastjson作json的序列化与反序列化;
在js使用JSON.stringify()、JSON.parse()作序列化与反序列化。
在dart中json序列化与反序列化有两种方式:
import 'dart:convert'; void main() async { String jsonString = '[{"name":"name1","age":1},{"name":"name2","age":2}]'; List list = jsonDecode(jsonString); list.forEach((item) { print(item['name']); }); String jsonString2 = jsonEncode(list); print(jsonString2); }
import 'dart:convert'; class User { String name; int age; User(this.name, this.age); // 将map转为User User.fromJson(Map<String, dynamic> json) { name = json['name']; age = json['age']; } // 将User转成map Map<String, dynamic> toJson() => { 'name': name, 'age': age, }; } void main() async { List<User> list = List(); list.add(User('name1', 1)); list.add(User('name2', 2)); // 这里会自动调用toJson String str = jsonEncode(list); print(str.runtimeType); // String print(str); // [{"name":"name1","age":1},{"name":"name2","age":2}] // 这里但愿反序列化为List<User> 可是还没有找到方法, // 网上文档基本就是翻译翻译官方文档,官网也没找到解决方案 List userList = jsonDecode(str); userList.forEach((item) { // 这里不得不手动强转 User u = User.fromJson(item); print(u.name); }); // name1 // name2 }
由于以前写js的缘由,更倾向使用第一种方式,由于不用写实体类;并且第二种方案即便转换成特定类型,除了可使用.直接访问到属性,也没太大意义。js没有这种定义实体类的习惯,照样玩耍。
本觉得三言两语就能够说完dart, 最后发现居然写了1300多行,并且还省略了一些琐碎的东西(好比dart的集合框架)。
祝你们flutter入坑快乐!~
最后献上文章开始提到的小福利。图片较小,不便查看,可下载源文件,该思惟导图源文件下载地址参见思惟导图, 若是能够的话,顺手帮忙点个星星啊☺