Flutter 小技巧之優(yōu)化使用的 BuildContext

Flutter 里的 BuildContext 相信大家都不會陌生迈着,雖然它叫 Context蛔趴,但是它實際是 Element 的抽象對象,而在 Flutter 里笨触,它主要來自于 ComponentElement

關于 ComponentElement 可以簡單介紹一下雹舀,在 Flutter 里根據(jù) Element 可以簡單地被歸納為兩類:

  • RenderObjectElement :具備 RenderObject 芦劣,擁有布局和繪制能力的 Element
  • ComponentElement :沒有 RenderObject ,我們常用的 StatelessWidgetStatefulWidget 里對應的 StatelessElementStatefulElement 就是它的子類说榆。

所以一般情況下虚吟,我們在 build 方法或者 State 里獲取到的 BuildContext 其實就是 ComponentElement

那使用 BuildContext 有什么需要注意的問題签财?

首先如下代碼所示串慰,在該例子里當用戶點擊 FloatingActionButton 的時候,代碼里做了一個 2秒的延遲唱蒸,然后才調用 pop 退出當前頁面邦鲫。

class _ControllerDemoPageState extends State<ControllerDemoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await Future.delayed(Duration(seconds: 2));
          Navigator.of(context).pop();
        },
      ),
    );
  }
}

正常情況下是不會有什么問題,但是當用戶在點擊了 FloatingActionButton 之后神汹,又馬上點擊了 AppBar 返回退出應用庆捺,這時候就會出現(xiàn)以下的錯誤提示。

image

可以看到此時 log 說屁魏,Widget 對應的 Element 已經(jīng)不在了滔以,因為在 Navigator.of(context) 被調用時,context 對應的 Element 已經(jīng)隨著我們的退出銷毀氓拼。

一般情況下處理這個問題也很簡單你画,那就是增加 mounted 判斷,通過 mounted 判斷就可以避免上述的錯誤桃漾。

class _ControllerDemoPageState extends State<ControllerDemoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await Future.delayed(Duration(seconds: 2));
          if (!mounted) return;
          Navigator.of(context).pop();
        },
      ),
    );
  }
}

上面代碼里的 mounted 標識位來自于 State 坏匪,因為 State 是依附于 Element 創(chuàng)建,所以它可以感知 Element 的生命周期呈队,例如 mounted 就是判斷 _element != null; 剥槐。

image

那么到這里我們收獲了一個小技巧:使用 BuildContext 時,在必須時我們需要通過 mounted 來保證它的有效性宪摧。

那么單純使用 mounted 就可以滿足 context 優(yōu)化的要求了嗎粒竖?

如下代碼所示,在這個例子里:

  • 我們添加了一個列表几于,使用 builder 構建 Item
  • 每個列表都有一個點擊事件
  • 點擊列表時我們模擬網(wǎng)絡請求蕊苗,假設網(wǎng)絡也不是很好,所以延遲個 5 秒
  • 之后我們滑動列表讓點擊的 Item 滑出屏幕不可見
class _ControllerDemoPageState extends State<ControllerDemoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView.builder(
        itemBuilder: (context, index) {
          return ListItem();
        },
        itemCount: 30,
      ),
    );
  }
}
class ListItem extends StatefulWidget {
  const ListItem({Key? key}) : super(key: key);
  @override
  State<ListItem> createState() => _ListItemState();
}

class _ListItemState extends State<ListItem> {
  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Container(
        height: 160,
        color: Colors.amber,
      ),
      onTap: () async {
        await Future.delayed(Duration(seconds: 5));
        if(!mounted) return;
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text("Tip")));
      },
    );
  }
}

由于在 5 秒之內沿彭,Item 被劃出了屏幕朽砰,所以對應的 Elment 其實是被釋放了,從而由于 mounted 判斷喉刘,SnackBar 不會被彈出瞧柔。

那如果假設需要在開發(fā)時展示點擊數(shù)據(jù)上報的結果,也就是 Item 被釋放了還需要彈出睦裳,這時候需要如何處理造锅?

我們知道不管是 ScaffoldMessenger.of(context) 還是 Navigator.of(context) ,它本質還是通過 context 去往上查找對應的 InheritedWidget 泛型廉邑,所以其實我們可以提前獲取哥蔚。

所以,如下代碼所示蛛蒙,在 Future.delayed 之前我們就通過 ScaffoldMessenger.of(context); 獲取到 sm 對象糙箍,之后就算你直接退出當前的列表頁面,5秒過后 SnackBar 也能正常彈出牵祟。

class _ListItemState extends State<ListItem> {
  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Container(
        height: 160,
        color: Colors.amber,
      ),
      onTap: () async {
        var sm = ScaffoldMessenger.of(context);
        await Future.delayed(Duration(seconds: 5));
        sm.showSnackBar(SnackBar(content: Text("Tip")));
      },
    );
  }
}


為什么頁面銷毀了深夯,但是 SnackBar 還能正常彈出

因為此時通過 of(context); 獲取到的 ScaffoldMessenger 是存在 MaterialApp 里诺苹,所以就算頁面銷毀了也不影響 SnackBar 的執(zhí)行咕晋。

但是如果我們修改例子,如下代碼所示筝尾,在 Scaffold 上面多嵌套一個 ScaffoldMessenger 捡需,這時候在 Item 里通過 ScaffoldMessenger.of(context) 獲取到的就會是當前頁面下的 ScaffoldMessenger

class _ControllerDemoPageState extends State<ControllerDemoPage> {
  @override
  Widget build(BuildContext context) {
    return ScaffoldMessenger(
      child: Scaffold(
        appBar: AppBar(),
        body: ListView.builder(
          itemBuilder: (context, index) {
            return ListItem();
          },
          itemCount: 30,
        ),
      ),
    );
  }
}

