2023.01.30 Components와 Props
개념적으로 컴포넌트는 JavaScript 함수와 유사합니다.
“props”라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환합니다.
리액트에서는 모든 페이지가 컴포넌트로 구성되어 있고,
하나의 컴포넌트는 또 다른 여러 개의 컴포넌트의 조합으로 구성될 수 있다.
리액트 컴포넌트는 개념적으로 자바스크립트의 함수와 비슷하다.
함수가 입력을 받아서 출력을 내뱉는 것처럼,
리액트 컴포넌트도 입력을 받아서 정해진 출력을 내뱉는다.
하지만 리액트 컴포넌트의 입력과 출력은 일반적인 자바스크립트 함수와는 조금 다르다.
리액트 컴포넌트가 해주는 역할은
어떠한 속성들을 입력으로 받아서 그에 맞는 리액트 엘리먼트를 생성하여 리턴해주는 것이다.
props는 prop 뒤에 복수형을 나타내는 알파벳 s를 붙여서
prop이 여러 개인 것을 의미한다.
prop는 property라는 영단어를 줄여서 쓴 것이다.
property는 '속성','특성'이라는 뜻을 가지고 있다.
리액트에서 props는 리액트 컴포넌트의 속성이다.
위에 그림처럼 props는 같은 리액트 컴포넌트에서 눈에 보이는 글자나
색깔 등의 속성을 바꾸고 싶을 때 사용하는 컴포넌트의 속 재료라고 생각하면 된다.
props는 컴포넌트에 전달할 다양한 정보를 담고 있는 자바스크립트 객체 이다.
컴포넌트에 어떤 데이터를 전달하고
전달된 데이터에 따라 다른 모스브의 엘리먼트를 화면에 렌더링하고 싶을 때,
해당 데이터를 props에 넣어 전달하는 것이다.
함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안 된다.
다음 sum 함수를 살펴보자.
function sum(a, b) {
return a + b;
}
이 함수에서는 a와 b라는파라미터의 값을 변경하지 않고 있다.
그리고 a와 b라는 파라미터 집합의 값이 같은 경우에는 항상 같은 값을 리턴할 것이다.
이러한 함수를 Pure 함수라고 한다.
입력값을 변경하지 않으며, 같은 입력같에 대해서는 항상 같은 출력값을 낸다는 의미이다.
function withdraw(account, amount) {
account.total -= amount;
}
이 함수는 account와 amount라는 파라미터를 받아 account의 total이라는 값에서 amount를 빼는 함수이다.
쉽게 말하면 계좌에서 출금을 하는 함수이다.
여기에서 이 함수는
입력으로 받은 파라미터 account의 값을 변경했다.
이런경우 impure 함수라고 한다.
위에 내용은 리액트 컴포넌트의 정의와 관련되어 있다.
아래 문장은 리액트 공식 문서에 나오는 컴포넌트의 특징을 설명한 문장이다.
All React components must act like pure function with respect to their pops.
모든 리액트 컴포넌트는 그들의 props에 관해서는 Pure함수 같은 역할을 해야 한다.
쉽게 말해
모든 리액트 컴포넌트는 props를 직접 바꿀 수 없고,
같은 props에 대해서는 항상 같은 결과(엘리먼트)를 보여줄 것! 이다.
JSX를 사용하는 경우에는
아래 코드와 같이 키와 값으로 이루어진 키-값 쌍의 형태로 컴포넌트에 props를 넣을 수 있다.
function App(props) {
return (
<Profile
name="소플"
introduction="안녕하세요, 소플입니다."
viewCount = {1500}
/>
);
}
이렇게 하면 이 속성의 값이 모두 Profile 컴포넌트에 props로
전달되며 props는 아래와 같은 형태의 자바스크립트 객체가 된다.
{
name:"소플",
introduction:"안녕하세요, 소플입니다.",
viewCount:1500
}
한 가지 눈여겨봐야 할 부분은 각 속성에 값을 넣을 때
중괄호를 사용한 것과 사용하지 않은 것의 차이다.
JSX와 마찬가지로 props에 값을 넣을 때에도
문자열 이외에 정수,변수,그리고 다른 컴포넌트 등이 들어갈 경우에는 중관호를 사용해서
감싸주어야한다.
리액트 초기 버전에서는 클래스 컴포넌트를 주로 사용하였으나,
클래스 컴포넌트가 사용하기 불편하다는 의견이 많이 나왔고
이후에는 함수 컴포넌트를 개선해서 주로 사용하게 되었다.
함수 컴포넌트를 개선하는 과정에서 개발된 것이 바로 훅(Hook)이라는 것이다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
이 함수는 데이터를 가진 하나의 “props” (props는 속성을 나타내는 데이터) 객체 인자를
받은 후 React 엘리먼트를 반환하므로 유효한 React 컴포넌트이다.
이러한 컴포넌트는 JavaScript 함수이기 때문에 말 그대로 “함수 컴포넌트”라고 호칭한다.
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
클래스 컴포넌트는 자바스크립스 ES6의 클래스라는 것을 사용해서 만들어진 형태의 컴포넌트이다.
이 코드는 위에서 살펴 본 함수 컴포넌트와 동일한 역할을 하는 컴포넌트이다.
함수 컴포넌트와의 가장 큰 차이점은 리액트의 모든 클래스 컴포넌트는
React.Component를 상속 받아서 만든다는 것이다.
컴포넌트의 이름을 지을 때
컴포넌트의 이름은 항상 대문자로 시작해야 된다는 점을 유의하자.
리액트는 소문자로 시작하는 컴포넌드를 DOM태그로 인식하기 때문이다.
예를 들어 <div>나 <span>과 같이 사용하는 것은 내장 컴포넌트라는 것을 뜻하며,
'div','span'과 같은 문자열 형태로 React.createElement( )에 전달된다.
하지만 <Foo>와 같이 대문자로 시작하는 경우에는 React.createElement(Foo)의 형태로 컴파일되며
자바스크립트 파일 내에서 사용자가 정의했거나 import한 컴포넌트를 가르킨다.
그렇기 때문에 컴포넌트 이름은 항상 대문자로 시작해야한다.
이전까지는 DOM 태그만을 사용해 React 엘리먼트를 나타냈다.
const element = <div />;
React 엘리먼트는 사용자 정의 컴포넌트로도 나타낼 수 있다.
const element = <Welcome name="Sara" />;
React가 사용자 정의 컴포넌트로 작성한 엘리먼트를 발견하면
JSX 어트리뷰트와 자식을 해당 컴포넌트에 단일 객체로 전달한다.
이 객체를 “props”라고 한다.
다음은 페이지에 “Hello, Sara”를 렌더링하는 예시이다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Welcome name="Sara" />;
root.render(element);
이 예시에서는 다음과 같은 일들이 일어난다.
1. <Welcome name="Sara" /\> 엘리먼트로 root.render()를 호출합니다.
2. React는 {name: 'Sara'}를 props로 하여 Welcome 컴포넌트를 호출합니다.
3. Welcome 컴포넌트는 결과적으로 <h1\>Hello, Sara</h1\> 엘리먼트를 반환합니다.
4. React DOM은 <h1\>Hello, Sara</h1\> 엘리먼트와 일치하도록 DOM을 효율적으로 업데이트합니다.
컴포넌트는 자신의 출력에 다른 컴포넌트를 참조할 수 있다.
이는 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있음을 의미한다.
React 앱에서는 버튼, 폼, 다이얼로그, 화면 등의 모든 것들이 흔히 컴포넌트로 표현된다.
예를 들어 Welcome을 여러 번 렌더링하는 App 컴포넌트를 만들 수 있다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
위의 그림처럼 컴포넌트 합성은
여러 개의 컴포넌트를 합쳐서 하나의 컴포넌트를 만드는 것이다.
컴포넌트 합성과 반대로 복잡한 컴포넌트를 쪼개서
여러 개의 컴포넌트로 나눌 수도 있다.
큰 컴포넌트에서 일부를 추출해서 새로운 컴포넌트를 만든다는 뜻이다.
컴포넌트 추출을 잘 활용하게 되면
컴포넌트의 재사용성이 올라가게 된다.
다음 Comment 컴포넌트를 살펴보자.
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Comment라는 컴포넌트는 댓글을 표시하기 위한 컴포넌트로 내부에
작성자의 프로필 이미지와 이름, 댓글 내용과 작성일을 포함하고 있다.
이 컴포넌트의 props는 아래와 같다.
props = {
author: {
name: "소플",
avatarUrl: "https://...",
},
text: "댓글",
date: Date.now(),
}
이제 이 컴포넌트에서 컴포넌트를 추출해 보자.
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
props에 기존에 사용하던 aurhor 대신 조금 더 보편적인 의미를 갖고 있는 user를 사용하였다.
보편적인 단어를 사용하는 것은 재사용성 측면을 고려하는 것이라고 보면 된다.
이렇게 추출된 Avatar 컴포넌트를 실제로 Comment 컴포넌트에 반영하면 아래와 같은 코드가 된다.
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
**<Avatar user={props.author} />**
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
사용자 정보를 담고 있는 부분을 추출해보자.
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
위의 코드는 사용자 정보를 담고 있는 부분을 UserInfo라는 컴포넌트로 추출한 것이다.
아까 처음에 추출했던 Avatar 컴포넌트도 여기에 함께 추출된 것을 볼 수 있다.
그리고 역시 props에 author대신 좀 더 보편적인 의미를 갖고 있는 user를 사용하였다.
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>
);
}
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;
import React from "react";
import Comment from "./Comment";
function CommentList(props) {
return (
<div>
<Comment />
</div>
)
}
export default CommentList;
수정
import React, { Profiler } 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 컴포넌트에 표시되는 작성자 이름과 댓글 내용을 동적으로
변경할 수 있게 하기 위해 props를 추가해보자.
Comment.jsx
작성자 이름과 댓글 내용을 각각 props.name / props.comment 값을 사용하도록 변경.
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>
);
}
CommentList.jsx
CommentList 컴포넌트에 props 추가
function CommentList(props) {
return (
<div>
<Commen 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 컴포넌트가 렌더링된 것을 볼 수 있다.
컴포넌트 기반 구조
개념적으로는 자바스크립트의 함수와 비슷함
Props의 개념
Props의 특징
Props 사용법
컴포넌트의 종류
함수 컴포넌트
클래스 컴포넌트
컴포넌트 이름 짓기
큰 컴포넌트에서 일부를 추출해서 새로운 컴포넌트를 만드는 것
기능 단위로 구분하는 것이 좋고,
나중에 곧바로 재사용이 가능한 형태로 추출하는 것이 좋음