현대 웹 개발의 영역에서, 컴포넌트 기반 아키텍처는 개발자들이 확장 가능하고 유지보수가 용이한 애플리케이션을 구축할 수 있게 하는 핵심 요소로 부상하였다. 이 아키텍처의 핵심에는 컴포넌트라는 개념이 자리잡고 있으며 기능을 캡슐화하는 고립되고 재사용 가능한 UI 조각들을 의미한다.
그러나 애플리케이션이 일관된 전체로 기능하기 위해서는 컴포넌트들이 서로 통신하고 데이터를 공유할 필요가 있다. 여기서 props가 컴포넌트 간 데이터를 전달하는 기본 개념으로 등장한다.
이는 컴포넌트의 재사용성을 향상시킬 뿐만 아니라 애플리케이션 내의 데이터 흐름을 관리 가능하고 예측 가능하게 만든다. 이 글에서는 props의 개념을 기본 사용법부터 처리 기법에 이르기까지 깊이 탐구하고 어떻게 구현되는지를 살펴볼 목표를 가지고 있다.
Props 이해하기
Props는 속성(properties)을 뜻하는 줄임말로 부모 컴포넌트로부터 자식 컴포넌트로 데이터의 흐름을 촉진한다.
Props란 무엇인가?
근본적으로 props는 부모 컴포넌트로부터 자식 컴포넌트로 전달할 수 있는 사용자 정의 속성이다. 컴포넌트 내부에서 관리되며 시간이 지남에 따라 변경될 수 있는 상태와 달리 props는 자식 컴포넌트의 관점에서 불변이다.
자식 컴포넌트가 부모로부터 받은 props를 수정할 수 없으며 오직 읽을 수만 있다는 것을 의미한다. 이 불변성 원칙은 예측 가능한 데이터 흐름을 보장하여 애플리케이션을 더 쉽게 디버깅하고 유지할 수 있게 한다.
Props는 숫자나 문자열과 같은 단순한 데이터 타입에 국한되지 않으며 함수, 객체, 배열 또는 다른 컴포넌트까지도 될 수 있어 애플리케이션 내 복잡한 데이터 구조와 상호작용을 관리하는 데 있어 매우 다재다능하다.
Props와 State 비교하기
Props와 상태는 모두 컴포넌트 기반 애플리케이션에서 데이터를 관리하는 데 필수적이지만 이 두 가지는 서로 다른 목적을 가진다.
- Props는 데이터와 이벤트 핸들러를 부모로부터 자식 컴포넌트로 전달하는 데 사용되며 단방향 데이터 흐름을 설정한다. 부모에 의해 설정되며 자식 컴포넌트에 의해 변경되지 않는다.
- 반면에, State는 컴포넌트 내부의 것으로 동작과 렌더링을 제어한다. Props와 달리 상태는 가변적이며 보통 사용자의 행동이나 시스템 이벤트에 응답하여 시간이 지남에 따라 변할 수 있다.
간단히 말해, 컴포넌트에 데이터를 전달하기 위해 props를 사용하고 컴포넌트 내부의 데이터를 관리하기 위해 state를 사용한다.
Props의 기본 사용법
React에서 Props 전달 및 접근하기
React에서는 props를 HTML 속성과 유사한 방식으로 컴포넌트에 전달한다.
다음은 자식 컴포넌트에 prop을 전달하는 간단한 예시다.
function ParentComponent() {
return <ChildComponent greeting="Hello, World!" />;
}
function ChildComponent(props) {
return <h1>{props.greeting}</h1>;
}
이 예시에서 ParentComponent
는 greeting
prop을 ChildComponent
에 전달하고, ChildComponent
는 이 prop을 접근하여 h1
태그 내에서 렌더링한다.
이는 props 사용의 기본 패턴을 보여준다. 데이터가 컴포넌트 트리를 통해 전달되고 자식 컴포넌트에서 렌더링이나 로직에 사용된다.
함수형 컴포넌트 vs 클래스 컴포넌트에서의 Props
React는 함수형 컴포넌트와 클래스 컴포넌트 두 가지 타입의 컴포넌트를 지원한다.
props를 전달하는 구문은 두 경우 모두 동일하지만, 컴포넌트 내에서 props에 접근하는 방식은 약간 다르다.
- 함수형 컴포넌트: 이전 예시와 같이 함수의 매개변수를 통해 props에 접근한다.
- 클래스 컴포넌트: 클래스의 메소드 내에서
this.props
를 통해 props에 접근한다.
class ChildComponent extends React.Component {
render() {
return <h1>{this.props.greeting}</h1>;
}
}
컴포넌트 간 데이터를 전달하는 통로로서 props가 어떻게 기능하는지, 불변성과 컴포넌트 타입에 따른 접근 방식의 구문 차이를 강조하는 스냅샷을 알 수 있었다.
Props 처리 심화
컴포넌트 기반 개발에서는 데이터 전달뿐만 아니라, 컴포넌트가 올바른 유형의 데이터를 받고 부모 컴포넌트에서 예상된 데이터가 제공되지 않을 때 예측 가능하게 동작하도록 하는 것을 포함한다.
기본 Props
기본 props는 부모 컴포넌트에서 제공되지 않는 props에 대해 기본값을 정의하는 방법이다.
이 기능은 예상된 props가 누락되었을 때 컴포넌트가 올바르게 동작하도록 하는 데 특히 유용하다.
React에서는 클래스 및 함수형 컴포넌트 모두에 대해 기본 props를 지정할 수 있다.
클래스 컴포넌트의 경우, defaultProps
객체를 할당할 수 있다.
class WelcomeMessage extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
WelcomeMessage.defaultProps = {
name: 'World',
};
함수형 컴포넌트의 경우, ES6의 기본 매개변수를 사용하여 동일한 효과를 달성할 수 있다:
function WelcomeMessage({ name = 'World' }) {
return <h1>Hello, {name}</h1>;
}
유효성 검증을 위한 Prop 타입 (Typescript)
Prop 타입은 컴포넌트가 올바른 유형의 props를 받는 것을 보장하는 메커니즘을 제공한다.
이는 애플리케이션의 데이터 흐름의 무결성을 유지하는 데 중요하며, 개발 중 버그를 잡는 데 도움이 될 수 있다.
React는 PropTypes를 통한 내장 prop 타입 유효성 검사를 지원했지만, 모던한 워크플로에서는 타입 검사를 위해 TypeScript를 사용하는 것이 일반적이다.
다음은 PropTypes를 사용한 예시이다.
import PropTypes from 'prop-types';
function WelcomeMessage({ name }) {
return <h1>Hello, {name}</h1>;
}
WelcomeMessage.propTypes = {
name: PropTypes.string,
};
TypeScript를 사용할 때는 다음과 같이 타입을 정의한다.
interface WelcomeMessageProps {
name: string;
}
const WelcomeMessage: React.FC<WelcomeMessageProps> = ({ name }) => {
return <h1>Hello, {name}</h1>;
};
Props와 함께 스프레드/레스트 연산자 사용하기
스프레드(...) 연산자는 특히 많은 수의 props를 다루거나, 모든 props를 명시적으로 나열하지 않고 컴포넌트를 통해 자식에게 props를 전달하고자 할 때 컴포넌트에 props를 전달하는 데 매우 유용할 수 있다.
예를 들어, 모든 가능한 버튼 속성을 명시적으로 정의하지 않고도 수용해야 하는 Button 컴포넌트가 있다고 가정해 보자.
function Button({ label, ...props }) {
return <button {...props}>{label}</button>;
}
이 방식은 컴포넌트를 더 유연하고 재사용 가능하게 만들어 다양한 곳에서 적응할 수 있다.
Best practice와 흔한 오류
Props의 불변성과 그 의미
Props는 데이터를 부모 컴포넌트로부터 자식 컴포넌트로 전달하는 데 사용된다.
자식 컴포넌트에서는 받은 props를 변경할 수 없으며, 이는 컴포넌트 기반 아키텍처의 기본 원칙에 반하는 행위이다.
// Props의 불변성 예시: React 컴포넌트
// 부모 컴포넌트
function ParentComponent() {
const message = "Message written by parent";
// ChildComponent에 message props를 전달
return <ChildComponent message={message} />;
}
// 자식 컴포넌트
function ChildComponent(props) {
// props.message를 변경하려고 하면 오류가 발생함
props.message = "Message written by child"; // 이 행위는 React에서 금지됨
return <h1>{props.message}</h1>; // 부모 컴포넌트로부터 받은 메시지를 출력
}
위 예시에서는 ChildComponent가 부모 컴포넌트로부터 받은 props를 변경하려고 하는 코드이다. React는 이를 허용하지 않으며, 이는 props의 불변성 원칙을 위반하는 행위이다. 따라서 자식 컴포넌트에서는 받은 props를 직접 변경할 수 없고, 오직 읽기만 가능하다.
Props의 불변성은 이런 단방향 데이터 흐름을 강제함으로써 애플리케이션 상태의 변화를 추적하고 디버깅하기 쉽게 만든다.
이러한 설계 결정은 데이터가 컴포넌트 트리를 따라 흐르고 상태 변화가 개별 컴포넌트 내부나 상태 관리 라이브러리에서 관리되는 예측 가능하고 이해하기 쉬운 코드 구조를 조성하는데 도움을 준다.
개발자들은 데이터 흐름과 상태 관리에 대한 명확한 전략으로 컴포넌트 디자인에 접근해야 한다. props는 부모로부터 자식 컴포넌트로의 통신을 위한 것이다.
컴포넌트 내에서 시간이 지남에 따라 변경되는 데이터는 내부 상태 관리를 사용해야 한다는 것을 인지하고 있어야 한다.
Context API와 상태 관리 라이브러리를 사용하여 Prop 드릴링 방지
Prop 드릴링이란 깊은 자식 컴포넌트가 해당 props를 필요로 하기 때문이 아니라, 여러 계층의 중첩된 컴포넌트를 통해 props를 전달하는 관행을 말한다. 이 관행은 컴포넌트 아키텍처를 복잡하게 만들고 코드를 유지 관리하고 이해하기 어렵게 만드는, 사용하지 않는 컴포넌트를 통해 props가 전달되는 얽힌 웹으로 빠르게 이어질 수 있다.
이 문제를 회피하기 위해, 개발자들은 React의 Context API와 같은 다른 프레임워크에서의 동등한 메커니즘을 사용하여 깊게 중첩된 컴포넌트에 데이터를 더 직접적으로 전달하는 방법을 제공할 수 있다. Context API를 통해 개발자들은 선호도, 테마 또는 현재 인증된 사용자와 같은 값을 컴포넌트 간에 명시적으로 prop을 통해 전달하지 않고도 공유할 수 있다.
Context를 생성함으로써, 컴포넌트는 이에 구독하고 필요한 데이터를 직접 받을 수 있으며, 이는 깊게 중첩될지라도 마찬가지다.
이 접근법을 통해 컴포넌트 구조를 단순화하고, 불필요한 prop 전달을 제거하며, 컴포넌트의 재사용성을 향상시킬 수 있다.
import { createContext, useContext } from 'react';
// 1. Context 생성
const UserContext = createContext();
// 2. Provider 컴포넌트
function UserProvider({ children, value }) {
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
// 3. 자식 컴포넌트에서 Context 사용
function ChildComponent() {
const user = useContext(UserContext); // Context를 통해 user 데이터에 접근
return <div>안녕하세요, {user.name}님!</div>;
}
// App 컴포넌트
function App() {
const user = { name: '홍길동', age: 30 };
// UserProvider를 사용하여 모든 자식 컴포넌트에 user 객체를 제공
return (
<UserProvider value={user}>
<ChildComponent />
</UserProvider>
);
}
한편 Redux와 같은 상태 관리 라이브러리는 애플리케이션 전체 상태를 관리하기 위한 더 강력한 솔루션을 제공한다.
이 라이브러리들은 애플리케이션의 어떤 컴포넌트에서든 접근할 수 있는 중앙 집중식 상태 저장소를 제공한다.
이를 통해 상태 업데이트 로직을 중앙집중화하고 상태 변경을 예측 가능하고 추적 가능하게 만듦으로써 (특히 대규모 애플리케이션에서) 상태 관리의 복잡성을 크게 줄일 수 있다.
흔하게 하는 실수들과 피하는 방법
컴포넌트 기반 개발에서 props는 필수적이지만 react로 개발하다 보면 빠질 수 있는 몇 가지 실수들이 있다.
그 중 하나는 props를 직접 변형하려는 시도로, 이는 props의 불변성을 위반할 뿐만 아니라 예측할 수 없는 컴포넌트 동작으로 이어질 수 있다. 이를 피하기 위해서는 항상 props를 읽기 전용으로 처리하고 가변 값에는 상태 또는 컨텍스트를 사용해야 한다.
또 다른 흔한 오류는 prop 드릴링에 지나치게 의존하는 것으로, 앞서 논의한 바와 같이 컴포넌트 계층을 어지럽히고 코드를 유지 관리하기 어렵게 만들 수 있다. Context API나 상태 관리 라이브러리를 활용하면 데이터를 관리하고 전달하는 더 깨끗하고 효율적인 방법을 제공함으로써 이 문제를 완화할 수 있다.
추가적으로, 개발자들은 적절한 유효성 검증 및 props 문서화를 소홀히 하여 사용하고 유지하기 어려운 컴포넌트를 만들 수 있다. React의 prop 타입을 활용하거나 TypeScript로 타입 검사를 사용하면 컴포넌트가 올바른 유형의 데이터를 받도록 보장하여, 런타임 오류를 줄이고 개발 경험을 향상시킬 수 있다.
// Prop 타입을 정의하여 타입 검사
import PropTypes from 'prop-types';
function UserProfile({ name, age }) {
return (
<div>
<h1>{name}</h1>
<p>{age} years old</p>
</div>
);
}
UserProfile.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};
마지막으로, props와 state 사이의 구분을 잘못 이해하면 컴포넌트 내에서 데이터 관리 전략을 부적절하게 만들 수 있다.
props는 컴포넌트 트리를 통해 데이터와 구성을 전달하기 위한 것이며, state는 컴포넌트 내에서 시간이 지남에 따라 변경되는 데이터를 관리하기 위한 것이다. 이 구분을 명확히 유지하는 것은 여러 번 강조해도 모자라지 않을 만큼 중요하다.
결론
Props는 컴포넌트 기반 개발에서 핵심 개념으로 컴포넌트 간 데이터 전송의 주요 수단으로 작용한다.
타입 검증 및 기본값 할당을 포함한 props 사용에 대한 최선의 실천 방법을 수용함으로써 흔한 개발 오류를 상당히 완화할 수 있다. 또한 다양한 프레임워크에서 props의 미묘한 차이를 이해하는 것은 개발자의 도구 상자를 넓히고 다양한 프로젝트 요구 사항 및 환경에 적응할 수 있게 한다.
컴포넌트 기반 아키텍처를 통해 웹 개발을 수행하고 있으므로 props는 한 동안 개발자들에게 중요한 기술로 남을 것이다. 이 글에서 소개된 기술들을 활용함으로써 애플리케이션이 단지 기능적일 뿐만 아니라 장기적으로 확장 가능하고 유지보수가 용이하도록 도와줄 수 있다.\\
references
Passing Props to a Component – React
'Web, Front-end' 카테고리의 다른 글
[React] 컴포넌트의 생명 주기(Life Cycle) (0) | 2024.02.18 |
---|---|
[React] 프로젝트 폴더(디렉토리) 구조 (0) | 2024.02.18 |
[Javascript] 웹 브라우저 환경에서의 자바스크립트 (0) | 2024.02.14 |
[Javascript] 자바스크립트 특징 (0) | 2024.02.01 |
JSON Server 활용하기 (0) | 2023.11.03 |
컴퓨터 전공 관련, 프론트엔드 개발 지식들을 공유합니다. React, Javascript를 다룰 줄 알며 요즘에는 Typescript에도 관심이 생겨 공부하고 있습니다. 서로 소통하면서 프로젝트 하는 것을 즐기며 많은 대외활동으로 개발 능력과 소프트 스킬을 다듬어나가고 있습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!