컴포넌트는 UI를 구성하는 조각(piece) 에 해당되며, 독립적으로 분리되어 재사용을 됨을 목적으로 사용됩니다. React 앱에서 컴포넌트는 개별적인 JavaScript 파일로 분리되어 관리합니다.
React 컴포넌트 파일 예시
Copy components /
├── Accordion . js
├── Container . js
├── Divider . js
├── DownLoadAndWatch . js
├── Dropdown . js
├── HomeLink . js
├── Info . js
├── Link . js
├── NetflixFAQ . js
├── NetflixIntro . js
├── OurStory . js
├── Promotion . js
├── Section . js
├── WatchOnDevice . js
└── WatchOnTV . js
함수 컴포넌트
React 컴포넌트는 개념상 JavaScript 함수와 유사합니다. 컴포넌트 외부로부터 속성(props
)을 전달 받아 어떻게 UI를 구성해야 할지 설정하여 React 요소(JSX를 Babel이 변환 처리)로 반환합니다. 이러한 문법 구문을 사용하는 컴포넌트를 React는 "함수 컴포넌트(function component) "로 분류합니다.
Copy import React from 'react'
// React 함수(Functional) 컴포넌트
function Button (props) {
// props 객체 → { type, act, children, ... }
return (
< button
type = { props .type || 'button' }
className = { `button button__ ${ act } ` }
>
{ props .children}
</ button >
)
}
// 컴포넌트 내보내기
export default Button
컴포넌트 네이밍 컨벤션
컴포넌트 이름은 항상 대문자로 시작하는 TitleCase 문법 사용을 권장 합니다. (HTML 표준 요소와 구분)
Copy < Button >React 버튼 요소</ Button >
Copy <button type="button" class="button">HTML 버튼 요소</button>
클래스 컴포넌트
ES6 부터 지원되는 클래스 문법 을 사용해 컴포넌트를 정의할 수도 있습니다. React는 이러한 문법을 사용하는 컴포넌트를 "클래스 컴포넌트(class component) "라고 부릅니다. 클래스 문법을 사용하면 아래와 같이 작성할 수 있습니다.
Copy import React from 'react'
// React 클래스(class) 컴포넌트
class Button extends React . Component {
// 생성자
// constructor(props) {
// super(props)
// }
// 렌더 메서드
render () {
// this.props 객체 → { type, act, children, ... }
const { type , act , children } = this .props
type = type ?? 'button'
return (
< button type = {type} className = { `button button__ ${ act } ` }>
{chilren}
</ button >
)
}
}
// 컴포넌트 내보내기
export default Button
함수형? 클래스형? 각 컴포넌트는 차이가 있나요?
React 세계관에서 함수형과 클래스 컴포넌트는 유사하지만, 클래스 컴포넌트의 경우 함수형 컴포넌트에 없는 기능을 추가적으로 사용할 수 있다는 점이 다릅니다. 하지만 React 훅의 등장(v16.8)으로 클래스 컴포넌트만 가지고 있던 기능을 함수 컴포넌트에서도 사용할 수 있게 되었습니다. 자세한 이야기는 훅 파트에서 다룹니다.
컴포넌트 렌더링
지금까지는 JSX를 사용해 React 요소를 생성했습니다. 하지만 JSX만으로는 "관리자", "로그아웃" React 요소를 생성만 할 수 있을 뿐. 유사한 코드를 비효율적으로 반복하게 됩니다.
Copy const adminButtonElement = (
< button
type = "button"
className = "button button__admin"
>
관리자
</ button >
)
const signOutButtonElement = (
< button
type = "button"
className = "button button__signOut"
>
로그아웃
</ button >
)
반면 컴포넌트를 사용하면 효율적으로 코드를 다음과 같이 재사용 할 수 있습니다.
Copy const adminButtonElement = < Button act = "admin" >관리자</ Button >
const signOutButtonElement = < Button act = "signOut" >로그아웃</ Button >
React 컴포넌트를 JSX 구문을 사용해 다른 컴포넌트 내부의 자식(children)으로 중첩(nested) 하면 React 가상 DOM Tree의 노드(Node)로 렌더링 됩니다.
Copy import React from 'react'
import ReactDOM from 'react-dom'
// Button 컴포넌트 불러오기
import Button from './components/Button'
// App 함수 컴포넌트
const App = () => (
< div className = "app" >
< Button act = "signOut" >로그아웃</ Button >
< Button act = "admin" >관리자</ Button >
</ div >
)
ReactDOM .render (
< App /> ,
document .getElementById ( 'root' )
)
현재 작성된 컴포넌트 트리(React 가상 DOM Tree)를 그려보면 다음과 같은 구조를 가지게 됩니다.
Virtual DOM Tree
Copy App
├── Button
└── Button
전달 속성 (props)
React는 컴포넌트로부터 생성 된 요소(JSX)를 발견하면 JSX 구문에 전달 된 속성과 자식을 해당 컴포넌트에 단일 객체로 전달합니다. 이 객체가 “props ” 입니다. JSX는 Babel 컴파일러에 의해 React.createElement() 구문으로 변환됩니다.
React JSX
Copy const adminButtonElement = < Button act = "admin" >관리자</ Button >
const signOutButtonElement = < Button act = "signOut" >로그아웃</ Button >
React API — createElement
Copy const adminButtonElement = React .createElement (
// Button 컴포넌트 (함수 또는 클래스)
Button ,
// props 객체
{
act : "admin" ,
children : [ "관리자" ]
}
)
const signOutButtonElement = React .createElement (
Button ,
{
act : "signOut" ,
children : [ "로그아웃" ]
}
)
JSX 구문으로 부터 전달 받은 속성 객체 props
는 컴포넌트 내부에 전달 되어 렌더링 과정에 활용됩니다.
Copy import { Component } from 'react'
export default class Button extends Component {
render () {
// this.props 객체 → { type, act, children, ... }
const { type , act , children } = this .props
type = type ?? 'button'
return (
< button type = {type} className = { `button button__ ${ act } ` }>
{chilren}
</ button >
)
}
}
전달 된 속성은 읽기 전용(readonly)
컴포넌트에 전달 된 속성 객체는 "읽기 전용"입니다. 즉, 전달 된 속성 값을 컴포넌트에서 수정할 수 없습니다. 전달 된 속성 값은 수정할 수 없지만, 컴포넌트 자신의 상태(state)는 수정 가능합니다.
컴포넌트 트리 (Tree)
컴포넌트는 다른 컴포넌트 안에 포함 될 수 있음을 앞에서 확인했습니다. 포함 된 컴포넌트는 "자식 컴포넌트", 그리고 포함 하는 컴포넌트는 "부모 컴포넌트"가 됩니다.
Copy < App >
< NavBar />
< Profile />
< Trends />
< Feed >
< Tweet />
< Like />
</ Feed >
</ App >
컴포넌트의 관계 모델(Components Model)을 그림으로 그려보면 다음과 같습니다.
데이터 흐름 (Flow)
컴포넌트가 상위(부모) 컴포넌트로부터 전달 받은 속성(props)은 공유 된 "데이터" 입니다. React는 단방향 데이터 흐름(one-way data flow) 방식 을 사용해 앱의 데이터를 공유하고, 데이터 흐름은 하향식(Top Down 방식) 으로 흐릅니다.
이미 작성된 컴포넌트 내부에서 컴포넌트로 사용할 수 있는 것이 보인다면 분리하는 것이 좋습니다. 예를 들어 다음의 함수 컴포넌트 DeliveryComment 코드는 매우 복잡합니다. 보다 세분화 하여 분리 가능한 컴포넌트로 나눠 구성하려면 어떻게 해야 할까 생각해봅시다.
Copy const DeliveryComment = (props) => {
return (
< div className = "delivery-comment" >
< div className = "commentary" >
< img
className = "avatar"
src = { props . user .avatarUrl}
alt = { props . user .name}
/>
< span
className = {[ 'rating-stars' , props . rating .score]}
ariaLabel = { props . rating .label}
></ span >
< strong className = "user-name" >{ props . user .name}</ strong >
< time className = "comment-date" >{ props . user .createdAt}</ time >
< p className = "comment-content" >{ props . user .content}</ p >
< div className = "reply-comment" >
{ /* ... */ }
</ div >
</ div >
</ div >
)
}
컴포넌트를 추출해 사용해야 하는 이유
컴포넌트 구조가 복잡한 경우, 요청사항에 따라 변경이 까다로울 수 있고 각 부품을 재사용하기도 어렵습니다. 이런 경우 컴포넌트를 작게 나눠 재사용하는 용도로 구분해 개발하는 것이 좋습니다.
이해를 돕기 위해 컴포넌트 UI를 그려보면 다음과 같습니다. DeliveryComment 컴포넌트 내부에 배치된 요소들 중 재사용 하면 좋을 것을 분리하는 겁니다. 분리하면 좋을 컴포넌트가 눈에 띄나요?
분리해 재사용하기 좋은 컴포넌트로 아바타(Avatar), 평가(RatingStars), 댓글(ReplyComment)로 나눌 수 있을 겁니다.
DeliveryComment 컴포넌트 내부에서 각 컴포넌트를 분리해 추출하면 다음과 같이 나눌 수 있습니다.
▀ Avatar
상위(부모) 컴포넌트로부터 user
속성을 전달 받아 처리하는 아바타 컴포넌트 입니다.
Copy const Avatar = props => {
const { avatarUrl , name } = props .user
return < img className = "avatar" src = {avatarUrl} alt = {name} />
}
▀ RatingStars
상위(부모) 컴포넌트로부터 rating
속성을 전달 받아 처리하는 평점(별점) 컴포넌트 입니다.
Copy const RatingStars = props => {
const { score , label } = props .rating
return < span className = {[ 'rating-stars' , score]} aria-label = {label} />
}
상위(부모) 컴포넌트로부터 reply
속성을 전달 받아 처리하는 댓글 컴포넌트 입니다.
Copy const ReplyComment = (props) => {
const { user } = porps .reply;
return (
< div className = "reply-comment" >
{ /* ... */ }
</ div >
)
}
아바타, 평점(별점), 댓글 컴포넌트 등을 분리 함에 따라 DeliveryComment 컴포넌트의 코드는 한결 다듬어졌고, 분리 된 각 컴포넌트는 다른 곳에서도 재 사용할 수 있게 되었습니다.
Copy import { Avatar } from './Avatar'
import { RatingStars } from './RatingStars'
import { ReplyComment } from './ReplyComment'
const DeliveryComment = props => {
const { user , rating , reply } = props
return (
< div className = "delivery-comment" >
< div className = "commentary" >
< Avatar user = {user} />
< RatingStars rating = {rating} />
< strong className = "user-name" >{ user .name}</ strong >
< time className = "comment-date" >{ user .createdAt}</ time >
< p className = "comment-content" >{ user .content}</ p >
< ReplyComment reply = {reply} />
</ div >
</ div >
)
}
컴포넌트를 나눠 추출하는 것이 처음에는 불 필요하게 느껴질 수 있지만, 재사용 가능한 컴포넌트를 분리 관리 함에 따라 앱의 규모가 커질 수록 개발에 드는 비용을 줄이고, 효율성을 높일 수 있습니다. (참고 )
컴포넌트는 한 가지의 작업만 하는 것이 이상적 입니다.
컴포넌트 규모가 커진다면 작은 서브 컴포넌트 들로 분리 되어야 합니다.
컴포넌트는 "배포 할 수 있는 가장 작은 단위 "를 말하며, 마치 벽돌 들을 쌓아 방의 구조를 설계하는 것과 같습니다. 컴포넌트를 설계하는 원칙의 핵심은 "변하지 않는 것 "과 "변하는 것들 "로 쪼개고 서로가 서로에게 미치는 영향이 크지 않도록 설계하는 것 입니다. (참고 )