Dev./Flutter

[플러터 / Flutter] StatefulWidget의 구조와 상태(State)의 모든 것

Wonder Park 2026. 1. 14. 17:12
728x90

StatefulWidget: 생동감 넘치는 앱을 만드는 핵심 전략

지금까지 우리가 만든 화면들은 한 번 그려지면 그대로 멈춰있는 정적인 모습이었습니다. 하지만 실제 앱은 버튼을 누르면 숫자가 변하고, 스위치를 켜면 배경색이 바뀌어야 하죠. 최근 바이브코딩 실습을 진행하며 "어떻게 하면 화면의 특정 부분만 실시간으로 업데이트할 수 있을까?" 고민하셨던 분들을 위해, 오늘은 플러터 앱에 생명력을 불어넣는 StatefulWidget을 아주 세세하게 파헤쳐 보겠습니다.


1. '상태(State)'란 무엇인가?

플러터 개발에서 '상태'라는 단어는 아주 중요합니다. 어렵게 생각할 것 없습니다.

💡 개념 비유: 전등 스위치

앱에서 상태란 "시간이 지남에 따라 변할 수 있고, 그 변화가 화면에 즉각 나타나야 하는 데이터"를 말합니다.

  • 전등의 상태: '켜짐(On)' 또는 '꺼짐(Off)'
  • 카운터의 상태: 현재 숫자 '0', '1', '2'...
  • 로그인 상태: '로그인 됨' 또는 '로그아웃 됨'

StatefulWidget은 이러한 데이터가 바뀔 때마다 "어이 플러터! 데이터가 바뀌었으니 화면을 다시 그려줘!"라고 요청할 수 있는 똑똑한 위젯입니다. 상태가 변하면 위젯은 자신을 재빌드(Rebuild)하여 새로운 모습을 보여줍니다.

2. 왜 클래스가 두 개나 필요할까요?

StatefulWidget을 생성하면 코드가 자동으로 두 덩어리가 생깁니다. 여기엔 플러터의 치밀한 성능 최적화 전략이 숨어 있습니다.

① 위젯 클래스 (겉껍데기)

불변(Immutable): "나는 이런 모양의 전등이야"라는 설정값만 가집니다. 부모로부터 받은 데이터를 저장하며, 값이 바뀌면 플러터가 이 껍데기를 통째로 새로 만듭니다.

② 상태 클래스 (알맹이/뇌)

가변(Mutable): 실제 변화하는 데이터를 가지고 있습니다. 화면을 그리는 build 함수와 데이터를 바꾸는 setState가 이곳에 모여 있습니다.

Why?: 성능을 위해 '껍데기'는 수시로 갈아치우되, 소중한 '데이터(뇌)'는 계속 유지하여 효율적으로 화면을 갱신하기 위함입니다.

3. setState(): 화면을 새로고침하는 마법의 버튼

변수의 값만 바꾼다고 화면이 자동으로 변하지 않습니다. 반드시 setState()라는 함수를 불러야 합니다.

  1. 데이터를 수정합니다. (예: _isOn = true;)
  2. setState(() { ... });를 호출합니다.
  3. 플러터가 "데이터가 바뀌었으니 build 함수를 다시 실행해서 화면을 새로 그려야지!"라고 인지하고 실행합니다.

4. 실전 코드: 전등 켜기/끄기 앱

바이브코딩 실습에서 유용하게 쓰일 전등 스위치 예제입니다. 주석을 통해 구조를 파악해 보세요.

// 1. 겉껍데기 위젯 클래스 (설정값 담당)
class LightBulb extends StatefulWidget {
  const LightBulb({super.key});

  @override
  State<LightBulb> createState() => _LightBulbState();
}

// 2. 실제 데이터와 화면을 담당하는 상태 클래스 (뇌 담당)
class _LightBulbState extends State<LightBulb> {
  bool _isOn = false; // [상태 데이터]

  void _toggleLight() {
    setState(() { // 화면 갱신을 명령하는 핵심 함수!
      _isOn = !_isOn; 
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('바이브코딩 실습: 전등 앱')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.lightbulb,
              size: 100,
              color: _isOn ? Colors.yellow : Colors.grey,
            ),
            Text(_isOn ? '전등이 켜졌습니다!' : '전등이 꺼졌습니다.'),
            ElevatedButton(
              onPressed: _toggleLight,
              child: Text(_isOn ? '끄기' : '켜기'),
            ),
          ],
        ),
      ),
    );
  }
}
        

5. 생명주기(Life Cycle) 핵심 요약

Stateful Widget은 태어나서 죽을 때까지의 과정이 있습니다. 입문 단계에서는 딱 두 가지만 기억하세요!

  • initState(): 위젯이 처음 태어날 때 딱 한 번 실행됩니다. 서버에서 데이터를 처음 받아오거나 초기 변수값을 설정할 때 사용합니다.
  • dispose(): 위젯이 화면에서 영원히 사라질 때 실행됩니다. 사용하던 타이머를 멈추거나 메모리를 정리할 때 반드시 필요합니다.

⚠️ 초보자가 자주 하는 실수

  • build() 함수 안에서 setState() 호출 금지: 화면을 그리면서 동시에 다시 그리라고 명령하면 무한 루프에 빠져 앱이 멈춥니다.
  • 모든 것을 Stateful로 만들기 금지: 상태 변화가 없는 정적인 화면은 StatelessWidget을 써야 메모리를 아끼고 성능을 높일 수 있습니다.

💡 퀴즈 (Check-point)

Q. 사용자가 입력창에 글자를 입력할 때마다 화면에 실시간으로 그 글자를 보여줘야 합니다. 이때 사용해야 할 위젯은 무엇일까요?

(정답: StatefulWidget입니다! 입력에 따라 데이터가 실시간으로 변하고 화면에 반영되어야 하기 때문이죠.)

728x90