以纯前端视野如何快速上手 Flutter

以纯前端视野如何快速上手 Flutter

前提声明:本人学习 Flutter 时间不是很久,对于 Flutter 的掌握尚浅,只是以自己切身经验来分享以前端的视角如何快速上手 Flutter 框架,对于其中细节有误部分欢迎大家耐心指正,不吝赐教。ヾ(≧▽≦*)o。

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

Flutter 从 React 中吸取灵感,通过现代化框架创建出精美的组件。它的核心思想是用 Widget 来构建你的 UI 界面。 Widget 描述了在当前的配置和状态下视图所应该呈现的样子。当 Widget 的状态改变时,它会重新构建其描述(展示的 UI),框架则会对比前后变化的不同,以确定底层渲染树从一个状态转换到下一个状态所需的最小更改

即然 Flutter 是从 React 中吸取灵感而来,那么我们就可以用 React 的学习视角来更好的理解和掌握 Flutter。

安装

在 Flutter 官网中,其提供了非常完备的安装和环境配置指南,此处不再赘述,请参考这个链接地址,全程安装过程非常简单。

脚手架

Flutter 提供了官方的脚手架工具,你只需要按照官网提供的编辑工具设定操作即可。

其默认创建的 Flutter 项目结构如下图所示,我们在开发的过程中只需要重点关注 lib 开发目录及 pubspec.yaml 配置文件即可,其他都是一些运行对应平台相关的工程代码,基本上我们都不会对其内容进行更改。

以纯前端视野如何快速上手 Flutter

核心概念

在 React 中,其核心概念也就 2 个,那就是声明式组件化(参考下图 React 官网对于声明式和组件化描述),对于 Flutter 而言,其核心概念也是相同的,唯一不同的就是语法表示不同。

以纯前端视野如何快速上手 Flutter

声明式

对于声明式的概念,相信大家应该都比较熟悉了,这里再简单的提一下,在编程语言中,我们有两种编程方式,分别是命令式和声明式, 我们可以简单的这样进行理解: 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

目前我们所用到的 UI 框架库(React、Vue、Flutter 等)都是声明式的编程方式,其用户界面描述就是应用的当前状态,可以用下图的公式来表示,当你的应用的状态发生改变时(例如,用户在设置界面中点击了一个开关选项,你改变了状态),这将会触发用户界面的重绘。

以纯前端视野如何快速上手 Flutter

在 React 中,我们使用 JSX 模板语法来声明式的组织代码格式,在 Flutter 中,我们使用 Widget 来组织代码格式,每一个 Widegt 都描述着当前UI元素的配置信息,但并不代表着最终绘制到设备屏幕上的显示元素,Widget 和元素的对应关系如下图所示。

以纯前端视野如何快速上手 Flutter

为了更好的理解 Flutter 中组织代码的形式,我们以一个“400* 400、边框大小为 2 像素、边框颜色为红色、圆角大小为 10 像素的绿色容器内,有垂直水平居中的两个文本元素 Hello 和 World!,其中 World 字体大小为 12 像素、文本颜色为红色,当用户点击 World!时,控制台输出 Hello World!字符”为例,对比下以 JSX 语法组织代码和以 Widget 组织代码格式的区别。

// React
// 表现:style.css
.box {
  width: 400px;
  height: 400px;
  border: 2px solid red;
  background: green;
  border-radius: 10px;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}

.text {
  font-size: 12px;
  color: red;
}

// 结构 & 行为
const element = (
   <div className="box">
      <p>Hello</p>
      <p className="text"
        onClick={() => {
          console.log("Hello World!");
        }}
      >
         World!
      </p>
    </div>
);

// Flutter
// 结构 & 表现 & 行为
const element = (
  Container(
    width: 400,
    height: 400,
    decoration: BoxDecoration(
        border: Border.all(color: Colors.red, width: 2),
        color: Colors.green,
        borderRadius: BorderRadius.circular(10)),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text('hello'),
        GestureDetector(
          onTap: () {
            print('Hello World!');
          },
          child: const Text('World!',
              style: TextStyle(color: Colors.red, fontSize: 12)),
        )
      ],
    )
  )
)

代码运行效果如下图所示。

以纯前端视野如何快速上手 Flutter

以纯前端视野如何快速上手 Flutter

