[React 교과서] 6장 React 에서 이벤트 다루기

리엑트 교과서를 정리한 내용입니다.

6.1 React에서 DOM이벤트 다루기

아래 예제 코드는 이벤트 리스너에서 this를 콘솔에 표시하도록 한 코드. 여기서 event 객체는 내장 DOM이벤트를 객체를 개선한 것으로 합성 이벤트라고 부름

1
2
3
4
5
<button onclick={(function(event){
console.log(this.event)
}).bind(this)}>
Save
</button>

아래 경우에 bind(tnis)로 바인딩하지 않는다.

  • this를 이용해서 해당 클래스를 참조 할 필요가 없을때
  • ES6+ 클래스 대신 예쩐방식인 React.createClass()를 사용할때. 이때는 createClass()가 자동으로 바인딩함.
  • 화살표 함수(() => {})를 사용할 때

Reaact버전 15에서 지원하는 DOM 이벤트

이벤트 분류 React가 지원하는 이벤트
마우스 이벤트 onClick, onContentMenu,
onDoubleClick, onDrag,
onDragEnd, onDragEnter,
onDragExit, onDragLeave,
onDragOver, onDragStart,
onDrop, onMouseDown,
onMouseEnter, onMouseLeave,
onMouseMove, onMouseOut,
onMouseOver, onMouseUp
키보드 이벤트 onKeyDown, onKeyPress, onKeyup
클립보드 이벤트 onCopy, onCut, onPaste
폼 이벤트 onChange, onInpute,
onSubmit, onTnvalid
포커스 이벤트 onFocus, onBlur
터치 이벤트 onTouchCancel, onTouchEnd,
onTouchMove, onTouchStart
UI이벤트 onScroll
휠 이벤트 onWheel
영역선택 이벤트 onSelect
이미지 이벤트 onLoad, onError
애니메이션 이벤트 onAnimationStart, onAnimationEnd,
onAnimationIteration
트랜지션 이벤트 onTransitionEnd

6.1.1 캡쳐 및 버블링 단계

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Mouse extends React.Component {
render() {
return <div>
<div
style={{border: '1px solid red'}}
onMouseOverCapture={((event)=>{
console.log('mouse over on capture event')
console.dir(event, this)}).bind(this)}
onMouseOver={((event)=>{
console.log('mouse over on bubbling event')
console.dir(event, this)}).bind(this)} >
Open DevTools and move your mouse cursor over here
</div>
</div>
}
}

6.1.2 React이벤트 살펴보기

6.1.3 React 합성 이벤트 객체 다루기

1
2
3
4
5
6
7
8
9
10
11
12
13
class Mouse extends React.Component {
render() {
return <div>
<div
style={{border: '1px solid red'}}
onMouseOver={((event)=>{
console.log('mouse is over with event')
console.dir(event)}).bind(this)} >
Open DevTools and move your mouse cursor over here
</div>
</div>
}
}

6.1.4 이벤트와 상태 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Content extends React.Component {
constructor(props) {
super(props)
this.state = {counter: 0}
}
handleClick(event) {
this.setState({counter: ++this.state.counter})
}
render() {
return (
<div>
<button
onClick={this.handleClick.bind(this)}
className="btn btn-primary">
Don't click me {this.state.counter} times!
</button>
</div>
)
}
}

6.1.5 이벤트 핸들러를 속성으로 전달하기

1
2
3
4
5
6
7
8
9
class ClickCounterButton extends React.Component {
render() {
return <button
onClick={this.props.handler}
className="btn btn-danger">
Increase Volume (Current volume is {this.props.counter})
</button>
}
}

6.1.6 컴포넌트 간의 데이터 교환

1
2
3
4
5
6
7
8
9
class ClickCounterButton extends React.Component {
render() {
return <button
onClick={this.props.handler}
className="btn btn-info">
Don't touch me with your dirty hands!
</button>
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Content extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {counter: 0}
}
handleClick(event) {
this.setState({counter: ++this.state.counter})
}
render() {
return (
<div>
<ClickCounterButton handler={this.handleClick}/>
<br/>
<Counter value={this.state.counter}/>
</div>
)
}
}

6.2 React가 지원하지 않는 DOM이벤트 처리하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Radio extends React.Component {
constructor(props) {
super(props)
this.handleResize = this.handleResize.bind(this)
let order = props.order
let i = 1
this.state = {
outerStyle: this.getStyle(4, i),
innerStyle: this.getStyle(1, i),
selectedStyle: this.getStyle(2, i),
taggerStyle: {top: order*20, width: 25, height: 25}
}
}
getStyle(i, m) {
let value = i*m
return {
top: value,
bottom: value,
left: value,
right: value,
}
}
componentDidMount() {
window.addEventListener('resize', this.handleResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize)
}
handleResize(event) {
let w = 1+ Math.round(window.innerWidth / 300)
this.setState({
taggerStyle: {top: this.props.order*w*10, width: w*10, height: w*10},
textStyle: {left: w*13, fontSize: 7*w}
})
}
render() {
return <div>
<div className="radio-tagger" style={this.state.taggerStyle}>
<input type="radio" name={this.props.name} id={this.props.id}>
</input>
<label htmlFor={this.props.id}>
<div className="radio-text" style={this.state.textStyle}>{this.props.label}</div>
<div className="radio-outer" style={this.state.outerStyle}>
<div className="radio-inner" style={this.state.innerStyle}>
<div className="radio-selected" style={this.state.selectedStyle}>
</div>
</div>
</div>
</label>
</div>
</div>
}
}

6.3 React를 다른 라이브러리와 통합하기: jQurey UI이벤트

6.3.1. 버튼 통합하기