這種情況下我們只能保證Item 不可見的時候 SnackBar 還能正常彈出筹淫, 而如果這時候我們直接退出頁面站辉,還是會出現(xiàn)以下的錯誤提示,因為 ScaffoldMessenger 也被銷毀了 损姜。

image

所以到這里我們收獲第二個小技巧:在異步操作里使用 of(context) 饰剥,可以提前獲取,之后再做異步操作摧阅,這樣可以盡量保證流程可以完整執(zhí)行汰蓉。

既然我們說到通過 of(context) 去獲取上層共享往下共享的 InheritedWidget ,那在哪里獲取就比較好棒卷?

還記得前面的 log 嗎顾孽?在第一個例子出錯時祝钢,log 里就提示了一個方法,也就是 State 的 didChangeDependencies 方法若厚。

image

為什么是官方會建議在這個方法里去調用 of(context) 拦英?

首先前面我們一直說,通過 of(context) 獲取到的是 InheritedWidget 测秸,而 當 InheritedWidget 發(fā)生改變時疤估,就是通過觸發(fā)綁定過的 Element 里 State 的didChangeDependencies 來觸發(fā)更新,所以在 didChangeDependencies 里調用 of(context) 有較好的因果關系霎冯。

對于這部分內容感興趣的铃拇,可以看 Flutter 小技巧之 MediaQuery 和 build 優(yōu)化你不知道的秘密全面理解State與Provider

那我能在 initState 里提前調用嗎沈撞?

當然不行慷荔,首先如果在 initState 直接調用如 ScaffoldMessenger.of(context).showSnackBar 方法,就會看到以下的錯誤提示关串。

image

這是因為 Element 里會判斷此時的 _StateLifecycle 狀態(tài)拧廊,如果此時是 _StateLifecycle.created 或者 _StateLifecycle.defunct ,也就是在 initStatedispose 晋修,是不允許執(zhí)行 of(context) 操作吧碾。

image

of(context) 操作指的是 context.dependOnInheritedWidgetOfExactTyp

當然墓卦,如果你硬是想在 initState 下調用也行倦春,增加一個 Future 執(zhí)行就可以成功執(zhí)行

@override
void initState() {
  super.initState();
  Future((){
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("initState")));
  });
}

簡單理解,因為 Dart 是單線程輪詢執(zhí)行落剪,initState 里的 Future 相當于是下一次輪詢睁本,自然也就不在 _StateLifecycle.created 的狀態(tài)下。

那我在 build 里直接調用不行嗎忠怖?

直接在 build 里調用肯定可以呢堰,雖然 build 會被比較頻繁執(zhí)行,但是 of(context) 操作其實就是在一個 map 里通過 key - value 獲取泛型對象凡泣,所以對性能不會有太大的影響枉疼。

真正對性能有影響的是 of(context) 的綁定數(shù)量和獲取到對象之后的自定義邏輯,例如你通過 MediaQuery.of(context).size 獲取到屏幕大小之后鞋拟,通過一系列復雜計算來定位你的控件骂维。

  @override
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;
    var padding = MediaQuery.of(context).padding;
    var width = size.width / 2;
    var height = size.width / size.height  *  (30 - padding.bottom);
    return Container(
      color: Colors.amber,
      width: width,
      height: height,
    );
  }

例如上面這段代碼,可能會導致鍵盤在彈出的時候贺纲,雖然當前頁面并沒有完全展示航闺,但是也會導致你的控件不斷重新計算從而出現(xiàn)卡頓。

詳細解釋可以參考 Flutter 小技巧之 MediaQuery 和 build 優(yōu)化你不知道的秘密

所以到這里我們又收獲了一個小技巧: 對于 of(context) 的相關操作邏輯,可以盡量放到 didChangeDependencies 里去處理潦刃。

最后侮措,今天主要分享了在使用 BuildContext 時的一些注意事項和技巧,如果你對于這方面還有什么疑問福铅,歡迎留言評論萝毛。

本文轉自 https://juejin.cn/post/7122409135055831053项阴,如有侵權滑黔,請聯(lián)系刪除。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末环揽,一起剝皮案震驚了整個濱河市略荡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歉胶,老刑警劉巖汛兜,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異通今,居然都是意外死亡粥谬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門辫塌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漏策,“玉大人,你說我怎么就攤上這事臼氨〔粲鳎” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵储矩,是天一觀的道長感耙。 經(jīng)常有香客問我,道長持隧,這世上最難降的妖魔是什么即硼? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮屡拨,結果婚禮上只酥,老公的妹妹穿的比我還像新娘。我一直安慰自己洁仗,他們只是感情好层皱,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赠潦,像睡著了一般叫胖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上她奥,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天瓮增,我揣著相機與錄音怎棱,去河邊找鬼。 笑死绷跑,一個胖子當著我的面吹牛拳恋,可吹牛的內容都是我干的。 我是一名探鬼主播砸捏,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谬运,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垦藏?” 一聲冷哼從身側響起梆暖,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掂骏,沒想到半個月后轰驳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡弟灼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年级解,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片田绑。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡勤哗,死狀恐怖,靈堂內的尸體忽然破棺而出辛馆,到底是詐尸還是另有隱情俺陋,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布昙篙,位于F島的核電站腊状,受9級特大地震影響,放射性物質發(fā)生泄漏苔可。R本人自食惡果不足惜缴挖,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焚辅。 院中可真熱鬧映屋,春花似錦、人聲如沸同蜻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湾蔓。三九已至瘫析,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贬循。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工咸包, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杖虾。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓烂瘫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奇适。 傳聞我的和親對象是個殘疾皇子坟比,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容