1. 네이티브 HTML 속성과 리액트 Props의 차이점
HTML에서는 요소에 속성(attributes)을 직접 설정하면 해당 요소에 자동으로 적용된다.
예를 들어 <div id="main">처럼 작성하면 해당 div 요소는 main이라는 id를 갖게 된다.
하지만 리액트의 커스텀 컴포넌트에서는 이러한 자동 적용이 이루어지지 않는다.
컴포넌트에 props를 전달하더라도, 해당 props는 컴포넌트 내부의 실제 DOM 요소에 자동으로 적용되지 않는다.
2. 문제: 커스텀 컴포넌트에서 Props가 자동 적용되지 않음
리액트의 커스텀 컴포넌트(ex: <Section id="examples">)에 설정한 속성은 자동으로 내부 DOM 요소로 전달되지 않는다.
이는 리액트가 컴포넌트를 블랙박스로 취급하며, 컴포넌트 내부의 구현 세부사항은 외부에서 알 필요가 없다는 설계 원칙 때문이다.
예를 들어, Section 컴포넌트에 id="examples" prop을 설정했다고 해서, 그 내부의 <section> HTML 요소에 자동으로 id 속성이 설정되지 않는다.
props가 자동으로 전달되지 않을 때의 문제점:
1) CSS 선택자가 작동하지 않음 (예: 부모에서의 #examples 스타일이 적용되지 않음)
2) JavaScript 이벤트 핸들러가 연결되지 않음
3) 접근성(accessibility) 속성이 적용되지 않음
4) 데이터 속성(data attributes)이 누락됨
이러한 문제로 인해 컴포넌트 사용자가 의도한 대로 스타일링이나 기능이 적용되지 않을 수 있다.
3. 해결1: 구조 분해 할당을 통한 개별 Props 전달
커스텀 컴포넌트에서 받은 props를 내부 HTML 요소에 전달하는 기본적인 방법은 각 prop을 명시적으로 추출하여 전달하는 것이다.
ex)
function Section({ id, className, title, children }) {
return (
<section id={id} className={className}>
<h2>{title}</h2>
{children}
</section>
);
}
이 방식은 간단한 컴포넌트에서는 잘 작동하지만, 많은 props를 처리해야 하는 경우 비효율적이다.
3.1. 수동 전달 방식의 한계점과 확장성 문제
수동 props 전달 방식에는 여러 한계가 있다.
1) 모든 가능한 HTML 속성을 고려해야 함 (id, className, style 등)
2) 새로운 prop을 추가할 때마다 컴포넌트 코드를 수정해야 함
3) 코드가 장황해지고 유지보수가 어려워짐
4) 오류 발생 가능성이 높아짐
대규모 프로젝트에서는 이러한 수동 접근법이 지속 불가능하다.
4. 해결2: Forwarded Props 패턴 (Proxy Props)
4.1. Rest와 Spread 연산자
JavaScript의 Rest와 Spread 연산자(...)는 객체나 배열의 요소를 효율적으로 다루는 문법이다.
1) Rest 연산자
- 여러 요소를 모아서 하나의 요소로 압축
- 함수 매개변수에서 나머지 인수들을 배열로 모음
- 객체 구조 분해에서 명시되지 않은 속성들을 하나의 객체로 모음
- 배열 구조 분해에서 나머지 요소들을 배열로 모음
2) Spread 연산자
- 하나의 요소를 여러 요소로 펼침
- 배열이나 객체의 모든 요소를 다른 배열이나 객체에 복사
- 함수 호출 시 배열의 요소들을 개별 인수로 전달
- 객체의 모든 속성을 다른 객체에 복사
ex)
// 객체에서 Rest - name은 "John", restInfo에는 {age: 30, job: "developer"}가 담김
const { name, ...restInfo } = { name: "John", age: 30, job: "developer" };
// 배열에서 Rest - first는 1, rest에는 [2, 3, 4, 5]가 담김
const [first, ...rest] = [1, 2, 3, 4, 5];
// 객체에서 Spread - 결과: { name: "John", age: 30, job: "developer", country: "Korea" }
const newPerson = { ...person, country: "Korea" };
// 배열에서 Spread - 결과: [1, 2, 3, 4, 5, 6]
const newArray = [...[1, 2, 3], ...[4, 5, 6]];
4.2. Rest 문법을 통한 Props 수집
Rest 문법을 사용하면 명시적으로 구조 분해한 props 외에 나머지 모든 props를 하나의 객체로 수집할 수 있다.
ex)
function Section({ title, children, ...restProps }) {
// title, children은 별도로 사용
// 나머지 모든 props는 restProps 객체에 모임
}
여기서 ...restProps는 title과 children을 제외한 모든 props를 포함하는 객체이다.
4.3. Spread 문법을 통한 Props 전달
Spread 문법을 사용하면 수집된 props를 내부 요소에 쉽게 전달할 수 있다.
ex)
function Section({ title, children, ...restProps }) {
return (
<section {...restProps}> {/* id, className 등 모든 props가 section에 적용됨 */}
<h2>{title}</h2>
{children}
</section>
);
}
이 방식은 컴포넌트 내부에서 직접 사용하지 않는 모든 props를 내부 DOM 요소로 자동 전달한다.
4.4. Forwarded Props 패턴의 장점
1) 코드 간결성 : 모든 가능한 prop을 명시적으로 전달할 필요가 없음
2) 유연성 : 컴포넌트 사용자가 원하는 표준 HTML 속성을 자유롭게 사용 가능
3) 유지보수성 : 새로운 속성 지원을 위해 컴포넌트 코드를 수정할 필요 없음
4) 표준 준수 : HTML 표준 속성을 그대로 사용할 수 있어 직관적임
5. Props 필터링
5.1. Forwarded Props 패턴의 주의사항
1) 내부 컴포넌트에 적합하지 않은 props가 전달될 수 있음
2) props 충돌이 발생할 수 있음 (예: 수동으로 설정한 className과 전달된 className)
3) 디버깅이 어려울 수 있음 (어떤 props가 어디서 왔는지 추적하기 어려움)
5.2. Props 필터링
위와 같은 이유로 때로는 모든 props를 그대로 전달하지 않고, 특정 props만 필터링하여 전달해야 할 수도 있다.
그럴 때 Props 필터링을 사용할 수 있다.
ex)
function Button({ variant, size, ...restProps }) {
// variant와 size는 내부적으로 클래스명 생성에 사용
const className = `btn btn-${variant} btn-${size}`;
// className은 병합하고 나머지 props는 그대로 전달
return <button className={className} {...restProps} />;
}
이렇게 하면 variant와 size는 DOM에 직접 속성으로 전달되지 않고, 내부적인 로직에만 사용된다.
'학습 > React' 카테고리의 다른 글
useRef (0) | 2025.03.23 |
---|---|
React 컴포넌트에서 변수와 함수의 스코프 차이 (0) | 2025.03.07 |
리액트 기초 - 강의 정리 2 (0) | 2025.02.25 |
리액트 기초 - 강의 정리 1 : JSX, 컴포넌트, 렌더링 (0) | 2025.02.21 |
useCallback (1) | 2025.01.30 |