방법1 jQuery슬라이더를 위한 이벤트를 React컴포넌트에서 등록하는 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class SliderButtons extends React.Component {
constructor(props) {
super(props)
this.state = {sliderValue: 0}
this.handleSlide = this.handleSlide.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleSlide(event, ui) {
this.setState({sliderValue: ui.value})
}
handleChange(value) {
return ()=> {
$('#slider').slider('value', this.state.sliderValue + value)
this.setState({sliderValue: this.state.sliderValue + value})
}
}
componentDidMount() {
$('#slider').on('slide', this.handleSlide)
}
componentWillUnmount() {
$('#slider').off('slide', this.handleSlide)
}
render() {
return <div>
<button disabled={(this.state.sliderValue<1) ? true : false}
className="btn default-btn"
onClick={this.handleChange(-1)}>
1 Less ({this.state.sliderValue - 1})
</button>
<button disabled={(this.state.sliderValue>99) ? true : false}
className="btn default-btn"
onClick={this.handleChange(1)}>
1 More ({this.state.sliderValue + 1})
</button>
</div>
}
}

6.3.2 라벨 통합하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SliderValue extends React.Component {
constructor(props) {
super(props)
this.handleSlide = this.handleSlide.bind(this)
this.state = {sliderValue: 0}
}
handleSlide(event) {
this.setState({sliderValue: event.detail.ui.value})
}
componentDidMount() {
window.addEventListener('slide', this.handleSlide)
}
componentWillUnmount() {
window.removeEventListener('slide', this.handleSlide)
}
render() {
return <div className="" >
Value: {this.state.sliderValue}
</div>
}
}

[React 교과서] 5장 React 컴포넌트 라이프사이클 이벤트

리엑트 교과서를 정리한 내용입니다.

5.1 React컴포넌트 라이프사이클 이벤트 한눈에 살펴보기

라이프사이클 이벤트를 기반으로 컴포넌트의 동작을 제어하고 사용자 정의를 할 수 있다.

  • 마운팅 이벤트 : React엘리먼트를 DOM 노드에 추가할때 발생
  • 갱신 이벤트 : 속성이나 상태가 변경되어 React엘리먼트를 갱신할 때 발생
  • 언마운팅이벤트 : React엘리먼트를 DOM에서 제거할때 발생

5.2 이벤트 분류

  • 마운팅 : React가 이벤트를 한번만 실행
  • 갱신: React가 이벤트를 여러번 실행
  • 언마운팅: React가 이벤트를 한번만 실행

아래 순서로 라이플 사이클이 실행된다

  1. constructor() : 엘리먼트를 생성하여 기본속성과 상태를 설정할때 실행
  2. 마운팅
    • componentWillMount(): DOM에 삽입전에 실행
    • componentDidMount(): DOM에 삽입 후에 실행
  3. 갱신
    • componentWillRecetiveProps(nextProps) : 컴포넌트가 속성을 받기 직전에 실행
    • shouldComponentUpdate(nextProps, nextState) : 컴포넌트가 갱신되는 조건을 정의해서 재렌더링을 초적화함. boolean값을 반환
    • componentWillUpdate(nextProps, nextState) : 컴포넌트가 갱신되기 직전에 실행
    • componentDidUpdate(prevProps, prevState) : 컴포넌트가 갱신된 후에 실행
  4. 언마운팅
    • componentWillUnmount() : 컴포넌트를 DOM에서 제거하기 전에 실행, 구독한 이벤트를 제거하거나 다른 정리 작업 수행 가능
  • 라이플사이클 이벤트와 속성 및 상태의 상관관계
마운팅 컴포넌트 속성 갱신 컴포넌트 상태 갱신
constructor()
componentWillMount() componentWillRecetiveProps()
shouldComponentUpdate() shouldComponentUpdate()
componentWillUpdate() componentWillUpdate()
render() render() render()
componentDidUpdate() componentDidUpdate()
componentDidMount()
forceUpdate()를 이용한 갱신 언마운팅
componentWillUpdate()
render()
componentDidUpdate()
componentWillUnmount()

5.3 이벤트 구현

라이프사이클 이벤트를 구현하려면 클래스에 메서드를 정의해야함.
ex) componentDidMouht()를 정의하면 컴포넌트 클래스의 엘리먼트가 DOM에 추가되었을때 이 메서드 호출하고 마운팅으로 분류 되어있으므로 컴포넌트 클래스의 인스턴스마다 한번만 호출됨.

1
2
3
4
5
6
7
8
9
10
11
12

class Content extends React.Component {
componentWillMount(){
console.log(ReactDOM.findDOMNode(this)) // DOM 노드가 null
}
componentDidMount(){
console.dir(ReactDOM.findDOMNode(this)) // DOM 노드가 <div>
}
render(){
return()
}
}
  • DOM은 늘 대문자로 쓴다.

5.4 모든 이벤트 실행하기

  • Logger컴포넌트의 렌더링과 세번의 갱신 실행 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Content extends React.Component {
    constructor(props) {
    super(props)
    this.launchClock()
    this.state = {
    counter: 0,
    currentTime: (new Date()).toLocaleString()
    }
    }
    launchClock() {
    setInterval(()=>{
    this.setState({
    counter: ++this.state.counter,
    currentTime: (new Date()).toLocaleString()
    })
    }, 1000)
    }
    render() {
    if (this.state.counter > 2) return <div/>
    return <Logger time={this.state.currentTime}></Logger>
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Logger extends React.Component {
constructor(props) {
super(props)
console.log('constructor')
}
componentWillMount() {
console.log('componentWillMount is triggered')
}
componentDidMount(e) {
console.log('componentDidMount is triggered')
console.log('DOM node: ', ReactDOM.findDOMNode(this))
}
componentWillReceiveProps(newProps) {
console.log('componentWillReceiveProps is triggered')
console.log('new props: ', newProps)
}
shouldComponentUpdate(newProps, newState) {
console.log('shouldComponentUpdate is triggered')
console.log('new props: ', newProps)
console.log('new state: ', newState)
return true
}
componentWillUpdate(newProps, newState) {
console.log('componentWillUpdate is triggered')
console.log('new props: ', newProps)
console.log('new state: ', newState)
}
componentDidUpdate(oldProps, oldState) {
console.log('componentDidUpdate is triggered')
console.log('old props: ', oldProps)
console.log('old state: ', oldState)
}
componentWillUnmount() {
console.log('componentWillUnmount')
}
render() {
console.log('rendering... Display')
return (
<div>{this.props.time}</div>
)
}
}

5.5 마운팅 이벤트

마운팅 이벤트 유형은 실제 DOM에 컴포넌트를 추가하는 것에 대한 이벤트다.

  • componentWillMount(): React엘리먼트가 실제 DOM에 곧 추가 될 것을 알림
  • componentDidMount(): React엘리먼트를 실제 DOM에 추가한 시전으로, 이 시점의 React엘리먼트는 DOM노드임.
    *constructor()는 componentWillMount()보다 먼저 실행됨.

5.5.1 componentWillMount()

초기 렌더링 직전에 실행됨. 브라우저와 프론트엔드에서 이루어짐.
서버 렌더링 과정에서도 componentWillMount()는 실행됨.

5.5.2 componentDidMount()

초기렌더링 마친 후 실행됨. 브라우저에서 한번 실행되고 서버렌더링에서는 실행 안 됨.
자식엘리먼트를 참조로 접근할 수 있음. 자식 컴포넌트의 componentDidMount()는 부모 컴포넌트의 componentDidMount()보다 먼저 호출됨

componentDidMount() 이벤트는 다른 라이브러리를 통합하기 가장 적절한 위치임.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Users extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [] // 이렇게 초기화 해놔야 render()에서 해당 상태가 존재하는지 안 하는지 신경 안쓸 수 있음
}
}
componentDidMount() {
fetch(this.props['data-url'])
.then((response)=>response.json())
.then((users)=>this.setState({users: users}))
}
render() {
return <div className="container">
<h1>List of Users</h1>
<table className="table-striped table-condensed table table-bordered table-hover">
<tbody>{this.state.users.map((user)=>
<tr key={user.id}>
<td>{user.first_name} {user.last_name}</td>
<td> {user.email}</td>
<td> {user.ip_address}</td>
</tr>)}
</tbody>
</table>
</div>
}
}

