JS开发者值得一看的Flutter入门

本文将从一个Javascript开发者角度介绍:Flutter的初衷,它与React Native的区别,还会通过对比JavaScript来介绍Dart语言特性。如果你需要了解如何搭建环境,请参考官网 => 开始使用Flutter

What is Flutter?

英 [ˈflʌtə(r)] , v. 飘动;(鸟或昆虫)鼓翼;飞来飞去;n. 振动;

Flutter是 Google 于 2015 年 5 月 3 日推出的免费开源跨平台开发框架,可以快速开发 Android 和 iOS 应用,同时也将是未来的 Google Fuchsia OS 下开发应用的主要技术框架。未来的 Flutter 的开发不仅仅局限于移动跨平台,目前已经支持 Web 开发、后端开发、PC 桌面应用开发(内测中)、嵌入式开发......

有些同学说,“React Native我都没用过,Flutter我也不想去掌握”.
一开始我学习React Naive的时候也有这个疑虑,转眼现在已经开发了两年的React Native项目了。现在学习Flutter没有那么大的抵触,因为掌握了一个跨平台客户端开发解决方案后再学习另外一个,你就会知道哪些是核心知识点,学起来更有目的性,学习效率会更高;就好比你掌握Vue之后去学习React一样.

学习一门技术的目的不应该只是会用,而是理解它的原理和特性,掌握其精髓方可举一反三。不要说“什么Angular,React,老夫只用jQuery!”, 比如掌握了数据驱动视图的那种单一数据流转的设计思路,你也可以通过约定再jQuery或任何框架里面应用。

比如学习Flutter不仅是了解如何用它开发一个app, 而是要获得一个跨平台UI解决方案的原理和经验.

Flutter vs React Native

Flutter说的跨平台那些优点React Native也有,为什么不用React Native?

技术 性能&体验 开发成本 学习成本 热更新
Flutter 接近原生 single 新语言Dart “不能”
React Native 一般 single 对js开发者友好 可以
web single JavaScript 可以
Native Double(Android & iOS) java & OC 不能

当年React Native就是为了解决web性能差,Native开发成本大的问题,看Flutter也是这样的目的,那么我们为什么非要学习新的语言呢?

主要还是因为Flutter的性能要优于React Native,我们从实现原理看下二者的区别.

在 Android 和 iOS 上,默认情况下 Flutter 和 React Native 都需要一个原生平台的
Activity / ViewController 支持,且在原生层面属于一个载体页(单页面应用), 而它们之间最大的不同点其实在于 UI 构建.

React Native 是一套 UI 框架,默认情况下 React Native 会在 Activity 下加载 JS 文件,然后运行在 JavaScriptCore 中解析 Bundle 文件布局,最终堆叠出一系列的原生控件进行渲染。
简单来说就是 通过写 JS 代码配置页面布局,然后 React Native 最终会解析渲染成原生控件,如 <View> 标签对应 ViewGroup/UIView,<Image> 标签对应 ImageView/UIImageView 等。

Flutter 中绝大部分的 Widget 都与平台无关, 开发者基于 Framework 开发 App ,而 Framework 运行在 Engine 之上,由 Engine 进行适配和跨平台支持,Flutter 甚至不使用移动平台的原生控件, 而是使用自己 Engine 来绘制 Widget (Flutter的显示单元),而 Dart 代码都是通过 AOT 编译为平台的原生代码,所以 Flutter 可以 直接与平台通信,不需要JS引擎的桥接。同时 Flutter 唯一要求系统提供的是 canvas,以实现UI的绘制。

所以可以得出,Flutter渲染UI的方式效率高,没有与Native原生组件通信过程,可参考下面的对比图:


RN和Flutter的UI渲染流程对比图(原创)

组件(Widget)是Flutter应用程序用户界面的基本构建块。不仅按钮、输入框、卡片、列表这些内容可作为Widget,甚至将布局方式、动画处理都视为Widget。所以Flutter具有一致的统一对象模型:Widget。

如果Widget需要根据用户交互或其他因素进行更改,则该Widget是有状态的。例如,如果一个Widget的计数器在用户点击一个按钮时递增,那么该计数器的值就是该Widget的状态。当该值发生变化时,需要重新构建Widget以更新UI。

Flutter中的状态和React中的状态概念一致。React的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。Flutter程序的运行可以认为是一个巨大的状态机,用户的操作、请求API和系统事件的触发都是推动状态机运行的触发点,触发点通过调用setState方法推动状态机进行响应。

Widget状态的生命周期(原创)

另外,Flutter开发中,大部分样式跟React Native一样是驼峰命名的属性。

TextStyle bold24Roboto = TextStyle(
  color: Colors.white,
  fontSize: 24,
  fontWeight: FontWeight.w900,
);

其他详细的请参考官网, 在此不展开详述了。

Dart 和 JavaScript

Dart 语言特性介绍没有比官网跟标准的了:Dart 开发语言概述,在此,作为一个javascript开发者,从javascript语言使用习惯上,挑几个相对有趣的区别聊聊。

假设你已经粗略看过一遍Dart的语言概述了,那么你会了解到Dart是需要编译的。编译的过程中会有一些限制;比如,语句末尾的';'是必须的,否则在编译过程会报错。这不同于js的语法校验,比如eslint, tslint等是可选的,而Dart不是。

类型

尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。
Dart 可以描述类型的关键字有:int, double, bool, String, var, void, dynamic或类名。
如果像js一样不适应不初始化一个变量的类型, Dart会在编译过程报错.

name = 'Mike'; // js允许,但Dart报错

不确定的类型可以使用var, 但var实际上是编译期抛给开发者使用的“语法糖”,一旦被编译,就会编译到对应的类型.

var name = 'Mike'; 
// 等同于 String name = 'Mike';

