Flutter에서 UI의 대부분은 위젯으로 이루어져 있습니다. Flutter의 위젯에 대해서 알아보도록 하겠습니다.
Flutter에서는 UI를 구성하는 가장 기본 단위를 위젯이라고 합니다. 공식문서에서는 위젯을 아래와 같이 소개합니다.
"Flutter emphasizes widgets as a unit of composition. Widgets are the building blocks of a Flutter app's user interface, and each widget is an immutable declaration of part of the user interface."
위젯은 UI에서 빌딩 블록이며, 각 위젯은 UI 일부에 대한 불변선언이라는 소개인데요. 무슨의미일까요?
아래와 같은 UI를 보도록 하겠습니다.
이 화면은 단순하게 가운데에 Hello World! 라는 글자를 보여주는 예시용 UI이지만, 코드로는 아래와 같이 표현됩니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text("Hello World!"),
),
),
);
}
}
Hello World! 라는 글자를 위해 MaterialApp, Scaffold, Center, Text 위젯이 계층형으로 선언되어 있습니다. VSC에서 현재 나의 프로젝트의 계층 구조를 볼 수도 있습니다.
이처럼 Flutter는 인스턴스로 선언된 모든 위젯을 계층형으로 관리합니다. 왜 이렇게 계층적으로 구성될까요?? 사용자가 앱을 사용하면서 특정 이벤트를 통해 UI가 변경될 수 있습니다. 이렇게 계층화된 구조에서 변경될 부분의 위젯 인스턴스를 다른 위젯으로 변경한다면 효율적으로 UI를 업데이트 할 수 있을 것입니다.
그렇습니다. 효율적으로 UI를 갱신하기 위해서 이러한 계층 구조를 사용하는 것입니다. 즉, Flutter는 UI를 이러한 위젯들의 모음으로 표현하고, 계층적으로 구성되므로 빌딩 블록이라고 하는 것이라고 이해할 수 있겠습니다.
UI를 갱신하기 위해서는 위젯을 재빌드한다고 하였습니다. 각 위젯은 build()라는 메소드를 재정의하는 방식으로 개발자가 UI를 선언할 수 있습니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text("Hello World!"),
),
),
);
}
}
MyApp은 runApp()메소드에서 직접 실행하게 되는 인스턴스이지만, 결국 위젯이라고 할 수 있습니다. 이 위젯은 오버라이딩된 build() 메소드를 재정의하여 UI를 선언하였습니다. 이 메소드는 하위 위젯에 따라서 여러개가 존재할 수 있겠죠. 그래서 Flutter는 하위 위젯에 build()를 계속 재귀적으로 호출하여 UI를 랜더링하기 위해서 필요한 모든 요소를 빌드합니다. 그리고 이 객체들을 객체 트리로 묶게 되죠.
UI갱신에서도 해당 build() 메소드를 호출하며 UI를 갱신하는 것입니다. build()메소드는 굉장히 중요한 역할을 맡았네요. 그렇기에 빠르게 실행될 수 있어야 할 것입니다. 이를 위하여 Stateless Widget, Stateful Widget의 개념이 등장했습니다.
맨 처음에 봤던 예시용 UI(Hello World!)는 단순하게 사용자에게 Text를 보여주는 화면입니다. 이러한 화면은 사용자에 어떠한 상호작용에도 Hello World!라는 글자를 보여줄 뿐입니다. 이러한 경우 Stateless Widget으로 위젯을 표현할 수 있습니다.
하지만 사용자에 상호작용으로 인해 UI가 변경되어야만 하는 부분도 존재합니다. 이러한 경우 Flutter에서는 위젯에 상태를 부여하며 해당 위젯을 갱신할 수 있는데, 이러한 위젯을 Stateful Widget이라고 합니다. 이 위젯 자체에서는 build()메소드를 실행할 수 없습니다.
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
대신, State에서 build()메소드를 재정의하여 UI를 선언할 수 있습니다. Stateful Widget은 setState 메소드를 이용하여 상태를 갱신합니다. Flutter create를 통해서 생성되는 예제앱은 이러한 상태갱신을 확인할 수 있는 좋은 예입니다.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// 바로 이부분
// 카운터의 증가를 State에게 전달
_counter++;
});
}
Widget build(BuildContext context) {
return Scaffold(
...
State를 통해 갱신되는 UI, 즉, StatefulWidget은 자식 위젯의 새 인스턴스를 생성할 수 있습니다.