flutter前瞻(一)—— 初识Dart
作者:青鸾
引子
就在几天前,Google团队在一场线上活动中,正式宣布了Flutter2的发布,对前端而言,这无疑是一个重磅炸弹!众所周知,现在APP的实现方案,无非以下几种:
- React Native
- Uni App
- 原生
- weex
- flutter
笔者从网上找了一张简单的对比图,不难发现,Flutter在性能方面脱颖而出,当然,伴随着的,也是很高的开发成本,特别是对没有接触过前端或Java的同学来讲,学习成本是巨大的。那么,为什么还要选择flutter,这里其实涉及到了一个非常重要的地方——跨平台。以原生APP为例,以往的方案,在开发阶段,便需要Andirod、IOS分别开发一套代码,再到开发完成,两套代码的维护,这成本无疑是巨大的,并且这还只是移动端。前几天发布的flutter2号称实现了真正的跨平台方案,Andriod、IOS、Windows、MacOs,甚至还有Linux系统,五端适配,只需要一套代码,单从跨平台层面,flutter无疑是最优先的选择,没有之一。另外,通过近几年一些社区的变化,不难发现,一些很常见的APP都已经用到了flutter这一框架,比如闲鱼、微信、百度网盘、快手等等,这些变化,也从侧面体现了flutter其实是未来APP开发选择的趋势。flutter这一框架,选择了Dart作为他的开发语言,欲学flutter,必先学Dart,本文带来了Dart的基础入门,大家读完一定会有所收获!
Dart语言的前世今生
该不该学Dart
如果你在3年前问这个问题,收到答案肯定是不应该学,因为2018年Dart被评为了最不应该学习语言的榜首。如果你现在问这个问题,我建议学习Dart,因为Dart被评为开发者最希望学习语言的榜首。
是的,没错,天差地别,这门当初号称要取代JavaScript的语言,虽然最终没能取代JavaScript(JavaScript牛逼!),但是同样取得了巨大成功,flutter在问世之初,便将Dart定义为了其底层语言,另外,谷歌最赚钱的广告业务——Adwords,也已经采用了Dart进行开发,由此可见,没有弱的语言,只是时机未到!
2011年10月10日,Google 发布了Dart语言,距今已经10年,先后经历了1.0、2.0等数个版本的迭代,经过了多款APP的重重考验,已经是一门真正成熟的语言。如果你是一名前端,或者有过Java开发的经验,那么其实Dart是很容易上手的,特别是Java同学,因为不说多的,单从语法,相似度就达到了70%,还在等什么,快来学习吧!
注意事项
- Dart语言语法规范非常严格,不同于JS,Dart中每句代码结束必须以“ ; ”结尾,否则就算语法错误
- main函数为页面的入口函数
- Dart中没有function关键字
- Dart中没有interface关键字
- Dart中的对象的key值不会被自动识别为String类型
练习方式
一、使用DartPad在线练习 —— DartPad传送门
二、建议使用开发工具:Visual Studio Code
-
安装Dart插件
-
安装插件Code runner
Code runner插件允许我们执行当前的文件代码,并可以在控制台查看输出
Hello World
作为重新认识新语言的常规套路,每个程序员都不可避免的需要经历Hello World的历练,今天学习Dart语言也不例外,所以我们先来用Dart语言,输出Hello World:
void main() {
// print类似于js中的console.log,用于打印输出
print('Hello World !');
}
类型
JavaScript作为一门很优秀的前端语言,为数不多被人诟病的,是它的弱类型,于是诞生了TypeScript。而Dart天生就是一门强类型语言。 Dart中的类型大致可以分为以下几种:
数据类型
字符串类型(String)
String str = 'str';
整型
int num = 0;
int num1 = 1.1; // 报错,整形无法存浮点型
浮点型
double num = 1.0;
double num1 = 1; // 浮点型可以存整型
布尔型
bool isShow = false;
// 在Dart中,只有bool类型可以作为判断条件,不同于JS
String str = '1';
if (str) { // 这种写法是错误的
print(true);
}
数组型(List, Set, Map)
List arr = [11, 22, 33, 44];
var s1 = new Set(arr);//去重
s1.addAll(arr);
var person = {'name': '小明', 'age': 18} //map只会保留一个相同的key值
函数
fn() {
return 1;
}
fn();
类
class Fn {
String name;
int age;
// 构造方法
Fn(name, age) {
this.name = name;
this.age = age;
}
}
变量
var 当不确定类型时,可以使用var声明,可以只声明不赋值(默认赋值为null),var声明的变量会自动进行简单的类型推导,如果在声明的时候已经赋值,则后续赋值时,必须保持类型统一
var a;
print(a); // null
var b = [];
b = '1'; // 会报错,因为类型发生了变化
dynamic 使用dynamic声明变量,表示关闭了类型检查
dynamic x= 'str';
x.foo(); // 不会报错
通过类型声明
//字符串
String str = "我是一个字符串";
//数字类型
int num = 1;
//Boolean类型
bool isShow = false;
//double类型
double price = 1.2;
// Map类型(Map对应着JS中的对象,与JS中的Map不同)
// 下面两种方式都可以创建Map对象
Map obj = {'a': 1};
print(obj); // {a: 1}
var obj1 = new Map();
obj1['a'] = 1;
print(obj1); // {a: 1}
// List集合类型
// 下面两种方式都可以创建List对象
List arr = ['a', 'b'];
var arr2 = new List();
arr2.add('a')
const 用于修饰常量,声明了必须赋值,且赋值后不可更改
const PI = 888;
// PI = 886 //错误,常量不可以被修改
print(PI);
final final也用于修饰常量,不同的是,final可以开始不赋值,但只能赋一次,
final PI = 888;
// PI = 886 //错误,常量不可以被修改
print(PI);
final与const对比 final不仅有const的编译时常量的特性,最重要的是它是运行时常量,并且final是惰性初始化,即在运行时第一次使用前才初始化。直接赋值用final或const均可,但如果从方法中获取,只能用final。
final time = new DateTime.now(); //当前时间
print(a);
// const time2 = new DateTime.now(); //报错
函数
Dart中的函数没有所谓function
可选参数
可选参数使用[ ]进行包裹,当我们不传时,默认返回null
List fn(String a, String b, [String c]) {
return [a, b, c];
}
print(fn('a', 'b')); // [a, b, null]
匿名函数
Dart中的匿名函数写法因为没有function关键字的原因,所以写法比较奇怪,() { } 就表示这是一个匿名函数
List arr = [1,2,3,4,5];
arr.forEach((n) {
print(n);
});
箭头函数
匿名函数的一种简写方式,适用于当表达式只有一句时
List arr = [1,2,3,4,5];
arr.forEach((n) => print(n));
// 注意:与JS不同,Dart中箭头函数即使只有一个参数,()也不能省略
闭包
与JS基本相同
fn(int n) {
return () => n + 1;
};
print(fn(5)()); // 6
参数默认值
void main() {
test_param(n1,[s1 = 12]) {
print(n1);
print(s1);
}
test_param(1);
// 1
// 12
}
泛型
此处简单介绍一下泛型的应用场景以及为什么要使用泛型 什么是泛型——泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持。
String fn(String v) {
return v;
};
// 当我们想给fn传入一个数字并且返回一个数字类型的时候,fn便无法满足需求
print(fn('str')); // str
// 重新写一个int类型的fn1,可以满足我们的需求,但是也造成了代码冗余
int fn1(int a) {
return a;
};
print(fn1(1));
当然我们可以
fn2(x) {
return x;
};
print(fn2(1)); // 1
print(fn2('str')); // str
这样不指定类型虽然解决了问题,但是却放弃了类型检查,这不是我们想要的结果,这时,便需要用到泛型
fn<T>(T v) {
return v;
};
// 此处的类型亦可省略,Dart会自动进行简单的类型推断
print(fn<int>(1));
print(fn<String>('str'));
我们在定义函数fn的时候,为他定义了类型,在使用的时候,传入对应类型,这样,就可以达到我们想要的效果,熟悉TS的朋友应该觉得这个很容易。
运算符
算术运算符
int a = 3;
int b = 2;
print(a + b); //加 5
print(a - b); //减 1
print(a * b); //乘 6
print(a / b); //除 1.5
print(a % b); //取余 1
print(a ~/ b); //取整 1
关系运算符
int a = 3;
int b = 2;
print(a == b); //等于
print(a != b); //不等于
print(a > b); //大于
print(a < b); //小于
print(a >= b); //大于等于
print(a <= b); //小于等于
逻辑运算符
取反
bool flag = false ;
print(!flag); //结果为true
&& 并且
bool flag1 = true;
bool flag2 = false;
print(flag1 && flag2); //结果为false
| | 或者
bool flag1 = true;
bool flag2 = false;
print(flag2 || flag1); //结果为true
赋值运算符
= 为直接赋值
int m = 11;
??= 赋默认值
var a = 1;
a ??= 10;
print(a); // 1
// 表示判断b是否为null,如果为null,则执行赋值,否则不执行操作
var b;
b ??= 10;
print(b); // 10
类型转换
字符串转整型
String str = '123';
var num = int.parse(str);
print(num is int); //结果为true
字符串转浮点
String str = '123';
var num = double.parse(str);
print(num is double); //结果为true
数字转字符串
var num = 10;
var str1 = num.toString();
print(str1 is String); //结果为true
操作符
. .操作符,与JQuery中的链式调用有异曲同工之妙
class Girl {
String name = '小红';
int age = 18;
Girl(this.name, this.age);
String getInfo() {
return this.name + '今年' + this.age.toString() + '岁';
}
}
void main() {
Girl P = new Girl('小李', 18);
P
..name = '1'
..age = 10;
// 使用..操作符时,直到结尾才算一句代码
// 以上代码等同于
// P.name = '1';
// P.age = 10;
print(P.name); // '1'
}
?.操作符
void main() {
Girl P = new Girl('小李', 18);
P
..name = '1'
..age = 10;
P?.getInfo(); // 表示当P中存在getInfo时,才会执行,否则不执行
// 以上代码等同于
// if (P.getInfo()) {
// P.getInfo()
// }
}
注释
//单行注释
var a = 'str';
print(a);
/*
* 多行注释
*
* */
fn() {
print(1);
}
fn();
/// 这是一个文档注释
class MyClass {
String a;
}
数组及常用API
与JS不同,在Dart中,数组被称为List
var list = [1, 2, 3];
print(list); // [1, 2, 3]
箭头函数
// 使用场景与特点基本与JS中ES6的箭头函数类似(注:Dart中无function关键字)
fn(int a, int b) => a + b;
print(fn(9, 8)); // 17
map
遍历数组中的每一个元素,与JS不同,Dart中只有一个参数,没有JS中下标参数,Map的返回值并不是一个数组,需要通过.toList()转为数组,因此如果使用过List类型定义的变量一定要记得toList()
List<int> list = [19, 20, 12];
List newList = list.map((v) => 2*v).toList();
print(newList); // [38, 40, 24]
foreach
遍历数组中的每一个元素,与JS不同,Dart中只有一个参数,没有JS中下标参数,没有返回值
List<int> list = [19, 20, 12];
list.forEach((v) => print(v));
// 19
// 20
// 12
contains
类似于JS中的includes, 用于检测数组中是否包含某项,参数为要确认是否存在的对象,返回一个bool值
List<int> list = [19, 20, 12];
var isContain= list.contains(19);
print(isContain); // true
sort
用法基本与JS相同
List nums = [9, 4, 7, 10];
var newNums = nums.sort((a, b) => a - b);
print(newNums); // [4, 7, 9, 10]
reduce
依次访问List中所有的元素,并将List中所有的元素根据传入的函数返回一个数值
List arr = [1, 2, 3, 4];
var newArr = arr.reducce((pre, next) => pre * next);
print(newArr); // 24
fold
累加器,比reduce多了一个参数,第一个参数可以自定义一个初始值,其他用法基本一样
List arr = [1, 2, 3, 4];
var newArr = arr.fold(1, (pre, next) => pre + next);
print(newArr); // 11
every
遍历List中每一个元素,验证是否每一个元素都满足传入函数的要求,返回一个bool值
List arr = [1, 2, 3, -1];
bool isOverZero = arr.every((v) => v > 0);
print(isOverZero); // false
where
类似于JS中的filter,返回值为符合条件的集合
List arr = [1, 2, 3.4, 555];
var newArr = arr.where((v) => v > 2);
print(newArr); // (3.4, 555), 返回值并非数组,可以使用.toList()转为数组
print(newArr.toList()); // [3.4, 555]
firstWhere
返回数组中符合条件的第一项
List arr = [1, 2, 3.4, 555];
var newArr = arr.firstWhere((v) => v > 2);
print(newArr); // 3.4
singleWhere
返回数组中符合条件的唯一一项,若有不止一项,则会报错
List arr = [1, 2, 3.4, 555];
var newArr = arr.singleWhere((v) => v > 4);
print(newArr); // 555
var newArrs = arr.singleWhere((v) => v > 2);
print(newArrs); // 会报错 Uncaught Error: Bad state: Too many elements
take
从数组中取走指定个数的元素,返回值为取走的元素,默认从第一项开始取,不会改变原数组 注意:take的返回值虽然不是数组形式,但依然可以继续使用take,进行链式调用
List arr = [1, 2, 3.4, 555];
print(arr.take(3)); // (1, 2, 3.4)返回值并不是数组形式,可通过toList()转换为数组
print(arr); // [1, 2, 3.4, 555]
skip
跳过数组中指定个数的元素,返回一个全新的数组,不会改变原数组
List arr = [1, 2, 3.4, 555];
print(arr.skip(3)); // (555) 返回值并不是数组形式,可通过toList()转换为数组
print(arr); // [1, 2, 3.4, 555]
from
克隆一个数组,返回一个新数组
List arr = [1, 2, 3, 4, 5];
var newArr = List.from(arr);
print(newArr); // [1, 2, 3, 4, 5]
add
向数组中添加一个元素,默认为尾部添加,会改变原数组
List arr = [1, 2, 3, 4];
arr.add(111);
print(arr); // [1, 2, 3, 4, 111]
addAll
向数组中添加一个数组的全部元素,默认为尾部添加,自身无法给自身添加自身
List arr = [1, 2, 3, 4];
List arr2 = [0, 0];
arr.addAll(arr2); // 表示把整个arr2添加到arr中
print(arr); // [1, 2, 3, 4, 0, 0]
insert
向数组的指定位置添加一个元素,接收两个参数,第一个参数为要添加的索引(下标),第二个参数为要添加的元素,可以为任意类型, 第一个参数必须为非负,且不大于添加数据前数组的长度
List l = [1,2,3];
l.insert(0,4);
print(l); // [4, 1, 2, 3]
l.insert(0, {});
print(l); // [{}, 4, 1, 2, 3]
insertAll
从指定的索引开始插入给定的值列表,第一个参数必须为非负,且不大于添加数据前数组的长度,第二个参数必须是Iterable类型,即可迭代的集合
List l = [1,2,3];
l.insertAll(0, ['a', 'b']);
print(l); // [a, b, 1, 2, 3]
l.insertAll(0, {4, 5});
print(l); // [4, 5, a, b, 1, 2, 3]
这些都是比较常用的API,还有一些不是很常用,这里就不一一列举了。
对象及常用API
Dart中的对象与JS中的对象不太一样,在Dart中,对象的key值不会被默认为String类型
void main() {
var obj = {
'a': 1,
'b': 2
};
// var obj = {a:1, b: 2}会被认为是语法错误
print(obj); {a: 1, b: 2}
// 获取对象中某个属性的值时,不能直接.属性名称访问
print(obj['a']); // 1
print(obj.a); // 语法错误
}
forEach
var obj = {
'a': 1,
'b': 2
};
obj.forEach((key, value) => print('$key: $value'));
// a: 1
// b: 2
keys(属性)
keys属性可以获取所有的key值,返回值并非数组,需要.toList()
var obj = {
'a': 1,
'b': 2
};
print(obj.keys); // (a, b)
values(属性)
values属性可以获取所有的value值,返回值并非数组,需要.toList()
var obj = {
'a': 1,
'b': 2
};
print(obj.values); // (1, 2)
isEmpty(属性)
可以判断对象是否为空(是否存在属性、属性值),返回bool值,还有一个isNotEmpty,顾名思义,与isEmpty作用相反
var obj = {};
print(obj.isEmpty); // true
print(obj.isNotEmpty); // false
remove
用来删除对象中的某一属性,访问不存在的属性时,返回值为null
var obj = {
'a': 1,
'b': 2
};
obj.remove('a');
print(obj); // {b: 2}
print(obj['a']); // null
addAll
用于往对象中批量添加属性
var obj = {
'a': 1,
'b': 2
};
obj.addAll({"sex": "男", 'name': '小明'});
print(obj); // {a: 1, b: 2, sex: 男, name: 小明}
// 添加单个属性时,可以直接添加
obj['c'] = 3;
print(obj); // {a: 1, b: 2, c: 3, sex: 男, name: 小明}
containsKey
用于判断对象中是否包含指定key值,返回bool值
var obj = {
'a': 1,
'b': 2
};
print(obj.containsKey('a')); // true
containsValue
用于判断对象中是否包含指定value值,返回bool值
var obj = {
'a': 1,
'b': 2
};
print(obj.containsValue(1)); // true
对象的API也还有很多,这里介绍了一些比较常用的,更多内容可以访问Dart官网
类(class)
最基本的类
class Girl {
String name = '小红';
int age = 18;
String getInfo() {
return this.name + '今年' + this.age.toString() + '岁';
}
}
void main() {
print(new Girl().name); // 小红
print(new Girl().getInfo()); // 小红今年18岁
}
自定义类
class Girl {
String name = '小红';
int age = 18;
// 自定义类的默认构造函数
// Girl(String name, int age) {
// this.name = name;
// this.age = age;
// }
// 如果构造函数中没有比较复杂的逻辑,可以简写为
Girl(this.name, this.age);
String getInfo() {
return this.name + '今年' + this.age.toString() + '岁';
}
}
void main() {
// 实例化,传入对应参数
print(new Girl('小李', 18).name); // 小李
print(new Girl('小李', 19).getInfo()); // 小李今年19岁
}
小结
当今的社会发展日新月异,很多时候,我们都被眼前的高楼大厦蒙蔽了双眼,却忽略了作为基础的钢筋水泥,前端之路,方兴未艾,即使是当下最火热的Vue、React,在不远的将来也可能被替换。万变不离其宗,只要打好基础,底层弄扎实,我相信,不管之后框架怎么变化,我们都可以很快适应,在学习Dart的时候,之前学过JS和TS的同学相信已经深有体会,本章内容虽然简单,但却是Dart中不可或缺的重要基础,希望大家下去都可以亲自尝试,毕竟眼过千遍,不如手过一遍,后续会持续更新,请关注我们的掘金账号!