而有没有编译器不能确定类型的变量呢?比如第三方库的,网络接口返回的变量,这样不确定的变量可以使用dynamic关键字来显示的描述这种情况。

import 'abc';

dynamic x = abc();

而dynamic被编译后,实际是一个 object类型,
只不过编译器会对dynamic类型进行特殊处理,
让它在编译期间不进行任何的类型检查,而是将类型检查放到了运行期。

笔者认为dynamic是真不知道什么类型;var是懒得去声明它是什么类型,交给编译器,但也有可能是编译期不能确定,而在运行期才能确定的变量类型。

另外,未赋值的变量在js中对应值是undefined, 而在Dart中,所有未初始化的变量的初始值为null。这是因为Dart将所有值都视为对象。但是如果不用var, dynamic定义时, 不赋值会在编译时报错。

final x; // Error: The final variable must be initialized.

const x;  // Error: The const variable must be initialized.

void x; // 不报错
print(x); // Error: This expression has type 'void' and can't be used.

var x; // 不报错
print(x); // null

在 JavaScript 中,1 或者任何非空对象都相当于 true。

// JavaScript
var myNull = null;
if (!myNull) {
  console.log('null is treated as false');
}
var zero = 0;
if (!zero) {
  console.log('0 is treated as false');
}

在 Dart 中,只有布尔类型值 true 才是 true。

// Dart
var myNull = null;
if (myNull == null) {
  print('use "== null" to check null');
}
var zero = 0;
if (zero == 0) {
  print('use "== 0" to check zero');
}

常量

如果你不想更改一个变量,可以使用关键字 final 或者 const 修饰.其中final跟js中的const用法相同,运行时只能赋值一次,不能被更改,准确说是运行时常量。
const在Dart中是编译时常量,必须在编译时确定其值, const 变量同时也是 final 的.

 const PI = 3.1415926;
 const y = PI * 3; // 编译通过

 const x = Random().nextInt(10); // 编译报错
 
 final z =  Random().nextInt(10); // 编译通过
  • final: adj. 最终的;不可更改的;
  • constant: adj. 不变的;恒定的;

函数

定义方式,没有function关键字。虽然高效 Dart 指南建议在公开的 API 上定义返回类型,不过即便不定义,该函数也依然有效。
但是如果声明void, 就在编译过程中做引用校验。

fooo1(){}
print(fooo1()); // 输出null

void fooo2(){}
print(fooo2()); // 编译时报错, Error: This expression has type 'void' and can't be used.

参数的定义方式跟js相同,也可以用{}来定义命名参数,另外Dart支持使用[]定义可选参数.

void foo2(String name, [String sex]){
  print(name);
}
foo2(); // 编译报错,提示缺少参数name

箭头函数, 闭包的用法跟js相同。

断言 assert

js没有断言, 断言有什么用?
在开发过程中,可以在条件表达式为 false 时使用 - assert(条件, 可选信息); - 语句来打断代码的执行。

// 确保变量值小于 100。
assert(number < 100, '变量值不符合条件'); // 如果number 小于100,会抛出异常

assert的作用有些类似if/else条件语句,但是assert只在开发环境,生产环境不会执行断言。
断言可以用于比较重要的调试代码,这样不用像条件语句那样的调试代码必须要在在运行前删除。
其实js中也有断言:console.assert,在不符合条件时打印:

console.assert(a === 3, "a 的值不是3!");

接口、抽象类

interface已经被Dart从关键字列表中移除, 意味着Dart没有接口概念,如果你希望创建一个接口,可以使用抽象类. 抽象类不能被实例化,只能被继承, 从而像接口一样约束了子类。

abstract class PopCompnent{
  show(){
    print('显示弹窗');
  }
}

继承、Mixin

Dart使用extends关键字继承类,只支持单继承,如果需要多继承,可以使用Mixin方式。
Mixin 是一种在多重继承中复用某个类中代码的方法模式。Dart使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式:

class Programer extends Person with Employee {
  // ···
}

定义一个类继承自 Object 并且不为该类定义构造函数,这个类就是 Mixin 类,除非你想让该类与普通的类一样可以被正常地使用,否则可以使用关键字 mixin 替代 class 让其成为一个单纯的 Mixin 类:

mixin Employee {
  int salary = 999999;
  //...
}

可以使用关键字 on 来指定哪些类可以使用该 Mixin 类,比如有 Mixin 类 A,但是 A 只能被 B 类使用,则可以这样定义 A:

mixin CompanyEmployee on Employee {
  // ···
}

包管理

Dart 生态系统使用packages来管理软件,使用 Pub 包管理工具 来获取 Dart 包。在 Pub 上,可以找到公开可用的包。相当于javascript的npm.
pubspec 是一个名为 pubspec.yaml 的文件,文件位于应用的根路径;它相当于npm中的package.json.

异步

async 和 await 关键字用于同步的写法实现异步编程。js中,async写在function前,Dart中,async写在方法体{}前.

函数体中使用await的话,函数必须用async修饰,否则js, dart都会报错。反之,如果函数已经用async修饰,不管有无await, js也会返回promise对象;而Dart也会返回Future对象.

Future被用来表示在未来才能知道结果的类,和js中的Promise类似。dart提供了对异步的支持,核心类包括Future和Stream,都位于dart:async包下.

import 'dart:async';

Future<String> fetchMessage() async => 'hello world';

fetchMessage().then((value){
    print(value);
},onError: (e) {
    print("onError: \$e");
}).catchError((e){
    print("catchError: \$e");
});

Dart相比JavaScript更像TypesScript,其他特性在此就不展开多说了。
如果你对上一部分中Dart这些特性有所好奇,想自己动手试试的话,可以使用在线运行Dart:dartpad, dartpad国内版.

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342