现代前端指南!前端开发那些事儿Flutter

[Flutter] 10-Flutter的生命周期

2020-07-05  本文已影响0人  codeTao

本章介绍 Flutter 的组件,以及组件的生命周期。

一、组件 Widget定义

Flutter 中的组件与前端组件的理解和作用基本一致,但是没有一个明确的概念解释 Flutter 组件,这里借用前端的组件定义来解释 Flutter 组件的概念。

一个 Flutter 组件,包含了组件的模板、样式和交互等内容,外部只要按照组件设定的属性、函数及事件处理等进行调用即可,完全不用考虑组件的内部实现逻辑。其中组件又包括无状态组件和有状态组件。

无状态组件,可以理解为将外部传入的数据转化为界面展示的内容,只会渲染一次

有状态组件,是定义交互逻辑和业务数据,可以理解为具有动态可交互的内容界面,会根据数据的变化进行多次渲染。

二、生命周期

在原生 Android 、原生 iOS 、前端 React 或者 Vue 都存在生命周期的概念,在 Flutter 中一样存在生命周期的概念,其基本概念和作用相似。 Flutter 中说的生命周期,也是指有状态组件,对于无状态组件生命周期只有 build 这个过程,也只会渲染一次,而有状态组件则比较复杂,下面我们就来看看有状态组件的生命周期过程。

1)、生命周期的流转

Flutter 中的生命周期,包含以下几个阶段:

@protected
@mustCallSuper
void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
}
图1 生命周期流程图

整个过程分为四个阶段:

2)、组件首次加载执行过程

我们先实现一段代码,来看下组件在首次创建的执行过程是否是按照图 1 的流程。

import 'package:flutter/material.dart';
/// 创建有状态测试组件
class TestStatefulWidget extends StatefulWidget {
  @override
  createState() {
    print('create state');
    return TestState();
  }
}
/// 创建状态管理类,继承状态测试组件
class TestState extends State<TestStatefulWidget> {
  /// 定义 state [count] 计算器
  int count = 1;
  /// 定义 state [name] 为当前描述字符串
  String name = 'test';
  @override
  initState() {
    print('init state');
    super.initState();
  }
  @override
  didChangeDependencies() {
    print('did change dependencies');
    super.didChangeDependencies();
  }
  @override
  didUpdateWidget(TestStatefulWidget oldWidget) {
    count++;
    print('did update widget');
    super.didUpdateWidget(oldWidget);
  }
  @override
  deactivate() {
    print('deactivate');
    super.deactivate();
  }
  @override
  dispose() {
    print('dispose');
    super.dispose();
  }
  @override
  reassemble(){
    print('reassemble');
    super.reassemble();
  }
  /// 修改 state name
  void changeName() {
    setState(() {
      print('set state');
      this.name = 'flutter';
    });
  }
  @override
  Widget build(BuildContext context) {
    print('build');
    return Column(
      children: <Widget>[
        FlatButton(
          child: Text('$name $count'), // 使用 Text 组件显示描述字符和当前计算
          onPressed:()=> this.changeName(), // 点击触发修改描述字符 state name
        )
      ],
    );
  }
}

上述代码把有状态组件的一些生命周期函数都进行了重写,并且在执行中都打印了一些字符串标识,目的是可以看到该函数被执行。

import 'package:flutter/material.dart';
import 'package:flutter_liftcycle_demo/pages/test_stateful_widget.dart';

/// APP 核心入口文件
void main() => runApp(MyApp());
/// MyApp 核心入口界面
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Lifecycle', // APP 名字
        theme: ThemeData(
          primarySwatch: Colors.blue, // APP 主题
        ),
        home: Scaffold(
            appBar: AppBar(
              title: Text('首页'), // 页面名字
            ),
            body: Center(
             child:
              TestStatefulWidget(),
            )
        ));
  }
}

代码修改后,我们打开手机模拟器,然后运行该 App ,在输出控制台可以看到下面的运行打印日志信息。

flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: reassemble
flutter: did update widget
flutter: build

运行结果中,打印过程可以看到是按照我们上面图 1 的执行流程在运行的,但其中最值得关注的是 build 运行了两次。这是在开发模式下才会执行的过程,在正式环境是不会出现的,因为重新渲染成本非常大,这个问题可以使用打印 build 的调用堆栈即可发现。如果你要关闭两次 build 也可以实现,在 Flutter 框架中搜索 constants.dart 文件,并找到下面这行代码,将 defaultValue 从 false 修改为 true。

