帶你深入理解不一樣的 Flutter

作者:GSYTech@微信公眾號 搜狐技術產品

出處:https://mp.weixin.qq.com/s/wETMBKmswBrXhuK5ZP20sg


本文字數: 2608

預計閱讀時間: 6 分鐘

本文主要介紹 Flutter 的現狀和 Widget 的靈魂設計,用不一樣的角度帶你領略 Flutter 的魅力所在,相信本篇文章會給你帶來 Flutter 上不一樣的理解。

一、Flutter 的現狀

Flutter 作為谷歌的新一代跨平臺技術方案,它使用 自帶的繪製引擎 ,優雅地解決了以前跨平臺開發上 兼容性能 的痛點,通過成熟的 Skia 引擎去實現控件的繪製,讓控件與平臺 解耦 ,在提高性能的同時也增強了代碼在不同平臺的 複用率

Flutter 如今在國內如:字節跳動、阿里巴巴、騰訊、京東、網易、美團等企業都相繼投入使用,甚至阿里巴巴已經成立了 Alibabab-Flutter 相關事業組用於集團開發維護,今年 Alibabab-Flutter 更是戰略上升到集團移動委員會四大戰略之一,得到了資源上的重點投入,由多 BU 合作共建。

從實際使用角度出發,Flutter 如今已經在各大企業的產品投入使用,在 《國內大廠在移動端跨平臺的框架接入分析》 中分析了 52 款大廠應用,其中就有 19 款應用集成了 Flutter 的開發能力。

Flutter 最大的特點就在於它的渲染引擎,Flutter 中提供的控件和 Canvas 能力都是通過 Flutter Engine 實現。

不同於 ReactNative 通過轉化為原生控件再去渲染,Flutter Engine 在 Android 和 iOS 平臺只需要提供對應的 Surface ,剩下都由 Flutter Engine 來完成繪製,這大大降低了 Flutter 和平臺的耦合度,同時也讓渲染出來的控件在不同平臺能有一致性的表現。

帶你深入理解不一樣的 Flutter

Flutter 如今除了 Android 和 iOS 平臺之外,對於 Web 、 MacOS 、 Linux 平臺的支持也進入了 beta 階段, Win 進入了 PreView 階段,這也是得益於 Flutter 優秀的底層設計。

而對於 Flutter ,相信開發者們最常用也最熟悉的就是 Widget ,而 Widget 的設計理念也正是它的靈魂所在。

二、Widget,Flutter 中一切的開始

要理解 Flutter 就要理解 Flutter 中最靈魂的設計:

Flutter 內一切皆 Widget , Widget 是不可變的(immutable),每個 Widget 狀態都代表了一幀。

理解這段話是非常重要的,因為 Flutter 中所有界面的展示效果,在代碼層面都是通過 Widget 作為入口開始。 Widget 是不可變的,說明頁面發生變化時 Widget 一定是被重新構建,所以 Widget 的固定狀態代表了一幀靜止的畫面,當畫面發生改變時,對應的 Widget 一定會變化。

舉個例子,如下代碼所示定義了一個 TestWidget , TestWidget 接受傳入的 title 和 count 參數顯示到 Text 上,同時如果 count 大於 99,則只顯示 99。

<code>Warnning
/// This class is marked as '@immutable'
/// but one or more of its instance fields are not final

class TestWidget extends StatelessWidget {

  final String title;

  int count;

  TestWidget({this.title, this.count});

  @override
  Widget build(BuildContext context) {
    this.count = (count > 99) ? 99 : count;
    return Container(
      child: new Text("$title $count"),
    );
  }
}
/<code>

這段代碼看起來沒有什麼問題,也可以正常運行,但是在編譯器上會有 “This class is marked as '@immutable',but one or more of its instance fields are not final” 的提示警告,這是因為 TestWidget 內的 count 成員變量沒有加上 final 聲明,從而在代碼層面容易產生歧義。

因為前面說過 Widget 是 immutable ,所以它的每次變化都會導致自身被重新構建,也就是 TestWidget 內的 count 成員變量其實是不會被保存且二次使用。

