StatefulWidget

김례원·2025년 9월 25일

Flutter

목록 보기
12/12
post-thumbnail

StatefulWidget이 뭐야?

  • Widget(껍데기)immutable(불변)이고,
  • State(속 내용)mutable(변경 가능)해서 화면 상태를 들고 있어.
  • build()State에 있다는 점이 포인트. setState() 로 상태를 바꾸면 build() 가 다시 그려져.

구조(정석 스켈레톤)

class MyWidget extends StatefulWidget {
  final int initialCount;
  const MyWidget({super.key, this.initialCount = 0});

  
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {
  late int count;
  late final AnimationController _controller;

  // 1) 최초 한 번
  
  void initState() {
    super.initState();
    count = widget.initialCount;
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    // 프레임 이후 1회 작업 (레이아웃 값 필요할 때 등)
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // do something after first build
    });
  }

  // 2) 상위의 InheritedWidget(예: Theme, MediaQuery) 의존이 바뀔 때
  
  void didChangeDependencies() {
    super.didChangeDependencies();
  }

  // 3) 부모가 새 props를 주입했을 때(동일 타입 위젯이 갱신)
  
  void didUpdateWidget(covariant MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.initialCount != widget.initialCount) {
      // 외부 값 변화에 반응
      setState(() => count = widget.initialCount);
    }
  }

  // 4) 그리기
  
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('$count'),
        const SizedBox(width: 8),
        ElevatedButton(
          onPressed: () => setState(() => count++),
          child: const Text('증가'),
        ),
      ],
    );
  }

  // 5) 트리에서 분리 직전(잠깐 빠졌다가 다시 붙을 수도 있음)
  
  void deactivate() {
    super.deactivate();
  }

  // 6) 완전 제거(리소스 정리 필수)
  
  void dispose() {
    _controller.dispose(); // controller/stream/focus 등 꼭 정리
    super.dispose();
  }
}

라이프사이클 한눈에

createState → initState → didChangeDependencies → build → (setState→build 반복) → didUpdateWidget(상위 변화 시) → deactivate → dispose

  • 초기화: initState
  • 상위 컨텍스트 의존 변경: didChangeDependencies
  • 리렌더: setStatebuild
  • 외부 파라미터 변경 대응: didUpdateWidget
  • 해제/정리: dispose

setState 올바르게 쓰기

setState(() {
  // 상태값만 바꿔라 (무거운 연산 X)
  count++;
});
  • 클로저 안은 상태 변경만. 비동기/네트워크 같은 무거운 일은 밖에서 하고, 끝난 뒤 한 번 setState 하자.
  • 비동기 뒤에 if (!mounted) return; 로 위젯 생존 여부 확인!

자주 하는 실수 & 팁

  • 컨트롤러/리소스를 build에서 생성 ❌ → initState에서 만들고 dispose에서 정리.
  • dispose 이후 setState 호출 ❌ → 비동기 처리 후 mounted 체크.
  • URL/레이아웃 값이 필요한 초기 작업addPostFrameCallback 사용.
  • 리스트에서 상태 보존Key(특히 ValueKey) 로 식별자 부여.

언제 StatefulWidget을 써?

  • TextField 컨트롤러, 애니메이션, 탭/스크롤 포지션, 타이머/스트림 등 위젯 자체 수명과 같이 가는 상태가 있을 때.
  • 반대로 비즈니스 상태(API 데이터, 로그인 상태 등)는 Provider/Stacked ViewModel 같은 상태관리에 두고 위젯은 가볍게.

React와 비교(감 잡기)

FlutterReact
StatefulWidget + State함수 컴포넌트 + useState/useEffect
initStateuseEffect(() => {...}, [])
didUpdateWidgetuseEffect의 deps 변화 반응
disposeuseEffect의 cleanup 함수 return () => {...}
setState()setState(...)
  • React는 컴포넌트 함수가 매 렌더마다 다시 실행되고 Hook으로 상태를 유지.
  • Flutter는 State 객체가 위젯 트리 안에 붙어 수명주기로 상태를 관리.

profile
분야를 가리지 않는 개발자

0개의 댓글