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 最终会解析渲染成原生控件,如 标签对应 ViewGroup/UIView, 标签对应 ImageView/UIImageView 等。

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

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


JS开发者值得一看的Flutter入门_第1张图片
RN和Flutter的UI渲染流程对比图(原创)

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

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

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

JS开发者值得一看的Flutter入门_第2张图片
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 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国内版.

参考

  • Flutter 官方文档
  • Dart 语言开发文档
  • 《Flutter技术入门与实战》
  • 为什么要掌握 Flutter?
  • 全网最全 Flutter 与 React Native 深入对比分析
  • 移动端跨平台开发的深度解析

你可能感兴趣的:(JS开发者值得一看的Flutter入门)