

1. Stateful Widget
- State라는 변경사항을 통해 생명주기를 갖는 Widget / 상태 변화
- final을 적으면 안됨
- 변수를 직접 변경해도 화면이 변경되지 않음
→ 값을 바꾸고 나서 build를 호출해야 다시 그려짐
- setState()로 변경해야 함
내부에 build() 포함 → 같이 바뀜
2. Stateless Widget
- 앱이 동작하면서 변하지 않는 Widget
3. Widget Tree
- 깊이 우선 탐색(DFS)
그래프나 트리를 탐색하는 데 사용되는 알고리즘 중 하나
한 지점에서 시작하여 해당 지점에서 방문 가능한 모든 정점을 탐색한 후, 그 중 하나를 선택하여 해당 정점에서 다시 DFS를 수행
이 과정을 모든 정점을 방문할 때까지 반복
- 재귀적인 접근
보통 재귀적으로 구현스택 자료구조를 사용하여 재귀 호출을 대체할 수 있음
- 깊이 우선
한 지점에서 시작하여 해당 지점에서 가능한 한 깊이까지 탐색
즉, 최대한 한 가지 경로를 따라 깊숙히 들어가다가 더 이상 진행할 수 없을 때 다시 돌아와 다른 경로를 탐색
- 스택 활용
재귀 호출이나 명시적인 스택을 사용하여 구현
각 정점을 방문할 때마다 스택에 해당 정점을 추가, 해당 정점에서 방문 가능한 다음 정점을 스택에 추가하는 방식
- 시간 복잡도 : O(V + E)
V는 정점의 수, E는 간선의 수
- 넓이 우선 탐색(BFS)
- 그래프나 트리를 탐색하는 알고리즘 중 하나입니다. 이 알고리즘은 시작 정점에서 시작하여 인접한 모든 정점을 먼저 방문한 후, 그 다음 레벨의 정점들을 방문합니다. 이 과정을 모든 정점을 방문할 때까지 반복합니다.
- 큐를 활용: BFS는 큐 자료구조를 사용하여 구현됩니다. 시작 정점을 큐에 넣고, 해당 정점과 인접한 모든 정점들을 큐에 넣은 후에는 큐에서 하나씩 빼면서 탐색을 진행합니다.
- 넓이 우선: BFS는 시작 정점으로부터 각 레벨에 있는 모든 정점을 우선하여 탐색합니다. 즉, 한 레벨의 모든 정점을 방문한 후에 다음 레벨로 넘어가는 방식으로 동작합니다.
- 최단 경로 탐색: BFS는 시작 정점으로부터 다른 정점까지의 최단 경로를 찾는 데 유용합니다. 먼저 더 가까운 정점부터 탐색하기 때문에 최단 경로를 먼저 찾을 수 있습니다.
- 시간 복잡도: BFS의 시간 복잡도는 O(V + E)입니다. 여기서 V는 정점의 수이고, E는 간선의 수입니다.
4. 컨텍스트 분리

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


- 변화는 상태만 stateful로 관리하고 바뀌지 않는 부분은 stateless로 관리함


- 숫자가 바뀌진 않음

5. hot reload
- 애플리케이션의 코드나 리소스를 수정한 후에도 변경 사항을 즉시 적용하는 기술
- 바뀐 부분만 reload 됨

- 상태를 바뀐다고 다시 그려지지 않음

6. notify
- 값이 바뀐다고 알려줘야 다시 그림을 그림
- GUI에서는 권한이 없음 / 내가 직접 build를 실행할 수 없기 때문


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. 실습하기
- 컨텍스트 분리의 장점 : 변하는 부분만 리로드 가능함


- 밑에 것만 리로드 됨

bottom은 header의 setState가 필요함
부모가 자식에게 값을 넘길 순 있지만 밑에 있는 페이지가 위에 값을 전달해줄 수 없음
⇒ 공통 조상 다시 그리기
노란색 전체 배경에서 상태 관리하기
H = 조상 / T = header / B = bottom

컨텍스트 분리 : 부분 리로드
상태값과 상태값을 변경 메서드가 분리되어 있으면 공통 조상에서 상태(변수)를 관리해야 함

- 매개변수로 함수 전달 가능함
- 조상이 리로드되서 포함된 모든 것이 그려짐

- 공통 조상으로 리로드되는데 컴포넌트로 분리한 이유

- 공통 조상을 찾아 리로드하기

- 컴포넌트 설계시 공통 분모를 최단 거리로 만들어야 함!!
한 줄로 쫙 쫘서 쪼개야 가능함

- stateless도 stateful의 자식이면 다시 그려짐

- stateless만 리로드 하려면 상태를 외부에서 가져오면 됨
상태를 클래스로 빼서 관리함 → observer 패턴 적용
여러 군데에서 변경해야 될 수 있음

- 구독자들은 관찰하고 있다고 자기들을 스스로 다시 그릴 수 있음
"퍼블리시-서브스크라이브(Publish-Subscribe)"는 소프트웨어 아키텍처 패턴 중 하나로, 컴포넌트 간의 통신을 가능하게 하는 방법 중 하나입니다. 이 패턴은 이벤트 기반 아키텍처에서 주로 사용되며, 발행자(Publisher)와 구독자(Subscriber) 간의 관계를 정의합니다.
- 퍼블리시(Publish): 발행자는 이벤트나 메시지를 생성하고, 해당 이벤트를 전달할 때 채널이나 토픽을 선택합니다. 이를 "퍼블리싱"이라고 합니다. 발행자는 생성한 이벤트를 특정 채널에 전송합니다.
- 서브스크라이브(Subscribe): 구독자는 발행자가 전달한 이벤트를 감지하고, 그 이벤트에 반응하여 적절한 동작을 수행합니다. 구독자는 특정 채널에 "구독"하여 해당 채널로부터 이벤트를 수신합니다.

- stateful과 stateless 상태 변경

- 조상으로 상태 관리
상태 값을 가지고 있는 컴포넌트와 상태를 변경하는 메서드가 있는 컴포넌트가 있음
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 되면 다시 그림은 그려지지만 객체 생성은 안됨
- 상태가 없어야 함
- 매개변수가 있으면 변경될 가능성이 있음




- 상수를 사용해서 오류가 안 남

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,
// );
// }
// }

Share article