네, 이해했습니다. 영상의 전체 내용을 타임라인에 맞춰 번역하고 상세히 정리해드리겠습니다.
0:00 - 0:14 [음악 및 박수]
0:14 - 0:34
매트 설리번: 안녕하세요. 긴 기술 세션 후 오후 5시 가까이에 와주셔서 감사합니다. 오늘 Flutter가 위젯을 어떻게 렌더링하는지에 대해 이야기하겠습니다. 제 이름은 매트 설리번입니다.
앤드류 피츠 기본: 제 이름은 앤드류 피츠기본입니다.
0:34 - 0:56
매트: 앞으로 30분 동안 Flutter가 내부적으로 어떻게 작동하는지 설명하겠습니다. 오늘 이야기할 내용은 Flutter를 사용하는 일상적인 작업에 꼭 필요한 것은 아니지만, 이를 이해하면 성능 좋은 Flutter 앱을 만드는 데 도움이 될 것입니다.
0:56 - 1:21
앤드류: 오늘 아침 키노트에서 들으셨겠지만, Flutter는 웹, 모바일, 데스크톱에서 아름다운 애플리케이션을 만들기 위한 Google의 UI 툴킷입니다. Flutter로 흥미로운 것들을 만들 수 있지만, 우리는 매우 간단한 것을 할 것입니다. 화면 중앙에 텍스트가 있는 앱을 만들 겁니다. 그게 전부입니다.
1:21 - 1:39
매트: 이것이 가장 간단한 Flutter 앱이죠. 두 개의 위젯뿐입니다.
앤드류: 네, 두 개의 위젯만 있습니다.
1:39 - 2:17
앤드류: Flutter를 처음 배울 때 모든 것이 위젯이라는 것을 배웁니다. 우리는 내부적으로 이 위젯 트리를 만듭니다. 이 경우에는 Center와 Text가 있습니다. 조금 더 흥미롭게 만들어 보겠습니다. 배경을 흰색으로 만들기 위해 맨 위에 Container를 추가합니다. 이제 세 개의 위젯이 있습니다. 위젯 트리는 이렇게 생겼습니다. 맨 위에 Container, Center, 그리고 RichText가 있습니다.
2:17 - 3:02
매트: 실제로는 그렇지 않습니다. 우리 문서를 보면 "위젯은 사용자 인터페이스의 일부에 대한 불변의 설명"이라고 나와 있습니다. 불변성과 UI는 잘 어울리지 않습니다. UI는 불변이 아니기 때문입니다. UI의 다른 부분들이 변하고 바뀝니다. UI는 계속 변화합니다. 새로운 위젯이 들어오고 나갑니다. 그렇다면 위젯이 불변이라면 Flutter는 어떻게 UI의 상태를 관리할까요? 어떻게 변화를 표현할까요?
3:02 - 3:41
앤드류: 좋은 질문입니다, 매트.
매트: 네, 좋은 질문이죠. 제가 답하겠습니다. 실제로 Flutter는 이를 위해 세 가지 개념을 가지고 있습니다. 하나의 위젯 트리가 아니라 실제로 세 개가 있습니다. 위젯 트리, 엘리먼트 트리, 그리고 마지막으로 렌더 오브젝트 트리입니다. 이 세 가지 다른 개념을 결합하여 UI 렌더링을 가능한 한 성능 좋게 만듭니다.
3:41 - 4:50
이 세 가지는 새로운 개념일 수 있으니 Flutter 문서를 다시 살펴보겠습니다. 위젯은 불변이며, 선언적 프레임워크에서 작업하기 때문에 이해가 됩니다. UI가 어떻게 보일지 선언하고 위젯으로 구성합니다. 위젯은 엘리먼트의 구성을 설명합니다. 엘리먼트는 위젯의 인스턴스화입니다. 트리의 가변 부분으로, UI의 업데이트와 변경을 관리합니다. 모든 것을 제어하며 위젯의 생명주기를 관리한다고 생각할 수 있습니다. 마지막으로 렌더 오브젝트는 렌더 트리의 객체입니다. 렌더 오브젝트의 트리가 UI를 레이아웃하고 그립니다. Flutter가 UI를 그릴 때 위젯 트리를 보지 않고 렌더 오브젝트 트리를 봅니다.
4:50 - 6:15
이를 다른 관점에서 보면 구성, 생명주기, 그리기가 있습니다. UI에 어떻게 매핑될까요? 구성 측면에서는 위젯을 작성하는 것입니다. 컴포넌트에 속성과 값을 할당하고 API를 통해 수행하며, 이것이 위젯 트리가 됩니다. 그 다음 생명주기가 있는데, 이는 UI의 전체 생명주기를 관리합니다. UI 계층에 어떤 컴포넌트가 존재하는지 파악하고 이러한 엘리먼트 간의 관계를 관리하며 변형을 처리합니다. 마지막으로 UI를 그려야 합니다. 실제로 이것들을 화면에 그려야 하는데, 여기서 렌더 오브젝트가 등장합니다. 각 컴포넌트는 자신을 그리는 방법을 알고 있지만, 중요한 것은 자식에 대한 제약도 제공한다는 것입니다.
6:15 - 7:13
이 세 가지 개념인 구성, 생명주기, 그리기는 위젯, 엘리먼트, 렌더 오브젝트 간의 관심사 분리와 잘 맞습니다. 위젯 트리는 구성을 정의하고, 엘리먼트는 생명주기를 처리하며, 렌더 오브젝트는 레이아웃을 잡고 그립니다. 이 모든 것을 세 개의 트리로 결합하여 세 가지 다른 관심사 분리를 가집니다. 여기에는 많은 이론이 있고, UI를 렌더링하는 데 이것보다 더 복잡할 필요가 있나 싶을 수 있습니다. 왜 하나로 충분할 수 있는데 세 가지를 만드는 걸까요? 조금 더 낮은 수준에서 예제를 살펴보겠습니다.
7:13 - 8:22
앤드류: 네, 이제 실제 코드와 예제를 살펴보겠습니다. 예를 들어 Padding을 봅시다. 매우 간단한 위젯으로, 주변에 공간을 추가하는 것이 전부입니다. 여기 코드가 있습니다. Padding이 있고 그 안에 텍스트가 있으며 약간의 공간을 추가합니다. 매우 간단합니다. 세 트리의 책임을 다시 상기해봅시다. 위젯은 구성, 엘리먼트는 관리, 렌더 오브젝트는 그리기를 담당합니다. Padding의 경우, 위젯은 얼마나 패딩을 줄지와 자식이 무엇인지 알아야 합니다. 엘리먼트는 위젯과 렌더 오브젝트를 관리하는 것 외에는 별로 하는 일이 없습니다. 렌더 오브젝트도 많은 일을 하지 않습니다. 레이아웃과 크기를 담당하며, Padding의 경우 자식과 패딩을 위한 충분한 크기만 기억하면 됩니다. 이게 세 트리가 하는 전부입니다.
8:22 - 9:15
UI를 이렇게 선언적으로 구축함으로써 매우 성능 좋은 앱을 만들 수 있습니다. Flutter 프레임워크는 많은 것을 파괴할 필요가 없고 많은 다른 컴포넌트를 재사용할 수 있기 때문입니다. 하지만 이것도 여전히 이론처럼 느껴집니다. 실제 코드를 보지 않았으니까요. 그래서 한 단계 더 내려가서 Flutter 프레임워크의 소스 코드를 실제로 살펴보겠습니다. Flutter 앱을 만들 때 가장 간단한 앱은 무엇일까요?
9:15 - 10:33
앤드류: 처음에 보여준 것과 같은 앱입니다. 텍스트만 있고 Center 객체도 없습니다. 단일 위젯만 있습니다. RichText가 있습니다. Flutter가 앱을 시작할 때 runApp 함수를 호출합니다. runApp에 전달한 위젯을 트리의 루트에 놓습니다. 이제 위젯 트리가 생겼습니다. 루트에 단일 위젯만 있고 그게 전부입니다. 다음으로 Flutter 프레임워크는 위젯에게 렌더 오브젝트를 만들라고 요청합니다. 죄송합니다, 엘리먼트를 먼저 만듭니다. 이 경우에는 LeafRenderObjectElement라고 합니다. 다음으로 Flutter는 엘리먼트에게 렌더 오브젝트를 만들라고 요청합니다. 이때 위젯은 해당 위젯을 그리는 데 필요한 모든 구성을 전달합니다. 이 경우에는 텍스트이므로 여러 텍스트 관련 속성이 있습니다. 이제 위젯 트리가 생겼습니다. RichText 위젯, LeafRenderObjectElement (자식이 필요 없음), 그리고 실제로 그리기를 하는 Render Paragraph가 있습니다.
10:33 - 12:42
매트: 지금까지 배운 것은 우리가 엘리먼트 클래스 이름 짓기에 어려움을 겪는다는 것입니다. 이제 우리는 정확히 같은 트릭을 사용할 것입니다. 앱을 가져와서 runApp을 두 번 호출할 것입니다. 두 경우 모두 Center와 그 아래에 RichText가 있습니다. 단, runApp을 두 번째 호출할 때는 RichText의 속성을 변경할 것입니다. 새 위젯 트리를 인스턴스화합니다. Center와 RichText가 있습니다. 이 두 중앙 위젯은 다릅니다. 위젯은 불변이며, 하나를 만들고 이제 두 번째를 만들었습니다. Flutter는 두 Center 위젯이 같은 런타임을 가지고 있으므로 재사용할 수 있다고 판단합니다. 두 RichText도 같은 런타임을 가지고 있으므로 트리의 다른 부분을 재사용할 수 있습니다. 그래서 오래된 위젯을 제거하고 새 위젯 트리를 넣습니다. 이 시점에서 update를 호출할 것입니다. 하지만 Center는 자식 외에 다른 속성이 없기 때문에 RenderPositionedBox는 변경되지 않습니다. 단일 자식 엘리먼트도 변경되지 않습니다. 새 것을 넣었지만 이 시점에서 렌더 트리와 엘리먼트 트리는 그대로 유지됩니다. 이제 RichText를 연결합니다. 여기서 update RenderObject를 호출할 것이고, 렌더 단락
12:42 - 13:31
매트: 지금까지 우리가 배운 것은 엘리먼트 클래스 이름 짓기에 어려움을 겪는다는 것입니다. 이제 우리는 정확히 같은 트릭을 사용할 것입니다. 앱을 가져와서 runApp을 두 번 호출할 것입니다. 두 경우 모두 Center와 그 아래에 RichText가 있습니다. 단, runApp을 두 번째 호출할 때는 RichText의 속성을 변경할 것입니다. 새 위젯 트리를 인스턴스화합니다. Center와 RichText가 있습니다. 이 두 중앙 위젯은 다릅니다. 위젯은 불변이며, 하나를 만들고 이제 두 번째를 만들었습니다. Flutter는 두 Center 위젯이 같은 런타임을 가지고 있으므로 재사용할 수 있다고 판단합니다. 두 RichText도 같은 런타임을 가지고 있으므로 트리의 다른 부분을 재사용할 수 있습니다.
13:31 - 14:48
그래서 오래된 위젯을 제거하고 새 위젯 트리를 넣습니다. 이 시점에서 update를 호출할 것입니다. 하지만 Center는 자식 외에 다른 속성이 없기 때문에 RenderPositionedBox는 변경되지 않습니다. 단일 자식 엘리먼트도 변경되지 않습니다. 새 것을 넣었지만 이 시점에서 렌더 트리와 엘리먼트 트리는 그대로 유지됩니다. 이제 RichText를 연결합니다. 여기서 update RenderObject를 호출할 것이고, 렌더 단락이 업데이트됩니다.
앤드류: 맞습니다. 그런데 저는 뭔가가 변경되기를 원합니다. 매트, 여러 위젯이 있을 때 뭔가 변경되면 어떻게 되나요?
14:48 - 16:14
매트: 지금까지 우리는 엘리먼트 클래스 이름 짓기가 어렵다는 것을 배웠습니다. 이제 우리는 이전과 정확히 같은 트릭을 사용할 것입니다. 앱을 가져와서 runApp을 두 번 호출할 것입니다. 두 경우 모두 Center와 그 아래에 RichText가 있지만, 두 번째 runApp 호출에서는 RichText의 속성을 변경할 것입니다.
새 위젯 트리를 인스턴스화합니다. Center와 RichText가 있습니다. 이 두 Center 위젯은 서로 다릅니다. 위젯은 불변이며, 하나를 만들고 이제 두 번째를 만들었습니다. Flutter는 두 Center 위젯이 같은 런타임 타입을 가지고 있으므로 재사용할 수 있다고 판단합니다. 두 RichText도 같은 런타임 타입을 가지고 있으므로 트리의 다른 부분을 재사용할 수 있습니다.
16:14 - 17:33
Flutter는 이전 위젯을 제거하고 새 위젯 트리를 넣습니다. 이 시점에서 update 메서드를 호출합니다. 하지만 Center는 자식 외에 다른 속성이 없기 때문에 RenderPositionedBox는 변경되지 않습니다. 단일 자식 엘리먼트도 변경되지 않습니다. 새 위젯을 넣었지만 이 시점에서 렌더 트리와 엘리먼트 트리는 그대로 유지됩니다.
이제 RichText를 연결합니다. 여기서 update RenderObject를 호출할 것이고, 렌더 단락이 업데이트됩니다. 이 예제를 통해 우리가 이 세 가지 개념(위젯, 엘리먼트, 렌더 오브젝트)을 사용하는 이유를 알 수 있습니다. 이를 통해 한 UI 상태에서 다른 상태로 전환할 때 필요한 작업량을 최적화할 수 있습니다. 모든 것을 버리지 않고 가능한 한 많이 재사용하려고 합니다.
17:33 - 18:31
이는 여러 자식이 있을 때도 마찬가지입니다. 우리는 같은 과정을 거칠 것입니다. 깊거나 넓은 트리에서 다른 부분을 변경할 때도 같은 과정을 거치며, 현재 렌더 트리 상태에서 다음 상태로 가능한 한 빨리 전환하려고 합니다. 이를 통해 초당 프레임 수를 유지하고 최소한의 작업으로 목표를 달성할 수 있습니다.
이제 이론적인 부분을 마무리하고 실제 데모를 살펴보겠습니다. 피츠, 우리가 무엇을 데모할 건가요?
앤드류: 네, 제가 이유 없이 자리를 비운 게 아닙니다. 실제 코드를 보여드리기 위해 왔습니다. 지금까지는 슬라이드에만 집중했고 실제로 일어나는 일을 많이 보지 못했습니다. 그래서 지금 그것을 살펴보겠습니다.
이 내용은 Flutter의 위젯 렌더링 과정과 최적화 전략에 대해 상세히 설명하고 있습니다. 특히 위젯, 엘리먼트, 렌더 오브젝트의 세 가지 개념이 어떻게 상호작용하여 효율적인 UI 업데이트를 가능하게 하는지 보여줍니다.
18:31 - 19:26
앤드류: 여기 또 다른 간단한 앱이 있습니다. 우리가 지금까지 다뤘던 간단한 앱과 매우 비슷해 보입니다. 텍스트와 이미지가 있고, 맨 아래에 버튼이 있습니다. 이 버튼을 클릭하면 텍스트와 이미지가 "Hello Flutter"에서 "Hello Dart"로 바뀌고, 다시 원래대로 돌아갑니다. 원하는 만큼 이 작업을 반복할 수 있습니다.
이 앱의 코드도 매우 간단합니다. 맨 위에 runApp으로 WidgetsApp이 있고, WidgetsApp에는 하나의 상태 있는(stateful) 요소가 있습니다. 이 상태 있는 요소도 많은 일을 하지 않습니다. 버튼이 있고, 버튼을 클릭할 때 전환되는 두 개의 트리가 있습니다. 여기서 볼 수 있듯이 이 두 트리는 거의 동일합니다. RichText가 있고, 이미지가 있으며, 그 사이에 보기 좋게 만들기 위한 약간의 공간이 있습니다.
19:26 - 21:18
이제 우리가 발표에서 배운 것을 기억해보면, 같은 타입의 새 위젯으로 일부 위젯을 교체할 때 RenderObject는 그대로 유지될 것으로 예상합니다. 이를 확인하기 위해 Dart 개발자 도구를 사용할 수 있습니다.
Dart 개발자 도구를 열어보겠습니다. 이 도구는 Flutter와 Dart 앱을 디버깅하고 분석하기 위한 브라우저 기반 도구 세트입니다. 왼쪽에 위젯 트리가 있고, 이는 우리가 예상한 것과 거의 정확히 일치합니다. 루트에 WidgetsApp이 있고, 이는 runApp으로 호출한 것입니다. 그리고 아래로 내려가면 RichText, 간격을 위한 SizedBox, 그리고 이미지가 있습니다.
매트, 부탁이 있습니다. RenderObject의 인스턴스 ID를 기록해주세요. 여기 RenderObject에 대해 일련의 숫자와 문자가 있는데, 이는 렌더 트리에서 해당 객체의 정확한 인스턴스를 나타냅니다. 각 위젯에는 RenderObject가 있습니다. RichText, SizedBox, 그리고 맨 아래에 이미지에 대한 것이 있습니다.
21:18 - 23:51
여기서 위젯의 모든 속성도 볼 수 있습니다. RichText에 "Hello Flutter"가 있고, 이는 앱에 표시되는 내용과 일치합니다. 이제 "switch tree" 버튼을 클릭하면 "Hello Dart"와 새 이미지가 표시됩니다. 트리를 새로고침하고 다시 확인해보면, RichText의 텍스트 속성이 이제 "Hello Dart"로 변경된 것을 확인할 수 있습니다.
RenderObject 인스턴스 ID를 다시 확인해보겠습니다. 매트, 우리가 기록한 노트와 비교해보면 어떤가요?
매트: 동일합니다.
앤드류: 그렇군요. RichText, SizedBox, 그리고 맨 아래의 이미지에 대한 RenderObject 인스턴스 ID가 모두 동일합니다. 하지만 아무것도 변하지 않은 것을 보는 것은 그다지 흥미롭지 않습니다.
이제 코드로 돌아가서 SizedBox 대신 Padding을 사용해보겠습니다. 코드를 변경하고 저장한 다음 "switch tree"를 클릭합니다. Flutter의 핫 리로드 덕분에 앱을 다시 시작할 필요가 없습니다. Dart 개발자 도구로 돌아가서 새로고침을 하면 위젯 트리가 Padding으로 변경된 것을 볼 수 있습니다.
다시 RenderObject를 확인해보겠습니다. 매트, RichText의 인스턴스 ID는 여전히 같나요?
매트: 네, 같습니다.
앤드류: 좋습니다. 하지만 Padding의 RenderObject는 완전히 새로운 것이죠?
매트: 네, 다른 RenderObject입니다. SizedBox와 Padding 위젯은 다른 런타임 타입이기 때문에 놀랍지 않습니다. Flutter는 어떤 타입의 RenderObject를 생성할지 몰랐기 때문에 기존 것을 파괴하고 새로운 요소와 RenderObject를 재구축해야 했습니다.
23:51 - 26:38
앤드류: 맞습니다. Padding RenderObject는 변경되었지만, 이는 완전히 다른 위젯이기 때문에 예상된 결과입니다. 그리고 이미지의 RenderObject는 여전히 동일합니다. 흥미로운 점은 이미지 자체는 변경되었지만 동일한 RenderObject를 사용하고 있다는 것입니다. Flutter는 단순히 다른 이미지 파일을 렌더링하도록 지시하고 있습니다.
이제 다시 "switch tree"를 클릭하고 위젯 트리를 새로고침해보겠습니다. SizedBox가 다시 돌아왔고, RichText의 RenderObject는 여전히 동일합니다. 하지만 SizedBox의 RenderObject를 확인해보면 이전과 완전히 다른 것을 볼 수 있습니다. Flutter가 Padding을 SizedBox로 교체할 때, Padding이 더 이상 필요하지 않다고 판단하고 모두 제거한 다음 SizedBox에 대한 새로운 요소와 RenderObject를 생성했기 때문입니다. 하지만 이는 괜찮습니다. 앱의 다른 모든 RenderObject와 요소들은 그대로 유지되었기 때문입니다.
결론적으로, 우리가 보여준 예제들은 꽤 간단했지만, 실제 Flutter 앱에서는 수백 개의 위젯이 여러 층으로 깊고 넓게 퍼져 있습니다. 전체 트리를 다시 만드는 것은 매우 비용이 많이 들기 때문에, Flutter는 이 세 가지 개념(위젯, 요소, RenderObject)을 사용하여 변경 사항을 처리하는 데 필요한 처리량을 최소화합니다.
다시 한 번 세 트리의 책임을 상기해봅시다:
1.
위젯은 UI를 구성합니다. 개발자가 작성하는 것이 바로 이것입니다.
2.
요소는 모든 것이 잘 작동하도록 관리합니다.
3.
RenderObject는 화면에 그리는 역할을 합니다.
이 세 가지 트리를 사용하는 것이 Flutter 앱에서 우리가 만족스러운 성능을 얻을 수 있는 이유입니다.