flutter快速入门笔记(1)

本系列不包含 Dart 语法笔记,以掌握 Dart 语法为前提记录。不懂可以百度搜一下Dart快速入门教程,如果你本来就懂js,基本可以一两小时内掌握Dart的语法,比如看下Flutter实战上的Dart简介

启动

flutter 入口文件 main.dart

main.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'package:flutter/material.dart'; // 导入md风格组件

void main() => runApp(MyApp()); // 启动flutter

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(home: MyHomePage(title: 'Flutter Demo Home Page'));
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) => {...};
}

用 vue3 项目来类比,main.dart 相当于 main.ts/js。runApp 相当于 createApp,MyApp 相当传进 createApp 的根组件,MyHomePage 相当于首页组件。

build 相当于 vue 的 h 或者 createVNode。

flutter 的三棵树

flutter 的三棵树分别是 widget、element、renderObject 树。
用 vue 类比就是 widget->template/jsx、element->虚拟 dom、renderObject->浏览器 dom 树。

可能不太准确,但是适合对 flutter 没有概念的人理解 flutter 整个渲染原理。

StatelessWidget

StatelessWidget 非常好理解,就是一个不可变的,没有状态的 widget,用 vue 类比就是一个.vue 文件里面只写个 template 标签,或者再 import 别的组件然后用到 template 上,而且没用 data 函数定义组件的状态。
比如上面的:

1
2
3
4
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(home: MyHomePage(title: 'Flutter Demo Home Page'));
}

相当于 vue 的这样的代码

MyApp.vue
1
2
3
4
5
6
7
<template>
<div>
<p>HelloWorld</p>
<div>
</template>
<script setup>
</script>

vue3编译器会发现这个div和p是静态的,会编译成_hoisted_1、_hoisted_2 这样的对象,提升到render函数之外,组件实例化时只会构建一次,避免每次执行render都要重新构建。

StatefulWidget

在 flutter 中,Widget 是不可变的,发生变化时需要销毁重建,所以谈不上状态。既然 widget 不可变,那则么实现有状态的 widget 呢,那就是用另一个类帮我们代理生成 widget,每次状态发生改变,那就让代理类生成一个新的 widget。

1
2
3
4
5
6
7
8
9
10
class MyHomePage extends StatefulWidget { // 其实StatefulWidget和StatelessWidget一样不可变
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState(); // 但是Statefule的子widget由State代理生成,也就是Statefule的子widget可以动态切换
}

class _MyHomePageState extends State<MyHomePage> { // State类代理里如果状态发生改变就生成新的widget交给外面的StatefulWidget
Widget build(BuildContext context) => {...};
}

用 vue 来理解:

1
2
3
4
5
6
7
8
9
10
11
import { h } from 'vue'
export default {
setup() {
// 类似createState
// do sth
return {
// ...
}
},
render: () => h('...'), // 类似build,当主动修改组件的状态时,就重新调用这个render
}

总结:StatefulWidget 不是万金油,要慎用。Widget 是不可变的,更新则意味着销毁 + 重建(build)。StatelessWidget 是静态的,一旦创建则无需更新;而对于 StatefulWidget 来说,在 State 类中调用 setState 方法更新数据,会触发视图的销毁和重建,也将间接地触发其每个子 Widget 的销毁和重建。

所以 StatefulWidget 要用对地方,比如如果根 Widget 是 StatefulWidget 的话,那么他状态一改变整个 widget 树都要重建,可想而知对性能的影响有多严重。

生命周期

对于这种数据驱动的框架,一般都会提供生命周期函数钩子,像 vue 的 onMounted、onUpdated 等等。State 的生命周期可以分为 3 个阶段:创建(插入视图树)、更新(在视图树中存在)、销毁(从视图树中移除)

创建

State 初始化时会依次执行 :构造方法(上面的 createState 方法) -> initState(像 vue 的 create 过程,生命周期只调用一次,用于设置默认值) -> didChangeDependencies -> build,随后完成页面渲染。

更新

Widget 的状态更新,主要由 3 个方法触发:

  • setState:相当于 vue 的 this.xxx = xxx,告诉代理,状态发生了改变,需要去更新视图
  • didChangeDependencies:依赖值发生改变,比如 vue 的全局状态改变,如果组件依赖了全局状态,那么也需要去更新视图
  • didUpdateWidget:比如父 Widget 状态发生改变,会触发子组件的这个方法,还有热更新也是系统会使用这个方法去更新视图。

一旦这三个方法被调用,Flutter 随后就会销毁老 Widget,并调用 build 方法重建 Widget。

销毁

组件被移除,或是页面销毁的时候,系统会调用 deactivate 和 dispose,来移除或销毁组件。相当于 vue 组件的 unmounte 过程,貌似vue组件卸载和销毁是一个周期内执行完,并提供beforeUnmount和unmounted钩子函数。

几种常见过程的生命周期过程

父 widget 状态变化

父 widget:setState -> build
子 widget: didUpdateWidget -> build

下一个 Widget:构建方法 -> (initState) -> didChangeDependencies -> build
当前 Widget: deactivate -> build

上一个 Widget:deactivate(还是调用 deactivate,设计的有点奇怪) -> build
当前 Widget: deactivate -> dispose

App 生命周期

除了 widget 的生命周期,app 也有生命周期,在 widget 中可以通过WidgetsBindingObserver类来调用 App 的生命周期钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class WidgetsBindingObserver {
// 页面 pop
Future<bool> didPopRoute() => Future<bool>.value(false);
// 页面 push
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
// 系统窗口相关改变回调,如旋转
void didChangeMetrics() { }
// 文本缩放系数变化
void didChangeTextScaleFactor() { }
// 系统亮度变化
void didChangePlatformBrightness() { }
// 本地化语言变化
void didChangeLocales(List<Locale> locale) { }
//App 生命周期变化
void didChangeAppLifecycleState(AppLifecycleState state) { }
// 内存警告回调
void didHaveMemoryPressure() { }
//Accessibility 相关特性回调
void didChangeAccessibilityFeatures() {}
}

生命周期回调

didChangeAppLifecycleState 回调函数中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对 App 生命周期状态的封装。它的常用状态包括 resumed、inactive、paused 这三个。

  • resumed:可见的,并能响应用户的输入。
  • inactive:处在不活动状态,无法处理用户响应。
  • paused:不可见并不能响应用户的输入,但是在后台继续活动中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class _MyHomePageState extends State<MyHomePage>  with WidgetsBindingObserver{
@override
@mustCallSuper
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);// 注册监听器
}
@override
@mustCallSuper
void dispose(){
super.dispose();
WidgetsBinding.instance.removeObserver(this);// 移除监听器
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
print("$state");
if (state == AppLifecycleState.resumed) {
//do sth
}
}
}

以上就是 flutter 的基本知识了,如果会 vue 的话入手 flutter 也是简简单单。如果点赞多就继续写一下布局、组件的快速入门教程XD