Sự khác biệt giữa các hàm và lớp để tạo các vật dụng có thể sử dụng lại là gì?


125

Tôi đã nhận ra rằng có thể tạo các widget bằng các hàm đơn giản thay vì phân lớp StatelessWidget . Ví dụ như sau:

Widget function({ String title, VoidCallback callback }) {
  return GestureDetector(
    onTap: callback,
    child: // some widget
  );
}

Đây là thú vị bởi vì nó đòi hỏi xa ít mã hơn một lớp học toàn diện. Thí dụ:

class SomeWidget extends StatelessWidget {
  final VoidCallback callback;
  final String title;

  const SomeWidget({Key key, this.callback, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
      return GestureDetector(
        onTap: callback,
        child: // some widget
      );
  }
}

Vì vậy, tôi đã tự hỏi: Có sự khác biệt nào ngoài cú pháp giữa các hàm và các lớp để tạo các widget không? Và nó là một thực hành tốt để sử dụng các hàm?


Tôi thấy chủ đề này rất hữu ích cho sự hiểu biết của tôi về vấn đề này. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Câu trả lời:


171

TL; DR: Thích sử dụng các lớp hơn các hàm để tạo cây tiện ích con có thể tái sử dụng .


CHỈNH SỬA : Để bù đắp cho một số hiểu lầm: Đây không phải là về các hàm gây ra vấn đề, mà là các lớp giải quyết một số.

Flutter sẽ không có StatelessWidget nếu một hàm có thể làm điều tương tự.

Tương tự, nó chủ yếu hướng đến các vật dụng công cộng, được tạo ra để tái sử dụng. Đối với các chức năng riêng tư chỉ được sử dụng một lần không quan trọng lắm - mặc dù nhận thức được hành vi này vẫn tốt.


Có một sự khác biệt quan trọng giữa việc sử dụng các hàm thay vì các lớp, đó là: Khung công tác không biết về các hàm, nhưng có thể nhìn thấy các lớp.

Hãy xem xét chức năng "widget" sau:

Widget functionWidget({ Widget child}) {
  return Container(child: child);
}

đã sử dụng theo cách này:

functionWidget(
  child: functionWidget(),
);

Và nó tương đương với lớp:

class ClassWidget extends StatelessWidget {
  final Widget child;

  const ClassWidget({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: child,
    );
  }
}

được sử dụng như vậy:

new ClassWidget(
  child: new ClassWidget(),
);

Trên giấy tờ, cả hai dường như làm chính xác cùng một điều: Tạo 2 Container, với một cái lồng vào cái kia. Nhưng thực tế hơi khác.

Trong trường hợp của các hàm, cây tiện ích con được tạo ra trông giống như sau:

Container
  Container

Trong khi với các lớp, cây tiện ích là:

ClassWidget
  Container
    ClassWidget
      Container

Điều này rất quan trọng vì nó thay đổi cách hoạt động của khung khi cập nhật tiện ích con.

Tại sao điều đó lại quan trọng

Bằng cách sử dụng các chức năng để chia cây widget của bạn thành nhiều widget, bạn có thể gặp lỗi và bỏ lỡ một số tối ưu hóa hiệu suất.

Không có gì đảm bảo rằng bạn sẽ gặp lỗi khi sử dụng các hàm, nhưng bằng cách sử dụng các lớp, bạn được đảm bảo không gặp phải những vấn đề này.

Dưới đây là một số ví dụ tương tác trên Dartpad mà bạn có thể tự chạy để hiểu rõ hơn các vấn đề:

Phần kết luận

Dưới đây là danh sách được sắp xếp về sự khác biệt giữa việc sử dụng các hàm và lớp:

