暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

前端工程师如何快速上手Flutter

中航鲸技术 2021-08-03
1049


0 前言

原生客户端开发,到DCloud,再到React Native,再到Flutter「前端一直在追求低成本高效率的满足用户体验」

为了更好的服务航空人,同时配合我们「理财、健康、生活」三位一体的服务升级计划,我们使用Flutter打造了我们全新的APP3.0。

1 为什么选择 Flutter

Flutter「Google」开源的UI工具包,帮助开发者通过一套代码高效构建多平台精美应用,支持移动网页桌面嵌入式平台

移动端跨平台技术对比

正如开头提到的,移动端跨平台技术在不断的发展,那么为什么会出现这些技术?它们又是为了解决什么问题呢?解答了这些问题,我们就可以知道为什么会诞生Flutter,以及它是如何做到「一套代码多端复用的同时还有高性能的交互体验」

H5 + 原生 APP


H5方式,实际只是套了个APP的壳。APPH5可以独立开发,互不影响,项目开发速度较快。热更新方便,并不依赖APP发版。其中,JavaScript利用Bridge与原先进行数据通信、调用原生API,前端界面使用Webview完成渲染。

但是,使用此方式开发的APP,初次打开时,会出现白屏。且在低性能的手机上体验较差,因为Webview就会消耗几十M的内存。

虽然,前端工程师可以采用单页面应用渐进式网页或者服务端渲染等技术方案进行性能优化,让体验接近原生。但是并不能弥补技术本身的缺陷。

React Native


为了解决H5方式的弊端,前端工程师们又推出了React Native。抛弃Webview,使用原生去解析RN显示配置,生成OEM Widgets,从而渲染出Native组件,让渲染性能和交互性能都大大提升。但是由于AndroidiOS端存在差异,需要进行特殊处理,实际是写了两套代码

而且,由于依赖Bridge和原生进行通信,最终Bridge也就成为了性能的瓶颈,尤其是高频率的通信场景(复杂的动画)体验不佳。

Flutter


最终,Flutter走出了另一条路,多了一个编译过程,使用DartAOT编译,将代码直接编译成Native代码,去掉Bridge,没有繁琐的数据通信和交互,性能也就更前进了一步。

不再使用平台的OEM Widgets,而是通过Skia-2D图形化引擎,绘制界面。Skia本身也是AndroidChrome的底层渲染引擎,所以性能方面完全不用担心。真正做到了「一套代码适配多端,且没有中间商赚差价(中间通信层),而且性能接近原生」

2 如何快速上手 Flutter

上文通过比较各种移动端跨平台技术方案,让我们知道为什么会诞生Flutter,如果你是个技术极客,想必已经想要自己动手尝试了。接下来,由于本人是一名前端工程师,我将从我的角度讲解「如何快速上手 Flutter」

将从以下几方面进行讲解:语言特性项目结构布局原理生命周期

语言特性

Flutter的编程语言是Dart,它是键全类型,使用静态类型检查来确保变量的值始终与变量的静态类型相匹配。尽管是强类型,但是支持类型推断,类型注释是可选的。

同时,它是一门面向对象的语言,在Dart中,变量都是引用类型,也就是说所有的变量都是对象

具体的使用方法,让我们对比下JavaScript

JavaScript VS Dart

void main() {
  // 变量声明
    var name;
    print(name == null);
    name = 'Jingqb';
    print(name);

    int age = 6;
    // 强类型 不可更改变量类型
    age = '6';

    // 字符串
    String slogan = 'Finance Health Life';
    // 模板字符串
    String describe = "Jingqb's slogan is $slogan";

    List sloganList = ['Finance''Health'];
    // 扩展运算符
    sloganList = [...sloganList, 'Life'];

    Function getJinqbAge() {}
    // 箭头函数
    String getJinqbSlogan() => slogan;

    // 异步函数 async 和 await
    Future getName(String name) async {
      String name = await Future.delayed(Duration(seconds: 1), () => name);
      print(name); // Jingqb
    }
}

复制

JavaScript一样,我们可以使用var
定义变量,但是初始值为null
。同时也支持模板字符串、扩展运算符、箭头函数、async
await

不同的是,如果我们明确知道声明变量的类型,可以在声明时就指定它的类型,这样上文中的age = '16';
在编写代码过程中IDE就会报错,避免代码实际运行时产生错误。个人感觉强类型的语言特性会是一种趋势,正如TypeScript的出现,虽然JavaScript写起来灵活,但是因为是解释型语言,有些错误要在运行时才会发现。而强类型则避免了一些低级错误。

项目结构

在日常的开发过程中,我们会使用脚手架来创建项目,比如create-react-app
Flutter也提供了脚手架,只要我们在命令行输入flutter create projectname
,即可创建项目。(创建之前,请先按照官网教程安装好Flutter)。

$ flutter create jingqb
Creating project jingqb...
...
...
...
Running "flutter pub get" in jingqb...                          1,359ms
Wrote 78 files.

All done!
In order to run your application, type:

  $ cd jingqb
  $ flutter run

Your application code is in jingqb\lib\main.dart.

复制

下图中的Android文件夹就是编译好的原生代码,iOS代码需在Mac中编译。

在开发过程中,我们都会使用开源软件包,在前端开发项目中,使用package.json
中管理这些依赖脚本。在Flutter项目中,使用pubspec.yaml
文件来管理这些依赖,也用来管理静态资源

