본문 바로가기
Flutter 독학으로 기본기 익히기

02. StatelessWidget과 StatefulWidget 비교하기

by daco2020 2025. 5. 1.

오늘 배울 것

실습을 통해 StatelessWidget StatefulWidget 차이를 비교하고 StatefulWidget 기본 사용법을 알아보겠습니다.

 

먼저 둘의 차이를 말해보자면

 

StatelessWidget은 화면이 절대 안 바뀌는 위젯이고,

StatefulWidget 상태(state)가 변하면 화면이 다시 그려지는 위젯입니다.

 

실제로 둘이 어떻게 다른지 실습을 통해 확인해 보겠습니다. 

 

 

 

실습 방법

버튼을 누르면 숫자가 1씩 증가하는 코드를 작성합니다.

이를 StatelessWidgetStatefulWidget 로 만들어 각각 앱을 실행합니다.

 

버튼을 눌렀을 때 화면의 숫자가 변하지 않으면 Stateless, 숫자가 증가하면 Stateful이라는 걸 직접 눈으로 확인해 봅니다.

 

 

 

StatelessWidget 실습 (변화 X)

아래 코드를 main.dart 파일에 붙여 넣기하고 저장합니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    int count = 0;

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('StatelessWidget Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('count: $count', style: const TextStyle(fontSize: 24)),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  count++;
                  print('count: $count');
                },
                child: const Text('증가 버튼'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

 

iOS 시뮬레이터를 통해 증가 버튼을 눌러봅니다.

 

 

증가 버튼을 눌러도 iOS 시뮬레이터에 count 값을 변하지 않습니다. 하지만 print() 로 출력하고 있는 Debug Console 에서는 버튼을 누를 때마다 count 가 변하고 있죠.

 

즉, count 라는 변수가 1씩 증가하고 있지만 해당 위젯이 StatelessWidget 이기 때문에 화면에 변화가 없는 것입니다.

 

 

 

StatefulWidget 실습 (변화 O)

이번에는 아래 코드를 main.dart 파일에 붙여 넣기하고 저장합니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('StatefulWidget Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('count: $count', style: const TextStyle(fontSize: 24)),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    count++;
                    print('count: $count');
                  });
                },
                child: const Text('증가 버튼'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

 

좌측 상단 디버깅 패널에서 Restart 버튼을 눌러 앱을 다시 실행합니다.

 

iOS 시뮬레이터를 통해 증가 버튼을 눌러봅니다. 

 

 

이번에는 증가 버튼을 누르니 변경된 count 값이 화면에도 반영되는 것을 확인할 수 있습니다. 이것은 setState() 를 통해 count 값 상태가 변경될 때마다 화면이 다시 그려지기 때문입니다. 

 

정리하자면 StatefulWidget는 앱에서 사용자와 상호작용이 생기면 그에 따라 데이터(상태)가 변하고 그 변화를 화면에 반영해 주기 위해 사용합니다.

 

 

 

StatefulWidget 사용법

그렇다면 StatefulWidget은 어떻게 사용할 수 있을까요? StatefulWidget의 코드를 작성하는 방법에 대해서 2 단계로 설명해 보겠습니다.

 

1단계, StatefulWidget 클래스 만들기

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState(); // 실제 상태를 담는 클래스 연결
}

 

StatefulWidget은 말 그대로 '상태를 가질 수 있는 위젯'입니다. 이 코드에서 실제 상태 데이터는 _MyAppState 안에 있습니다.

 

 

2단계, 상태(State)를 정의하는 클래스 작성

class _MyAppState extends State<MyApp> {
  int count = 0; // 상태가 되는 변수

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Stateful Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('count: $count', style: TextStyle(fontSize: 24)),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    count++; // 상태 변경
                  });
                },
                child: Text('증가 버튼'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

setState()는 '내가 지금 상태값(count)을 바꿨으니까 화면을 다시 그려줘' 라고 Flutter에게 알리는 신호예요. setState()을 통해 상태값을 바꾸지 않으면 화면은 다시 그려지지 않습니다. (build() 함수가 다시 실행되지 않음)

 

 

 

build() 는 화면 전체를 매번 다시 그리는 걸까?

이런 궁금증이 생길 수 있습니다. 상태 값이 변경될 때마다 build()를 다시 호출한다면 성능에는 문제가 없을까?


build()는 새로운 위젯 트리를 생성하긴 하지만 실제 화면에서는 바뀐 부분만 효율적으로 업데이트하기 때문에 성능에는 큰 문제가 없습니다. 

 

예를 들어 다음처럼 count 값이 바뀌면,

setState(() {
  count++;
});

 

build()가 호출되지만 Flutter는 Text('count: $count') 부분만 바뀌었다고 판단해서 그 부분만 실제로 다시 그립니다.


이렇게 Flutter가 알아서 최적화를 해주긴 하지만, build() 안에 비싼 연산을 넣거나 너무 자주 전체 트리를 rebuild 하는 것은 지양하는 것이 좋습니다.

 

 

 

 

Recap

- StatelessWidget은 상태가 변해도 화면이 바뀌지 않는 위젯입니다.
- StatefulWidget은 상태(state)가 바뀌면 build()를 다시 호출하여 화면을 다시 그립니다.
- setState()를 호출해야 상태 변경이 화면에 반영됩니다.
- build()는 매번 전체를 다 그리는 게 아니라 실제로 바뀐 부분만 최적화하여 업데이트합니다.

 

다음 글에서는 위젯 트리 구조 DevTools 로 Widget Tree 보는 방법에 대해 알아보겠습니다.