본문 바로가기

나만의 모음집

리액트의 다양한 활용 팁을 정리하였습니다. 모르셨다면 하나 알아가세요.

반응형

해당 포스트는..

해당 포스트는 리액트를 사용하면서 알게된 팁이나 주요하다고 판단되는 활용 예제를 만들어 정리 후 모아두는 공간입니다. 참고한 자료가 있으면 하단의 참고문헌에 모아둘 예정입니다. 

 

해당 포스트는 추가되는 내용이 있으면 관리 효율성을 위해 그 날을 기준으로 갱신되어 업로드 됩니다.

 

동적으로 래퍼 컴포넌트를 생성하는 팁

보통 재사용성을 높이기 위한 방식으로 Card 와 같은 컴포넌트를 만듭니다. Card 컴포넌트의 래퍼가 되는 태그는 ul 이 될 수도 있고, div 가 될수도 있고, article 이 될 수도 있죠.

 

그런데 이러한 경우에는 정적이기 때문에, 개발자가 직접 해당 컴포넌트에 들어가서 수정하지 않는 이상은 불변한 상태에 있게 됩니다. 하지만, 리액트는 한 가지 특별한 동작이 있습니다. Container 와 같이 대문자로 시작하는 것을 리액트 컴포넌트로 인지한다는 사실입니다.

 

이러한 원리를 이용하면, 동적 컴포넌트를 아주 손쉽게 만들 수 있습니다. 그럼 이를 같이 만들어 봅시다.

Card 컴포넌트에 container prop 을 전달해봅시다.

우선 Card 컴포넌트가 있다고 가정하고, 해당 컴포넌트의 prop 으로 container='div' 를 전달해줍니다.

<Card container='div'>
  <p>마이 신용 카드</p>
</Card>

 

Card 컴포넌트 내부에서 const Container 변수에 prop 을 할당합시다.

그 다음에는 prop 으로 전달한 container 를 const 키워드로 선언한 Container 에 할당해준 후, 해당 변수를 {children} 에 감싸줍니다.

export default function Card({container, children}) {
   const Container = container
	return (
    	<Container>
          	{ children}
        </Container>
    )
}

 

이 때 주의해야 할 점은 Container 는 컴포넌트로서 역할을 하기 때문에 재할당이 불가능하도록 let 이 아닌 const 키워드를 사용해주어야 한다는 점입니다.

 

이렇게 되면 Container 에는 앞서 'div' 가 전달되고, 리액트 에서는 해당 컴포넌트를 사용자 정의 컴포넌트로 인식하게 됩니다. 또한, div는 내부적으로  Container 컴포넌트가 반환하는 리액트 요소로 보고, 실제 DOM 에 반영 시에는 리액트 요소를 HTML 요소로 변환하는 과정을 선언적으로 처리한 후 화면에 그려줍니다. 

 

결과적으로 prop 으로 전달하는 문자열이 div, ul, li, ... 등의 어떠한 값이라도 해당 문자열을 기반으로 HTML태그를 생성하여 필요에 따라 다양한 래퍼 컴포넌트를 재사용할 수 있게 되었습니다.

 

타입스크립트를 사용한다면?

사실 앞서 방식은 타입스크립트를 사용하지 않는 경우에 적용하기 쉽지만, 타입스크립트를 사용하는 경우에는 별도의 타입을 지정해주어야 합니다.

 

아래 로직은 제가 NextJS 프로젝트에서 실제로 사용한 로직을 가져와 봤습니다. 여기서 elemetName 과 Container 변수의 타입을 React.ElementType 으로 지정한 것을 알 수 있습니다. 이렇게 해야 타입스크립트는 해당 컴포넌트와 요소를 리액트 요소로 인지할 수 있게 되므로 타입에러가 발생하지 않습니다.

import { HTMLAttributes, ReactNode } from "react"

interface PropsType  extends HTMLAttributes<HTMLElement>  {
    elementName:  React.ElementType
    children: ReactNode
}

