Components and Props
Components
- 리액트는 컴포넌트 기반(Component Based)구조
- 작은 컴포넌트들이 모여서 하나의 컴포넌트를 구성하고, 이러한 컴포넌트들이 모여서 전체 페이지를 구성
- 하나의 컴포넌트를 반복적으로 사용함으로써 전체 코드 양을 줄어 개발 시간과 유지 보수 비용을 줄일 수 있다.
- 컴포넌트의 역할은 어떠한 속성을 입력으로 받아 그에 맞는 리액트 엘리먼트를 생성하여 리턴해주는 것
- 만들고자 하는 대로 props(속성)을 넣으면 해당 속성에 맞춰 화면에 나타날 엘리먼트를 만들어 주는 하나의 함수로 이해하면 쉬움
Props
- 리액트 컴포넌트의 속성
- 리액트 컴포넌트에서 눈에 보이는 글자나 색깔 등의 속성을 바꾸고 싶을 때 사용하는 컴포넌트 속의 재료
- 컴포넌트에 전달할 다양한 정보를 담고 있는 자바스크립트 객체
- 컴포넌트에 어떤 데이터를 전달하고 전달된 데이터에 따라 다른 모습의 엘리먼트를 화면에 렌더링하고 싶을 때, 해당 데이터를 props에 넣어 전달
- Props는 읽기 전용
- 모든 리액트 컴포넌트는 props를 직접 바꿀 수 없고, 같은 props에 대해서는 항상 같은 결과(엘리먼트)를 리턴해야 함
- 리액트 컴포넌트는 Pure 함수와 같은 역할을 함
Props 사용법
JSX를 사용하는 경우
- 키와 값으로 이루어진 키-값 쌍의 형태로 컴포넌트에 props를 넣을 수 있음
- 아래 코드에서는 App 컴포넌트 안에 Profile 컴포넌트를 사용하고 있으며, Profile 컴포넌트 안에 name, introduction, viewCount라는 세 가지 속성이 props로 전달됨
function App(props) {
return (
<Profile
name="지은"
introduction="안녕하세요, 지은입니다."
viewCount={1500}
/>
);
}
- 즉, props는 아래와 같은 형태의 자바스크립트 객체가 됨
{
name: "지은",
introduction: "안녕하세요, 지은입니다.",
viewCount: 1500
}
- 이전에 JSX에서 자바스크립트 코드를 사용하려면 중괄호를 사용해야 한다고 학습하였음
- props에 값을 넣을 때에도 문자열 이외에 정수, 변수, 그리고 다른 컴포넌트 등이 들어갈 경우에는 중괄호를 사용해서 감싸주어야 함
- 중괄호를 사용하게 되면 아래와 같이 props의 값으로 컴포넌트를 넣을 수도 있음
funtion App(props) {
return (
<Layout
width={2500}
height={1440}
header={
<Header thtile="Jieun의 블로그 입니다.">
}
footer={
<Footer />
}
/>
);
}
JSX를 사용하지 않는 경우
- createElement 두 번째 파라미터 값에 자바스크립트 객체를 넣으면 그게 곧 해당 컴포넌트의 Props
React.createElement(
type,
[props],
[...children]
)
- 위에서 작성했던 Profile 컴포넌트를 JSX를 사용하지 않고 코드를 작성하면 아래와 같다.
React.createElement(
Profile,
{
name: "지은",
introduction: "안녕하세요, 지은입니다.",
viewCount: 1500
},
null
);
컴포넌트 만들기
컴포넌트 종류
함수 컴포넌트
- 리액트의 컴포넌트는 일종의 함수
- 하나의 props 객체를 받아 리액트 엘리먼트를 리턴
- 아래 Welcome 함수는 props 객체를 받아 인사말이 담긴 리액트 엘리먼트를 리턴
function Welcom(props) {
return <h1>안녕, {props.name}</h1>;
}
클래스 컴포넌트
- 자바스크립트 ES6의 클래스를 사용하여 만들어진 형태의 컴포넌트
- 클래스 컴포넌트는
React.Component
를 상속받아서 만듦
- 위에서 살펴본 함수 컴포넌트 Welcome과 동일한 역할을 하는 컴포넌트를 클래스 형태로 만들면 아래와 같다.
class Welcome extends React.Component {
render() {
return <h1>안녕, {this.props.name}</h1>;
}
}
컴포넌트 이름 짓기
- 컴포넌트의 이름은 항상 대문자로 시작해야함
- 리액트는 소문자로 시작하는 컴포넌트를 DOM 태그로 인식하기 때문
<div>
나 <span>
과 같이 사용하는 것은 내장 컴포넌트라는 것을 뜻하며, div, span과 같은 문자열 형태로 React.createElement()
에 전달됨
- 하지만
<Foo />
와 같이 대문자로 시작하는 경우에는 React.createElement(Foo)
형태로 컴파일되며 자바스크립트 파일 내에서 사용자가 정의했거나 임포트한 컴포넌트를 가리킴
const element = <div />;
const element = <Welcome name="지은" />;
컴포넌트 렌더링
- 컴포넌트로부터 엘리먼트를 만든 후 엘리먼트를 파라미터로
ReactDOM.render()
를 호출하여 화면에 렌더링
- 위에서 작성했던
Welcome
컴포넌트를 렌더링하는 코드를 작성하면 아래와 같다.
function Welcome(props) {
return <h1>안녕, {props.name}</h1>;
}
const element = <Welcome name="지은" />;
ReactDOM.render(
element,
document.getElementById('root')
);
컴포넌트 합성
- 여러 개의 컴포넌트를 합쳐서 하나의 컴포넌트를 만드는 것
- 리액트에서는 컴포넌트 안에 또 다른 컴포넌트를 사용할 수 있기 때문에, 복잡한 화면을 여러 개의 컴포넌트로 나누어서 구현할 수 있음
- Welcome 컴포넌트를 사용해서 컴포넌트 합성을 하는 코드를 작성해보면 아래와 같다.
- App 컴포넌트 안에 세 개의 Welcome 컴포넌트가 있고, 각각의 Welcome 컴포넌트는 각기 다른 props를 가지고 있음
- 이렇게 App 컴포넌트를 root로 해서 하위 컴포넌트들이 존재하는 형태가 리액트로만 구성된 앱의 기본적인 구조
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App(props) {
return (
<div>
<Welcome name="Mike" />
<Welcome name="Steve" />
<Welcome name="Jane" />
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
컴포넌트 추출
- 큰 컴포넌트에서 일부를 추출해서 새로운 컴포넌트를 만드는 것
- 컴포넌트 추출을 잘 활용하면 컴포넌트의 재사용성이 올라감.
- 컴포넌트가 작아질수록 해당 컴포넌트의 기능과 목적이 명확해지고, props도 단순해지기 때문에 다른 곳에서 사용할 수 있는 확률이 높아지기 때문!
예시 코드를 통해 컴포넌트를 추출하는 과정을 살펴보자.
function Comment(props) {
return (
<div ClassName="comment">
<div ClassName="user-info">
<img className="avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div ClassName="user-info-name">
{props.author.name}
</div>
</div>
<div ClassName="comment-text">
{props.text}
</div>
<div ClassName="comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
- 위 컴포넌트에서는
<img>
태그를 사용하여 사용자의 프로필 이미지를 표시하고 있음
- 이 부분을 추출하여 Avatar라는 별도의 컴포넌트로 만들어보자.
function Avatar(props) {
return (
<img className="avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
- 이렇게 추출한 Avatar 컴포넌트를 Comment 컴포넌트에 반영해보자.
function Comment(props) {
return (
<div ClassName="comment">
<div ClassName="user-info">
<Avatar user={props.author} />
<div ClassName="user-info-name">
{props.author.name}
</div>
</div>
<div ClassName="comment-text">
{props.text}
</div>
<div ClassName="comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
- 이번에는 사용자 정보를 담고 있는 부분을 추출해보자.
funtion UserInfo(props) {
return (
<div className="user-info">
<Avatar user={props.user} />
<div className="">
{props.user.name}
</div>
</div>
);
}
- 추출한 UserInfo 컴포넌트를 Comment 컴포넌트에 반영해보자.
function Comment(props) {
return (
<div ClassName="comment">
<UserInfo user={props.author} />
<div ClassName="comment-text">
{props.text}
</div>
<div ClassName="comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
- 처음 코드와 비교하면 코드가 매우 단순해 진 것을 볼 수 있음.
- 컴포넌트를 어느 정도 수준까지 추출하는 것이 좋은지에 대해 정해진 기준은 없지만, 기능 단위로 구분하는 것이 좋고, 나중에 곧바로 재사용이 가능한 형태로 추출하는 것이 좋음.
실습 - 댓글 컴포넌트 만들기
- 컴포넌트와 props에 대해 학습한 내용을 바탕으로 댓글 컴포넌트를 만들어보자.
- Comment라는 이름의 리액트 함수 컴포넌트를 만든다.
import React from 'react';
function Comment(props) {
return (
<div>
<h1>제가 만든 첫 컴포넌트입니다.</h1>
</div>
);
}
export default Comment;
- 여러개의 댓글 컴포넌트를 포함하고 있는 댓글 목록 컴포넌트를 만든다.
import React from 'react';
import Comment from './Comment';
function CommentList(props) {
return (
<div>
<Comment />
</div>
);
}
export default CommentList;
- 만든 CommentList 컴포넌트를 실제로 화면에 렌더링하기 위해 index.js 파일을 수정한다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import CommentList from '.chapter_05/CommentList';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CommentList />
</React.StrictMode>
);
reportWebVitals();
- 실행 결과
- 실제 댓글 모양처럼 보이게 하기 위해 Comment 컴포넌트에 간단한 CSS 스타일을 작성하고 컴포넌트 코드를 변경해보자.
import React from 'react';
const styles = {
wrapper: {
margin: 8,
padding: 8,
display: "flex",
flexDirection: "row",
border: "1px solid grey",
borderRadius: 16,
},
imageContainer: {},
image: {
width: 50,
height: 50,
borderRadius: 25,
},
contentContainer: {
marginLeft: 8,
display: "flex",
flexDirection: "column",
justifyContent: "center",
},
nameText: {
color: "black",
fontSize: 16,
fontWeight: "bold",
},
commentText: {
color: "black",
fontSize: 16,
},
};
function Comment(props) {
return (
<div style={styles.wrapper}>
<div style={styles.imageContainer}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png"
style={styles.image}
/>
</div>
<div style={styles.contentContainer}>
<span style={styles.nameText}>문지은</span>
<span style={styles.commentText}>제가 만든 첫 컴포넌트입니다.</span>
</div>
</div>
);
}
export default Comment;
- 실행 결과
- 작성자 이름과 댓글 내용을 동적으로 변경할 수 있게 하기 위해 props를 추가해보자.
- 작성자 이름과 댓글 내용을 각각
props.name
, props.comment
값을 사용하도록 변경한다.
import React from 'react';
const styles = {
wrapper: {
margin: 8,
padding: 8,
display: "flex",
flexDirection: "row",
border: "1px solid grey",
borderRadius: 16,
},
imageContainer: {},
image: {
width: 50,
height: 50,
borderRadius: 25,
},
contentContainer: {
marginLeft: 8,
display: "flex",
flexDirection: "column",
justifyContent: "center",
},
nameText: {
color: "black",
fontSize: 16,
fontWeight: "bold",
},
commentText: {
color: "black",
fontSize: 16,
},
};
function Comment(props) {
return (
<div style={styles.wrapper}>
<div style={styles.imageContainer}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png"
style={styles.image}
/>
</div>
<div style={styles.contentContainer}>
<span style={styles.nameText}>{props.name}</span>
<span style={styles.commentText}>{props.comment}</span>
</div>
</div>
);
}
export default Comment;
- 변경하면 아직 name과 comment 값이 정의되지 않아 undefined 값이기 때문에 아래와 같이 아무런 내용도 나오지 않는다.
- CommentList 컴포넌트에서 코드를 수정하여 props로 name과 comment 값을 전달해보자.
import React from 'react';
import Comment from './Comment';
function CommentList(props) {
return (
<div>
<Comment name={"문지은"} comment={"안녕하세요, 문지은입니다."}/>
</div>
);
}
export default CommentList;
- 이제 댓글 내용이 정상적으로 표시됨을 확인할 수 있다.
- Comment 컴포넌트를 하나 더 추가하고 작성자 이름과 댓글 내용을 다르게 입력하여 댓글을 하나 더 추가해보자.
import React from 'react';
import Comment from './Comment';
function CommentList(props) {
return (
<div>
<Comment name={"문지은"} comment={"안녕하세요, 문지은입니다."}/>
<Comment name={"유재석"} comment={"리액트 재미있어요!"}/>
</div>
);
}
export default CommentList;
- 댓글이 하나 더 표시됨을 확인할 수 있다.
- 이제 댓글 데이터를 별도의 객체로 분리해서 동적으로 받아온 데이터를 표시할 수 있는 구조로 만들어보자.
- comments 라는 배열을 만들어 댓글 데이터를 담고 있는 개체들을 넣어주고,
- 자바스크립트 배열의 map() 함수를 써서 각 댓글 객체에 대해 Comment 컴포넌트를 리턴하도록 코드를 작성한다.
import React from "react";
import Comment from "./Comment";
const comments = [
{
name: "문지은",
comment: "안녕하세요, 문지은입니다.",
},
{
name: "유재석",
comment: "리액트 재미있어요~!",
},
{
name: "강호동",
comment: "저도 리액트 배워보고 싶어요!!",
},
];
function CommentList(props) {
return (
<div>
{comments.map((comment) => {
return (
<Comment name={comment.name} comment={comment.comment} />
);
})}
</div>
);
}
export default CommentList;
- comments 배열에 있는 댓글 객체 수만큼 Comment 컴포넌트가 렌더링된 것을 볼 수 있다.
실습 전체 코드
References