XHR 요청을 위한 코드를 componentDidMount()에 사용하는 것이 좋음

5.6 갱신 이벤트

마운팅 이벤트는 React를 다른 프레임워크나 라이브러리 데이터 저장소와 연결하는데 사용하곤 함.
갱신 이벤트는 컴포넌트를 갱신하는것과 관련됨.

  1. componentWillRecivierProps(newProps)
  2. shouldComponentUpdate()
  3. componentWillIpdate()
  4. componentDidUpdate()

5.7 언마운팅 이벤트

DOM에서 요소를 분리하거나 제거하는것

5.7.1 componentWillUnmount()

DOM에서 컴포넌트가 제거되기 직전 호출.
ex)타이머 제거하거나 DOM요소 정리하거나, componentDidMount()에서 연결한 이벤트 제거 등.

[React 교과서] 4장 React 컴포넌트의 객체 상태

리엑트 교과서를 정리한 내용입니다.

리엑트에서 가장 중요한 부분이다.

4.1 React컴포넌트의 상태란?

React의 상태는 컴포넌트의 변경 가능한 데이터 저장소이다.
컴포넌트를 속성과 상태가 있는 함수라고 생각하면 함수의 결과가 UI표현(뷰)이다.
속성과 상태는 모두 뷰를 갱신하기 위해 사용하지만 목적이 다름.

  • 상태:
    상태는 이름을 활용하여 접근함. this.state 객체속성 ex) this.state.inputFileValue
    뷰의 랜더링이 갱신될 때 동적 정보를 출력하기 위해 사용됨.
    관련된 부분만 갱신됨.

아래 안티 패턴 예시
상태 외의 다른 값을 변경해도 뷰를 갱신할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let inputValue ='Texas'
class Autocomplete extends React.Component {
updateValues(){ //--사용자 입력에 의해 실행됨
this.props.inputValue = 'California'
inputValue ='California'
this.inputValue='California'

}
render(){
return(
<div>
{this.props.inputValue}
{inputValue}
{this.inputValue}
</div>
)
}
}

속성은 부모 컴포넌트에서 새로운 값을 전달하면 뷰를 갱신하여 현재 다루는 자식 컴포넌트의 새로운 인스턴스를 생성함.
해당 자식 컴포넌트의 컨텍스트내에서 this.props.inputValue=’California’로 변경해도 소용이 없음!

4.2 상태 객체 다루기

4.2.1 상태 객체에 접근하기

맴버변수로 this 를 통해 접근 가능. this.state.name와 같은 방식으로 접근.
render()에서 this.state를 랜더링 할 수 있음. ex) {this.state.inputFieldValue}

  • 현재 시간 출력하는 시계 구현하기
    • 폴더 구조
      /clock
      index  
      
      /jsx
      script.jsx  
      clock.jsx  
      
      /js
      script.js  
      clock.js  
      react.js  
      react-dom.js  
      
1
2
3
4
5
6
7
8
9
10
11

class Clock extends React.Component{
render(){
return <div>{this.state.currentTime}</div>
}
}

ReactDOM.render(
<Clock/>
document.getElementById('content')
)

위와 같이 하면 ‘currentTime’ 값이 null이라는 오류가 발생한다.
속성과 달리 상태는 부모 객체에서 설정하는것이 아니고 render()안에서 setState를 실행 할 수도 없다.

4.2.2 초기 상태 설정하기

초기 상태를 설정하려면 React.Component를 사용하는 ES6클래스의 생성자에서 this.state를 선언함. 반드시 super()에 속성을 전달하여 실행한다.

1
2
3
4
5
6
7
8
9
class MyFancyComponent extends React.Component{
constructor(props){
super(props)
this.state = {...}
}
render(){
...
}
}

초기 상태를 설정하면서 다른 로직도 추가 가능.
ex) new Date()를 사용하여 currentTime 값을 설정함. toLocaleString()을 사용하면 상용자 위치에 맞는 적절한 날짜시간 형식을 보여줄 수 있음.

1
2
3
4
5
6
7
class Clock extends React.Component{
constructor(props){
super(props)
this.state={currentTime: (new DAte()).toLocaleString('en')}
}
...
}

초기 상태 주의사항

  • this.state는 반드시 객체여야함.
  • 생성자 메서드의 이름은 반드시 constructor로 함.
  • 부며 클래스가 있는 클래스에서 construtcor()매서드를 생성하면 항상 super()를 호출함.
  • 상속으로 클래스를 구현하면 constructor()매서드를 따로 작성하지 않으면 super()를 호출한것으로 가정함.
  • constructor()메서드 내에서 한번만 this.state로 직접 상태를 선언하고 그 외 부분에서는 this.stat=…로 직접 상태 선언하지 않는다.