export default function ButtonContainer({ elementName, children, ...props }: PropsType) {
    const Container: React.ElementType = elementName;
    return (
        <Container {...props}>
            {children}
        </Container>
    )
}

 

 

나머지 매개변수(...prop)를  활용한 상속 꿀팁

이번에 리액트 프로젝트의 리팩터링을 하면서, 자주 사용되고, 중복되는 구조를 가지는 컴포넌트를 모듈화하는 작업을 하였습니다. 

 

이 중에서는 id 와 className 을 prop 으로 전달하여 바인딩해줘야 하는 컴포넌트도 있었는데, 매번 이를 받아서 처리하는 것이 귀찮은 작업처럼 느꼈졌습니다. 근데, 여기서 나머지 매개변수를 이용하면, 더 효율적으로 작업할 수 있지 않을까 생각했고, 다행히 적용이 되기에 이를 꿀팁으로 공유하고자 남겨둡니다.

 

Section 컴포넌트가 있다고 가정합시다.

우선 App 컴포넌트 내부에 Section 이라는 모듈화된 컴포넌트가 있다고 가정해봅시다. 해당 컴포넌트는 id 와 className 을 상속받고 있습니다.

import React from 'react';
import Section from './Section';

const App = () => {
  return (
    <Section id="my-section" className="section-class">
      This is the content of the section.
    </Section>
  );
};

export default App;

 

그리고 이를 상속받은 Section 컴포넌트는 내부적으로 아래와 같이 id 와 className 을 받아서 각각 section 태그의 id와 className 에 넣어주고 있습니다.

<section id="my-section" className="section-class">
  마이 섹션
</section>

 

그런데 이렇게 할 필요가 없습니다.

 

...prop 을 사용하면 간편하게 바인딩할 수 있습니다.

바로 아래와 같이 {...props} 을 Section 컴포넌트의 매개변수로 전달해주면, ...props 의 내부에는 { id:' ', className:'' } 이 들어 있게 됩니다. 그리고 이를 section 태그의 속성 자리에 { ...props  } 를 전달해주면, 그 자리에 id 와 className 속성이 JSX 문법에 맞게 해석 되어 바인딩되게 됩니다.

const Section = ({...props}) => {
  return (
    <section {...props}>
      마이 섹션
    </section>
  );
};

export default Section;

 

타입스크립트를 사용한다면??

...prop 은 결국 나머지 매개변수로 전달받은 요소를 다시 ... 전개 연산자로 풀어주는 것이기 때문에, prop 으로 전달받은 속성들이 어떤 역할을 하는지 그 타입을 명시해주어야 합니다. 저 같은 경우에는 extends 를 통한 상속을 활용하여, ...prop 으로 전달받은 속성들이 HTML Element의 속성임을 명시해주었습니다.

import { HTMLAttributes, ReactNode } from "react"

interface PropsType  extends HTMLAttributes<HTMLElement>  {
    elementName:  React.ElementType
    children: ReactNode
}

export default function ButtonContainer({ elementName, children, ...props }: PropsType) {
    const Container: React.ElementType = elementName;
    return (
        <Container {...props}>
            {children}
        </Container>
    )
}

 

만약에 Input 태그에 대한 ...props 를 활용하는 경우에는 다음과 같이 InputHTMLAttributes<HTMLElement> 를 상속하여 사용할 수 있습니다. 참고로 ref 를 forwardRef 없이 넘기고 있는데, 이는 NextJS14 버전 이상에서는 리액트19 에서 제공하는 기능을 사용할 수 있기 때문입니다. 19 버전 부터는 ref 를 일반적인 prop 으로 전달하여 사용할 수 있게 됩니다.

import { ChangeEventHandler, InputHTMLAttributes, RefObject } from "react"

interface PropsType extends InputHTMLAttributes<HTMLElement> {
    value?: string
    ref?: RefObject<HTMLInputElement>
    onChange?: ChangeEventHandler<HTMLInputElement>
}

export default function Input({
    value,
    ref,
    name,
    onChange,
    ...props
}: PropsType) {
    return (
        <input
            ref={ref}
            defaultValue={value}
            onChange={onChange}
            {...props}
        />
    )
}

 

