Flutter开始干系列-Provider4 Selector和Consumer对比使用

5,238 阅读3分钟

不得不说 Flutter 还很年轻,Flutter 和其生态环境都在告诉迭代,很多公司和开发者现在开始了 Flutter 的尝试。比如 Flutter 1.12+ ,比如 Provider 也迭代到了 Provider4,有一些变更,最重要的是增加了 Selector 实现了更细粒度的控制,使用过后就有一种想替换掉所有 Consumer 。

如果没有了解过 Provider 的同志,可以看看我很久很久之前写了两篇关于 Provider3 的文章,Flutter开始干系列-状态管理Provider3 简单介绍了Provider, Flutter开始干系列-完整列表与Provider3实战展现了一个简单的实战效果。

话不多说先上2张gif效果:

Consumer

Consumer

Selector

Selector

细心的同学可能已经看出来区别了。使用 Consumer 效果中点击 Increment 时,除了顶部的较大的数字变动外,下面的 Increment build 后面的数字也跟着变动了。使用 Selector 效果中点击 Increment 时,只有顶部的较大的数字变动。

实现 ChangeNotifer

首先实现一个 CounterNotifier 实现计数并在数字并通知更新,一般情况下我们使用 Provider 都是需要和 ChangeNotifer 一起使用。

class CounterNotifier with ChangeNotifier {
  int _count = 0;
  int _count1 = 0;
  int _buildCount = 0;

  int get count => _count;

  int get count1 => _count1;

  int get buildCount => _buildCount;

  increment() {
    _count++;
    notifyListeners();
  }

  increment1() {
    _count1++;
    notifyListeners();
  }
}

使用 Consumer

class _ProvidePageState extends State<ProvidePage> {
  CounterNotifier _counter = CounterNotifier();

  @override
  Widget build(BuildContext context) {
    print('build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: ChangeNotifierProvider.value(
          value: _counter,
          child: Column(
            children: <Widget>[
              Consumer<CounterNotifier>(
                builder: (context, v, child) {
                  return Expanded(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          'Increment1 build ${_counter.count1}  count => You have pushed the button this many times:',
                        ),
                        Text(
                          '${_counter.count}',
                          style: Theme.of(context).textTheme.display1,
                        ),
                        FlatButton(
                            onPressed: () {
                              _counter.increment();
                            },
                            child: Text('Increment'))
                      ],
                    ),
                  );
                },
              ),
              Consumer<CounterNotifier>(
                builder: (context, v, child) {
                  return Expanded(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          'Increment build ${_counter.count}  count => You have pushed the button this many times:',
                        ),
                        Text(
                          '${_counter.count1}',
                          style: Theme.of(context).textTheme.display1,
                        ),
                        FlatButton(
                            onPressed: () {
                              _counter.increment1();
                            },
                            child: Text('Increment1'))
                      ],
                    ),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Demo 不复杂,这里 ChangeNotifierProvider 持有了 CounterNotifier ,这里使用了两个 Consumer 实现了局部刷新。通过上面的效果图可以看到,一个 Consumer 中的按钮点击,除了刷新了当前的 Consumer,另一个 Consumer 也刷新重建了。

使用 Selector

class _ProvidePageState extends State<ProvidePage> {
  CounterNotifier _counter = CounterNotifier();

  @override
  Widget build(BuildContext context) {
    print('build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: ChangeNotifierProvider.value(
          value: _counter,
          child: Column(
            children: <Widget>[
              Selector<CounterNotifier, int>(
                selector: (_, notifier) => notifier.count,
//                shouldRebuild:(previous,next)=> false,
                builder: (context, value, child) {
                  return Expanded(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          'Increment1 build ${_counter.count1}  count => You have pushed the button this many times:',
                        ),
                        Text(
                          '${_counter.count}',
                          style: Theme.of(context).textTheme.display1,
                        ),
                        FlatButton(
                            onPressed: () {
                              _counter.increment();
                            },
                            child: Text('Increment'))
                      ],
                    ),
                  );
                },
              ),
              Selector<CounterNotifier, int>(
                selector: (_, notifier) => notifier.count1,
                builder: (context, value, child) {
                  return Expanded(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          'Increment build ${_counter.count}  count => You have pushed the button this many times:',
                        ),
                        Text(
                          '${_counter.count1}',
                          style: Theme.of(context).textTheme.display1,
                        ),
                        FlatButton(
                            onPressed: () {
                              _counter.increment1();
                            },
                            child: Text('Increment1'))
                      ],
                    ),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Demo 也不复杂,主要的区别仅仅是将 Consumer 换成了 Selector 其他都一样。但是这时候点击哪个 Selector 就只有哪个 Selector 重建刷新了。

Seletor 介绍

上面我们直接就上了效果图和代码,最后简单介绍下 Selector。

Selector({
    Key key,
    //  Widget Function(BuildContext context, T value, Widget child)
    // 用于构建 Widget
    @required ValueWidgetBuilder<S> builder,
    // S Function(BuildContext, A)
    // 用于指定使用哪个值作为重重建判断依据
    @required S Function(BuildContext, A) selector,
    // bool Function(T previous, T next)
    // 是否重建,一般情况下不用我们实现
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })

Selector 有 Selector0 ~ Selector6,具体使用大同小异,可以传 0 ~ 6 个 ChangeNotifer,但是 S(Value)泛型只有一个,如果需要多个个值同时判断返回 S 怎么办呢?这个时候可以使用元祖 Tuple

最后附上Demo地址(详见main3、main4):github.com/joker-fu/fl…