Làm thế nào để đối phó với việc xây dựng widget không mong muốn?


143

Vì nhiều lý do, đôi khi buildphương thức của các vật dụng của tôi được gọi lại.

Tôi biết rằng nó xảy ra bởi vì một phụ huynh cập nhật. Nhưng điều này gây ra tác dụng không mong muốn. Một tình huống điển hình mà nó gây ra vấn đề là khi sử dụng cách FutureBuildernày:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

Trong ví dụ này, nếu phương thức xây dựng được gọi lại, nó sẽ kích hoạt một yêu cầu http khác. Đó là không mong muốn.

Xem xét điều này, làm thế nào để đối phó với xây dựng không mong muốn? Có cách nào để ngăn chặn cuộc gọi xây dựng?



4
Trong tài liệu về nhà cung cấp, bạn liên kết ở đây với nội dung "Xem câu trả lời stackoverflow này sẽ giải thích chi tiết hơn tại sao sử dụng hàm tạo .value để tạo giá trị là không mong muốn." Tuy nhiên, bạn không đề cập đến hàm tạo giá trị ở đây hoặc trong câu trả lời của bạn. Ý của bạn là liên kết ở một nơi khác?
Suragch

Câu trả lời:


224

Các xây dựng phương pháp được thiết kế theo cách như vậy mà nó nên tinh khiết / không có tác dụng phụ . Điều này là do nhiều yếu tố bên ngoài có thể kích hoạt một bản dựng widget mới, chẳng hạn như:

  • Tuyến pop / đẩy
  • Thay đổi kích thước màn hình, thường là do sự xuất hiện của bàn phím hoặc thay đổi hướng
  • Phụ huynh đã tạo lại con của nó
  • Một kế thừaWidget tiện ích phụ thuộc vào Class.of(context)thay đổi ( mẫu)

Điều này có nghĩa là buildphương thức không được kích hoạt cuộc gọi http hoặc sửa đổi bất kỳ trạng thái nào .


Làm thế nào điều này có liên quan đến câu hỏi?

Vấn đề bạn đang gặp phải là phương pháp xây dựng của bạn có tác dụng phụ / không thuần túy, khiến cuộc gọi xây dựng bên ngoài trở nên rắc rối.

Thay vì ngăn chặn cuộc gọi xây dựng, bạn nên làm cho phương thức xây dựng của mình thuần túy, để nó có thể được gọi bất cứ lúc nào mà không bị ảnh hưởng.

Trong trường hợp ví dụ của bạn, bạn sẽ chuyển đổi widget của mình thành một StatefulWidgetcuộc gọi HTTP sau đó đến cuộc gọi initStatecủa bạn State:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

Tôi biết điều này rồi. Tôi đến đây vì tôi thực sự muốn tối ưu hóa việc xây dựng lại

Cũng có thể tạo ra một vật dụng có khả năng xây dựng lại mà không buộc con cái của nó phải xây dựng quá.

Khi thể hiện của một widget vẫn như cũ; Flutter cố tình sẽ không xây dựng lại trẻ em. Nó ngụ ý rằng bạn có thể lưu trữ các phần của cây widget để ngăn chặn việc xây dựng lại không cần thiết.

Cách dễ nhất là sử dụng các hàm tạo phi tiêu const:

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

Nhờ consttừ khóa đó , ví dụ DecoratedBoxsẽ giữ nguyên ngay cả khi bản dựng được gọi hàng trăm lần.

Nhưng bạn có thể đạt được kết quả tương tự bằng tay:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

Trong ví dụ này khi StreamBuilder được thông báo về các giá trị mới, subtreesẽ không xây dựng lại ngay cả khi StreamBuilder / Cột thực hiện. Điều đó xảy ra bởi vì, nhờ việc đóng cửa, trường hợp MyWidgetkhông thay đổi.

Mẫu này được sử dụng rất nhiều trong hình ảnh động. Sử dụng điển hình là AnimatedBuildervà tất cả các chuyển tiếp như AlignTransition.

Bạn cũng có thể lưu trữ subtreevào một trường trong lớp của mình, mặc dù ít được khuyến nghị hơn vì nó phá vỡ tính năng tải lại nóng.


2
Bạn có thể giải thích tại sao lưu trữ subtreetrong một trường lớp phá vỡ tải lại nóng?
mFeinstein

4
Một vấn đề tôi gặp phải StreamBuilderlà khi bàn phím xuất hiện màn hình thay đổi, do đó các tuyến đường phải được xây dựng lại. Vì vậy, StreamBuilderđược xây dựng lại và một cái mới StreamBuilderđược tạo ra và nó đăng ký vào stream. Khi StreamBuilderđặt mua một stream, snapshot.connectionStatetrở thành ConnectionState.waitingmà làm cho mã trở lại của tôi một CircularProgressIndicator, và sau đó snapshot.connectionStatethay đổi khi có dữ liệu và mã của tôi sẽ trở lại một widget khác nhau, mà làm cho nhấp nháy màn hình với các công cụ khác nhau.
mFeinstein

1
Tôi quyết định thực hiện StatefulWidget, đăng ký streamvào initState()và đặt currentWidgetvới setState()như là streamgửi dữ liệu mới, chuyển currentWidgetđến build()phương thức. Có một giải pháp tốt hơn?
mFeinstein

1
Tôi cảm thấy hơi khó hiểu. Bạn đang trả lời câu hỏi của riêng bạn, tuy nhiên từ nội dung, nó không giống như vậy.
sgon00

8
Uh, nói rằng một bản dựng không nên gọi một phương thức HTTP hoàn toàn đánh bại ví dụ rất thực tế của a FutureBuilder.
TheGeekZn

6

Bạn có thể ngăn chặn cuộc gọi xây dựng không mong muốn, sử dụng các cách này

1) Tạo lớp Statefull con cho từng phần nhỏ của UI

2) Sử dụng thư viện Nhà cung cấp , vì vậy sử dụng nó, bạn có thể dừng cuộc gọi phương thức xây dựng không mong muốn

Trong các trường hợp dưới đây gọi phương thức xây dựng tình huống

  • Sau khi gọi initState
  • Sau khi gọi didUpdateWidget
  • khi setState () được gọi.
  • khi bàn phím mở
  • khi hướng màn hình thay đổi
  • Widget cha mẹ được xây dựng sau đó widget con cũng xây dựng lại

0

Rung cũng có ValueListenableBuilder<T> class . Nó cho phép bạn xây dựng lại chỉ một số vật dụng cần thiết cho mục đích của bạn và bỏ qua các vật dụng đắt tiền.

bạn có thể xem các tài liệu ở đây ValueListenableBuilder làm rung các tài liệu
hoặc chỉ mã mẫu bên dưới:

  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:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
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.