InheritedWidget - ์์ ฏ ํธ๋ฆฌ๋ก ๊ฐ ๋ด๋ ค์ฃผ๊ธฐ
์ง๋ ๊ธ๋ค์์ ๊ฐ์ด ๋ฐ๋๋ ํ๋ฉด์ StatefulWidget์ State์ ๊ฐ์ ๋๊ณ setState๋ก ๋ค์ ๊ทธ๋ฆฐ๋ค๊ณ ํ๋ค. ๊ทธ๋ฐ๋ฐ ๊ทธ ๊ฐ์ ํธ๋ฆฌ ๊น์ํ ๊ณณ์ ์์ ฏ์ด ์จ์ผ ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น? ํ๋ฉด ๋งจ ์์ count๊ฐ ์๊ณ , ๊ทธ ๊ฐ์ ๋ณด์ฌ์ฃผ๋ Text๋ ์ธ ๋จ๊ณ ์๋์ ์๋ค๊ณ ํด๋ณด์. ์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ฐฉ๋ฒ๋๋ก ์ง๋ฉด ์ด๋ ๊ฒ ๋๋ค.
class CountPage extends StatefulWidget {
const CountPage({super.key});
@override
State<CountPage> createState() => _CountPageState();
}
class _CountPageState extends State<CountPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Section(count: count); // ํ ๋จ๊ณ
}
}
class Section extends StatelessWidget {
final int count;
const Section({super.key, required this.count});
@override
Widget build(BuildContext context) {
return InfoBox(count: count); // Section์ count๋ฅผ ์ฐ์ง ์๋๋ฐ ๋๊ฒจ์ผ ํจ
}
}
class InfoBox extends StatelessWidget {
final int count;
const InfoBox({super.key, required this.count});
@override
Widget build(BuildContext context) {
return CountText(count: count); // InfoBox๋ ๋ง์ฐฌ๊ฐ์ง
}
}
class CountText extends StatelessWidget {
final int count;
const CountText({super.key, required this.count});
@override
Widget build(BuildContext context) {
return Text('$count'); // ์ง์ง๋ก ์ฐ๋ ๊ณณ์ ์ฌ๊ธฐ๋ฟ
}
}์๋ฌ๊ฐ ๋๋ ๊ฑด ์๋๋ค. ๊ทธ๋ฐ๋ฐ Section๊ณผ InfoBox๋ count๋ฅผ ์ฐ์ง๋ ์์ผ๋ฉด์ ์์ฑ์๋ก ๋ฐ์์ ์๋๋ก ๋๊ธฐ๊ธฐ๋ง ํ๋ค. ๊ฐ์ด ํ๋ ๋ ํ์ํด์ง๋ฉด ์ค๊ฐ์ ์๋ ์์ ฏ๋ค์ ์์ฑ์๋ฅผ ์ ๋ถ ๊ณ ์ณ์ผ ํ๋ค. ์์ ฏ์ด ์ด ๋จ๊ณ ๊น์ด์ง๋ฉด ์ด ๊ตฐ๋ฐ๋ฅผ ๊ณ ์ณ์ผ ํ๋ค.
์์ธ: ๊ฐ์ ์๋๋ก ๋ณด๋ด๋ ๊ธธ์ด ์์ฑ์ ํ๋๋ฟ์ด๋ค
๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ๋ ์ด๋ฏธ ์์ฑ์๋ก ๋๊ธฐ์ง ์๊ณ ๋ ์์ชฝ์ ๊ฐ์ ์ฐ๋ ์ฝ๋๋ฅผ ์ฌ๋ฌ ๋ฒ ๋ดค๋ค.
Theme.of(context).colorScheme.primary
MediaQuery.sizeOf(context).widthTheme๋ MediaQuery๋ ์ด๋์ ๊ฐ ์์ฑ์๋ก ๋ฐ์ ๊ฒ ์๋๋ค. of(context)๊ฐ ํธ๋ฆฌ์์ ๋ด ์์น๋ก๋ถํฐ ์๋ก ์ฌ๋ผ๊ฐ๋ฉด์ ๊ฐ์ฅ ๊ฐ๊น์ด ๊ฒ์ ์ฐพ์์จ๋ค. ์ด ๋์์ ํ๋ ์์ ฏ์ด InheritedWidget์ด๋ค. ๋ด ๊ฐ๋ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ด๋ ค๋ณด๋ผ ์ ์๋ค.
ํด๊ฒฐ: InheritedWidget์ผ๋ก ๊ฐ์ ํธ๋ฆฌ์ ๊ฑธ์ด๋๊ธฐ
class CountScope extends InheritedWidget {
final int count;
const CountScope({
super.key,
required this.count,
required super.child,
});
static CountScope of(BuildContext context) {
final scope = context.dependOnInheritedWidgetOfExactType<CountScope>();
assert(scope != null, 'CountScope๊ฐ ์์ ฏ ํธ๋ฆฌ ์์ ์์ด์');
return scope!;
}
@override
bool updateShouldNotify(CountScope oldWidget) => count != oldWidget.count;
}์ธ ๋ถ๋ถ์ผ๋ก ๋๋๋ค.
- ํ๋์
child: ๋ด๋ ค๋ณด๋ผ ๊ฐ(count)๊ณผ, ์ด ๊ฐ์ ์ธ ์ ์๋ ์์ญ(child์๋ ์ ๋ถ)์ด๋ค.required super.child๋ ๋ถ๋ชจ ์์ฑ์๋กchild๋ฅผ ๋๊ธฐ๋ ์ค์ ๋ฌธ๋ฒ์ด๋ค. of: ์๋ก ์ฌ๋ผ๊ฐCountScope๋ฅผ ์ฐพ์์ค๋ ์ ์ ๋ฉ์๋๋ค. ์ฐพ์ง ๋ชปํ๋ฉด null์ด๋ผ์!๋ก ๋จ์ธํ๊ธฐ ์ ์assert๋ก ํ์ธํ๋ค.Theme.of(context)๊ฐ ์ด ๋ชจ์์ด๋ค.updateShouldNotify: ๊ฐ์ด ๋ฐ๋์์ ๋ ์๋์ ์์ ฏ๋ค์ ๋ค์ ๊ทธ๋ฆด์ง ์ ํ๋ค.count๊ฐ ๋ฌ๋ผ์ก์ ๋๋งtrue๋ฅผ ๋๋ ค์ค๋ค.
์ด์ ์ค๊ฐ ์์ ฏ์์ count๊ฐ ์ฌ๋ผ์ง๋ค.
class _CountPageState extends State<CountPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return CountScope(
count: count,
child: Column(
children: [
const Section(), // count๋ฅผ ์ ๋๊ฒจ๋ ๋จ
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('+1'),
),
],
),
);
}
}
class Section extends StatelessWidget {
const Section({super.key});
@override
Widget build(BuildContext context) => const InfoBox();
}
class InfoBox extends StatelessWidget {
const InfoBox({super.key});
@override
Widget build(BuildContext context) => const CountText();
}
class CountText extends StatelessWidget {
const CountText({super.key});
@override
Widget build(BuildContext context) {
final count = CountScope.of(context).count; // ์๋ก ์ฌ๋ผ๊ฐ ์ฐพ์์ด
return Text('$count');
}
}Section๊ณผ InfoBox๋ count๋ฅผ ๋ชฐ๋ผ๋ ๋๋ค. ๊ฐ์ด ํ์ํ CountText๋ง CountScope.of(context)๋ก ๊บผ๋ด ์ด๋ค.
const ์์ ฏ์ ๋ค์ ์ ๋ง๋ค์ด์ง๋๋ฐ, CountText๋ ์ด๋ป๊ฒ ๊ฐฑ์ ๋๋
์ ์ฝ๋์์ const Section()์ const๋ผ์ setState๊ฐ ๋ถ๋ ค๋ ์๋ก ๋ง๋ค์ด์ง์ง ์๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ฒํผ์ ๋๋ฅด๋ฉด CountText์ ์ซ์๋ ๋ฐ๋๋ค. CountText๊ฐ of(context)๋ฅผ ๋ถ๋ฅด๋ ์๊ฐ "๋๋ CountScope์ ์์กดํ๋ค"๊ณ ๋ฑ๋ก๋๊ธฐ ๋๋ฌธ์ด๋ค. setState๋ก ์ CountScope๊ฐ ๋ง๋ค์ด์ง๊ณ updateShouldNotify๊ฐ true๋ฅผ ๋๋ ค์ฃผ๋ฉด, ์์กด์ ๋ฑ๋กํ ์์ ฏ๋ง ๊ณจ๋ผ์ ๋ค์ ๊ทธ๋ ค์ง๋ค. ์ค๊ฐ์ Section, InfoBox๋ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
์ด๊ฒ of ์์ ๋ค์ด ์๋ dependOnInheritedWidgetOfExactType์ ์ญํ ์ด๋ค. ๊ฐ์ ์ผ์ ํ๋ ๊ตฌ๋
ํ์ง ์๋ getInheritedWidgetOfExactType๋ ์๋ค. ๊ฐ์ ํ ๋ฒ ์ฝ๊ธฐ๋ง ํ๊ณ ๋ณ๊ฒฝ์๋ ๋ฐ์ํ์ง ์๋๋ค. "๊ตฌ๋
ํ๋ฉฐ ์ฝ๊ธฐ"์ "ํ ๋ฒ๋ง ์ฝ๊ธฐ"๊ฐ ๊ฐ๋ฆฐ๋ค๋ ๊ฑธ ๊ธฐ์ตํด ๋๋ฉด ๋ค์ ๊ธ์์ ๋ง๋ watch์ read๊ฐ ์ฝ๊ฒ ์ดํด๋๋ค.
์ฝ์ง ํฌ์ธํธ: ๊ฐ์ ๊ฑธ์ด๋ ๊ณณ๊ณผ ์ฝ๋ ๊ณณ์ด ๊ฐ์ build๋ฉด ๋ชป ์ฐพ๋๋ค
๊ฐ์ ๋ด๋ ค์ฃผ๋ ์ชฝ์์ ๋ฐ๋ก ์ฝ์ด๋ณด๋ฉด ์ด๋ฐ ์ผ์ด ์๊ธด๋ค.
@override
Widget build(BuildContext context) {
return CountScope(
count: count,
child: Text('${CountScope.of(context).count}'), // ์ฌ๊ธฐ์ ์๋ฌ๋จ
);
}of๋ ์๋ก๋ง ์ฌ๋ผ๊ฐ๋ค. ์ด build์ context๋ ์ด ์์ ฏ ์์ ์ ์์น์ด๊ณ , CountScope๋ ๊ทธ๋ณด๋ค ์๋์ ์๋ค. ๋ด ์์ชฝ์๋ CountScope๊ฐ ์์ผ๋ ๋ชป ์ฐพ๊ณ , ์์์ ๋ฃ์ด๋ assert๊ฐ ํฐ์ง๋ค.
ํด๊ฒฐ์ ์ฝ๋ ์ชฝ์ ๋ณ๋์ ์์ ฏ์ผ๋ก ๋นผ์ CountScope๋ณด๋ค ์๋์ ๋๋ ๊ฒ์ด๋ค. ์์์ CountText๋ฅผ ๋ฐ๋ก ๋ง๋ ์ด์ ๊ฐ ์ด๊ฑฐ๋ค. ์์ ฏ์ ์๋ก ๋ง๋ค๊ธฐ ๋ฒ๊ฑฐ๋ก์ฐ๋ฉด Builder๋ก ๊ฐ์ธ์ ์ context๋ฅผ ์ป์ ์๋ ์๋ค.
CountScope(
count: count,
child: Builder(
builder: (context) => Text('${CountScope.of(context).count}'), // OK
),
)Builder์ context๋ CountScope ์๋์ ์์น๋ผ์ ์๋ก ์ฌ๋ผ๊ฐ๋ฉด ์ฐพ์ ์ ์๋ค.
InheritedWidget๋ง์ผ๋ก๋ ์ ๋ถ์กฑํ๊ฐ
InheritedWidget์ ๋ค๋ฅธ ์์ ฏ์ฒ๋ผ ๋ถ๋ณ์ด๋ค. count๋ฅผ ๋ฐ๊พธ๋ ค๋ฉด ์์ชฝ์ State์์ setState๋ฅผ ๋ถ๋ฌ์ ์ CountScope๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค. ๊ทธ๋์ ๊ฐ์ ์ฝ๋ ๊ฑด ํธํด์ก์ง๋ง, ๊ฐ์ ๋ฐ๊พธ๋ ์ชฝ์ ์ฌ์ ํ StatefulWidget๊ณผ ์ฝ๋ฐฑ์ ๊ธฐ๋์ผ ํ๋ค. ๊ฐ์ด ๋์ธ ๊ฐ๋ก ๋์ด๋๋ฉด InheritedWidget ํด๋์ค์ of, updateShouldNotify๋ฅผ ๊ฐ๋ง๋ค ๋ง๋ค์ด์ผ ํ๋ ๊ฒ๋ ๋ฒ๊ฑฐ๋กญ๋ค.
์ด ๋ฐ๋ณต์ ์ค์ฌ์ฃผ๋ ๊ฒ ๋ค์ ๊ธ์ Provider๋ค. Provider๋ InheritedWidget์ ๊ฐ์ผ ํจํค์ง๋ผ์, ์ค๋ ๋ณธ ์๋ฆฌ๊ฐ ๊ทธ๋๋ก ์์ ๋ค์ด ์๋ค. ์ฐธ๊ณ ๋ก Theme, MediaQuery๋ ๋ชจ๋ InheritedWidget์ด๋ค.
Compose, SwiftUI์ ๋น๊ตํ๋ฉด
// Compose - CompositionLocal
val LocalCount = compositionLocalOf { 0 }
CompositionLocalProvider(LocalCount provides count) {
Section() // ์ค๊ฐ ํจ์๋ count๋ฅผ ๋ชจ๋ฅธ๋ค
}
@Composable
fun CountText() {
Text("${LocalCount.current}")
}// SwiftUI - Environment
struct CountKey: EnvironmentKey {
static let defaultValue = 0
}
extension EnvironmentValues {
var count: Int {
get { self[CountKey.self] }
set { self[CountKey.self] = newValue }
}
}
Section().environment(\.count, count)
struct CountText: View {
@Environment(\.count) var count
var body: some View { Text("\(count)") }
}- ๊ฐ์ ๊ฑธ์ด๋๋ ๊ณณ: Flutter
InheritedWidget์ ๋ง๋ค์ด ํธ๋ฆฌ์ ๋ฃ๊ธฐ / ComposeCompositionLocalProvider/ SwiftUI.environment - ๊ฐ์ ๊บผ๋ด๋ ๊ณณ: Flutter
CountScope.of(context)/ ComposeLocalCount.current/ SwiftUI@Environment - ๊ธฐ๋ณธ๊ฐ: Flutter๋ ์์ผ๋ฉด ์๋ฌ(
assert) / ComposecompositionLocalOf์ ๊ธฐ๋ณธ๊ฐ / SwiftUIdefaultValue - ์ฌ๊ทธ๋ฆฌ๊ธฐ ๋์: ์ธ ๋ฐฉ์ ๋ชจ๋ ๊ฐ์ ์ฝ๋ ์ชฝ๋ง ๋ค์ ๊ทธ๋ ค์ง๋ค
Compose์ CompositionLocal๊ณผ ๊ฐ์ฅ ๋ฎ์๋ค. ๋ค๋ง Flutter๋ ์ด๊ฑธ ์ํด ์์ ฏ ํด๋์ค๋ฅผ ์ง์ ๋ง๋ค์ด์ผ ํด์ ์ฝ๋๊ฐ ๊ธธ๋ค.
์ ๋ฆฌ
- ํธ๋ฆฌ ๊น์ ๊ณณ์ ์์ ฏ์ด ๊ฐ์ ์ฐ๋ ค๊ณ ์์ฑ์๋ก ๊ณ์ ๋๊ธฐ๋ฉด, ์ฐ์ง ์๋ ์ค๊ฐ ์์ ฏ๊น์ง ์ฝ๋๋ฅผ ๊ณ ์ณ์ผ ํ๋ค
Theme.of(context)์ฒ๋ผ ์๋ก ์ฌ๋ผ๊ฐ๋ฉฐ ๊ฐ์ ์ฐพ์์ฃผ๋ ์์ ฏ์ดInheritedWidget์ด๊ณ ,of์updateShouldNotify๋ฅผ ๋ง๋ค์ด ์ด๋คof๋ ๊ฐ์ ๊ตฌ๋ ํ๋ฉฐ ์ฝ์ด์, ๊ฐ์ด ๋ฐ๋๋ฉด ์ฝ์ ์์ ฏ๋ง ๋ค์ ๊ทธ๋ ค์ง๋ค (const์์ ฏ ์ฌ์ด์ ์์ด๋ ๊ฐฑ์ ๋๋ค)- ๊ฐ์ ๊ฑธ์ด๋ ๊ณณ๊ณผ ๊ฐ์
build์์ ์ฝ์ผ๋ฉด ์๋ก ์ฌ๋ผ๊ฐ๋ ๋ชป ์ฐพ์ผ๋ฏ๋ก, ์ฝ๋ ์ชฝ์ ๋ณ๋ ์์ ฏ์ด๋Builder๋ก ๋ถ๋ฆฌํ๋ค - ๊ฐ์ ์ฝ๊ธฐ๋ ํธํด์ง์ง๋ง ๋ฐ๊พธ๋ ๊ฑด ์ฌ์ ํ
setState์ ๊ธฐ๋๊ณ ์ฝ๋๋ ๋ฐ๋ณต๋๋ฏ๋ก, ์ด๋ฅผ ๊ฐ์ผ Provider๋ฅผ ์ด๋ค
๋ค์ ๊ธ์์๋ ChangeNotifier์ Provider๋ก ์ํ๋ฅผ ๊ณต์ ํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ค๋ณด๊ฒ ๋ค.
