11. Flutter 수업- Flutter 상태관리 익숙해지기!

상태관리
홍윤's avatar
Oct 04, 2024
11. Flutter 수업- Flutter 상태관리 익숙해지기!
💡

상태관리

 

1. 컨텍스트가 분리되어 있지 않으면 전체가 다시 그려진다

상태 관리와 재구성:
  • Flutter에서는 상태가 변경되면 그와 관련된 위젯 트리가 다시 빌드됩니다.
  • 불필요한 위젯 트리 전체의 재구성을 막기 위해 상태를 변경할 필요가 없는 위젯변경이 필요한 위젯을 구분하는 것이 매우 중요합니다.

2.텍스트를 분리하여, 상태가 변경되는 부분만 StatefulWidget(SF)로 만든다.

컨텍스트 분리:
  • 상태가 변경되더라도 영향을 받지 않아야 하는 부분(위젯)을 StatelessWidget으로 만들고, 상태가 변경되는 부분만 StatefulWidget으로 만듭니다.
  • 이를 통해 성능을 최적화하고, 불필요한 재구성을 방지할 수 있습니다.

3.그림을 그리다 보면, 상태의 화면과 행위의 화면이 다를 때가 있다.

부모 위젯에서 상태 관리:
  • 상태가 여러 자식 위젯에 영향을 미치는 경우, 부모 위젯에서 상태를 관리하고 자식들에게 그 상태와 상태를 변경하는 함수를 전달합니다.
  • 이 패턴을 사용하면 상태와 행위를 명확하게 분리할 수 있고, 유지보수가 용이해집니다.
 
이러한 개념을 적용하면, Flutter에서 성능을 유지하면서 상태를 효율적으로 관리하고 위젯을 다시 그리는 문제를 최소화할 수 있습니다.

