[Flutter] 위젯이 생성되기 까지
Flutter로 개발을 할 때 Widget tree, Element Tree, Render Tree를 다들 한 번쯤은 들어보셨을 겁니다.
대충 어떤 내용인지는 알고 계시겠지만 그 정도에서 넘어가기에는 더 심도있게 알아야한다고 생각하여 이번 글에서 정리하여 소개해보겠습니다.
우선 Flutter는 선언형 UI 입니다.
해당 접근 방식은 UI를 함수나 객체로 선언하여, 상태가 변경될 때마다 UI를 새로 빌드하는 방식입니다.
선언형 UI
개발자는 UI의 최종 상태만을 선언하고, 해당 상태를 어떻게 표현할지 지정합니다. Flutter에서는 build() 메서드 내에서 UI를 구성하는 위젯을 선언적으로 정의하며, 이 위젯들이 트리 구조를 형성합니다.
- 명령형 UI와 비교하면 명령형 UI에서는 UI의 각 상태를 명령으로 정의하고, 상태 변경 시 이를 어떻게 업데이트할지 개발자가 직접 관리해야 합니다.
- 선언형 UI에서는 UI 상태와 이를 표현하는 방법만 정의하며, 상태 변경 시 프레임워크가 자동으로 UI를 업데이트합니다.
장점
- 코드의 간결함:
- 선언형 UI는 UI의 최종 상태를 선언적으로 표현하므로 코드가 직관적이고 간결합니다. 개발자가 상태 변경에 따른 UI 업데이트 로직을 일일이 작성할 필요가 없습니다.
- 상태 관리의 용이성:
- 상태가 변경되면 자동으로 UI가 재구성되므로, 상태 관리가 단순해집니다. setState()나 Riverpod, Provider 같은 상태 관리 도구와 결합하여 효율적으로 UI를 갱신할 수 있습니다.
- 예측 가능성:
- UI가 항상 특정 상태에 의해 결정되므로, 상태와 UI 간의 관계가 명확하고 예측 가능합니다. 이는 디버깅과 유지보수에 유리합니다.
- 재사용성:
- 위젯을 독립적이고 재사용 가능한 구성 요소로 설계하기 쉽습니다. 이로 인해 코드의 재사용성이 높아지고, 모듈화가 용이해집니다.
단점
- 성능 이슈:
- 상태가 변경될 때마다 UI 전체를 재구성하므로, 복잡한 UI에서는 성능 저하가 발생할 수 있습니다. 이를 해결하기 위해 Flutter는 const 위젯이나 RepaintBoundary, shouldRebuild 등을 통해 최적화를 제공합니다.
- 복잡한 UI 구조:
- 복잡한 UI를 선언형으로 구성할 때, 위젯 트리의 깊이가 깊어질 수 있으며, 이를 이해하고 유지보수하는 것이 어려울 수 있습니다. 이로 인해 코드의 가독성이 떨어질 수 있습니다.
- 부적절한 사용 시 비효율성:
- 모든 상황에 선언형 UI가 적합한 것은 아닙니다. 특히 매우 간단한 UI 로직에서는 선언형 방식이 오히려 과도한 접근일 수 있습니다. 이 경우, 불필요한 리빌드가 발생할 수 있어 비효율적일 수 있습니다.
그럼 여기서 선언하는 UI는 무엇일까요? 바로 위젯입니다. Flutter에서는 거의 모든 것이 위젯이고 심지어 레이아웃 모델도 위젯입니다.
Flutter 앱 내에서 볼 수 있는 이미지, 아이콘, 글자 모두 위젯입니다. 하지만 Row, Column, Grid 같이 볼 수 없는 위젯들도 있는데요, 이들은 보이는 위젯들을 제어하고, 제한하며, 정렬시켜 줍니다.
Widget Tree
위젯트리는 개발자가 직접적으로 코드를 작성하는 계층입니다.
위젯은 불변(immutable)합니다. 즉, UI가 업데이트될 때 기존 위젯이 수정되는 것이 아니라, 새로운 위젯 인스턴스가 생성됩니다.
Diagram 예시
위 다이어 그램은 Widget Tree의 예시입니다.
Row내에 3개의 Column이 존재하며, 이 Row를 Container가 감싸고 있는 형태인데 이처럼 실질적으로 저희가 작성하는 코드는 이 쪽 부분입니다.
Element Tree
- Element Tree는 위젯트리에서 어떤 위젯들이 있는지 탐색하고, 요소들 간의 관계를 관리, 변형 및 모핑 하는 작업입니다.
- Element는 위젯이 변경될 때마다 업데이트되며, 위젯의 생명주기를 관리합니다. 이 트리는 상태가 관리되는 StatefulWidget과 상태가 없는 StatelessWidget을 다르게 처리합니다.
Widget Tree와 Element Tree의 관계
위 사진은 Flutter 공식 문서에 소개된 자료인데요, 사진을 보시면 Widget Tree와 Element Tree는 1:1 관계로 이루어져 있습니다.
StatefulWidget의 예시를 들어보자면,
1. setState를 통해 상태가 변경
2. Element Tree가 Render Tree에게 전달해 상태를 바꾸라고 명령
3. 이 명령을 들은 Render Tree가 UI를 변경
이전 글과 연관 지어 설명드리자면 글 중에 Riverpod 뽀개기, 또는 상태관리 관련된 내용에서 관여하는 Tree는 실질적으로 Element Tree입니다.
이 처럼 Element Tree는 Widget Tree와 Render Tree의 중재역할을 하고 있습니다.
Render Tree
RenderObject의 생성은 Element가 하며, 실제로 UI를 화면에 그리는 역할을 합니다.
각 RenderObject는 화면의 특정 영역을 차지하고, 이 RenderObject가 화면에 어떻게 그려질지를 정의합니다.(위젯의 배치, 크기, 그리기 순서를 결정)
특징
- Flutter는 Widget, Element, RenderObject를 한 쌍으로 하여 계층적 구조와 각각의 tree들을 만들고 있습니다. 이러한 체계 덕분에 불필요한 렌더링을 줄이고, 뛰어난 성능을 발휘할 수 있습니다. 또한 개발자에게는 오직 위젯 트리만 관여하게 함으로써 개발을 더욱 수월하게 해주고 있습니다.
- Flutter는 build() 함수를 통해 Widget Tree를 만든다.
- 각 Widget마다 createElement() 함수를 통해 Element Tree 또한 생성된다.
개발자가 작성한 위젯 트리에서 어떤 위젯들이 있는지 탐색 후
1. build()
build() 함수를 통해 Widget Tree가 생성됩니다.
2.createElement()
각 위젯마다 createElement()를 Element Tree를 생성합니다.
3.Render Object 생성
Element가 생성될 때 Render Object도 생성됩니다.
3.layout()
Element Tree에서 각 Element Tree의 레이아웃을 결정한 뒤
4.paint()
paint() 함수를 통해 RenderObject를 생성하여 직접 표현하게 됩니다.
이번글에서는 Flutter에서 위젯이 생성되기까지의 내용을 소개해봤는데, 저도 글을 작성하면서 좀 더 깊게 알게 된 것 같습니다.
여러분들한테도 도움이 되셨으면 좋겠습니다. 오늘도 빡코딩!