version: 1.0.0+1
environment:
  sdk: ">=2.12.0 <3.0.0"
dependencies:
  flutter:
    sdk: flutter
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
dev_dependencies:
  flutter_test:
    sdk: flutter

fonts:
     - family: Schyler
       fonts:
         - asset: fonts/Schyler-Regular.ttf
         - asset: fonts/Schyler-Italic.ttf
           style: italic
     - family: Trajan Pro
       fonts:
         - asset: fonts/TrajanPro.ttf
         - asset: fonts/TrajanPro_Bold.ttf
           weight: 700

复制

布局原理

Widget

Flutter将组件命名为Widget,官方提供了300多个widget,在Flutter中一切UIWidgetFlutter有两大不同风格的Widget库,一个是基于Material Design(材料设计)风格的组件库;一个是基于cupertinoiOS设计风格的组件库。

中心思想是用Widget构建你的UI。当Widget的状态发生变化时,Widget会重新构建UIFlutter会对比前后变化的不同,确定底层渲染树从一个状态转换到下一个状态所需的最小更改(类似于React/Vue中虚拟DOM的diff算法)。

Flutter中,Widget的功能是「描述一个 UI 元素的配置数据」,也就是说,Widget并不是最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据

实际上,Flutter中真正代表屏幕上显示元素的类是Element,也就是说,「Widget 只是描述 Element 的配置数据」。一个Widget可以对应多个Element。这是因为同一个Widget对象可以被添加到UI树的不同部分,而真正渲染时,UI树的每一个Element节点都会对应一个Widget对象

理解上面的概念,我们就能接受Flutter的一个缺陷:嵌套地狱。我们只是在写配置信息,页面最终的展示样子由Element决定,这也叫声明式写法。和ReactJSX
原理类似。

React中,组件一开始分为函数组件类组件,是为了解决组件渲染的性能问题,需要根据数据变化的可以创建为类组件,后期更是进化出Hook:解决性能问题的同时更好的复用组件逻辑。

Flutter中,组件可以分为StatelessWidgetStatefulWidget,和React类似,StatefulWidget拥有复杂的生命周期和状态可变,也是使用setState
方法改变状态。

布局

Flutter支持线性布局Row
Column
)、弹性布局Flex
Expanded
)、流式布局ListView
GridView
)和层叠布局Stack
Position
)。

网页开发过程中,我们使用三剑客来完成页面:HTML定义结构,CSS定义样式,JavaScript定义交互。而在Flutter中,正如上文所述,我们只通过Widget来完成UI「结构、样式和交互三者合一」。所以布局需选择合适的Widget。要不然会报错,在调试过程中,IDE会提示,如果未解决,在生产的表现是渲染不完整

Flutter布局离不开约束,其原理是:「父元素向子元素传递约束,子元素向父元素传递大小」。这跟网页不同,网页里盒子模型的大小是由子元素决定。

一开始布局时,会出现一些问题,比如为什么字不会换行、为什么元素超出页面高度不能自动滚动等。与网页端不同,浏览器做了很多幕后工作。而在Flutter中,我们需要选择合适的Widget才能完成布局。同时要注意自定义组件的划分,避免嵌套地狱

由于Widget的数据决定了UI交互,和网页端不同,对于一个元素的样式我们可以通过覆写CSS来自定义样式。而在Flutter,就没有这么容易了。不过我们可以利用好Theme
来改变样式。

生命周期

React一样,组件具有生命周期,利用生命周期,我们可以控制组件的渲染与变化。

无状态组件,生命周期只有build
,只会渲染一次。而有状态组件,会根据数据的变化进行多次渲染。它的生命周期包含以下几个阶段:

  1. createState,创建State
    的方法,当StatefulWidget被调用时会立即执行createState

  2. initStateState
    各变量的初始赋值,或者获取服务端数据后调用setState
    来设置State
    。(这个方法需调用super
    重写父类的方法)

  3. didChangeDependencies,该函数是在该组件依赖的State
    (全局)发生变化时调用,例如语言或者主题等。

  4. build,主要是返回需要渲染的Widget,由于数据变化时,build
    就会被调用,因此在该函数中只返回Widget相关逻辑,避免因组件多次渲染影响性能。

  5. reassemble,主要是提供开发阶段使用,在debug模式下,每次热重载都会调用该函数。

  6. didUpdateWidget,该函数主要是在组件重新构建时调用,比如说热重载或父组件发生build

  7. deactivate,在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用dispose
    永久移除。

  8. dispose,永久移除组件,并释放组件资源。

总结

本文简单介绍了Flutter的相关知识,但路漫漫其修远兮,要想用好Flutter,还涉及到很多方面:数据管理路由管理接口请求等。

虽然Flutter的优点不少,但是也有一些缺陷:

  • 因为发展时间短,生态圈不够繁荣,优秀的开源库较少
  • 组件过度封装,需挑选合适的Widget才能完成页面
  • 本质上只是UI层,脱离不了原生,需要开发人员具备原生基础开发能力,或者和原生开发工程师配合
  • 由于代码是编译生成,打包后,安装包会增大

综上所述,本人觉得Flutter未来可期,应该是目前移动端跨平台技术中比较优秀的。

技术总是在不断发展,应该保持好奇心,去了解去学习,而不是排斥新技术的出现。并且技术本身是相通的,应善于归纳总结自己所掌握的技术,通过对比或者类比的方式,找出技术之间的相同点或差异点,快速入门新技术。


文章转载自中航鲸技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论