그 전까지 정적인 Stateless UI를 만들었다.
// StatelessWidget
class App extends StatelessWidget {
const App({super.key});
Widget build(BuildContext context) {
이제 상태 값을 가진 Stateful UI를 만들어 보자. 그 전에 설명을 좀 하고 넘어가겠다.
플러터에서 Stateful UI는 상태(state)를 가지고 있는 사용자 인터페이스를 구현하는 방법이다.
동적인 UI를 만들고 사용자와의 상호작용에 따라 상태가 변하여 UI를 업데이트할 수 있게 해줌.
데이터의 변경에 따라 UI를 실시간으로 업데이트할 수 있음.
StatefulWidget
을 확장하여 클래스를 만든다. StatefulWidget은 두 가지 클래스로 구성된다.
StatefulWidget은 사용자의 상호작용에 따라 상태를 변경하고, State 클래스는 변경된 상태에 따라 UI를 다시 렌더링
Stateful UI를 구현하는 과정은 다음과 같다:
createState()
메소드를 오버라이드하여 해당 위젯의 상태를 관리하는 State 클래스를 생성build()
메소드를 오버라이드하여 UI를 렌더링setState()
메소드를 호출하여 변경된 상태를 알림이에 따라 플러터는 자동으로 build()
메소드를 호출하여 UI를 업데이트한다.
플러터에서 구현하는 방법은 되게 간단하다. 스니펫을 사용하거나 코드 액션을 사용하면 된다.
st만 입력해도 저렇게 나오고, 저걸 입력하면
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Widget build(BuildContext context) {
return const Placeholder();
}
}
이렇게 된다.
Stateless 클래스를 클릭 후 코드 액션 클릭하면
class App extends StatefulWidget {
const App({super.key});
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
Widget build(BuildContext context) {
...
State를 가진 클래스를 포함하여 자동으로 수정이 가능하다.
이 수정 방법으로 State를 사용하는 법도 배워보자.
읽기
간단한 앱 하나를 만들어보겠다.
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatefulWidget {
const App({super.key});
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: const Color(0xFFF4EDDB),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Click Here!',
style: TextStyle(fontSize: 30),
),
],
),
),
),
);
}
}
간단하게 스타일만 적용한 앱이다. Text
로 'Click Here!'
를 Stateless하게 렌더링하고 있다. 이제 여기에 State를 넣어서 바뀌게 해보겠다.
되게 쉽다. State를 사용할 변수를 선언하고, 그 값을 UI 안에 넣어주기만 하면 된다.
class _AppState extends State<App> {
// 1. State 'counter' 선언
int counter = 0;
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: const Color(0xFFF4EDDB),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Click Here!',
style: TextStyle(fontSize: 30),
),
// 2. 'counter' UI에 표시
Text(
'$counter',
style: const TextStyle(fontSize: 30),
),
],
),
),
),
);
}
}
되게 쉽다! UI에 변수를 쓸 때는 $
을 붙이는 걸 잊지 말자.
2가지가 필요하다. IconButton 등의 버튼 관련 위젯, 그리고 SetState 함수다.
class _AppState extends State<App> {
int counter = 0;
void onClicked() {
counter = counter + 1;
}
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: const Color(0xFFF4EDDB),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Click Here!',
style: TextStyle(fontSize: 30),
),
Text(
'$counter',
style: const TextStyle(fontSize: 30),
),
IconButton(
iconSize: 40,
onPressed: onClicked,
icon: const Icon(Icons.add_box_rounded),
)
],
),
),
),
);
}
}
되게 쉽다! IconButton
을 선언하면 아래와 같이 나온다.
IconButton(onPressed: onPressed, icon: icon)
onPressed
에는 Set 함수를 넣고, icon
에는 아이콘 관련 데이터를 넣으면 된다!
결과는 아래와 같다.
근데 직접 버튼을 클릭해보면 작동하지 않을 것이다 왜그럴까? JS나 TS에선 저렇게하면 되는데 왜 안되는 걸까?
바로 플러터에는 setState()
메소드를 필수로 사용해야한다.
setState()
메소드그냥 위 코드와 같이 void onClicked() {counter = counter + 1;}
를 넣는다면 플러터에서는 얘가 상태를 바꾸는지 안바꾸는지 모르기 때문이다.
플러터에서 상태가 바뀌고, 그 상태가 바뀌었다면 다시 재렌더링 하라고 플러터에게 명령을 내리는 것과 같다.
void onClicked() {
setState(() {
counter = counter + 1;
});
}
이런 식으로 setState()
메소드를 사용하여 플러터에게 데이터가 변경됐음을 알려주어야 한다.
성능을 최적화하고 불필요한 리렌더링을 방지하기 위해 그렇다.
플러터에서 UI 업데이트는 변경된 상태를 감지하고 필요한 경우에만 다시 렌더링하는, 우리에게 친근한 "리액트 스타일"의 접근 방식을 채택하고 있다. 이를 통해 성능을 최적화하고 불필요한 UI 업데이트를 피할 수 있다.
기본적으로 플러터는 위젯의 build()
메소드가 호출될 때 해당 위젯의 UI를 렌더링한다. 하지만 특정 상태가 변경되었을 때만 UI를 다시 그리는 것이 효율적이다. 이를 위해 setState()
메소드를 사용하여 상태가 변경될 때만 build()
메소드를 호출하고 UI를 업데이트한다.
setState()
를 호출하지 않으면 상태 변화가 감지되지 않아 UI 업데이트가 발생하지 않는다. 따라서 setState()
를 사용하여 상태 변경을 알려줘야만 플러터는 해당 변경사항을 감지하고 UI를 업데이트한다.
그러한 이유로 setState()
를 이렇게도 호출할 수 있다.
void onClicked() {
counter = counter + 1;
setState(() {});
}
ㅋㅋㅋ 어떤가! 이 함수를 실행하면 마지막에 리렌더링 해주세요~ 하는 식으로 해도 된다. 리액트를 공부해서인지 이 로직이 이해가 잘갔다 ㅎㅎ
짠! Dart Pad에서 간단히 저렇게 구현할 수 있다! 끝!
state 관련은 비슷해서 새로운 느낌 없이 잘 보이네요 ㅋㅋ 고생하셨습니다 !