  1. Các lớp học:
  • cho phép tối ưu hóa hiệu suất (hàm tạo const, xây dựng lại chi tiết hơn)
  • đảm bảo rằng việc chuyển đổi giữa hai bố cục khác nhau sẽ xử lý tài nguyên một cách chính xác (các chức năng có thể sử dụng lại một số trạng thái trước đó)
  • đảm bảo rằng tải lại nóng hoạt động bình thường (sử dụng các chức năng có thể phá vỡ tải lại nóng cho showDialogs& tương tự)
  • được tích hợp vào trình kiểm tra widget.
    • Chúng ta thấy ClassWidgettrong cây widget do devtool hiển thị, giúp hiểu những gì trên màn hình
    • Chúng tôi có thể ghi đè debugFillProperties để in các tham số được truyền cho tiện ích con là gì
  • thông báo lỗi tốt hơn
    Nếu một ngoại lệ xảy ra (như ProviderNotFound), khung công tác sẽ cung cấp cho bạn tên của tiện ích con hiện đang xây dựng. Nếu bạn chỉ tách cây tiện ích con của mình trong các hàm + Builder, lỗi của bạn sẽ không có tên hữu ích
  • có thể xác định khóa
  • có thể sử dụng API ngữ cảnh
  1. Chức năng:
  • có ít mã hơn (có thể được giải quyết bằng cách sử dụng function_widget tạo mã )

Nhìn chung, việc sử dụng các hàm trên các lớp để tái sử dụng các widget được coi là một hành vi xấu vì những lý do này.
Bạn có thể , nhưng nó có thể cắn bạn trong tương lai.


Nhận xét không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

10

Tôi đã nghiên cứu về vấn đề này trong 2 ngày qua. Tôi đã đi đến kết luận sau: CÓ THỂ chia nhỏ các phần của ứng dụng thành các chức năng. Thật lý tưởng khi các hàm đó trả về a StatelessWidget, vì vậy có thể tối ưu hóa, chẳng hạn như tạo StatelessWidget const, vì vậy nó không phải xây dựng lại nếu không cần thiết. Ví dụ: đoạn mã này hoàn toàn hợp lệ:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            const MyWidgetClass(key: const Key('const')),
            MyWidgetClass(key: Key('non-const')),
            _buildSomeWidgets(_counter),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _buildSomeWidgets(int val) {
    print('${DateTime.now()} Rebuild _buildSomeWidgets');
    return const MyWidgetClass(key: Key('function'));

    // This is bad, because it would rebuild this every time
    // return Container(
    //   child: Text("hi"),
    // );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('${DateTime.now()} Rebuild MyWidgetClass $key');

    return Container(
      child: Text("hi"),
    );
  }
}

Việc sử dụng hàm ở đó là hoàn toàn tốt, vì nó trả về a const StatelessWidget. Vui long sửa cho tôi nêu tôi sai.


Ai đó có thể giải thích tại sao những gì tôi nói là sai? Ý tôi là, tôi cho rằng nó đã sai khi đưa ra những phiếu phản đối.
Sergiu Iacob

Tôi thực sự đồng ý với bạn. Tôi đã có ý định viết một bản phân tích chi tiết hơn về sự khác biệt, nhưng vẫn chưa giải quyết được. Hãy thoải mái đưa ra lập luận của bạn vì tôi nghĩ điều quan trọng là phải hiểu ưu và nhược điểm của các phương thức widget v.
TheIT

@SergiuIacob Chúng ta có thể sử dụng consttrước lớp không trạng thái cho mọi trường hợp không? Hay phải có những trường hợp nhất định? Nếu có, chúng là gì?
aytunch

1
@aytunch Tôi không nghĩ bạn có thể sử dụng constở mọi nơi. Ví dụ: nếu bạn có một StatelessWidgetlớp trả về một Textchứa giá trị của một biến và biến đó thay đổi ở đâu đó, so với giá trị của bạn StatelessWidgetnên được xây dựng lại, vì vậy nó có thể hiển thị giá trị khác, do đó, nó không thể được const. Tôi nghĩ rằng cách an toàn để nói nó là: bất cứ nơi nào bạn có thể, sử dụng const, nếu nó là an toàn để làm như vậy.
Sergiu Iacob

3
Tôi đang tranh luận xem có nên tự trả lời câu hỏi này không. Câu trả lời được chấp nhận là sai rõ ràng, nhưng Rémi đã làm rất nhiều để cố gắng và giúp cộng đồng xôn xao, vì vậy mọi người có thể không xem xét câu trả lời của anh ấy nhiều như của người khác. Điều đó có thể thấy rõ từ tất cả các phiếu ủng hộ. Mọi người chỉ muốn "nguồn chân lý duy nhất" của họ. :-)
DarkNeuron

4