在 Flutter 中,其组织代码的形式像是写 Javascript 中的对象,它把 UI 元素信息以 “key-value” 的形式在一个对象中描述出来,类似 React 中 VDOM 的表达方式,无法做到类似 Web 将 UI 元素拆分为 “结构-表现-行为” 三者分离的模式,UI 元素样式和行为越复杂,其 Widget 也就越庞大,当一个 Wiget 树中包含多个复杂的元素时,一旦你想在其中删除或者添加一个 Widget 嵌套时,保证其正确的嵌套关系将会十分复杂,当然这并不会带来太大的问题,因为我们还有另外一个杀手锏,那就是组件化,合理的组件化完全可以降低 Widget 树的复杂度。

组件化

组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,由多种属性、状态反映其内部特性。组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

对于一个复杂的系统而言,我们将其按照功能逻辑进行划分,将其划分为多个小组件,每一个小组件都代表着一个简单的功能或者一个 UI 片段,就类似一个乐高产品,将其划分为不同的单元模块,通过实现每一个单元模块对应的功能并将其重新组合形成一个乐高产品。

以纯前端视野如何快速上手 Flutter

从前端的角度而言,组件,从概念上类似 JavaScript 函数,他可以接受任意的参数(即 Props),并用于返回用户描述页面展示内容的元素。在 React 中,组件按照其状态被分为有状态组件和无状态组件(纯函数组件),在 Flutter 中,这个概念同样适用。

无状态和有状态组件

在 React 中,无状态组件一般使用纯函数来表示,有状态组件使用 ES6 中的 Class 来表示。在 Flutter中,其也有无状态组件和有状态组件的概念,其无状态组件使用 StatelessWidget 类来表示,有状态组件使用 StatefulWidget + State 类来表示。

以 React 官方文档中的 Welcome 组件为例,我们对比下 React 和 Flutter下的写法,代码片段如下:

// React 无状态组件
function Welcome(props) {
   return <p>Hello, {props.name}</p>
}
// Flutter 无状态组件
class Welcome extends StatelessWidget {
  final String name;
  const Welcome({Key? key, required this.name}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Text('Hello, $name');
  }
}

// React 有状态组件
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// Flutter 有状态组件
class Welcome extends StatefulWidget {
  final String name;
  const Welcome({Key? key, required this.name}) : super(key: key);

  @override
  _WelcomeState createState() => _WelcomeState();
}

class _WelcomeState extends State<Welcome> {
  @override
  Widget build(BuildContext context) {
    return Text('Hello, ${widget.name}');
  }
}

在组件概念这块,Flutter 和 React 是一样的,你完全可以按照你对 React 组件的理解来理解 Flutter 中的 StatelessWidget 与 StatefulWidget。当然,对于 React 组件组合和提取等思想,flutter 中都是适用的。

组件生命周期

对于有状态组件而言,由于状态变化的副作用会影响 UI 的展示,当状态发生变化时,UI 就会进行对应的更新,为了更好的 Hook 组件变化逻辑,于是对外暴漏了组件变化不同阶段的钩子函数,也被称为生命周期。

Flutter 组件生命周期如下图所示:

以纯前端视野如何快速上手 Flutter

其各个阶段的含义如下:

  • initState:当 widget 第一次插入到 widget 树时会被调用,对于每一个 State 对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等

  • didChangeDependencies():当 State 对象的依赖发生变化时会被调用

  • build():它主要是用于构建 widget 子树的,会在如下场景被调用:

    • 在调用 initState() 之后。
    • 在调用 didUpdateWidget() 之后。
    • 在调用 setState() 之后。
    • 在调用 didChangeDependencies() 之后。
    • 在State对象从树中一个位置移除后(会调用 deactivate )又重新插入到树的其他位置之后。
  • reassemble():此回调是专门为了开发调试而提供的,在热重载(Hot Reload)时会被调用,此回调在 Release 模式下永远不会被调用。

  • didUpdateWidget ():在 Widget 重新构建时,Flutter 框架会调用 widget.canUpdate 来检测 Widget 树中同一位置的新旧节点,然后决定是否需要更新,如果 widget.canUpdate 返回 true 则会调用此回调。正如之前所述, widget.canUpdate 会在新旧 Widget 的 keyruntimeType 同时相等时会返回 true,也就是说在在新旧 Widget 的 keyruntimeType 同时相等时 didUpdateWidget()就 会被调用。

  • deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过 GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用 dispose() 方法。

  • dispose():当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。

