What is React
A JavaScript library for building user interfaces
React 는 유저 인터페이스 building 을 위한 자바스크립트 라이브러리이다.
아래의 글은 React 공식문서를 보고 간단하게 요약 및 정리한 내용이다.
React 의 특징
- React 는 데이터의 흐름이 한 방향으로만 흐르는 단방향 데이터 흐름을 가진다.
- React 는 별도의 파일에 마크업과 로직을 넣어 기술을 인위적으로 분리하는 대신, 둘 모두를 포함하여 느슨하게 연결하는 컴포넌트component 유닛을 통해 관심사를 분리한다. 컴포넌트란 프로그래밍에 있어 재사용이 가능한 각각의 독립된 모듈을 뜻한다. 컴포넌트 기반 프로그래밍을 하면, 마치 레고 블록처럼 이미 만들어진 컴포넌트들을 조합하여 화면을 구성할 수 있다.
- React 는 가상 돔Virtual dom 을 이용하여 DOM 을 조작한다. view 에 변화가 있다면 그 변화를 먼저 가상 돔에 적용시키고 그 최종적인 변화를 실제 DOM 에 던져준다. 가상 돔을 이용하면 여러번 일어나야 하는 연산을 딱 한 번만 하게 되어 브라우저 내에서 발생하는 연산의 양을 줄이고 성능 개선을 돕는다. ref
JSX
JSX 는 React 에서 컴포넌트를 랜더링하여 UI 의 생김새를 정의할 때 사용하는 문법이다. HTML 과 비슷하게 생겼지만 자바스크립트의 확장언어extenstion 이다.
JSX 는 React 의 엘리먼트element 를 생성한다.
const name = 'Lena';
const firstname = 'Choi';
const element = (
<h1>
Hello, {name} {firstname}
</h1>
);
ReactDOM.render(element, document.getElementById('root'));
렌더링 된 엘리먼트는 불변객체로, 생성된 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없다. UI 를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이를 ReactDOM.render()
를 통해 전달하는 것이다. render()
메소드는 화면에 UI 를 생성해주는 역할을 한다.
아래의 함수는 setInterval()
콜백을 이용해 매초마다 ReactDOM.render()
을 호출한다.
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
ref 를 참고하면 DOM 이 어떻게 업데이트 되는지 확인할 수 있다.
JSX 의 특징
JSX 또한 자바스크립트 표현식이므로 컴파일이 끝나면 자바스크립트 객체로 인식된다. 따라서 JSX 내에서 자바스크립트 언어를 활용할 수 있다. 또한 camelCase 프로퍼티 명명 규칙을 사용해야 한다. ex) class = className
태그가 비어있다면 XML 처럼 /> 를 이용하여 바로 닫아주어야 한다.
Component 와 props
React 가 컴포넌트로 작성한 엘리먼트를 발견하면 JSX 어트리뷰트attribute 와 자식을 단일 객체로 전달하게 된다. 이 객체를 props 라고 한다. props 는 프로퍼티property 의 줄임말로, 컴포넌트에서 사용 할 데이터 중 변동되지 않는 데이터를 다룰 때 사용된다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name='Lena' />;
ReactDOM.render(element, document.getElementById('root'));
위의 코드를 실행하면 다음과 같은 일들이 일어난다.
1. <Welcome name='Lena' />
엘리먼트로 ReactDOM.render()
을 호출한다.
2. React 는 {name: 'Lena'}
를 props 로 하여 Welcome
컴포넌트를 호출한다.
3. Welcome
컴포넌트는 결과적으로 <h1>Hello, {props.name}</h1>
엘리먼트를 반환한다.
4. React DOM 은 <h1>Hello, {props.name}</h1>
엘리먼트와 일치하도록 DOM 을 업데이트 한다.
Component 의 분리
function formatDate(date) {
return date.toLocaleDateString();
}
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>
);
}
const comment = {
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'https://placekitten.com/g/64/64',
},
};
const element = (
<Comment date={comment.date} text={comment.text} author={comment.author} />
);
ReactDOM.render(element, document.getElementById('root'));
이 컴포넌트는 상수 comment 의 author
(객체), text
(문자열) 및 date
(날짜)를 props로 받는다.
기존의 Comment
컴포넌트를 각각 Avatar
, UserInfo
, Comment
컴포넌트로 분리해보자.
function Avatar(props) {
return (
<img className='Avatar' src={props.user.avatarUrl} alt={props.user.name} />
);
}
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>
);
}
props
모든 React 컴포넌트는 props 를 다룰 때 반드시 순수함수처럼 기능하여야 한다. 여기서 순수함수란, 같은 인자에 대해서 언제나 동일한 값을 반환하는 함수를 의미한다. 항상 동일한 값을 반환하기 위해서는 props 가 변동되지 않아야 하므로 props 는 읽기전용이 되어야 한다.
State 와 Lifecycle
React 컴포넌트에서 생성, 변경이 가능한 데이터를 state 라고 부른다. state 는 오직 state 가 생성된 컴포넌트 내에서만 변경이 가능하다. state 를 업데이트하려면 무조건 setState
메소드를 사용해야 한다.
props 와 state 모두 데이터를 갖고 있는 일반 자바스크립트 객체인데, 차이점은 props 는 받은 데이터이거나 생성된 데이터, 즉 데이터의 기원이 자기 자신이 아닌 데이터이고 state 는 자신의 컴포넌트 내에서 만들어 낸 데이터라는 점이다. props 는 함수의 매개변수처럼 컴포넌트에 전달되는 값을 의미하고 state 는 함수 내에서 선언된 변수처럼 컴포넌트 내부에서 관리된다.
setState()
호출은 비동기적으로 이루어지므로 setState
호출 직후 새로운 값이 this.state
에 반영된다고 여기면 안된다. 따라서 setState()
가 가장 최신의 state 값을 갖기 위해서는 객체 대신 함수를 전달해야 한다.
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
함수 컴포넌트는 state 가 없는 컴포넌트이다. 클래스 컴포넌트보다 메모리를 덜 사용하지만 state 를 사용할 수 없다.
위의 함수 컴포넌트를 클래스로 변환하여 state 를 적용시켜 보자. 함수 컴포넌트를 클래스 컴포넌트로 변환하는 과정은 아래와 같다.
1. React.Component
를 확장하는 동일한 이름의 ES6 class 를 생성한다.
2. render()
라는 이름의 빈 메서드를 추가한다.
3. 함수의 내용을 render()
메서드 안으로 옮긴다.
4. render()
안의 props 를 this.props
로 변경한다.
5. 남아있는 빈 함수 선언을 삭제한다.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
이제 해당 클래스에 로컬 state 를 추가해보자.
먼저 render()
메서드 안에 있는 [this.props.date]
를 [this.state.date]
로 변경한다.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
초기 this.state
를 지정하는 class constructor 를 추가한다
constructor 메서드는 class 로 생성된 객체를 생성하고 초기화하기 위한 특수한 메서드이다. 클래스 컴포넌트는 항상 props 로 기본 constructor 를 호출해야 한다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
<Clock />
요소에서 date
프로퍼티를 삭제한다. 결과는 아래와 같다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Lifecycle method
컴포넌트가 많은 애플리케이션에서는 컴포넌트가 삭제될 때 해당 컴포넌트가 사용중이던 리소스를 확보하는것이 중요하다.
위 코드에서 Clock 이 처음 DOM 에 랜더링 될 때마다 타이머를 설정하려고 한다. 이를 마운팅mounting 이라고 한다. 반대로 DOM 이 삭제될 때마다 타이머를 해제하려고 한다. 이를 언마운팅Unmounting 이라고 한다. 컴포넌트 클래스에서는 특별한 메서드를 선언하여 컴포넌트가 마운트/언마운트 될 때 일부 코드를 작동하도록 할 수 있다. 이러한 메서드들을 lifecycle method 라고 부른다.
위 class Clock 이 처음 마운팅될 때 타이머를 설정하려고 한다.
componentDidMount()
메서드는 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행된다. 여기에 타이머를 설정해보도록 하자.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
실행 과정을 설명하면 아래와 같다.
1. <Clock />
가 ReactDOM.render()
로 전달되었을 때 React는 Clock
컴포넌트의 constructor를 호출한다. Clock
이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state
를 초기화한다.
2. React는 Clock
컴포넌트의 render()
메서드를 호출하고 Clock
의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트한다.
3. Clock
출력값이 DOM에 삽입되면, React는 componentDidMount()
생명주기 메서드를 호출한다. 그 안에서 Clock
컴포넌트는 매초 컴포넌트의 tick()
메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청한다.
4. 매초 브라우저가 tick()
메서드를 호출한다. 그 안에서 Clock
컴포넌트는 setState()
에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행한다. setState()
는 컴포넌트의 state
객체에 대한 업데이트를 실행한다. setState()
호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render()
메서드를 다시 호출한다. 이 때 render()
메서드 안의 this.state.date
가 달라지고 렌더링 출력값은 업데이트된 시각을 포함한다. React는 이에 따라 DOM을 업데이트한다.
5. Clock
컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount()
생명주기 메서드를 호출한다.
이벤트 처리하기
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 한다.
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
위의 toggle 컴포넌트는 on/off 상태가 토글되는 버튼을 렌더링한다.
조건부 렌더링
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
위의 Greeting 컴포넌트는 isLoggedIn
의 프로퍼티에 따라서 다른 인사말을 렌더링한다.
위 컴포넌트에 로그인과 로그아웃 버튼을 나타내는 두 컴포넌트를 추가해보자.
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
논리 && 연산자로 If 를 인라인에 표현하기
JSX 안에는 중괄호를 이용하여 표현식을 포함할 수 있다.
true && expression
은 앞의 조건이 true 일때 출력된다. 조건이 false 라면 React 는 무시한다.
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
조건부 연산자로 If 를 인라인에 표현하기
condition ? true : false
를 사용할 수도 있다.
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 ? (
<h2>You have {unreadMessages.length} unread messages.</h2>
) : (
<h2>You have No messages to read.</h2>
)}
</div>
);
}
const messages = ["React", "Re: React", "Re:Re: React"];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById("root")
);
리스트와 Key
map()
함수로 이용하여 numbers list 을 반복 실행한다.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
numbers 배열을 프로퍼티로 받는 컴포넌트로 만들어보자.
이때 리스트의 요소들에 키를 넣어야 한다. 키는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트로, React 가 각 항목을 식별하는 것을 도와준다. 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 하며, 식별자가 아닌 항목의 인덱스를 key 로 사용하는 것은 추천하지 않는다. 보통 데이터의 ID 값을 Key 로 사용한다.
map()
함수 내부에 있는 엘리먼트에 키를 넣어주는게 좋다.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
State 끌어올리기
React 에서 state 를 공유하는 일은 그 값을 필요로 하는 컴포넌트간 가장 가까운 공통 조상으로 state 를 끌어올림으로써 이뤄낼 수 있다. 이를 State 끌어올리기 lifting-state-up 이라고 한다.
물이 끓는지 여부를 계산하기
주어진 온도에서 물의 끓는 여부를 추정하는 온도 계산기를 만들어보자.
function Boiling(props) {
if (props.celcius >= 100) {
return <p> boil </p>;
}
return <p> not boil </p>;
}
class Calculator extends React.Component {
constructor(props) {
super(props); // super 는 부모클래스 생성자의 참조이다. super 를 호출하기 전에는 this 를 사용할 수 없다.
// 밑의 onChange={this.handleChange} 에서 작성중인 데이터가 변할때마다 state 를 변화시킨다.
// this.handleChange.bind(this) 를 통해 handleChange 에서 선언될 this 는 Calculator 를 가리켜야 한다고 알려준다.
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
// setState 는 state 객체에 대한 업데이트를 실행한다.
// e.target.value = value property of some DOM element
// e = 이벤트 객체, e.target = 이벤트가 발생한 DOM
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
먼저 Boiling 이라는 이름의 컴포넌트를 만들었다. 이 컴포넌트는 celsius prop 을 받아서 이 온도가 물이 끓기에 충분한지의 여부를 출력한다.
그 다음으로 Calculator 라는 컴포넌트를 만든다. 이 컴포넌트는 온도를 입력할 수 있는 <input>
을 렌더링하고 그 값을 this.state.temperature
에 저장한다.
컴포넌트 분리 후 화씨 입력 필드 추가하기
위 코드는 섭씨 100도에서 물이 끓는다는 것을 계산해주는 코드이다. 여기에 화씨를 입력하는 필드를 추가하고자 한다.
먼저 class Calculator 에서 TemparatureInput 컴포넌트를 빼내는 작업을 시행해보자.
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
// Calculator 가 분리된 두 개의 온도 입력 필드를 렌더링 하도록 변경하였다.
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
이렇게 분리할 경우 Calculator
에서 Boiling 함수를 알 수 없다. 현재 입력된 온도 정보가 TemperatureInput
안에 숨겨져 있기 때문이다.
섭씨 화씨 변환기
위 코드는 Celsius 값만 받는 코드이지만, 섭씨와 화씨가 동기화 된 상태로 있기 위해서는 state 끌어올리기가 필요하다. class Calculator 가 공유될 state 를 소유하고 있으면 이를 통해 두 input 필드가 서로 일관된 값을 유지하도록 만들 수 있다.
아래 코드는 위 코드의 TemperatureInput
에 존재하던 state 를 상위 props 를 가질 Calculator
로 끌어올린 후의 코드이다.
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
// 섭씨화씨 컨버터
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
function Boiling(props) {
if (props.celsius >= 100) {
return <p> boil </p>;
}
return <p> not boil </p>;
}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
// Before: this.setState({temperature: e.target.value})
// 부모인 Calculator 로부터 props 를 건네받을 수 있다.
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
// TemperatureInput 에 존재하던 state 를 이쪽으로 끌어올렸다. 이렇게 끌어올려진 state 를 진리의 원천 source of turth 라고 한다.
// scale default 값이 'c'... 그냥 없이 string 으로 해놔도 된다.
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<Boiling
celsius={parseFloat(celsius)} />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
이제 어떤 Input 필드를 수정하더라도 Calculator
의 this.state.temperature
와 this.state.scale
이 갱신된다.
Input 값을 변경할 때에 일어나는 일의 순서는 아래와 같다.
1. 먼저 React DOM 이 <input>
의 onChange
에 지정된 함수 this.handleChange
를 호출한다.
2. TemperatureInput
컴포넌트의 handleChange
메서드는 새로 입력된 값과 함께 this.props.onTemperatureChange()
를 호출한다. 이 컴포넌트의 props는 부모 컴포넌트인 Calculator
에서 가져온 것이다.
3. Calculator
내부에는 섭씨( props 의 scale 이 c 인 경우) 가 들어오면 handleCelsiusChange
메서드를, 화씨 ( props 의 scale 이 f 인 경우) 가 들어오면 handleFahrenheitChange
메서드를 바라볼 수 있도록 바인드해두었다. 이 메서드들은 내부적으로 Calculator
컴포넌트가 this.setState()
를 호출하게 한다.
4. Calculator
는 render()
메서드를 호출한다. 여기에서 tryConvert
함수가 실행되어 섭씨온도와 화씨온도가 동기화 된다.
5. React 가 Boiling
컴포넌트에게 섭씨온도celsius props 를 건네면 ( input 값에 화씨온도를 넣어도 이미 동기화 된 섭씨온도가 존재하기 때문에 가능 ) Boiling
컴포넌트가 호출된다.
6. React DOM 이 변경된 Input 값에 알맞는 다른 온도 변환 값과 Boiling
컴포넌트의 결과 값을 갱신한다.
합성Composition
컴포넌트에 JSX 를 중첩하여 임의의 자식을 전달할 수 있다.
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
What's your name?
</p>
</FancyBorder>
);
}
ReactDOM.render(
<WelcomeDialog />,
document.getElementById('root')
);
더 구체적인 컴포넌트가 일반적인 컴포넌트를 렌더링하고 props 를 통해 내용을 구성할 수 있다.
아래의 코드는 Dialog
컴포넌트를 SignUpDialog
컴포넌트에서 렌더링 시키도록 구성한 코드이다.
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {name: ''};
}
render() {
return (
<Dialog title="Welcome"
message="What's your name?">
<input value={this.state.name}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Submit
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.name}!`);
}
}
ReactDOM.render(
<SignUpDialog />,
document.getElementById('root')
);