InheritedWidget은 생성자를 통해 명시적으로 전달하지 않고 위젯 트리 아래로 데이터를 전달하는 Flutter의 특별한 위젯입니다. 자식 위젯은 BuildContext를 통해 데이터에 접근하고 자동으로 업데이트를 구독합니다. InheritedWidget의 데이터가 변경되면 모든 종속 위젯이 다시 빌드됩니다. Flutter API Reference, 2025에 따르면 InheritedWidget은 Theme, MediaQuery, Localizations 및 대부분의 상태 관리 라이브러리의 기초입니다.
핵심 포인트
InheritedWidget은 Widget Tree의 모든 하위 항목이 데이터에 접근할 수 있도록 하는 위젯입니다. 생성자를 통해 자식 요소에만 데이터를 전달하는 일반 위젯과 달리 InheritedWidget은 서브트리의 모든 위젯이 매개변수 체인 없이 데이터에 접근할 수 있게 합니다. 이는 “prop drilling” 문제(이 데이터를 사용하지 않는 많은 중간 위젯을 통해 데이터를 전달하는 것)를 해결합니다.
Flutter에는 여러 내장 InheritedWidget이 포함되어 있습니다: Theme(색상 구성표 및 스타일), MediaQuery(화면 크기, 방향, 픽셀 밀도), Localizations(지역화된 문자열), Directionality(텍스트 방향), DefaultTextStyle(기본 텍스트 스타일). 이러한 위젯은 MaterialApp과 같은 루트 위젯에 의해 설정되며 애플리케이션 전체에서 사용할 수 있습니다.
InheritedWidget에는 자체 상태가 없으며 생성자를 통해 전달된 데이터를 저장합니다. InheritedWidget의 부모가 새 데이터로 다시 빌드되면 updateShouldNotify 메서드가 호출되어 이전 데이터와 새 데이터를 비교합니다. 메서드가 true를 반환하면 모든 종속 위젯이 다시 빌드용으로 표시됩니다. 이는 간단하지만 효과적인 반응형 업데이트 메커니즘입니다.
InheritedWidget을 통한 데이터 전달 메커니즘은 Element Tree를 기반으로 합니다. 위젯이 dependOnInheritedWidgetOfExactType을 호출하면 해당 요소가 InheritedElement에 대한 종속성을 등록합니다. InheritedWidget이 변경되면 InheritedElement가 모든 종속 요소에 알리고 다음 프레임에서 다시 빌드됩니다.
dependOnInheritedWidgetOfExactType 메서드는 트리에서 InheritedWidget을 찾는 것뿐만 아니라 현재 요소를 알림에 구독시킵니다. dependOn 대신 findAncestorWidgetOfExactType을 사용하면 위젯이 데이터를 가져오지만 변경 시 다시 빌드되지 않습니다. 이것은 중요한 차이점입니다: dependOn은 구독이고 findAncestor는 일회성 조회입니다.
위젯이 InheritedWidget을 요청하면 Flutter는 현재 요소에서 루트까지 Element Tree를 올라가며 각 InheritedElement의 유형 일치를 확인합니다. 첫 번째로 일치하는 InheritedElement가 반환됩니다. 즉, 트리에서 가장 가까운 InheritedWidget이 우선순위를 가지며 InheritedWidget을 하위 항목 가까이 배치하여 특정 수준에서 데이터를 재정의할 수 있습니다.
class ThemeData {
final Color primaryColor;
final TextTheme textTheme;
const ThemeData({required this.primaryColor, required this.textTheme});
}
class MyTheme extends InheritedWidget {
final ThemeData data;
const MyTheme({required this.data, required Widget child}) : super(child: child);
static MyTheme of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
assert(widget != null, "MyTheme not found in tree");
return widget!;
}
@override
bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}
이 예제에서 MyTheme은 정적 of 메서드를 사용하여 하위 항목에 데이터를 제공합니다. dependOnInheritedWidgetOfExactType 메서드는 종속성을 등록하고 updateShouldNotify는 이전 데이터와 새 데이터를 비교하여 종속 위젯을 다시 빌드해야 하는지 결정합니다.
사용자 정의 InheritedWidget을 만드는 것은 두 단계로 구성됩니다: InheritedWidget을 확장하는 클래스 정의와 하위 항목에서 접근하기 위한 정적 of 메서드 구현입니다. 데이터는 생성자를 통해 전달되며 updateShouldNotify 메서드가 종속 위젯을 다시 빌드할 시기를 결정합니다.
클래스는 InheritedWidget을 확장해야 하며 필수 child 매개변수가 있는 생성자를 통해 데이터를 받아야 합니다. 데이터는 프리미티브, 객체, 함수 등 모든 유형이 될 수 있습니다. 주요 규칙은 이전 값과 새 값을 안정적으로 비교할 수 있도록 데이터가 불변(immutable)이어야 한다는 것입니다.
정적 of 메서드는 BuildContext를 받아 InheritedWidget 데이터를 반환합니다. 내부적으로 dependOnInheritedWidgetOfExactType을 호출하여 트리에서 지정된 유형의 가장 가까운 InheritedWidget을 찾습니다. InheritedWidget을 찾을 수 없으면 구현에 따라 예외를 throw하거나 기본값을 반환합니다.
데이터에 접근하려면 위젯이 build 메서드 내에서 MyWidget.of(context)를 호출합니다. Flutter는 자동으로 위젯을 업데이트에 구독시킵니다. 데이터가 변경되면 위젯은 다음 프레임에서 다시 빌드됩니다. 불필요한 매개변수 없이 깔끔하고 선언적인 코드를 작성할 수 있습니다.
class UserPreferences extends InheritedWidget {
final String languageCode;
final bool darkMode;
const UserPreferences({
required this.languageCode,
required this.darkMode,
required Widget child,
}) : super(child: child);
static UserPreferences of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
}
@override
bool updateShouldNotify(UserPreferences oldWidget) =>
oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}
이 예제에서 UserPreferences는 사용자 설정을 저장합니다. updateShouldNotify 메서드는 각 필드를 개별적으로 비교하여 하나의 매개변수만 변경될 때 불필요한 다시 빌드를 방지합니다. 여러 필드가 있는 자체 InheritedWidget에도 유사한 접근 방식을 사용하세요.
updateShouldNotify는 데이터 변경에 대해 종속 위젯에 알려야 하는지 결정하는 InheritedWidget의 핵심 메서드입니다. 메서드가 false를 반환하면 InheritedWidget 자체가 동일한 데이터로 새 인스턴스를 받더라도 종속 위젯이 다시 빌드되지 않습니다. 이는 성능에 매우 중요합니다.
실제로 변경되고 표시에 영향을 주는 필드만 비교합니다. InheritedWidget에 10개의 필드가 있지만 UI에 영향을 주는 필드가 하나만 있으면 해당 필드만 확인합니다. 컬렉션의 경우 깊은 비교 또는 불변 데이터 구조를 사용합니다. List나 Map은 참조로 비교하므로 ==를 사용하지 마세요.
가장 흔한 실수는 비교 없이 true를 반환하는 것입니다. 이로 인해 데이터가 변경되지 않았더라도 모든 종속 위젯이 모든 부모 업데이트에서 다시 빌드됩니다. 두 번째 실수는 데이터가 변경되었을 때 false를 반환하여 UI가 오래된 상태로 유지되는 것입니다. 세 번째는 모든 프레임에서 실행되어 성능을 저하시키는 복잡한 비교입니다.
InheritedWidget과 콜백(생성자를 통한 함수 전달)은 서로 다른 문제를 해결합니다. InheritedWidget은 트리의 다양한 수준에서 많은 위젯이 필요한 데이터에 적합합니다. 콜백은 부모에서 특정 자식으로 또는 그 반대로 단방향 이벤트를 전달하는 데 편리합니다. 선택은 애플리케이션 아키텍처와 업데이트 빈도에 따라 달라집니다.
다른 중첩 수준에서 많은 위젯이 데이터를 필요로 할 때 InheritedWidget을 사용합니다: 앱 테마, 사용자 설정, 기기 정보, 현재 세션 데이터. InheritedWidget은 거의 변경되지 않지만 UI의 여러 부분에서 필요한 “전역” 데이터에 특히 효과적입니다.
콜백(콜백 함수)은 자식 위젯에서 부모로 이벤트를 전달하는 데 적합합니다: 버튼 누름, 목록 항목 선택, 양식 제출. 콜백은 자식이 수행할 수 있는 작업을 명시적으로 나타내며 숨겨진 종속성을 만들지 않습니다. 적은 수의 수준을 통해 트리 아래로 데이터를 전달하는 경우 생성자 매개변수를 사용하는 것이 더 간단합니다.
| 기준 | InheritedWidget | 콜백 |
|---|---|---|
| 방향 | 위에서 아래로 (부모 → 하위) | 아래에서 위로 (자식 → 부모) 또는 직접 |
| 범위 | 전체 서브트리 | 특정 위젯 |
| 다시 빌드 | 데이터 변경 시 자동 | 수동 setState 필요 |
| 복잡성 | 중간 (InheritedWidget 클래스 필요) | 낮음 (단순한 함수) |
Provider와 Riverpod는 InheritedWidget 위에 구축된 Flutter의 인기 있는 상태 관리 라이브러리입니다. 이들은 기능을 확장합니다: ChangeNotifier 지원, 마운트 해제 시 자동 폐기, 지연 초기화, 제네릭을 사용한 단순화된 구문을 추가합니다.
Provider는 InheritedWidget을 사용하여 모든 유형의 객체를 트리 아래로 전달합니다. ChangeNotifierProvider는 ChangeNotifier를 통해 변경 사항을 추적하고 notifyListeners가 호출될 때 updateShouldNotify를 호출합니다. 이렇게 하면 개발자가 수동으로 InheritedWidget을 만들고 updateShouldNotify를 구현할 필요가 없습니다.
직접 InheritedWidget은 더 많은 제어를 제공하고 외부 종속성이 필요하지 않습니다. Provider는 Consumer, Selector, MultiProvider, ProxyProvider와 같은 준비된 인프라를 제공합니다. 선택은 애플리케이션 복잡성에 따라 달라집니다. 간단한 프로젝트에는 직접 InheritedWidget으로 충분하고 큰 프로젝트에는 Provider나 Riverpod가 보일러플레이트 코드를 줄입니다.
// 직접 InheritedWidget
class UserProvider extends InheritedWidget {
final UserData userData;
const UserProvider({required this.userData, required Widget child}) : super(child: child);
static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
@override
bool updateShouldNotify(UserProvider old) => old.userData != userData;
}
// Provider 동등
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
예제의 두 접근 방식은 동일한 문제를 해결합니다 — UserData를 트리 아래로 전달하는 것입니다. Provider는 코드 양을 줄이지만 InheritedWidget의 메커니즘을 숨깁니다. 직접 InheritedWidget은 완전한 제어와 작동 방식에 대한 이해를 제공하며, 이는 Flutter 학습 및 복잡한 다시 빌드 문제 디버깅에 특히 중요합니다.
자주 묻는 질문
InheritedWidget은 BuildContext를 통해 모든 하위 항목이 데이터를 사용할 수 있도록 하는 반면, 일반 위젯은 생성자를 통해서만 데이터를 전달합니다. InheritedWidget은 또한 하위 항목을 데이터 업데이트에 구독시킵니다.
종속 위젯은 updateShouldNotify가 true를 반환할 때만 다시 빌드됩니다. 메서드가 올바르게 구현되면 다시 빌드는 데이터가 실제로 변경될 때만 발생하며 모든 부모 다시 빌드 시에는 발생하지 않습니다.
네, 동일한 트리에서 원하는 수의 InheritedWidget을 사용할 수 있습니다. 각각 특정 유형의 데이터를 제공하며 위젯은 여러 InheritedWidget에서 동시에 데이터를 가져올 수 있습니다.
dependOn은 위젯을 업데이트에 구독시킵니다 — 데이터가 변경되면 위젯이 다시 빌드됩니다. findAncestor는 구독 없이 일회성 조회를 수행하며 위젯은 데이터 변경을 알지 못합니다.
단순한 상태(테마, 설정)에는 InheritedWidget으로 충분합니다. 비즈니스 로직이 포함된 복잡한 상태에는 Provider, Riverpod 또는 BLoC를 사용하세요 — 이들은 InheritedWidget 위에 구축되어 필요한 인프라를 추가합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.