Dart 변수 값 변경: 재할당과 Flutter 상태 업데이트

Dart 변수의 값이 바뀌는 것과 Flutter 화면이 갱신되는 것은 같은 사건이 아닙니다. list.add()가 메모리의 객체를 바꿔도 Flutter가 다시 build해야 한다는 신호를 받지 못하면 사용자는 이전 값을 봅니다. 반대로 같은 List 객체를 그대로 수정했더라도 setState가 호출되면 화면은 새 길이를 표시합니다.

이 차이를 말로만 설명하지 않기 위해 2026년 9월 30일 Flutter 3.47.5와 Dart 3.13.4에서 작은 StatefulWidget을 만들었습니다. List만 변경, 같은 List 변경 뒤 setState, 새 List를 만들어 재할당한 뒤 setState의 세 경로를 위젯 테스트로 눌렀습니다. analyzer, 위젯 테스트, Web release 빌드가 모두 통과했습니다.

먼저 구분할 세 가지: 변수, 객체, 렌더링

확인 대상 무엇이 바뀌나 확인 방법
변수 재할당 변수가 다른 객체를 가리킴 identical(before, after)가 false인지 확인
객체 내부 변경 같은 List의 요소·길이가 바뀜 List 길이와 identical을 함께 확인
Flutter 화면 갱신 위젯이 다시 build되어 새 값을 표시 위젯 테스트에서 렌더링된 Text 확인

var items = <String>[]; 다음에 items.add('A')를 호출하면 변수 items는 여전히 같은 객체를 가리킵니다. 내부 길이만 1이 됩니다. items = [...items, 'A']는 새 List를 만든 뒤 변수에 다시 대입하므로 객체 정체성도 바뀝니다. 그러나 두 코드 모두 그 자체만으로 Flutter 재빌드를 예약하지는 않습니다.

세 버튼을 같은 순서로 실행한 결과

테스트는 처음에 화면 숫자 0을 확인한 다음 각 버튼을 한 번씩 눌렀습니다. 콜백으로 실제 List 길이와 객체 정체성을 기록하고, 테스트 프레임 이후 화면에 표시된 숫자를 별도로 조회했습니다.

실행 경로 메모리 길이 화면 숫자 객체 교체
items.add만 호출 1 0 아니요
같은 List 변경 + setState 2 2 아니요
복사한 List 재할당 + setState 3 3 예
Flutter List 직접 변경과 setState 및 새 객체 재할당의 화면 갱신 차이를 직접 검증한 결과
2026년 9월 30일 Flutter 3.47.5 위젯 테스트에서 List 직접 변경, 같은 객체 변경 후 setState, 새 List 재할당 후 setState를 차례로 실행한 결과입니다.

첫 번째 경로에서 메모리의 길이는 1이었지만 화면은 0을 유지했습니다. 두 번째 경로에서 같은 List에 항목을 추가하고 setState를 호출하자 화면이 2로 바뀌었습니다. 이 결과는 “Flutter에서는 항상 불변 List만 써야 화면이 바뀐다”는 설명도 정확하지 않음을 보여줍니다. StatefulWidget 자체는 객체 교체를 요구하지 않고, 해당 State가 다시 build되어야 한다는 알림을 요구합니다.

최소 코드에서 화면이 멈추는 지점

List<String> items = [];

void addSilently() {
  items.add('A');
  // 메모리 값은 바뀌지만 build를 다시 예약하지 않음
}

void addAndRebuild() {
  setState(() {
    items.add('B');
  });
}

Flutter의 setState 콜백은 동기적으로 상태를 바꾸고, 그 뒤 프레임워크가 이 State의 재빌드를 예약합니다. 따라서 setState(() async { ... })처럼 콜백을 비동기로 만드는 대신, 비동기 작업을 먼저 기다린 다음 위젯이 아직 유효한지 확인하고 짧은 동기 콜백에서 결과만 반영해야 합니다.

