Flutter性能優(yōu)化-關(guān)于函數(shù)返回widget的方式是否真的不可用

不久前,我看到一篇關(guān)于性能優(yōu)化的文章模叙,提到應(yīng)該使用類Widget 替代函數(shù)返回的 Widget歇拆,理由是 "框架不知道函數(shù),但是可以看到類"。這個(gè)說法讓我感到困惑故觅。后來厂庇,我的同事在性能優(yōu)化的分享中也提到了這個(gè)問題,同事指出系統(tǒng)源碼中使用的大多是類Widget输吏,這更加加深了我的困惑权旷,因?yàn)樵创a中也存在函數(shù) Widget,而一些復(fù)雜的組件评也,例如 Scaffold炼杖、Container、ButtonStyleButton盗迟、TabBarView 中的 build 函數(shù)中坤邪,有大量的判斷邏輯,這里面有許多用局部變量來簡化嵌套代碼罚缕,我理解的這些代碼與函數(shù) Widget 并沒有什么本質(zhì)區(qū)別艇纺。我和同事討論了這個(gè)問題,但沒有得出一個(gè)令人滿意的解釋邮弹。之后黔衡,我查閱了一些相關(guān)的討論和視頻(鏈接在下面),但仍未得出結(jié)論腌乡。只能自己動(dòng)手實(shí)踐來探究這個(gè)問題盟劫。

源碼才是解釋一切最有效的手段

通過framework類可以知道Widget 與 Element 具體調(diào)用的流程,看一下里面performRebuild方法(簡化了無關(guān)討論內(nèi)容的代碼):

void performRebuild() {
  Widget? built = build();
  _child = updateChild(_child, built, slot);
}

這種方法能夠揭示 widget 和 Element 的更新過程与纽。在此過程中侣签,系統(tǒng)首先調(diào)用 build() 方法,將所有 widget 打包成一個(gè) widget急迂。然后影所,系統(tǒng)遍歷該新 widget,以更新現(xiàn)有的 Element 樹和 Widget 樹僚碎。更新過程的代碼如下:

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    final Element newChild;
    if (child != null) { 
         // 判斷新舊 widget是否是同屬于StatefulWidget或StatelessWidget猴娩,一般只有熱重載時(shí)才有可能變成 false
        bool hasSameSuperclass = true;
        if (hasSameSuperclass && child.widget == newWidget) {
            newChild = child;
        } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
            child.update(newWidget);
            newChild = child;
        } else {
            deactivateChild(child);
            newChild = inflateWidget(newWidget, newSlot);
        }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
}

/// 非繼承于RenderObjectElement的Element更新Element 上的_widget 參數(shù)并繼續(xù)子組件查找更新
/// 繼承于RenderObjectElement的Element除了更新了_widget還更新了 RenderObject
void update(covariant Widget newWidget) {
    _widget = newWidget;
}

Element inflateWidget(Widget newWidget, Object? newSlot) {
    final Key? key = newWidget.key;
    if (key is GlobalKey) {
         // 如果 key 是GlobalKey,需要將這個(gè)key 所對(duì)應(yīng)的Element 移動(dòng)到現(xiàn)在位置后再去重新刷新
        final Element? newChild = _retakeInactiveElement(key, newWidget);
        newChild._activateWithParent(this, newSlot);
        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
        return updatedChild!;
    }
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
}

static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
}

更新過程的代碼表明勺阐,除了 child 和 newWidget 為空以及 key 變化外卷中,Element 的更新僅取決于新舊 Widget 的層級(jí)和類型。只有 Widget 的層級(jí)或類型發(fā)生變化皆看,才會(huì)導(dǎo)致 Element 重新創(chuàng)建仓坞。因此,無論使用類Widget 還是返回 Widget 的函數(shù)腰吟,在更新之前都會(huì)被組合成一個(gè) Widget无埃,并且不會(huì)導(dǎo)致 Widget 的層級(jí)或類型發(fā)生變化徙瓶。在 RenderObject 樹中,只有繼承了 RenderObjectWidget 的組件的 Element 才會(huì)調(diào)用 mount 方法以創(chuàng)建新的 RenderObject嫉称。只要 Element 保持不變侦镇,RenderObject 的結(jié)構(gòu)也將保持不變。因此织阅,從這個(gè)角度來看壳繁,類Widget 和函數(shù) Widget 沒有本質(zhì)區(qū)別。

