Code前端首页关于Code前端联系我们

Flutter开发中避免构建Widget的终极解决方案

terry 2年前 (2023-09-23) 阅读数 67 #移动小程序

Flutter是一个基于React框架概念开发的框架。有很多相似之处,但也有细微的差别。我感触比较深的是flutter的重建。那么有没有办法阻止恢复呢?有!

在小部件前面添加 const

是可以的,一劳永逸,但是当您添加 const 时,您的小部件永远不会更新。如果你不是在写静态页面,最好不要使用它。

将您的元素编写为“叶子”元素

请参阅 flutter 文档并将所有元素定义为叶子(树的最低级别),然后更改叶子元素内部的属性以防止叶子折叠。相互撞击。 ,呃,在我看来,这和​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​人,如果调用setState,所有组合值都会被重置。是的,我就是用这个思路解决了原来的重建问题。例如,使用StreamBuilder可以加载您的组件,然后使用流来触发StreamBuilder的内部构建,并使用StreamBuilder来分离外部组件。以这种方式编写有一些缺点。我必须编写一个额外的流并关闭该流,这确实毫无意义。

使用其他库,比如Provider

可以看到Provider库的作者提供了一些widget来减少配置,但感觉不是很简单易用。这些库的实现与 StreamBuilder 类似。使用小部件来区分其他小部件并限制其中的更新,但它们都有一个共同点。需要使用额外的外部变量来更新内部

终极方法

用过react的人都知道,react类组件有一个非常重要的生命周期,叫做shouldComponentUpdate。我们可以在组件内部重写这个声明周期来提高性能。

最好的方法是比较单元的新旧部分的值,看看它们是否匹配。如果它们匹配,则无需更新该项目。 Flutter有同样的生命周期吗?不!

flutter团队认为flutter的速度非常快,并且flutter有类似反馈的diff算法来比较组件是否需要更新。他们做了优化和缓存,因为更新 flutter 组件是一项非常昂贵的任务。 ,而重建widget只是创建了一个新的widget实例,就像执行一段dart代码一样,没有对UI层进行任何更改,而且它还对新旧widget进行了差异,使用diff widgets来减少。对元素的需求。任何改变,只要不导致组件被破坏或重建,都不会影响整体性能。

但是通过谷歌和百度,你仍然可以找到人们在寻找防止重制的方法,这说明市场还是有需求的。我个人认为这并不算过度优化。事实上,还有一些需要改进的地方。比如Google推荐的状态管理库Provider,提供了一种减少不必要配置的方法

(我)不要(想)太多(付出)说(笑):

library should_rebuild_widget;

import 'package:flutter/material.dart';

typedef ShouldRebuildFunction<T> = bool Function(T oldWidget, T newWidget);

class ShouldRebuild<T extends Widget> extends StatefulWidget {
  final T child;
  final ShouldRebuildFunction<T> shouldRebuild;
  ShouldRebuild({@required this.child, this.shouldRebuild}):assert((){
    if(child == null){
      throw FlutterError.fromParts(
          <DiagnosticsNode>[
            ErrorSummary('ShouldRebuild widget: builder must be not  null')]
      );
    }
    return true;
  }());
  @override
  _ShouldRebuildState createState() => _ShouldRebuildState<T>();
}

class _ShouldRebuildState<T extends Widget> extends State<ShouldRebuild> {
  @override
  ShouldRebuild<T> get widget => super.widget;
  T oldWidget;
  @override
  Widget build(BuildContext context) {
    final T newWidget = widget.child;
    if (this.oldWidget == null || (widget.shouldRebuild == null ? true : widget.shouldRebuild(oldWidget, newWidget))) {
      this.oldWidget = newWidget;
    }
    return oldWidget;
  }
}

复制代码

这几行代码,不到40个代码,我们看一下测试代码:

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:should_rebuild_widget/should_rebuild_widget.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Test(),
    );
  }
}

class Test extends StatefulWidget {
  @override
  _TestState createState() => _TestState();
}

class _TestState extends State<Test> {
  int productNum = 0;
  int counter = 0;

