在 Flutter 中用到最多的两个 Widget 就是 StatefulWidget 和 StatelessWidget,他们分别对应两个概念:有状态和无状态
有状态的意思是:它的父 Widget 刷新时,它不会被动的刷新,而它主动刷新也不会影响父 Widget 无状态的意思是:它的父 Widget 刷新是,它也会被动的刷新
比如 Text 就是一个无状态的 Widget,每次文字变化,你只需调用 setState,它就会刷新 而比如 Dialog 就是一类有状态的 Widget,如果在 AlertDialog 的外部调用 setState,它不会刷新。
首先,为什么不都用无状态的 Widget? 其实原因很简单,如果都是无状态的,那么每次刷新都从 Widge它树的根节点开始渲染太耗费性能,也没有必要
那怎么实现局部刷新的呢? 首先,我们看它的设计:
StatelessWidget 设计
class StatelessWidget extends Widget{
  @override
  StatelessElement createElement() => StatelessElement(this);
  @protected
  Widget build(BuildContext context);
}
StatefulWidget 设计
class StatefulWidget extends Widget{
  @override
  StatefulElement createElement() => StatefulElement(this);
  @protected
  State createState();
}
abstract class State<T extends StatefulWidget> extends Diagnosticable {
  void initState() {
    ...
  }
  void setState(VoidCallback fn) {
  ...
  }
  Widget build(BuildContext context);
  void dispose() {
   ...
  }
}
上面所说的有状态的其实是指这个 State 在父 Widget 刷新时没有被销毁重建,而 StatefulWidget 本身还是重新创建了。
从上面可以看到 StatelessWidget 的 build 方法在 Widget 中,而 StatefulWidget 则是剥离到 State 对象里,而 State 对象又由 StatefulElement 持有,由于之前讲的 Widget 的不可变性导致:StatelessWidget 和 StatefulWidget 的实例在每次父 Widget 刷新时都会销毁重建,所以 StatelessWidget.build 会及时刷新,而由于 StatefulWidget 的 build 方法被剥离到 State 对象中,而 State 对象又是由 StatefulElement 持有,所以在父 Widget 刷新时,State 对象并没有重建,State.build 方法并没有变化。
看到很多人文为什么不把 build 方法放在 StatefulWidget 中,非得创建一个 State 对象,写起来很费劲,这就是为什么。
那么,Flutter 如何实现局部刷新? 首先,当你在一个 StatefulWidget 中调用 setState,发生了下面这些事情:
  void setState(VoidCallback fn) {
    _element.markNeedsBuild();
  }
它会调用当前 StatefulElement.markNeedsBuild(),markNeedsBuild是由其父类markNeedsBuild` 实现的:
  void markNeedsBuild() {
    if (!_active)
      return;
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
- 把自己标记为 _dirty
- 调用 BuildOwner.scheduleBuildFor(Element)
BuildOwner 全局只有一个
那么 scheduleBuildFor 又做了哪些事情
   void scheduleBuildFor(Element element) {
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }
- 把 StatefulElement 加入到 BuildOwner 全局变量:_dirtyElements 中
- 标记需要在下次 VSync 时刷新界面
---------- 中间略去 VSync 信号处理过程 ----------
等下次 VSync 信号过来后,会调用 BuildOwner 的 buildScope 方法:
 void buildScope(Element context, [ VoidCallback callback ]) {
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
        _dirtyElementsNeedsResorting = false;
          callback();
      }
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        _dirtyElements[index].rebuild();
        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            // It is possible for previously dirty but inactive widgets to move right in the list.
            // We therefore have to move the index left in the list to account for this.
            // We don't know how many could have moved. However, we do know that the only possible
            // change to the list is that nodes that were previously to the left of the index have
            // now moved to be to the right of the right-most cleaned node, and we do know that
            // all the clean nodes were to the left of the index. So we move the index left
            // until just after the right-most clean node.
            index -= 1;
          }
        }
      }
    } finally {
      for (Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
    }
  }
这个方法主要做的事情:
- 遍历 _dirtyElements,调用每个 Element.rebuild()
- rebuild 完成后,将这些 Element 的 _inDirtyList 标记为 false(此刻 Element 由于还没有执行 layout 和 paint,所以仍然是脏的,但是已经执行了 build,所以为了避免在本次 VSync 再被加入到 _dirtyElements 中,所以用此标识符标记)
- 最后清空 _dirtyElements
致此,我们就知道了如何实现局部 build

