flutter中的rebuild一些理解

田田田就是我發表於2021-08-12

在flutter中濫用statefulWidget將導致不可知的一些問題,其中一個問題就是會導致不停的rebuild整個頁面,設想一下,你要是隻需要點選一個按鈕,更新一個按鈕,卻在點選這個按鈕的時候,rebuild了整個頁面,豈不是對效能的過大消耗? 為了解決這個問題,和直觀的感覺這個問題,我引入了provider這個外掛來作說明。 先上效果:

按第一個區域的按鈕:

按第一個區域的按鈕

按下第二個區域後再按下第三個區域:

按下第二個區域後再按下第三個區域

這裡我用了三塊區域來作說明:這三塊區域都是使用的隨機顏色,每次rebuild都會改變這些顏色。 首先,第一個區域是使用provider外掛的,點選了按鈕,counter會加1,但背景色沒有做改變。 第二個區域使用的資料來自於最外面的statefulWidget,點選了按鈕之後整個頁面將會rebuild。 第三個區域是相當於一個葉子節點,它的counter資料來自於自己,點選按鈕之後,這個區域會發生rebuild即只更新這個區域。 程式碼如下:

import 'dart:math';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'models/counter.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CounterModel(0)),
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Test()
      )
    );
  }
}

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

class _TestState extends State<Test> {
  // 這個counter將在第二區域中使用
  int counter = 0;

  _incrementCounter(){
    setState(() {
      ++counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          constraints: BoxConstraints.expand(),
          child: Column(
            children: <Widget>[
              Counter1(
                title: '使用provider不會觸發rebuild'
              ),
              Counter2(
                counter: counter,
                onClick: _incrementCounter,
                title: '使用的是最外層的資料,改變會觸發整個頁面的rebuild',
              ),
              Counter3(
                title: '葉子節點,使用的自己內部的資料,自身rebuild不會影響外面',
              )
            ],
          ),
        ),
      ),
    );
  }
}
// 首先,第一個區域是使用provider外掛的,點選了按鈕,counter會加1,但背景色沒有做改變
class Counter1 extends StatelessWidget {

  final counter;
  final title;
  Counter1({this.counter,this.title});
  BuildContext ctx;

  @override
  Widget build(BuildContext context) {
    ctx = context;
    Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
    return Container(
      child: Consumer(builder: (BuildContext context, CounterModel counterModel, Widget child){
        return AnimatedContainer(
          duration: Duration(seconds: 1),
          color: color,
          height: 150,
          child: Column(
            children: <Widget>[
              Text(title,style: TextStyle(fontSize: 30)),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text('counter= ${counterModel.count}')
                ],
              ),
              RaisedButton(
                color: color,
                textColor: Colors.white,
                elevation: 20,
                child: Text('點我增加'),
                onPressed: _increment
              )
            ],
          ),
        );
      })
    );
  }

  void _increment() {
    Provider.of<CounterModel>(ctx,listen: false).increment();
    print(Provider.of<CounterModel>(ctx,listen: false).count);
  }
}
// 第二個區域使用的資料來自於最外面的statefulWidget,點選了按鈕之後整個頁面將會rebuild。
class Counter2 extends StatelessWidget {
  final VoidCallback onClick;
  final int counter;
  final String title;
  Counter2({
    Key key,
    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: 250,
      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'),
          ),
        ],
      ),
    );
  }
}
// 第三個區域是相當於一個葉子節點,它的counter資料來自於自己,點選按鈕之後,這個區域會發生rebuild即只更新這個區域。
class Counter3 extends StatefulWidget {
  final title;
  Counter3({
    Key key,
    this.title
  }): super(key: key);

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

class _Counter3State extends State<Counter3> {
  int counter = 0;
  @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: 250,
      child:Column(
        children: <Widget>[
          Text(widget.title,style: TextStyle(fontSize: 30)),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('counter = $counter',style: TextStyle(fontSize: 43,color: Colors.white),),
            ],
          ),
          RaisedButton(
            color: color,
            textColor: Colors.white,
            elevation: 20,
            onPressed: _click,
            child: Text('increment Counter'),
          ),
        ],
      ),
    );
  }

  _click() {
    setState(() {
      ++counter;
    });
  }
}

複製程式碼

第一個區域中的model檔案:

import 'package:flutter/material.dart';

class CounterModel with ChangeNotifier {
  int _count;
  CounterModel(this._count);
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void setValue(int val) {
    _count = val;
    notifyListeners();
  }
}
複製程式碼

相關文章