Overview.
이번에는 key 가 왜 중요한지에 대하여 알아보자 조금 어려운(?) 개념이라는데 40분만 들어서는 아무래도 영 몹미더운 부분이 있을 수 있으니 양해 부탁드립니다.
flutter 로는 많은 설명이 있으니 그 부분은 간단히? 들어가봅니다.
- Stateless 와 Stateful
- Key 를 어떻게 부모 객체에 전달 할 수 있을까
0. 문서 (구글 짱이야)
https://velog.io/@sooah/Flutter-Key
[Flutter] Key
super key 에러 제거하기
velog.io
https://dart-ko.dev/language/constructors
생성자
Dart 생성자에 대한 모든 것.
dart-ko.dev
https://www.youtube.com/watch?v=6t2ZKrwDn3U&list=PL93mKxaRDidGqIewEULPeKMJRcWD9KRrn&index=37
[Flutter] Key 란 무엇인가?
Key 란? 위젯 트리에서 위젯이 움직일 때마다 현 상태를 보존하는 역할을 하는 Key입니다. Key를 사용해서 이전에 스크롤 한 위치를 기억해서 다른 페이지를 갔다가 다시 빌드 될 때 해당 스크롤 위
jutole.tistory.com
1. Stateless 와 Stateful
Widget Tree 나 사진은 따로 많으니깐 중요 부분만 간단하게만 알아봅시다!
Stateless
두개 화면만 봐도 알 수 있듯 Stateless 는 상태와 같은 타입이라면 계산 과정에서 B 와 A 의 index 가 변경되었더라도 랜더링이 가능합니다. 이정도야 뭐 아실테고
Stateful
Stateful 위젯은 랜더링시 타입으로 비교하는 것이 아닌 key 값으로 비교를 합니다.
abstract class StatefulWidget extends Widget {
/// Initializes [key] for subclasses.
const StatefulWidget({ super.key });
...
// ----------------------------------------
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });
final Key? key;
위와 같이 해당 Widget 의 key 가 final 로 되어있는 것을 알 수 있습니다. 따라서 key 값으로 비교를 한다는 것을 알 수 있죠.
그럼 이렇게 볼 수 있죠.
하나의 Container[key] -> Stateful [key] -> Widget [key]
다만 이 세가지의 객체 중에서 제일 먼저 실행되는 객체는 무엇일까요?
Java 에서는 super(key) 로 Container[key] -> Stateful [key] -> Widget [key] 순서대로 객체를 생성하게 됩니다.
다만 Dart 에서는 반대입니다. Widget [key] -> Stateful [key] -> Container [key] 이 순서로 객체를 생성하게 되는것이죠.
그렇다면 한마디로 밑과 같은 형식을 사용한다면? 오류가 발생하게 됩니다.
오류 코드
class A {
var key;
A(this.key) {
print('A 생성');
print(key);
}
}
class B extends A {
B (var key) {
super(key); // 불가능-> 오류
print("B 생성");
}
}
void main () {
var key = "cos";
B(key); // B 객체 생성
}
2. Key 를 어떻게 부모 객체에 전달 할 수 있을까
답은 이니셜라이저를 사용해야합니다.
위에 문서쪽에 생성자에 대한 문서를 넣어놨습니다.
문서를 잠시 보시죠.
부모 클래스의 Non-default 생성자 호출
자식 클래스의 생성자는 부모 클래스의 이름이 없고(unnamed), 인수가 없는(no-argument) 생성자를 디폴트로 호출합니다. 부모 클래스의 생성자는 자식 클래스 생성자 바디의 처음에 호출됩니다. 이니셜라이저 리스트가 사용되면, 부모 클래스가 호출되기 전에 실행됩니다. 요약하자면, 실행 순서는 다음과 같습니다:
- 이니셜라이저 리스트
- 부모 클래스의 인자가 없는 생성자
- 메인 클래스의 인자가 없는 생성자
위 부분에서 봐야하는 곳은 부모 클래스의 생성자는 자식 클래스 생성자 바디의 처음에 호출됩니다. 라는 부분입니다.
따라서 문서와 같이 이니셜라이저를 사용하게 되면 부모 클래스가 호출되기 전에 실행되기 때문에 안전하게 key 를 전달할 수 있는 것이죠.
코드를 보시죠
class A {
var key;
A({this.key}) {
print('A 생성');
print(key);
}
}
class B extends A {
B (var key) : super(key : key) {
//super(key); // 불가능 -> 이니셜라이저를 사용해야됨
print("B 생성");
}
}
void main () {
var key = "cos";
B(key); // B 객체 생성
}
// 내부 super(key) 를 사용하지 못하는 이유
// A 객체가 B 보다 먼저 생성 되기 때문!! 한마디로 super(key)를 무시하고 먼저 A 객체를 생성
// 따라서 이니셜라이저를 사용해야만 key 값을 넘겨 줄수 있음.
// 자바에서는 super(key) 로써 B 객체 생성 후 A 객체가 생성되지만
// dart 에서는 또는 flutter 에서는 부모 객체[A]가 먼저 생성됨
위와 같은 코드를 flutter 에서 활용해 보겠습니다 뭐 다들 똑같은 형식으로 구현을 하시더라구요 ㅎㅎㅎㅎ
import 'dart:math';
import 'package:flutter/material.dart';
// key란?
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: _Home(),
);
}
}
class _Home extends StatefulWidget {
const _Home({super.key});
@override
State<_Home> createState() => _HomeState();
}
class _HomeState extends State<_Home> {
List<Widget> boxs = [
MyBoxKey(ValueKey("1")),
MyBoxKey(ValueKey("2")),
];
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.change_circle_outlined),
onPressed: () {
setState(() {
boxs.insert(1, boxs.removeAt(0));
//Widget b = boxs.removeAt(0);
//boxs.add(b);
});
},
),
body: Row(children: boxs),
),
);
}
}
// StatefulWidget -> 키 값을 비교하고 랜더링
class MyBoxKey extends StatefulWidget {
// 자식의 생성자가 실행될 때 부모의 생성자가 먼저 실행됨.
// 자식의 스택보다 부모의 생성자가 먼저 실행됨.
Color mColor = Colors.primaries[Random().nextInt(Colors.primaries.length)];
MyBoxKey(Key key):super(key : key);
//{super(key);} -> 오류
@override
State<MyBoxKey> createState() => _MyBoxKeyState();
}
class _MyBoxKeyState extends State<MyBoxKey> {
@override
Widget build(BuildContext context) {
return Container(
color: widget.mColor,
height: 150,
width: 150,
);
}
}
위와 같이 이니셜라이저를 사용할 수 있다는 것을 확인할 수 있습니다.
MyBoxKey(Key key):super(key : key);
//{super(key);} -> 오류
3. 그럼 Stateful 은 키값이 무조건 있어야 되는가?
그건 좀 다릅니다. 생성자를 활용할 수 있기 때문이죠.
코드로 확인하실 수 있습니다.
import 'dart:math';
import 'package:flutter/material.dart';
// key란?
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: _Home(),
);
}
}
class _Home extends StatefulWidget {
const _Home({super.key});
@override
State<_Home> createState() => _HomeState();
}
class _HomeState extends State<_Home> {
List<Widget> boxs = [
MyBoxConstructor(Colors.red),
MyBoxConstructor(Colors.blue),
];
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.change_circle_outlined),
onPressed: () {
setState(() {
boxs.insert(1, boxs.removeAt(0));
//Widget b = boxs.removeAt(0);
//boxs.add(b);
});
},
),
body: Row(children: boxs),
),
);
}
}
// StatefulWidget -> 생성자 값을 비교하고 랜더링
class MyBoxConstructor extends StatefulWidget {
// 자식의 생성자가 실행될 때 부모의 생성자가 먼저 실행됨.
// 자식의 스택보다 부모의 생성자가 먼저 실행됨.
Color mColor;
MyBoxConstructor(this.mColor);
//{super(key);} -> 오류
@override
State<MyBoxConstructor> createState() => _MyBoxConstructorState();
}
class _MyBoxConstructorState extends State<MyBoxConstructor> {
@override
Widget build(BuildContext context) {
return Container(
color: widget.mColor,
height: 150,
width: 150,
);
}
}
위와 같이 생성자를 사용할 수도 있다는 것도 확인할 수 있습니다.
MyBoxConstructor(this.mColor);
//{super(key);} -> 오류
4. key 의 종류
UniqueKey
자동으로 유니크한 키가 생성되기 때문에 확실하게 위젯을 구별할 때 사용된다.
ValueKey
하나의 정보에서 생성되는 키
텍스트나 숫자 등 구별이 가능한 하나의 정보를 키에 넣어서 사용한다.
ObjectKey
객체를 이용해 구별하는 키
만약 일기장의 경우 날짜를 키값으로 사용하면 중복 키값이 발생할 수 있다.
날짜+제목을 하나의 오브젝트 키로 사용한다면 중복키를 제거할 수 있을 것이다.
PageStorageKey
사용자의 스크롤 위치를 기억하는데 사용되는 키
GlobalKey
전체 앱에서 고유하게 사용할 수 있는 키
서로 다른 2개의 스크린에서 같은 위젯을 띄우고 상태는 똑같게 유지해야 하는 경우 등에 사용된다.
주의할 점은 uniquekey는 자동으로 키를 만들어 주기 때문에 편리하지만 key를 사용해 액세스 할 때는 그 값을 기억할 필요가 있습니다.
'flutter & dart' 카테고리의 다른 글
flutter & dart - AnimatedContainer (0) | 2024.04.03 |
---|---|
flutter & dart - TabBar , DefaultTabController (0) | 2024.04.02 |
flutter & dart - 아코디언 같은 ExpantionPanelList (0) | 2024.04.02 |
flutter & dart - 사이드바? Drawer (0) | 2024.04.02 |
flutter & dart - Draggable , DragTarget (0) | 2024.04.01 |