Flutter开发 -- 需要了解的Dart知识

纯是水字数的,要好好学的同学还是看官网的好,哈哈~~

虽然Dart是基础,但是先跟着Flutter官网做一个"第一个Flutter App"也不耽误,有些人是了再回头专门学Dart还更省时间。

变量

在类型安全的Dart代码中,要声明一个变量也只需要用var。有了类型推断,这些变量的类型会通过初始值确定下来。

dart 复制代码
var name = "hello, world!";

const

可以使用var声明一个变量。也可以使用const来声明一个常量。

const声明的常量在Flutter的widget里面更是常用。这里先简单介绍一下,后面有详细的介绍。这样的常量是编译期常量值。也就是在编译的时候就是一个常量了。这个编译期的常量怎么理解,看这个例子:

dart 复制代码
const a = 5;
const b = DateTime.now(); // 报错:Const variables must be initialized with a constant value.

表达式a = 5好说,编译的时候就知道这个值,而且不会改变。对于DateTime.now()这个表达式是无法编译通过的。因为b的值只有在运行的时候执行了DateTime.now()才知道值是什么。

final

这个就可以弥补const的不足。比如:

dart 复制代码
final someDate = DateTime.now(); // OK

final修饰的常量就是在这个常量初始化之后就不可变更的。

Function

一个简单的例子:

dart 复制代码
int add(int a, int b) {
    return a + b;
}

还可以更加简化:

dart 复制代码
int add(int a, int b) => a + b;

传递参数

最简单的:

dart 复制代码
void debugger(String message, int lineNum) {
    print("$message, in line $lineNum");
}

// 调用一把
debugger("A bug", 55);

使用命名参数:

dart 复制代码
void debugger({String? message, int? lineNum}) {  print("$message, in line $lineNum");}  

debugger(message: "A bug", lineNum: 5);

注意 :如果没有使用required修饰,那么那个参数就需要定义成nullab的类型。或者,给那个参数一个默认值。

还可以定义可选位置参数,使用中括号括起来,如:

dart 复制代码
void debugger(String message, int? lineNum, [String device = ""]) {
  print("$message, line number: $lineNum");
}

在"第一个Flutter App"我们已经见识到了继承了StatelessWidget和StatefulWidget的类了。Dart的类(Null除外)都继承了Object类。

构造函数

定义类最关键是需要一个构造函数。一般定义的方式是用ClassName或者ClassName.identifier的方式。比如:

dart 复制代码
class TodoItem {  
  final int id;  
  
  const TodoItem({    
      required this.id,  
  });  
  factory TodoItem.fromJson(Map<String, dynamic> json) {   
      // return TodoItem(/*... */);  
  }
}

final todoItem = TodoItem({id: 1});  
final p = Map<String, dynamic>{"id": 1};final todoItem = TodoItem.fromJson(p);

私有成员

在Dart里,私有成员以"_"开头。

注意:这里的私有成员和常遇到的私有不是一回事。在Dart的类里的私有成员不是指类的外部不能访问这个私有成员,而是指在同一个包(Library)里可以访问,其他的包不能访问。一个Flutter的app就是一个包,虽然从来没有这么称呼过一个Flutter app。

const, final成员变量

定义类的实例常量:

dart 复制代码
class A {
  const a = 5; // 报错,only static fields can be declared const
}

只有静态成员可以用const修饰!要么换成final,要么在字段前面加static.

只能这样用:

dart 复制代码
Row(
  children: [
    const Image(image: NetworkImage('https://image-address')),
    const Text("Image description"),
  ]
)

对于final来说就没有那么多限制了。这样可以:

dart 复制代码
class A {
  final a = 5;
  final b = DateTime.now();
}

这样也可以:

dart 复制代码
class A {
  static final a = 5;
  static final b = DateTime.now();
}

但是,final修饰的成员变量只能被赋值一次。这一点还是没有变。其他特殊情况且听下文分解。

late成员

late这个关键字会经常遇到。

当你有了一个成员变量了,但是要稍微晚一点再初始化就会用导致换个关键字了。如:

dart 复制代码
class CounterButton extends StatelessWidget {
  String someText; // 1
  CounterButton({super.key}) { //报错:Non-nullable instance field 'someText' must be initialized.
    someText = "|";  // 2
  }

