이 튜토리얼은 2018년에 새로운 강의로 재작성되었습니다 [새 튜토리얼 보기]
이번 강좌에서는 Component 에서 사용 할 데이터를 다루는 State 와 Props 에 대하여 알아보겠습니다.
1. 시작하기
강좌 4편 – Component 생성 및 모듈화 에서 사용하던 프로젝트를 계속해서 사용하겠습니다.
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>React App</title> </head> <body> <div id="root"></div> <script src="bundle.js"></script> </body> </html>
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './components/App'; const rootElement = document.getElementById('root'); ReactDOM.render(<App />, rootElement);
src/components/App.js
import React from 'react'; import Header from './Header'; import Content from './Content'; class App extends React.Component { render(){ return ( <div> <Header/> <Content/> </div> ); } } export default App;
src/components/Header.js
import React from 'react'; class Header extends React.Component { render(){ return ( <h1>Header</h1> ); } } export default Header;
src/components/Conent.js
import React from 'react'; class Content extends React.Component { render(){ return ( <div> <h2>Content</h2> <p> Hey! </p> </div> ); } } export default Content;
2. props
props
는 컴포넌트에서 사용 할 데이터 중 변동되지 않는 데이터를 다룰 때 사용됩니다. parent 컴포넌트에서 child 컴포넌트로 데이터를 전할 때, props
가 사용됩니다. 예제를 통하여 이에 대하여 알아보겠습니다.
2.1 props 추가하기
컴포넌트에서 immutable (변하지 않는) 데이터가 필요 할 땐,
render()
메소드의 내부에 안에 { this.props.propsName }
형식으로 넣고,
컴포넌트를 사용 할 때, < >
괄호 안에 propsName="value"
를 넣어 값을 설정합니다.
Header 컴포넌트와 Content 컴포넌트가 props를 사용하도록 업데이트 해보겠습니다.
Header.js
import React from 'react'; class Header extends React.Component { render(){ return ( <h1>{ this.props.title }</h1> ); } } export default Header;
위와 같이 props 값이 렌더링 될 위치에 { this.props.propsName }
를 넣습니다.
Content.js
import React from 'react'; class Content extends React.Component { render(){ return ( <div> <h2>{ this.props.title }</h2> <p> { this.props.body } </p> </div> ); } } export default Content;
contentTitle 와 contentBody props 를 넣어주었습니다.
2.2 props 사용하기
자, 이제 App 컴포넌트에도 props 를 넣어주고, App 컴포넌트에서 사용되는 props 값을 child 컴포넌트들로 전달하겠습니다.
App.js
import React from 'react'; import Header from './Header'; import Content from './Content'; class App extends React.Component { render(){ return ( <div> <Header title={ this.props.headerTitle }/> <Content title={ this.props.contentTitle } body={ this.props.contentBody }/> </div> ); } } export default App;
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './components/App'; const rootElement = document.getElementById('root'); ReactDOM.render(<App headerTitle = "Welcome!" contentTitle = "Stranger," contentBody = "Welcome to example app"/>, rootElement);
2.3 기본 값 설정하기
props 값을 임의로 지정해주지 않았을 때 사용할 기본값을 설정하는 방법을 알아보도록 하겠습니다.
기본값을 설정 할 땐, 컴포넌트 클래스 하단에 className.defaultProps = { propName: value }
를 삽입하면 됩니다.
App.js
import React from 'react'; import ReactDOM from 'react-dom'; import Header from './Header'; import Content from './Content'; class App extends React.Component { render(){ return ( <div> <Header title={ this.props.headerTitle }/> <Content title={ this.props.contentTitle } body={ this.props.contentBody }/> </div> ); } } App.defaultProps = { headerTitle: 'Default header', contentTitle: 'Default contentTitle', contentBody: 'Default contentBody' }; export default App;
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './components/App'; const rootElement = document.getElementById('root'); ReactDOM.render(<App />, rootElement);
2.4 Type 검증(Validate)하기
컴포넌트 에서 원하는 props 의 Type 과 전달 된 props 의 Type 이 일치하지 않을 때 콘솔에서 오류 메시지가 나타나게 하고 싶을 땐, 컴포넌트 클래스의 propTypes
객체를 설정하면 됩니다. 또한, 이를 통하여 필수 props 를 지정할 수 있습니다. 즉, props 를 지정하지 않으면 콘솔에 오류 메시지가 나타납니다.
한번 Content 컴포넌트의 propTypes
을 지정 해 볼까요?
Content.js
import React from 'react'; class Content extends React.Component { render(){ return ( <div> <h2>{ this.props.title }</h2> <p> { this.props.body } </p> </div> ); } } Content.propTypes = { title: React.PropTypes.string, body: React.PropTypes.string.isRequired }; export default Content;
두 props 의 Type 를 모두 string 을 지정하고,
body는 .isRequired 를 추가하여 필수 props 로 설정하였습니다.
자, 이제 App 컴포넌트에서 잘못된 값을 줘보도록 하겠습니다.
App.js
import React from 'react'; import ReactDOM from 'react-dom'; import Header from './Header'; import Content from './Content'; class App extends React.Component { render(){ return ( <div> <Header title={ this.props.headerTitle }/> <Content title={ this.props.contentTitle } body={ this.props.contentBody }/> </div> ); } } App.defaultProps = { headerTitle: 'Default header', contentTitle: 5, contentBody: undefined }; export default App;
contentTitle 엔 숫자를 지정하였고, contentBody에는 빈 값을 전달하도록 설정하였습니다.
Validation이 실패하면 브라우저에서 다음과 같은 오류가 나타납니다.
테스트가 끝나면 2.3 의 App.js 상태로 되돌리세요.
예제
예제를 통하여 여러 종류의 Type 를 Validate 하는 방법을 알아보도록 하겠습니다. (reference: React.js 메뉴얼)
import React from 'react'; class ValidationExample extends React.Component { /* ... */ } Content.propTypes = { // JS primitive types optionalArray: React.PropTypes.array, optionalBool: React.PropTypes.bool, optionalFunc: React.PropTypes.func, optionalNumber: React.PropTypes.number, optionalObject: React.PropTypes.object, optionalString: React.PropTypes.string, // anything that can be rendered ( numbers, string, elements, array, fragment) optionalNode: React.PropTypes.node, // React element optionalElement: React.PropTyps.element, // instance of specific class optionalMessage: React.PropTypes.instanceOf(Message), // limited to specific values optionalEnum: React.PropTypes.oneOf(['News', 'Photos']), // one of many types optionalUnion: React.PropTypes.oneOfType([ React.PropTypes.string, React.propTypes.number ]), // array of specific type optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), // object with property values of a certain type optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), // object with particular shape optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number }), // Required function requiredFunc: React.PropTypes.func.isRequired, // Required prop with any data type requiredAny: React.PropTypes.any.isRequired, // custom validator customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error('Validation failed!'); } } }; /* ... */ export default ValidationExample;
3. State
컴포넌트에서 유동적인 데이터를 다룰 때, state 를 사용합니다.. React.js 어플리케이션을 만들 땐, state를 사용하는 컴포넌트의 갯수를 최소화 하는 것 을 노력해야합니다. 예를들어, 10 개의 컴포넌트에서 유동적인 데이터를 사용 하게 될 땐, 각 데이터에 state를 사용 할 게 아니라, props 를 사용하고 10 개의 컴포넌트를 포함시키는 container 컴포넌트를 사용하는것이 효율적입니다.
3.1 기본적인 사용 방법
StateExample.js (미리보기: JSFiddle)
import React from 'react'; class StateExample extends React.Component { constructor(props) { super(props); this.state = { header: "Header Initial state", content: "Content Initial State" }; } updateHeader(text){ this.setState({ header: "Header has changed" }); } render() { return ( <div> <h1>{this.state.header}</h1> <h2>{this.state.content}</h2> <button onClick={this.updateHeader.bind(this)}>Update</button> </div> ); } } export default StateExample;
- state 의 초기 값을 설정 할 때는 constructor(생성자) 메소드에서
this.state= { }
를 통하여 설정합니다. - state 를 렌더링 할 때는
{ this.state.stateName }
을 사용합니다. - state 를 업데이트 할 때는
this.setState()
메소드를 사용합니다. ES6 class에선 auto binding이 되지 않으므로, setState 메소드를 사용 하게 될 메소드를 bind 해주어야 합니다. (bind 하지 않으면 React Component 가 가지고있는 멤버 함수 및 객체에 접근 할 수 없습니다.)
4. 적용: State 와 Props
유동적인 데이터를 렌더링하며, parent 컴포넌트와 communicate 하는 예제 컴포넌트 RandomNumber 를 만들어봅시다.
RandomNumber.js
import React from 'react'; import ReactDOM from 'react-dom'; class RandomNumber extends React.Component { updateNumber(){ let value = Math.round(Math.random()*100); this.props.onUpdate(value); } constructor(props){ super(props); this.updateNumber = this.updateNumber.bind(this); } render(){ return ( <div> <h1>RANDOM NUMBER: { this.props.number }</h1> <button onClick={this.updateNumber}>Randomize</button> </div> ); } } export default RandomNumber;
랜덤 숫자를 나타내는 h1 element와, 클릭 하면 새로운 랜덤값으로 바꾸는 button element를 렌더링 합니다.
이 컴포넌트에서는 두가지 prop을 사용합니다.
- number: 랜덤 값
- onUpdate: function 형태의 prop 으로서, parent 컴포넌트에 정의된 메소드를 실행 할 수 있게 합니다.
코드 설명
- Line 8: props 로 받은 함수를 실행합니다.
- Line 11 ~ 14: React 컴포넌트의 생성자입니다. super(props) 로 상속받은 React.Component 의 생성자 메소드를 실행 한 후, 저희가 입력한 코드를 실행합니다.. 13번 줄에서는 update 메소드에서 this.props 에 접근 할 수 있도록 binding 을 해줍니다.
- Line 20: 버튼을 클릭하였을 시 update() 메소드를 실행합니다.
이제, parent 컴포넌트인 App 컴포넌트에서 RandomNumber 컴포넌트를 사용해봅시다.
App.js
import React from 'react'; import ReactDOM from 'react-dom'; import Header from './Header'; import Content from './Content'; import RandomNumber from './RandomNumber'; class App extends React.Component { constructor(props){ super(props); this.state = { value: Math.round(Math.random()*100) }; this.updateValue = this.updateValue.bind(this); } updateValue(randomValue){ this.setState({ value: randomValue }); } render(){ return ( <div> <Header title={ this.props.headerTitle }/> <Content title={ this.props.contentTitle } body={ this.props.contentBody }/> <RandomNumber number={this.state.value} onUpdate={this.updateValue} /> </div> ); } } App.defaultProps = { headerTitle: 'Default header', contentTitle: 'Default contentTitle', contentBody: 'Default contentBody' }; export default App;
코드 설명
- Line 5: RandomNumber.js 를 import 합니다.
- Line 12: 초기 state 를 설정합니다.
- Line 16: updateValue() 메소드에서 this.setState 에 접근 할 수 있도록 bind 합니다.
- Line 20~22: state 를 변경 할 때는 setState({key: value}) 메소드 를 사용합니다.
- Line 31-32: RandomNumber 컴포넌트를 사용합니다.
출력물
마치면서..
props 와 state, 생긴건 비슷하지만 용도는 다릅니다. 헷갈리지 않도록 다음 특성을 기억하세요.
특성 | props | state |
---|---|---|
parent 컴포넌트에 의해 값이 변경 될 수 있는가? | 예 | 아니오 |
컴포넌트 내부에서 변경 될 수 있는가? | 아니오 | 예 |
이 포스트에서 사용된 코드는 GitHub 에서 리뷰 할 수 있습니다.
다음 강좌에서는 비슷한 코드를 반복해서 렌더링할 때 사용 되는 keys 와 map 개념에 대하여 알아보겠습니다.