flutter前瞻(一)—— 初识Dart

859 阅读14分钟

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

  1. 安装Dart插件在这里插入图片描述

  2. 安装插件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中不可或缺的重要基础,希望大家下去都可以亲自尝试,毕竟眼过千遍,不如手过一遍,后续会持续更新,请关注我们的掘金账号!


技术分享宣传图@3x.png