  @override
  Widget build(BuildContext context) {
    final counterProvider = CounterProvider.of(context);
    return         Text("${someText ?? "|"}"), // *
        // 略
      ],
    );
  }
}

在【1】定义了一个成员变量someText,这个变量不是可空的。在构造函数那里就会报错了。你打算在构造函数里初始化这个变量的。但是不行,报错。

那就只好late出场了。

dart 复制代码
class CounterButton extends StatelessWidget {
  late String someText; // 这里加上late关键字
  CounterButton({super.key}) { 
    someText = "|";  // 在构造函数中初始化
  }
}

还有一个关于late很有趣的例子:

dart 复制代码
class AnyWidget extends StatelessWidget {
  late String aString;

  AnyWidget({super.key}){
    print("constructor started");
    aString = getAStringSomewhere(); // *
    print("constructor finished");
  }

  String getAStringSomewhere(){
    print("we're generating a string now");
    return "Hello World";
  }

  @override
  Widget build(BuildContext context) {
    print("build");
    return Text(aString);
  }
}

来看打印的结果: constructor started we're generating a string now constructor finished build

在构造函数中使用了方法getAStringSomewhere()给变量aString赋值,所以在构造函数执行的过程中打印出了对应的字符串。再看一个例子:

dart 复制代码
class AnyWidget extends StatelessWidget {
  late String aString = getAStringSomewhere();

  AnyWidget({super.key}) {
    print("constructor finished");
  }

  String getAStringSomewhere() {
    print("we're generating a string now");
    return "Hello World";
  }

  @override
  Widget build(BuildContext context) {
    print("build"); 
    return Text(aString); // *
  }
}

build方法中使用了成员aString, 这个时候调用了方法getAStringSomewhere(),所以对应的字符串是在build执行的过程打印出来的。

由此可以得出结论,late修饰的变量在使用的时候才去调用初始化它的方法。

Mixin

定义一个mixin

现在我们有一只鸟,但是还要让它会写博客,会打游戏。

一只鸟:

dart 复制代码
class Bird {
  void fly() {
    print("A bird is flying");
  }

  void chirp() {
    print("A bird is chirping");
  }
}

定义一个写博客的mixin

dart 复制代码
mixin Writer {
  void writ({required String text}) {
    print("Writing something $text");
  }
}

再来定义一个打游戏的mixin。

dart 复制代码
mixin Game {
  void play({required String gameName}) {
    print("Play a game named $gameName");
  }
}

然后把这两个功能作用到鸟身上:

dart 复制代码
class SpecialBird extends Bird with Writer, Game {}

Dart是不支持多继承的,所以使用mixin可以很好的弥补这个缺陷。

使用on关键字

在定义mixin的时候使用on可以让你的mixin只作用在特定的继承体系下。比如,要限制写博客只能是鸟,那么可以这样:

dart 复制代码
mixin Writer on Bird {
  void writ({required String text}) {
    print("Writing something $text");
  }
}

这时,你想让老虎也来写博客就不好使了:

dart 复制代码
class Tiger with Writer {} // 报错

扩展类型

扩展类型就是用来给一个已经存在的类型增加方法的,而且使用扩展类型还不需要修改已经存在的类型。扩展类型不必和已有类型定义在一起,通常也不定义在一起。毕竟,有些已经存在的方法是Dart、Flutter内置的或者更多是第三方库的没法直接修改。

定义扩展类型

dart 复制代码
extension ExtensionName on <Type> {
  // 各种方法 
}

ExtensionName你的扩展的名字,Type就是你要扩展的类型的名字了。 来看一个例子:

dart 复制代码
extension MyExtension on String {
    int get lengthPlus => this.length + 1;

    String capitalize() {
        return "${this[0].toUpperCase()}${this.substring(1)}";
    }
}

上例中,定义了一个叫做MyExtension的扩展。它扩展了String类型。给String类型增加了一个capitalize方法。

重名了怎么办

类型扩展的多了,难免会遇到重名的情况。

类型的扩展定义的多了难免出现的就是出现方法同名的情况。比如,string_apis.dartstring_dart2.dart 都定义了一个String的扩展叫做NumberParsing,里面都提供了一个方法parseInt。在调用的时候会出现错误,如:

dart 复制代码
import 'package:example_app/extensions/string_apis.dart';
import 'package:example_app/extensions/string_apis2.dart';
// 略

