【问题标题】:Riverpod unnecessary rebuildsRiverpod 不必要的重建
【发布时间】:2021-06-10 09:02:36
【问题描述】:

问题是:当我单击其中一个 CharBarButtons 小部件时,所有其他 CharBarButtons 也会被重建有没有办法避免这种情况并在使用 StateNotifier 持有 List 时单独进行重建(整数是按钮的位置)

CharBarButtons : (这里我根据 isTapped 的布尔值改变 CharBarButtons 的装饰)


class CharBarButtons extends HookWidget {
  CharBarButtons({required this.index});
  final int index;
  @override
  Widget build(BuildContext context) {
    final isTapped = useProvider(IndexStackProvider).contains(index);
    final _randomBar = useProvider(randomBarProvider);
    final usableWidth = MediaQuery.of(context).size.width -
        (MediaQuery.of(context).padding.left +
            MediaQuery.of(context).padding.right);
    final usableHeight = MediaQuery.of(context).size.height -
        (MediaQuery.of(context).padding.bottom +
            MediaQuery.of(context).padding.top);
    return InkWell(
        onTap: () {
          if (isTapped == false) {
            context.read(IndexStackProvider.notifier).append(index);
          }
        },
        child: AnimatedContainer(
            curve: Curves.easeOutExpo,
            duration: Duration(milliseconds: 125),
            child: Center(
                child: Center(
                    child: Opacity(
              opacity: isTapped == false ? 1 : 0.5,
              child: Text('${_randomBar[index]}',
                  style: TextStyle(
                      color: Colors.black87,
                      fontWeight: FontWeight.bold,
                      fontSize: usableHeight * 0.03)),
            ))),
            width: usableWidth * 0.12,
            height: usableWidth * 0.12,
            decoration: isTapped == false
                ? BoxDecoration(
                    color: const Color.fromRGBO(255, 227, 212, 1),
                    border: Border.all(
                        color: Colors.black.withOpacity(0.6), width: 2),
                    borderRadius: BorderRadius.circular(usableWidth * 0.02),
                    shape: BoxShape.rectangle,
                  )
                : BoxDecoration(
                    color: Color.fromRGBO(199, 177, 165, 1),
                    border: Border.all(
                        color: Colors.black.withOpacity(0.3), width: 2),
                    borderRadius: BorderRadius.circular(usableWidth * 0.02),
                    shape: BoxShape.rectangle,
                  )));
  }
}

CharBarButtons 的祖先:CharBar 小部件是一个简单的小部件,决定应该创建多少个 CharBarButton。

class CharBar extends HookWidget {
  const CharBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final usableWidth = MediaQuery.of(context).size.width -
        (MediaQuery.of(context).padding.left +
            MediaQuery.of(context).padding.right);
    final usableHeight = MediaQuery.of(context).size.height -
        (MediaQuery.of(context).padding.bottom +
            MediaQuery.of(context).padding.top);
    final _randomChars = useProvider(randomBarProvider);
    return Container(
      height: usableWidth * 0.24,
      width: usableWidth,
      child: _randomChars.length <= 6
          ? Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
              for (var i = 0; i < _randomChars.length; i++)
                CharBarButtons(index: i)
            ])
          : Column(children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  for (var i = 0; i < _randomChars.length ~/ 2; i++)
                    CharBarButtons(index: i)
                ],
              ),
              SizedBox(height: usableHeight * 0.05),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  for (var i = _randomChars.length ~/ 2;
                      i < _randomChars.length;
                      i++)
                    CharBarButtons(index: i)
                ],
              )
            ]),
    );
  }
}

这里是状​​态通知器:

class IndexStackState extends StateNotifier<List<int>> {
  IndexStackState() : super([]);
  void append(int index) {
    state = [...state, index];
  }

  void delete() {
    state = [...state.sublist(0, state.length - 1)];
  }

  void reset() {
    state = [];
  }
}

这里是提供者:

final IndexStackProvider =
    StateNotifierProvider.autoDispose<IndexStackState, List<int>>(
        (ref) => IndexStackState());

注意:CharBar(祖先)没有任何不必要的重建或其他祖先。只有 CharBarButtons 有不必要的重建。

注意 2:我知道 contains() 方法也很昂贵,但我将列表限制为最多 12 个整数元素。所以我认为这不应该是那么大的问题。

注意 3:由于这行代码(第一段代码)导致问题,并且在状态更新时它会重建所有 CharBarButtons,我无法以索引方式收听。我在这里收听整个列表。

final isTapped = useProvider(IndexStackProvider).contains(index);

注 4:实际问题是,我们能否将重建限制为索引方式而不是监听整个列表状态

【问题讨论】:

    标签: flutter riverpod


    【解决方案1】:

    因为您使用的是hooks,所以这实际上是可能的。

    变化:

    final isTapped = useProvider(IndexStackProvider).contains(index);
    

    到:

    final isTapped = useProvider(indexStackProvider.select((value) => value.contains(index)));
    

    select 语句使我们的小部件仅在函数参数返回的值发生变化时重建。

    我在下面提供了一个完整的示例,供任何想要自己测试的人使用。

    import 'package:flutter/material.dart';
    import 'package:flutter_hooks/flutter_hooks.dart';
    import 'package:hooks_riverpod/hooks_riverpod.dart';
    
    class IndexStackState extends StateNotifier<List<int>> {
      IndexStackState() : super([]);
    
      void append(int index) {
        state = [...state, index];
      }
    
      void delete() {
        state = [...state.sublist(0, state.length - 1)];
      }
    
      void reset() {
        state = [];
      }
    }
    
    final indexStackProvider =
        StateNotifierProvider.autoDispose<IndexStackState, List<int>>((ref) => IndexStackState());
    
    class ExampleWidget extends HookWidget {
      const ExampleWidget({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              children: List.generate(5, (index) => TestWidget(index)),
            ),
          ),
        );
      }
    }
    
    class TestWidget extends HookWidget {
      const TestWidget(this.index, {Key? key}) : super(key: key);
    
      final int index;
    
      @override
      Widget build(BuildContext context) {
        final isTapped = useProvider(indexStackProvider.select((value) => value.contains(index)));
        debugPrint('Building index: $index');
    
        return Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Center(
              child: Text('$index: ${isTapped.toString()}'),
            ),
            SizedBox(width: 16),
            ElevatedButton(
              onPressed: () {
                final notifier = context.read(indexStackProvider.notifier);
                isTapped ? notifier.delete() : notifier.append(index);
              },
              child: Text(isTapped ? 'Delete' : 'Append'),
            )
          ],
        );
      }
    }
    

    文档here.

    【讨论】:

    • 天啊,.select() 太神奇了!!你和雷米是天才 :D 再次感谢!!
    • @sweCB 不客气!这绝对是一个鲜为人知的功能,因为它仅适用于钩子。我们今天可以展示它真是太棒了!
    • 它现在也可以在 1.0.0-dev 版本的钩子之外使用,在 Pub 上可用。
    • @wujek 1.0 即将推出,多么令人兴奋!当我有时间进行更改时,我会更新此答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-07
    • 1970-01-01
    • 2015-11-10
    • 2021-04-21
    • 2022-11-28
    • 1970-01-01
    • 2021-07-28
    相关资源
    最近更新 更多