4.2.3 상태 갱신하기

클래스 매서드인 this.setState(data, callback)를 사용하면 상태 변경 가능.
data를 현재 상태에 병합하고 render()을 호출, 이후 callback함수 실행됨.
setState()가 비동기로 작동함.

시계를 시작하려면 setInterval()을 한번 호출해야함.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Clock extends React.Component{
counstructor(props){
super(props)
this.lauchClock()
this.state ={
currentTime: (new DAte()).toLocaleString('en')
}
}
launchClock(){
setInterval(()=> {
console.log('Updating time...')
this.setState({
currentTime: (new Date()).toLocaleString('en')
})
}, 1000)
}
render() {
console.log('Rendering Clock...')
return <div>{this.state.currentTime}</div>
}
}

setState()로 상태를 교체할때 상태 항목이 여러개 있으면 명시해준것만 바뀌고 나머진 값이 그대로임.
setState()가 render()를 실행시킴.

4.3 상태 객체와 속성

상태 객체와 속성은 모두 클래스의 맴버이며 각각 this.state와 this.props를 말함.

  • 상태 객체 vs 속성 객체
    • 상태 객체는 변경가능, 속성 객체는 변경 불가능.
    • 상태는 해당 컴포넌트 자체에서 정의, 속성은 부모 컴포넌트에서 전달(부모컴포넌트에서만 값을 변경 가능함).
    • 상태는 컴포넌트에서 설정되고 갱신, 속성은 뷰 생성시에 정해지고 변경안됨.

4.4 상태비저장 컴포넌트

  • 상태비저장 컴포넌트는 상태 객체가 없고 이벤트 또는 메서드를 갖지 않음.
  • 상태비저장 컴포넌트의 목적은 뷰를 랜더링 하는것 뿐임.
  • 속성을 전달받아 처리.
  • 예측하기 편해서 이해, 유지보수, 디버깅이 편함.
    => 상태비저장컴포넌트 많이 사용하고 상태저장컴포넌트는 적게 사용하는게 바람직함.

아래는 상태비저장 컴포넌트 예제

1
2
3
4
5
class HelloWorld extends React.Component{
render(){
return <h1{...this.props}> Hello{ this.props.frameworkName} world!!!</h1>
}
}

상태가 필요하지 않으면 함수로 선언하면됨.

1
2
3
4
5
6
7
8
9
10
functipn Link(props){
return <a href ={props.href} target="_blank" className="btn btn-primary">
{props.text}</a>
}

ReactDOM.render(
<Link text ='Buy React Quickly'
href ='https://www.manning.com/books/react-quickly' />,
document.getElementById('content')
)

1
2
3
4
5
6
7
8
9
const Link = (props)=>{
return (
<href={props.href}
target ="_blank"
className="btn btn-primary" >
{props.text}
</a>
)
}

상태비저장 컴포넌트는 상태를 가질 수 없지만 propTypes와 defaultProps를 프로퍼티로 가질 수 있다.

1
2
3
4
5
6
7
8
9
10
function Link (props){
return <a href = {props.href}
target="_blank"
className="btn btn-primary">
{props.text}
</a>
}

Link.propTypes={...}
Link.defaultProps={...}

4.5 상태비저장 컴포넌트와 상태저장 컴포넌트의 비교

HTML 렌더링을 처리하는것으로 충분한 경우 상태비저장 컴포포넌트가 더 선언적이고 작동이 잘됨.

  • 상태를 사용해야 하는 경우

    • UI 상태, 상호작용, 서버에서 데이터 불러오는것 등 을 관리하려면 상태저장 컴포넌트를 활용한다.
  • 아날로그와 디지털 방식으로 노출하는 시계

    • 프로젝트 구조
      /clock-analog-digital
      /jsx
      analog-display.jsx  
      clock.jsx  
      digital-display.jsx  
      script.jsx  
      
      /js
      analog-display.js  
      clock.js  
      digital-display.js  
      script.js  
      react.js  
      react-dom.js  
      
      /
      index.html
