Flutter 入門與實戰(四十三):setState 和 ModelBinding用法對比

島上碼農發表於2021-08-06

這是我參與8月更文挑戰的第6天,活動詳情檢視:8月更文挑戰

前言

上兩篇我們介紹了使用 InheritedWidget 深入狀態管理,並且解耦了元件和狀態管理,從而使得程式碼更易於維護,而且還能實現區域性重新整理的效果。上兩篇文章傳送門:

到底能不能實現區域性重新整理呢嗎,我們本篇通過一個示例來驗證一下。

假設

回到我們前一個故事 —— 小芙和雷思相親的故事,假設我們的雷思不是那麼木,他能夠讀懂小芙的心思(共享狀態),那麼也許結局可能就不是之前那樣。所謂心有靈犀一點通,我們分別通過ModelBinding和初級的setState方式來實現這樣的效果。

使用 ModelBinding 實現狀態共享

為了使用 ModelBinding 實現狀態共享,我們需要將雷思和小芙作為ModelBinding的子元件。同時,我們另外寫了一個不依賴於狀態的元件(StatelessNoDepend),也放直在 ModelBinding 的子元件中,以便驗證區域性重新整理是否有效。

class StatefulStatelessDemoPage extends StatelessWidget {
  StatefulStatelessDemoPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('3年後'),
      ),
      body: ModelBindingV2(
        child: Column(
          children: [
            Xiaofu3(),
            Leisi(),
            StatelessNoDepend(),
          ],
        ),
        create: () => FaceEmotion(emotion: '驚訝'),
      ),
    );
  }
}
複製程式碼

同時,在雷思(Leisi) 元件中,我們通過一個文字來顯示狀態中的資料,這裡是一個情緒描述字串。這個就是從狀態管理中共享得到,因為依賴於狀態,因此在狀態改變的時候會重新build。

@override
Widget build(BuildContext context) {
    print('ModelBinding=====build:雷思');
    return Center(
      child: Text(
          'ModelBinding=====雷思感受到了小芙的${ModelBindingV2.of<FaceEmotion>(context).emotion}'),
    );
 }
複製程式碼

同樣的,在小芙(Xiaofu3)中,我們也會讀取狀態的情緒字串,以及使用了一個按鈕來改變情緒,從而使得元件重新整理。

@override
Widget build(BuildContext context) {
    print('ModelBinding=====build:小芙');
    return Center(
      child: Column(children: [
        Text(
            'ModelBinding=====小芙的表情:${ModelBindingV2.of<FaceEmotion>(context).emotion}'),
        TextButton(
            onPressed: () {
              ModelBindingV2.update<FaceEmotion>(
                  context, FaceEmotion(emotion: '高興'));
            },
            child: Text('小芙表情變了')),
      ]),
    );
}
複製程式碼

為了看是否真的被重建,我們在三個元件(Leisi,Xiaofu3和StatelessNoDepend)中的build 方法裡列印了一個資訊,同時我們在建構函式也列印了構造資訊。執行後,我們點選按鈕,整個列印的資訊如下:

flutter: ModelBinding=====constructor: 小芙

flutter: ModelBinding=====constructor: 雷思

flutter: constructor: 不依賴狀態的元件

flutter: ModelBinding=====build:小芙

flutter: ModelBinding=====build:雷思

flutter: build:不依賴於狀態的元件
-------------------------------

flutter: ModelBinding=====build:小芙

flutter: ModelBinding=====build:雷思
複製程式碼

分隔線以下是點選按鈕後的列印資訊,可以看到,第一次載入的時候,三個元件都 build 方法都被呼叫了。而點選按鈕後,只有 Leisi 和 Xiaofu 的build 方法被呼叫,這說明了確實實現了區域性重新整理的效果 —— 不依賴於狀態的元件不會被重建。

使用 setState 共享狀態

使用 setState的話會要複雜一點,我們需要通過父元件將資料傳遞給子元件,然後在狀態發生改變的時候,需要呼叫 setState 方法更新子元件,這個時候還需要父元件的更新狀態方法傳遞到更改狀態的子元件裡。顯然,耦合度是很高的。

class SetStateDemo extends StatefulWidget {
  SetStateDemo({Key key}) : super(key: key);

  _SetStateDemoState createState() => _SetStateDemoState();
}

class _SetStateDemoState extends State<SetStateDemo> {
  FaceEmotion faceEmotion = FaceEmotion();

  void updateEmotion(FaceEmotion newEmotion) {
    if (faceEmotion != newEmotion) {
      setState(() {
        faceEmotion = newEmotion;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('setState 方式'),
      ),
      body: Column(
        children: [
          StatelessXiaofu(face: faceEmotion, updateEmotion: updateEmotion),
          StatelessLeisi(face: faceEmotion),
          StatelessNoDepend(),
        ],
      ),
    );
  }
}
複製程式碼

剩下的程式碼很簡單,就不貼了,現在看進入頁面和點選按鈕更改狀態後的整個過程。

flutter: setState=====constructor:小芙

flutter: setState=====constructor: 雷思

flutter: constructor: 不依賴狀態的元件

flutter: setState=====build:小芙

flutter: setState=====build:雷思

flutter: build:不依賴於狀態的元件
------------------------------

flutter: setState=====constructor:小芙

flutter: setState=====constructor: 雷思

flutter: constructor: 不依賴狀態的元件

flutter: setState=====build:小芙

flutter: setState=====build:雷思

flutter: build:不依賴於狀態的元件
複製程式碼

可以看到,第一次進入頁面的過程和ModelBinding是一樣的。但是,在點選按鈕呼叫setState方法的時候就完全不一樣了,使用ModelBinding方法只是呼叫了 依賴於狀態的build方法,而setState之後全部子元件被重新構造了一遍,也就是移除後再插入了新的元件——這就效能消耗和 ModelBinding相比,肯定高很多。那麼,setState 的過程到底發生了什麼?我們下一篇通過原始碼來分析一下。

總結

本篇對比了使用 InheritedWidget 實現狀態共享和使用 setState 方式實現狀態共享的區別,很明顯,使用 InheritedWidget的方式效能更高,可以實現區域性重新整理,而且不會出現 setState 那種重構整個元件樹的情況。這個特點十分重要,意味著我們要儘可能地避免在高層級的元件上直接使用 setState重新整理介面,而是要依賴於狀態管理實現區域性重新整理。當然,如果這個元件本身是元件樹的葉子節點,那麼使用 setState 不會有什麼效能損失,這個時候倒是沒必要非得使用狀態管理工具。


我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章。

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章