复用Widget提高build性能

来源:blog.csdn.net 时间:2021-03-14 14:41
StatefulWidget根据state的变化会进行rebuild,所有子widget也会随之rebuild。通过diff可以避免element的更新,因为element的更新成本很高。相比较来说widget的创建和rebuild则要轻量的多,理论上反复进行也无伤大雅。但是如果有些子widget无需创建和rebuild时,是否可以优化掉这些多余执行呢?

使用const
在widget前面加const是个一劳永逸的办法,但是你一旦加了const,你这个widget就永远不会更新了,除非你是在写静态页面,否则你最好不要用它。

使用HoC
用过react的人都知道,react的类组件有个很重要的生命周期叫shouldComponentUpdate ,可以在组件内部重写这个声明周期来进行性能优化。虽然Flutter不提供这种机制,但我们可以实行一个类似效果:
 

class ShouldRebuildWidget<T extends Widget> extends StatefulWidget {
  final Function shouldRebuild;
  final Function widgetProvider;
  ShouldRebuild({
    @required this.shouldRebuild,
    @required this.widgetProvider});
    
  @override
  _ShouldRebuildState createState() => _ShouldRebuildState<T>(provider);
}

class _ShouldRebuildState<T extends Widget> extends State<ShouldRebuild> {

  _ShouldRebuildState(this._provider) : super() {}

  ChildProvider _provider;

  @override
  ShouldRebuild<T> get widget => super.widget;

  T _oldWidget;
  @override
  Widget build(BuildContext context) {
    if (this._oldWidget == null || (widget.shouldRebuild == null ? true : widget.shouldRebuild(_oldWidget))) {
      this._oldWidget = _provider();
    }
    return _oldWidget;
  }
}
 

ShouldRebuildWidget类似react的HOC,包裹真正的widget;
shouldRebuild方法类似react的shouldComponentUpdate,由于flutter的state不是immutable的,无法返回新旧两个state进行比较,只能返回当前子widget进行比较,比较后发现无需创建新的子widget则返回false。

使用效果
首先实现一个WrappedWidget,
class Counter extends StatelessWidget {
  final VoidCallback onClick;
  final int count;
  final String title;
  Counter({this.count,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,
      data-height: 150,
      child:Column(
        children: <Widget>[
          Text(title,style: TextStyle(fontSize: 30),),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('counter = ${this.count}',style: TextStyle(fontSize: 43,color: Colors.white),),
            ],
          ),
          RaisedButton(
            color: color,
            textColor: Colors.white,
            elevation: 20,
            onPressed: onClick,
            child: Text('increment Counter'),
          ),
        ],
      ),
    );
  }
}
 
使用Counter时,使用ShouldBuildWidget进行包裹
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>[
               ShouldRebuildWidget<Counter>(
                shouldRebuild: (Counter old) {
                  return old.count != counter;
                },
                provider: () => Counter(count: counter,onClick: _incrementCounter,title: 'I am good Counter',),

              ),

              Text('productNum = $productNum',style: TextStyle(fontSize: 22,color: Colors.deepOrange),),
              RaisedButton(
                onPressed: _incrementProduct,
                child: Text('increment Product'),
              )
            ],
          ),
        ),
      ),
    );
  }
}
 


可以看到,点击increment Product时,Counter不会创建和rebuild,只有点击increment Counter才会重建并且rebuild