1
2
3
4
5
6
7
8
...
render() {
console.log('Rendering...')
return <div>
<AnalogDisplay time={this.state.currentTime}/>
<DigitalDisplay time={this.state.currentTime}/>
</div>
}
1
2
3
const DigitalDisplay = function(props){
return <div>{props.time}</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const AnalogDisplay = function (props) {
let date = new Date(props.time);
let dialStyle = {
position: 'relative',
top: 0,
left: 0,
width: 200,
height: 200,
borderRadius: 20000,
borderStyle: 'solid',
borderColor: 'black'
};
let secondHandStyle = {
position: 'relative',
top: 100,
left: 100,
border: '1px solid red',
width: '40%',
height: 1,
transform: 'rotate(' + (date.getSeconds() / 60 * 360 - 90).toString() + 'deg)',
transformOrigin: '0% 0%',
backgroundColor: 'red'
};
let minuteHandStyle = {
position: 'relative',
top: 100,
left: 100,
border: '1px solid grey',
width: '40%',
height: 3,
transform: 'rotate(' + (date.getMinutes() / 60 * 360 - 90).toString() + 'deg)',
transformOrigin: '0% 0%',
backgroundColor: 'grey'
};
let hourHandStyle = {
position: 'relative',
top: 92,
left: 106,
border: '1px solid grey',
width: '20%',
height: 7,
transform: 'rotate(' + (date.getHours() / 12 * 360 - 90).toString() + 'deg)',
transformOrigin: '0% 0%',
backgroundColor: 'grey'
};
return React.createElement(
'div',
null,
React.createElement(
'div',
{ style: dialStyle },
React.createElement('div', { style: secondHandStyle }),
React.createElement('div', { style: minuteHandStyle }),
React.createElement('div', { style: hourHandStyle })
)
);
};

[React 교과서] 3장 JSX

리엑트 교과서를 정리한 내용입니다.

3.1 JSX의 정의와 장점

함수 호출과 객체 생성을 위한 문법적 편의를 제공하는 자바스크립트의 확장. JSX가 React에 필수적이지는 않지만 같이 사용할것이 권장됨

  • 개발자 경험 개선: 코드를 읽기 쉽다
  • 팀의 생산성 향상: HTML과 비슷하여 친숙함
  • 문법 오류와 코드량 감소

  • JSX코드 예제

    1
    2
    3
    4
    5
    <div>
    <HelloWorld/>
    <br/>
    <a href = "https://eunii.github.io"> eunii blog</a>
    </div>
  • 위의 코드 자바스크립트로 변경한 예제

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    React.createElement(
    "div",
    null,
    React.createElement(HellowWorld, null),
    React.createElement("br", null),
    React.createElement(
    "a",
    {href : "https://eunii.github.io"},
    "eunii blog"
    ),
    )

자바스크립트 코드 사이에 <>가 있으면 처음에는 어색하지만. React.createBlement(NAME, …) 대신 을 사용하는것이 훨씬 직관적이고 편리하다.
JSX를 사용하려면 브라우저에서 실행하기 전에 컴파일 또는 트랜스파일 과정을 거쳐 일반적 자바스크립트 파일로 변환해야한다.

3.2 JSX의 이해

3.2.1 JSX로 React 엘리먼트 생성하기

1
2
3
4
5
React.createElement(
name,
{key1: value1, key2: vlaue2, ...},
child1, child2, ..., childN
)

위의 코드를 JSX로 바꾸면 아래와 같다

1
2
3
4
5
6
<name key1=value1 key2 value2 ...>
<child1/>
<child2/>
...
<childN/>
</name>

계속 공부해 왔던 HelloWorld예제를 자바스크립트로 작성하면

1
2
3
4
ReactDOM.render(
React.createElement('h1', null, 'Hello world!'),
document.getElementById('content')
)

위의 코드를 JSX로 작성하면

1
2
3
4
ReactDOM.render(
<h1>Hello world!</h1>
document.getElementById('content')
)

JSX문법으로 작성한 객체도 변수에 저장 가능하다.

harmony
1
2
3
4
5
let helloWorldReactElement = <h1>Hello world!</h1>
ReactDOM.render(
helloWorldReactElement,
document.getElementById('content')
)

3.2.2 React 컴포넌트에 JSX 사용하기

JSX 태그는 표준 HTML 태그 이름이기도 하다. 컴포넌트를 다룰 때에도 같은 문법을 사용하고 컴포넌트 클래스 이름이 대문자로 시작한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

- JSX를 이용해서 생성한 HelloWorld클래스
```js
class HelloWorld extends React.Component{
render(){
return(
<div>
<h1> 1. Hello world!</h1>
<h2> 2. Hello world!</h1>
</div>
)
}
}
ReactDOM.render(
<HelloWorld/>
document.getElementById('content')
)

3.2.3 JSX에서 변수 출력하기

컴포넌트를 작송할때, 약간의 코드로 자체적으로 뷰를 변경할 수 있는 코드를 만들기. ex) 현재 시간/날짜를 사용하기
JSX없이 React만 사용하면 +를 이용해 연결하거느 ‘와 ${varName}로 표시한 문자열 탬플릿을 사용할 수 있다. (템플릿 리터럴)

1
2
3
4
5
6
class DateTimeNow extends React.Component{
render(){
let dateTimeNow = new Date().toLocaleDateString()
return <span> Hello {this.props.userName}, current date and time is {dateTimeNow}.</span>
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
let helloWorldReactElement = <h1>Hello world!</h1>
class HelloWorld extends React.Component {
render() {
return <div>
{helloWorldReactElement}
{helloWorldReactElement}
</div>
}
}
ReactDOM.render(
<HelloWorld/>,
document.getElementById('content')
)

3.2.4 JSX에서 속성 사용하기

사용자 계정에 연결할 컴포넌트를 만들때, href와 title의 값은 사용자에 따라 달라져야 하므로 하드 코딩할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
class ProfileLint extends React.Component{
render(){
return (
<a href ={this.props.url}
title ={this.props.lable}
target="_blank">
Profile
</a>

)
}
}

위의 코드에서 속성값은 ProfileLink 생성시에 정의된다. ProfileLink를 생성하는 부모 컴포넌트에서 이 값을 전달함.

1
<ProfileLink url='/user/azat' label='Profile for azat' />

가끔 사용자 지정 데이터를 속성으로 추가할 때가 있다. DOM요소에 속성으로 넣는것은 흔히 사용하지만 HTML 비표준 속성에 데이터를 저장하는 것은 안티패턴으로 여겨진다.
DOM에서 데이터를 가져오는것은 메모리 상의 가상 저장소에서 데이터를 가져오는것보다 느리다.

데이터를 반드시 HTML요소의 속성으로 저장해야 하는 경우는 data-*속성을 사용한다.

1
<li data-react-is-awesom={this.reactIsAwesome}> React is awesome!</li>

위의 경우reactIsAwesome 값이 true라면 아래와 같이 HTML이 렌더링 된다

1
<li data-react-is-awesom="true"}> React is awesome!</li>

하지만 아래와 같이 비표준 HTML속성을 전달하면 HTML속성이 렌더링 되지 않는다.

1
2
<li react-is-awesom={this.reactIsAwesome}> React is awesome!</li>
<li reactIsAwesom={this.reactIsAwesome}> React is awesome!</li>

위의 결과는 아래와 같다

1
2
<li> React is awesome!</li>
<li> React is awesome!</li>

아것대신 this.props를 통해서 입력한데이터는 모든 속성에 접근할 수 있다. 모든 속성을 전달해야 한다면 …를 사용할 수 있다.({…this.props)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class HelloWorld extends React.Component {
render() {
return <h1 {...this.props}>Hello {this.props.frameworkName} world!!!</h1>
}
}

ReactDOM.render(
<div>
<HelloWorld
id='ember'
frameworkName='Ember.js'
title='A framework for creating ambitious web applications.'/>
<HelloWorld
id='backbone'
frameworkName='Backbone.js'
title='Backbone.js gives structure to web applications...'/>
<HelloWorld
id='angular'
frameworkName='Angular.js'
title='Superheroic JavaScript MVW Framework'/>
</div>,
document.getElementById('content')
)

3.2.5 React 컴포넌트 메서드 생성하기

React 컴포넌트에 애플리케이션을 위한 메서드를 자유롭게 추가할 수 있다.

1
2
3
4
5
6
7
8
9
class Content extends React.Component {
getUrl(){
return 'http://webapplog.com'
}

render(){
...
}
}

{}안에 클래스 메서드를 호출 하는 방식으로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
class Content extends React.Component {
getUrl() {
return 'http://webapplog.com'
}
render() {
return (
<div>
<p>Your REST API URL is: <a href={this.getUrl()}>{this.getUrl()}</a></p>
</div>
)
}
}

3.2.6 JSX의 if/else처리

  • 유저 세션에 따른 렌더링 처리 예시(자바스크립트)
    1
    2
    3
    4
    5
    6
    7
    ...
    render(){
    if(user.session)
    return <a href="/logout"> Logout</a>
    else
    return <a href="/login"> Login</a>
    }

JSX로 3가지 방법 표현법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//방법1 변수
render(){
let link
if(this.props.user.session){
link = <a href="/logout"> Logout</a>
else
link = <a href="/login"> Login</a>
return <div>{link}</div>
}
}

// 방법 2 표현식
render(){
let link = (sessionFlag) => {
if(sessionFlag){
return <a href="/logout"> Logout</a>
else
return <a href="/login"> Login</a>
}
return <div>{link(this.props.user.session})}</div>
}

//방법 3 삼항연산자
render(){
return(
<div>
{(this.props.uwer.session)
? <a href="/logout"> Logout</a>
: <a href="/login"> Login</a>
}
</div>
)
}

  • return 문 이전에 JSX 외부에 변수를 선언한 후 JSX 내부에서 {}를 사용하여 출력한다
  • return 문 이전에 JSX외부에서 값을 반환하는 함수 표현식을 선언한 후 JSX내부의 {}에서 실행한다
  • 삼항연산자를 사용한다
  • JSX 내부에서 즉시 실행함수를 사용한다.

3.2.7 JSX의 주석 작성 방법

일반 자바스크립트 주석과 비슷하다. {}로 감싸서 작성한다.

3.3 Babel을 이용한 JSX 트랜스 파일러 설정하기

JSX파일을 자바스크립트코드로 변환하는 과정을 트랜스파일레이션이라고 한다.

  • Babel 명령줄 인터페이스 도구
  • Node.js 또는 브라우저 자바스크립트로 작성한 스크립트(API방식): babel-core 패키지를 이용해서 스크립트를 직성해 변환하는 방식
  • 빌드 도구: Grunt, Gulp, Webpack도구에서 Babel을 플러그인으로 사용할 수 있다.

1. Node.js npm 설치

3.4 React와 JSX의 까다로운 부분

태그를 닫을 때 반드시 /를 넣어야 한다.

3.4.1 특수문자

HTML엔터티코드를 사용하여 저작권 표시나 말바꿈표 따옴표 등을 사용할 수 있다.

  • 1
    - ```mdash;
  • 1
    2
    3
    4

    ```js
    <span>&copy;&mdash;&ldquo;</span>
    <input value ="&copy;&mdash;&ldquo;"/>

아래 코드는 작동하지 않는 오류 코드

1
2
3
var specialChars = "&copy;&mdash;&ldquo;"
<span>{specialChars}</span>
<input value={specialChars}/>

위험한 HTML구문에 대해서 자동으로 이스케이프를 적용함. 특수문자를 노출하려면 다음 방법중 하나를 선택한다.

  • 배열로 출력해 여러개의 분자열로 분리
  • 소스코드에 특수문자를 직접 복사해서 넣는다
  • 특스문자를 \u로 시작하는 이스케이프 시퀸스로 바꾼 후에 유니코드번호를 찾아 사용한다.
  • String.fromCharCode(charCodeNumber)를 이용해서 유니코드 번호에서 문자로 변경한다
  • React 엘리먼트의 _html에 dangerouslySetInnerHTML을 이용하는 방법이 있으나 추천하지 않는다

[React 교과서] 2장 React 첫걸음

리엑트 교과서를 정리한 내용입니다.

엘리먼트는 컴포넌트의 인스턴스이며 컴포넌트 클래스라고 한다.

2.1 엘리먼트 중첩

여러개 엘리먼트가 중첩되는 경우가 많다.
ReactDOM.render()에는 하나의 React엘리먼트만 인자로 전달할 수 있다.
동일한 DOM객체에 두개의 h1태그를 렌더링해야 하는 경우 두 요소를

``````으로 감싸 사용
1
2
3
4
5
6
7
```js
var h1 = React.createElement('h1', null, 'Hello world!') //-h1요소를 생성하여 변수에 담음
var div = React.createElement('div', null, h1,h1) //-세번째 이후 매개변수가 문자열이 아니면 새로 생성하는 엘리먼트의 자식 엘리먼트다.
ReactDOM.render( //h1요소를 ID가 content인 실제 DOM에 랜더링함
div,
document.getElementById('content')
)

createElement 의 첫번째 매개변수에는 아래 두가지 자료형을 입력할 수 있다.

  • 문자열로 작성한 일반적 HTML태그. 소문자로 작성 ex) ‘h1’, ‘div’
  • React 컴포넌트 객체, HelloWorld를 예로 들 수 있음. React 컴포넌트 객체이름은 대문자로 시작한다.

