위젯이란
플러터에서 위젯이란 화면에 그려지는 객체를 의미합니다. 화면에 그려지는 모든 것은 모두 위젯이라고 생각하면 됩니다.
위젯은 상태가 있느냐 없느냐에 따라 Stateless Widget, Stateful Widget으로 나뉘게 됩니다. 여기서 상태란 위젯을 구현하기 위해 사용되는 정보입니다.
상태(state)
예를 들어 현재 시간을 알려주는 시계 역할을 하는 위젯이 있다고 생각해봅시다. 매 초마다 현재 시간은 변할태니 시계 위젯은 매 초마다 모양이 변해야겠죠? 이런 기능을 위젯에서 구현하려면 일단 현재 시간이라는 값을 위젯 내부에서 보관해야하고, 시간이 흐를 때마다 시계 침 위치를 변동시켜주어야 합니다. 여기서 현재 시간에 대한 값과 시계 침 위치에 대한 값이 상태라고 불리는 것들입니다.
그렇다면 상태가 필요없는 위젯은 무엇이 있을까요? 예를 들자면 이미지 위젯이 있겠습니다. 특정 이미지를 보여주는 위젯이 있다면 그 위젯은 한번 이미지를 그려놓으면 변할 일이 없으니 상태가 필요없습니다. 물론 이미지가 변한다면 얘기가 달라지겠죠.
Stateless Widget
위에서 설명했듯이 어떤 위젯이 상태의 변화가 불필요하다면 그 위젯은 Stateless Widget으로 사용하는 것이 효율 면에서 좋습니다. 물론 Stateless 라고 써있어서 그 위젯은 상태가 없다고 생각하시면 안됩니다. 모든 위젯에는 상태가 있습니다. 다만 그 상태가 특정 상황에 변할 수 있느냐 없느냐에 따라 나뉘는 것이죠. Stateless
widget도 상태는 있지만 그 상태는 변하지 않을 뿐입니다.
Stateful Widget
위젯에 상태가 있고, 그 상태가 변할 수 있으며 상태에 따라 위젯이 변한다면 Stateful Widget으로 사용해야 합니다. Stateful Widget에서는 초기 상태 값을 지정할 수도 있고 그 값이 변했을 경우 변화에 반응하도록 위젯을 설정할 수 있습니다. 다만 Stateful Widget은 Stateless Widget에 비해 비용이 비싸고 구조를 설계하는 데에 복잡해진다는 단점이 있습니다.
하지만 여러분이 플러터를 사용한다면 stateful Widget의 사용은 피할 수 없습니다. 일단 뭔가 그럴싸한 위젯을 만들려면 stateful Widget은 반드시 사용해야 하기 떄문이죠.
Widget LifeCycle
위젯은 위젯트리에 등록되어 화면에 그려지고, 위젯트리에 빠져서 화면에서 사라질 때까지 다음과 같은 생명 주기를 갖습니다.
먼저 위젯은 생성되면 가장 먼저 생성자가 호출됩니다. 그 다음 만약 위젯이 Stateful Widget이라면 내부적으로 createState 함수를 호출하는데, 이 함수는 상태 위젯을 관리하기 위한 플러터 엔진의 구조적 단계입니다. 사실 플러터 개발자 입장에서는 몰라도 큰 문제 없는 부분이여서 넘어가겠습니다.
그 다음에 위젯에서 사용하는 상태를 초기화하기 위한 함수인 initState()가 호출되는데요. initState()가 완료될 때까지 위젯은 그려지지 않습니다. 때문에 이곳에서 위젯에서 사용할 상태값 등을 초기화해주면 됩니다. 물론 비동기 함수를 Initstate 내부에서 사용한다면 build()가 호출되기 전에 작업이 완료되지 않을 수도 있기 때문에 이 경우, 후 처리를 생각해주어야 합니다.
initState()가 완료되면 위젯은 build()가 호출되며 화면에 그려지게 됩니다. build 함수는 모든 위젯이 구현하고 있는 함수이며, 화면에 그려지는 위젯을 반환 값으로 갖습니다. 즉 유저들은 build() 내부의 코드와 소통하는 것이죠.
Stateful Widget은 setState라는 특별한 함수를 가지고 있는데, setState함수를 호출하게 되면 위젯은 다시 build함수를 호출하게 됩니다. 즉, 화면에 다시 그려진다는 것이죠. 만약 여러분이 위젯의 상태가 변하여 다시 그려야 하는 상황이 온다면 이 setState함수를 호출하면 됩니다.
Stateless Widget은 상태 변화가 없기 때문에 initState함수도 없고 setState함수도 없습니다. 생성자로 전달된 값을 통하여 한번 build를 호출하고 계속 그 모습을 유지하죠.
마지막으로 위젯은 자신이 속한 페이지를 나가거나, 프로그램의 문맥 상 사라져야 한다면 dispose()함수를 호출하며 화면에서 사라지게 됩니다. 만약 해당 위젯에 메모리 할당 해제를 해줘야하는 작업이 필요하다면 이 dispose 함수 내부에서 수행해주면 됩니다.
요약하자면 이렇습니다.
위젯의 탄생 : Constructor -> initState(Stateful Widget만)
위젯의 활동 : build
위젯의 죽음 : dispose
WidgetTree
지금까지 하나의 위젯에 대해서 설명을 드렸는데요. 실제로 플러터 앱은 페이지가 쌓여있고 그 페이지 내부에 위젯 트리가 형성되어있는 구조로 설계되어집니다.
다음과 같이 위젯트리에 Page2가 등록되었다고 가정해보겠습니다. Page1에서 라우팅을 하여 Page2로 이동한 상황입니다.
Page2에는 최상위 위젯으로 Scaffold가 있고 자식 위젯으로 Column이 있으며 그 밑에 4개의 커스텀 위젯이 있습니다.
Page2에 들어가게 되면 처음 위젯트리에 등록되는 것은 Scaffold 위젯입니다. Scaffold의 initstate 함수가 호출될 것이고 build가 진행되겠죠.
Scaffold의 build가 진행되며 완료되기 전에 Column 위젯이 위젯트리에 등록됩니다. Column 위젯은 Scaffold의 자식이니까요.
마찬가지로 Column 위젯은 initstate 함수가 호출되고 build를 호출할 것입니다. 그 후 Column 위젯의 build가 완료되기 전에 Widget01 ~ Widget04이 위젯트리에 등록됩니다.
마지막으로 Widget01~04들은 각각 initstate 함수를 호출하고 build함수를 호출하게 됩니다.
모든 위젯은 자신의 build가 완료되기 전에 자식의 build가 완료되어야 합니다.