在 Flutter 中使用 setState 時的 6 個簡單技巧

杭州程式設計師張張 發表於 2022-05-19
Flutter

setState 函式是在 Flutter 應用程式中管理狀態的最基本方法。以下是一些保持應用可維護性的最佳實踐。

StatefulWidgetsetState 函式是一種在 Flutter 應用程式中管理狀態的簡單方法。但是,當您希望您的應用程式正常工作和高效能時,您需要避免幾個陷阱。以下是您應該堅持的一些最佳實踐。

setState 有什麼用?

setState 是Flutter發出 rebuild (重建)當前 widget 及其後代的方式。在 rebuild 過程中,最新的變數值將被用於建立使用者介面。比方說,一個使用者將一個開關從開啟切換到關閉。該開關有一個儲存該值的支援變數,所以在改變之後,它被設定為 false。開關本身並不反映這一變化,直到它被重建為新的支援欄位值。

  • 更改值
  • 呼叫 setState()
  • 使用者介面已更新

💡技巧 1:保持 widgets 小!

setState 觸發了對你當前所在的小元件的重建。如果你的整個應用程式只包含一個widget,那麼整個widget將被重建,這將使你的應用程式變得緩慢。請看下面的例子。

import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  State<Home> createState() => _State();
}

class _State extends State<Home> {
  bool _tile1 = false;
  bool _tile2 = false;
  bool _tile3 = false;
  bool _tile4 = false;
  bool _tile5 = false;

  @override
  Widget build(BuildContext context) {
    print("built method Home"); // <-- setState triggers build here!
    return Scaffold(
        appBar: AppBar(title: const Text("Demo")),
        body: Center(
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile1 ? "on" : "off"}"),
                  value: _tile1,
                  onChanged: (_) {
                    setState(() {
                      _tile1 = !_tile1;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile2 ? "on" : "off"}"),
                  value: _tile2,
                  onChanged: (_) {
                    setState(() {
                      _tile2 = !_tile2;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile3 ? "on" : "off"}"),
                  value: _tile3,
                  onChanged: (_) {
                    setState(() {
                      _tile3 = !_tile3;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile4 ? "on" : "off"}"),
                  value: _tile4,
                  onChanged: (_) {
                    setState(() {
                      _tile4 = !_tile4;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile5 ? "on" : "off"}"),
                  value: _tile5,
                  onChanged: (_) {
                    setState(() {
                      _tile5 = !_tile5;
                    });
                  })
            ])));
  }
}

這裡我們在一個 Column 中有 5 個 SwitchListTile 小部件,它們都是同一個小部件的一部分。

如果您切換任何控制元件,整個螢幕都會被重建。 Scaffold, AppBar, Column, ... 但只要重建已更改的小部件就足夠了。讓我們看下一個程式碼示例:

import 'package:flutter/material.dart';

class Home2 extends StatefulWidget {
  const Home2({Key? key}) : super(key: key);

  @override
  State<Home2> createState() => _State();
}

class _State extends State<Home2> {
  @override
  Widget build(BuildContext context) {
    print("built method Home2");
    return Scaffold(
        appBar: AppBar(title: const Text("Demo")),
        body: Center(
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: const <Widget>[
              Switch(),
              Switch(),
              Switch(),
              Switch(),
              Switch()
            ])));
  }
}

class Switch extends StatefulWidget {
  const Switch({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _SwitchState();
}

class _SwitchState extends State<Switch> {
  bool _value = false;

  @override
  Widget build(BuildContext context) {
    print("build method Switch"); // <-- setState triggers build here!
    return SwitchListTile(
        activeColor: Colors.green,
        inactiveThumbColor: Colors.red,
        title: Text("Switch is ${_value ? "on" : "off"}"),
        value: _value,
        onChanged: (_) {
          setState(() {
            _value = !_value;
          });
        });
  }
}

在這裡,我們將 SwitchListTile 包裝在單個 StatefulWidget 中。頁面看起來相同,但如果您單擊此示例中的任何開關,則只有單擊的小部件將重建。

💡技巧 2:不要在構建方法中呼叫 setState

來自 Flutter API 文件

這個方法有可能在每一幀中被呼叫,除了建立一個小部件外,不應該有任何副作用。

build 方法旨在構建小部件樹,因此我們應該保持這種方式。不要在這裡做花哨的事情,它會減慢你的應用程式。對setState的呼叫可能會觸發額外的重建,在最壞的情況下,你可能最終會出現一個異常,告訴你目前有一個重建正在進行。

💡技巧 3:不要在 initState 方法中呼叫 setState

initState 將在完成後觸發重建,因此無需在此方法中呼叫 setState。此方法旨在初始化與狀態相關的屬性,例如設定預設值或訂閱流。不要在這裡做任何其他事情!

💡技巧4:setState()和setState(...)是相等的

像這樣使用 setState 沒關係

setState((){
  _text = “Hello”;
});

或者像這樣

_text = “Hello”;
setState((){});

結果是一樣的。

💡技巧 5:setState(...) 程式碼必須很小

不要在setState內做任何大的計算,因為它將阻止你的應用程式重繪。請看下面的示例程式碼:

setState(() {
  for (var i = 0; i < 10000; i++) print(i);
  _value = true;
});

只有在列印語句之後,小部件才會重建。在這段時間裡,你的應用程式不會對使用者的操作做出反應,它將在之後執行這些操作。因此,如果使用者因為沒有視覺反饋而多次點選一個控制元件,多次重建就會堆積起來,會使應用程式的速度更慢。

一個更好的方法是在執行一個長期執行的操作時顯示一個進度指示器,這樣使用者就知道正在發生一些事情,他需要等待完成。

💡技巧 6:setState(...) 程式碼不能是非同步的

執行程式碼時

setState(() async {
  await Future.delayed(const Duration(seconds: 5));
});

你最終會得到一個類似這樣的異常資訊:

在 Flutter 中使用 setState 時的 6 個簡單技巧

在方法之外執行非同步操作,然後呼叫它。

結束

我希望這些見解能幫助你更好地理解Flutter中setState的機制。堅持這些技巧,你會有更少的問題和更快的應用程式。原始碼例子可以在GitHub上找到。