Future<void> load() async {
  final loaded = await repository.fetchItems();
  if (!mounted) return;
  setState(() {
    items = loaded;
  });
}

여기서 mounted 확인은 화면이 사라진 뒤 완료된 요청이 폐기된 State를 갱신하지 못하게 막습니다. 비동기 결과의 최신 요청 보장이나 오류 상태 모델링은 별도 문제이므로 이 예제에서는 측정하지 않았습니다.

같은 List 수정과 복사 후 재할당 중 무엇을 고를까

이번 테스트에서는 두 방식 모두 setState와 함께 사용할 때 화면이 갱신됐습니다. 선택 기준은 화면 갱신 여부가 아니라 상태의 소유권과 변경 추적입니다.

상황 권장 방향 이유
한 State 안에서만 쓰는 작은 임시 목록 같은 List 수정도 가능 소유자가 하나이고 변경 지점을 추적하기 쉬움
여러 위젯·서비스가 같은 List 참조를 공유 복사 후 재할당 다른 참조의 예상치 못한 동시 변경을 줄임
값 비교로 변경 여부를 판단하는 상태 관리 새 객체 생성 이전·다음 상태의 구분이 명확함
항목이 매우 많고 복사 비용이 우려됨 먼저 프로파일링 추측으로 가변 상태를 공유하면 정확성 비용이 더 클 수 있음

특히 부모, 자식, 저장소가 동일한 mutable List를 들고 있으면 한 곳의 add가 다른 곳의 읽기 결과도 바꿉니다. 복사 후 재할당은 이 별칭(alias) 효과를 줄이고, 변경 전 상태와 변경 후 상태를 로그나 테스트에서 비교하기 쉽게 합니다.

화면이 안 바뀔 때 확인하는 순서

  1. 이벤트 콜백이 실제 호출됐는지 로그나 중단점으로 확인합니다.
  2. 콜백 직후 메모리 값이 기대대로 바뀌었는지 확인합니다.
  3. 해당 값을 읽는 위젯이 같은 State의 setState 또는 사용 중인 상태 관리 도구의 알림 대상인지 확인합니다.
  4. 화면이 읽는 변수가 방금 수정한 변수와 같은 인스턴스인지 확인합니다.
  5. 비동기 완료 전에 위젯이 dispose됐거나, 더 늦게 끝난 이전 요청이 값을 덮어쓰지 않았는지 확인합니다.

첫 단계부터 무조건 setState를 추가하면 잘못된 객체를 수정한 문제나 이벤트가 호출되지 않은 문제를 숨길 수 있습니다. 값 변경, 알림, 렌더링을 순서대로 분리하면 원인을 빠르게 좁힐 수 있습니다.

직접 검증한 범위와 남은 제한

  • Windows 10 x64, Flutter 3.47.5 stable, Dart 3.13.4에서 실행했습니다.
  • flutter analyze는 문제 없음, 위젯 테스트는 1개 통과, Web release 빌드는 성공했습니다.
  • 테스트는 StatefulWidget과 setState만 다뤘습니다. Provider, Riverpod, Bloc의 알림·동등성 규칙을 대신 검증하지 않습니다.
  • 실기기 프레임 시간과 대규모 List 복사 비용은 측정하지 않았으므로 성능 우위를 주장하지 않습니다.
  • 비동기 네트워크 요청, 화면 전환, 동시 요청 순서는 이 실험에 포함하지 않았습니다.

이 글과 겹치지 않는 다음 주제

이 글은 값 변경이 Flutter 화면까지 전달되는 경로에만 집중합니다. 재할당 가능 여부와 var·final·const의 객체 변경 차이는 Dart var·final·const 실행 비교에서 확인할 수 있습니다. 타입 추론으로 정해진 타입에 다른 값을 대입할 때의 오류는 Dart에서 var와 명시 타입을 선택하는 기준에서 분리해 다룹니다.

실행 결과와 대조한 공식 자료