이번 시간에는 위젯을 리빌드 하는 전략에 대해서 몇가지 파헤쳐보고자 합니다.
Consumer, Selector, .read, .watch메서드 등의 주요 컨셉과 사용에 대해서 같이 보겠습니다.
Consumer<MyModel>(
builder: (context, model, child) {
return Text(model.someValue);
},
)
<MyModel>
처럼 제네릭으로 어떤 타입의 데이터를 listen할지 구체적으로 명시 해야 합니다.ChangeNotifierProvider<MyModel>( // 첫 번째 MyModel Provider
create: (context) => MyModel(data: 'Data from Provider 1'),
child: Builder(
builder: (context) {
return ChangeNotifierProvider<MyModel>( // 두 번째 MyModel Provider (더 가까움)
create: (context) => MyModel(data: 'Data from Provider 2'),
child: Consumer<MyModel>(
builder: (context, myModel, child) {
return Text(myModel.data); // Data from Provider 2가 표시
},
),
);
}
),
);
하지만 우리가 알고싶은건 이런게 아니죠, MultiProvider로 같이 꽂아버리면 어떻게 될까요?
MultiProvider(
providers: [
Provider<MyModel>(create: (_) => MyModel(data: '1번타자')),
Provider<MyModel>(create: (_) => MyModel(data: '2번타자')),
],
child: MyWidget(),
);
Error: Multiple providers of the same type found at the same level in the widget tree.
class Consumer<T> extends SingleChildStatelessWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider<T>]
/// {@endtemplate}
Consumer({
Key? key,
required this.builder,
Widget? child,
}) : super(key: key, child: child);
/// {@template provider.consumer.builder}
/// Build a widget tree based on the value from a [Provider<T>].
///
/// Must not be `null`.
/// {@endtemplate}
final Widget Function(
BuildContext context,
T value,
Widget? child,
) builder;
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
<T>
을 명시해주고, ChangeNotifier에서 notifyListeners()가 호출되면, 해당 Provider를 리슨하는 모든 Consumer 위젯의 builder 함수가 실행됩니다.Consumer<MyModel>(
builder: ((context, value, child) {
return Column(
children: [child!, Text("child를 이렇게 사용할 수 있어요.")],
);
}),
child: Text("Consumer의 child 파라미터"),
),
SingleChildStatelessWidget
의 추상 메서드를 구현한 것이며, 개발자는 대부분 생성자를 사용하고 이 메서드를 직접 호출하지는 않습니다.StatefulWidget
에서 setState
메서드를 호출하면 일반적으로 해당 위젯 자체와 그 자식 위젯들이 모두 다시 빌드되는 반면, Consumer
위젯을 사용하면 상태 변화에 의존하는 특정 위젯만 선택적으로 다시 빌드할 수 있습니다.
class MyHomePage extends StatefulWidget {
MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int rebuildCount = 0;
Widget build(BuildContext context) {
rebuildCount++;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("current rebuild count is ${rebuildCount}"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("current rebuild count is ${rebuildCount}"),
ElevatedButton(
onPressed: () {
setState(() {});
},
child: const Text('rebuild screen'),
),
const Text(
'현재 카운트:',
style: TextStyle(fontSize: 20),
),
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: const TextStyle(
fontSize: 40, fontWeight: FontWeight.bold),
);
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
context.read<CounterProvider>().decrement();
},
child: const Text('-'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () {
context.read<CounterProvider>().increment();
},
child: const Text('+'),
),
],
),
],
),
),
);
}
}
Selector<MyModel, String>(
selector: (context, model) => model.specificValue,
builder: (context, value, child) {
return Text(value);
},
)
selector: (context, model) => model.specificValue
형태로 콕 찝어서 골라놓고, 이 값이 변하는지만 예의주시 합니다.Selector 위젯은 shouldRebuild라는 콜백 함수를 쥐어 줄 수 있습니다. 이것은 Selector의 핵심 기능 중 하나로, rebuild 에 조건을 추가할 수 있는 기능입니다.
selector 함수가 반환하는 값이 변경되었을 때, 실제로 UI를 rebuild 할 필요가 있는지를 추가적으로 판단하는 역할을 합니다.
예를 들어서, 감자튀김의 갯수를 표시하고 있다고 해볼까요?
중요한 것은 갯수이지, 감자튀김 각각의 사이즈나 눅눅해진 정도가 아니겠죠? 이 경우 아래처럼 구현 할 수 있습니다.
Selector<MyModel, List<int>>(
selector: (context, model) => model.potato,
shouldRebuild: (previous, next) => previous.length != next.length,
// 리스트의 길이가 변경된 경우에만 rebuild
builder: (context, numbers, child) {
return Text('감자튀김 ${numbers.length}개 남음');
},
)
간단히 정리하면 아래와 같으며, 내가 지금 사용하고자 하는 데이터의 특성에 맞춰서 전략적으로 사용해 보면 좋겠습니다 :)