美文网首页
provider之selector

provider之selector

作者: kurumic | 来源:发表于2020-03-18 21:29 被阅读0次

    provider在新版本中添加了selector,整理一下selector使用时候会踩的坑

    一.Consumer和Selector的简单区别

    provider早期版本中只有Consumer,当model中调用notifyListeners()的时候,对应的Consumer的视图就会整个rebuild,而在3.x版本之后出现Selector可以仅选择model中某个值来最小范围的刷新视图,并且在4.0版本之后会对使用的值进行deep check,也可以自己自定义shouldRebuild。

    二. 常见坑

    1.如果为同一个model,避免在Consumer中使用selector

    class TestPage extends StatelessWidget {
        @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ChangeNotifierProvider<TestModel>(
            create: (_) => TestModel(),
            child: Consumer<TestModel>(builder: (ctx, testModel, child) {
              debugPrint('Consumer build');
              return Column(
                children: <Widget>[
                  Text(testModel.string1),
                  RaisedButton(onPressed: () {
                    Provider.of<TestModel>(ctx, listen: false).changeString2();
                  }, child: Text('change'),),
                  Selector<TestModel, String>(
                    selector: (_, testModel) => testModel.string2,
                    builder: (_, str, c) {
                       debugPrint('string2 build');
                      return Text(str);
                    },
                  )
                ],
              );
            }),
          ),
        );
      }
    }
    
    
    • 上面代码中selector是没有意义的,当调用changeString2修改string2触发notifyListeners()的时候, Consumer就会重新rebuild,包裹下的selector也会更新rebuild。

    修改后

     ChangeNotifierProvider<TestModel>(
            create: (_) => TestModel(),
            child: Selector<TestModel, String>(selector: (_, testModle) => testModle.string1 , builder: (ctx, strint1, child) {
              debugPrint('Consumer build');
              return Column(
                children: <Widget>[
                  Text(strint1),
                  RaisedButton(onPressed: () {
                    Provider.of<TestModel>(ctx, listen: false).changeString2();
                  }, child: Text('change'),),
                  Selector<TestModel, String>(
                    selector: (_, testModel) => testModel.string2,
                    builder: (_, str, c) {
                       debugPrint('string2 build');
                      return Text(str);
                    },
                  )
                ],
              );
            }),
          )
    

    如上修改后,当model中string2变化的时候,就只会rebuild string2的视图,string1并不会rebuild。

    2.使用Provider.of获取model中的值报错

    class TestPage extends StatelessWidget {
        @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ChangeNotifierProvider<TestModel>(
            create: (_) => TestModel(),
            child: Consumer<TestModel>(builder: (ctx, testModel, child) {
              debugPrint('Consumer build');
              return Column(
                children: <Widget>[
                  Text(testModel.string1),
                  RaisedButton(onPressed: () {
                    Provider.of<TestModel>(context, listen: false).changeString2();
                  }, child: Text('change'),),
                  Selector<TestModel, String>(
                    selector: (_, testModel) => testModel.string2,
                    builder: (_, str, c) {
                       debugPrint('string2 build');
                      return Text(str);
                    },
                  )
                ],
              );
            }),
          ),
        );
      }
    }
    

    如上代码是会报 Could not find the correct Provider<TestModel> above this TestPage Widget的错误,修改为 Provider.of<TestModel>(ctx, listen: false).changeString2(), 关于context问题请查阅相关资料,这里不再赘述。同时在selected中注意设置listen为false。

    3.dart中的引用对象问题

    dart中List等属于引用类型对象,和js一样。由此当使用selector选择的值为引用类型对象的时候,需要特别注意。

    ChangeNotifierProvider<TestModel>(
            create: (_) => TestModel(),
            child: Selector<TestModel, List>(
                selector: (_, testModel) => testModel.numberList,
                shouldRebuild: (prev, next) => prev.first != next.first,
                builder: (ctx, numberList, child) {
                  return Column(
                    children: <Widget>[
                      RaisedButton(
                        onPressed: () {
                          Provider.of<TestModel>(ctx, listen: false).changeNumber();
                        },
                        child: Text(numberList.first.toString()),
                      ),
                      Text(numberList.last.toString())
                    ],
                  );
                }),
          )
    
    changeNumber() {
        numberList.first = Random().nextInt(10);
        debugPrint(numberList.first.toString());
        notifyListeners();
      }
    

    在某种场景下,可能有时候会和如上代码所示一样希望selected一个引用对象,仅希望在引用对象中某个值变化的时候rebuild视图,然而会发现通过上面的代码会发现numberList.first已经改变,但是视图不会rebuild。就算是取消自定义的shouldRebuild使用源码中的deep check也依然不会rebuild。

    看下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;
      }
    }
    

    可以发现selector会缓存selected的值并保存到value,如果seleted的值为引用对象的时候,value和selected指向同一个引用地址,当调用changeNumber的时候,修改了numberList的值,但是引用地址没变,缓存的value中的值也随着发生变化,所以在_shouldRebuild的时候对比的value和selected一直是一样的,视图也就不会rebuild。

    官方在缓存value的时候并没有使用深拷贝,而是推荐选择的值为immutable

    Why selected value must be immutable

    相关文章

      网友评论

          本文标题:provider之selector

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