(이전 포스트와 이어집니다!)
4. Element Tree
이전 포스트에서 Widget Tree는 화면에 나타나는 모든 UI를 구성하고 있는 요소인 Widget의 부모-자식 관계를 나타내는 구조라고 적었습니다. Element Tree는 Widget Tree에 있는 각 Widget 인스턴스들의 상태(State)와 생명주기(LIfe cycle)를 관리를 담당합니다.
왜 Widget의 상태와 생명주기를 Widget 자체, 혹은 Widget Tree에서 관리하지 않고 별도의 Element Tree에서 담당하게 개발이 되었을까요? 이유는 Flutter에서 상태가 변경이 될 때 이전 상태와 비교를 통해 꼭 필요한 부분만 빌드가 되는 구조를 만들어 최고의 성능을 보장하기 위해서입니다.
Element Tree는 Widget Tree의 변경 사항을 추적하고, Widget의 상태가 변경되었을 때 필요한 부분만 업데이트를 합니다. 이는 Widget의 initState나 setState와 같이 상태를 관리하는 메서드를 사용할 때도 마찬가지로 적용됩니다.
간단한 예시를 통해 Element Tree를 더 이해해보도록 하겠습니다. 아래 코드 스니펫은 버튼을 누르면 카운트가 올라가는 아주 간단한 기능을 하는 위젯입니다.
// My Counter Widget
class MyCounterWidget extends StatefulWidget{
@override
_MyCounterWidgetState createState() => _MyCounterWidgetState();
}
class _MyCounterWidgetState extends State<MyCounterWidget> {
int _counter = 0;
void _increaseCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Counter: $_counter'),
ElevatedButton(
onPressed: _increaseCounter,
child: Text('Increase Counter'),
),
],
),
}
}
위와 같은 코드가 빌드 되면, 프레임워크에 인해 아래와 같은 엘리먼트 트리가 생성됩니다.
...
Element: Column
Element: Text (Counter: 0)
Element: ElevatedButton
Element: Text (Increment)
이때 Elevated Button Widget을 누르게 되면 _increaseCounter 메서드가 호출이 되고, 메서드 내부의 setState가 호출이 되며 Element Tree는 이전 상태와 비교했을 때 상태 변화가 필요한 Widget을 감지하고, 새로운 상태로 업데이트 합니다. setState 메서드가 호출되지 않으면 Widget의 상태가 변화하지 않으므로, UI의 변화도 없게 됩니다. 새로운 상태로 업데이트 된 Element Tree는 다음과 같습니다.
...
Element: Column
Element: Text (Counter: 1)
Element: ElevatedButton
Element: Text (Increment)
이처럼 Element Tree는 Widget 인스턴스의 상태와 생명주기를 관리한다는 것을 알 수 있습니다. Stateful Widget에서 setState메서드를 호출하는 것으로 Widget을 업데이트 하는 것이 보편적인 방법이지만, Provider 등 상태관리 패기지를 사용해서 Widget의 상태관리를 하는 방법도 있습니다. 이 내용은 상태관리 패키지에 대한 포스트를 작정할 기회가 있으면 더 깊게 다루어보겠습니다.
5. Render Tree
Widget Tree가 Widget들 사이의 위계를 나타내고 Element Tree가 Widget 인스턴스의 상태와 생명주기를 관리하는 구조라면, Render Tree는 UI 요소의 위치, 크기 등 레이아웃과 그리기를 담당합니다. Render Tree의 노드는 Render Object라고 하는 위젯의 시각적 정보가 포함된 오브젝트입니다. Render Object는 화면에 어떤 것을 그려야 하는지에 대한 상세한 정보가 있습니다.
Flutter가 단순히 모바일 개발 프레임워크, 혹은 크로스플랫폼 프레임워크가 아니라 UI 툴킷이라고 불리는 이유는, 이 Render Object를 사용하면 생각할 수 있는 모든 UI를 만들어낼 수 있기 때문입니다. 아래 영상처럼 게임개발에도 사용될 수 있습니다 (물론 Flutter가 게임 개발에 적합한 도구는 아니라고 생각합니다.)
Render Tree 역시 Element Tree의 영향을 받습니다. 위와 비슷한 예시를 들어 설명해보겠습니다.
// My Counter Widget
class MyCounterWidget extends StatefulWidget{
@override
_MyCounterWidgetState createState() => _MyCounterWidgetState();
}
class _MyCounterWidgetState extends State<MyCounterWidget> {
int _counter = 0;
void _increaseCounter() {
setState(() {
_counter++;
});
}
bool _isEven() {
return _counter % 2 == 0
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Counter: $_counter',
style: TextStyle(
color: _isEven() ? Colors.pink : Colors.orange,
)
),
ElevatedButton(
onPressed: _increaseCounter,
child: Text('Increase Counter'),
),
],
),
}
}
위 예시에서 _counter가 짝수면 분홍색, 홀수면 주황색 글씨를 보여주는 기능을 추가했습니다. 시 _increaseCounter 메서드가 호출이 되면 setState에 의해 해당 Widget의 상태가 업데이트 되고, Render Tree는 변경된 색상 정보를 반영합니다. 이 경우 Render Tree의 구조는 다음과 같습니다.
// isEven == true
...
RenderObject: RenderBox(Column)
RenderObject: RenderBox(Text - Color(Pink))
RenderObject: RenderBox(ElevatedButton)
// isEven == false
...
RenderObject: RenderBox(Column)
RenderObject: RenderBox(Text - Color(Orange))
RenderObject: RenderBox(ElevatedButton)
6. 결론
이처럼 Flutter는 UI 구조를 세 개의 트리 구조를 통해 효율적으로 관리하고 있습니다. Dart 언어 자체의 빠른 성능과 더불어 독특한 트리 구조는 꼭 필요한 부분의 UI만 새로 빌드하며 언제나 최적의 성능을 내고 있습니다. 세 트리의 상호작용을 이해하고 적절히 활용하면, 복잡하고 반응성이 뛰어난 애플리케이션을 개발하는 데 큰 도움이 됩니다. Flutter는 이러한 트리 구조를 통해 개발자에게 강력하고 유연한 도구를 제공하여, 다양한 플랫폼에서 일관되고 아름다운 UI를 구현할 수 있게 합니다.
'Tech' 카테고리의 다른 글
[Flutter] Dart와 Flutter 업데이트 (1) | 2024.06.14 |
---|---|
[Flutter] 플러터의 트리 구조 (1/2) (15) | 2024.05.24 |
[Flutter] 웹뷰 패키지 2종 비교 (24) | 2024.05.10 |