1. Flutter는 상태를 가지고 그림을 그린다.

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int num = 1; @override Widget build(BuildContext context) { print("빌드됨"); return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("번호 : $num", style: TextStyle(fontSize: 30)), MyContainer(), // new를 두번 하지 않는다. 이것은 내가 제어 할 수 있따. ], ), ), floatingActionButton: FloatingActionButton( onPressed: () { num++; setState(() {}); print("num : $num"); }, child: Icon(Icons.add), ), ); } } class MyContainer extends StatelessWidget { const MyContainer(); @override Widget build(BuildContext context) { print("MyContainer 빌드됨"); return Container( height: 20, color: Colors.blue, ); } }
💡

코드설명

컨텍스트 분리하지 않으면 전체가 다시 그러진다.

2.

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int num = 1; @override Widget build(BuildContext context) { //나 그려짐 나오면 전체가 다시 그려진 것이다. print("나그려짐"); //자식이 있으면 자식의 크기만큼 작아지고 자식이 없으면 맥스 크기로 켜진다! return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Expanded( child: Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ), ), Expanded( child: Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { num++; setState(() {}); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ), ), ], ), ), ); } }
💡

코드설명

1번 코드랑 똑같이 텍스트분리가 되지 않았다.
 

3.

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // 1. 상태 int num = 1; // 2. 행위 void add() { num++; setState(() {}); } @override Widget build(BuildContext context) { print("나그려짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(num: num), Bottom(add: add), ], ), ), ); } } class Bottom extends StatelessWidget { final add; Bottom({required this.add}); @override Widget build(BuildContext context) { return Expanded( child: Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { add(); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ), ); } } class Header extends StatelessWidget { const Header({ super.key, required this.num, }); final int num; @override Widget build(BuildContext context) { return Expanded( child: Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ), ); } }
💡

코드설명

컨텍스트는 분리하였지만 전체가 다시 그려진다.

 

4.

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatefulWidget { //StatefulWidget 상태와 행위를 가질 수 있다. //부모의 영역을 다시 시작하면 자식들도 다시 그려진다. @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // 1. 상태 int num = 1; // 2. 행위 void add() { num++; setState(() {}); } @override Widget build(BuildContext context) { print("나그려짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(num: num), Bottom(add: add), ], ), ), ); } } class Bottom extends StatelessWidget { final add; Bottom({required this.add}); @override Widget build(BuildContext context) { print("Botton 다시 그려짐"); return Expanded( child: Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { add(); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ), ); } } class Header extends StatelessWidget { const Header({ super.key, required this.num, }); final int num; @override Widget build(BuildContext context) { print("Header 다시 그려짐"); return Expanded( child: Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ), ); } }
💡

코드설명

3번과 마찬가지로 컨텍스트 분리는 하였지만 실행하면 전체가 다시 그려진다.

5.

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { print("HomePage 다시 그려짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(), Bottom(), ], ), ), ); } } class Bottom extends StatelessWidget { @override Widget build(BuildContext context) { print("Bottom 다시 그려짐"); return Expanded( child: Container( color: Colors.blue, ), ); } } class Header extends StatefulWidget { @override State<Header> createState() => _HeaderState(); } class _HeaderState extends State<Header> { int num = 1; void add() { num++; setState(() {}); } @override Widget build(BuildContext context) { print("Header 다시 그려짐"); return Expanded( child: Container( color: Colors.red, child: Align( child: Column( children: [ Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ElevatedButton( onPressed: () { add(); }, child: Text("증가")) ], ), ), ), ); } }
notion image
💡

코드설명

컨텍스트 분리 O, 상태와 행위가 같이 있으면 그 부분만 Sf로 바꾸면 그 부분만 리로드 할수 있습니다.


6. 과제 : 상태관리 HomePage(StatefulWidget)에서 (StatelessWidget)로 바꾸고 Father로 컨텍스트 분리해서 Top과 Bottom만 그림 그려보기!

1. Before 상태관리 (Father X)

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } // 1. Father 클래스 생성 - SF 만들기 // 2. Father 클래스가 class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int num = 1; void add() { num++; setState(() {}); } @override Widget build(BuildContext context) { print("HomePage"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(), Expanded( child: Top(num: num), ), Expanded( child: Bottom(add: add), ), ], ), ), ); } } class Header extends StatelessWidget { const Header({ super.key, }); @override Widget build(BuildContext context) { print("Header"); return Container( color: Colors.green, height: 200, ); } } class Bottom extends StatelessWidget { // 행위 Function add; Bottom({required this.add}); @override Widget build(BuildContext context) { print("Bottom"); return Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { add(); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ); } } class Top extends StatelessWidget { const Top({ super.key, required this.num, }); final int num; @override Widget build(BuildContext context) { print("Top"); return Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ); } }

2. After 상태관리 (Father O)

import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { print("HomePage 그러짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Header(), Expanded( child: Father(), ), ], ), ), ); } } class Father extends StatefulWidget { @override State<Father> createState() => _FatherState(); } class _FatherState extends State<Father> { int num = 1; void add() { setState(() { num++; }); } @override Widget build(BuildContext context) { print("Father 그려짐"); return Column( children: [ Expanded( child: Top(num: num), ), Expanded( child: Bottom( add: add, ), ), ], ); } } class Header extends StatelessWidget { const Header({ super.key, }); @override Widget build(BuildContext context) { print("header"); return Container( color: Colors.green, height: 200, ); } } class Top extends StatelessWidget { final int num; const Top({Key? key, required this.num}) : super(key: key); @override Widget build(BuildContext context) { print("Top 그려짐"); return Container( color: Colors.red, child: Center( child: Text( '$num', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ); } } // Bottom 위젯: 버튼을 눌러 add 함수를 호출해 상태를 변경하는 역할 class Bottom extends StatelessWidget { final VoidCallback add; const Bottom({Key? key, required this.add}) : super(key: key); @override Widget build(BuildContext context) { print("Bottom 그려짐"); return Container( color: Colors.blue, child: Center( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: add, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ); } }
Share article

Uni