상태관리

송민경's avatar
Apr 15, 2024
상태관리

notion image

1. Stateful Widget

  • State라는 변경사항을 통해 생명주기를 갖는 Widget / 상태 변화
  • final을 적으면 안됨
  • 변수를 직접 변경해도 화면이 변경되지 않음
→ 값을 바꾸고 나서 build를 호출해야 다시 그려짐
  • setState()로 변경해야 함
내부에 build() 포함 → 같이 바뀜
 

2. Stateless Widget

  • 앱이 동작하면서 변하지 않는 Widget
 

3. Widget Tree

  • 깊이 우선 탐색(DFS)
그래프나 트리를 탐색하는 데 사용되는 알고리즘 중 하나
한 지점에서 시작하여 해당 지점에서 방문 가능한 모든 정점을 탐색한 후, 그 중 하나를 선택하여 해당 정점에서 다시 DFS를 수행
이 과정을 모든 정점을 방문할 때까지 반복
 
  • 재귀적인 접근
보통 재귀적으로 구현스택 자료구조를 사용하여 재귀 호출을 대체할 수 있음
  • 깊이 우선
한 지점에서 시작하여 해당 지점에서 가능한 한 깊이까지 탐색
즉, 최대한 한 가지 경로를 따라 깊숙히 들어가다가 더 이상 진행할 수 없을 때 다시 돌아와 다른 경로를 탐색
  • 스택 활용
재귀 호출이나 명시적인 스택을 사용하여 구현
각 정점을 방문할 때마다 스택에 해당 정점을 추가, 해당 정점에서 방문 가능한 다음 정점을 스택에 추가하는 방식
  • 시간 복잡도 : O(V + E)
V는 정점의 수, E는 간선의 수
 
  • 넓이 우선 탐색(BFS)
  • 그래프나 트리를 탐색하는 알고리즘 중 하나입니다. 이 알고리즘은 시작 정점에서 시작하여 인접한 모든 정점을 먼저 방문한 후, 그 다음 레벨의 정점들을 방문합니다. 이 과정을 모든 정점을 방문할 때까지 반복합니다.
 
  1. 큐를 활용: BFS는 큐 자료구조를 사용하여 구현됩니다. 시작 정점을 큐에 넣고, 해당 정점과 인접한 모든 정점들을 큐에 넣은 후에는 큐에서 하나씩 빼면서 탐색을 진행합니다.
  1. 넓이 우선: BFS는 시작 정점으로부터 각 레벨에 있는 모든 정점을 우선하여 탐색합니다. 즉, 한 레벨의 모든 정점을 방문한 후에 다음 레벨로 넘어가는 방식으로 동작합니다.
  1. 최단 경로 탐색: BFS는 시작 정점으로부터 다른 정점까지의 최단 경로를 찾는 데 유용합니다. 먼저 더 가까운 정점부터 탐색하기 때문에 최단 경로를 먼저 찾을 수 있습니다.
  1. 시간 복잡도: BFS의 시간 복잡도는 O(V + E)입니다. 여기서 V는 정점의 수이고, E는 간선의 수입니다.
 

4. 컨텍스트 분리

notion image
설정 : 버튼을 클릭하면 사과가 딸기로 바뀌고 사과 그림이 딸기 그림으로 다시 그려짐
  • build하면 전체가 다 그려져서 비효율적
  • 바뀌는 부분만 다시 그리는게 효율적
→ build안에 바뀌는 부분을 새로운 컴포넌트로 분리 : 컨텍스트 분리
내부의 build만 실행시켜서 부분만 그릴 수 있음
  • 외부의 build를 실행시키면 내부의 build도 다시 그려짐
build() : 그림을 그려주는 메서드
notion image
notion image
  • 변화는 상태만 stateful로 관리하고 바뀌지 않는 부분은 stateless로 관리함
notion image
notion image
  • 숫자가 바뀌진 않음
notion image
 

5. hot reload

  • 애플리케이션의 코드나 리소스를 수정한 후에도 변경 사항을 즉시 적용하는 기술
  • 바뀐 부분만 reload 됨
notion image
  • 상태를 바뀐다고 다시 그려지지 않음
notion image
 

6. notify

  • 값이 바뀐다고 알려줘야 다시 그림을 그림
  • GUI에서는 권한이 없음 / 내가 직접 build를 실행할 수 없기 때문
