Flutter空安全最小必备知识

从Flutter 2开始,Flutter便在配置中默认启用了空安全,通过将空检查合并到类型系统中,可以在开发过程中捕获这些错误,从而防止再生产环境导致的崩溃。

1. 什么是空安全

时至今日,空安全已经是一个屡见不鲜的话题,目前像主流的编程语言Kotlin、Swift、Rust 等都对空安全有自己的支持。Dart从2.12版本开始支持了空安全,通过空安全开发人员可以有效避免null错误崩溃。空安全性可以说是Dart语言的重要补充,它通过区分可空类型和非可空类型进一步增强了类型系统。

1.1 引入空安全的好处

  • 可以将原本运行时的空值引用错误将变为编辑时的分析错误;
  • 增强程序的健壮性,有效避免由Null而导致的崩溃;
  • 跟随Dart和Flutter的发展趋势,为程序的后续迭代不留坑;

2. 空安全最小必备知识

2.1 空安全的原则

Dart 的空安全支持基于以下三条核心原则:

  • 默认不可空:除非您将变量显式声明为可空,否则它一定是非空的类型;
  • 渐进迁移:您可以自由地选择何时进行迁移,多少代码会进行迁移;
  • 完全可靠:Dart 的空安全是非常可靠的,意味着编译期间包含了很多优化,
  • 如果类型系统推断出某个变量不为空,那么它 永远 不为空。当您将整个项目和其依赖完全迁移至空安全后,您会享有健全性带来的所有优势------更少的 BUG、更小的二进制文件以及更快的执行速度。

2.2 引入空安全前后Dart类型系统的变化

在引入空安全前Dart的类型系统是这样的:

意味着在之前,所有的类型都可以为Null,也就是Nul类型被看作是所有类型的子类。

在引入空安全之后:

可以看出,最大的变化是将Null类型独立出来了,这意味着Null不在是其它类型的子类型,所以对于一个非Null类型的变量传递一个Null值时会报类型转换错误。

提示:在使用了空安全的Flutter或Dart项目中你会经常看到?.、!、late的大量应用,那么他们分别是什么又改如何使用呢?请看下文的分析

2.3 可空(?)类型的使用

我们可以通过将?跟在类型的后面来表示它后面的变量或参数可接受Null:

dart 复制代码
class CommonModel { 
    String? firstName; // 可空的成员变量 
    int getNameLen(String? lastName /*可空的参数*/) { 
        int firstLen = firstName?.length ?? 0; 
        int lastLen = lastName?.length ?? 0; 
        return firstLen + lastLen; 
    } 
}

对于可空的变量或参数在使用的时候需要通过Dart 的避空运算符?.来进行访问,否则会抛出编译错误。

当程序启用空安全后,类的成员变量默认是不可空的,所以对于一个非空的成员变量需要指定其初始化方式:

dart 复制代码
class CommonModel { 
    List names=[];//定义时初始化 
    final List colors;//在构造方法中初始化 
    late List urls;//延时初始化 
    CommonModel(this.colors); 
    ...

2.4 延迟初始化(late)的使用

对于无法在定义时进行初始化,并且又想避免使用?.,那么延迟初始化可以帮到你。通过late修饰的变量,可以让开发者选择初始化的时机,并且在使用这个变量时可以不用?.

dart 复制代码
late List urls;//延时初始化 setUrls(List urls){ this.urls=urls; } int getUrlLen(){ return urls.length; }

延时初始化虽然能为我们编码带来一定便利,但如果使用不当会带来空异常的问题,所以在使用的时候一定保证赋值和访问的顺序,切莫颠倒

2.4.1 延迟初始化(late)使用范式:

在Flutter中State的initState方法中初始化的一些变量是比较适合使用late来进行延时初始化的,因为在Widget生命周期中initState方法是最先执行的,所以它里面初始化的变量通过late修饰后既能保障使用时的便利,又能防止空异常,看下具体的用法:

dart 复制代码
class _TravelPgeState extends State<TravelPge> with TickerProviderStateMixin {
    List<TravelTab> tabs = []; 
    TravelCategoryModel? travelTabModel; 
    late TabController _controller; 
    @override 
    void initState() { 
        super.initState(); 
        _controller = TabController(length: 0, vsync: this); 
    } 
    ...

2.5 空值断言操作符(!)的使用

当我们排除变量或参数的可空的可能后,可以通过!来告诉编译器这个可空的变量或参数不可空,这对我们进行方法传参或将可空参数传递给一个不可空的入参时特别有用:

dart 复制代码
get _listView => ListView(
    children: [
        BannerWidget(bannerList: bannerList), 
        LocalNavWidget(localNavList: localNavList), 
        if (gridNavModel != null) GridNavWidget(gridNavModel: gridNavModel!), 
        SubNavWidget(suNavList: subNavList), 
            if (salesBoxModel != null) SalesBoxWidget(salesBox: salesBoxModel!), 
            _logoutBtn, 
            const SizedBox( 
                height: 800, 
                child: ListTile(
                    title: Text('哈哈'), 
                ), 
             ) 
     ], 
);

上述代码是根据gridNavModel与salesBoxModel模块数据是否为空时动态创建的列表,在确保变量不为空的情况下使用了空值断言操作符!

除此之外,!还有一个常见的用处:

dart 复制代码
bool isEmptyList(Object object) {
    if (object is! List) return false; 
    return object.isEmpty; 
}

用在这里表示取反,上述代码等价于:

dart 复制代码
bool isEmptyList(Object object) {
    if (!(object is List)) return false; 
    return object.isEmpty; 
}

3. 更多空安全适配案例

3.1 自定义Widget的空安全适配

自定义Widget的空安全适配分两种情况:

  • Widget的空安全适配
  • State的空安全适配

3.1.1 Widget的空安全适配

对于自定的Widget无论是页面的某控件还是整个页面,通常都会为Widget定义一些属性。在进行空安全适配时要对属性进行一下分类:

  • 可空的属性:通过?进行修饰
  • 不可空的属性:在构造函数中设置默认值或者通过required进行修饰
dart 复制代码
/// H5容器 
class HiWebView extends StatefulWidget {
    final String? url; 
    final String? statusBarColor;
    final String? title; 
    final bool? hideAppBar;
    final bool? backForbid; 
    const HiWebView(
        {super.key, 
        this.url, 
        this.statusBarColor, 
        this.title, 
        this.hideAppBar, 
        this.backForbid}); 
        ...

Tip:如果构造方法中使用了@required那么需要改成required

3.1.2 State的空安全适配

State的空安全适配主要是根据它的成员变量是否可空进行分类:

  • 可空的变量:通过?进行修饰

  • 不可空的变量:可采用以下两种方式进行适配

    • 定义时初始化
    • 使用late修饰为延时变量
dart 复制代码
class _TravelPgeState extends State<TravelPge> with TickerProviderStateMixin {
    List<TravelTab> tabs = [];//定义时初始化 
    TravelCategoryModel? travelTabModel;
    late TabController _controller;//延时初始 
    @override 
    void initState() {
        super.initState();
        _controller = TabController(length: 0, vsync: this);
    } 
    ...
相关推荐
断竿散人几秒前
JavaScript 异常捕获完全指南(下):前端框架与生产监控实战
前端·javascript·前端框架
Danny_FD2 分钟前
Vue2 + Vuex 实现页面跳转时的状态监听与处理
前端
小飞悟3 分钟前
别再只会用 px 了!移动端适配必须掌握的 CSS 单位
前端·css·设计
安思派Anspire3 分钟前
LangGraph + MCP + Ollama:构建强大代理 AI 的关键(一)
前端·深度学习·架构
LRH4 分钟前
JS基础 - 基于 Generator + Promise 实现 async/await 原理
前端·javascript
Jolyne_4 分钟前
可配置永久生效的Table组件的封装过程
前端·react.js
断竿散人5 分钟前
JavaScript 异常捕获完全指南(上):从同步异步到 Promise 错误处理
前端·javascript·promise
肖魏眸7 分钟前
vue3 格式化 : antfu 组合 prettier & eslint & 提交格式化校验
前端·代码规范
婉婉耶7 分钟前
VUE带你乘风破浪~
前端·vue.js
橘黄的猫7 分钟前
深入浅出掌握 Git 子模块:项目管理利器
前端·github