• 作者:老汪软件技巧
  • 发表时间:2024-09-01 04:01
  • 浏览量:

今天,我们来回顾一下 Flutter 中对于保证 widget 交互稳定性起着重要作用的一个属性。该属性的名称是 mounted。

什么是已 mounted 属性?

Mounted 属性是 Flutter 框架在 State 类中提供的布尔标志。它指示状态对象当前是否在小部件树中。

创建 State 对象之后、调用 initState 之前,框架通过将 State 对象与 BuildContext 关联来“mounted”该对象。 State 对象保持挂载状态,直到框架调用 dispose,之后框架将不再要求 State 对象再次构建。换句话说,当一个 widget 从 widget 树中删除时,其关联的状态对象也被删除,并且 mounted 属性被设置为 false。

mounted 属性的目的是什么?

Mounted 属性的主要目的是帮助您管理状态并避免与不再属于小部件树一部分的小部件交互时可能发生的潜在错误。具体来说,mounted 在涉及异步操作或延迟操作的场景中很有用。

何时使用 Mounted 属性?

我认为应该把第一名让给异步操作。

当执行异步操作(例如网络请求、数据库查询)时,一旦操作完成,状态对象可能会被更新。但是,如果在操作完成之前将小部件从小部件树中删除,则尝试更新状态可能会导致运行时错误。在这种情况下,mounted 属性允许您在进行任何更新之前检查状态对象是否仍然处于活动状态。

String _data = '';
bool _isFetching = false;
@override
 void initState() {
   super.initState();
   _fetchData();
 }
void _fetchData() async {
  // assuming that _isFetching is a boolean that tracks the loading state
  setState(() {
    _isFetching = true;
  });
  try {
    final data = await fetchDataFromServer();
    if (mounted) {
      setState(() {
        _data = data;
        _isFetching = false;
      });
    }
  } catch (error) {
    if (mounted) {
      setState(() {
        _isFetching = false;
        _error = error;
      });
    }
  }
}

异步操作完成后,我们在调用 setState 之前检查 Mounted 属性。这确保了小部件仍然是小部件树的一部分。

为什么这很重要?同样,如果小部件已从树中删除(例如,由于导航离开页面或小部件被处置),调用 setState 将导致运行时错误。在这里,我们的辅助 Mounted 属性通过确认小部件仍然处于活动状态并且可以安全更新来防止这种情况发生。

让我们进入下一个环节:计时器和延迟操作、动画。

当使用计时器、延迟操作或动画时,小部件可能会在计时器或操作完成之前被丢弃。如果在调用 setState 之前检查 mounted 属性,就能确保只有在部件仍处于活动状态时才进行更新。

void _startTimer() {
  Timer.periodic(Duration(seconds: 1), (timer) {
    if (mounted) {
      setState(() {
        _counter++;
      });
    }
  });
}

在 dispose 方法中,清理任何资源或取消正在进行的操作至关重要。由于当小部件即将从小部件树中删除时会调用 dispose,因此最好在执行可能依赖于小部件状态的操作之前检查 mounted 属性是一个很好的做法。

@override
void dispose() {
  _timer?.cancel();
  super.dispose();
}

另一个是:Navigator。

如果要执行可能被导航中断的异步操作(如使用 Navigator.push 或 Navigator.pop),请确保在更新状态前挂载部件。

Future<void> _navigateAndFetchData() async {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => AnotherPage()),
  );
  final result = await someAsyncOperation();
  if (mounted) {
    setState(() {
      _data = result;
    });
  }
}

我想讨论的最后一个是:具有异步初始化的有状态小部件。

对于在 initState 中执行异步操作的有状态小部件,最好使用 Mounted 属性来确保仅在小部件处于活动状态时才发生更新。

@override
void initState() {
  super.initState();
  _initializeAsync();
}
Future<void> _initializeAsync() async {
  final data = await fetchData();
  if (mounted) {
    setState(() {
      _data = data;
    });
  }
}

好吧好吧,这是最后一篇:长时间运行的操作。

对于长时间运行的任务,例如文件 I/O 或计算,您可以在调用 setState 之前使用 Mounted 属性来确保 UI 更新是安全的。

Future<void> performLongRunningTask() async {
  await longRunningTask();
  if (mounted) {
    setState(() {
      _taskCompleted = true;
    });
  }
}

为什么要使用 Mounted 属性?

如果总结我们的讨论,我们得到的是:

什么时候 mounted 属性可能不是必需的?

同步操作:用于不涉及异步任务或后台操作的纯同步操作。然而,确保适当的资源管理并避免在小部件处置期间更新状态仍然很重要。

简单小部件:适用于不涉及任何异步逻辑、后台任务或复杂生命周期管理的简单小部件。

在 Flutter 3.7 中使用 context.mounted

在 Flutter 3.7 之前,无法检查 Flutter 中是否安装了 StatelessWidget。这只能在 State/Stateful 小部件上实现。

但从Flutter 3.7开始,BuildContext 本身就有一个 mounted 属性。

这使得检查是否安装了任何小部件变得很容易,并且您可以从任何地方检查它,无论它来自 StatefulWidget State 还是来自 Stateless widget,就像这样:

// inside any widget
@override
Widget build(BuildContext context) {
  return ElevatedButton(
    child: const Text('Submit'),
    onPressed: () async {
      await doSomeAsyncWork(); // a method that returns a Future
      if (context.mounted) {
        Navigator.of(context).pop();
      }
    },
  );
}

我在 StackOverflow 上看到很多关于无论如何都要在任何地方使用 mounted 属性的必要性的问题。请记住,使用 mounted 属性不是强制性的,但这是一个很好的做法。在特殊情况下使用的良好实践。

原文:/@wartelski/…