如何选择无状态还是有状态组件

和 React 不同,React 为了践行函数式编程,对于无状态组件引入了 Hook 概念,不推荐使用类形式来描述有状态组件,将有状态组件变为“无状态组件 + 副作用函数”,有状态组件和无状态组件的区别在书写层面也不是那么明显了,唯一可以区分的便是在无状态组件中是否使用副作用函数。

在 Flutter 中,有状态组件是借助于 State 类来实现的,两者在书写层面还是存在很大的差别。对于 Widget 而言,其要么是无状态的,要么是无状态的,区别就在于当前 Widget 是否依赖状态的改变,如果一个 Widget 发生了变化,而它所处的用户界面或数据中断了 UI,那么该 Widget 就是有状态的,否者就是无状态的。

在 Flutter 中,有三种组织状态的方式,分别是 Widget 管理它的自身状态、由其父 Widget 管理 Widget 状态、通过混搭的方式,选择何种方式组织组件状态也决定着你选择何种组件。

高级指引

状态管理

Flutter 是响应式框架,其状态管理模式和前端响应式框架常用的状态管理方式一样,前端目前使用的状态管理库在 Flutter 中都有相应的库与之对应,因此在状态管理方面我们完全可以按照前端管理组件状态的模式来处理。Flutter 中常用的状态管理器请参考这个地址:状态 (State) 管理参考

Package和插件

以纯前端视野如何快速上手 Flutter

Flutter Package 会被发布到 pub.dev 网站上,你可以通过关键字快速找到适合项目的第三方包应用,在使用的时候优先考虑 Flutter Favorites 页面列出了一系列编写应用时可以使用的插件和 Package(注意:在选择时需要注意下当前包的平台支持情况)。

在 Flutter 项目中,所有的 Package 都在 pubspec.yaml 文件中管理,pubspec.yaml 类似前端项目中的 package.json 文件。pubspec.lock 文件类似前端项目中 package-lock.json,用于维护安装包依赖树关系及每个依赖锁定的版本。在管理安装包这块两者是十分相似的,在理解这块心智模型的时候,你可以将其看作管理前端 Package 即可。

下面是 Flutter 关于包管理中常用的 package 命令。

flutter pub get // 根据 pubspec.yaml 安装所有 package 等价于前端的 npm install 
flutter pub add <package> // 安装 package 等价于前端的 npm install <package>
flutter pub remove <package> // 卸载 package 等价于前端的 npm uninstall <package>
flutter pub upgrade // 将 pubspec.yaml 下依赖的 package 升级到最新版本
flutter pub upgrade <package> // 将 package升级到最新版本
flutter pub cache add <package> // 手动缓存一个 package 到本地
flutter pub cache repair // 修复本地缓存
flutter pub cache clean // 清空本地缓存(就是前端的node_modules)

总结

Flutter 应该是目前跨平台开发技术中综合来看最好的一个了。对于 Web 前端开发者而言,虽然其很多思想非常契合我们开发认知,但是其与现如今成熟的 Web 技术生态完全不共享,自成一套技术体系,要求 Web 前端开发者重新花费时间和精力再去学习一门全新的技术,其还是有一定的抵触心理滴(但是如果你真的下定决心做下去,这并不会花费你太多的时间,因为这其中很多的思想理念都和 Web 生态一致)。当然我也希望 Web 可以快速出现解决方案去解决这个问题,毕竟我们曾经的理念可是 “Any application that can be written in JavaScript will eventually be written in JavaScript.”。

对于用 Web 语法来写 Flutter,市面上团队做过实践了。但是这样来回折腾是好是坏呢,谁也不清楚,毕竟我们就是这样一路走来的,最后的最后,还是希望 Atwood’s Law 永远有效,毕竟我还是很喜欢 JavsScript 的。

参考文档

  1. 一文读懂跨平台技术的前世今生
  2. 《Flutter实战·第二版》
  3. React 设计思想中文版

原文链接:https://juejin.cn/post/7215115493857738810 作者:BEFE团队

(0)
上一篇 2023年3月28日 上午10:37
下一篇 2023年3月28日 上午10:48

相关推荐

发表回复

登录后才能评论