본 게시글은 리액트 공식문서(영문)를 바탕으로 이해하는 과정을 작성한 것입니다.
컴포넌트는 UI를 독립적이고 재사용가능하도록 나누고, 각각의 독립인 조각으로 생각하게 해줍니다.
개념적으로 컴포넌트는 자바스트립트 기능처럼 보입니다. 이것들은 props 라고 불리는 입력을 받아들입니다. 그리고 스크린에 나타나야하는 리액트 요소들을 리턴해줍니다.
컴포넌트는 함수형 컴포넌트와 클래스형 컴포넌트로 나뉩니다. 클래스형 컴포넌트는 state기능 및 라이프사이클을 이용할 수 있고, 임의 메서드 지정이 가능합니다.
import React from 'react';
const MyComponent = () => {
return <div>안녕하세요, 제 이름은 김민주입니다.</div>
};
export default MyComponent;
리액트에서 Class를 사용하기 위해선 React.Component
를 상속받아야 합니다.
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return <div>안녕하세요, 제 이름은 김민주입니다.</div>;
}
}
export default MyComponent;
import React from "react";
import MyComponent from "./MyComponent";
const App = () => {
return (
<>
<MyComponent/>
<MyComponent/>
</>
)
};
export default App;
리액트를 실행하기 위해선 항상 index.js 와 index.html이 있어야한다.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App/>, document.getElementById('root'));
컴포넌트를 정의하는 가장 심플한 방법은 자바스크립트 function을 이용하는 겁니다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
이 function은 명백히 리액트 컴포넌트입니다. 왜냐면 데이터를 가진 하나의 props(properties의 약자) 객체를 받고, 리액트 엘리먼트를 리턴하기 때문입니다. 우리는 이런 컴포넌트가 말 그대로 자바스크립트 function 이기 때문에 function components
라고 부릅니다.
우리는 ES6 class문법을 컴포넌트를 정의하기 위해 사용할 수 있습니다.
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
리액트의 관점에서 두 컴포넌트는 같아 보입니다.
컴포넌트 렌더링
예전에 우리는 DOM 태그를 표현하기 위해 리액트 요소만을 사용했습니다.
const element = <div />;
하지만, 요소들은 사용자 정의 컴포넌트로도 표현이 가능합니다.
const element = <Welcome name="Sara" />;
리액트가 사용자 정의 컴포넌트 요소를 보게 되면, 리액트는 이 컴포넌트를 하나의 객체로 여기고, JSX 속성과 자식을 넘깁니다. 우리는 이 객체를 props
라고 부릅니다.
예를 들어 여기 페이지에 hello sara
라고 하는 코드가 있다고 해봅시다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
이 예제에서 어떤 일이 일어났는지 요약해보자면
1. <Welcom name='Sara'/>
요소가 담긴 ReactDOM.render()
를 호출합니다.
2. 리액트는 props
인 {name: 'Sara'}
와 Welcome 컴포넌트를 호출합니다.
3. Welcome 컴포넌트는 결과로 <h1>Hello, Sara</h1>
을 반환합니다.
4. 리액트 DOM은 <h1>Hello, Sara</h1>
에 맞춰 DOM을 업데이트합니다.
💡 항상 컴포넌트의 이름은 대문자로 시작해야합니다.
리액트는 소문자로 시작하는 컴포넌트를 DOM 태그로 인식합니다. 예를 들어,는 HTML의 div태그로 표현되지만 은 컴포넌트로 인식되며, 범위 내에서 Welcome이 존재해야합니다.
컴포넌트 구상하기
컴포넌트는 그들의 결과값을 다른 컴포넌트에 참조시킬 수 있습니다. 이런 점은 컴포넌트를 어떤 디테일 수준에서든 추상화해 사용할 수 있도록 해줍니다. 버튼, 폼, 대화, 스크린 : 리액트 앱에서 이 모든 것들이 공통적으로 컴포넌트로 표현됩니다.
예를 들어 Welcome을 여러번 렌더하는 앱 컴포넌트를 만든다고 생각해봅시다.
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')
);
일반적으로 새로운 리액트 앱은 가장 위에 하나의 app 컴포넌트를 갖고 있습니다. 하지만 만약 기존에 존재하던 앱에 리액트를 통합하려 한다면, 당신은 아마 버튼 같은 작은 컴포넌트부터 시작해 점진적으로 계층의 윗부분(App())으로 올라가야 할겁니다.
컴포넌트 추출하기
예를 들어 이 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
(an object), text
(a string), date
(a date)를 props로 받습니다.
컴포넌트가 감싸져 있어 변경되기 어려워 보이고, 개별적인 부분으로 재사용되기 힘들어보입니다. 여기서 컴포넌트들을 추출해봅시다.
먼저 Avatar 속성을 꺼내봅니다.
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/> );
}
아바타 컴포넌트는 Comment안에 렌더될 필요가 없습니다. 왜냐면 우리는 prop으로 더욱 일반적인 이름을 받기 때문이죠 : author보다 user 사용
props의 이름을 정의할 땐, 문맥상에서 어떻게 쓰이는 지보다 컴포넌트 자체의 관점에서 짓기를 추천합니다.
이제 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>
);
}
그 다음 우리는 Avatar옆에 Username을 렌더하는 UserInfo 컴포넌트를 추출해볼겁니다.
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
보다 더 간결하게 만들어 주었습니다.
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에 여러번 사용되거나, 자체만으로도 복잡한 것이 있다면 개별 컴포넌트로 분리되기 좋은 후보가 됩니다.
props는 컴포넌트 속성을 설정할 때 사용하는 요소입니다. props값은 해당 컴포넌트를 불러오는 부모 컴포넌트에서 인자값으로 전달해줍니다.
import React from 'react';
const MyComponent = props => {
return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>
};
export default MyComponent;
import React from "react";
import MyComponent from "./MyComponent";
const App = () => {
return (
<>
<MyComponent name = "minju"/>
<MyComponent name = "kim"/>
</>
)
};
export default App;
index.js는 그대로 사용가능!
props에 인자값을 전달하지 않는 경우, 기본값을 설정할 수 있습니다.
import React from 'react';
const MyComponent = props => {
return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>
};
MyComponent.defaultProps = {
name : "홍길동"
}
export default MyComponent;
import React from "react";
import MyComponent from "./MyComponent";
const App = () => {
return (
<>
<MyComponent name = ""/>
<MyComponent/>
</>
)
};
export default App;
빈 문자열이 입력되는 경우 빈 문자열 그대로 출력하고,
아예 인자값이 전달되지 않은 경우에만 defaultProps가 적용되는 것을 확인할 수 있습니다.
부모 컴포넌트에서 컴포넌트를 태그처럼 사용한 뒤, 그 안에 글자를 넣을 수 있습니다.
입력된 글자를 컴포넌트에서 사용하기 위해서 Props.children을 사용합니다.
import React from 'react';
const MyComponent = props => {
return (
<div>
안녕하세요, 제 이름은 {props.name}입니다.
children 값은 {props.children}입니다.
</div>
)
};
MyComponent.defaultProps = {
name : "홍길동"
}
export default MyComponent;
import React from "react";
import MyComponent from "./MyComponent";
const App = () => {
return (
<>
<MyComponent name = "김민주">리액트 Component</MyComponent>
</>
)
};
export default App;
지금까지는 props 내부 값을 추출하기 위해 뒤에 props.name / props.children 처럼 붙여주는 방식이었습니다. ES6의 비구조화 할당 문법을 이용해 내부값을 바로 추출해 더욱 편하게 작업할 수 있습니다.
import React from 'react';
const MyComponent = props => {
const {name, children } = props
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
children 값은 {children}입니다.
</div>
)
};
export default MyComponent;
parameter부분에도 비구조화 할당을 적용할 수 있습니다.
import React from 'react';
const MyComponent = ({name, children }) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
children 값은 {children}입니다.
</div>
)
};
export default MyComponent;
class형 컴포넌트에서도 비구조화 문법으로 props를 받을 수 있습니다.
import React , {Component} from 'react';
class MyComponent extends Component {
render(){
const {name, children} = this.props;
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
children 값은 {children}입니다.
</div>
);
}
}
컴포넌트의 필수 props를 지정하거나 타입을 지정할 때 사용합니다.
설정 방법은 defaultProp과 비슷하며, 사용하기 위해선
1. 라이브러리를 다운받습니다.
yarn add prop-types
npm은
npm install --save prop-types
MyComponent.js
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = ({name, children}) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
children 값은 {children}입니다.
</div>
)
};
MyComponent.propTypes = {
name: PropTypes.string
}
export default MyComponent;
App.js
import React from "react";
import MyComponent from "./MyComponent";
const App = () => {
return (
<>
<MyComponent name = {3343}>리액트 Component</MyComponent>
</>
)
};
export default App;
이렇게 string으로 타입을 지정해놓고 숫자타입을 넣게 되면
이렇게 콘솔창에 타입 에러가 뜨게된다.
propTypes에 값을 지정하지 않았을 때, 경고 메세지를 띄워주기 위해 사용합니다.
class형 컴포넌트에선 propTypes와 isRequired를 class안에 넣어 사용할 수 있습니다.
컴포넌트를 function로 정의하든 class로 정의하든, 컴포넌트는 절대 자체적으로 props를 수정할 수 없습니다. 이 합계 예시를 보면
function sum(a, b) {
return a + b;
}
이렇게 입력값을 바꾸려는 시도를 하지 않고 항상 같은 입력값에 같은 결과물을 출력하는 함수들을 순수함수라고 부릅니다.
반대로 이런 함수는 입력값을 바꾸기 때문에 순수하지 않습니다.
function withdraw(account, amount) {
account.total -= amount;
}
리액트는 유연한 편이지만, 하나의 엄격한 규칙이 있습니다.
모든 리액트 컴포넌트는 반드시 props를 다룰 때 항상 순수함수로서 작동할 것
당연히 어플리케이션의 UI들은 역동적이고 계속해서 바뀝니다. State를 이용하면 리액트 컴포넌트가 이 규칙을 어기는 것 없이, 유저의 행동이나 네트워크 상태, 그 밖에 기타 등등 사항들에 따라 계속해서 상태를 바꾸는 것을 허용합니다.
참고
리액트 공식문서
리액트를 다루는 기술