如上所示代碼中 count 成員沒有 final 聲明,所以理論是可以對 count 進行二次修改賦值,造成 count 成員好像被保存在 TestWidget 中被二次使用的錯覺,容易產生歧義。

那這時候可能有人會問: 每次 Widget 都重構,那怎麼保存狀態?這樣 Widget 的設計會不會性能很差?

三、Element,Flutter 的背後大佬

前面說過, Flutter 中的 Widget 是不可變的,比如前面用的 StatelessWidget ,一個 Widget 狀態代表了一幀,而在 Flutter 裡提供了 StatefulWidget 實現跨幀保存狀態的功能。

那什麼是 StatefulWidget ?如下代碼所示,只需要讓 TestWidget 繼承 StatefulWidget ,然後將 build 方法和 count 寫到 State 內,就可以實現 count 狀態的跨幀保存。

當然這裡的 title 參數還是定義在 Widget 層面,所以依舊需要 final 。

<code>class TestWidget extends StatefulWidget {
  final String title;

  TestWidget({this.title});

  @override
  _TestWidgetState createState() => _TestWidgetState();
}

class _TestWidgetState extends State {
  int count;

  @override
  Widget build(BuildContext context) {
    this.count = (count > 99) ? 99 : count;
    return InkWell(
      onTap: () {
        setState(() {
          count++;
        });
      },
      child: Container(
        child: new Text("${widget.title} $count"),
      ),
    );
  }
}
/<code>

可以看到 StatefulWidget 本身依舊是不可變的,所以 title 屬性需要定義為 final ,但是 build 方法被放到了 State 中, count 也被移動到 State 中,所以 count 可以支持跨幀保存。

為什麼寫到 State 就可以實現跨幀保存 ? State 和 Widget 是什麼關係?這就需要介紹 Flutter 中另一個概念: Element 。

事實上如果查看 Flutter 對於 Widget 的定義,可以看到官方對於 Widget 的定義是 Describes the configuration for an [Element]. ,也就是 Element 才是 Widget 真正的體現。

帶你深入理解不一樣的 Flutter

對於 Element 大家可能會比較陌生,因為 Flutter 開發中很少直接使用 Element 。但是如果說到 BuildContext 相信大家就不會陌生,因為 BuildContext 在開發中經常被使用到,而 Element 恰好就是 BuildContext 的實現類,你使用的 BuildContext 其實就是 Element 。

所以在 Flutter 的開發過程中, Element 其實扮演著十分重要的角色:一個 Widget 被首次加載時,會先創建出它對應的 Element ,就比如上面介紹的 StatefulWidget ,會對應先創建出 StatefulElement ;而 StatefulElement 被調用的時候,會先通過 widget.createState() 創建出 State ,之後 _state 就被保存在 Element ,所以 _state 可以跨 Widget 保存。

帶你深入理解不一樣的 Flutter

理解了嗎? Element 才是一個真正的工作節點,而 Widget 僅僅是一個“配置信息” 。

因為 Widget 僅僅是“配置信息” ,所以它可以每次改變時都被重構,而 Element 才是真正的節點,它每次讀取 Widget 的狀態去配置和繪製,但是 Element 並不是每次都重新創建,而是在 Widget 首次被加載時通過 createElement 創建出來。

什麼是首次加載呢?通俗點來說就是這個 Element 在 mount 的時候。

舉一個例子,如下面代碼所示:

  • DemoPage Scaffold body StatePage

  • StatePage

    data “init”

    _StatePageState

    data

  • floatingActionButton setState _DemoPageState內 data “

    Change By setState”

  • 但是最終結果

    StatePage

    界面依然顯示

    “init”

<code>class DemoPage extends StatefulWidget {
  @override
  _DemoPageState createState() => _DemoPageState();
}

class _DemoPageState extends State {
  
  String data = "init";
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      /// 將data數據傳遞給StatePage
      body: StatePage(data),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          /// 改變data數據
          setState(() {
            data = "Change By setState";
          });
        },
      ),
    );
  }
}

class StatePage extends StatefulWidget {

