Flutter一切皆widget但是不要將所有東西放入一個widget

Breeze_Luckly發表於2021-08-23

這是我參與8月更文挑戰的第21天,活動詳情檢視:8月更文挑戰。為應掘金的八月更文挑戰, 作為 Flutter 開發人員,我相信您在您的開發生活中至少聽說過這句流行的句子:“**一切都是widget”。這是 Flutter 的口頭禪,它揭示了這個非常好的 SDK 的內在力量!

當我們在widgets目錄中,我們可以看到很多小部件,如PaddingAlignSizedBox,等。我們通過組合它們來建立其他小部件,我發現這種方法可擴充套件、強大且易於理解。

但是當我閱讀 一些我在網際網路上找到的或由新採用者編寫的原始碼時,有一件讓我震驚的事情:擁有大量build ` 方法的趨勢,例項化很多小部件!我發現這很難閱讀、理解和維護。

作為軟體開發人員,我們必須記住,軟體的真實生活從第一次釋出給使用者開始。該軟體的原始碼將由其他人(包括您未來的您)閱讀和維護,這就是為什麼保持我們的程式碼簡單、易於閱讀和理解非常重要。

“小部件中的一切”的示例可以在Flutter 文件本身中找到。本教程的目標是展示如何構建此佈局:

image-20210822082626144

最終程式碼達到了它的目的:展示如何簡單地建立上述佈局。正如我們所見,甚至還有一些變數和方法可以為佈局的各個部分提供語義。這是一個很好的觀點,因為它使程式碼更容易理解。

import 'package:flutter/material.dart';
​
void main() {
  runApp(MyApp());
}
​
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.only(bottom: 8),
                  child: Text(
                    'Oeschinen Lake Campground',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                Text(
                  'Kandersteg, Switzerland',
                  style: TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          Text('41'),
        ],
      ),
    );
​
    Color color = Theme.of(context).primaryColor;
​
    Widget buttonSection = Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          _buildButtonColumn(color, Icons.call, 'CALL'),
          _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
          _buildButtonColumn(color, Icons.share, 'SHARE'),
        ],
      ),
    );
​
    Widget textSection = Container(
      padding: const EdgeInsets.all(32),
      child: Text(
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
        softWrap: true,
      ),
    );
​
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),
      ),
    );
  }
​
  Column _buildButtonColumn(Color color, IconData icon, String label) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
複製程式碼

事實上,情況可能更糟。這是我不喜歡的這段程式碼的典型多合一小部件版本:。

import 'package:flutter/material.dart';
​
void main() {
  runApp(MyApp());
}
​
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            Container(
              padding: const EdgeInsets.all(32),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          padding: const EdgeInsets.only(bottom: 8),
                          child: Text(
                            'Oeschinen Lake Campground',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                        Text(
                          'Kandersteg, Switzerland',
                          style: TextStyle(
                            color: Colors.grey[500],
                          ),
                        ),
                      ],
                    ),
                  ),
                  Icon(
                    Icons.star,
                    color: Colors.red[500],
                  ),
                  Text('41'),
                ],
              ),
            ),
            Container(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.call, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'CALL',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.near_me, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'ROUTE',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.share, color: color),
                      Container(
                        margin: const EdgeInsets.only(top: 8),
                        child: Text(
                          'SHARE',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            color: color,
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            Container(
              padding: const EdgeInsets.all(32),
              child: Text(
                'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
                'Alps. Situated 1,578 meters above sea level, it is one of the '
                'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
                'half-hour walk through pastures and pine forest, leads you to the '
                'lake, which warms to 20 degrees Celsius in the summer. Activities '
                'enjoyed here include rowing, and riding the summer toboggan run.',
                softWrap: true,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
複製程式碼

在第二個版本中,我們有一個大build方法的小部件,它很難閱讀、理解和維護。

現在讓我們看看我將如何重寫它:

import 'package:flutter/material.dart';
​
void main() {
  runApp(MyApp());
}
​
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter layout demo',
      home: const HomePage(),
    );
  }
}
​
class HomePage extends StatelessWidget {
  const HomePage({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter layout demo'),
      ),
      body: ListView(
        children: [
          const _Header(),
          const _SubHeader(),
          const _Buttons(),
          const _Description(),
        ],
      ),
    );
  }
}
​
class _Header extends StatelessWidget {
  const _Header({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Image.asset(
      'images/lake.jpg',
      width: 600,
      height: 240,
      fit: BoxFit.cover,
    );
  }
}
​
class _SubHeader extends StatelessWidget {
  const _SubHeader({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const _Title(),
                const _SubTitle(),
              ],
            ),
          ),
          const _Likes(),
        ],
      ),
    );
  }
}
​
class _Title extends StatelessWidget {
  const _Title({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        'Oeschinen Lake Campground',
        style: TextStyle(
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}
​
class _SubTitle extends StatelessWidget {
  const _SubTitle({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Text(
      'Kandersteg, Switzerland',
      style: TextStyle(
        color: Colors.grey[500],
      ),
    );
  }
}
​
class _Likes extends StatelessWidget {
  const _Likes({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        Icon(
          Icons.star,
          color: Colors.red[500],
        ),
        Text('41'),
      ],
    );
  }
}
​
class _Buttons extends StatelessWidget {
  const _Buttons({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          const _Button(icon: Icons.call, text: 'CALL'),
          const _Button(icon: Icons.share, text: 'ROUTE'),
          const _Button(icon: Icons.share, text: 'SHARE'),
        ],
      ),
    );
  }
}
​
class _Button extends StatelessWidget {
  const _Button({
    Key key,
    @required this.icon,
    @required this.text,
  })  : assert(icon != null),
        assert(text != null),
        super(key: key);
​
  final IconData icon;
  final String text;
​
  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
​
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            text,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
​
class _Description extends StatelessWidget {
  const _Description({
    Key key,
  }) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(32),
      child: Text(
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
        softWrap: true,
      ),
    );
  }
}
複製程式碼

你不覺得這更易讀嗎?

? 有什麼好處?

我理解為什麼教程不經常這樣做:它需要更多行(在我的示例中為 100 行),人們可能想知道為什麼我們要建立這麼多其他小部件。由於教程旨在專注於一個概念,因此這樣編寫它們可能會適得其反。但結果是,新採用者可能傾向於在他們的build方法中放置一個大的小部件樹。 讓我們看看為佈局的每個部分都有一個獨特的小部件有什麼好處:

可讀性

我們為佈局的每個語義部分建立一個小部件。因此,每個小部件都有一個較小的build方法。它更易於閱讀,因為您無需滾動即可到達小部件的末尾。

可理解性

每個小部件都有一個與其角色匹配的名稱,這稱為語義命名。通過這樣做,當我們閱讀程式碼時,更容易在我們的腦海中對映程式碼的哪一部分與我們在應用程式上看到的內容相匹配。我在這裡看到了可理解性方面的兩個改進: \1. 當我們閱讀其他地方引用的此類小部件時,我們幾乎知道它的作用,而無需檢視其實現。 2.在閱讀帶有語義命名的小部件的構建方法之前,我們已經對其內容有一個大致的瞭解。

可維護性

如果您必須更換一個元件或更改一個部件,它只會在一個地方,與其他小部件的其餘部分分開。多虧了這種做法,它更不容易出錯,因為每個小部件的角色都得到了很好的定義。在您的應用程式甚至另一個應用程式中的另一個頁面中共享佈局的一部分也將更加容易。

Performances

前面的所有原因應該足以讓您採用這種方式來建立 Flutter 應用程式,但是這樣做還有一個好處:我們提高了應用程式的效能,因為每個小部件都可以與其他小部件分開重建(事實並非如此如果我們使用方法來分隔我們的佈區域性分)。例如,假設我們必須在單擊它時增加紅星旁邊的數字。在這個版本中,我們可以製作_Likes一個StatefulWidget並處理這裡的增量。當使用者點選星星時,只有_Likes小部件會被重建。在第一個版本中,MyApp如果我們將其設為StatefulWidget.

Flutter 文件中也解釋了這種最佳實踐:

setState()在狀態上呼叫時,所有後代小部件都將重建。因此,將setState()呼叫本地化到 UI 實際需要更改的子樹部分。如果更改包含在樹的一小部分,請避免在樹的高處呼叫 setState()。

另一個優點是能夠const更頻繁地使用關鍵字。然後可以快取和重新使用小部件。正如Flutter 文件所述:

重用小部件比建立新的(但配置相同的)小部件要高效得多。

⚡️ 如何提高工作效率?

如您所見,通過為佈局的每個語義部分建立一個小部件,我們編寫了更多程式碼。我們可以在 Visual Studio Code 中使用Dart擴充套件提供的stlessstful片段,

為了我自己的需要,我建立了新的片段,稱為slesssful,這樣我的工作效率比以往任何時候都高。如果您希望在 Visual Studio Code 中使用它們,則必須遵循此文件並新增以下內容:

{
  "Flutter stateless widget": {
        "scope": "dart",
        "prefix": "sless",
        "description": "Insert a StatelessWidget",
        "body": [
            "class $1 extends StatelessWidget {",
            "  const $1({",
            "    Key key,",
            "  }) : super(key: key);",
            "",
            "  @override",
            "  Widget build(BuildContext context) {",
            "    return Container(",
            "      $2",
            "    );",
            "  }",
            "}"
        ]
    },
    "Flutter stateful widget": {
        "scope": "dart",
        "prefix": "sful",
        "description": "Insert a StatefulWidget",
        "body": [
            "class $1 extends StatefulWidget {",
            "  const $1({",
            "    Key key,",
            "  }) : super(key: key);",
            "",
            "  @override",
            "  _$1State createState() => _$1State();",
            "}",
            "",
            "class _$1State extends State<$1> {",
            "  @override",
            "  Widget build(BuildContext context) {",
            "    return Container(",
            "      $2",
            "    );",
            "  }",
            "}"
        ]
    },
}
複製程式碼

? 結論

我相信這是編寫 Flutter 應用程式的好方法,我希望你也相信。如果不是這樣,我對你的意見很感興趣?!

從現在開始,記住這句話:“Everything’s a widget but don’t put everything in one widget! ”。

相關文章