김태희(로토/Roto) [Travel Systems CX]
Coupang Web Developer
Coupang
Travel And Local
Systems CX (Brussel)
김태희(로토/roto)
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(<HelloMessage name="John" />, mountNode);
"use strict";
var HelloMessage = React.createClass({
displayName: "HelloMessage",
render: function render() {
return React.createElement(
"div",
null,
"Hello ",
this.props.name
);
}
});
ReactDOM.render(React.createElement(HelloMessage, { name: "John" }), mountNode);
Transpiling
// es5 style
var YourComponent = React.createClass({
render: function() {
return (
<div>Hi?</div>
);
}
});
// es6 style
class YourComponent extends React.Component {
render() {
return (
<div>Hi!</div>
);
}
}
혹은 atom 사용
// es5 style
var YourComponent = React.createClass({
render: function() {
return (
<div>Hello! React.js!</div>
);
}
});
// es6 style
class YourComponent extends React.Component {
render() {
return (
<div>Hello! React.js!</div>
);
}
}
// 틀린 예
render: function(){
return (
<div>쿠팡 최고의 동호회</div>
<div>쿠뺀으로 오세요!</div>
);
}
// 올바른 예
render: function(){
return (
<div>
<div>쿠팡 최고의 동호회</div>
<div>쿠뺀으로 오세요!</div>
</div>
);
}
render 함수가 반환하는 마크업의
최상위에는 단일 Root Node가 있어야 한다.
모든 태그는 닫혀야 한다.
// 자주하는 실수
<br>
<img src="bali.png">
<input type="text">
// 올바른 예
<br/>
<img src="bali.png"/>
<input type="text" />
attribute는 camelCase로 작성해야한다.
// 잘못된 예
<table cellpadding="5">
<tr rowspan="2">
...
</tr>
</table>
// 올바른 예
<table cellPadding="5">
<tr rowSpan="2">
...
</tr>
</table>
class는 className으로
label의 for는 htmlFor로
// 잘못된 예
<div class="wrapper">
<label for="name">이름</label>
<input id="name" />
</div>
// 올바른 예
<div className="wrapper">
<label htmlFor="name">이름</label>
<input id="name" />
</div>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="https://fb.me/react-15.2.1.js"></script>
<script src="https://fb.me/react-dom-15.2.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
// 이곳에 코드를 작성합니다.
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="https://fb.me/react-15.2.1.js"></script>
<script src="https://fb.me/react-dom-15.2.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
// HelloWorld Component 정의
var HelloWorld = React.createClass({
// Component는 render 함수를 기준으로 화면에 그려진다.
render: function () {
return (
<div>Hello React!!!</div>
);
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="https://fb.me/react-15.2.1.js"></script>
<script src="https://fb.me/react-dom-15.2.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
// HelloWorld Component 정의
var HelloWorld = React.createClass({
render: function () {
return (
<div>Hello World!!!</div>
);
}
});
// HelloWorld Comopnent 마운팅하기
ReactDOM.render(
<HelloWorld />,
document.getElementById('example')
);
</script>
</body>
</html>
// Hello Component 정의
var Hello = React.createClass({
render: function (){
return (
<span>Hello!!</span>
);
}
});
// World Component 정의
var World = React.createClass({
render: function (){
return (
<span>World!!</span>
);
}
});
// Hello와 World Component를 조합한 새로운 Component
var ThreeHelloOneWorld = React.createClass({
render: function (){
return (
<div>
<Hello />
<Hello />
<Hello />
<World />
</div>
)
}
});
ReactDOM.render(
<ThreeHelloOneWorld />,
document.getElementById('example')
);
var Band = React.createClass({
render: function() {
var bandName = 'Cou Fighters';
return (
<div>
Band Name: {bandName}
</div>
);
}
});
ReactDOM.render(
<Band />,
document.getElementById('example')
);
변수명을 {}로 감싼다.
var Band = React.createClass({
render: function() {
var bandName = this.props.bandName;
return (
<div>
Band Name: {bandName}
</div>
);
}
});
ReactDOM.render(
<Band bandName="Cou Fighters"/>,
document.getElementById('example')
);
var Band = React.createClass({
render: function() {
var bandName = this.props.bandName;
return (
<div>
Band Name: {bandName}
</div>
);
}
});
var BandLineUp = React.createClass({
render: function() {
return (
<div>
<h3>Band Line Up</h3>
<Band bandName="Cou Fighters" />
<Band bandName="삼거리 별다방" />
<Band bandName="AZ Taste" />
<Band bandName="TTB" />
</div>
);
}
});
ReactDOM.render(
<BandLineUp />,
document.getElementById('example')
);
// Timer Component 정의
var Timer = React.createClass({
// Component의 state를 정의하는 함수
getInitialState: function(){
return {
count: 0
};
},
// Component가 화면에 Mount 되면 실행되는 Life cycle 함수
componentDidMount: function(){
setInterval(this.tick, 1000);
},
// setState를 통해 상태를 갱신하는 함수
tick: function() {
this.setState({
count: this.state.count + 1
});
},
render: function () {
return (
<div>tick count {this.state.count}</div>
);
}
});
ReactDOM.render(
<Timer />,
document.getElementById('example')
);
// Timer Component 정의. props로 initialCount를 받아 자신의 state에 설정
var Timer = React.createClass({
getInitialState: function () {
return {
count: this.props.initialCount
};
},
componentDidMount: function (){
setInterval(this.tick, 1000);
},
tick: function() {
this.setState({
count: this.state.count + 1
});
},
render: function (){
return (
<div>
<TimerText count={this.state.count} />
</div>
)
}
});
// count를 props로 받아 화면에 뿌려주는 역할
var TimerText = React.createClass({
render: function (){
return (
<span>현재 Count는 {this.props.count} 이다!</span>
);
}
});
// Timer를 렌더링하면서 props로 initialCount를 넘겨줌
ReactDOM.render(
<Timer initialCount={105}/>,
document.getElementById('example')
);
// 각 케이스에 대응하는 컴포넌트 두개를 생성
var CoupangGreeting = React.createClass({
render: function() {
return <h1>Welcome to the Coupang!</h1>;
}
});
var GuestGreeting = React.createClass({
render: function() {
return <h1>Hello Guest!</h1>;
}
});
return 하는 Component를 다르게 하기
// 각 케이스에 대응하는 컴포넌트 두개를 생성
var CoupangGreeting = React.createClass({
render: function() {
return <h1>Welcome to the Coupang!</h1>;
}
});
var GuestGreeting = React.createClass({
render: function() {
return <h1>Hello Guest!</h1>;
}
});
var Greeting = React.createClass({
render: function(){
var isLoggedIn = this.props.isLoggedIn;
if(isLoggedIn){
return <CoupangGreeting />
}else{
return <GuestGreeting />
}
}
});
var LoginButton = React.createClass({
render: function() {
return (
<button>로그인 버튼</button/>
)
}
});
var LogoutButton = React.createClass({
render: function() {
return (
<button>로그아웃 버튼</button>
);
}
});
변수를 하나 만들고 condition에 따라 해당 변수에 Component를 넣은 뒤 해당 변수를 binding
var LoginButton = React.createClass({
render: function() {
return (
<button>로그인 버튼</button/>
)
}
});
var LogoutButton = React.createClass({
render: function() {
return (
<button>로그아웃 버튼</button>
);
}
});
var LoginControl = React.createClass({
render: function() {
var button = null;
if(this.props.isLoggedIn){
button = <LoginButton />;
}else{
button = <LogoutButton />
}
return (
<div>
Hello! {button}
</div>
);
}
});
&& 연산자의 특성을 이용하기
var Ticket = React.createClass({
isValidTicket() {
// 유효성 체크한 값 리턴
return true;
}
render: function () {
return (
<div>
<h3>티켓 이름: {this.props.ticketName}</h3>
{!this.isValidTicket() &&
<h2>유효기간이 만료된 티켓입니다!</h2>
}
</div>
);
}
});
삼항연산자 이용하기
var User = React.createClass({
render: function () {
return (
<div>
<h2>{this.props.isLoggedIn ? '로그인 하셨네요!' : '로그인 해주세요 T_T'}</h2>
</div>
);
}
});
var numbers = [1, 2, 3, 4, 5];
var listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>
{listItems}
</ul>
);
render: function() {
var selectedProducts = [
{
id: 1,
name: 'Fender American Standard Jazz Bass',
price: 2000000
},
{
id: 2,
name: 'Yamaha Revstar RS620 BRB',
price: 800000
}
];
var products = [];
selectedProducts.forEach(function(selectedProduct, i){
products.push(
<li key={i}>
선택하신 상품 {selectedProduct.name}의 가격은 {selectedProduct.price}원 입니다.
</li>
)
});
return (
<div>
<ul>
{products}
</ul>
</div>
);
}
var Timer = React.createClass({
propTypes: {
initialCount: React.PropTypes.number
},
......
});
var TimerText = React.createClass({
propTypes: {
count: React.PropTypes.number.isRequired
},
....
});
React.createClass({
propTypes: {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
optionalSymbol: React.PropTypes.symbol,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: React.PropTypes.node,
// A React element.
optionalElement: React.PropTypes.element,
// You can also declare that a prop is an instance of a class. This uses
// JS's instanceof operator.
optionalMessage: React.PropTypes.instanceOf(Message),
// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
// An object that could be one of many types
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// An array of a certain type
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// An object with property values of a certain type
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// An object taking on a particular shape
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// You can chain any of the above with `isRequired` to make sure a warning
// is shown if the prop isn't provided.
requiredFunc: React.PropTypes.func.isRequired,
// A value of any data type
requiredAny: React.PropTypes.any.isRequired,
// You can also specify a custom validator. It should return an Error
// object if the validation fails. Don't `console.warn` or throw, as this
// won't work inside `oneOfType`.
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// You can also supply a custom validator to `arrayOf` and `objectOf`.
// It should return an Error object if the validation fails. The validator
// will be called for each key in the array or object. The first two
// arguments of the validator are the array or object itself, and the
// current item's key.
customArrayProp: React.PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
},
/* ... */
});
propTypes에 선언된 타입과 props가 다른 경우..
isRequired로 선언된 props를 누락한 경우..
development mode에서만 check
var ClickCounter = React.createClass({
getInitialState: function() {
return {
clickCounter: 0
};
},
render: function () {
return (
<div>
현재 버튼은 {this.state.clickCounter} 번 눌렸습니다.
<button onClick={this.handleClick}>눌러봅시다!</button>
</div>
)
},
handleClick: function(){
this.setState({
clickCounter: this.state.clickCounter + 1
});
}
});
ReactDOM.render(<ClickCounter/>, document.getElementById('example'));
대략 이런 모양새
var TodoApp = React.createClass({
getInitialState: function () {
return {
todos: [
{
todoText: '쿠뺀 가입하기'
},
{
todoText: '악보 외우기'
},
{
todoText: 'Bass 연습하기'
}
]
};
},
render: function () {
return (
<div>
<TodoText />
<TodoList todos={this.state.todos}/>
</div>
);
}
});
var TodoText = React.createClass({
render: function () {
return (
<form>
<input type="text"
placeholder="할 일을 입력하세요."/>
<button type="submit">추가하기</button>
</form>
);
}
});
var TodoList = React.createClass({
propTypes: {
todos: React.PropTypes.array
},
render: function () {
var todoLiComponents = [];
var todos = this.props.todos;
todos.forEach(function(todo, i){
todoLiComponents.push(
<li key={i}>
{todo.todoText}
</li>
);
});
return (
<ul>
{todoLiComponents }
</ul>
);
}
});
var TodoText = React.createClass({
getInitialState: function () {
return {
todoText: ''
};
},
render: function () {
return (
<form>
<input type="text"
placeholder="할 일을 입력하세요."
value={this.state.todoText}
onChange={this.handleTodoTextChange}/>
<button type="submit">추가하기</button>
</form>
)
},
handleTodoTextChange: function(e) {
this.setState({
todoText: e.target.value
});
}
});
var TodoText = React.createClass({
propTypes: {
onAddTodo: React.PropTypes.func.isRequired
},
getInitialState: function () {
return {
todoText: ''
};
},
render: function () {
return (
<form onSubmit={this.handleSubmit}>
<input type="text"
placeholder="할 일을 입력하세요."
value={this.state.todoText}
onChange={this.handleTodoTextChange}/>
<button type="submit">추가하기</button>
</form>
)
},
handleTodoTextChange: function(e) {
this.setState({
todoText: e.target.value
});
},
handleSubmit: function(e) {
e.preventDefault();
this.props.onAddTodo(this.state.todoText);
this.setState({
todoText: ''
});
}
});
var TodoApp = React.createClass({
getInitialState: function () {
return {
todos: [
{
todoText: '쿠뺀 가입하기'
},
{
todoText: '악보 외우기'
},
{
todoText: 'Bass 연습하기'
}
]
};
},
render: function () {
return (
<div>
<TodoText onAddTodo={this.handleAddTodo}/>
<TodoList todos={this.state.todos} />
</div>
)
},
handleAddTodo: function(todoText) {
// 기존 state의 todos 를 clone
var newTodos = this.state.todos.slice();
newTodos.push({
todoText: todoText
});
this.setState({
todos: newTodos
});
}
});
var TodoApp = React.createClass({
getInitialState: function () {
return {
todos: []
};
},
componentDidMount: function() {
$.get('http://demo4539895.mockable.io/todo')
.done(function(result){
this.setState({
todos: result
});
}.bind(this));
},
render: function () {
return (
<div>
<TodoText onAddTodo={this.handleAddTodo}/>
<TodoList todos={this.state.todos} />
</div>
)
},
handleAddTodo: function(todoText) {
// 기존 state의 todos 를 clone
var newTodos = this.state.todos.slice();
newTodos.push({
todoText: todoText
});
this.setState({
todos: newTodos
});
}
});
var TodoApp = React.createClass({
...
render: function () {
return (
<div>
<TodoText onAddTodo={this.handleAddTodo}/>
{// onCompleted props 추가 }
<TodoList todos={this.state.todos}
onCompleted={this.handleCompleted}/>
</div>
)
},
handleCompleted: function(index) {
// props로 넘어간 이 핸들러가 호출되면서 파라메터로 완료처리할 todo의 index를 넘긴다
// clone 후 값을 바꾸고 setState
var newTodos = this.state.todos.slice();
newTodos[index].isCompleted = true;
// state가 바뀌면서 다시 화면이 그려짐
this.setState({
todos: newTodos
});
}
...
});
var TodoList = React.createClass({
propTypes: {
todos: React.PropTypes.arrayOf(React.PropTypes.shape({
todoText: React.PropTypes.string,
isCompleted: React.PropTypes.bool
})).isRequired,
onCompleted: React.PropTypes.func.isRequired
},
render: function () {
var todoLis = [];
var todos = this.props.todos;
todos.forEach(function(todo, i){
// todo의 isCompleted 값에 따라 동적으로 completed className을 설정
todoLis.push(
<li key={i}
className={todo.isCompleted ? 'completed' : ''}
onClick={this.handleClick}>
{todo.todoText}
</li>
)
}.bind(this));
return (
<ul>
{todoLis}
</ul>
)
},
// todo 클릭 시 이벤트 핸들러. 이벤트가 일어난 지점의 index를 구해서 props의 onCompleted Callback 호출
handleClick: function (e) {
this.props.onCompleted($(e.target).index());
}
});
var TodoStatus = React.createClass({
propTypes: {
todos: React.PropTypes.array.isRequired
},
render: function (){
var todos = this.props.todos;
var completedCount = todos.filter(function(todo){
return todo.isCompleted;
}).length;
return (
<div>
{todos.length} 개의 할 일 중에 {completedCount}개가 완료됨
</div>
)
}
});
Todo의 상태를 보여주는 TodoStatus Component 추가
...
render() {
return (
<div>
<TodoText onAddTodo={this.handleAddTodo}/>
<TodoList todos={this.state.todos} onCompleted={this.handleCompleted}/>
<TodoStatus todos={this.state.todos} />
</div>
)
}
...
TodoApp render 에 새 Component 추가
npm install -g create-react-app
class HelloES6React extends React.Component {
render() {
const {name} = this.props;
return (
<div>hi!!!{name}</div>
)
}
}
HelloES6React.propTypes = {
name: React.PropTypes.string.isRequired
};
ReactDOM.render(<HelloES6React name="로토" />, document.getElementById('example'));
import React from 'react';
class TodoText extends React.Component {
constructor(props) {
super(props);
this.state = {
todoText: ''
};
this.handleTodoTextChange = this._handleTodoTextChange.bind(this);
this.handleSubmit = this._handleSubmit.bind(this);
}
_handleTodoTextChange(e) {
this.setState({
todoText: e.target.value
});
}
_handleSubmit(e) {
e.preventDefault();
this.props.onAddTodo(this.state.todoText);
this.setState({
todoText: ''
});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text"
placeholder="할 일을 입력하세요."
value={this.state.todoText}
onChange={this.handleTodoTextChange}/>
<button type="submit">추가하기</button>
</form>
)
}
}
export default TodoText;
import React from 'react';
class TodoList extends React.Component {
render() {
let todoLiComponents = [];
const todos = this.props.todos;
todos.forEach((todo, i) => {
todoLiComponents.push(
<li key={i}>
{todo.todoText}
</li>
);
});
return (
<ul>
{todoLiComponents }
</ul>
);
}
}
export default TodoList;
import React from 'react';
import TodoText from './TodoText';
import TodoList from './TodoList';
class TodoApp extends React.Component {
constructor() {
super();
this.state = {
todos: []
};
this.handleAddTodo = this._handleAddTodo.bind(this);
}
_handleAddTodo(todoText) {
var newTodos = this.state.todos.slice();
newTodos.push({
todoText: todoText
});
this.setState({
todos: newTodos
});
}
render() {
return (
<div>
<TodoText onAddTodo={this.handleAddTodo}/>
<TodoList todos={this.state.todos}/>
</div>
)
}
}
export default TodoApp;
최초 DOM Mounting은 이곳에서.
import React from 'react';
import ReactDOM from 'react-dom';
import TodoApp from './TodoApp';
import './index.css';
ReactDOM.render(
<TodoApp />,
document.getElementById('root')
);
class TodoApp extends React.Component {
// constructor 생략
state = {
todos: []
}
componentDidMount () {
$.get('http://demo4539895.mockable.io/todo')
.done((result) => {
this.setState({
todos: result
});
});
}
render() {
return (
<div>
<TodoText onAddTodo={this.handleAddTodo}/>
<TodoList todos={this.state.todos}
onCompleted={this.handleCompleted}/>
</div>
)
}
handleAddTodo = (todoText) => {
// 기존 state의 todos 를 clone
var newTodos = this.state.todos.slice();
newTodos.push({
todoText: todoText
});
this.setState({
todos: newTodos
});
};
class TodoList extends React.Component {
static propTypes = {
todos: React.PropTypes.array,
onCompleted: React.PropTypes.func.isRequired
};
render () {
var todoLis = [];
var todos = this.props.todos;
todos.forEach((todo, i) => {
todoLis.push(
<li key={i}
className={todo.isCompleted ? 'completed' : ''}
onClick={this.handleClick}>
{todo.todoText}
</li>
)
});
return (
<ul>
{todoLis}
</ul>
)
}
handleClick = (e) => {
this.props.onCompleted($(e.target).index());
};
}
By 김태희(로토/Roto) [Travel Systems CX]
coupang 쿠키 8기 react.js 강의용 슬라이드