  _incrementCounter(){
    setState(() {
      ++counter;
    });
  }
  _incrementProduct(){
    setState(() {
      ++productNum;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          constraints: BoxConstraints.expand(),
          child: Column(
            children: <Widget>[
              ShouldRebuild<Counter>(
                shouldRebuild: (oldWidget, newWidget) => oldWidget.counter != newWidget.counter,
                child: Counter(counter: counter,onClick: _incrementCounter,title: '我是优化过的Counter',) ,
              ),
              Counter(
                counter: counter,onClick: _incrementCounter,title: '我是未优化过的Counter',
              ),
              Text('productNum = $productNum',style: TextStyle(fontSize: 22,color: Colors.deepOrange),),
              RaisedButton(
                onPressed: _incrementProduct,
                child: Text('increment Product'),
              )
            ],
          ),
        ),
      ),
    );
  }
}



class Counter extends StatelessWidget {
  final VoidCallback onClick;
  final int counter;
  final String title;
  Counter({this.counter,this.onClick,this.title});
  @override
  Widget build(BuildContext context) {
    Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
    return AnimatedContainer(
      duration: Duration(milliseconds: 500),
      color:color,
      height: 150,
      child:Column(
        children: <Widget>[
          Text(title,style: TextStyle(fontSize: 30),),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('counter = ${this.counter}',style: TextStyle(fontSize: 43,color: Colors.white),),
            ],
          ),
          RaisedButton(
            color: color,
            textColor: Colors.white,
            elevation: 20,
            onPressed: onClick,
            child: Text('increment Counter'),
          ),
        ],
      ),
    );
  }
}



复制代码

布局翻译:flutter开发防止widget rebuild终极解决办法

  • 我们已经定义了Counter元素,并且Counter在构造阶段会改变它的颜色。每次执行构建时,背景颜色都会随机生成,以便我们检查项目是否已构建。此外,计数器还从父元素接收计数器值并显示它。它还得到一个标题来区分不同的Counter名称
  • 看这里的代码
           Column(
            children: <Widget>[
              ShouldRebuild<Counter>(
                shouldRebuild: (oldWidget, newWidget) => oldWidget.counter != newWidget.counter,
                child:  Counter(counter: counter,onClick: _incrementCounter,title: '我是优化过的Counter',),
              ),
              Counter(
                counter: counter,onClick: _incrementCounter,title: '我是未优化过的Counter',
              ),
              Text('productNum = $productNum',style: TextStyle(fontSize: 22,color: Colors.deepOrange),),
              RaisedButton(
                onPressed: _incrementProduct,
                child: Text('increment Product'),
              )
            ],
          )
复制代码

上面的Counter是用ShouldRebuild包装的,发送shouldRebuild参数是因为指定的条件是如果没有收到这个counter就构建。计数器不兼容。如果比较新旧计数器并发现计数器匹配,则不会重建它们。以下计数器未优化。

  • 当点击按钮添加Product增量Product时,会导致productNum增加,但计数器目前并没有增量,所以ShouldRebuild填充的Counter并没有重建,下面的Unwrapped容器是弥补的。看gif:
flutter开发防止widget rebuild终极解决办法

声明的规则

实际上,规则和用const声明的widget的规则是一样的。我们来看看flutter源码

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
      if (child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner._debugElementWasRebuilt(child);
          return true;
        }());
        return child;
      }

...
}
复制代码

去掉它的部分。第一个

if (child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        return child;
   }
复制代码

这是关键。 Flutter 检测到 child.widget 也是,即旧的 widget 和新的 widget 是一样的。如果引用匹配,则直接返回孩子。一旦完成此过程,设置过程就完成了。请看看它做了什么

@override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
复制代码

当你看到rebuild()时,你就知道你必须执行构建。

其实,检查 if (child.widget == newWidget) 之后,我们也知道为什么 Text() 会阻止文本被多次创建,因为常量永远不会改变

总结

在这个 Widget 中我们可以在主元素中设置状态,然后将页面分成几个子元素,然后用ShouldRebuild附加子元素,并设置一个构建条件以防止不必要的重建。您可以根据需要设置State。当然,如果你的状态被多个组件使用,你就需要状态管理。然而,有些人可能认为这太过分了。我个人认为是否需要改进取决于你的具体情况。如果有一天用户反映你的页面卡住了,你需要改进,或者你觉得配置对你产生了影响。动画等功能会重复执行,因此需要避免重建它们。

github:shouldRebuild

作者:超人一路向北

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门