flutter优缺点_大前端之路:这么多跨端技术,为什么选择 Flutter?

随着互联网技术的发展,业务平台对多设备、多终端的需求越来越多,因此,仅掌握单一平台编程开发能力已经稍显欠缺。由于在业务开发过程中,开发者大部分的时间都专研于一种编程语言,如果想要掌握多端开发能力,则又稍显力不从心。因此大前端的概念应运而生。0 1 什么是大前端 大前端概念对于编程开发者来说早已耳熟能详,从我的角度来理解这个概念的话,主要是通过同一套编程代码,经过框架编译转化能够适应于多端平台的前端交互界面。当然这里只介绍目前应用较广的三个方面,即 iOS、Andorid 和 Web H5,之后可以再延伸到小程序、TV、Watch 等其他智能设备,如图所示: ee43fb80d68d0c16efbcb6233da47d5c.png 多端平台的前端交互界面 大前端的核心是为了解决多端不一致和人力的问题。比如在一些交互复杂度不高的应用中,通过这种模式可以更好地节省人力成本,特别是在一些前期快速发展的创业公司,可以使用较少的人力来支撑一些核心业务功能。0 2 跨端技术的发展 移动端跨平台技术经过了一个漫长的发展史,如下图所示。下面主要介绍下在这个发展过程中跨平台技术有了哪些进步或者做了哪些优化。 293b50f1ae19969141a858565d12e55d.png 跨端技术的发展过程 Ionic/Cordova(Hybrid),在技术原理上的核心是,将原生的一些能力通过 JSBridge 封装给 Web 来调用,扩充了 Web 应用能力。但是这种方法有两个不足,一是依赖客户端,二是在性能和体验上都非常依赖于 Web 端。因此,整体上的体验不可预知。目前这个技术还经常被应用到,例如,当前 App 内会提供白名单域名和可调用的 JSBridge 方法,由此来增强 H5 与客户端交互能力,从而提升 App 内 H5 的灵活性。React Native/Weex,在原来的 Hybrid 的 JSBridge 基础上进行改进,将 JavaScript 的界面以及交互转化为 Native 的控件,从而在体验上和原生界面基本一致。但因为是 JIT 模式,因此需要频繁地在 JavaScript 与 Native 之间进行通信,从而会有一定的性能损耗影响,导致体验上与原生会有一些差异。Flutter,取长补短,结合了之前的一些优点,解决了与 Native 之间通信的问题,同时也有了自渲染模式(框架自身实现了一套 UI 基础框架,与原来的渲染模式基本一致)。从而在体验和性能上相对之前的两种框架表现都较好。 经过上面的技术原理和优缺点对比,Flutter 在各方面都表现出了突出的优势,因此把 Flutter 作为入门大前端的核心框架。0 3 选择 Flutter 的思考 大前端这个概念从开始到现在已经有整整 10 年时间,那为什么到现在还没有一统江湖呢?其实从我的角度来看,Flutter 也不会创造一统江湖的成果,因为多端或者智能设备的发展终究不会停止,也很难做到统一标准。那为什么我们还要选择 Flutter 来作为大前端核心框架呢? 事实上 Flutter 的确能够为我们解决一些场景问题,节省人力成本,同时不影响用户体验。 选择 Flutter 并不是为了代替 iOS 或者 Android,而是做一个技术互补,比如,Flutter 负责业务功能,而 iOS 和 Android 则负责部分的底层交互提供服务给到 Flutter 应用。Flutter 也是在这两年刚刚兴起的,在应用起步初期还需要部分底层的服务与原生平台进行交互。相信再经过一段时间的发展,Flutter 在这方面会不断地优化和提升,也将能够独立覆盖到更多复杂的业务场景。因此希望你能够明白大前端的概念,以及 Flutter 目前的应用场景。 分析完后,下面对目前的技术团队和未来技术团队进行一个简单分析,也可以认为是一个预测。如果你觉得有帮助,那么可以往这方面进行一些尝试,如图 3 所示。大部分开发者会集中在跨端技术团队中;而另一部分核心技术攻坚则在相应的平台技术端(比如 Android 基础技术团队、iOS 基础技术团队或 Web 基础技术团队),为跨端技术团队提供基础技术服务支撑。当然如果跨端技术团队将组件完善并且可通用化,那么跨端技术团队的人员则可以更快地配置组装的方式构建业务功能。 7969ea6622e2d0af5124381a34c33a6e.png 技术团队选型 那么,你对 Flutter 了解多少呢?不如先跟着我先来学学 Flutter Dart 的语法。0 4 Flutter Dart 语法 学习钱你需要有一定的 JavaScript 基础,比如基础数据类型、函数、基础运算符、类、异步原理和文件库引入等,这也是 JavaScript 的核心知识点。接下来将通过对比与 JavaScript 的差异点来学习 Dart 语言。 ①  基础数据类型 与 JavaScript 相比较,我们整体上看一下图 1 两种语言的对比情况,相似的部分这里就不介绍了,比如 Number 和 String,其使用方式基本一致。下面主要基于两者的差异点逐一讲解,避免混淆或错误使用。 66ff4c1d31463ee6ea25408f299e2200.png Dart 与 JavaScript 基础数据类型对比 ②  Symbol 的区别 在 JavaScript 中,Symbol 是将基础数据类型转换为唯一标识符,核心应用是可以将复杂引用数据类型转换为对象数据类型的键名。 在 Dart 中,Symbol 是不透明的动态字符串名称,用于反映库中的元数据。用 Symbol 可以获得或引用类的一个镜像,概念比较复杂,但其实和 JavaScript 的用法基本上是一致的。例如,下面代码首先 new 了一个 test 为 Map 数据类型,设置一个属性 #t(Symbol 类型),然后分别打印 test、test 的 #t、test 的 Symbol("t") 和 #t。 64dd303eea55187fae0a5a8392329c97.png 运行代码结果如下: c6ad0989cc1061f15771b3b33e887117.png 其中,test 包含了一个有 Symbol 为对象的 Key,value 为 symbol test 字符串的对象。test 的 #t 与 Symbol("t") 打印结果一致,#t 则与 Symbol("t') 是同一形式。 在上面的代码示例中,两者的核心在使用上基本是一致的,只是在理解方面相对不一样。Symbol 在 Dart 中是一种反射概念,而在 JavaScript 中则是创建唯一标识的概念③  Undefined 和 Null 由于 Dart 是静态脚本语言,因此在 Dart 中如果没有定义一个变量是无法通过编译的;而 JavaScript 是动态脚本语言,因此存在脚本在运行期间未定义的情况。所以这一点的不同决定了 Dart 在 Undefined 类型上与 JavaScript 的差异。 null 在 Dart 中是的确存在的,官网上是这样解释的,null 是弱类型 object 的子类型,并非基础数据类型。所有数据类型,如果被初始化后没有赋值的话都将会被赋值 null 类型。 下面的代码,首先定义了一个弱类型 number,其次定义了 int 类型的 num2,number 类型的 num1 以及 double 类型的 num3 ,最后我们打印出这些只定义了未被赋值的值。var number;int num2;num num1;double num3;print('number is var:$number,num2 is int:$num2,num2 is num:$num1,num3 is double:$num3'); 可以看到运行结果如下:flutter: number is var:null,num2 is int:null,num2 is num:null,num3 is double:null 从运行结果我们可以看到,代码中声明了变量,但未赋值的变量在运行时都会被赋值为 null,这就是 Dart 中 null 类型存在的目的。 ④  Map 和 List Map 和 List 与 JavaScript 中的 Array 和 Map 基本一致,但在 JavaScript 中不是基本数据类型,都属于引用数据类型。因此也就是分类不同,但在用法和类型上基本没有太大差异。 ⑤  弱类型(var、object 和 dynamic) 相对 JavaScript 而言,Dart 也存在弱类型(可以使用 var、object 和 dynamic 来声明),不过在这方面为了避免弱类型导致的客户端(App)Crash 的异常,Dart 还是对弱类型加强了校验。 var 数据类型声明,第一次赋值时,将其数据类型绑定。下面代码使用 var 声明了一个弱类型 t,并赋值 String 类型 123,而接下来又对 t 进行其他类型的赋值。 ead6c73ee0b66bd2b22a5077c46a531e.png 这样的代码在 Dart 编译前就会报错,因为 t 在一次 var 赋值时就已经被绑定为 String 类型了,再进行赋值 Number 类型时就会报错。 fb52d0361a8b12aeac66c35ff598d958.png object 可以进行任何赋值,没有约束,这一点类似 JavaScript 中的 var 关键词赋值。在编译期,object 会对数据调用做一定的判断,并且报错。例如,声明时为 String 类型,但是在调用 length 时,编译期就会报错。如果数据来自接口层,则很容易导致运行时报错。因此这个要尽量减少使用,避免运行时报错导致客户端(App)Crash 的异常。 dynamic 也是动态的数据类型,但如果数据类型调用异常,则只会在运行时报错,这点是非常危险的,因此在使用 dynamic 时要非常慎重。 ⑥  基础运算符 两种语言的基础运算符基本都一致。由于 Dart 是强数据类型,因此在 Dart 中没有 “=== ”的运算符。在 Dart 中有一些类型测试运算符,与 JavaScript 中的类型转换和 typeof 有点相似。 这里也介绍一些 Dart 中比较简洁的写法: ?? 运算符,比如,t??'test' 是 t!= null ? t : 'test' 的缩写;级联操作,允许对同一对象或者同一函数进行一系列操作,例如下面代码的 testObj 对象中有三个方法 add()、delete() 和 show(),应用级联操作可以依次进行调用。 7b9c0dcf3f5bb8ea82fe3426aad56994.png ⑦  函数 从我的理解来说,两者区别不大。箭头函数、函数闭包、匿名函数、高阶函数、参数可选等基本上都一样。在 Dart 中由于是强类型,因此在声明函数的时候可以增加一个返回类型,这点在 TypeScript 中的用法是一致的,对于前端开发人员来说,没有太多的差异点。 ⑧  类 类的概念在各种语言上大部分都是一致的,但在用法上可能存在差异,这里着重介绍一下 Dart 比较特殊的一些用法。 ⑨  命名构造函数 Dart 支持一个函数有多个构造函数,并且在实例化的时候可以选择不同的构造函数。 下面的代码声明了一个 Dog 类,类中有一个 color 变量属性和两个构造函数。red 构造函数设置 Dog 类的 color 属性为 red,black 构造函数设置 Dog 类的 color 属性为 black。最后在 main 函数中分别用两个构造函数创建两个实例,并分别打印实例的 color 属性。 70c21749388247178ad4da3499eef42c.png 运行代码后输出了两种颜色,即 red 和 black。就代码而言,我们可以应用同一个类不同的构造函数实现类不同场景下的实例化。 ⑩  访问控制 默认情况下都是 public,如果需要设置为私有属性,则在方法或者属性前使用 “”。 ⑪  抽象类和泛型类 抽象类和其他语言的抽象类概念一样,这里在 JavaScript 中没有这种概念,因此这里稍微提及一下,主要是实现一个类被用于其他子类继承,抽象类是无法实例化的。 下面的代码使用关键词 abstract 声明了一个有攻击性的武器抽象类,包含一个攻击函数和一个伤害力获取函数,Gun 和 BowAndArrow 都是继承抽象类,并需要实现抽象类中的方法。 96f7807669b625a76e63da7a0be2b90a.png 泛型类,主要在不确定返回数据结构时使用,这点与 TypeScript 中的泛型概念一样。 在下面的代码中,我们不确定数组中存储的类型是 int 还是 string,又或者是 bool,这时候可以使用泛型 来表示。在使用泛型类的时候可以将设定为自己需要的类型,比如下面的 string 调用和 int 调用。 cadcdf2c2681c7edc476edd00a6893ef.png ⑫  库与调用 · Dart 库管理 Dart 和 JavaScript 一样,有一个库管理资源(pub.dev)。你可以在这里搜索找到你想要的一些库,接下来只要在 Dart 的配置文件 pubspec.yaml 中增加该库即可。这点类似于在 JavaScript 的 package.json 中增加声明一样,同样也有 dependencies 和 dev_dependencies。 增加类似的数据配置,如下代码: 88d1e2196077ee6f0caa51e61c1f503b.png · 开发 Dart 库 Dart 也支持开发者自己开发一些库,并且发布到 pub.dev 上,这点基本上和 npm 管理一致,这里我只介绍 pub.dev 库的基本格式。 adde0c3380064278724c18f91f1d4c89.png 对于前端开发人员来说,这个结构和我们所看到的 npm 模块很相似,pubspec 和 package 很相似,核心是 lib 中的库名对应的库文件 .dart,该文件是一个 dart 类。类的概念上面已经介绍过了,将私有方法使用 "" 保护,其他就可以被引用该库的模块调用,如果是自身库的一些实现逻辑,可以放在 src 中。 开发完成该库以后,如果需要发布到 pub.dev,则可以参照官网的说明,按步骤进行即可。 ·  Dart 调用库 这里引入库的方式也与 ES6 的 import 语法很相似。先看看下面的一个例子,其目的是引入 pages 下的 homepage.dart 模块。 da67d3433a04538a98a302427c4adc11.png 在上面的例子中,import 为关键词,package 为协议,可以使用 http 的方式,不过最好使用本地 package 方式,避免性能受影响。接下来的 startup_namer 为库名或者说是该项目名,pages 为 lib 下的一个文件夹,homepage.dart 则为具体需要引入的库文件名。 当然这里也可以使用相对路径的方式,不过建议使用 package 的方式,以保持整个项目代码的一致性,因为对于第三方模块则必须使用 package 的方式。 0 5 总结 本次首先介绍了 Dart 基础数据类型、基础运算符、类以及库与调用。然后通过对比 JavaScript 的一些特殊差异性,来加深前端开发人员对 Dart 语言编程的理解。相信你通过本课时的学习,可以掌握 Dart 的编程,并且能够写一些 Dart 的第三方库。 如果,你想要更系统地、深入地了解 Flutter,以上内容是不够的,你还是需要更多完整的内容去学习。 04bd5e568359f1910fa0dbab8b6b780f.png Flutter 快学快用 24 讲

代码交流 2021