iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0
Mobile Development

Flutter 30: from start to store系列 第 10

Flutter介紹:組件狀態管理

  • 分享至 

  • xImage
  •  

今天預計跟大家一起探討什麼是組件的狀態,以及如何操作組件的狀態,分為下列部分:

  • state
  • prop
  • StatelessWidget
  • StatefulWidget
  • stateless和stateful的使用時機

好的,那我們就開始吧!


state

  • 事物的「狀態」,大致上是指「綜合所有可以表達目前目標狀況的資訊」。例如我要顯示「感冒」的狀態,可以由「頭痛」、「喉嚨痛」、「流鼻水」等症狀表達出來。

  • 一個組件的狀態則由這個組件內部所含的資料所表示。比如說一個最簡單的開關,現在的狀態是「開」?還是「關」?那就是由這個開關內部紀錄的資料來決定。

    class Switch {
        bool isOpen = false; // 當前開關是否打開(否)
        Switch();
    
        // 切換開關
        void toggleSwitch(){
            isOpen = !isOpen;
        }
    }
    
    var fancySwitch = Switch();
    print(fancySwitch.isOpen); // false
    
    fancySwitch.toggleSwitch();
    print(fancySwitch.isOpen); // true
    

    如上,我定義的switch class內部有fieldisOpen可以紀錄開關是否開啟,又定義method toggleSwitch來調整isOpen,切換開關是否開啟。

    這時候我們就說isOpen表達了這個開關的狀態,是這個switch的state。

  • 當然一個組件可以不只有一個state,大多數時候都是綜合多筆資料一起表達組件狀態。


prop

  • 相對於state是組件內部紀錄的資料,prop是組件外部傳進來的資料。
  • 組件被建立後,可以自由內部更改的state,但無法更改props。
  • 以上述開關為例,
    class Switch {
        final bool isOpen;
    
        Switch(this.isOpen);
    }
    
    var switchClose = Switch(false); // 關閉的開關,外部給予開關組件'false'值
    
    var switchOpen = Switch(true); // 開啟的開關,外部給予開關組件'true'值
    
    可以看到這個例子的區別:「是開是關」的資料並不是內部建立並操控的,而全然是由外部提供的。Switch只負責依照外部提供的資料來建立對應的開關而已。

StatelessWidget

  • StatelessWidget是一個不需要自己紀錄內部狀態的組件,需要的資料只能靠外部提供。

    class Title extends StatelessWidget {
      final String text;
      const Title({super.key, reqiured this.text});
    
      @override
      Widget build(BuildContext context) {
        return Text(
        text,
        style: const TextStyle(
          fontSize: 40,
          fontWeight: FontWeight.w900,
        ),
      ),
    }
    

    如上例,我建立一個Title的無狀態組件,這個title不需要變動內部的資料,只要把交給它的text以指定的樣式呈顯就可以了。此時text就是這個Title的prop。

    這樣一個Title組件就可以重複使用,並依照外部交給他的prop去改變標題的內容,不必每次都把要顯示的標題寫死在組建裡面。

    Title(text:'今日標題');  
    Title(text:'明日標題');    
    

StatefulWidget

  • StatefulWidget是有自己內部狀態的組件,可以建立自己的state也可以接受外部的prop,一個StatefulWidget會分開定義「widget」和「widget所使用的狀態」。

    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
    
      final String title; // 外部傳入的props
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    

    以widget來說,widget在創建時可以接受外部prop,並且在此透過createState定義自己的state操作

    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0; // state
    
      void _incrementCounter() {
        setState(() {
          _counter++; // 更動自己的state
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title), // 使用widget取得的prop
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter', // state透過Text組件顯示在畫面上,一旦state更新會觸發Text組件的重製
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter, // 調用變更state的_incrementCounter函式
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    
    

    而在State class這邊,透過setState() 來改變內部的狀態,標註所有與「被改變的state」相關的組件為待更新,並在build()觸發時重繪被標註的組件,達到變更畫面的效果。

  • 而為什麼StatelessWidget只要一個class,而StatefuleWidget要特別拆成兩個class來處理呢?

    由於Flutter 的widget創建後內部的資料是不能更動(immutable)的,要更改畫面必須重新創建,因此採用「創建widget」之後「注入state」方法,由state來掌管可變的狀態。

    此外將state與widget拆分,也可以確保每次widget重製時,state可以獨立於widget之外仍然保留狀態。

    這在官方文件有所說明。


stateless和stateful的使用時機

  • 那麼,StatelessWidgetStatefulWidget的使用時機分別為何呢?這點也沒有一定的規則,只能依當下的情況由開發者自行設計考量。

  • 比如我可以讓一個開關組件不自己紀錄開關狀態,把資料記在外部,讓其他使用Switch widget的組件告訴這個組件應該開還是關:

    class Switch extends StatelessWidget {
      final bool isOpen;
    
      const Switch({super.key, 
        required this.isOpen,
      });
    
      @override
      Widget build(BuildContext context) {
        return Text(isOpen?'Open':'Close');
    }  
    
    var openSwitch = Switch(true); // 需要顯示一個「開」狀態的開關
    
    
  • 我也可以讓Switch自己紀錄自己的開關狀態,調用自己的方法來切換開關

    class Switch extends StatefulWidget {
      const Switch({super.key});
    
      @override
      State<Switch> createState() => _SwitchState();
    }
    
    class _SwitchState extends State<Switch> {
      bool _isOpen = false;
    
      void toggleSwitch() {
        setState(() {
          _isOpen = !_isOpen;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return ElevatedButton(
            onPressed: toggleSwitch,  // 按下開關就會切換開/關
            child: Text(_isOpen ? 'Open' : 'Close')); 
    
      }
    }
    

    不過使用StatefulWidget時要特別小心,由於state變更時,內部所有的結構都會被重新繪製,需要被繪製的組成愈多,就愈耗效能。因此應該盡量只讓需要變動的組件採用StatefulWidget,而可以單純依照外部資料顯示的組件採用StatelessWidget,才能最有效地分配運算資源。

    例如:「開關」會有狀態的切換的情境,可以使用StatefuleWidget;而「文字」、「圖形」等組件不用有太大的變動,只要內容傳進去就可以顯示了,相較之下更適合使用StatelessWidget

    不過這些都沒有一定的標準。最後決定要把一個Widget設計成Statefule或Stateless,除了考量效能,也取決於我們想把邏輯封裝在哪一個層級的組件中,讓專案架構更乾淨、資料流更好管理。


Recap

今天大致了解了state, prop的概念,也看到stateful和stateless的差別。接下來要在何時使用statefule和stateless widget就是開發者設計上的問題了。

明天會和大家一起看看flutter的三個基本元件: Image, Text, Button,並實作於專案中~


上一篇
Flutter介紹:頁面的建構 - Scaffold
下一篇
Flutter介紹:頁面的建構 - Image, Text, Button
系列文
Flutter 30: from start to store30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言