notion image
notion image
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 = 3; @override Widget build(BuildContext context) { print("나 그러짐"); return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Text("${num}"), TextButton( onPressed: () { num++; print("num : ${num}"); setState(() {}); // rebuild }, child: Text("증가")), ], ), ), ); } } class HeaderPage extends StatelessWidget { HeaderPage(); @override Widget build(BuildContext context) { return Container( color: Colors.red, child: Align( child: Text( "1", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ); } } class BottomPage extends StatelessWidget { BottomPage(); @override Widget build(BuildContext context) { return Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { print("버튼 클릭됨"); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ); } }
 

7. 실습하기

  • 컨텍스트 분리의 장점 : 변하는 부분만 리로드 가능함
notion image
notion image
  • 밑에 것만 리로드 됨
notion image
 
bottom은 header의 setState가 필요함
부모가 자식에게 값을 넘길 순 있지만 밑에 있는 페이지가 위에 값을 전달해줄 수 없음
⇒ 공통 조상 다시 그리기
노란색 전체 배경에서 상태 관리하기
 
H = 조상 / T = header / B = bottom
notion image
컨텍스트 분리 : 부분 리로드
상태값과 상태값을 변경 메서드가 분리되어 있으면 공통 조상에서 상태(변수)를 관리해야 함
notion image
  • 매개변수로 함수 전달 가능함
  • 조상이 리로드되서 포함된 모든 것이 그려짐
notion image
  • 공통 조상으로 리로드되는데 컴포넌트로 분리한 이유
notion image
  • 공통 조상을 찾아 리로드하기
notion image
  • 컴포넌트 설계시 공통 분모를 최단 거리로 만들어야 함!!
한 줄로 쫙 쫘서 쪼개야 가능함
notion image
  • stateless도 stateful의 자식이면 다시 그려짐
notion image
  • stateless만 리로드 하려면 상태를 외부에서 가져오면 됨
상태를 클래스로 빼서 관리함 → observer 패턴 적용
여러 군데에서 변경해야 될 수 있음
notion image
  • 구독자들은 관찰하고 있다고 자기들을 스스로 다시 그릴 수 있음
"퍼블리시-서브스크라이브(Publish-Subscribe)"는 소프트웨어 아키텍처 패턴 중 하나로, 컴포넌트 간의 통신을 가능하게 하는 방법 중 하나입니다. 이 패턴은 이벤트 기반 아키텍처에서 주로 사용되며, 발행자(Publisher)와 구독자(Subscriber) 간의 관계를 정의합니다.
  1. 퍼블리시(Publish): 발행자는 이벤트나 메시지를 생성하고, 해당 이벤트를 전달할 때 채널이나 토픽을 선택합니다. 이를 "퍼블리싱"이라고 합니다. 발행자는 생성한 이벤트를 특정 채널에 전송합니다.
  1. 서브스크라이브(Subscribe): 구독자는 발행자가 전달한 이벤트를 감지하고, 그 이벤트에 반응하여 적절한 동작을 수행합니다. 구독자는 특정 채널에 "구독"하여 해당 채널로부터 이벤트를 수신합니다.
notion image
  • stateful과 stateless 상태 변경
notion image
  • 조상으로 상태 관리
상태 값을 가지고 있는 컴포넌트와 상태를 변경하는 메서드가 있는 컴포넌트가 있음
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; void increase(){ num++; setState(() {}); } @override Widget build(BuildContext context) { return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Expanded(child: HeaderPage(num)), Expanded(child: BottomPage(increase)), // () 하지마라 ], ), ), ); } } class HeaderPage extends StatelessWidget { int num; HeaderPage(this.num); @override Widget build(BuildContext context) { print("header"); return Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ); } } class BottomPage extends StatelessWidget { Function increase; BottomPage(this.increase); @override Widget build(BuildContext context) { print("bottom"); return Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { print("버튼 클릭됨"); increase(); }, child: Text( "증가", "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ); } }
 
  • 상태를 변경하는 메서드에 객체의 내용이 같을 경우
  • 시리얼라이즈드 넘버?
  • new가 몇번이 되든 모든 데이터(객체의 상태 값)가 동일하면 싱글톤으로 관리해도 됨
하나만 띄우고 다 그리면 됨 = 하나의 객체만 보고 그림을 그림
 
 
const
  • 한번만 new가 됨
  • build 되면 다시 그림은 그려지지만 객체 생성은 안됨
  • 상태가 없어야 함
  • 매개변수가 있으면 변경될 가능성이 있음
notion image
notion image
notion image
notion image
  • 상수를 사용해서 오류가 안 남
notion image
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 { const HomePage({Key? key}) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int num = 1; void increase() { num++; setState(() {}); } @override Widget build(BuildContext context) { return Container( color: Colors.yellow, child: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ Expanded(child: HeaderPage(num)), // Expanded(child: const MiddlePage(1)), // Expanded(child: const MiddlePage(1)), Expanded(child: BottomPage(increase)), ], ), ), ); } } class HeaderPage extends StatelessWidget { int num; HeaderPage(this.num); @override Widget build(BuildContext context) { print("header"); return Container( color: Colors.red, child: Align( child: Text( "${num}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, decoration: TextDecoration.none), ), ), ); } } class BottomPage extends StatelessWidget { Function increase; BottomPage(this.increase); @override Widget build(BuildContext context) { print("bottom"); return Container( color: Colors.blue, child: Align( child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () { print("버튼 클릭됨"); increase(); }, child: Text( "증가", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 100, ), ), ), ), ); } } // class MiddlePage extends StatelessWidget { // final num; // const MiddlePage(this.num); // // @override // Widget build(BuildContext context) { // return Container( // color: Colors.white, // ); // } // }
notion image
Share article
RSSPowered by inblog