Provider的局部刷新机制

以下以Provider 4.0.0版本进行分析。


使用方法就不说了,简单的来说,提供一个数据类型派生自ChangeNotifier,修改数据后调用notifyListeners()进行刷新通知。
有数据刷新需求的Widget外层包裹一个ListenableProvider,构造方法'create'将派生自ChangeNotifier的数据提供出去,'child'就用户自己写的Widget。
通过Provider.of(context)获得数据,进行UI绘制或者修改数据后刷新。最关键的代码就是这一行。

//provider.dart
static T of(BuildContext context, {bool listen = true}){
  //⑴,inheritedElement是_InheritedProviderScopeElement
  final inheritedElement = _inheritedElementOf(context);
  if(listener){
    //⑵
    context.dependOnInheritedElement(inheritedElement);
  }
  //⑶
  return inheritedElement.value;
}

⑴:
研究过ListenableProvider的源码可以知道,ListenableProvider build的Widget是_InheritedProviderScope派生自InheritedWidget,提供的element是_InheritedProviderScopeElement.
⑵:
了解Widget的构建后,可以知道这里的context就是调用Provider.of()本身的BuildContext,即一般来说是StatefulElement/Element,而StatefulElement派生自Element

//StatefulElement
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, {Object aspect}){
  //
  return super.dependOnInheritedElement(ancestor, aspect:aspect);
}
//Element
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, {Object aspect}){
  ...
  //this代表调用Provider.of的这个Element,
  ancestor.updateDependencies(this, aspect);
}

//InheritedElement
void updateDependencies(Element dependent, Object aspect){
  setDependencies(dependent, null);
}

final Map _dependents = HashMap();
void setDependencies(Element dependent, Object value){
  _dependents[dependent] = value;
}

于是所有调用Provider.of()的widget的Element都被存到了InheritedElement的_dependents.keys中。

⑶:

//_InheritedProviderScopeElement
//这里的value就是给到的数据
T get value => _delegateState.value;

//_CreateInheritedProviderState
T get value{
  ...
  //这个startListening是在ListenableProvider中定义的闭包
  _removeListener ??=delegate.startListening?.call(element, _value);
  ...
}

//ListenableProvider
static VoidCallback _startListening(InheritedContext e, Listenable value){
  //这里的value就是提供的数据,e就是_InheritedProviderScopeElemet
  value? .addListener(e.makeNeedsNotifyDependents);
  return () => value?.removeListener(e.makNeedsNotifyDependents);
}

class ChangeNotifier implements Listenable{
  ObserverList  _listeners = ObserverList();
}

经过上面的第3步,将e.makeNeedsNotifyDependents这个闭包放入了_listeners,从上面的代码也可以看到,只有第一个闭包才会被放入。

现在准备工作都做好了,开始更新数据吧

//ChangeNotifier
void notifyListeners(){
  final List localListeners = List.from(_listeners);
  for(final VoidCallback listener in localListeners){
    if(_listeners.contains())
      listener();
  }
}

于是,随即调用makeNeedsNotifyDependents()@_InheritedProviderScopeElemet

//_InheritedProviderScopeElemet
void makeNeedsNotifyDependents(){
  markNeedsBuild();
  _shouldNotifyDependents = true;
}

void markNeedsBuild(){
  _dirty = true;
  owner.scheduleBuildFor(this);
}

在下一个vsync来到时,因为该element被设置为_dirty,因为会进行build工作

Widget build(){
  if(_shouldNotifyDependents){
    _shouldNotifyDependents = false;
    notifyClients(Widget);
  }
  return super.build();
}

//ProxyElement
Widget build() => widget.child;

void notifyClient(InheritedWidget oldWidget){
  for(final Element dependent in _dependents.key){
    notifyDependent(oldWidget, dependents);
  }
}

void notifyDependent(covariant InheritedWidegt oldWidget, Element dependent){
  dependent.didChangeDependencies(); 
}

void didChangeDependencies(){
  makeNeedsBuild();
}

因此,每一次渲染,都会把调用个过Provider.of()的Element保存起来,在下一帧到来的时候进行重新绘制渲染。


Provider提供了一个Selector,可以自定义是否进行rebuild,需要注意的是,如果其父节点进行了build,其必定rebuild,因为使用Selector的时候要特别注意其挂载的节点,否则就丧失了Selector提供的本意。

class _Selector0State extends SingleChildState> {
  T value;
  Widget cache;
  Widget oldWidget;

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    final selected = widget.selector(context);

    var shouldInvalidateCache = oldWidget != widget ||
        (widget._shouldRebuild != null &&
            widget._shouldRebuild.call(value, selected)) ||
        (widget._shouldRebuild == null &&
            !const DeepCollectionEquality().equals(value, selected));
    if (shouldInvalidateCache) {
      value = selected;
      oldWidget = widget;
      cache = widget.builder(
        context,
        selected,
        child,
      );
    }
    return cache;
  }
}

其实同理,就可以编写自己的带cache的Widget了

你可能感兴趣的:(Provider的局部刷新机制)