React 조건부 랩핑 컴포넌트 만들기

Deinal·2023년 4월 12일
0
post-thumbnail

개요

강형욱 선생님은 강아지에게 "강조되고 반복되는 소리"가 강아지를 불안하게 만든다고 하셨다. 똑같이 개발자에게도 반복되는 코드는 불안하게 만든다. 프로젝트를 진행하던 중 나를 불안하게 만드는 코드가 생겨 어떻게 해결했는지 기록하려 한다.

불편한 코드

{item.menues.map((menue => {
  if(menue.path) {
    return (
      <Link key={menue.key} href={menue.path} legacyBehavior>
        <div className={`nav-link ${router.pathname === menue.path && 'active'}`}>
          <div>
            <span className='nav_icon'>{menue.icon && menue.icon}</span>
          </div>
          <div>
            <span>{menue.label}</span>
          </div>
          <div>
            {menue.children && (<UpOutlined />)}
          </div>
        </div>
      </Link>
    )
  } else {
    return (
      <div key={menue.key} className={`nav-link ${router.pathname === menue.path && 'active'}`}>
        <div>
          <span className='nav_icon'>{menue.icon && menue.icon}</span>
        </div>
        <div>
          <span>{menue.label}</span>
        </div>
        <div>
          {menue.children && (<UpOutlined />)}
        </div>
      </div>
    )
  }
}))}

위 코드를 보면 menu.path 값이 있으면 Link 컴포넌트를 감싸고 없으면 그대로 렌더하는 로직을 작성하였다. 중복되는 태그를 없애기 위해 처음으로 생각한 벙법은 아래와 같다.

대안 1. 반복되는 코드를 함수로 분리한다.

const renderLinkContent = (path, icon, label, children) => {
  return (
    <div className={`nav-link ${router.pathname === path && 'active'}`}>
      <div>
        <span className='nav_icon'>{icon && icon}</span>
      </div>
      <div>
        <span>{label}</span>
      </div>
      <div>
        {children && (<UpOutlined />)}
      </div>
    </div>
  )
}
return (
  <div>
    {props.data.map((item, index) => (
      <Fragment key={index}>
      <div className='nav-category'>
        <span>{item.category}</span>
      </div>
      {item.menues.map((menue => (menue.path ? ( 
        <Link key={menue.key} href={menue.path} legacyBehavior>
          {renderLinkContent(menue.path, menue.icon, menue.label, menue.children)}
        </Link>) : (renderLinkContent(menue.path, menue.icon, menue.label, menue.children))
      )))}
      <div>
        <hr></hr>
      </div>
    </Fragment>
  ))}
 </div>
)

중복되는 소스는 제거가 되었지만,,, 몇 가지 불편한 생각은 떠나지 않았다. 첫 번째 renderLinkContent 함수를 굳이 만들 바에 차라리 컴포넌트로 분리하면 되지 않을까? 그리고 분리를 하는 게 맞나? 하는 생각이 먼저 들었고 두 번째는 코드가 아름답지 않고 지저분하게 보였다. 그래서 좀 더 괜찮은 방법이 없을까 고민하고 인터넷 검색을 하던 중 괜찮은 글을 보게 되어 적용하였다.

대안 2. ConditionalWrpper 컴포넌트

Conditional wrapping in React 글 링크

해당 글에서는 재사용 가능하게 ConditionalWrapper 컴포넌트를 만들고 Wrapper 컴포넌트에 비교 값과 래핑 되었을 때의 컴포넌트를 props로 넘겨준다.

export default function CondinalWrapper({condition, wrapper, children}) {
  return condition ? wrapper(children) : children 
}

위 코드를 적용하면 아래와 같이 재사용할 수 있는 깔끔하고 아름다운 코드로 바꿀 수 있다.

{item.menues.map((menue => (
  <CondinalWrapper
    condition={menue.path}
    wrapper={children => <Link key={menue.key} href={menue.path} legacyBehavior>{children}</Link>}
  >
    <div className={`nav-link ${router.pathname === menue.path && 'active'}`}>
      <div>
        <span className='nav_icon'>{menue.icon && menue.icon}</span>
      </div>
      <div>
        <span>{menue.label}</span>
      </div>
      <div>
        {menue.children && (<UpOutlined />)}
      </div>
    </div>
  </CondinalWrapper>
)))}

기존의 문제점이던 중복된 코드를 제거하였고 어디서든 재사용 가능한 방식이라는 점에서 좋았다.

Reference

Conditional wrapping in React

profile
궁금증이 많은 사람입니다.

0개의 댓글