温故知新-setState原理

40 阅读2分钟

在创建StatefulWidget时,当数据发生变化时,调用setState()方法后,页面就会做出相应的改变,这是为什么呢?

setState方法里面做了什么

  1. 执行了入参的callBack方法,这个方法不能是Future类型的
  2. 执行了element的markNeedsBuild方法
void setState(VoidCallback fn) {
    final Object? result = fn() as dynamic;
    _element!.markNeedsBuild();
  }

ElementmarkNeedsBuild方法

  1. 将element标记为脏,_dirty = true,一个element在一个绘制帧间隔中只会被标记一次脏
  2. 将脏的element作为参数,调用ownerscheduleBuildFor方法,owner就是在WidgetsBinding中创建的,在整个应用生命周期内只会有一个owner实例。
void markNeedsBuild() {
    if (dirty) {
      return;
    }
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

BuildOwnerscheduleBuildFor方法

  1. BuildScope是在应用启动时调用runApp,在创建rootElement时创建的,在整个应用生命周期内只会有一个buildScope实例。
  2. buildScope执行_scheduleBuildFor方法,入参是标记为脏的element。
void scheduleBuildFor(Element element) {
    final BuildScope buildScope = element.buildScope;
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    buildScope._scheduleBuildFor(element);
  }

BuildScope_scheduleBuildFor方法

将脏element添加到_dirtyElements列表中,并将element._inDirtyList状态设置为true。

void _scheduleBuildFor(Element element) {
    assert(identical(element.buildScope, this));
    if (!element._inDirtyList) {
      _dirtyElements.add(element);
      element._inDirtyList = true;
    }
    if (!_buildScheduled && !_building) {
      _buildScheduled = true;
      scheduleRebuild?.call();
    }
    if (_dirtyElementsNeedsResorting != null) {
      _dirtyElementsNeedsResorting = true;
    }
  }

到这里就完成了setState的标记工作,即把待更新的element添加到脏元素列表,并将其标记为脏,等待着下一步的处理。

回头看WidgetsBindingdrawFrame

ScheduleBinding调度新的一帧时,会调用buildOwner!.buildScope(rootElement!)方法,buildScope(rootElement!)方法会做些什么呢?

 void drawFrame() {
    try {
      if (rootElement != null) {
        buildOwner!.buildScope(rootElement!);
      }
      super.drawFrame();
  }

buildOwner!.buildScope

  1. 更新buildScope的状态,处理_dirtyElements中的脏元素
  2. element调用自身的rebuild方法,_dirty设置为false,element的子类会重写rebuild,但是其最终都会调用widget或是State的build方法重新更新对应的widget树
  3. 清空buildScope_dirtyElements列表,重置buildScope的状态
void buildScope(Element context, [VoidCallback? callback]) {
    final BuildScope buildScope = context.buildScope;
    if (callback == null && buildScope._dirtyElements.isEmpty) {
      return;
    }
    try {
      _scheduledFlushDirtyElements = true;
      buildScope._building = true;
      if (callback != null) {
        callback();
      }
      buildScope._flushDirtyElements(debugBuildRoot: context);
    } finally {
      buildScope._building = false;
      _scheduledFlushDirtyElements = false;
    }
  }
void _flushDirtyElements({required Element debugBuildRoot}) {
    _dirtyElements.sort(Element._sort);
    _dirtyElementsNeedsResorting = false;
    try {
      for (int index = 0; index < _dirtyElements.length; index = _dirtyElementIndexAfter(index)) {
        final Element element = _dirtyElements[index];
        if (identical(element.buildScope, this)) {
          _tryRebuild(element);
        }
      }
    } finally {
      for (final Element element in _dirtyElements) {
        if (identical(element.buildScope, this)) {
          element._inDirtyList = false;
        }
      }
      _dirtyElements.clear();
      _dirtyElementsNeedsResorting = null;
      _buildScheduled = false;
    }
  }
  
  void _tryRebuild(Element element) {
   ......
    try {
      element.rebuild();
    } catch (e, stack) {
     ......
    }
  }

最后通过PipleOwner去重新计算布局合成新的UI,至此也就是setState的原理流程。