flutter学习指南

180 阅读4分钟

初识 dart

ps: Flutter开发环境搭建的步骤可以查找我之前的文章。

学习flutter之前我们必须先学会dart,可以通过查看dart官网快速了解这门语言。如果你之前使用过typescript或者java语言,那么你将很快就能上手这门语言。

以下举例一些 dart 语言的重要特征:

异步

Future 类似于 javascript 中的 Promise, async await 语法则几乎于 javascript 中一致。

Future<void> printWithDelay(String message) async {
    await Future.delayed(Duration(seconds: 1)); // 等待1s
    print(message);
}

等同于

Future<void> printWithDelay(String message) async {
    Future.delayed(Duration(seconds: 1)).then((_){
        print(message);
    })
}

异常处理

通常我们可以使用 async 和 await 来控制异步流程,使用 try 语句配合 on 或 catch (两者也可同时使用)关键字来捕获一个异常.

void main() {
  try {
    // 可能会抛出异常的代码
    int result = 10 ~/ 0; // ~/ 除法,结果返回整数,故意除以0,会抛出除零异常
    print("Result: $result");
  } catch (e) {
    // 捕获并处理异常
    print("Exception caught: $e");
  } finally {
    // 无论是否发生异常,都会执行该块中的代码
    print("Finally block executed");
  }
}

空安全(null-safety)以及一些语法糖

变量声明的时候默认是非空的,如果你想让变量可以为 null,只需要在类型声明后加上 ?。

int x = 1; // 默认不为空,必须在声明的时候初始化
int? a = null

如果不知道可空类型的表达式是否等于 null,可以使用条件属性访问,操作符 ?.。

fn?.action(); // 如果 fn 为 null,则返回 null,否则调用 action()

避空运算符,??= 赋值运算符,仅当该变量为空值时才为其赋值

int? a; 
a ??= 3;
print(a); // 3

级联,要对同一对象执行一系列操作,请使用级联(..)。

myObject..someMethod()

虽然它仍然在 myObject 上调用了 someMethod,但表达式的结果却不是该方法返回值,而是是 myObject 对象的引用!

querySelector('#confirm')
  ?..text = 'Confirm'
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'))
  ..scrollIntoView();

在第一个级联位置,使用 空判断 级联操作符 (?..),它可以确保级联操作均在实例不为 null 时执行

Stream 数据流

Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。

1、可以使用异步 for 循环 await for ,来替代 Stream API。

Future<void> readFileAwaitFor() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();
  // 使用 transform() 方法生成具有不同类型数据的流
  // 将转换后的数据变换成一个 LineSplitter 执行
  var lines = inputStream.transform(utf8.decoder).transform(const LineSplitter());
  try {
    await for (final line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

异步 for 循环,通过 try-catch 来处理错误。代码位于异步 for 循环之后,会在 stream 被关闭后执行。

2、使用 listen 方法来订阅一个Stream并监听其中的事件,使用 onError 方法来捕获并处理错误。

import 'dart:async';

void main() {
  Stream<int> stream = countStream(5);

  stream.listen(
    (event) {
      print('Received event: $event');
    },
    onError: (error) {
      print('Error occurred: $error');
    },
  );
}

Stream<int> countStream(int count) async* {
  for (int i = 1; i <= count; i++) {
    if (i == 3) {
      throw Exception('An error occurred');
    }
    yield i;
    await Future.delayed(Duration(seconds: 1));
  }
}

初识 widget

总结起来就一句话,Flutter 中是通过 Widget 嵌套 Widget 的方式来构建UI和进行事件处理的,Flutter 中万物皆为Widget。

写构建 Flutter 应用的时候,我们经常会看到 StatelessWidget 和 StatefulWidget。

StatelessWidget

StatelessWidget 一般用于不需要维护状态的场景(类比 React 中无状态组件的概念)。它通常在build方法中通过嵌套其他 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget。build方法有一个context参数,它是BuildContext类的一个实例,表示当前 widget 在 widget 树中的上下文,每一个 widget 都会对应一个 context 对象(因为每一个 widget 都是 widget 树上的一个节点)。

class MyApp extends StatelessWidget {
    const MyAppBar({super.key});
    
    @override
    Widget build(BuildContext context) {
        return Text('文字');
    }
}

StatefulWidget

StatefulWidget 会对应一个 State 类,State表示与其对应的 StatefulWidget 要维护的状态。在 Widget 构建的时候可以读取到 state 里面的信息,当我们调用 setState 方法时可以更新 state,并且通知 Flutter 重新调用 build 方法来更新 UI。

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

这段代码是使用 flutter create project 命令生成的官方基础代码。State 中有两个常用属性:

  1. context,功能前文以后说过了,同 StatelessWidget 中的 BuildContext。
  2. widget,它表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。我们可以看到示例代码中的用法 “widget.title”, 在 state 类里面,我们访问了与它对应的 widget 的属性。

State 生命周期

对应web开发中组件的生命周期,深刻理解这些函数的调用时机对于我们优化代码至关重要。

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

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

  • build():主要是用于构建 widget 子树。

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

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

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

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

通常我们会在 initState 中初始化状态,调用 setState 改变状态并更新UI,在 dispose 中我们销毁一些控制器等来释放资源,这些内容在后面我们会经常看到。