Riverpod 不必要的重建
Riverpod unnecessary rebuilds
问题是:当我单击我的一个 CharBarButtons 小部件时,所有其他 CharBarButtons 也被重建有没有办法避免这种情况并在使用 StateNotifier 持有列表时单独进行重建(整数是按钮的位置)
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 小部件是一个简单的小部件,决定了应该创建多少个 CharBarButtons。
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)
],
)
]),
);
}
}
这是 StateNotifier :
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,我无法按索引收听 manner.I 我正在听整个在这里列出。
final isTapped = useProvider(IndexStackProvider).contains(index);
注4:实际问题是,我们能否将重建限制为索引方式而不是监听整个列表状态
因为你用的是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.
问题是:当我单击我的一个 CharBarButtons 小部件时,所有其他 CharBarButtons 也被重建有没有办法避免这种情况并在使用 StateNotifier 持有列表时单独进行重建(整数是按钮的位置)
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 小部件是一个简单的小部件,决定了应该创建多少个 CharBarButtons。
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)
],
)
]),
);
}
}
这是 StateNotifier :
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,我无法按索引收听 manner.I 我正在听整个在这里列出。
final isTapped = useProvider(IndexStackProvider).contains(index);
注4:实际问题是,我们能否将重建限制为索引方式而不是监听整个列表状态
因为你用的是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.