  final String data;

  StatePage(this.data);

  @override
  _StatePageState createState() => _StatePageState(data);
}

class _StatePageState extends State {

  String data;
   //// data是從StatePageState的構造方法傳入
  _StatePageState(this.data);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: new Center(
          child: new Text(data ?? "")
      ),
    );
  }
}
/<code>

為什麼 StatePage 界面依然顯示 “init” ?因為雖然 StatePage 這個 Widget 的 data 發生了改變,但是 StatePage 的 createState() 方法只在第一次被加載時調用,對應創建出來的 state 被保存在 Element 中,所以 _StatePageState 中的 data 只在第一次時被傳遞進來。

關鍵就在於 _StatePageState 創建時 data 只被賦數一次。

如果這時候要讓 StatePage 的 data 正常改變,就需要使用 widget.data 。看出來了沒, 如果把 State 放到 Element 的級別上來看,通過 widget.data 去更新的邏輯,是不是 Widget 看起來就很像是一個配置文件 。

那最後再問一個問題: Widget 和 Element 的對應關係是怎麼樣?

四、Element 和 Widget 的對應關係

從官方的註釋裡可以看出, Widget 做為配置文件,和 Element 是應該是一對多的關係,因為真實工作的其實是 Element 樹,而 Widget 作為配置文件,可能會在多個地方被複用。

帶你深入理解不一樣的 Flutter

舉個例子,如下代碼所示,通過運行後可以看到 textUseAll 在多個地方被 inflated 並且正常渲染出 3333333 的效果。

這是因為 textUseAll 僅僅是作為配置文件,所以被多個 Element 加載並不會出現問題,因為這並不是一個真正的 "View" 被多個地方加載。

<code>final textUseAll = new Text(
  "3333333",
  style: new TextStyle(fontSize: 18, color: Colors.red),
);

class MyHomePage extends StatelessWidget {
  void goNext(context) {
    Navigator.of(context).push(MaterialPageRoute(builder: (context) {
      return ShowPage();
    }));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Container(
        color: Colors.blue,
        height: 80,
        child: Stack(
          children: [
            new Center(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                textBaseline: TextBaseline.alphabetic,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  textUseAll,
                  Text(
                    ' GSY ',
                    style: TextStyle(fontSize: 36, fontFamily: "Heiti"),
                  ),
                  textUseAll,
                ],
              ),
            ),
          ],
        ),
      )),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          goNext(context);
        },
        tooltip: 'Next',
        child: Icon(Icons.add),
      ),
    );
  }
}

class ShowPage extends StatelessWidget {
  void goNext(context) {
    Navigator.of(context).push(MaterialPageRoute(builder: (context) {
      return ShowPage();
    }));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: new Center(
          child:
          textUseAll,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          goNext(context);
        },
        tooltip: 'goNext',
        child: Icon(Icons.add),
      ),
    );
  }
}
/<code>

當然,當你給上面的 textUseAll 的 Text 配置上 GlobalKey 就是會是另外一個故事,因為 GlobalKey 會讓一個 Widget 變成“全局單例” ,具體解釋這裡就不展開,感興趣的可以自己嘗試下。

最後補充一點:Flutter 中除了 Widget 、 Element 之外,還有 RenderObject 、 Layer 等才能組成 Dart 層的渲染閉環,這裡主要介紹了 Widget 和 Element 的關係,是因為 Widget 和 Element 的理解是 Flutter 中入門時最容易忽略的, Flutter 對於 Widget 的設計讓我們寫代碼時更像是在寫配置文件。

五、最後

本篇文章以 Flutter 的 Widget 和 Element 為核心要點展開,為大家介紹了 Flutter 中最有意思的設計之一,事實上只有掌握了這些基礎概念,才能在後續的學習裡更好地理解 Flutter 的開發思想,當然更加完善的體系內容可以在 《Flutter開發實戰詳解》 中找到。


作者:GSYTech@微信公眾號 搜狐技術產品

出處:https://mp.weixin.qq.com/s/wETMBKmswBrXhuK5ZP20sg


分享到:


相關文章: