![[Flutter] SizedBox를 Padding 대신 사용하지 말아야 하는 이유](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F47s7G%2FbtsMci0EE6p%2FO3CDkerNEqWsRclqIqQsKK%2Fimg.png)
개요
Flutter로 앱 개발을 하다보면 `Padding`과 `SizedBox`를 굉장히 많이 사용하게 된다. `Padding`은 이름 그대로 위젯에게 padding, 즉 간격을 주는 위젯이기에 많이 사용되고, `SizedBox`는 `Padding`과 같은 기능을 함과 동시에 위젯의 위치를 잡는다거나 빈 공간에 위젯을 미리 할당하는 식으로 여러 방식으로 활용이 가능하여 많이 사용된다.
그런데 `SizedBox`가 아무리 `Padding`과 똑같은 기능을 한다고 해도, 코드를 작성하는 부분이나 성능적인 부분에서 차이가 발생하게 된다. 그렇다면 둘 중에 어떤 부분이 더 좋다고 할 수 있을까? 물론 어느 하나가 무조건 더 좋다고는 말 할 수 없지만, `SizedBox`를 `Padding` 대신 사용하는 것을 지양해야한다는 것이 필자의 생각이다. 그 이유를 여러가지 측면에서 확인해보자.
성능 측면, 반응형 측면 : 엄청나게 의미있진 않다
성능 측면 : 이를 뒷받침하는 수치는 없음
사실 성능적인 측면에서는 정확하게 밝혀진 것은 없다. `SizedBox` 대신에 `Padding`을 사용해야한다고 주장하는 사람들이 성능적인 비교 그래프나 빌드 시간에 대한 수치적인 정보를 가져오진 않는다. 이론적으로 `Padding`이 성능상 우위를 점한다는 주장일 뿐, 정확한 주장이라고 하긴 어렵다. `Padding`이 `SizedBox`보다 빠르다는 주장의 근거는 아래와 같다.
위젯 트리에 노드가 추가되어 위젯을 다시 빌드하는 시간이 증가한다. 하나의 위젯 내부에 있는 위젯의 총 개수가 늘어나 위젯을 순회하는 시간이 늘어난다.
그러나 이론적으로 `const` 키워드를 붙이게 되면 `SizedBox`를 재사용하게 되기에 `Padding`과 큰 차이가 나진 않게 된다. 또한 차이가 난다고 해도 수치적으로 유의미한 차이가 나지 않기에 사실 성능적인 측면에서는 큰 의미가 없다.
반응형 : SizedBox로 간격을 조절해도 충분히 반응형은 구현할 수 있음
`SizedBox`를 사용하는 것은 반응형 UI설계에서도 좋지 않긴 하다. `SizedBox`로 간격을 조정한다는 것은 고정된 값으로 간격을 조정한다는 것이다. 그 말은 즉슨 각 기기의 화면 크기에 따라 유동적인 크기 계산이 들어가지는 것이 아니라, 기기의 화면 크기를 고려하지 않고 고정된 크기로 렌더링이 들어가게 된다. 좁은 화면에서는 더 좁은 간격을, 더 넓은 화면에서는 더 넓은 간격을 주지 못하고 고정된 너비만을 제공하게 될 수 있다.
이 상황에서는 `Padding`을 사용하는 것도 그렇게 좋은 상황은 아니다. 그렇기에 `Spacer`나 `Flexible`을 사용하는 것을 권장한다. 결국엔 SizedBox와는 크게 관련없는 이야기가 된다. 게다가 `SizedBox`로 반응형을 구현하지 못한다는 것은 아니다. `SizedBox`를 `Padding`과 같이 간격을 목적으로 사용하면 고정된 간격을 가지는 반응형 UI는 구현이 가능하고, 이정도만 되어도 충분히 훌륭한 UI를 만들 수 있게 된다.
그렇기에 성능적인 측면에서나 반응형 구현에서의 측면에서나 말은 많지만 필자가 생각하기에는 크게 와닿거나 치명적인 이유는 아니다.
코드 작성 측면 : 가독성
압축적인 코드
필자가 생각하기에 `Padding`으로만 간격을 만드는 핵심적인 이유는 가독성이다. 그리고 이를 뒷받침해주는 이유는 `Padding`은 한 번에 4방향에 간격을 줄 수 있다는 것이다. 극단적인 예시로, 4방향 모두에 간격을 주려면 4개의 `SizedBox`가 필요하다. 그에 비해 `Padding`은 깊이는 한 단계 깊어지지만 하나의 위젯으로 해결이 가능하다. 이런 식으로, 개수를 깊이 하나로 압축해버릴 수 있기에 `Padding`을 사용하면 코드가 조금 더 간결해진다. ▼
/// SizedBox를 사용했을 때
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
const SizedBox(width: 20),
Column(
children: [
const SizedBox(height: 20),
CustomWidget(),
const SizedBox(height: 20),
],
),
const SizedBox(width: 20),
],
);
}
}
/// Padding을 사용했을 때
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(20),
child: CustomWidget(),
);
}
}
맥락 이해
아래와 같이 30씩의 간격을 둬야하는 뷰가 있다고 해보자. 그 간격을 `SizedBox`로 두게 되면 아래와 같이 코드가 작성된다. ▼
class CustomWidget extends StatelessWidget {
const CustomWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
const SizedBox(height: 30),
CustomPlatformCheckboxGroup(
...
),
const SizedBox(height: 30),
CustomImagePickerSection(
...
),
const SizedBox(height: 30),
CustomTextField(
...
),
]);
}
}
같은 간격이기에 `const`로 작성하면 메모리를 재사용하기에 성능적으로는 `SizedBox`를 하나만 사용하는것과 같다. 하지만 맥락 이해의 측면에서 봤을 때는 생각보다 명확하지 않다. 해당 `SizedBox`가 의미하는 것이 간격이라는 것은 어느정도 유추할 수 있지만, 사이에 껴있는 `SizedBox`는 어떤 방향을 기준으로 간격을 주는지 알기가 모호하다.
이런 경우 특정 `SizedBox`만 봐서는 이해할 수 없고, 전체 맥락을 봐야지 특정 `SizedBox`가 왼쪽과 위를 기준으로 간격을 주고자 했는지, 아래와 오른쪽을 기준으로 간격을 주고자 했는지 알 수 있게 된다. 코드 자체는 독립적이나, 이해에 있어서는 독립적이지 않게 된다. ▼
const SizedBox(height: 30), // <- 여기를 봐야,
CustomPlatformCheckboxGroup(
...
),
const SizedBox(height: 30), // <- 여기의 SizedBox가 어떤 방향의 간격을
CustomImagePickerSection( // 주고자 했는지 파악이 가능해진다.
...
),
const SizedBox(height: 30), // <- 또한 여기까지 봐야할 수 도 있다.
CustomTextField(
...
),
그에 비해 `Padding`은 맥락 이해 부분에서 자유롭다. 필자는 위젯을 만들 때 내부에 `padding` 프로퍼티를 꼭 넣는 편이다. 그래야 다른 뷰에서 사용할 때 `Padding`으로 감싸지 않아도 위젯 자체에서 padding을 넣을 수 있기 때문이다. (요즘에는 `padding`이라는 용어가 내부의 간격인지, 외부의 간격인지 구분하기 어려워서 flutter의 `Container`와 css에서 사용하는 단어인 `margin`이라는 용어로 대체하고자 한다.)
아래와 같이 내부에 `padding`을 넣거나 `Padding`으로 감싸거나 하면 해당 `padding`이 어떤 의미를 갖는지 독립적인 맥락으로 이해할 수 있게 된다. `EdgeInsets`의 프로퍼티들을 읽으면 그 맥락을 쉽게 이해할 수 있게 된다. ▼
class CustomWidget extends StatelessWidget {
const CustomWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
CustomPlatformCheckboxGroup(
padding: const EdgeInsets.only(top: 30),
...
),
CustomImagePickerSection(
padding: const EdgeInsets.only(top: 30),
...
),
CustomTextField(
padding: const EdgeInsets.only(top: 30),
...
),
]);
}
}
인지부조화
"그래봤자 둘이 비슷비슷해 보이는데?"
뷰에 들어가는 내용이 많지 않은 상황에서는 이렇게 구분하고자 하는 것이 의미가 없을 수 있다. 허나 아래와 같이 각 위젯의 프로퍼티가 많아지고, 뷰에서 보여주고자 하는 섹션들이 많아지면 사이사이에 낀 `SizedBox`가 가독성을 크게 해칠 수 있게 된다. ▼
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
CustomPlatformCheckboxGroup(
padding: const EdgeInsets.only(top: 30),
checkedPlatforms: state.selectedPlatformList,
callback: state.selectPlatform,
),
CustomImagePickerSection(
padding: const EdgeInsets.only(top: 30),
imageFileList: state.portfolioImageList,
removeImageFilecallback: controller.removeImageFile,
addImageFileCallback: controller.addImageFile,
state: state
.componentStates[PortfolioRegisterComponent.IMAGE_PICKER.name]!,
),
CustomTextField(
padding: const EdgeInsets.only(top: 30),
headerTitle: "제목",
controller: controller.titleController,
hint: "제목을 입력하세요",
width: double.infinity,
scrollPadding: scrollPadding,
focusNode: controller.titleFocusNode,
onSubmitted: (value) => controller.onSubmitted(),
),
]);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
const SizedBox(height: 30),
CustomPlatformCheckboxGroup(
checkedPlatforms: state.selectedPlatformList,
callback: state.selectPlatform,
),
const SizedBox(height: 30),
CustomImagePickerSection(
imageFileList: state.portfolioImageList,
removeImageFilecallback: controller.removeImageFile,
addImageFileCallback: controller.addImageFile,
state: state
.componentStates[PortfolioRegisterComponent.IMAGE_PICKER.name]!,
),
const SizedBox(height: 30),
CustomTextField(
headerTitle: "제목",
controller: controller.titleController,
hint: "제목을 입력하세요",
width: double.infinity,
scrollPadding: scrollPadding,
focusNode: controller.titleFocusNode,
onSubmitted: (value) => controller.onSubmitted(),
),
]);
}
}
`SizedBox`도 사용은 간격 때문에 하는 것이지만 결국엔 클래스이다. 해당 뷰에 요소들이 몇 개 있는 지를 생각할 때 우리는 뷰에 있는 컴포넌트들을 생각하지 간격을 생각하진 않는다. 우리는 하나의 클래스를 위젯이자 컴포넌트로 생각하기에 화면에 있는 요소들의 수와 뷰에 들어있는 클래스들의 수를 어느정도 동일시하게 생각한다.▼
![](https://blog.kakaocdn.net/dn/boaPD3/btsMcpFjiC7/kKVpYFO2XZ1DzUghSRd9Z0/img.png)
이럴 때 `SizedBox`는 그런 인지를 해치게 되는 주요한 요인이 된다. 3개의 클래스가 body영역에 있는 것을 기대하지만, `SizedBox`를 사용하게 되면 총 5개 혹은 그 이상의 클래스가 body영역에 존재하게 된다. `SizedBox`없이 클래스만 작성하게 되면 해당 뷰에 어떤 요소가 있는지를 확실하게 파악할 수 있다. ▼
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
Section1(
padding: const EdgeInsets.only(top: 30),
...
),
Section2(
padding: const EdgeInsets.only(top: 30),
...
),
Section3(
padding: const EdgeInsets.only(top: 30),
...
),
]);
}
}
/// 인지한 것보다 더 많은 클래스가 존재하게 된다.
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
Section1(
...
),
const SizedBox(height: 30),
Section2(
...
),
const SizedBox(height: 30),
Section3(
...
),
]);
}
}
마치며
앞의 여러가지 것들로 `SizedBox`대신 `Padding`을 써야하는 이유를 이야기했다. 몇은 너무나도 세세한 부분이라 억지스러운 부분도 없진 않으나 이런 세세한 부분들이 쌓이고 쌓여 피로가 되고 프로젝트 지체의 이유가 되기에 별 거 아닌거 같아도 중요하게 봐야한다고 생각한다.
물론 어디까지나 이건 현재의 나의 생각일 뿐이다. 다른 누군가나 나타나서 `SizedBox`를 써야하는 다른 더 좋은 이유를 제시하고 충분히 납득할 만한 이유라면 나의 의견을 고집하지 않고 다시 `SizedBox`를 사용할 의향이 있다. 하지만 아직까지는 나는 이런 이유들로 인해 `Padding`을 더 선호한다.
'Develop > Flutter' 카테고리의 다른 글
[Flutter][Issue] Flutter 카카오 로그인 릴리즈 키 (0) | 2024.12.29 |
---|---|
[Flutter][Widget][Issue] TabBar 왼쪽에 공간이 생기는 현상 (0) | 2024.12.11 |
[Flutter][Error] Lexical or Preprocessor Issue (Xcode): 에러 해결 (0) | 2024.10.16 |
[Flutter] Event Bus 패턴 (0) | 2024.09.28 |
[Flutter] Dart는 싱글 스레드 언어 (0) | 2024.08.04 |