[!INFO] 책 정보
- 저자: 저자/아자트_마르단
- 번역: 번역/곽현철
- 출판사: 출판사/길벗
- 발행일: 2018-05-25
- origin_title: -
- 나의 평점: 8
- 완독일: 2021-01-16 12:32:38
21-03 리액트 교과서
[21-01 파이썬과 리액트를 활용한 주식 자동 거래 시스템 구축](21-01 파이썬과 리액트를 활용한 주식 자동 거래 시스템 구축)
[파이썬 웹 프로그래밍](파이썬 웹 프로그래밍)
정보
sub title: 기본기에 충실한 리액트 입문서
#저자/아자트_마르단
#번역/곽현철
#출판사/길벗
reading status: [Done]
finished date: 2021.1.29
score[max 10]: 8
- tag
-
저자 github github
E:\0-Mysource\MyApp\React\react-quickly_book_code
https://github.com/Minswa/MyDoc/blob/master/%EB%8F%85%EC%84%9C%EB%A1%9D/20210116213237.md
2021-04-10 20:45
_경험에 , 왜 읽었나 질문,궁금 등 __
React Router,Express,Webpack,JSX,Babel 등에대해서 알게됨
React 문법,설명 이외는 난해해서, 따로 공부해야함
책 밑줄 정리
React 살펴보기
- React는 선언적이며, 뷰 또는 UI레이어의 역할만 한다.
- React는 ReactDOM.render() 메서드를 통해 컴포넌트를 실제로 사용한다
- 컴포넌트는 클래스로 생성하고 필수적인 render() 메서드를 포함한다
- 컴포넌트는 재사용할 수 있고, 불가변 속성을 전달받아서 this.props.NAME 으로 접근할 수 있다.
React 첫걸음
. 엘리먼트는 컴포넌트의 인스턴스이다
. 컴포넌트클래스 라고도한다
. ReactDOM.render() 에는 하나의 React 엘리먼타만 인자로 전달 할 수있다-중요
. React 엘리먼트를 중첩하여 자식 엘리먼트로 추가하려면 createElement()의 3번째 인자로 계속 전달하면된다
. React 엘리먼트를 생성할 때 사용자 정의 컴포넌트 클래스를 사용한다
. 속성을 사용하여 React element의 랜더링 결과를 바꾼다
. 부모 컴포넌트는 자식 엘리먼트에 속성을 전달할 수도 있다
. React 컴포넌트를 통해 컴포넌트 기반 아키텍처를 구현할 수 있다.
1
2
3
4
5
|
let h1=React.createElement('h1',null,'Hello World!')
ReactDOM.render(
h1,
document.getElementById('content')
)
|
를 이렇게
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let h1=React.createElement('h1',null,'Hello World!')
class HelloWorld extends React.Component { -----React 컴포넌트 클래스 정의(이름은 대문자로 시작!)
render(){ --- 엘리먼트 하나를 반환하는 함수 render()를 생성한다
return React.createElement('div',null,h1,h1) ---return 문에는 React 엘리먼트를 반환하도록
---구현하여 Rect 클래스가 render()를 실행하면
--- 두개의 <h1>엘리먼트를 감싼 <div>엘리먼트를 받을 수 있다
}
}
ReactDOM.render( --- React 엘리먼트를 ID가 content인 실제 DOM에 넣어준다
React.createElement(HelloWorld,null), ----첫번째 인자로 HelloWorld를 전달하여 엘리먼드를 생성한다.
----이때HelloWorld클래스는 문자열이 아닌 객체다
document.getElementById('content')
)
|
- 같은 컴포넌트에 다른 속성 값을 입력하면 컴포넌트가 랜더링한 엘리먼트의 모습을 다르게 할 수 있다
- 속성은 render()를 통해 랜더링할 수 있고, 컴포넌트 클래스의 코드에서 사용할 수 있으며, HTML속성으로도 사용할 수있다 (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
24
25
26
27
28
29
30
31
|
class HelloWorld extends React.Component {
render() {
return React.createElement(
'h1',
this.props, # 모든 속성을 자식 엘리먼트에게 전달한다 {id: 'ember',frameworkName: 'Ember.js',title: 'A framework ..'}) 요런 속성이 그대로 넘어간다
'Hello ' + this.props.frameworkName + ' world!'
)
}
}
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')
)
# createElement(객체,{속성1:value1,...속성n:value n},) ... 구조
|
JSX
- : JSX는 함수 호출,객채 생성을 위한 문법적 편의 제공하는 javascript의 확장
- React.createElement() 호출 반복해야하는 불편 해소
- 사용자 정의 컴포넌트는 반드시 대문자로 시작해야합니다
참고링크 https://ko.reactjs.org/docs/jsx-in-depth.html
장점
-
표현력이 뛰어나 코드를 읽기 쉽다
-
HTML과 비슷하여 개발 초보에게도 친숙하다
-
작성 코드가 줄어들고, 반복으로 인한 스트레스 줄어든다
-
사용자 경험 만큼이나 개발자의 경험을 중요하게 여긴다
-
React.createElement(NAME,…) 를 으로 간단히 표현
-
JSX를사용하려면 트랜서파일 transpile 과정을 거쳐 일반 자바스크립트로 변환해야한다
-
사용예)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
React.createElement(
name,
{key1:value1,key2:value2,...},
child1,child2,child3...childN
)
# JSX
<name key1=value1 key2=value2 ...>
<child1/>
<child2/>
...
<childN/>
</name>
let helloWorldReacElement= <h1> Hello World </h1>
ReactDOM.render(
helloWorldReactElement,
document.getElement('content')
)
|
JSX변수 출력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# react
class DateTimeNow extends React.Component{
render(){
let dateTimeNow=new Date().toLocalString()
return React.createElement(
'span',
null,
'Current Time ${dateTimeNow}'
)
}
}
# JSX
class DateTimeNow extends React.Component{
render(
let dateTimeNow=new Date().toLocalString()
return <span>Current time {dateTimeNow}</span>
)
}
<span> string.. {this.props.usrName} ..{dateTimeNow} </span>
<p> string ... {new Date(Date.now()).toLocalTimeString()}</p>
|
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')
)
|
JSX에서 속성 사용하기
{...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')
)
|
React 컴포넌트 메서드 생성하기
: React 컴포넌트는 클래스 이기 때문에 메소드 추가 가능
1
2
3
4
5
6
7
8
9
10
11
12
|
class Content extends React.Component{
getUrl(){
return 'http://naver.com'
}
render(){
return(
<div>
<p> Your REST API URL is <a href={this.getrl()}>{this.getUrl()}</a></p>
</div>
)
}
}
|
JSX if/else 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
...
render(){
if (this.props.user.session)
return <a href='/logout'>Logout</a>
else
return <a href='/login'>Login</a>
}
...
# 삼항연산자
let msg= (userAUth) ? 'welcome' : 'fail'
render(){
let sessionFlag=this.props.user.session
return <div>
<a href={(sessionFlag) ? '/logout':'/login'}> {(sessionFlag) ? 'Logout' : 'Login'} </a>
</div>
}
|
이렇게 심플하게 사용하라
1
2
3
4
5
6
7
8
9
10
11
12
|
calss MyReactComp extends React.Compenent{
render(){
// JSX 상요하지 않는 영역에서 변수 생성
let varname = ... //if/else, 삼항연산자 등
return (
//JSX 사용 출력 , 삼항 연산자 , 함수 실행 결과 {} 로 표시
/* 주석 여려줄
...ㅁㄴㅇㄹ
*/
)
}
}
|
JSX 주석
{/* 자바스크립트 주석과 동일 */} , //
Babel을 이용한 JSX 컴파일(트랜스파일러) 설정하기
- #babel #package
- 브라우저는 JSX를 해석할 수 없다
./node_moduls/.bin/babel js/jsxscript.jsx -o js/script.js
- package.json 의
"build-myapp":"./node_moduls/.bin/babel js/jsxscript.jsx -o js/script.js" 추가하고 npm run build-myapp 로 가능
- -w 옵션으로 자동 컴파일
./node_moduls/.bin/babel js/jsxscript.jsx -o js/script.js [-w|--watch] 파일변경시 자동 컴파일됨
- 디렉토리 전체 컴파일
./node_moduls/.bin/babel source -d build -d|–out-dir 옵션 사용
- jsx를 1개 js로 합쳐 컴파일
./node_moduls/.bin/babel source -o script-compiled.js
React Component 상태의 객체
- : state 는 React 컴포넌트에 데이터를저장하고, 데이터의 변경에 따라 자동으로 뷰를 갱신하도록 하는 핵심 개념이다
- React의 상태는 컴포넌트의 변경 가능한 데이터 저장소이다.
- 독립적이며 기능 중심인 UI와 논리의 블록이다
- 상태 데이터는 부의; 랜더링이 갱신될 때 동적 정보를 출력하기 위해 사용된다
초기 상태 설정하기 (생성자)
- : 반드시 super()에 속성을 전달해줘야한다. 부모 클래스(React.Component) 의 기능을 정상적으로 사용할 수 없다.
- 상태 객체(this.ststs) 는 배열이나, 다른 객체를 중첩해서 가질 수 있다.
this.state={key:value,key=[value1,val2..]}
- 변수의 값을 변경/뷰에 반영하려면 반드시 setState() 매서드를 이용해야 한다
-
=> 화살표 함수를 사용해서 자동으로 바인딩된 함수(this)를 생성 할 수 있다
- 다른 방법으로는
function(){....}.bind(this) 로 하면된다
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
|
class Clock extends React.Component {
constructor(props) { //생성자 이름은 반듯이 이 이름으로 해야한다
super(props) //부모에 속정 전달 -꼭 해야함
this.launchClock()
this.state = {currentTime: (new Date()).toLocaleString()}
}
launchClock() {
setInterval(()=> { // => 화살표 함수
console.log('Updating time...')
this.setState({
currentTime: (new Date()).toLocaleString()
})
}, 1000)
}
/*
this로 바인당 다른 방법
setInterval(**function(){**
this.setState({
currentTime: (new Date()).toLocaleString()
})
**}.bind(this)** , 1000)
*/
render() {
console.log('Rendering Clock...')
return <div>{this.state.currentTime}</div>
}
}
|
상태 객체와 속성
- : this.state - 변경 가능, this.props - 변경불가능
- 속성은 부모에서 전달 받음, 상태는 자신(자식) 에서 생성 관리함
(7df7a914f394eda7ecf1b3499a674014.png)
상태비저장 컴포넌트
- : 상태비저장 컴포넌트와, 상태저장 컴포넌트를 분리하여 적절히 사용하면, 코드재사용이나, 로직이 복잡해지지 않는다
- 아래 예에서 analog 컴포넌트를 구분하여 clock 컴포넌트가 간결해 졌다
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
|
//script.jsx
ReactDOM.render(
<Clock />,
document.getElementById('content')
)
//clock.jsx
//상태 저장 컴포넌트
class Clock extends React.Component {
constructor(props) {
super(props)
this.launchClock()
this.state = {
currentTime: (new Date()).toLocaleString()
}
}
launchClock() {
setInterval(()=> {
console.log('Updating...')
this.setState({currentTime: (new Date()).toLocaleString()})
}, 1000)
}
render() {
console.log('Rendering...')
return <div>
<AnalogDisplay time={this.state.currentTime}/>
<DigitalDisplay time={this.state.currentTime}/>
</div>
}
}
//digital-display.jsx
//상태 비저장 컴포넌트
const DigitalDisplay = function(props) {
return <div>{props.time}</div>
}
//analog-display.jsx
//상태 비저장 컴포넌트
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 = {...}
let minuteHandStyle = {...}
let hourHandStyle = {...}
return <div>
<div style={dialStyle}>
<div style={secondHandStyle}/>
<div style={minuteHandStyle}/>
<div style={hourHandStyle}/>
</div>
</div>
}
|
: 상태비저장 컴포넌트에 함수 갖으려면
1
2
3
4
|
const DigitalDisplay = function(props) {
const locale= time => (new Date(time)).toLocaleString('en')
return <div>{locale(props.time)}</div>
}
|
- 상태비저장 컴포넌트는 단순하게 유지해야한다
- 상태 객체나 메서드를 추가하지 말자
- 특히, 외부 메서드나, 함수를 호출하지 않도록 하자, (이런 방법이 예측 가능성,단순성이 깨진다)
ch05 React Life Cycle Event
공식 문서 [링크]https://reactjs.org/docs/react-component.html
컴포넌트 라이프 사이클
실행 순서
- constructor() :
- 마운팅
- componentWillMount() : DOM 에 삽입하기전에 실행된다
- componentDidMount() : DOM 에 삽입되어 랜더링이 완료된 후 실행
- 갱신
- componentWillReceiveProps (nextProps) : 컴포넌트가 속성을 받기 직전에 실행된다
- shouldComponentUpdate(nextProps, nextState) : 컴포넌트가 갱신되는 조건을 정의해서 재렌더링을 최적화 할 수 있다. boolean값을 반환단다
- componentWillUpdate(nextProps, nextState) : 컴포넌트가 갱신되기 직전에 실행된다
- componentDidUpdate(prevProps, prevState) : 컴포넌트가 갱신된 후에 실행된다
- 언마운팅
- componentWillUnmount() : 컴포넌트를 DOM에서 제거하기 전에 실행되며, 구독한 이벤트를 제거하거나 다른 정리 작업을 수행할 수 있다.
▼ 표 5-1라이프사이클 이벤트와 속성 및 상태의 관계
|
|
|
|
|
| 마운팅 |
컴포넌트 속성 갱신 |
컴포넌트 상태 갱신 |
forceUpdate()를 이용한 갱신 |
언마운팅 |
| constructor() |
|
|
|
|
| componentWillMount() |
|
|
|
|
|
componentWillReceiveProps() |
|
|
|
|
shouldComponentUpdate() |
shouldComponentUpdate() |
|
|
|
componentWillUpdate() |
componentWillUpdate() |
componentWillUpdate() |
|
| render() |
render() |
render() |
render() |
|
|
componentDidUpdate() |
componentDidUpdate() |
componentDidUpdate() |
|
| componentDidUpdate() |
|
|
|
|
|
|
|
|
componentDidMount() |
(4889b46660154f642acd74bb5f678df9.png)
(42d5f16ce3f10de5011ccd76951961fe.png)
샘플코드
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
|
//script.jsx
ReactDOM.render(
<div>
<Content />
</div>,
document.getElementById('content')
)
//content.jsx
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>
}
}
//logger.jsx
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>
)
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<!- index.html -->
<!DOCTYPE html>
<html>
<head>
<script src="js/react.js"></script>
<script src="js/react-dom.js"></script>
<link rel="stylesheet" href="css/bootstrap.css">
</head>
<body>
<div class="container-fluid">
<div id="content"></div>
<script src="js/logger.js"></script>
<script src="js/content.js"></script>
<script src="js/script.js"></script>
</div>
</body>
</html>
|
결과
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
|
constructor
componentWillMount is triggered
componentDidMount is triggered
DOM node: <div>2021. 1. 23. 오후 4:52:48</div>
componentWillReceiveProps is triggered
new props: {time: "2021. 1. 23. 오후 4:52:49"}
shouldComponentUpdate is triggered
new props: {time: "2021. 1. 23. 오후 4:52:49"}
new state: null
componentWillUpdate is triggered
new props: {time: "2021. 1. 23. 오후 4:52:49"}
new state: null
componentDidUpdate is triggered
old props: {time: "2021. 1. 23. 오후 4:52:48"}
old props: null
componentWillReceiveProps is triggered
new props: {time: "2021. 1. 23. 오후 4:52:50"}
shouldComponentUpdate is triggered
new props: {time: "2021. 1. 23. 오후 4:52:50"}
new state: null
componentWillUpdate is triggered
new props: {time: "2021. 1. 23. 오후 4:52:50"}
new state: null
componentDidUpdate is triggered
old props: {time: "2021. 1. 23. 오후 4:52:49"}
old props: null
componentWillUnmount
|
so that~
- componentWillMount()는 서버,클라이언트 모두에서 실행
- componentDidMount() 는 클라이언트에서만 실행
- shouldComponentUpdate() 는 렌더링 직전에 실행된다. 새로운 속성이나 상태가 전달된 후 이뤄진다.
1
|
shouldComponentUpdate(newProps, newState){ return this.state.opacity !== + newProps.isVisible}
|
ch06 Event 다루기
- : 화살표 함수 ()=>{} 에는 this를 bind할 필요없다
<button onClock={(function(event){...}).bind(this)}> text </button>
<button onClock={(event) => {...}> text </button>
-
이벤드 함수이용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class SaveButton extends React.Component {
constructor(props){
super(props)
this.handleSave=this.handleSave.bind(this) // 생성자에서 바인딩하면 아래버튼처럼 곳곳에서 bind할 필요없다
}
handleSave(event) {
console.log(this, event)
}
render() {
return <div>
{/* <button onClick={((event)=>{console.log(this, event)})}>Save</button> */}
{/*<button onClick={this.handleSave.bind(this)}>Save</button> // bind()가 반환하는 함수정의를 onClick에 전달하다*/}
<button onClick={this.handleSave}> Save </button>
</div>
}
}
|
▼ 이벤트관련 공식 문서링크
▼ 표 6-1React 버전 15에서 지원하는 DOM 이벤트1
|
|
| 이벤트 분류 |
React가 지원하는 이벤트 |
| 마우스 이벤트 |
onClick, onContextMenu, onDoubleClick, onDrag, onDragEnd,
onDragEnter, onDragExit, onDragLeave, onDragOver, onDragStart,
onDrop, onMouseDown, onMouseEnter, onMouseLeave,
onMouseMove, onMouseOut, onMouseOver, onMouseUp |
| 키보드 이벤트 |
onKeyDown, onKeyPress, onKeyUp |
| 클립보드 이벤트 |
onCopy, onCut, onPaste |
| 폼 이벤트 |
onChange, onInput, onSubmit, onInvalid |
| 포커스 이벤트 |
onFocus, onBlur |
| 터치 이벤트 |
onTouchCancel, onTouchEnd, onTouchMove, onTouchStart |
| UI 이벤트 |
onScroll |
| 휠 이벤트 |
onWheel |
| 영역선택 이벤트 |
onSelect |
| 이미지 이벤트 |
onLoad, onError |
| 애니메이션 이벤트 |
onAnimationStart, onAnimationEnd, onAnimationIteration |
| 트랜지션 이벤트 |
onTransitionEnd |
▲ 그림 6-2이벤트 전파 단계링크
(3f32cb95cdefe4cf13b1262b8be0ee1e.png)
![ㅇㅇ
이벤트 단계
- 캡처 단계
- 대상 단계
- 버블링 단계
: 캡처 단게를 위한 이벤트 리스너 등록할때 Capture를 추가해준다
onMouseOver → onMouseOverCapture
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>
}
}
|
- : mouse over on capture event → mouse over on bubbling event 순서로 로그 생성된다
-
하위요소에서 event를 처리하지 않으면 위로 버블링 된다
합성이벤트
React 버전 15의 합성 이벤트 인터페이스에 포함되어 있는 몇 가지 프로퍼티와 메서드를 살펴보면 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
•currentTarget: 이벤트를 캡처한 요소의DOMEventTarget(대상 요소 또는 부모 요소일 수 있다.)
•target:DOMEventTarget, 이벤트가 발생한 요소
•nativeEvent:DOMEvent, 브라우저 내장 이벤트 객체
•preventDefault(): 링크나 폼 전송 버튼처럼 기본 동작을 방지하는 메서드
•isDefaultPrevented(): 기본 동작이 방지되었을 때 실행하면true를 반환한다.
•stopPropagation(): 이벤트 전파 중단
•isPropagationStopped(): 이벤트 전파가 중단되었을 때 실행하면true를 반환한다.
•type: 태그명 문자열
•persist(): 합성 이벤트를 이벤트 풀에서 꺼낸 후 사용자 코드에서 이벤트에 대한 참조를 유지할 수 있도록 한다.
•isPersistent(): 합성 이벤트를 이벤트 풀에서 꺼낸 경우 실행하면true를 반환한다.
|
이벤트 핸들러를 속성으로 전달하기
ch06\onclick-props
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
|
//index.html
<!DOCTYPE html>
<html>
<head>
<script src="js/react.js"></script>
<script src="js/react-dom.js"></script>
<link rel="stylesheet" href="css/bootstrap.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="content"></div>
<script src="js/click-counter-button.js"></script>
<script src="js/content.js"></script>
<script src="js/script.js"></script>
</body>
</html>
//script.jsx
ReactDOM.render(
<Content/>,
document.getElementById('content')
)
//content.jsx
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
counter={this.state.counter}
handler={this.handleClick}/>
</div>
)
}
}
//click-counter-button.jsx
//상태비저장 컴포넌트로, 부모이벤트를 사용하기에, 이 ㅌ컴포넌트를 다른 곳에 사용할수 있다 . 좋은 구조임
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>
}
}
|
컴포넌트간 데이터 교환 (좋은 구조임)
ch06\onclick-parent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//content.jsx 가 counter.jsx, click-counter-button.jsx 를 관장해서 연결성을 최소화한다
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>
)
}
}
|
React가 지원하지 않는 DOM 이벤트 처리학
- : resize는 React가 지원하지 않는다
- React에서 미지원 이벤트(resize 등)을 처리하려면 라이프사이클 이벤트를 사용한다
ch06\radio
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
|
//radio.jsx
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() { // DOM 제거될때 Event를 반듯이 제거해준다, 메모리 누수발생
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>
}
}
|
React를 다른 라이브러리와 통합하기: JQuery UI 이벤트
요약
- onClick은 마우스와 트랙패드의 클릭을 캡처한다.
- JSX 문법으로 이벤트 리스너를 추가할 때는<a onNAME={this.METHOD}>로 작성한다.
- constructor()또는 JSX를 이용해bind()로 이벤트 핸들러에this를 바인딩해서 컴포넌트 클래스의 인스턴스에 접근할 수 있다.
- componentDidMount()는 브라우저에서만 실행된다.componentWillMount()는 브라우저와 서버 측 렌더링에서 모두 실행된다.
- React는 합성 이벤트 객체를 제공함으로써 거의 대부분의 표준 HTML DOM 이벤트를 지원한다.
- React를 다른 프레임워크와 통합하거나 React가 지원하지 않는 이벤트를 처리하기 위해componentDidMount()와componentWillUnmount()를 사용할 수 있다.
React 폼 다루기
: 내부 상태와 뷰를 동기화하도록 구현하는 것이 가장 좋은 방법
- render()에서 상태 값을 이용해 엘리먼트를 정의한다.
- onChage를 이용해서 폼 요소에 발생하는 변경 사항을 감지한다.
- 이벤트 핸들러에서 내부 상태를 갱신한다.
- 새로운 값이 상태에 저장되면 새로운render()가 실행되어 뷰를 갱신한다.
: 폼 요소를 위한 세 가지 이벤트를 지원한다.
- onChage: 폼의 입력 요소에 변경이 생기면 발생한다.
- onInput:
<textarea><input>요소의 값이 변경될 때 발생한다. React 팀은 이 이벤트의 사용을 추천하지 않는다
- onSubmit: 폼 제출 시 발생한다. 흔히Enter를 눌렀을 때 발생한다.
React의onChange와 HTML의onChange는 서로 다르게 동작한다는 것이다. React의onChange이벤트는 HTML의onInput이벤트와 더 비슷하고 일관성이 있다. React에서는 가급적onChange이벤트를 활용하고,onInput이벤트의 네이티브 구현에 접근해야 하는 경우에만onInput을 사용하는 것을 추천한다. React가 감싸서 만든onChange의 동작이 일관성 있어 믿을 수 있기 때문이다.
폼요소
: 입력 영역을 네 가지 요소 <input>, <textarea>, <select>, <option>
- value:
<input>, <textarea>, <select>에 적용된다.
- checked:
<input>에 type=“checkbox” 또는type=“radio” 인 경우 적용된다.
- selected:
<select>와 함께 <option>을 사용할 때 적용된다
• text: 일반적인 텍스트 입력 영역
• password: 보안을 위해 입력 내용이 가려진 텍스트 영역
• radio: 라디오 버튼. 라디오 버튼 그룹을 만들 때는 name 속성에 같은 값을 입력한다.
• checkbox: 체크박스 요소. 체크박스 그룹을 만들 때는 name 속성에 같은 값을 입력한다.
• button: 버튼 폼 요소
참고소스 ch07 elements
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
146
147
148
149
150
151
152
153
154
155
156
157
|
class Content extends React.Component {
constructor(props) {
super(props)
this.handleRadio = this.handleRadio.bind(this)
this.handleCheckbox = this.handleCheckbox.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleInput = this.handleInput.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleSelectChange = this.handleSelectChange.bind(this)
this.handleFirstNameChange = this.handleFirstNameChange.bind(this)
this.state = {
description: `With the right pattern, applications will be more scalable and easier to maintain.
If you aspire one day to become a Node.js architect (or maybe you're already one and want to extend your knowledge), this presentation is for you.`,
radioGroup: {
angular: false,
react: true,
polymer: false
},
checkboxGroup: {
node: false,
react: true,
express: false,
mongodb: false
},
selectedValue: 'node'
}
}
handleRadio(event) {
let obj = {} // 다른 radio 삭제
obj[event.target.value] = event.target.checked // true target.checked 속성을 이용해서 라디오 버튼이 선택되었는지 여부를 확인한다.
this.setState({radioGroup: obj})
}
handleCheckbox(event) {
let obj = this.state.checkboxGroup
obj[event.target.value] = event.target.checked // true or false
this.setState({checkboxGroup: obj})
}
handleChange(event) {
console.log('onChange event: ', event.target.value, event.target.checked)
}
handleInput(event){
console.log('onInput event: ', event.target.value, event.target.checked)
}
handleFirstNameChange(event) {
this.setState({firstName: event.target.value})
}
handleSubmit(event){
console.log(event.target.value, event.target.checked)
fetch(this.props['data-url'], {method: 'POST', body: JSON.stringify(this.state)})
.then((response)=>{return response.json()})
.then((data)=>{console.log('Submitted: ', data)})
}
handleSelectChange(event) {
this.setState({selectedValue: event.target.value})
console.log(event.target.value, event.target.selected)
}
render() {
return <div className="container">
<form onSubmit={this.handleSubmit}>
<h2>input: text</h2>
<input type="text" name="new-book-title" defaultValue="Node: The Best Parts"/>
<hr/>
<h2>input: password</h2>
<input type="password" defaultValue="123456" onChange={this.handleChange} onInput={this.handleInput}/>
<hr/>
<h2>input: radio</h2>
<label>
<input type="radio" name="radioGroup" value='angular' checked={this.state.radioGroup['angular']} //상태 객체 또는 상태 객체에 있는 한 값에서 필요한 값을 가져와서 사용할 수 있다.
onChange={this.handleRadio}/>
Angular
</label>
<br/>
<label>
<input type="radio" name="radioGroup" value='react' checked={this.state.radioGroup['react']} onChange={this.handleRadio}/>
React
</label>
<br/>
<label>
<input type="radio" name="radioGroup" value='polymer' checked={this.state.radioGroup['polymer']} onChange={this.handleRadio}/>
Polymer
</label>
<hr/>
<h2>input: checkbox</h2>
<label>
<input
type="checkbox"
name="checkboxGroup"
value='node'
checked={this.state.checkboxGroup['node']}
onChange={this.handleCheckbox}/>
Node
</label>
<br/>
<label>
<input
type="checkbox"
name="checkboxGroup"
value='react'
checked={this.state.checkboxGroup['react']}
onChange={this.handleCheckbox}/>
React
</label>
<br/>
<label>
<input
type="checkbox"
name="checkboxGroup"
value='express'
checked={this.state.checkboxGroup.express}
onChange={this.handleCheckbox}/>
Express
</label>
<br/>
<label>
<input
type="checkbox"
name="checkboxGroup"
value='mongodb'
checked={this.state.checkboxGroup['mongodb']}
onChange={this.handleCheckbox}/>
MongoDB
</label>
<hr/>
<textarea
name="description"
defaultValue={this.state.description}
onChange={this.handleChange}/>
<hr/>
<textarea
name="description1"
defaultValue={"Pro Express.js is for the reader\nwho wants to quickly get up-to-speed with Express.js, \nthe flexible Node.js framework"}
onChange={this.handleChange}/>
<hr/>
<select value={this.state.selectedValue} onChange={this.handleSelectChange}>
<option value="ruby">Ruby</option>
<option value="node">Node</option>
<option value="python">Python</option>
</select>
<hr/>
<select multiple={true} defaultValue={['meteor', 'react']} readOnly>
<option value="meteor">Meteor</option>
<option value="react">React</option>
<option value="jQuery">jQuery</option>
</select>
<hr/>
<h2>input: first name [text]</h2>
<input type="text" name="first-name" onChange={this.handleFirstNameChange}/>
<hr/>
<h2>input: button</h2>
<input type="button" defaultValue="Send" onClick={this.handleSubmit}/>
<hr/>
<input type="text" name="title" value="Mr." />
</form>
</div>
}
}
|
textarea
1
2
3
|
render() {
return <textarea name="description" value={this.state.description}/>
}
|
select,option
변경 감지
onChange 이벤트를 발생시키는 요인은 요소에 따라 차이가 있다.
• <input>, <textarea>, <select>: value가 변경될 때 onChange 이벤트가 발생한다.
• <input> 체크박스와 라디오 버튼: checked가 변경될 때 onChange 이벤트가 발생한다.
숫자만 입력받기
소스 : ch07\account
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Content extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.state = {accountNumber: ''}
}
handleChange(event) {
console.log('Typed: ', event.target.value)
this.setState({accountNumber: event.target.value.replace(/[^0-9]/ig, 🤔})
}
render() {
return <div>
Account Number:
<input
type="text"
onChange={this.handleChange}
placeholder="123456"
value={this.state.accountNumber}/>
<br/>
<span>{this.state.accountNumber.length > 0 ? 'You entered: ' + this.state.accountNumber: ''}</span>
</div>
}
}
|
폼 제어 다른 방법
- ch07\uncontrolled
-
비제어 컴포넌트를 다룰 때는onChange같은 이벤트를 이용해 입력을 감지하지 않으므로refs를 통해 참조로 값에 접근한다
• render 메서드에서 반환하는 엘리먼트의 ref 속성에 문자열을 전달하는 경우 카멜 표기법으로 작성되어 있어야 한다. 예를 들어 email:<input ref=“userEmail” /> 처럼 작성한다.
• 지정한 이름으로 다른 메서드에서 DOM 인스턴스에 접근한다. 예를 들어 이벤트 핸들러에서 this.refs.NAME이 this.refs.userEmail 이다.
1
2
3
4
5
6
|
let emailNode = ReactDOM.findDOMNode(this.refs.email)
let email = emailNode.value
또는 ReactDOM.findDOMNode메서드가 좀 길어서 쓰기 불편하므로 줄여서 작성해보자.
let fD = ReactDOM.findDOMNode
let email = fD(this.refs.email).value
|
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
|
//이메일 폼 시작하기
class Content extends React.Component {
constructor(props) {
super(props)
this.submit = this.submit.bind(this)
this.prompt = ‘Please enter your email to win $1,000,000.’ —\- 클래스 속성 정의
}
submit(event) {
let emailAddress = this.refs.emailAddress
let comments = this.refs.comments
console.log(ReactDOM.findDOMNode(emailAddress).value) —\- 참조를 이용해서 이메일 주소 입력 값에 접근하여 출력한다.
console.log(ReactDOM.findDOMNode(comments).value)
}
render() {
return (
<div className=“well”>
<p>{this.prompt}</p> —- Content 컴포넌트의 this.prompt 값을 출력한다.
<div className=“form-group”>
Email: <input ref=“emailAddress” className=“form-control” type=“text”
placeholder=“hi@azat.co”/> —- placeholder 속성이 있는 이메일 입력 영역을 구현한다. placeholder 속성은 입력할 내용을 알려주는 시각적 장치다. className과 ref 속성도 사용했다.
</div>
<div className=“form-group”>
Comments: <textarea ref=“comments” className=“form-control” placeholder=“I like
your website!“/>
</div>
<div className=“form-group”>
<a className=“btn btn-primary” value=“Submit” onClick={this.submit}>Submit</a> —- onClick 이벤트가 있는 제출 버튼에서 this.submit을 호출한다.
</div>
</div>
)
}
|
기본값 설정하기
1
2
3
|
<input type=“text” name=“new-book-title” defaultValue=“Node: The Best Parts”/>
<input type="text" name="new-book-title" defaultValue={this.props.title}/>
<input type="text" name="new-book-title" defaultValue={this.state.title}/>
|
요약
• 폼을 다루는 방법 중 권장하는 방법은 변경을 감지하여 이벤트 리스너로 상태에 데이터를 저장하는 제어 컴포넌트를 사용하는 것이다.
• 변경을 감지하거나 감지하지 않는 비제어 컴포넌트를 사용하는 방법은 좋은 방법이 아니므로 피하는 것이 좋다.
• 참조와 기본값은 모든 경우 사용할 수 있지만, 제어 컴포넌트의 경우에는 사용할 필요가 없다.
• React의 <textarea> 는 innerHTML 대신 value 속성을 사용한다.
• this.refs.NAME은 클래스 참조에 접근하는 방법이다.
• defaultValue는 엘리먼트의 초기 뷰(DOM)를 설정할 때 사용할 수 있다.
• 참조를 설정하려면 ref={el => { this.input = el; }}처럼 함수를 사용하거나 ref=”NAME”으로 문자열을 사용할 수 있다.
ch08 확장성을 고려한 react 컴포넌트
컴포넌트의 기본 속성
: defaultProps로 기본 속성을 지정가능
1
2
3
4
5
6
7
8
|
class Datepicker extends React.Component{
...
}
Datepicker.defaultProps={
currentDate: Date(),
row : 4,
locale : 'US'
}
|
React 속성 타입과 유효성 검사
- : React 컴포넌트 클래스에 propType 정적 송석을 이용하면 속성 타입 설정,
- 자료형을 강제한느 대신 경고를 보여준다 (개발 모드에서는 콘솔에서 경고 메세지를 볼수있다)
- 프로덕션 모드에서는 잘못된 속성을 방지하지 않는다.
- 즉 propType은 개발단계에서의 편의 기능이다
ch08\default-props
PropTypes의 종류
PropTypes으로 설정할 수 있는 종류는 아래와 같습니다.
| kind |
description |
| array |
배열 |
| bool |
true/false |
| func |
함수 |
| number |
숫자 |
| object |
객체 |
| string |
문자열 |
| symbol |
심벌 개체(ES6) |
| node |
렌더링 가능한 모든것(number, string, element, 또는 그것들이 포함된 array/fragment) |
| element |
React element |
| instanceOf(ClassName) |
JS에서 instanceof로 정의 가능한 클래스 인스턴스 |
| oneOf([…Value]) |
포함된 값들중 하나.(ex: oneOf([‘남자’,’여자’])) |
| oneOfType([…PropTypes]) |
포함된 PropTypes들중 하나. (ex: oneOfType([PropTypes.string, PropTypes.instanceOf(MyClass)])) |
| arrayOf(PropTypes) |
해당 PropTypes으로 구성된 배열 |
| objectOf(PropTypes) |
주어진 종류의 값을 가진 객체 |
| shape({key:PropTypes}) |
해당 스키마를 가진 객체.(ex:shape({name:PropTypes.string,age:PropTypes.number})) |
| exact({key:PropTypes}) |
명확하게 해당 스키마만 존재해야함. |
Reference
Typechecking With PropTypes:https://reactjs.org/docs/typechecking-with-proptypes.html
자식 엘리먼트 렌더링
_ch08\children
{this.props.children}
{this.props.children[0]}
React.Children.count(this.props.children) 으로 배열인지 검사
React.Children.map()
React.Children.forEach()
React.Children.toArray()
공식문서 참고 [링크]https://reactjs.org/docs/react-api.html
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
|
class Content extends React.Component {
render() {
return (
<div className="content">
{this.props.children} //모든 자식을 렌더링
</div>
)
}
}
ReactDOM.render(
<div>
<Content>
<h1>React</h1>
<p>Rocks</p>
</Content>
<Content>
<img src="images/azat.jpg" width="100"/>
</Content>
<Content>
<a href="http://react.rocks">http://react.rocks</a>
</Content>
<Content>
<a className="btn btn-danger" href="http://react.rocks">http://react.rocks</a>
</Content>
</div>,
document.getElementById('content')
)
|
코드 재사용 위한 React 고차 컴포넌트 생성하기 (HOC)
- Higher-order Component
- 다른 컴포넌트의 기능을 상속받는 패턴
- HOC는 함수일쁜으로 화살표 함수를 이용해서 선언할 수 있다.
- 훌륭항 코드 추상화 방법이다
- 재사용 가능한 React 컴포넌트를 작은 모듈로 만들수 있다
- 고차 컴포넌트와 속성타입은 사용이 편리하고 개발자 친화적인 컴포넌트를 만들 수 있는 도구이다
- React에서 고차 컴포넌트를 생성하여 공통 속성,메서드, 이벤트를 캡슐화할 수 있다
const yourName= (Component) => { ... }
ch08\hi-order
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
|
const LoadWebsite = (Component) => {
class _LoadWebsite extends React.Component {
constructor(props) {
super(props)
this.state = {label: 'Run', handleClick: this.handleClick.bind(this)}
}
getUrl() {
return 'https://facebook.github.io/react/docs/top-level-api.html'
}
handleClick(event) {
document.getElementById('frame').src = this.getUrl()
}
componentDidMount() {
console.log(ReactDOM.findDOMNode(this))
}
render() {
console.log(this.state)
return <Component {...this.state} {...this.props} />
}
}
_LoadWebsite.displayName = 'EhnancedComponent'
return _LoadWebsite
}
const EnhancedButton = LoadWebsite(Button)
const EnhancedLink = LoadWebsite(Link)
const EnhancedLogo = LoadWebsite(Logo)
class Content extends React.Component {
render() {
return (
<div>
<EnhancedButton />
<br />
<br />
<EnhancedLink />
<br />
<br />
<EnhancedLogo />
<br />
<br />
<iframe id="frame" src="" width="600" height="500"/>
</div>
)
}
}
class Button extends React.Component {
render() {
return <button
className="btn btn-primary"
onClick={this.props.handleClick}>
{this.props.label}
</button>
}
}
class Link extends React.Component {
render() {
return <a onClick={this.props.handleClick} href="#">{this.props.label}</a>
}
}
class Logo extends React.Component {
render() {
return <img onClick={this.props.handleClick} width="40" src="logo.png" href="#"/>
}
}
|
displayName을 이용한 자식 컴포넌트와 부모 컴포넌트의 구분
펼침 연산자(…)를 사용해서 모든 속성 전달하기
- : 펼침연산자를 이용하면 객체(my_obj) 의 모든 속성을 엘리먼트의 속성으로 전달할 수 있다.
<Component {...my_obj} />
- 펼침 연산자는 객체 또는 변수의 모든 데이터를 전달하는 포괄문이다
<Component {...this.state} {...this.props} className="main" /> 현재 클래스의 모든 상태,속성,이름을 전달한다
ch09 메뉴 컴포넌트
메뉴 JSX로 만들기
ch09\menu-jsx
1
2
3
4
|
npm init -y //package.json 파일 생성
vi package.json //컴파일 환경 셋팅
npm i //install 개발의존 패키지 설치(devDependencies)
npm run build //컴파일 실행
|
package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"name": "menu-jsx",
"version": "1.0.0",
"description": "",
"main": "script.js",
"scripts": {
"build": "./node_modules/.bin/babel script.jsx -o script.js -w" //build -w감시 옵션추가,자동컴팔
},
"author": "Azat Mardan",
"license": "MIT",
"babel": {
"presets": ["react"] //babel 이 React JSX를 컴팔하도록 설정
},
"devDependencies": { //React , JSX프리셋과 함게 Babel CLI 추가
"babel-cli": "6.9.0",
"babel-preset-react": "6.5.0"
}
}
|
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
|
class Menu extends React.Component {
render() {
let menus = ['Home',
'About',
'Services',
'Portfolio',
'Contact us']
return (
<div>
{menus.map((v,i) => {
return <div key={i}><Link label={v}/></div>
})}
</div>
)
}
}
class Link extends React.Component {
render() {
const url='/'
+ this.props.label
.toLowerCase()
.trim()
.replace(' ', '-')
return <div>
<a href={url}>
{this.props.label}
</a>
<br/>
</div>
}
}
ReactDOM.render(<Menu />, document.getElementById('menu'))
|
ch10\tooltip
Timer 컴포넌트
ch11\timer
React 아키텍처
Webpack 빌드 도구
- : 개발 코드를 실행 가능한 최소 단위로 묶고, 편리하게 배포
- create-react-app 을 이용하면 빠르게 시작 가능(Webpack,Babel 사용할수 있도록 자동설정)
ch12\email-webpack
Webpack 역활
: Webpack이 프로젝트 내 모든 자바스크립트의 의존성을 분석한 후 다음 작업을 수행 한다
- 모든 의존 모듈을 올바른 순서로 불러오도록 한다
- 모든 의존 모듈을 한번씩만 불어오도록한다
- 자바스크립트 파일이 가능한 한 적은 파일로 묶여지도록 한다 (정적 자원이라 부른다)
진행 단계
- Webpack 설치
- 의존 모듈 설치하고 ,package.json 에 저장하기
- webpack.config.js 를 통해 webpack 설정하기
- 개발서버(webpack-dev-server)와 핫 모듈 대체 설정하기
webpack 설정
: webpack으로 처리하는 일은
- JSX파일을 javascript로 변환한다 : babel-loader, babel-core, babel-preset-react
- css-loader 를 이용해서 requier로 css를 불러오고, url과 impors 처리한다
- style-loader를 이용해서 css를
<styler> 태그로 삽입한다
- 모든 javascript파일을 bundle.js 파일이라는 하나의 파일로 묶는다
- 소스맵을 통해 개발자 도구에서 적절하게 소스 코드의 행을 확인할 수 있게 한다
webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
module.exports = {
entry: './jsx/app.jsx', //빌드를 시작할 파일을 정의(일반적으로 다른 파일을 불러오는 메인 파일이다)
output: {
path: __dirname + '/js/', //번들링이 끝난 파일의 경로를 정의
filename: 'bundle.js' //index.html에서 사용할 번들링이 끝난 파일의 파일 이름
},
devtool: '#sourcemap', //컴파일된 소스코드에서 원본 jsx소스로 적절하게 연결되도록한다, 개발자도구,디버깅유용
stats: {
colors: true,
reasons: true
},
module: {
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },//javascript에서 css를 불러오고,페이지삽입로더
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
loaders: ['babel-loader'] //JSX변환을 위한 로더 지정
}
]
}
}
|
Webpack 실행과 빌드 테스트
핫 모듈대체
- : HMR(Hot Module Replacement) 을 이용하면 화면을 새로고침(처음부터) 하지않고 바로 변경사항을 확인 할 수 잇다
- HMR을 확인하려면 새로운 설정파일과 webpack-dev-server (WDS)를 사용해야한다
webpack.dev.config.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
|
const webpack = require('webpack') //webpack불러온다
module.exports = {
entry: [
'webpack-dev-server/client/?http://localhost:8080', //진입점에 WDS추가
'./jsx/app.jsx' //앱의 메인 파일을 추가
],
output: {
publicPath: 'js/', //WDS위한 경로설정, bundle.js를 사용할수있도록 한다
path: __dirname + '/js/',
filename: 'bundle.js'
},
devtool: '#sourcemap',
module: {
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
loaders: ['react-hot-loader', 'babel-loader'] //react-hot-loader추가,모든 JSX에 자동 HMR적용
}
]
},
devServer: {
hot: true //WDS를 HMR모드로설정
},
plugins: [new webpack.HotModuleReplacementPlugin()] //HMR 플러그인 추가
}
|
./node_moduels/.bin/webpack-dev-server --config webpack-dev-config.js
React 라우팅
참고 사이트
- React Router로 라우팅 하기링크
- 1장. 리액트 라우터 사용해보기링크
라우팅
설치
1
2
3
4
|
$ create-react-app react-router-tutorial
$ cd react-router-tutorial
$ yarn add react-router-dom
$ yarn add cross-env --dev
|
소스
ch13\naive-router
ch13\router
- Rourter 컴포넌트는 URL에서 정보를받고
- 속성으로 전달받은 mapping 객체를 통해 JSX에 연결한다
- 브라우저 API windows.location.hash 를 통해 URL에 접근할 수 있다
package.json
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
|
{
"name": "naive-router",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "./node_modules/.bin/webpack -w" //편의를위해 webpack 빌드 스크립트를 npm 스크립트에 저장한다
},
"author": "Azat Mardan",
"license": "MIT",
"babel": {
"presets": [
"react" //Babel이 사용할 프리셋을 지정한다(jsx를 위해 react를 추가)
]
},
"devDependencies": {
"babel-core": "6.18.2",
"babel-loader": "6.4.1",
"babel-preset-react": "6.5.0",
"webpack": "2.4.1",
"react": "15.5.4",
"react-dom": "15.5.4"
},
"dependencies": {
}
}
//webpack.config.js
module.exports = {
entry: './jsx/app.jsx', //빌드를 시작할 진입점 파일을 정의한다(보통 메인 파일이다)
output: {
path: __dirname + '/js/', //번들파일의 경로를 저장한다
filename: 'bundle.js' //index.html에서 사용할 번들 파일이름 정의
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
loader: 'babel-loader' //JSX변환을 처리할 로더 지정
}
]
}
}
|
router.jsx
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
|
//router.jsx
const React = require('react')
module.exports = class Router extends React.Component {
constructor(props) {
super(props)
this.state = {hash: window.location.hash}
this.updateHash = this.updateHash.bind(this)
}
updateHash(event) {
// console.log(event);
this.setState({hash: window.location.hash})
}
componentDidMount() {
window.addEventListener('hashchange', this.updateHash, false)
}
componentWillUnmont() {
window.removeEventListener('hashchange', this.updateHash, false)
}
render() {
// console.log(this.props.mapping, this.state.hash)
if (this.props.mapping[this.state.hash])
return this.props.mapping[this.state.hash]
else
return this.props.mapping['*']
}
}
|
app.jsx
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
|
const React = require('react')
const ReactDOM = require ('react-dom')
const ReactRouter = require('react-router')
const History = require('history')
const Content = require('./content.jsx')
const About = require('./about.jsx')
const Contact = require('./contact.jsx')
const Login = require('./login.jsx')
const Post = require('./post.jsx')
const Posts = require('./posts.jsx')
const {withRouter} = require('react-router')
const posts = require('../posts.js')
let { Router,
Route,
Link
} = ReactRouter
let hashHistory = ReactRouter.useRouterHistory(History.createHashHistory)({
queryKey: false
})
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={Content} >
<Route path="/about" component={About} />
<Route path="/posts" component={Posts} posts={posts}/>
<Route path="/posts/:id" component={Post} posts={posts}/>
<Route path="/contact" component={withRouter(Contact)} />
</Route>
<Route path="/login" component={Login}/>
</Router>
), document.getElementById('content'))
|
Redux 를 이용한 데이터 다루
Redux 데이터 라이브러리 사용하기
:Redux는 런타임에서 애플리케이션이 처리하는 모든 데이터를 포함하고, 저장하고, 변경하는 커다란 변수다
React Router를 이용한서점 만들기
ch18\nile
- npm install 를 실행 의존 모듈 설치
- npm run build로 앱 구동
- 프로젝트 최상위에서 로컬 웹 서버를 실행, package.json 에 추가한 webpack-dev-server를 사용
- 브라우저에서 localhost:8080으로 접속
Jest,Express,MongoDB를 이용한 자동완성 컴포넌트 구현
프로젝트 구조와 Webpack 설정
웹서버 구현 Express
ch20/autocomplete/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
|
const express = require('express'),
mongodb = require('mongodb'),
app = express(), //exporess 앱 초기화
bodyParser = require('body-parser'),
validator = require('express-validator'),
logger = require('morgan'),
errorHandler = require('errorhandler'),
compression = require('compression'),
exphbs = require('express-handlebars'),
url = 'mongodb://localhost:27017/autocomplete', //MongoDB연결
ReactDOM = require('react-dom'),
ReactDOMServer = require('react-dom/server'),
React = require('react')
require('babel-register')({ //JSX파일 불러올수있도록 babel-register preset설정
presets: ['react']
})
const Autocomplete = React.createFactory(require('./src/autocomplete.jsx')),
//JSX파일을 이용해서 React 컴포넌트 함수 팩토리를 생성한다
//이렇게 하면 새로운 인스턴스를 반환하며,createElement()를 사용하지 않아도 된다
port = 3000
...
|
RESTful API 정의하기
ch20/autocomplete/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
|
...
mongodb.MongoClient.connect(url, function(err, db) {
if (err) {
console.error(err)
process.exit(1)
}
app.use(compression())
app.use(logger('dev'))
app.use(errorHandler())
app.use(bodyParser.json())
app.use(validator())
app.use(express.static('public'))
app.engine('handlebars', exphbs())
app.set('view engine', 'handlebars')
app.use(function(req, res, next){
req.rooms = db.collection('rooms')
return next()
})
app.get('/rooms', function(req, res, next) {
req.rooms.find({}, {sort: {_id: -1}}).toArray(function(err, docs){
if (err) return next(err)
return res.json(docs)
})
})
app.post('/rooms', function(req, res, next){
req.checkBody('name', 'Invalid name in body').notEmpty() //공백검사
var errors = req.validationErrors()
if (errors) return next(errors)
req.rooms.insert(req.body, function (err, result) {
if (err) return next(err)
return res.json(result.ops[0])
})
})
...
|
서버에서 React 렌더링하기
ch20/autocomplete/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
app.get('/', function(req, res, next){
var url = 'http://localhost:' + port + '/rooms'
req.rooms.find({}, {sort: {_id: -1}}).toArray(function(err, rooms){
if (err) return next(err)
res.render('index', {
autocomplete: ReactDOMServer.renderToString(Autocomplete({
options: rooms,
url: url
})),
data: `<script type="text/javascript">
window.__autocomplete_data = {
rooms: ${JSON.stringify(rooms, null, 2)}, //stringify를 이용해서 출력값을 보기좋게 만든다
url: "${url}"
}
</script>`
})
})
})
app.listen(port)
})
|
브라우저 스크립트 추가하기
ch20/autocomplete/src/app.jsx
1
2
3
4
5
6
7
8
9
10
11
|
const React = require('react')
const ReactDOM = require('react-dom')
const Autocomplete = require('./autocomplete.jsx')
const {rooms, url} = window.__autocomplete_data
ReactDOM.render(<Autocomplete
options={rooms}
url={url}/>,
document.getElementById('autocomplete')
)
|
요약
- Handlebars에서 중괄호를 이용해서 이스케이프 처리하지 않은 HTML을 출력할 수 있으며, React에서는 dangerouslySetInnerHTML의
_html을 이용한다
- findRenderedDOMComponentWithClass()는 CSS 클래스 이름을 이용해서 하나의 컴포넌트를 찾고, scryRenderedDOMComponentsWithClass()는 CSS 클래스 이름을 이용해서 여러개의 컴포넌트를찾는다
- babel-register를 사용하면 JSX파일을 불러오고 사용할 수 있다.
require('babel-register')({presets:['react'])
부록
사용된 어플리케이션 설치하기
Express
참고 site
강좌 09편: Express 프레임워크 사용해보기링크
Express와 Mongoose를 통해 MongoDB와 연동하여 RESTful API 만들기 링크
설치하기
1
|
npm i express -S //-S는 express를 package.json 의 의존목록에 추가하는 옵션
|
- 입반적으로 서버파일은
index.js , app.js, server.js 등으로 생성한다
- 실행
node 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
|
//불러오기
var express=require('express')
var app=express()
//설정
app.set('view engine','jade') //템플릿 엔진을 jade로 설정
//미들웨어 설정 , static 정적자원등
app.use(express.static(path.join(__dirname,'public')))
//라우팅 app.NAME() 패턴,
app.get('/rooms',function(req,res,next){
req.rooms.find({},{sort:{_id:-1}}.toArray(function(err,docs){
if(err) return next(err)
return res.json(docs)
})
})
//오류 핸들러
var errroHandler = require('errorhandler')
app.use(errorHandler)
//앱실행 리스너
http.createServer(app).listen(portNumber,callback)
|
Bootstrap 설치하기
- :부트스트랩Bootstrap은 웹사이트를 쉽게 만들 수 있게 도와주는 HTML, CSS, JS 프레임워크이다. 하나의 CSS로 휴대폰, 태블릿, 데스크탑까지 다양한 기기에서 작동한다. 다양한 기능을 제공하여 사용자가 쉽게 웹사이트를 제작, 유지, 보수할 수 있도록 도와준다.
- https://getbootstrap.com/
:다양한 css를 무료로 제공한다
:링크 https://bootswatch.com/
Babel을 이용한 JSX 컴파일
1
2
3
4
5
|
npm init //package.json 생성
// "babel": { "preset":["react"]}, ...
//콘솔에서 설
npm i babel-cli --save-dev
npm
|
[1]: Single Page Application (싱글 페이지 어플리케이션) 의 약자입니다. 말 그대로, 페이지가 1개인 어플리케이션이란 뜻입니다.
SPA 의 단점은, 앱의 규모가 페이지에 관련된 렌더링 관련 스크립트도 불러오기 때문이죠. 하지만 걱정하지마세요. 우리가 2장에서 배울 Code Splitting 을 다면 라우트 별로 파일들을 나눠서 트래픽과 로딩속도를 개선 할 수 있다