Flutter开发中遇到问题想明白的知识点

357 阅读7分钟

const vs final

Dart中const和final关键字修饰变量。final变量代表仅仅能单次赋值,第二次赋值的时候,IDE编译器会提示报错,final变量代表只能赋值一次。const变量代表编译时期常量,const变量是隐式的final变量, const编译时复制,不能够在运行时改变const变量的值。final变量可以理解为运行时变量,const变量可以理解为编译器变量。final变量赋值可以使用const变量以及其他变量,甚至方法(动态计算)的返回值;const变量赋值不能通过final变量和方法。构造方法如何使用const修饰时候,相应类中的属性都必须被设置成final。实例属性不能不设置为const,因为实例属性是运行时才能确定的,不能使用编译时期变量。虽然final对象不能被修改,但其字段可以更改。相比之下,const对象及其字段则不能被更改:它们是不可变的。

void main() {
  const age = 10;
  const doubleAges = age * 2;
  final twoAge = doubleAge(age);
  const person = Person(age, "Bob");
  const baz = [1 ,  2 , 3]; // Equivalent to `const []` = [1, 2, 3];
  baz[1] = 4;
  print(baz);
}

int getAge(int age) {
  return age;
}

int doubleAge(int age) {
  return age * 2;
}

class Person {
  final int age;
  final String name;
  const Person(this.age, this.name);
}

示例代码如上所示,baz[1] = 4;这行会报错,const修饰的集合也是不能修改的。应用场景方面:const通常用于那些在编译时就已经知道的值,比如数学常数,或者配置固定的值。而final可能用于需要在运行时才能确定的情况,比如从API获取的数据,或者某个计算后的结果。接下来,实例化对象的时候,const可以用来创建编译时常量的对象,这样多个地方的const构造会指向同一实例,节省内存。而final虽然对象不可变,但每次初始化都会生成新实例。在Flutter中,const常用于widget的实例化,提高性能,因为可以避免重复创建相同实例。赋值时机:final可在声明时、构造函数或代码块中初始化,const必须在声明时直接赋值。

占满剩余屏幕的宽高

在Flutter中,double.infinity是一个特殊的数值,表示无限大。虽然它在数学上无意义,与约束系统(Constraints)配合时能实现灵活的布局效果。可以占用剩余空间的所有空间。示例代码如下:

Container(
  width: double.infinity, // 横向填满父容器
  color: Colors.blue,
  child: const Center(
    child: Text("水平居中且宽度填满"),
  ),
)

Flutter的布局系统基于约束传递,使用double.infinity不会导致性能问题,因为它只是表达一种布局意图。但需确保不违反约束规则,否则会抛出异常(如 Unbounded height)。

使用Expanded明确父级约束,Expanded类是Row、Column或Flex的子项的小部件,以便Expanded填充屏幕可用空间。 示例代码如下:

import 'package:flutter/material.dart';

main() {
  runApp(DoubleInfinity());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            Text("data1"),
            Text("data2"),
            Text("data3"),
            Text("data4"),
            Expanded( // 占据 Column 剩余高度
              child: Container(
                color: Colors.purple,
              ),
            ),
          ],
        )
        ,
      ),
    );
  }
}

还有一个知识点需要注意:动态布局适配适配,结合LayoutBuilder动态响应父级约束,实现自适应布局。示例:根据父级宽度按比例布局,如何宽度全部占用就使用double.infinity。示例代码如下:

LayoutBuilder(
  builder: (context, constraints) {
    return Container(
      width: constraints.maxWidth * 0.8, // 父级宽度的 80%
      height: 100,
      color: Colors.yellow,
    );
  },
)

热重载vs热重启vs重新启动应用

Flutter开发过程中需要注意软件重新运行的三种方式:热重载(Hot Reload)、热重启(Hot Restart)和重新启动应用(Full Restart)。热重载是注入代码并保持状态,热重启是重新运行应用但保持部分状态(不包含页面状态),而重新启动应用是完全关闭再启动,重置所有状态。运行时长:Flutter开发过程中编译过程是增量编译的,可以理解为只编译修改的代码,这样热重载运行时长能够达到秒级;热重启需要编译全量的代码,时长就到达了十秒级;重新启动应用需要编译全量代码的同时,还需要生成相应的应用,然后再安装到相应的机器上,时长就达到了分钟级别。从时长上选择的话,我们尽量选择耗时短的,如果实在不行再依次选择耗时较长的。

