美文网首页
Provider的局部刷新机制

Provider的局部刷新机制

作者: Wi1ls努力努力再努力 | 来源:发表于2020-11-04 11:49 被阅读0次

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


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

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

    ⑴:
    研究过ListenableProvider的源码可以知道,ListenableProvider build的Widget是_InheritedProviderScope<T>派生自InheritedWidget,提供的element是_InheritedProviderScopeElement<T>.
    ⑵:
    了解Widget的构建后,可以知道这里的context就是调用Provider.of<T>()本身的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<T>的这个Element,
      ancestor.updateDependencies(this, aspect);
    }
    
    //InheritedElement
    void updateDependencies(Element dependent, Object aspect){
      setDependencies(dependent, null);
    }
    
    final Map<Element, Object> _dependents = HashMap<Element, Object>();
    void setDependencies(Element dependent, Object value){
      _dependents[dependent] = value;
    }
    

    于是所有调用Provider.of<T>()的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<Listenable> e, Listenable value){
      //这里的value就是提供的数据,e就是_InheritedProviderScopeElemet
      value? .addListener(e.makeNeedsNotifyDependents);
      return () => value?.removeListener(e.makNeedsNotifyDependents);
    }
    

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

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

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

    //ChangeNotifier
    void notifyListeners(){
      final List<VoidCallback> localListeners = List<VoidCallback>.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<T>()的Element保存起来,在下一帧到来的时候进行重新绘制渲染。


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

    class _Selector0State<T> extends SingleChildState<Selector0<T>> {
      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的局部刷新机制

          本文链接:https://www.haomeiwen.com/subject/ovgwvktx.html