Có một sự khác biệt lớn giữa những gì chức năng làm và những gì lớp làm.


Cho phép tôi sẽ giải thích nó từ đầu .🙂 (chỉ về mệnh lệnh)

  • Lịch sử lập trình, chúng ta đều biết bắt đầu với các lệnh cơ bản đơn giản (ví dụ-: Assembly).

  • Tiếp theo Lập trình có cấu trúc đi kèm với các điều khiển Luồng (ví dụ: if, switch, while, for, v.v.) Mô hình này cho phép các lập trình viên kiểm soát luồng chương trình một cách hiệu quả và cũng giảm thiểu số lượng dòng mã theo vòng lặp.

  • Tiếp theo lập trình thủ tục xuất hiện và nhóm các hướng dẫn thành các thủ tục (hàm). Điều này mang lại Hai lợi ích chính cho các lập trình viên.

    1. Nhóm các câu lệnh (hoạt động) thành các khối riêng biệt.

    2.Có thể sử dụng lại các khối này. (Chức năng)

Nhưng trên tất cả các mô hình đã không đưa ra giải pháp để Quản lý các ứng dụng. Lập trình thủ tục cũng chỉ có thể sử dụng cho các ứng dụng quy mô nhỏ. Điều đó không thể được sử dụng để phát triển các ứng dụng web lớn (ví dụ: ngân hàng, google, youtube, facebook, stackoverflow, v.v.), không thể tạo các khung như android sdk, flaming sdk và nhiều hơn nữa ......

Vì vậy, các kỹ sư nghiên cứu nhiều hơn để quản lý các chương trình một cách thích hợp.

  • Cuối cùng, Lập trình hướng đối tượng đi kèm với tất cả các giải pháp để quản lý ứng dụng ở mọi quy mô. (Từ hello world đến nghìn tỷ người sử dụng hệ thống tạo như google, amazon, và ngày nay là 90% ứng dụng).

  • Trên hết, tất cả các ứng dụng đều được xây dựng xung quanh các Đối tượng, có nghĩa là ứng dụng là một tập hợp các đối tượng này.

vì vậy các đối tượng là nền tảng cơ bản cho bất kỳ ứng dụng nào.

lớp (đối tượng lúc chạy) nhóm dữ liệu và các hàm liên quan đến các biến (dữ liệu) đó. vì vậy đối tượng tổng hợp dữ liệu và các hoạt động liên quan của chúng.

[Ở đây tôi sẽ không giải thích về oop]


👉👉👉Ok Now Hãy đến với khuôn khổ rung động.👈👈👈

-Dart hỗ trợ cả thủ tục và oop Nhưng, Flutter framework xây dựng hoàn toàn bằng cách sử dụng các lớp (oop). (Vì khung có thể quản lý lớn không thể tạo bằng thủ tục)

Ở đây tôi sẽ tạo danh sách lý do họ sử dụng các lớp thay vì các hàm để tạo widget.👇👇👇


1 - Hầu hết các lần xây dựng phương pháp (widget con) gọi số chức năng đồng bộ và không đồng bộ.

Ví dụ:

  • Để tải xuống hình ảnh mạng
  • nhận đầu vào từ người dùng, v.v.

vì vậy phương thức build cần phải giữ trong widget lớp riêng biệt (vì tất cả các phương thức khác được gọi bằng phương thức build () có thể giữ trong một lớp)


2 - Sử dụng lớp widget, bạn có thể tạo số lớp khác mà không cần viết đi viết lại cùng một đoạn mã (** Use Of Inheritance ** (mở rộng)).

