안녕하세요.
이번 포스팅에서는 flutter app에서 상태를 관리하는 방법 중 하나인 state hoisting에 대해 간단히 알아보겠습니다.
언어: dart
IDE: Android Studio
Framework: Flutter
Test device: Android
State Hoisting
React, Flutter와 같은 선언형 UI 프레임워크에서 주로 사용되는 개념입니다. 이는 상태(state: UI에 영향을 미치는 변경 가능한 data)를 더 상위의 컴포넌트나 컨텍스트로 이동시켜 여러 하위 컴포넌트에서 공유할 수 있도록 하는 것을 말합니다.
State Hoisting은 다음과 같은 경우에 주로 사용됩니다.
- 두 개 이상의 컴포넌트가 동일한 상태에 접근해야 할 때: 여러 곳에서 상태를 중복 관리하는 대신, 상태를 공통된 상위 컴포넌트로 끌어올려 한 곳에서 관리하도록 합니다. 이를 통해 상태 불일치를 방지할 수 있습니다.
- 상위 컴포넌트가 상태를 제어해야 할 때: 상위 컴포넌트가 하위 컴포넌트에서 발생하는 상태 변화를 관리하거나 이에 반응할 수 있습니다.
- 캡슐화와 재사용성: 하위 컴포넌트를 stateless 상태로 유지할 수 있고, 이를 재사용 가능한 컴포넌트로 활용할 수 있습니다.
아래의 포스팅에서 사용한 소스를 state hoisting 설계대로 수정해 보겠습니다.
https://it-of-fortune.tistory.com/47
기본 상태의 소스입니다.
main.dart
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: LoadingExample(),
));
}
class LoadingExample extends StatefulWidget {
@override
_LoadingExampleState createState() => _LoadingExampleState();
}
class _LoadingExampleState extends State<LoadingExample> {
bool _showMessage = false;
bool _isLoading = false;
void _setMessageVisibility() async {
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 3));
setState(() {
_isLoading = false;
_showMessage = !_showMessage;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Delay Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _setMessageVisibility,
child: Text('Show message'),
),
SizedBox(height: 20),
if (_showMessage)
Text(
'Hello World',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
),
);
}
}
먼저, parent widget으로부터 state를 받기 위한 child widget class를 만들어 줍니다.
main.dart
class childWidget extends StatelessWidget {
final bool showMessage;
final bool isLoading;
final VoidCallback onButtonPressed;
const childWidget({
required this.showMessage,
required this.isLoading,
required this.onButtonPressed,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: onButtonPressed,
child: Text('Show message'),
),
SizedBox(height: 20),
if (isLoading) CircularProgressIndicator(),
if (showMessage)
Text(
'Hello World',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
);
}
}
이제 이 class가 생성되면서 showMessage과 isLoading 상태를 전달받고, 버튼 클릭 시 수행할 동작 또한 전달받습니다.
Parent widget class는 아래와 같이 수정해 줍니다.
main.dart
class _LoadingExampleState extends State<LoadingExample> {
bool _showMessage = false;
bool _isLoading = false;
void _setMessageVisibility() async {
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 3));
setState(() {
_isLoading = false;
_showMessage = !_showMessage;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('State Hoisting Example')),
body: childWidget(
showMessage: _showMessage,
isLoading: _isLoading,
onButtonPressed: _setMessageVisibility,
),
);
}
}
이 class에서는 상태를 set 해주는 부분만 있고, 이후 변화된 상태를 하위 widget에게 전달해 주면서 UI를 그려주게 됩니다.
아래는 전체 코드입니다.
main.dart
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: LoadingExample(),
));
}
class LoadingExample extends StatefulWidget {
@override
_LoadingExampleState createState() => _LoadingExampleState();
}
class _LoadingExampleState extends State<LoadingExample> {
bool _showMessage = false;
bool _isLoading = false;
void _setMessageVisibility() async {
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 3));
setState(() {
_isLoading = false;
_showMessage = !_showMessage;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('State Hoisting Example')),
body: childWidget(
showMessage: _showMessage,
isLoading: _isLoading,
onButtonPressed: _setMessageVisibility,
),
);
}
}
class childWidget extends StatelessWidget {
final bool showMessage;
final bool isLoading;
final VoidCallback onButtonPressed;
const childWidget({
required this.showMessage,
required this.isLoading,
required this.onButtonPressed,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: onButtonPressed,
child: Text('Show message'),
),
SizedBox(height: 20),
if (isLoading) CircularProgressIndicator(),
if (showMessage)
Text(
'Hello World',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
);
}
}
사용자가 보는 동작 면에서는 수정하기 전과 차이가 없지만, 하위 컴포넌트를 생성해 주었으며 state hoisting을 활용하여 해당 컴포넌트의 상태를 관리하도록 설계된 코드입니다.
테스트 시 동작에도 문제가 없어 보입니다.
state hoisting은 분명 상태를 관리하기 적합한 방법 중 하나이지만, 관리해야 할 상태가 많아지거나 여러 계층의 컴포넌트에게 상태와 callback 함수를 전달해야 할 경우 오히려 코드가 복잡해지고 관리가 어려워질 수 있으므로 자신의 프로젝트 규모에 맞게 적용하는 것이 중요합니다.
이상 포스팅을 마치겠습니다.
감사합니다.
'Flutter Application > 앱 설계' 카테고리의 다른 글
Flutter 상태 관리 - Bloc pattern (1) | 2024.11.29 |
---|