컴포넌트의 재사용성을 최대화 하고 싶다면, 이렇게 해보세요.

코드 리팩터링을 하면서, 컴포넌트의 재사용성을 높이는 다양한 방법이 있다는 사실을 알게 되었는데, 그중에서  JSX 슬릇을 이용하면 이를 최대화할 수 있습니다.

 

어디서든 사용이 가능한 Card 컴포넌트

현재 어느 UI 에서든 재사용이 가능한 Card 컴포넌트가 있습니다. 하지만, 같은 디자인이라도 내부에 들어가는 구조와 내용은 달라질 수 있습니다. 예를 들어, 다음과 같이 Card 컴포넌트의 prop 으로 header, body, footer 를 전달한다고 가정해봅시다.

// App 컴포넌트
const App = () => {
  return (
    <Card
      header={<h1>This is the header</h1>}
      body={<p>This is the body content</p>}
      footer={<button>Click me</button>}
    />
  );
};

export default App;

 

이 경우 Card 컴포넌트 내부에서는 아래와 같이 구조분해할당하여 각 슬릇에 할당함으로써 요구되는 사항에 따라 서로 다른 내용을 렌더링하는 카드 컴포넌트를 만들 수 있습니다.

import React from 'react';

// Card 컴포넌트
const Card = ({ header, body, footer }) => {
  return (
    <div className="card">
      <div className="card-header">
        {header}
      </div>
      <div className="card-body">
        {body}
      </div>
      <div className="card-footer">
        {footer}
      </div>
    </div>
  );
};

 

물론 이는 한 가지 예시에 불과합니다. 활용에 따라서는 <div className="card-header"> ~ </div> 자체를 {header} 로 지정하고 해당 card-header 클래스가 입력된 요소 자체를 Card 컴포넌트의 prop 으로 전달하여 보다 동적이면서도 재사용성을 최대한 높이는 방식으로 응용이 가능합니다.

 

이미지는 public 에 둘까요? assets 에 둘까요?

public 폴더에 넣는 경우 | 파비콘 같은 공개적으로 접근해도 괜찮은 이미지이면서 최적화되지 않아도 가벼운 이미지

보통 public 폴더에 들어가는 이미지들은 localhost:3000/image.png 형식으로 바로 접근이 가능합니다. public 폴더에 있는 파일들은 웹펙 등의 도구를 통해서 빌드 과정을 통해 최적화나 난수화 등의 처리가 되지 않기 때문에, 공개적으로 접근이 가능한데, 여기에 두는 image 파일의 경우에는 이렇게 공개적으로 접근해도 괜찮은 파일들 (예를들어, 파비콘이 있습니다.)을 보관해두면 됩니다. 다만, 최적화가 전혀되지 않기 때문에, 너무 큰 파일들을 보관해두면 빌드 파일의 크기가 커지게 되므로 유의해야 합니다.

 

즉, 본인이 판단했을 때 공개적으로 접근이 가능하고, 아무런 변형(최적화, 난수화 등의 처리)이 없어도 무방한 파일들을 보관해두면 됩니다.

 

assets 폴더에 넣는경우 | App 과 같은 컴포넌트 내부에서 사용되는 이미지

반대로 assets 폴더에 들어가는 이미지들은 빌드 과정을 통해서 변형이 이루어지기 때문에, 공개적으로 접근이 불가능합니다. 즉, localhost:3000/src/assets/image.png 로 접근이 불가능하다는 의미입니다. 

 

해당 이미지들은 빌드 과정에서 인식되어 최적화되고, 웹 사이트에 제공하기 직전에 public/ 폴더로 삽입되기 때문에, 가져온 이미지는 참조한 위치에서 자동으로 링크가 생성되어 사용됩니다. 

 

따라서 App 과 같은 컴포넌트 내부에서 사용되는 이미지들은 일반적으로 src/assets 폴더에 저장하는 것이 좋습니다.

 

참고자료

【한글자막】 React 완벽 가이드 2024 with React Router & Redux

반응형