컴포넌트와 props
컴포넌트를 통해 UI 를 독립적이고 재사용 가능한 부분으로 분리하고,
각 부분을 독립적으로 생각할 수 있습니다.
개념상 컴포넌트는 자바스크립트 함수와 비슷합니다.
“props”이라 불리는 임의의 입력을 받아들이고,
화면에 무엇이 표시되어야 하는지를 서술하는 React 요소를 반환합니다.
함수형 및 클래스 컴포넌트
컴포넌트를 정의하는 가장 간단한 방법은 자바스크립트 함수를 작성하는 것입니다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
이 함수는 유효한 React 컴포넌트로, “props” (properties 를 나타냄) 객체 인수를 받고 React 요소를 반환합니다.
이러한 컴포넌트는 말 그대로 자바스크립트 함수이기 때문에 “함수형 컴포넌트”라고 부릅니다.
컴포넌트를 정의하기 위해 ES6 class 를 사용할 수도 있습니다.
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
위의 두 컴포넌트는 React 관점에서 봤을 때 동일한 기능을 갖습니다.
클래스는 몇가지 기능을 더 가지고 있는데, 이는 다음 섹션 에서 다룹니다.
그 때까지 간결함을 유지하기 위해 함수형 컴포넌트를 사용할 것입니다.
컴포넌트 렌더링
이전에는 DOM 태그를 나타내는 React 요소만 있었습니다.
const element = <div />;
그러나, 는 사용자 정의 컴포넌트를 나타낼 수도 있습니다.
const element = <Welcome name="Sara" />;
React 가 사용자 정의 컴포넌트를 나타내는 요소를 처리할 때는,
JSX 어트리뷰트를 하나의 객체를 통해 컴포넌트로 전달합니다.
이 객체를 “props” 라고 부릅니다.
예를 들어, 이 코드는 “Hello, Sara”를 페이지에 렌더링합니다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(element, document.getElementById("root"));
<Welcome name="Sara" />
요소로 ReactDOM.render() 를 호출합니다.- React 가
{name: 'Sara'}
를 props 로 하여 Welcome 컴포넌트를 호출합니다. - Welcome 컴포넌트가 그 결과로
<h1>Hello, Sara</h1>
요소를 반환합니다. - React DOM 이
<h1>Hello, Sara</h1>
과 일치하도록 DOM 을 효율적으로 업데이트합니다.
[경고]
컴포넌트 이름은 항상 대문자로 시작하도록 지으세요. (소문자는 html 로 인식)
예를 들어 <div />
는 DOM 태그를 나타내지만 <Welcome />
은 컴포넌트를 나타내며 스코프에 Welcome 이 있어야 합니다.
컴포넌트 조립하기
컴포넌트의 출력에서 다른 컴포넌트를 가져와 사용할 수 있습니다.
이를 통해 모든 세부 레벨에서 동일한 컴포넌트 추상화를 사용할 수 있습니다.
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>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
일반적으로, 새롭게 작성되는 React 앱은 단일 App 컴포넌트를 최상위에 둡니다.
그러나 기존 앱에 React 를 도입하는 경우, Button 같은 작은 컴포넌트부터
덩치를 키워나가기 시작하여 점차적으로 뷰 계층의 최상단으로 나아갈 수 있습니다.
컴포넌트 추출
컴포넌트를 더 작은 컴포넌트로 쪼개는 것을 두려워하지 마십시오.
예를 들어, 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>
);
}
이 컴포넌트는 author (객체), text (문자열), date (Date 객체)를 props 로 받고,
소셜 미디어 웹사이트의 댓글을 나타냅니다.
이 컴포넌트는 중첩 때문에 변경하기 까다로울 수 있으며,
각 파트를 다시 사용하기도 어렵습니다.
여기에서 몇가지 컴포넌트를 추출해봅시다.
먼저, Avatar 를 추출할 수 있습니다.
function Avatar(props) {
return (
<img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} />
);
}
Avatar 는 자기가 Comment 내에서 렌더링되는 지를 알고 있을 필요가 없습니다.
따라서 author 대신 user 라는 더 일반적인 이름을 사용합니다.
속성 이름을 지을 때는, 컴포넌트가 사용되는 상황이 아닌 컴포넌트 그 자체만 생각하는 것이 좋습니다.
이제 Comment 를 약간 단순화시킬 수 있습니다.
// 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>
);
}
이어서, 유저의 이름 옆에 Avartar 를 렌더링하는 UserInfo 컴포넌트를 추출해봅시다.
// 기능 분할 - userInfo
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">{props.user.name}</div>
</div>
);
}
이제 Comment 가 더 단순해졌습니다.
// 정말 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>
);
}
컴포넌트를 추출하는 건 처음에는 쓸데없는 일처럼 보일 수 있지만,
재사용 가능한 컴포넌트 팔레트를 사용하면 큰 앱에서 그 진가를 발휘합니다.
적당한 기준을 잡아보자면, UI 의 일부분이 여러 번 사용되거나 (Button, Panel, Avatar),
자체적으로 충분히 복잡하다면 (App, FeedStory, Comment),
그것들은 재사용 가능한 컴포넌트가 될 좋은 후보입니다.
Props 는 읽기전용입니다
컴포넌트를 함수나 클래스 중 어떤 걸로 선언했건, 자기 자신의 props 를 수정할 수 없습니다. sum 함수를 살펴봅시다.
function sum(a, b) {
return a + b;
}
위와 같은 함수는 입력을 변경하려 하지 않고,
동일한 입력에 대해 항상 동일한 결과를 반환하기 때문에 “순수” 함수라고 불립니다.
위와 반대로, 이 함수는 입력을 변경하기 때문에 순수하지 않습니다.
function withdraw(account, amount) {
account.total -= amount;
}
React 는 매우 유연하지만 한가지 엄격한 규칙을 갖고 있습니다.
모든 React 컴포넌트는 props 에 대해서는 순수 함수처럼 동작해야합니다.
[순수 함수란?]
함수는 주어진 입력으로 계산하는 것 이외에 프로그램의 실행에 영향을 미치지 않아야 하며,
이를 부수 효과(side effect)가 없어야 한다고 합니다. 이러한 함수를 순수 함수라고 합니다.
예를 들어, count, length 함수는 임의의 문자열이나 배열에 대해서
항상 같은 길이를 반환하며, 그 외의 일은 일어나지 않습니다.
func plusNumber(num: Int) -> (Int -> Int) {
return { x in
return x + num
}
}
let addFive = plusNumber(5)
addFive(1) // 5
addFive(10) // 15
위의 코드에서 익명함수를 만들어 사용하게 되고 어떤 값이 들어오던지
5 를 더하여 반환하게 되므로 부수 효과가 발생하지 않습니다.
순수 함수의 참조 투명성(referential transparency, RT)으로 입력 값이 같으면
결과 값도 같다면 표현식은 참조에 투명하다(referentially transparent)라고 합니다.
표현식 f(x)가 참조에 투명한 모든 x 에 대해 참조가 투명하다면 함수 f 는 순수하다(pure)라고 합니다.
따라서 코드의 블록을 이해하기 위해 일련의 상태 갱신을 따라갈 필요가 없고
국소 추론(local reasoning)만으로도 코드를 이해할 수 있습니다.
모듈적인 프로그램은 독립적으로 재사용할 수 있는 구성요소(component)로 구성됩니다.
따라서 순수 함수는 입력과 결과가 분리되어 있으며,
어떻게 사용되는지에 대해서는 전혀 신경쓰지 않아도 되므로 재사용성이 높아집니다.