2.2 React컴포넌트 클래스 생성

컴포넌트 클래스(component class)를 사용하면 기능을 느슨하게 결합된 부분으로 분리하여 코드를 재사용할 수 있다.
‘Hello world!’를 별도의 클래스로 분리하는 방법.

class HelloWorld extends React.Component로 상속받아 사용

1
2
3
4
5
6
7
8
9
10
let h1 = React.createElement('h1', null, 'Hello world!')
class HelloWorld extends React.Component {
render() {
return React.createElement('div', null, h1, h1)
}
}
ReactDOM.render(
React.createElement(HelloWorld, null),
document.getElementById('content')
)

여기서 render()는 엘리먼트 하나만 반환함

Hello world!를 여러번 노출해야하면 아래와 같이 클래스 재사용 가능

1
2
3
4
5
6
7
8
9
ReactDOM.render(
React.createElement(div,
null,
React.createElement(HelloWorld),
React.createElement(HelloWorld),
React.createElement(HelloWorld)
),
document.getElementById('content' )
)

2.3 React 속성(props) 사용하기

컴포넌트의 속성(properties)은 React선언형 스타일의 기초다.

1
React.createElememt('a', {href: 'https://eunii.github.io'})

속성은 컴포넌트 내부에서는 변경할 수 없는 값이다.
부모 컴포넌트가 자식 생성 시점에 속성을 할당한다.