class MyButton extends StatelessWidget {
  const MyButton({super.key});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        print('${"100".parseInt()}'); // 报错
      },
      child: const Text('Press me'),
    );
  }
}

现在来处理一下这个问题:

  1. 使用hide关键字隐藏掉一个扩展,如:
dart 复制代码
import 'package:flutter_todo_wasm/extensions/string_apis.dart';
import 'package:flutter_todo_wasm/extensions/string_apis2.dart' hide NumberParsing;
  1. 如果定义的两个字符串的扩展名字不同的话,比如一个是NumberParsingNumberParsing2。那么可以直接使用扩展名称来调用parseInt方法来避免同名冲突。
dart 复制代码
import 'package:flutter_todo_wasm/extensions/string_apis.dart';
import 'package:flutter_todo_wasm/extensions/string_apis2.dart';

// 略

onPressed: () {
    print('${NumberParsing("100").parseInt()}');
    print('${NumberParsing2("100").parseInt()}');
},
  1. 给其中的一个字符串的扩展起一个别名
dart 复制代码
import 'package:flutter_todo_wasm/extensions/string_apis.dart';
import 'package:flutter_todo_wasm/extensions/string_apis2.dart' as rad;

// 略

onPressed: () {
    print('${NumberParsing("100").parseInt()}');
    print('${rad.NumberParsing2("100").parseInt()}');
},

异步编程

异步编程的重要性就不多说了,懂得都懂。我们来看看Flutter有哪些异步编程的知识。

Future

Flutter的异步编程是基于Future这个概念的。一个Future代表了一个不会立刻出现,在不远的未来才会出现的值或者错误。比如一个网络请求:

dart 复制代码
Future fetchTodoList({}) {
  return Future.delayed(Duration(seconds: 2), () => '从你的后端获取的数据');
}

async await

使用Future看起来解决了一个返回一个将来才能拿到值的问题。但是,Future有一个不大不小的问题,就是如果有多个Future有依赖关系的时候会有嵌套Future,会有很多的缩进,最后那些代码变得难以阅读。就像callback hell一样。

所以,dart引入了async-await。这样多个有依赖关系的Future看起来就像是一段顺序执行的代码:

dart 复制代码
Future<void> fetchDataAndPrint() async {  
  var data = await fetchData();  
  print(data);  
}

async关键字用来表示一个方法是异步的,await关键字则是用来等待Future的值的。而且还不会阻塞整个app的运行。整个app的main方法也可以是async-await的:

dart 复制代码
void main() async {  
  print("Fetching data...");  
  var data = await fetchData();  
  print("Data received: $data");  
}

在Widget里使用async await

既然是基于Future的那必定是FutureBuilder了。

dart 复制代码
FutureBuilder<String>(
  future: fetchData(), // 1
  builder: (context, snapshot) { // 2
    if (snapshot.connectionState == ConnectionState.done) {
      return Text(snapshot.data ?? 'Error fetching data');
    } else {
      return CircularProgressIndicator();
    }
  },
)

future属性提供一个异步方法,它返回一个Future

builder中,根据snapshotconnectionState的值判断future返回的状态返回不同的widget。

错误处理

Flutter提供了优雅的处理异步编程中的异常的方法。那就是try, catch和finally

dart 复制代码
Future<void> fetchDataWithException() async {  
  try {  
    var data = await fetchDataWithError();  
    print(data);  
  } catch (error) {  
    print("Error: $error");  
  }  
}

to be continued

相关推荐
wuhanwhite4 小时前
Flutter Release 打包后插件失效问题排查与解决(实战分享)
flutter
BG4 小时前
Flutter Svg转Path对象,path.getBounds()获取测量信息不准问题记录
flutter
MaoJiu5 小时前
Flutter中实现Hero Page Route效果
flutter
神经骚栋5 小时前
Flutter面试题01-Flutter中的三棵树
flutter·面试
小严家1 天前
Flutter完整开发指南 | Flutter&Dart – The Complete Guide
开发语言·flutter
倾云鹤1 天前
flutter实现Function Call
flutter·llm·function call
程序员老刘·2 天前
Flutter版本选择指南:避坑3.27 | 2025年9月
flutter·跨平台开发·客户端开发
懒得不想起名字2 天前
Flutter二维码的生成和扫描
flutter
鹏多多2 天前
flutter-详解控制组件显示的两种方式Offstage与Visibility
前端·flutter