Dev./Flutter

[Flutter/플러터] 위젯(Widget)과 상태(State)의 모든 것

Wonder Park 2026. 1. 4. 20:17
728x90

위젯(Widget)과 상태(State)의 모든 것: 앱 개발의 첫 단추

최근 바이브 코딩을 해보면서 예전 공부했던 플러터를 복습해야 한다는 생각이 많이 들어서 차근차근 다시 글을 만들려고 합니다. 바이브 코딩을 하면서 막혔던 게 바로 코드를 모를 때라서 다트와 플러터에 대한 기본은 확실히 하고 가야 할 것 같았습니다. 앱 개발이 막막하게 느껴질 수도 있지만, 플러터의 핵심 철학인 '위젯'만 제대로 이해하면 누구나 나만의 앱을 만들 수 있습니다. 오늘은 플러터 개발의 '관문'이라 불리는 위젯과 상태(State)에 대해 상세히 알아보겠습니다.


1. Widget(위젯)이란 무엇인가?

플러터에서 가장 자주 듣게 될 말은 바로 "모든 것이 위젯이다(Everything is a Widget)"입니다.

🧱 개념 비유: 레고(LEGO) 블록

레고 성을 만들기 위해 작은 브릭들을 하나하나 쌓아 올리듯, 플러터 앱 역시 작은 '위젯'들을 조립하여 완성됩니다. 눈에 보이는 버튼, 텍스트뿐만 아니라 화면의 중앙 정렬(Center)이나 간격(Padding) 같은 보이지 않는 설정조차도 모두 위젯입니다.

왜 모든 것을 위젯으로 만들었을까요?

  • 재사용성: 한 번 잘 만든 버튼 위젯을 다른 화면에서도 그대로 가져다 쓸 수 있습니다.
  • 유지보수: 특정 부품만 갈아 끼우듯 UI를 쉽게 수정할 수 있어 개발 속도가 비약적으로 향상됩니다.

2. Stateless vs Stateful Widget (핵심!)

위젯은 성격에 따라 두 가지로 나뉩니다. 이 차이를 아는 것이 플러터 개발의 절반이라고 해도 과언이 아닙니다.

① Stateless Widget

상태가 없는 위젯: 한 번 그려지면 스스로 변하지 않는 정적인 위젯입니다.

  • 예: 로고 이미지, 단순 안내 텍스트, 배경색
  • 특징: 데이터 변화가 없는 화면 요소에 사용되어 성능을 최적화합니다.

② Stateful Widget

상태가 있는 위젯: 사용자 상호작용이나 데이터 변화에 따라 화면을 '다시 그려야 할 때' 사용합니다.

  • 예: 카운터 숫자, 로그인 입력창, 좋아요 버튼
  • 특징: setState() 함수를 통해 플러터에게 화면 갱신을 요청할 수 있습니다.

3. Widget Tree(위젯 트리)란?

위젯들은 서로 부모-자식 관계를 맺으며 거대한 나무(Tree) 구조를 형성합니다.

플러터 위젯 트리 구조도

[그림] 위젯들의 계층 구조 - 위젯 트리(Widget Tree)

예를 들어, Scaffold(뼈대)라는 부모 위젯 안에 AppBar(상단바)와 Body(본문)가 자식으로 들어가고, 그 안에 다시 Column, Text 등이 배치되는 식입니다. 플러터는 이 트리를 실시간으로 감시하며 변화가 있는 부분만 효율적으로 업데이트합니다.

4. 실습 코드로 익히기

바이브코딩 실습 과정에서 다룬 카운터 앱 코드를 통해 실제 작동 원리를 살펴봅시다.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

// 1. 앱의 전체적인 설정 (변하지 않음)
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const CounterPage(),
    );
  }
}

// 2. 상태에 따라 변화하므로 StatefulWidget 사용
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _count = 0; // 앱의 상태(State)

  void _increment() {
    setState(() { // 화면 갱신 명령
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('기본기 복습: 카운터 앱')),
      body: Center(
        child: Text('$_count', style: TextStyle(fontSize: 40)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}
        

💡 퀴즈 (Check-point)

Q. 여러분이 '로그인 화면'을 만든다고 가정해 봅시다. 아이디를 입력하는 칸(TextField)은 Stateless일까요, Stateful일까요?

(정답: Stateful입니다. 사용자가 글자를 입력할 때마다 화면에 표시되는 텍스트가 실시간으로 변해야 하기 때문이죠!)

728x90