안녕하세요. 프론트엔드 개발자 Lennon입니다.
최근에 토스 유튜브 영상: "가독성 좋은 코드"란 무엇일까? 에서 프론트엔드 리드 세 분께서 이야기하는 걸 보았는데, 신입 및 주니어가 가장 많이 하는 실수가 빠르게 만드는 거라고 하더라구요. 아키텍처 및 코드 품질이 아쉬울 확률이 크다고.. 저를 보는 것 같더라구요. 🥲
스트리밍 리팩토링
단어도 재밌었습니다. 한 번 봐보셔도 좋을 것 같아요.
프론트엔드를 포함해 현업 개발자라면 사내에서 사내 제품 개발을 꾸준히 진행하실 것 같은데요,
저도 2년 동안 두 곳의 회사에서 개발을 진행하며 정말 다양한 기능에 대해 아키텍쳐 설계부터 코드 레벨 구현까지 정말 많은 기능들을 개발해온 것 같아요.
요즘엔 무턱대고 개발을 진행하는 것보단 주어진 기능에 대해 아키텍처, 구현, 성능 개선, 리팩토링을 포함해 비즈니스적인 데드라인도 함께 고려해서 개발을 진행하려 노력하고 있습니다.
데드라인과 각각의 과정들의 일정을 산출하는 건 항상 어려운 것 같아요. 그만큼 설계의 중요성을 많이 깨닫고 있어요.
해당 글에선 최근 사내 제품 개발에서 웹 빌더와 관련해 데이터 구조부터 아키텍쳐 설계, 설계에 따른 리액트 로직을 구현한 경험에 대해 이야기해 보려고 합니다.
만약 글을 읽으셨고, 조금 더 좋은 접근법이 떠오르신다면 댓글 써주시면 감사드리겠습니다 🙂
웹 빌더 개발에 대한 요구사항이 왔다고 생각해 봅시다.
각각의 Section들이 있고, 각 Section들은 Section
, Flex
, Text
, Image
, Button
요소들이 함께하고 있다고 가정하겠습니다. 아래처럼요.
위는 간단한 Section이고, 정말 복잡한 다른 Section들의 종류가 10가지 이상 존재합니다.
위의 구조는 아래와 같이 될 것 같아요.
// Section 1
Flex
- Text
- Text
- Button
- Flex
- Image
- Image
만약 Section 내의 Flex가 복잡하게 얽혀있으면 아래와 같이 복잡한 형태도 가능했어요.
// Section 2
Flex
- Text
- Button
- Flex
- Text
- Flex
- Flex
- Text
- Button
- Flex
- Text
- Button
..
..
..
이 모든 뷰를 프론트에서 직접 만드는 건 아무리 봐도 비효율적인 것 같아요. 또한, 직접 만들다 Section이 일부 수정되기라도 하면 유지보수하기가 복잡할 것 같아요.
해당 시점에 좋은 코드를 작성하기 위해 로직 설계가 필요했습니다.
이제 위의 페인 포인트를 해결하기 위해 데이터 구조를 함께 고민해 보면 좋을 것 같아요.
앞서 말씀드린 것처럼 Section
, Flex
, Text
, Image
, Button
요소들만 존재합니다.
설계한 구조를 말씀드리면 비즈니스적 데드라인을 맞추기 위해 node
형태의 재귀 컴포넌트를 목적으로 데이터 구조 설계를 시작했습니다.
위 요소를 보면 Flex 일 때만 Node가 존재합니다.
즉 위의 요소들은
// Section 1
node: [
Text,
Text,
Button,
node: [
Image,
Image
]
]
// Section 2
node: [
Text,
Button,
node: [
Text,
node: [
node: [
- Text
- Button
]
node: [
- Text
- Button
]
]
]
]
위와 같은 형식이 될 것 같아요.
이제 서버에서 넘어오는 데이터를 뷰로 만들어주는 재귀 컴포넌트를 구현하면 될 것 같습니다.
그전에 본인의 기능 구현을 위한 설계 및 로직에 대해 한번 더 분석해봐도 좋을 것 같아요. 논의할 동료가 있다면 결론 도출을 함께 해봐도 좋을 것 같습니다.
데이터 구조와 대략적인 컴포넌트 설계가 이루어졌으니 앞으로 만들 기능을 어떻게 설계할지 고민을 해볼까요?
저는 아래 정도의 고민들을 했었던 것 같아요.
이번 수정 로직에 전역 상태가 필요할지
데이터 구조에 따라 수정 로직들은 어떤 형태로 반영할 건지
공통된 요소들에 대해 어떻게 수정 로직을 구현할건지
재귀 컴포넌트로 개발했을 때 성능은 과연 괜찮을지
Hover 이벤트에 대한 성능이 괜찮을지
해당 설계가 추후 확장성에 이점이 있는지
물론 설계는 개발하면서도 계속 바뀌었습니다.
실제 개발을 하다보면 설계와 다르게 예상치 못한 것들을 마주치기 때문인데요 🥲
이제 설계가 끝났으니 구현을 하러 가볼까요?
데이터 구조를 보면 node만 배열 형식으로 되어있고 모든 node는 렌더링이 되어야 하는 요소들입니다.
NodeRenderer
컴포넌트는 node를 탐색하는 재귀 컴포넌트입니다.
내부 로직은 여러분들도 예측할 수 있을 것 같아요.
Text
, Button
, Image
를 만나면 렌더링 해주고, Flex
, 즉 node를 만나면 다시 NodeRenderer
를 호출해서 진행합니다.
이제 해당 컴포넌트를 만들었으니 데이터 구조에 따라 모든 Section에 대해 뷰를 구현할 필요 없이 서버에서 넘어오는 데이터만 반영해 주면 자동으로 뷰를 그려주니 편할 것 같아요.
하지만 재귀인 만큼 성능적으로는 문제가 생길 가능성이 크긴 하니, 렌더링이 최소화되도록 내부에 전달되는 Props부터 시작해 내부 성능 최적화 설계를 잘 할 필요가 있을 것 같습니다.
이제 뷰로 표현할 데이터 형식을 정하고, 해당 데이터 형식을 통해 뷰로 표현까지 잘 마무리한 것 같아요.
웹 빌더에서 뷰만 존재하면 큰 의미가 없겠죠? 유저가 정말 본인의 웹을 쉽고 명확하게 빌딩할 수 있는 환경을 구축해야할 것 같습니다.
Landwich 서비스는 아래와 같은 수정 요구사항이 있었어요.
요소에 마우스를 Hover 하면 해당 부분에 하이라이팅이 되어야 한다.
텍스트 및 버튼은 더블 클릭으로 텍스트 수정이 가능해야 한다.
좌측 Slide에서 사이즈, 정렬 등등 많은 CSS 요소들이 수정이 가능해야 하고, 정확하게 클릭한 요소랑 매핑이 되어야한다.
그래서 저희는 Hover 및 Click에 대응이 되도록 모든 요소에 고유 id 값을 가지도록 데이터 구조를 설계했습니다.
Section
, Flex
, Text
, Button
, Image
를 가진 모든 요소들은 각각의 고유한 id 값을 가지고 있어요.
또한, 이 부분도 Hover에 따른 성능 문제가 생기지 않도록 잘 설계해야할 것 같아요.
예로 아래와 같은 이슈도 있었답니다.
이젠 클릭한 요소에 대해 id
값을 가져올 수 있고, 그에 맞는 id
값을 찾아 매핑할 수 있었습니다.
이제 클릭한 요소에 대해 수정 로직만 구현하면 될 것 같아요.
위에 말씀드린 것처럼 CSS 수정할 수 있는 요소가 정말 많습니다.
우선 id 값 매핑을 통해 Text, Button, Image, Flex, Section Slide가 존재하니 모두 함께 사용할 에셋들을 먼저 만들면 좋을 것 같아요.
개인적으로 공통 에셋을 만들 때 중요한 건 서버 데이터를 정확히 파악하고, 에셋이 그에 완벽하게 대응하도록 내부 설계를 잘 하는 게 중요하다고 생각합니다.
가령 서버에서 padding 값이 아래 형식으로 오는데,
padding: {
top: number;
bottom: number;
left: number;
right: number;
}
에셋 사용단에서
<SettingPaddingBox
paddingTop={padding.top}
paddingBottom={padding.bottom}
paddingLeft={padding.left}
paddingRight={padding.right}
/>
위와 같이 활용하는 것보단
<SettingPaddingBox
padding={padding}
/>
이처럼 데이터 형식을 추상화해 활용하는 게 가독성 및 코드 파악에 이점이 있는 것 같아요.
이제 수정할 에셋들을 전부 정의해 줬으니, 실제 로직이 동작하도록 비즈니스 로직을 작성하러 가볼까요?
공통되는 에셋들이 많으니 공통되는 수정 로직이 많을 것으로 예상이 돼요.
custom hook을 활용해 모든 Slide 및 에셋에 쉽게 대응이 가능하도록 로직을 설계해야 할 것 같습니다.
타입스크립트를 사용한다면 타입을 정확하게
추론하는 게 중요합니다.
사용단에서 타입이 정확하게 추론되도록 아래와 같이 제네릭을 받는 Custom Hook을 정의하였습니다.
경험상 단일 책임 원칙에 따라 제네릭이 필요하지 않은 경우들이 있었기에, 로직 설계에 따라 custom hook에서 제네릭이 필요할 수 있고, 필요하지 않을 수 있을 것 같아요.
공통으로 만든 Custom hook을 각 Slide 별로 타입 추론을 위해 제네릭을 넣어주고, 필요한 수정 로직을 작성해줍니다.
이 과정에서 전체 Slide에 공통되는 수정 로직이 있다면 따로 모듈화를 진행해도 괜찮을 것 같아요.
그럼 이제, Slide에서 해당 부분을 아까 정의한 에셋에 넘겨주면 간단하게 디자인 수정은 완료하였습니다.
기존 개발에는 프론트와 백엔드 간의 정확한 Api 타입을 위해 Swagger랑 연동된 api-type SDK만 존재했는데요.
해당 글에선 각 Section들의 타입과 위는 간소화하게 설명했지만 웹 빌더답게 Image, Text, Button, Flex 등 내부에는 정말 많은 디자인 요소들이 있고, 해당 부분에 대해 백엔드와 타입이 일치할 필요가 있었습니다.
그래서 이번엔 일반 플랫폼 타입들에 대해서도 SDK를 정의하고 함께 사용하고 있습니다.
해당 부분에서 장점과 단점이 있는 것 같아요.
장점
1. 백엔드와 프론트엔드간의 타입이 정확하다
2. 플랫폼 타입, Api 타입도 따로 있으니 프론트엔드 로직에서만 존재하는 타입이 아니라면 따로 타입을 선언할 필요가 없다.
단점
1. CI/CD를 모두 해놓긴 했지만, 급하게 수정이 이루어져야 할 때는 불편할 수 있다.
정도가 있을 것 같아요. 저는 이번 SDK 도입은 아주 만족했습니다.
이번 글에선 리액트로 로직 설계하기
주제로 개발했던 경험을 공유해 보았습니다.
앞서 말씀드린 것 처럼 더 좋은 방법들이 존재할 것 같아요. 혹시나 더 좋은 방법이 있다면 댓글 부탁드립니다. 🙂
긴 글 읽어주셔셔 감사합니다.
보시는 분들 모두 앞으로의 여정에서 더 큰 성과를 이루시길 응원하며 마무리하겠습니다.
좋은 글 감사합니다! 많이 배워 갑니다~