从 Flutter
发布到现在, 越来越多人开始尝试使用 Dart
来完成部分功能;
Dart
的前生今世一类的话题,这里就不展开了,只要知道 Flutter
是 google
推出跨平台方案就好,至少不必担心Dart性能
与生态问题
(如果google
真的想支持的话).
先看一下最终的效果显示:
Dart 语言
根据最新的语言排行榜...不好意思,没找到Dart,就目前来看,在前端没拼过JS,在其他领域好像也就目前 Flutter 在支持,不过相信随着Flutter盛行,情况会越来越好的.
Dart 语言有两种模式:JIT
,AOT
;这个和之前 Android/Java 情况很相似,JVM 一直采用解释执行的方式, 直到 ART
出现,Android端才开始 AOT
模式;
JIT
比较方便,不需要提前编译,AOT
运行时比较快,而 Flutter 则在 debug 和 release 时,分别适用不同模式,保证开发效率以及应用流畅.
说了那么多,还没有到正题,接下来,我们来看下 Dart
中比较 "特殊" 的特性:泛型
Dart泛型 与 其他泛型
首先来看一段关于泛型的 Dart
代码(排列组合:):
List<int> a = <int>[];
List<num> b = <num>[];
List<dynamic> c = <dynamic>[];
List<Object> d = <Object>[];
List e = [];
print(a.runtimeType == b.runtimeType); // false
print(a.runtimeType == c.runtimeType); // false
print(a.runtimeType == d.runtimeType); // false
print(a.runtimeType == e.runtimeType); // false
print(b.runtimeType == c.runtimeType); // false
print(b.runtimeType == d.runtimeType); // false
print(b.runtimeType == e.runtimeType); // false
print(c.runtimeType == d.runtimeType); // false
print(c.runtimeType == e.runtimeType); // true
print(d.runtimeType == e.runtimeType); // false
虽然都是 List
类型,但泛型不同,获取到的runtimeType
都不同,由此可知:
Dart中泛型运行时依旧存在.
相比之下,Java中的泛型就比较随意了:
List<Integer> a = new ArrayList<>();
List<Object> b = new ArrayList<>();
List c = new ArrayList<>();
java泛型在运行时,会被擦除,所以上面的a,b,c判断时都属于List
类型.
再回到前面 Dart 部分,可以看到只有变量c
和e
的运行时类型相同,并且如果使用编译器的话,就可以发现:
List c = <dynamic>[];
List<dynamic>
其实就是 List
,两者是一样的.
在知道了dart的泛型特性后,不禁会思考:List<int>
和List
,或者说和List<dynamic>
是什么关系?和List呢?
还是开始的dart示例
,在后面我们添加几行判断(变量b的类型为List\<num\>
)
print(b is List<int>); // false
print(b is List<num>); // true
print(b is List<Comparable>); // true
print(b is List); // true
Comparable
是num类实现的抽象类
,根据验证结果可知:
dart泛型List时,泛型为父类的List是泛型为子类List的父类
这个有点绕,理解这个意思就行;其实看到这里,如果之前有过java开发经验的话,就会发现这个其实和java中的数组很相似,参考Java中的数组和List集合以及类型强转
这个图说明了java中数组之间是存在继承关系的,并且和实际类之间的继承关系相似.
接下来我们看一下泛型类中泛型的关系;
void main() {
var aa = AA.name("123");
aa.test();
}
class AA<T> {
T field;
AA.name(this.field);
void test() {
print("".runtimeType); //String
print(T); //String
print(field.runtimeType == T); //true
}
}
相信大家对这段代码以及结果都没有什么疑问,泛型在传递之后,到了类AA
中仍然为String
类型,但下面这段代码就不同了:
void main() {
var aa = AA.name([]);
aa.test();
}
class AA<T> {
T field;
AA.name(this.field);
void test() {
print([].runtimeType); //List<dynamic>
print(T); //List<dynamic>
print(field.runtimeType == T); //false
}
}
我们在创建类时,指定泛型为List
(或者说是List<dynamic>
),然后在AA
中判断,发现泛型T
和field的运行时类型
不同,虽然toString
是一样的,但如果打印hashCode
可以发现,对应着两个不同的Type
.
加入我们传入一个map
类型,结果更是不如人意
void main() {
var aa = AA.name({"ss":1});
aa.test();
}
class AA<T> {
T field;
AA.name(this.field);
void test() {
print(field.runtimeType); // _InternalLinkedHashMap<String, int>
print(T); // Map<String, int>
print(field.runtimeType == T); // false
}
}
实际类型是泛型的一个实现类;
那假如传入一个自定义泛型类呢?
void main() {
var aa = AA.name(BB<num>());
aa.test();
}
class AA<T> {
T field;
AA.name(this.field);
void test() {
print(field.runtimeType); // BB<num>
print(T); // BB<num>
print(field.runtimeType == T); // true
}
}
class BB<T> {}
还好,自定义泛型类时,运行时runtimeType
与泛型Type
是相同的
关于dart泛型的探讨只进行到这里,大概总结一下:
- 泛型在
运行时保留
- List泛型之间存在关系,可以通过
is
进行判断,如field is List<num>
- List泛型即便
toString
方法返回相同的值,也可能是两个不同的Type
有了上面的论断,接下来进入正题
dart-bean
bean只是我们通用的一种class类的说明,用于表明数据模型.
这里还是先看一下其他语言中的bean;
先看java中通常的做法:
public class TestBean {
private String username;
private boolean isVip;
public String getUsername() {
return username;
}
public TestBean setUsername(String username) {
this.username = username;
return this;
}
public boolean isVip() {
return isVip;
}
public TestBean setVip(boolean vip) {
isVip = vip;
return this;
}
}
kotlin中表示形式会简单的多:
data class TestBean(
var username: String? = null,
var isVip: Boolean = false
)
其实bean的代码提现不是关键,重要的地方在于,如何方便的为bean赋值
和操作bean
,因为 JVM 语言可以进行反射,因此,在获取到后台传入的json格式数据时,我们可以很方便的通过一些库完成自动赋值操作:
var bean = Gson().fromJson("{\"username\":\"www\",\"isVip\":false}",TestBean::class.java)
在dart中,bean的代码表现形式和其他语言基本相同,都是在单独的class中声明成员变量:
class TestBean {
String username;
bool isVip;
}
如果只是单纯的dart项目,我们仍然可以通过镜像
功能来为bean赋值:
import 'dart:mirrors';
class TestBean {
String username;
bool isVip;
}
void main() {
Map<String, dynamic> json = {
"username": "www",
"isVip": false,
};
var class_bean = reflectClass(TestBean);
var obj = class_bean.newInstance(Symbol.empty, []).reflectee;
var instance_bean = reflect(obj);
class_bean.declarations.forEach((key, value) {
if (value is VariableMirror) {
var key_string = RegExp("^Symbol\\(\"(.+?)\"\\)\$")
.firstMatch(key.toString())
.group(1);
instance_bean.setField(key, json[key_string]);
}
});
print("${obj.username} ${obj.isVip}"); // www false
}
虽然dart的镜像功能看起来很是"不爽",但确实是有这个实现的.
不过有些不幸的是,在Flutter中,dart不允许使用镜像功能,具体原因可以参考Flutter实战中提到一段话:
很多人可能会问Flutter中有没有像Java开发中的Gson/Jackson一样的Json序列化类库?答案是没有!因为这样的库需要使用运行时反射,这在Flutter中是禁用的。运行时反射会干扰Dart的tree shaking,使用tree shaking,可以在release版中“去除”未使用的代码,这可以显著优化应用程序的大小。由于反射会默认应用到所有代码,因此tree shaking会很难工作,因为在启用反射时很难知道哪些代码未被使用,因此冗余代码很难剥离,所以Flutter中禁用了Dart的反射功能,而正因如此也就无法实现动态转化Model的功能。
因此,如果想在Flutter中实现bean,就需要其他的一些技巧.
flutter dart-bean
目前大多数情况下,都是在bean类中添加命名构造函数,然后通过工具自动生成部分代码帮助解析和构建bean.
例如上面的bean类会对应生成如下代码:
class Response {
String username;
bool isVip;
Response.fromJsonMap(Map<String, dynamic> map)
: username = map["username"],
isVip = map["isVip"];
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['username'] = username;
data['isVip'] = isVip;
return data;
}
}
即多添加两部分内容:
- 命名构造函数:
Response.fromJsonMap
,参数为Map<String, dynamic>
类型,用于由 json 生成 bean
;为json反序列化 toJson
方法,用于将对象序列化为json字符串
通过添加两个方法,可以实现json串及bean的相互转换.不过如果每次都得手写那会很麻烦,因为可能需要的字段特别多,一般这种情况都需要用工具来完成.
官方也提供了相应的库:json_serializable
,不过如果我们习惯了 GsonFormat 的快捷,不免会对这种方法感到不满,并且目前针对 dart 生成bean有很多不得不考虑的问题,比如泛型,嵌套内部类等等,这些情况下,我们无法通过一般的命令行或者工具直接生成.
这里我改写了 ,把其中的java类型部分修改为了dart类型,同时修改了生成方法:
插件地址在jetbrains-plugins-JsonToDartBean;
Package包地址在json_holder_impl;
详细的使用教程请参考github:json-to-dart-bean;
效果大概是这样:
使用方法很简单,只要按照教程导入包,然后安装插件就好,这里进行几点说明:
1. 插件功能
因为插件修改自GsonFormat
,因此命名规范等都沿用了之前的定义,功能主要包括:
-
在dart文件中,使用快捷键
alt + d
,会根据粘贴的json 生成 bean -
json 支持创建时修改类型,这点和 GsonFormat 相同
-
json 支持嵌套内部类,如果内部类不想创建新的,想使用已存在的某个class,也可以通过创建时修改类型来达到目的:
-
假如默认要生成的类型是这样:
-
我们可以修改类型或者去掉勾选,变成这样:
-
-
支持两层List连续嵌套,比如这种情况,会生成List<List>类型的变量:
{ "values":[ [ 1,2,4 ] ] }
2. 库特点
通过该插件简单的生成较短的代码进行查看:
//******************************************************************
//**************************** Generate By JsonToDartBean **********
//**************************** Thu Jun 06 18:33:38 CST 2019 **********
//******************************************************************
import 'package:json_holder_impl/json_holder_impl.dart';
class FirstBean with JsonHolderImpl<FirstBean> {
/// [key : value] => [name : www]
String get name => getValue("name");
set name(String value) => setValue("name", value);
FirstBean.fromJson([Map<String, dynamic> json]) {
fromJson(json);
}
@override
JsonHolderImpl<FirstBean> provideCreator(Map<String, dynamic> json) {
return FirstBean.fromJson(json);
}
@override
List<FirstBean> provideListCreator() {
return <FirstBean>[];
}
@override
List<List<FirstBean>> provideListListCreator() {
return <List<FirstBean>>[];
}
}
可以看到类中其实没有生成任何的成员域,所有变量都同等的被get 和 set
方法替代,这种方式相对于目前普遍的bean构建方法有一个好处,那就是变量值只有在真正调用的时候才会去解析,能在一定程度上加快运行速度(同样在父类中处理了类型转换,并且可自定义,具体自定义方式参考上面提到的github教程即可).
代码中只看到了fromJson
方法,toJson
方法 定义在了父类JsonHolderImpl
中,直接调用即可.
注: 代码不可用于商业用途 注: 如有 插件 GsonFormat 涉及到的版权问题,请及时告知