속성은 아래와 같은 용도로 사용가능

  • 일반적 HTML 요소의 속성: href, title, style, class
  • React 컴포넌트 클래스의 자바스크립트코드에서 this.props의 값. 예를 들어 this.props.PROPERTY_NAME
    render()메서드에서 입력하여 렌더링하거나 코드에서 활용 할 수 있다.

HelloWorld컴포넌트에 아래 세가지 속성을 추가한다.

  • id
  • frameworkHame
  • title
    id와 title은 h1의 표준속성과 일치에서 랜더링하고 frameworkName은 랜더링 하지 않는다.

h1을 구현하기 전에 helloWorld클래스의 속성을 전달해야함.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ReactDOM.render(
React.createElement(
'div',
null,
React.createElement(HelloWorld, {
id: 'ember',
frameworkName: 'Ember.js',
title: 'A framework for creating ambitious web applications.'}),
React.createElement(HelloWorld, {
id: 'backbone',
frameworkName: 'Backbone.js',
title: 'Backbone.js gives structure to web applications...'}),
React.createElement(HelloWorld, {
id: 'angular',
frameworkName: 'Angular.js',
title: 'Superheroic JavaScript MVW Framework'})
),
document.getElementById('content')
)

render()에서 frameworkName속성 사용하기

1
2
3
4
5
6
7
8
9
10

class HelloWorld extends React.Component {
render() {
return React.createElement(
'h1',
this.props, //모든 속성을 자식 엘리먼트에 전달한다.
'Hello ' + this.props.frameworkName + ' world!'
)
}
}

[React 교과서] 1장 React 살펴보기

리엑트 교과서를 정리한 내용입니다.

1.1 React란 무엇인가?

  • 페이스북에서 만든 자바스크립트 기반의 UI 컴포넌트 라이브러리.

1.2 React가 해결할 수 있는 문제

  • DOM을 매번 새로 생성하기 위해 UI를 함수로 만들었다.

1.3 React의 장점

  • 단순한 앱 개발
  • 빠른 UI
  • 코드량 감소

1.3.1 간결성

  • 선언형 스타일 채택
  • 순수한 자바스크립트를 이용한 컴포넌트 기반 아키텍쳐
  • 강력한 추상화

선언형 스타일 채택

선언형 스타일의 장점은 간단하고 가독성이 좋다. 결과값에 더 집중했다.
뷰에 변경이 발생하는경우 react가 알아서 갱신.

  • 명령형 코드 예시
    1
    2
    3
    4
    5
    6
    var arr =[1,2,3,4,5],
    arr2 = []
    for (var i=0; i<arr.length; i++){
    arr2[i] = arr[i]*2
    }
    console.log('a',arr2)

결과창

1
a [2,4,6,8,10]

  • 선언형 코드 예시
    1
    2
    3
    var arr = [1,2,3,4,5]
    arr2 = arr.map(function(v,i){ return v*2 })
    console.log('b', arr2)

결과창

1
b [2,4,6,8,10]

자바스크립트를 이용한 컴포넌트 기반 아키텍처

코드 재사용이 쉬워 코드를 줄일 수 있다.

  • 코드 예시
    1
    2
    if (user.session) return React.createElement('a',{hret: '/logout'}, 'Logout')
    else return React.createElement('a', {href:'/login'}, 'Login')

강력한 추상화

내부 인터페이스를 숨기고 정규화 과정을 거친 합성 메서드와 속성을 제공한다.

1.3.2 속도와 테스트 용이성

가상 DOM은 자바스크립트 메모리에만 존자한다. 데이터 변경시 가상 DOM을 머저 비교하고 랜더링이 필요한 경우애만 실제 DDM에 랜더링 한다.
필요한 부분반 갱신함.