在 Dart 層面荔棉,類Widget 和函數(shù) Widget 之間存在一些差別闹炉。例如,類Widget 支持 const润樱,而函數(shù) Widget 則不支持渣触。在一些開源的 Flutter 小項(xiàng)目中,大多數(shù)采用類Widget壹若,其中的數(shù)據(jù)通常寫在本地且不變嗅钻。因此,它們通常使用 const店展,以優(yōu)化項(xiàng)目性能养篓。然而,在實(shí)際項(xiàng)目中赂蕴,數(shù)據(jù)通常來自后端柳弄,這些數(shù)據(jù)是動(dòng)態(tài)變化的。即使將組件封裝為類Widget概说,其調(diào)用方的參數(shù)也會(huì)變化语御,因此不能使用 const 進(jìn)行修飾。

綜上所述席怪,函數(shù) Widget 并不是不能使用的,可以在項(xiàng)目中適當(dāng)?shù)奈恢檬褂煤瘮?shù) Widget纤控,我總結(jié)出的使用規(guī)則如下:

  • 可能出現(xiàn)復(fù)用的Widget使用statelessWidget或者statefulWidget進(jìn)行封裝挂捻,這個(gè)是遵循Flutter的通用規(guī)則。
  • statelessWidget頁面里面船万,不需要變化的部分可以使用函數(shù)進(jìn)行模塊的拆分刻撒,需要變化的部分可以使用statefulWidget進(jìn)行封裝。
  • statefulWidget頁面里面耿导,不需要變化且不需要外部變量的部分声怔,需要使用statelessWidget封裝且用const修飾,不需要單獨(dú)變化且需要外部變量的部分可以使用函數(shù)進(jìn)行模塊的拆分舱呻,需要單獨(dú)變化的部分使用statefulWidget封裝醋火。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悠汽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芥驳,更是在濱河造成了極大的恐慌柿冲,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兆旬,死亡現(xiàn)場離奇詭異假抄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丽猬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門宿饱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脚祟,你說我怎么就攤上這事谬以。” “怎么了愚铡?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵蛉签,是天一觀的道長。 經(jīng)常有香客問我沥寥,道長碍舍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任邑雅,我火速辦了婚禮片橡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淮野。我一直安慰自己捧书,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布骤星。 她就那樣靜靜地躺著经瓷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪洞难。 梳的紋絲不亂的頭發(fā)上舆吮,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音队贱,去河邊找鬼色冀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柱嫌,可吹牛的內(nèi)容都是我干的锋恬。 我是一名探鬼主播,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼编丘,長吁一口氣:“原來是場噩夢啊……” “哼与学!你這毒婦竟也來了彤悔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤癣防,失蹤者是張志新(化名)和其女友劉穎蜗巧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蕾盯,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幕屹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了级遭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片望拖。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挫鸽,靈堂內(nèi)的尸體忽然破棺而出说敏,到底是詐尸還是另有隱情,我是刑警寧澤丢郊,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布盔沫,位于F島的核電站,受9級(jí)特大地震影響枫匾,放射性物質(zhì)發(fā)生泄漏架诞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一干茉、第九天 我趴在偏房一處隱蔽的房頂上張望谴忧。 院中可真熱鬧,春花似錦角虫、人聲如沸沾谓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽均驶。三九已至,卻和暖如春枫虏,著一層夾襖步出監(jiān)牢的瞬間辣恋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國打工模软, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饮潦。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓燃异,卻偏偏與公主長得像,于是被迫代替她去往敵國和親继蜡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子回俐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361