const bool kReleaseMode = bool.fromEnvironment('dart.vm.product', defaultValue: true);

其实这里会触发 didUpdateWidget 函数,是因为 TestStatefulWidget 组件是 MyApp 组件中的子组件,从而导致 MyApp 函数中的 build 触发子组件 didUpdateWidget 函数的执行,具体会在下面触发组件再次 build 中详细说明。

3)、触发组件再次 build

触发组件再次 build 有三种方式: 一个是 setState ,另一个是 didChangeDependencies ,再一个是 didUpdateWidget 。

图 2 测试组件运行界面
//点击中间的text
flutter: set state
flutter: build
@override
Widget build(BuildContext context) {
  print('build');
  return Column(
    children: <Widget>[
      FlatButton(
        child: Text('$name $count'), // 使用 Text 组件显示描述字符和当前计算
        onPressed:()=> this.changeName(), // 点击触发修改描述字符 state name
      ),
      SubStatefulWidget() // 加载子组件
    ],
  );
}

接下来我们实现 SubStatefulWidget 子组件的代码,和父组件基本相似,只是在打印处都加了 sub ,其次 build 实现逻辑也修改了,具体代码如下:

import 'package:flutter/material.dart';
/// 创建子组件类
class SubStatefulWidget extends StatefulWidget {
  @override
  createState() {
    print('sub create state');
    return SubState();
  }
}
/// 创建子组件状态管理类
class SubState extends State<SubStatefulWidget> {
  String name = 'sub test';
  @override
  initState() {
    print('sub init state');
    super.initState();
  }
  @override
  didChangeDependencies() {
    print('sub did change dependencies');
    super.didChangeDependencies();
  }
  @override
  didUpdateWidget(SubStatefulWidget oldWidget) {
    print('sub did update widget');
    super.didUpdateWidget(oldWidget);
  }
  @override
  deactivate() {
    print('sub deactivate');
    super.deactivate();
  }
  @override
  dispose() {
    print('sub dispose');
    super.dispose();
  }
  @override
  reassemble(){
    print('sub reassemble');
    super.reassemble();
  }
  @override
  Widget build(BuildContext context) {
    print('sub build');
    return Text('subname $name'); // 使用Text组件显示当前name state
  }
}

代码实现完成后,我们再重新加载 App ,可以看到如下运行日志信息。

flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: sub create state
flutter: sub init state
flutter: sub did change dependencies
flutter: sub build
flutter: reassemble
flutter: sub reassemble
flutter: did update widget
flutter: build
flutter: sub did update widget
flutter: sub build

为了验证上面逻辑,我们现在再次点击图 3 中的红色部分,来触发 TestStatefulWidget 组件的 build ,看下是否会触发子组件的 didUpdateWidget 和 build。

图 3 增加子组件界面点击指示图

在运行日志窗口可以看到增加了下面的日志信息。

flutter: set state
flutter: build
flutter: sub did update widget
flutter: sub build

这就说明了父组件的变化会引发子组件的 build ,虽然子组件没有任何的改动。这点如果是在前端的话,是需要使用 shouldUpdateComponent ,来介绍重新构建,不过在 Flutter 中是没有该功能来减少重新 build 的。

4)、组件销毁触发

在上面的代码基础上,我们直接在 TestStatefulWidget 组件中注释子组件 SubStatefulWidget 的调用,然后热重载即可看到下面的日志信息(请注意一定是需要热重载才会有效果,主要目的是一开始加载了该组件,后面再去掉该组件触发)。

flutter: reassemble
flutter: sub reassemble
flutter: did update widget
flutter: build
flutter: sub deactivate
flutter: sub dispose

5) 补充: 生命周期属性

其实生命周期中initState方法前后还有两个属性调用,如下图:

1、mounted是State内部设置的一个属性,事实上我们不了解它也可以,但是如果你想深入了解它,会对State的机制理解更加清晰;

mounted属性源码

2、dirty state的含义是脏的State

3、clean state的含义是干净的State

上一篇 下一篇

猜你喜欢

热点阅读