1.3.3 React의 폭넓은 개발 커뮤니티와 생태계

  • React 컴포넌트 목록: 1
    [2](http://devarchy.com/react-components)  
    

등등..

1.4 React의 단점

  • AngularJS나 Ember같은 기능을 사용하려면 라이브러리를 함꼐 사용해야함
  • 다른 프레임워크만큼 성숙하지 않음.-> 핵심 API가 조금씩 바뀜
  • 관련 자료가 부족
  • 단방향 데이터 바인딩만 제공
  • 리액티브 프로그래밍은 아니다.

1.5 웹 애플리케이션에 React적용하기

  • UI 라이브러리로 React와 관련된 Reduz나 React Router를 활용한 단일 페이지 애플리케이션 스택의 구성
  • MVC의 V를 대체하는 UI라이브러리로 기존 IVX프레임워크와 결합
  • jQuery 를 기반으로 서버 측 렌더링을 거친 애플리케이션에서 자동완성 등 일부 기능을 위한 UI컴포넌트로 활용
  • 대부분의 로직을 직접처리하는 백엔드에서 서버측 렌더링 탬플릿 라이브러리로 활용
  • 백엔드와 프론트엔드서 모두 자바스크립트를 사용하는 경우
  • React Native를 UI 라이브러리로 사용한 모바일 앱
  • 여러가지 렌더링 대상에 적용할 목적으로 사용하는 UI라이브러리

1.5.1 React 라이브러리와 렌더링 대상

  • UI개발이 필요한 환경에 어디든지 사용할 수 있게 만드는중. ReactDOM.rander()를 사용함(여러 환경에서 사용가능)

1.5.2 단일 페이지 애플리케이션과 React

서버보다는 클라이언트(브라우저)에 로직이 더 많은 팻 클라이언트다. SPA는 HTML렌더링, 입력값 겁증, UI변경 등의 기능을 브라우저에서 해결한다.
SPA 방식으로는 MVC아키텍처를 많이 사용하지만 다른 방식도 있음.

1.5.3 React 개발 스택

모델링,스타일라우팅 등에 정해진 방법이 없어 다른 라이브러리를 결합해 사용한다.

(책 56쪽)

  • 데이터 모젤링과 백엔드
  • 라우팅
  • React용 Bootstrap컴포넌트
  • 날짜 입력 컴포넌트
  • WAI-ARIA정의를 따르는 자동완성컴포넌트

1.6 첫번째 React앱 만들기:Hello World

파일구조
/hello-world
/js
react.js
react-dom.js
index.html

예시 코드는 여기 에서 다운받을 수 있다.

index.html파일 내용

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<script src="js/react.js"></script>
<script src="js/react-dom.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
</script>
</body>
</html>

React 엘리먼트를 생성하려면 React.createElement(clementName, data, child)를 호출한다

  • elementName : HTML 태그명’h1’처럼 문자열로 작성하거나 직접 만든 컴포넌트 를래스 객체를 넘겨줄 수 있음
  • data: 속성이나 상위컴포넌트에 받는 값으로 null이나{name: Azat} 같은 형태의 데이너
  • child: 자식 엘리먼트나 태그 내부에 작성하는 텍스트
1
2
3
4
5
var h1 = React.createElement('h1', null, 'Hello world!') //-h1요소를 생성하여 변수에 담음
ReactDOM.render( //h1요소를 ID가 content인 실제 DOM에 랜더링함
h1,
document.getElementById('content')
)

아래처럼 변수에 담지 않고 직접 넣어줄수도 있다.

1
2
3
4
5

ReactDOM.render( //h1요소를 ID가 content인 실제 DOM에 랜더링함
React.createElement('h1', null, 'Hello world!')
document.getElementById('content')
)

웹 브라우져에서


Hello world!



객체가 만들어진것을 볼 수 있다.

[React] React 시작하기

1. Nodejs 설치 및 최신 버전 확인(6버전 이상)

  • https://nodejs.org/ko/ 여기서 최신 nodejs를 설치한다.
  • 콘솔 창(window + R -> cmd 검색) 에서 아래와 같이 명령어를 입력하면 내 pc에 깔린 nodejs버전이 나온다.
    1
    node -v

2. 프로젝트를 생성할 폴더 위치로 이동하여 다음과 같이 프로젝트를 시작한다.

아래 명령어로 폴더에 이동후

1
cd d:\dev\react\tutorial

아래 명령어로 프로젝트를 시작한다.

1
2
$ npm install -g create-react-app   
$ create-react-app my-app

3. my-app/src 하위의 파일들을 삭제한다.

1
2
$ cd my-app
$ rm -f src/*

4. my-app/src/index.css 파일을 만들어 아래 내용을 복사한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}

ol, ul {
padding-left: 30px;
}

.board-row:after {
clear: both;
content: "";
display: table;
}

.status {
margin-bottom: 10px;
}

.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}

.square:focus {
outline: none;
}

.kbd-navigation .square:focus {
background: #ddd;
}

.game {
display: flex;
flex-direction: row;
}

.game-info {
margin-left: 20px;
}

5. my-app/src/index.js 파일을 아래 내용을 복사하여 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';


class Square extends React.Component {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}

class Board extends React.Component {
renderSquare(i) {
return <Square />;
}

render() {
const status = 'Next player: X';

return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}

class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}

// ========================================

ReactDOM.render(
<Game />,
document.getElementById('root')
);

6. 아래 명령어로 시작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

$ npm start
```

## 7. localhost:3000 으로 아래와 같은 화면이 뜬다.

## 8. my-app/src/index.js파일을 열어보면 아래 세가지 컴포넌트로 구성되어 있다.

- Square : 하나의 <button>을 렌더링함
- Board : 9개의 사각형을 랜더링함
- Game : 공백있는 하나의 보드를 랜더링함

## 9. Board 컴포넌트에서 Square 컴포넌트로 데이터(숫자 0~9) 전달하기

### 9.1 index.js파일의 Board클래스 부분을 아래와 같이 변경한다.
```js
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />;
}

9.2 Square클래스 render 메서드를 아래와 같이 변경한다.

1
2
3
4
5
6
7
8
9
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
  • 이렇게 변경해서 저장 후 npm start로 실행해보면 비어있던 9개 사각형 칸에 0~8까지 숫자가 입력되서 나온다.

10. 대화형 컴포넌트 : 빈 사각형을 클릭하면 X표시가 되게 변경하기

10.1 Sqare의 render()함수를 아래와 같이 변경한다.

Square클래스의 생성자에서 this.state을 설정하여 상태(초기값 null)를 가질 수 있게 한다.

1
2
3
4
5
6
7
8
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
}

아래와 같이 render()함수를 변경하면 사각형을 클릭하면 클릭한 빈 사각형안의 값이 X로 바뀐다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}

render() {
return (
<button className="square" onClick={() => this.setState({value: 'X'})}>
{this.state.value}
</button>
);
}
}

11 State에 저장하기

클릭 할때마다 o,x 번갈아 나오게 하고, 승자를 가리기 위해 9개 블럭의 상태를 동시에 확인해야함.
여러 하위 컴포넌트로 부터 데이터를 모으거나, 하위 컴포넌트들이 서로 통신하기 원하면 상위컴포넌트로 state를 이동시킴.
상위 컴포컨트는 props를 통해 하위 컴포넌트로 state를 전달해 줄 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}

renderSquare(i) {
return <Square value={i} />;
}

render() {
const status = 'Next player: X';

return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}

지금 board의 renderSquare매서드는 아래와 같다.

1
2
3
4
renderSquare(i) {
return <Square value ={i} />;

}

Square에 value prop를 전달하도록 수정한다

1
2
3
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}

1
2
3
4
5
6
7
8
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
1
2
3
4
5
6
7
8
9
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => this.props.onClick()}>
{this.props.value}
</button>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}

handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}

renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}

render() {
const status = 'Next player: X';

return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}

class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}

render() {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}

class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [
{
squares: Array(9).fill(null)
}
],
stepNumber: 0,
xIsNext: true
};
}

handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState({
history: history.concat([
{
squares: squares
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}

jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}

render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);

const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});

let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}

return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={i => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}

// ========================================

ReactDOM.render(<Game />, document.getElementById("root"));

function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×