重载类型适用场景
热重载(Hot Reload)修改UI样式(颜色、布局、字体等),调整业务逻辑(函数内部代码、条件判断),更新setState触发的状态管理逻辑
热重启(Hot Restart)修改全局变量或静态字段的初始值,调整 main() 函数或根 Widget(如 MaterialApp 配置),新增/删除类或方法(无需修改原生代码),Hot Reload 失效但不想完全重启时
重新启动应用(Full Restart)修改 pubspec.yaml(添加/删除依赖包),修改原生代码(Android/iOS 目录中的文件),更改 Flutter 插件或通道(Platform Channel)逻辑,应用崩溃或陷入不可恢复的状态
通过合理使用这三种机制,可以最大化开发效率,减少等待时间,同时确保代码变更正确生效。

高阶函数

高阶函数通常是指那些接受其他函数作为参数,或者返回函数的函数。在Dart里,函数是一等公民,所以高阶函数应该是支持的。Dart中的函数特性

  1. 支持匿名函数(Lambda 表达式):(参数) => 表达式 或 (参数) { 代码块 }。
  2. 函数可赋值给变量:Function myFunc = () {};。
  3. 闭包(Closure):函数可捕获并访问其词法作用域外的变量。

高阶函数对集合的处理,示例代码如下:

  List<int> numbers = [1, 2, 3, 4, 5];

// 使用高阶函数处理集合
  var doubled = numbers.map((n) => n * 2).toList(); // [2, 4, 6, 8, 10]
  var even = numbers.where((n) => n % 2 == 0).toString(); // [2, 4]
  var sum = numbers.reduce((a, b) => a + b); // 15

Widget事件回调:Flutter的UI组件通过高阶函数实现交互逻辑,如按钮点击、滑动事件,示例代码如下:

ElevatedButton(
  onPressed: () { // 接受一个函数参数
    print('Button pressed!');
  },
  child: Text('Click Me'),
)

上面代码onPressed的实参是一个匿名函数,匿名函数没有返回值和方法名称。 在Flutter(Dart)中,typedef(类型别名)是一个用于简化复杂类型声明、提高代码可读性和复用性的关键字。它常用于为函数类型或泛型类型定义别名,尤其在处理回调函数、事件处理器和复杂数据结构时非常实用。 示例代码如下:

/// Signature of callbacks that have no arguments and return no data.
typedef VoidCallback = void Function();

import 'package:flutter/material.dart';

class ResuableCard extends StatelessWidget {
  final Color colour;
  final Widget cardChild;
  final void Function() onPress;

  ResuableCard({required this.colour, required this.cardChild, required this.onPress});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onPress,
      child: Container(
        child: cardChild,
        decoration: BoxDecoration(
          color: colour,
          borderRadius: BorderRadius.circular(10),
        ),
        margin: EdgeInsets.all(20),
      ),
    );
  }
}

上面的代码在开发过程中需要注意onPress应该声明为void Function()类型。 在异步高阶函数中使用try-catch捕获异常,或返回Future结合async/await处理。高阶函数常伴随闭包使用,闭包允许函数捕获外部变量,但需注意变量生命周期,避免内存泄漏。 高阶函数的最佳实践:

  1. 命名明确:高阶函数名应清晰表达其用途(如 map、filter)。
  2. 避免过度嵌套:多层高阶函数可能降低可维护性。
  3. 类型安全:使用泛型(Generics)确保输入/输出类型正确。
  4. 性能优化:避免在 build() 方法中频繁创建高阶函数(可能导致不必要的重建)。

高阶函数是Flutter开发中提升代码质量和开发效率的利器。通过熟练运用集合操作、事件回调和自定义高阶函数,开发者可以编写更简洁、灵活的代码,同时与Flutter的响应式架构深度结合。掌握高阶函数的使用场景与最佳实践,将显著提升复杂应用的构建能力。

参考资料

dart.dev/effective-d… dart.dev/language/va… dart.dev/effective-d… medium.com/@dnkibere/p…