Và cũng bằng cách sử dụng kế thừa (mở rộng) và đa hình (ghi đè), bạn có thể tạo lớp tùy chỉnh của riêng mình. (Ví dụ dưới đây, Trong đó tôi sẽ tùy chỉnh (Ghi đè) hoạt ảnh bằng cách mở rộng MaterialPageRoute (vì chuyển đổi mặc định của nó mà tôi muốn tùy chỉnh) .👇

class MyCustomRoute<T> extends MaterialPageRoute<T> {
  MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
      : super(builder: builder, settings: settings);

  @override                                      //Customize transition
  Widget buildTransitions(BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    if (settings.isInitialRoute)
      return child;
    // Fades between routes. (If you don't want any animation, 
    // just return child.)
    return new FadeTransition(opacity: animation, child: child);
  }
}

3 - Các hàm không thể thêm điều kiện cho các tham số của chúng, Nhưng sử dụng hàm tạo của tiện ích lớp Bạn có thể làm điều này.

Xuống bên dưới Code example👇 (tính năng này được sử dụng nhiều bởi các widget khung)

const Scaffold({
    Key key,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  }) : assert(primary != null),
       assert(extendBody != null),
       assert(extendBodyBehindAppBar != null),
       assert(drawerDragStartBehavior != null),
       super(key: key);

4 - Các hàm không thể sử dụng const và tiện ích lớp có thể sử dụng const cho các hàm tạo của chúng. (ảnh hưởng đến hiệu suất của luồng chính)


5 - Bạn có thể tạo bất kỳ số lượng widget độc lập nào bằng cách sử dụng cùng một lớp (các thể hiện của một lớp / đối tượng) Nhưng hàm không thể tạo các widget độc lập (instance), nhưng có thể sử dụng lại.

[mỗi phiên bản có biến phiên bản riêng của chúng và hoàn toàn độc lập với các tiện ích con khác (đối tượng), Nhưng biến cục bộ của hàm phụ thuộc vào từng lệnh gọi hàm * (có nghĩa là, khi bạn thay đổi giá trị của biến cục bộ, nó sẽ ảnh hưởng đến tất cả các phần khác của ứng dụng sử dụng chức năng này)]


Có nhiều Ưu điểm trong lớp hơn các hàm .. (trên đây chỉ là một số trường hợp sử dụng)


🤯 Suy nghĩ cuối cùng của tôi

Vì vậy, đừng sử dụng Functions làm khối xây dựng ứng dụng của bạn, chỉ sử dụng chúng để thực hiện các Operations. Nếu không, nó gây ra nhiều vấn đề không thể khắc phục khi ứng dụng của bạn có thể mở rộng .

  • Sử dụng các hàm để thực hiện một phần công việc nhỏ
  • Sử dụng lớp làm khối xây dựng ứng dụng (Quản lý ứng dụng)

📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍 📍📍📍📍📍📍📍

BẠN KHÔNG THỂ ĐO LƯỜNG CHẤT LƯỢNG CHƯƠNG TRÌNH BẰNG SỐ BÁO CÁO (hoặc dòng) SỬ DỤNG CỦA CHƯƠNG TRÌNH

📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍 📍📍📍📍📍📍📍

Cảm ơn vì đã đọc


Chào mừng bạn đến với Stackoverflow! Tôi không thực sự chắc chắn những gì bạn đang cố gắng diễn đạt với câu trả lời của mình. Bạn có thể sử dụng một chức năng tốt để xây dựng các widget. shrinkHelper() { return const SizedBox.shrink(); }cũng giống như sử dụng const SizedBox.shrink()nội tuyến trong cây tiện ích con của bạn và bằng cách sử dụng các chức năng trợ giúp, bạn có thể giới hạn số lượng lồng vào một nơi.
DarkNeuron

@DarkNeuron Cảm ơn bạn đã chia sẻ. Tôi sẽ cố gắng sử dụng các chức năng trợ giúp.
TDM

2

Khi bạn đang gọi tiện ích Flutter, hãy đảm bảo rằng bạn sử dụng từ khóa const. Ví dụconst MyListWidget();


9
Tôi có thể biết cách này trả lời câu hỏi OP không?
CopsOnRoad

2
Có vẻ như tôi đã trả lời tôi phần sai. Tôi đang cố gắng trả lời câu hỏi của Daniel rằng phương pháp xây dựng tiện ích con không trạng thái đã cấu trúc lại vẫn đang được gọi. Bằng cách thêm consttừ khóa khi gọi widget không trạng thái đã cấu trúc lại, nó chỉ nên được gọi một lần.
user4761410 28/12/18

1
Đồng ý. Hiểu rồi. Mọi người có thể phản đối câu trả lời này vì nó không liên quan gì đến câu hỏi OP. Vì vậy, bạn nên xóa nó. Dù sao lựa chọn là của bạn.
CopsOnRoad
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.