리액트로 로직 잘 설계하기

Lennon·2024년 7월 2일
48
post-thumbnail

들어가며

안녕하세요. 프론트엔드 개발자 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부터 시작해 내부 성능 최적화 설계를 잘 할 필요가 있을 것 같습니다.

이제 뷰로 표현할 데이터 형식을 정하고, 해당 데이터 형식을 통해 뷰로 표현까지 잘 마무리한 것 같아요.

웹 빌더에서 뷰만 존재하면 큰 의미가 없겠죠? 유저가 정말 본인의 웹을 쉽고 명확하게 빌딩할 수 있는 환경을 구축해야할 것 같습니다.

웹 빌더 개발

Hover 및 Click 매핑

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 제작

공통되는 에셋들이 많으니 공통되는 수정 로직이 많을 것으로 예상이 돼요.
custom hook을 활용해 모든 Slide 및 에셋에 쉽게 대응이 가능하도록 로직을 설계해야 할 것 같습니다.

타입스크립트를 사용한다면 타입을 정확하게 추론하는 게 중요합니다.
사용단에서 타입이 정확하게 추론되도록 아래와 같이 제네릭을 받는 Custom Hook을 정의하였습니다.

경험상 단일 책임 원칙에 따라 제네릭이 필요하지 않은 경우들이 있었기에, 로직 설계에 따라 custom hook에서 제네릭이 필요할 수 있고, 필요하지 않을 수 있을 것 같아요.

✅ 공통 Custom hook Slide별 반영

공통으로 만든 Custom hook을 각 Slide 별로 타입 추론을 위해 제네릭을 넣어주고, 필요한 수정 로직을 작성해줍니다.
이 과정에서 전체 Slide에 공통되는 수정 로직이 있다면 따로 모듈화를 진행해도 괜찮을 것 같아요.

✅ Slide Components에 반영

그럼 이제, Slide에서 해당 부분을 아까 정의한 에셋에 넘겨주면 간단하게 디자인 수정은 완료하였습니다.

타입 SDK 정의

기존 개발에는 프론트와 백엔드 간의 정확한 Api 타입을 위해 Swagger랑 연동된 api-type SDK만 존재했는데요.

해당 글에선 각 Section들의 타입과 위는 간소화하게 설명했지만 웹 빌더답게 Image, Text, Button, Flex 등 내부에는 정말 많은 디자인 요소들이 있고, 해당 부분에 대해 백엔드와 타입이 일치할 필요가 있었습니다.

그래서 이번엔 일반 플랫폼 타입들에 대해서도 SDK를 정의하고 함께 사용하고 있습니다.

해당 부분에서 장점과 단점이 있는 것 같아요.

장점
1. 백엔드와 프론트엔드간의 타입이 정확하다
2. 플랫폼 타입, Api 타입도 따로 있으니 프론트엔드 로직에서만 존재하는 타입이 아니라면 따로 타입을 선언할 필요가 없다.

단점
1. CI/CD를 모두 해놓긴 했지만, 급하게 수정이 이루어져야 할 때는 불편할 수 있다.

정도가 있을 것 같아요. 저는 이번 SDK 도입은 아주 만족했습니다.

마무리

이번 글에선 리액트로 로직 설계하기 주제로 개발했던 경험을 공유해 보았습니다.
앞서 말씀드린 것 처럼 더 좋은 방법들이 존재할 것 같아요. 혹시나 더 좋은 방법이 있다면 댓글 부탁드립니다. 🙂

긴 글 읽어주셔셔 감사합니다.
보시는 분들 모두 앞으로의 여정에서 더 큰 성과를 이루시길 응원하며 마무리하겠습니다.

profile
좋은 글을 쓰려 노력합니다. 제 경험이 누군가에게 도움이 되기를 바랍니다 🌊

6개의 댓글

comment-user-thumbnail
2024년 7월 8일

좋은 글 감사합니다! 많이 배워 갑니다~

1개의 답글
comment-user-thumbnail
2024년 10월 10일

안녕하세요! 저도 웹 빌더를 개발하고 입장이라 너무 반갑습니다 ㅎㅎ
궁금한 지점들이 있어서 질문 드립니다!

  1. 데이터 구조 설계

Flex 요소의 자식으로 node 배열을 가질 수 있고, node 배열에는 또 모든 하위 컴포넌트가 들어가도록 구현이 된 것 같아요. 저도 비슷한 고민을 하다가 상위 객체가 하위 객체를 포함하도록 구현하면 하위 객체(컴포넌트)가 리렌더링이 될 때 상위 요소도 무조건 리렌더링이 되는 구조가 되어서 다른 방식으로 구현을 했었어요.

Flex는 node 배열을 가지되, node에는 하위 컴포넌트 자체가 아니라 고유 id값만 포함하도록 했어요. 하위 컴포넌트가 변경되더라도 상위 컴포넌트에는 영향을 주지 않도록요.

작성자 분께서는 혹시 재귀적은 구조로 가되, 성능적인 부분을 고려해봐야한다고 글에도 남겨주셨는데 성능적인 문제를 어떻게 푸셨는지가 궁금합니다!

  1. Hover 및 Click 매핑

Hover나 Click의 경우, 하위 컴포넌트가 상위 컴포넌트와 동일한 width, height를 가지는 경우, 사용자가 어떤 요소에 Hover 혹은 Click 했는지 파악하기 어려울 것 같아요. 이런 부분은 어떻게 해소하셨을까요?

깨알 같이 제 글도 남기고 갑니다,,,
https://medium.com/catchtable/how-to-create-complex-web-pages-without-a-developer-5fe97483f4fa

2개의 답글