랄지 IT
React 정리 본문
리액트
- 복잡한 UI개발을 간단하게 만듦
- 크고 활발한 사용자 커뮤니티
- 모델이나 라우팅 라이브러리와 함께 사용시, (기존)웹/모바일 기술 스택 대체할 수 있음
* 스택: 완전한 플랫폼을 만드는데 필수적인 소프트웨어 하위 시스템 또는 구성 요소들의 모임
- JSX, 단방향 데이터 흐름, 선언형 프로그래밍
- 확고하게 MVC라고 할 수는 없다
* 이책(리액트 교과서)의 예제는 노드 버전 6, npm 버전3 기준임
<리액트 기초>
자바스크립트로 만든 리액트 컴포넌트는 UI를 재사용 할 수 있다.
백엔드에 대한 비즈니스 로직까지 갖춘 컴포넌트를 간단히 추가할 수 있다.
템플릿 없이 순수하게 자바스크립트만으로 컴포넌트로 UI를 구성하는 방법은 리액트가 최초이고,
이 방법은 유지보수와 재사용, 확장에 좀 더 용이하다.
하지만, 리액트가 웹 프론트엔드 개발의 완벽한 해결책이라고 볼 수는 없다. (장점 많지만, 단점도 있다는 뜻)
리액트는, 한마디로 자바스크립트를 이용해서 만드는 UI컴포넌트 라이브러리다.
리액트 UI 컴포넌트는 매우 독립적이며, 특정 관심사에 집중된 기능 블록이다.
(컴포넌트는 시각적 표현과 동작을 구현한다. 어떤 컴포넌트는 서버와 직접 통신하기도 한다.)
컴포넌트 기반 아키텍처 (CBA)는 리액트 이전에도 존재했지만, 템플릿 언어가 없는 순수한 자바스크립트 기반으로 CBA를 구현해냈다.
데이터가 변경될 때마다 뷰를 직접 변경하고 관리하는 것은 매우 고통스러운 일이다.
리액트는 이것을 해결하기 위해 만들었다.
- by 리액트 공식웹사이트
* 데이터 변경에 따른 뷰의 갱신은 리액트가 최우선으로 해결하려는 과제이다.
* 리액트 팀은 (리액트를 개발하기 전에) 메모리에서 DOM요소를 생산하는 것은 빠르지만, 실제 DOM으로 랜더링하는 과정에서 병목이 발생한다는 점을 알게되었고, 이런 DOM에서의 문제를 피하기 위한 알고리즘을 만들어냈다. 이것은 리액트의 속도를 높일 수 있는 성능면에서도 이득이 있었다.
리액트가 성공한 요인으로는 이런 훌룡한 성능과 개발자 친화적이며 컴포넌트를 기반으로 한 구조를 들 수 있다.
리액트의 장점
- 단순한 앱 개발: 순수 자바스크립트로 만든 컴포넌트 기반 아키텍처이므로 React Native를 사용시 안드로이드, IOS 개발 용이
- 빠른 UI: 뛰어난 성능 (가상 DOM)
- 코드량 감소: 커뮤니티, 개발 생태계를 통해 수많은 라이브러리와 컴포넌트를 접할 수 있다.
- 간결성: 사용법이 간단(다른 프론트엔드 프레임워크 등에 비해서)
리액트를 간결하게 만드는 기능들
- 선언형 스타일: 뷰를 자동으로 갱신
* 선언형 스타일: 이게 내가 원하는 모습이야!
* 명령형 스타일: 이건 이런식으로 해야 해!
- 순수한 자바스크립트를 이용한 컴포넌트 기반 아키텍처
- 강력한 추상화: 크로스브라우징 위해 다르게 구현해야 했던 인터페이스나 이벤트 핸들링을 정규화(표준화) 함
리액트는 내부적으로 가상 DOM을 사용하여 이미 반영된 뷰와 새로운 뷰의 차이점을 찾아낸다
(DOM비교 or 상태와 뷰의 보정)
=> 상태를 갱신하면 뷰는 이에 따라 자동으로 갱신된다.
=> 전체 페이지를 다시 랜더링 하는 대신 DOM을 조작해 필요한 부분만 변경한다.
리액트의 컴포넌트 클래스는 컴포넌트 기반 아키텍처를 구성하는 블록이다.
리액트는 자바스크립트와 마크업을 한 곳에 두어, 템플릿을 사용할 때처럼 매번 파일이나 언어를 바꾸는 수고를 줄여준다.
리액트는 강력한 문서 모델 추상화를 제공한다.
=> 내부의 인터페이스는 숨기고, 대신에 정규화 과정을 거친 합성 메서드와 속성을 제공한다.
(예; 브라우저 종류에 상관없이 항상 같은 이벤트 객체를 전달 받는 것, 터치 이벤트에 대해 합성이벤트를 제공하여 모바일 기기를 대응한 웹앱을 만들때 유용)
=> 서버측 렌더링 기능(SEO와 성능개선에도 유용함)
리액트는 꼭 필요한 부분만 갱신하여 내부 상태(가상 DOM)와 뷰(실제 DOM)을 같게 만든다.
리액트의 단점
- 모든 기능을 갖춘 프레임워크는 아니다.
(Angular나 Ember 같은 기능을 사용하려면 Redux나 Router같은 라이브러리를 함께 사용해야 한다.)
- 다른 프레임워크만큼 성숙하지 않다. 핵심 API는 여전히 조금씩 바뀌고 있다.
* API: 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스
- 웹 개발에 새로운 방법을 제시했음 => 정통 수준의 모범 사례, 자료 등이 많이 부족.
- 단방향 데이터 바인딩만 제공. => 복잡도를 줄이는데 도움을 주지만, 양방향 바인딩에 비해 코드를 더 작성해야 하므로 불편하게 느낄 수도 있다.
- 리액트를 리액티브 프로그래밍이라고 볼 수는 없다. => 탄력성과 반응성이 뛰어난 리액티브 프로그래밍을 위해서는 RxJS같은 도구를 사용해야 한다.
리액트를 UI일부에만 적용할 수도 있다.
리액트로 프론트엔드를 개발하기 위해 특정 백엔드를 사용할 필요는 없다.
리액트는 UI라이브러리일 뿐이므로, 어떤 형태의 백엔드나 프론트엔드 데이터 관리 라이브러리와도 함께 사용할 수 있다.
리액트 라이브러리와 렌더링 대상
- react와 react-dom이라는 두 패키지로 분리해서 npm에 배포하기 시작함.
- react와 react-dom으로 분리되면서 웹용으로 개발된 리액트 컴포넌트와 모바일 앱을 위한 리액트 네이티브용 컴포넌트 간에 코드를 공유할 수 있게 되었다.
- 여기에 리액트의 유틸리티 라이브러리가 추가되었다.
* 유틸리티: 이용, 개발에 도움이 되는 것
- 리액트는 거의 항상 JSX와 함께 사용된다.
JSX는 리액트 UI개발을 편리하게 해주는 간단한 언어이고,
Babel같은 도구를 사용해서 자바스크립트로 변환한다.
단일 페이지 애플리케이션과 리액트
- 단일 페이지 애플리케이션(SPA) 아키텍처는 서버보다는 클라이언트(즉, 브라우저)측에 로직이 더 많은 팻 클라이언트다. SPA는 HTML렌더링, 입력값 검증, UI변경 등의 기능을 브라우저에서 해결한다.
- 순서: 사용자가 url 입력 > 브라우저가 요청을 서버로 전송 > 서버는 응답으로 정적 자원(html, css, js 등) 보냄 > 자바스크립트는 로드 후, 추가로 ajax나 xhr요청을 보내 서벗에서 데이터를 불러옴 > 데이터는 json, xml등의 포맷으로 전달 받음 > spa에 데이터가 ㄷ전달되면, 사용자 인터페이스를 구성하는 html을 렌더링함, spa는 템플릿에 전달받은 데이터를 밀어 넣고 브라우저 상에서 ui를 렌더링함 > 브라우저렌더링이 끝나면 spa는 로딩을 없애고, 사용가능한 상태가 됨
- 정리: spa 방식은 ui렌더링을 대부분 브라우저상에서 해결한다. 구현방식으로는 mvc같은 아키텍처가 가장 인기가 좋지만, 다른 방식도 있다. (mvc와 유사한 아키텍처를 사용한다고 가정함)
- spa 아키텍처는 브라우저에서 렌더링 되므로 데이터도 브라우저에서 처리한다.
- 상태(state)는 변경할 수 있는 ui에 대한 데이터이다.
리액트 개발 스택
- 리액트는 모든것을 갖춘 프론트엔드 프레임워크가 아니므로, 데이터 모델링, 스타일, 라우팅 등에 대해 정해진 방법이 없다. 이 때문에 개발자들은 해당 기능을 할 수 있는 라이브러리와 리액트를 결합해서 사용한다.
- 리액트와 관련된 라이브러리는 나날이 발전하고 있고, 많은 컴포넌트가 npm 모듈로 배포되고 있다.
- 개발 스택에는 jsx도 있는데, jsx는 문법이 간단하다. <>를 이용하여 자바스크립트로 작성하는 리액트 객체를 만든다. jsx를 사용하는 이유는 리액트의 코드 구현과 가독성 면에서 더 편리하기 때문이다. jsx는 자바스크립트로 변환하는 작은언어라고 생각하면 좋다. jsx는 브라우저에서 작동하지 않지만, 컴파일 과정의 소스코드로 활용된다. coffescript와 비슷하다. jsx를 꼭 사용해야 하는 것은 아니지만 권장된다.
<리액트 실습>
- js파일 두개(react.js, react-dom.js) 추가한 후, 리액트 앱 만들어 보기
(연습용 html 파일)
<head>
<scrip src="react.js"></scrip>
<scrip src="react-dom.js"></scrip>
</head>
- body에 div id로 지정한후, 바로 밑에 스크립트 태그로 react 스크립트 추가한다
<body>
<div id="content"></div>
<scrpt>
// 요기에 작성
</scrpt>
</body>
- react 엘리먼트 생성하려면 createElement(eleName, data, child)를 호출한다
* eleName: 태그명(문자열) or 컴포넌트 클래스 객체
* data: 속성 or 상위컴포넌트에서 받는 값 (ex; {name: 'vale'})
* child: 자식엘리먼트 or (태그내부의)텍스트
- //요기에 작성 들어갈 태그 랜더링(방법1)
- h1요소를 리액트 엘리먼트로 생성하고 변수 h1에 담는다.
h1변수는 리액트 h1 컴포넌트의 인스턴스이다.
* 엘리먼트는 컴포넌트의 인스턴스 이며, 컴포넌트 클래스 라고도 한다.
- h1 요소를 ID가 content인 실제 DOM에 렌더링 한다.
var h1 = React.createElement('h1', null, 'Hello World');
ReactDOM.render(
h1,
document.getElementById('content')
)
- //요기에 작성 들어갈 태그 랜더링(방법2)
- 이런식으로 변수 없이 바로 렌더링 할 수 도 있다.
ReactDOM.render(
React.createElement('h1', null, 'Hello World'),
document.getElementById('content')
)
- 위처럼, html을 만들어서 브라우저에서 직접 여는 것보다 각자의 환경에 개발 서버를 실행하는 것이 좋다.
웹 서버 위에서는 ajax나 xhr요청을 할 수도 있다.
* XMLHttpRequest (XHR)은 AJAX 요청을 생성하는 JavaScript API입니다.
XHR의 메서드로 브라우저와 서버간의 네트워크 요청을 전송할 수 있습니다.
엘리먼트 중첩
- 리액트 엘리먼트를 생성할 때, React.createElement()를 사용하는데,
대부분의 UI는 여러 개의 HTML 요소로 이루어져 있다. (한 영역안에 버튼 여러개, 썸네일, 비디오 등...)
이렇듯 계층적 방식으로 더 복잡한 구조를 만드는 방법은 엘리먼트를 중첩하는 것이다.
- 단, ReactDOM.render()에는 하나의 리액트 엘리먼트만 인자로 전달할 수 있다.
그래서 동일한 DOM계층에 요소 두개를 렌더링 하는 경우, 두 요소를 감싸는 방법이 있다.
(<div>등을 컨테이너로 사용)
- createElement()에 전달하는 매개변수의 수는 제한이 없으므로, 아래와 같이 작성해준다.
ReactDOM.render(
React.createElement('div', null, h1, h1),
document.getElementById('content')
)
- createElement의 첫번째 매개변수로 div(컨테이너 태그) 외에도 사용자 정의 컴포넌트 클래스도 사용할 수 있다.
- React.Component 클래스를 상속받아서 리액트 컴포넌트 클래스를 생성한다.
- 컴포넌트 클래스의 render() 메서드는 엘리먼트 하나만 반환하므로,
여러개의 동일 계층 엘리먼트를 반환하려면, div등으로 감싸야 한다.
* 컴포넌트 클래스는 render()메서드가 필수사항이다.
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')
)
* 만약, HelloWorld 를 여러번 노출해야 한다면,
ReactDOM.render에서 div(컨테이너 태그)로 감싼 후, 사용해주면 된다.
ReactDOM.render(
React.createElement('div',
null,
React.createElement(Helloworld),
React.createElement(Helloworld),
...
),
document.getElementById('content')
)
- 위처럼 Helloworld 엘리먼트를 여러번 노출한경우, 모두 똑같이 노출된다.
속성을 입력해서, 내용이나 동작을 변경할 수 있다.
<리액트 속성>
리액트 컴포넌트의 속성(properties)는 리액트 선언형 스타일의 기초이다.
속성을 통해 리액트 엘리먼트가 다양한 모습을 가질 수 있다.
속성은 컴포넌트 내부에서는 변경할 수 없는 값이다.
부모 컴포넌트는 자식의 생성 시점에 속성을 할당한다.
자식 앨리먼트(다른 컴포넌트 안에 중첩된 엘리먼트; helloword안의 h1같은)는
다음과 같이 속성명에 값을 입력하는 방식으로 속성을 전달 할 수 있다.
<태그이름 속성명=값 />
리액트의 속성은 HTML 속성을 작성하는 것과 비슷하다.
* 일반적인 HTML 요소의 속성: href, title, style, class 등....
* React 컴포넌트 클래스의 자바스크립트 코드에서 this.props의 값
(예: this.props.속성명)
속성의 기능을 활용해서 속성 값에 따라 렌더링 하는 엘리먼트를 아예 다른 모습으로 바꿀 수 있다.
같은 컴포넌트에 다른 속성 값을 입력하면 컴포넌트가 렌더링한 엘리먼트의 모습을 다르게 할 수 있다는 것이다.
예를들어 다음과 같이 this.props.heading이 true이면, <h1>을, false이면 <p>를 렌더링 한다.
render(){
if(this.props.heading) return <h1>hello</h1>
else return <p>hello</p>
}
앞의 <리액트 실습>에서 사용했던 예제의
createElement() 사용시 두번째 인자로 속성('키-값' 쌍, 제한 없음)을 작성하여 넘겨준다.
ReactDOM.render(
React.createElement('div',
null,
React.createElement(Helloworld, {id: 'ember1', title: '...'}),
React.createElement(Helloworld, {id: 'ember2', title: '...'}),
...
),
document.getElementById('content')
)
Helloworld 컴포넌트의 render() 메소드 내에서 this.props 객체에 접근하면
createElement() 의 두번째 매개변수로 전달한 객체에 접근할 수 있다.
class HelloWorld extends React.Component{
render(){
return React.createElement(
'h1',
null,
'Hello ' + this.props.title + ' world'
)
}
}
<JSX>
JSX는 리액트 컴포넌트를 생성하는 좋은 방법이다.
개발자 경험(DX)를 개선해준다. => 중첩된 선언형 구조를 잘 나타내주므로 가독성이 좋다.
생산성이 향상되고, 문법 오류와 코드량이 감소한다.
JSX가 리액트에 필수적이지는 않지만 잘 어울리고, 리액트 제작자들도 사용을 권장하고 있다.
(JSX는 createElement같은, 리액트 메서드를 위한 문법적 개선이다. )
JSX 사용전 코드)
React.createElement(
'div',
null,
React.createElement(Helloworld, null),
React.createElement('br', null),
React.createElement('a', {href: 'http...'}, 'Greate..')
)
JSX 사용후 코드)
<div>
<Helloworld/>
<br />
<a href="http...">Greate..</a>
</div>
JSX는 React.createElement()로 함수 호출을 반복해서 작성하는 대신
<NAME />으로 작성해서 마치 HTML처럼 보이게 하므로 입력한 내용을 줄이고 가독성도 좋게 만든다.
JSX로 작성한 코드는 Babel등의 트랜스파일러(도구)를 사용해서 자바스크립트로 변환해야 한다.
*JSX 트랜스파일러(Babel) 사용 방법
1. node.js에서 bable-core 패키지를 이용
(babel cli, babel-preset-react를 지역모듈로 설치 후, package.json파일에서 presets 항목 작성)
2. 명령줄 인터페이스 도구인 bable-cli 패키지 사용
3. 빌드도구(Grunt, Gulp, Webpack)에서 Babel을 플러그인으로 사용
JSX없이 리액트를 사용할때는 +를 이용하여 연결하거나,
백틱(`)과 ${변수명}으로 표시한 문자열 템플릿(템플릿 리터럴)을 사용할 수 있다.
=> JSX에서는 중괄호{} 표기법을 사용하여 변수를 동적으로 출력할 수 있다.
*JSX에서는 {}표기법으로 변수를 출력하고, 자바스크립트 코드도 실행할 수 있따.
class DateTimeNow extends React.Component {
render(){
let dateTimeNow = new Date().toLocalString()
return <span>Current date and time is {dateTimeNow}</span>
}
}
변수 뿐만 아니라 속성도 출력할 수 있다.
<span>Hello {this.props.userName}, your.... </span>
엘리먼트 속성을 정의 할때는, JSX 태그안에 key=value 같은 표기법으로 속성을 정의할 수 있다.
속성에 하드코딩한 값을 사용하는 것은 유연하지 않으므로, 속성에 동적으로 생성한 값을 엘리먼트에 전달할 수 있다.
ReactDOM.render((
<div>
<a href={this.props.url}>Time</a>
<DateTimeNow userName="Azat" />
</div>
),
document.getElementById('content')
)
리액트 컴포넌트 메서드;
리액트 컴포넌트가 클래스이기 때문에 메서드를 자유롭게 추가할 수 있다.
JSX안에서 직접 반환값을 출력하려면, {}를 사용한다.
class Content extends React.Component{
getUrl(){
return 'http...'
}
render(){
return {
<div>
<a href={this.getUrl()}>your...</a>
</div>
}
}
}
if/else 처리
다음과 같이 조건의 결과에 따라 컴포넌트가 뷰를 변경할 수 있도록 할 수 있다.
render(){
if(this.props.user.session)
return <a href="/logout">Logout</a>
else
return <a href="/login">Login</a>
}
JSX 사용시 주의 사항
JSX는 자식 엘리먼트가 없거나 단일 태그를 사용할 때는 태그를 닫을 때 반드시 슬래시(/)를 넣어야 한다.
JSX는 위험한 (특수한) 구문에 대해 자동으로 이스케이프를 적용하므로, ©... 등과 같은 엔터티코드 사용이 불가하다. (특수문자를 직접 복사해서 넣거나 해야함)
가끔 DOM노드에 추가 데이터를 전달해야 하는 경우가 있는데, (지양해야 하지만, 간혹 사용시) data-를 사용한다.
<li data-object-id="...">...</li>
JSX의 스타일 속성은 자바스크립트 객체를 전달하고, CSS 속성은 카멜표기법으로 작성한다.
let smallFontSize = {fontSize: '10px'}
<input style={smallFontSize} />
혹은
<input style={{ fontSize:'10px', borderColor:'red' }}
JSX는 class와 for를 제외하면, 표준 HTML 속성을 모두 사용할 수 있다.
=> class는 className으로, for는 htmlFor를 사용한다.
속성에 값을 입력하지 않은 경우, 적용되는 기본값은 true 다.
<input disabled />
<리액트 상태>
속성(properties)은 해당 컴포넌트 생성시에 전달받는 값이기 때문에,
현재 컴포넌트 내부에서 수정할 수 없다.
* 부모 컴포넌트에서만 자식의 속성을 변경할 수 있다.
=> 속성을 사용하여 뷰를 갱신하기 위해서는 서버에서 응답을 받을때마다 새로운 속성으로 엘리먼트를 렌더링 하는 것이다. 하지만 이 경우 관련된 로직을 컴포넌트 외부에 작성해야 하므로 독립적인 컴포넌트가 될 수 없다.
이때 사용하는 새로운 이벤트에 대응하여 뷰를 갱신하기 위해 변경할 수 있는 자료형이 상태(state)이다.
리액트의 상태는 컴포넌트의 변경 가능한 데이터 저장소이다.
변경가능 하다는 것은 상태 값을 변경할 수 있다는 것이다.
컴포넌트를 다시 생성하지 않고 사용자 조작으로 발생한 이벤트를 처리하여 뷰를 갱신하기 위해서는 상태 객체를 이용하면 된다. 즉, 서버 응답에 따라 콜백 코드가 컴포넌트의 상태를 변경하고, 상태를 갱신하고 나면 뷰가 영리하게 뷰를 갱신한다. (변경 부분만 갱신)
리액트 컴포넌트의 상태(state)는 리액트 컴포넌트에 데이터를 저장하고,
데이터의 변경에 따라 자동으로 뷰를 갱신하도록 하는 핵심 개념이다.
다음과 같이 상태 객체에 접근 할 수 있다.
this.state.객체의속성명
상태 데이터는 흔히 뷰의 렌더링이 갱신될 때 동적 정보를 출력하기 위해 사용된다.
=> 사용자 입력을 받아 서버에 xhr 요청을 보내고, 돌아온 응답에 따라 상태를 변경한다.
=> 변경된 상태를 뷰에 반영하여 뷰를 최신 상태로 유지한다. (변경된 상태에 관련된 부분만 갱신)
reder() 에서 상태 데이터를 사용하려면 먼저 상태를 초기화해야한다.
=> React.Component를 사용하는 클래스의 생성자에서 this.state를 선언한다.
반드시 super()에 속성을 전달하여 실행해야 한다. (부모 클래스의 기능을 정상적으로 사용하기 위해)
* this.state의 값은 반드시 객체{} 여야 한다.
class MyFancyComponent extends React.Component {
constructor(props){
super(props)
this.state = {...}
}
render(){
...
}
}
상태 객체는 배열이나 다른 객체를 중첩해서 가질 수 있다.
this.state = {
name: 'abc',
books: [
...
]
}
constructor() 메서드는 리액트 엘리먼트가 생성되는 시점에 한번만 호출된다.
즉, constructor()내에서 한번만 this.state로 직접 상태를 선언할 수 있다.
이외의 부분에서는 직접 상태를 선언하지 않도록 해야한다.
this.setState(data, callback)를 사용하면 상태를 변경할 수 있다. setState()가 비동기로 작동하기 때문에 콜백함수를 사용할 수 있다.
=> 정리하면 생성자에서 this.state로 초기값 설정후, setState()를 통해 값을 갱신하는 것이다.
1초마다 시간 갱신되는 시계 컴포넌트 예제)
class Clock extends React.Component {
constructor(props){
super(props)
this.launchClock()
this.state = {
currentTime: (new Date()).toLocalString('en')
}
}
launchClock(){
setInterval(()=>{
console.log('update time...')
this.setState({
currentTime: (new Date()).toLocalString('en')
})
})
}
render(){
console.log('Rendering Clock...')
return <div>{this.state.currentTime}</div>
}
}
setState()로 전달하는 상태는 상태 객체의 일부분만 갱신한다. 매번 상태 객체를 완전히 바꾸지 않는다는 뜻이다.
만약, 상태 객체의 값을 전부 갱신하고 싶다면 setState()에 새로운 값을 명시적으로 전달해야 한다.
*코드가 외부 데이터에 의존하는 매우 특이한 경우, 다시 렌더링 하기 위해 this.forceUpdate()를 호출할 수 있따. 그렇지만 이 방법은 상태가 아닌, 외부 데이터에 의존하여 컴포넌트를 불안정하게 만들고, 외부 요소와 강하게 결합되어 좋지 않으므로 피해야 한다.
상태 비저장 컴포넌트
- 상태 객체가 없으며, 컴포넌트 메서드 또는 다른 리액트의 라이프 사이클 이벤트 또는 메서드를 갖지 않는다.
- 상태 비저장 컴포넌트의 목적은 오직 뷰를 렌더링 하는 것이다.
- 이 컴포넌트가 할 수 있는 것은 속성을 전달 받아 처리하는 것 뿐이다.
- 오직 HTML로 렌더링 하는 것이 필요한 작업의 전부 일때 사용한다.
- 출력을 결정하는 입력이 한가지(속성) 뿐이기 때문에 예측할 수 있다.
- 상태 비저장 컴포넌트는 더 많이 사용할 수록, 상태 저장 컴포넌트는 더 적게 사용할 수록 더 좋다.
- 상태를 가질 수 없지만, propType과 defaultProps를 프로퍼티로 가질 수 있다.
상태 비저장 컴포넌트 예)
class HelloWorld extends React.Component{
return <h1 {...this.props}>Hellow {props.name} World</h1>
}
* 상태 비저장 컴포넌트는 함수형 스타일을 사용하여 더 간결하게 작성할 수 있다.
=> 상태가 필요하지 않다면, 리액트 컴포넌트를 함수로 선언할 수 있다.
const HelloWorld = function(props){
return <h1 {...this.props}>Hellow {props.name} World</h1>
}
* 화살표 함수 사용시
const HelloWorld = (props) => {
return <h1 {...this.props}>Hellow {props.name} World</h1>
}
* 함수 선언식 사용시,
function Link(props){
return <a href={props.href} target="_blank" className="btn btn-primary">
{props.text}</a>
}
ReactDOM.render(
<Link text="Buy.."
href="http..." />
document.getElementById('content')
)
* 상태 비저장 컴포넌트에 객체 리터럴로 CSS 사용하기
const AnalogDisplay = function AnalogDisplay(props){
let date = new Date(props.time)
let dialStyle = {
position: 'relative',
top: 0,
left: 0,
borderRadius: 20000,
...
}
let secondStyle = {...}
let minuteHandStyle = {...}
let hourHandStyle = {...}
return <div>
<div style={dialStyle}>
<div style={secondStyle}></div>
<div style={minuteHandStyle}></div>
<div style={hourHandStyle}></div>
</div>
</div>
}
보통 상태 비저장 컴포넌트는 (화살표)함수로 작성한 컴포넌트,
상태 저장 컴포넌트는 클래스로 작성한 컴포넌트를 의미하여 구분하고 있다.
상태 비저장 컴포넌트는 단순하게 유지하는 것을 권장하므로,
상태 객체나 메서드를 추가하지 않도록 해야한다.
상태 비저장 컴포넌트를 여러개의 상태 저장 컴포넌트와 적절히 사용하면
좀 더 유연하고, 간단하며, 더 나은 설계를 할 수 있다.
<컴포넌트 라이프사이클 이벤트>
컴포넌트를 좀 더 세밀하게 제어해야할 경우, 컴포넌트 라이프 사이클 이벤트를 사용할 수 있다.
(예를들어, 화면 너비에 따라 크기가 변경되는 경우, 서버에 xhr 요청을 보내 정보를 가져오는 메뉴 컴포넌트를 개발 해야 하는 경우 등)
모든 리액트 컴포넌트는 라이프 사이클 이벤트가 있다.
컴포넌트가 수행한 작업이나 앞으로 수행할 작업에 따라 특정 시점에 실행된다. (한번만 or 계속)
라이프 사이클 이벤트는 다음과 같이 세가지로 분류된다.
1. 마운팅 이벤트(한번): 리액트 엘리먼트를 DOM노드에 추가할 때 발생
- componentWillMount() : 삽입전(서버,클라이언트 모두 실행)
- componentDidMount() : 삽입후(클라이언트에서만 실행)
2. 갱신 이벤트(여러번): 속성이나 상태가 변경되어 리액트 엘리먼트를 갱신할 때 발생
- componentWillReceiveProps(nextProps) : 속성받기 전(속성 받는 시점에 끼여들어서 로직 추가)
- shouldComponentUpdate(nextProps, nextState) : 렌더링 직전에 실행(false 반환시 재 랜더링 되지 않도록 하여 재렌더링을 제어할 수 있다.=>랜더링 최적화)
- componentWillUpdate(nextProps, nextState) : 갱신 전
- componentDidUpdate(nextProps, nextState) : 갱신 후
3. 언마운팅 이벤트(한번): 리액트 엘리먼트를 DOM에서 제거할 때 발생
- componentWillUnmount() : 컴포넌트 제거 전(정리에 사용)
* this.forceUpdate()를 호출하면, 갱신을 강제하여 컴포넌트가 재렌더링 된다.
=> 컴포넌트의 순수성을 해치기 때문에 사용하지 않는것이 좋다.
라이프사이클 이벤트를 구현하려면, 클래스에 메서드를 정의해야 한다. (규칙)
=> 특정 메서드가 정의되어 있다면, 컴포넌트의 실행주기 중에 이 메서드를 호출한다.
class Content extends React.Component {
componentWillMount(){ ... }
componentDidMount(){ ... }
render(){
return (...)
}
}
컴포넌트에서 데이터를 가져오는 등의 로직을 구현할 때, 사용할 수 있을 것이다.
*언마운팅 예제)
componentDidMound()에 이벤트리스너 추가후,
componentWillUnmount()에서 이벤트리스너를 제거한다.
class Note extends React.Component{
confirmLeave(e){ ... }
componentDidMount(){
window.addEventListener("beforeload", this.confirmLeave)
}
componentWillUnmount(){
window.removeEventListener("beforeload", this.confirmLeave)
}
}
componentWillUnmount()에서 이벤트 리스너를 제거하지 않으면 Note 엘리먼트를 제거한 후에도 대화상자가 계속 노출된다. 대부분의 라이프사이클 이벤트는 개발자가 컴포넌트의 행동을 조정할 수 있게 해준다.
<리액트에서 DOM 이벤트 다루기>
이벤트 핸들러를 정의해서 사용자 조작에 대응할 수 있는 리액트 엘리먼트를 만드는 방법을 살펴보자.
JSX로 작성한 엘리먼트에 속성값으로 이벤트 핸들러(함수 정의)를 정의한다.
속성으로 사용하는 이벤트 이름은 표준 W3C DOM 이벤트를 onClick, onMouseOver 처럼 카멜 표기법으로 작성한다.
사용예)
<button onClick={(function(event) {
console.log(this.event)
}}.bind(this)>
Save
</button>
* bind()를 이용하면, 이벤트 핸들러 함수가 클래스 인스턴스인 리액트 엘리먼트에 대한 참조를 유지할 수 있다.
this를 이용해서 해당 클래스를 참조할 필요가 없거나, 화살표 함수를 사용할때는 사용하지 않는다.
이벤트 핸들러를 클래스 메서드로 선언하면 좀 더 깔끔하다
class SaveButton extends React.Component{
handleSave(event){ ... }
render(){
return <button onClick={this.handleSave.bind(this)}>
Save
</button>
}
}
클래스의 constructor에서 이벤트 핸들러를 클래스에 바인딩할 수도 있다.
=> 메서드를 한번 이상 사용한다면, 생성자에서 바인딩하여 중복을 줄일 수 있다.
class SaveButton extends React.Component{
constructor(props){
super(props)
this.handleSave = this.handleSave.bind(this)
}
handleSave(event){ ... }
render(){
return <button onClick={this.handleSave}>
Save
</button>
}
}
리액트는 선언형 스타일이므로, 객체를 조작할 필요가 없고, 제이쿼리같은 방식으로 이벤트를 등록하지 않는다.
대신에 onClick={handleSave} 처럼 JSX에 속성으로 이벤트를 선언한다.
리액트가 이벤트를 각 요소가 아닌 최상위 부모인 document 요소에 연결한다.
이 덕분에 리액트는 좀 더 빠르게 작동하고, 높은 성능을 가지게 된다.
만약, 같은 종류의 이벤트를 사용하는 다른 엘리먼트가 있다면, 리액트가 내부적으로 올바른 자식 엘리먼트와 매핑하여 처리한다.
리액트는 브라우저 내장 이벤트를 감싸여 크로스 브라우징 문제를 해결했다.
=> 즉, 리액트는 내장 이벤트 객체를 크로스 브라우징 목적으로 감싸서 브라우저 이벤트를 합성(또는 정규화)한다.
합성 이벤트 객체를 제공함으로써 거의 대부분의 표준 HTML DOM 이벤트를 지원한다.
이벤트 핸들러를 클래스 메서드로 작성하고 render()에서 바인딩한다.
대부분의 경우, 리액트에서 이벤트 핸들러를 작성할 때는 일반적인 클래스 메서드와 구분하기 위해 handle을 앞에 붙이고 mouseOver 같은 이벤트 이름을 넣거나 save처럼 수행하는 동작을 이름으로 사용한다.
예) handleMouseOver(event){ ... }
이벤트와 함께 상태를 사용하여 이벤트를 처리하고 컴포넌트의 상태를 변경할 수 있다면 사용자 조작과 상호작용하는 UI를 만들 수 있을 것이다.
앞서 살펴본 상태비저장 컴포넌트는 프레젠테이션 컴포넌트라고 부르고, 상태 저장 컴포넌트는 컨테이너 컴포넌트라고부른다.
프레젠테이션 컴포넌트는 별도의 상태가 없으므로 부모 컴포넌트가 전달한 이벤트 핸들러를 this.props.handler 속성으로 접근하여 사용한다.
프레젠테이션 컴포넌트의 이벤트 사용예)
class ClickCounterButton extends React.Component{
render(){
return <button
onClick={this.props.handler}
className="btn btn-danger">
Increase Volumn Current volume is {this.props.counter}
}
}
ClickCounterButton 컴포넌트에 counter와 이벤트 핸들러를 속성으로 전달하려면, 부모 컴포넌트의 render() 메서드에서 JSX를 작성할때 속성으로 추가한다.
ClickCounterButton의 counter는 속성이므로 변경할 수 없다.
그렇지만 부모 컴포넌트인 Content에서는 상태이므로 변경할 수 있다.
* 이벤트 핸들러를 속성으로 전달한다.
ClickCounterButton 의 부모컴포넌트 예)
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.handlerClick} />
</div>
)
}
}
리액트가 지원하지 않는 DOM이벤트(예를들어 resize 등)은
리액트 컴포넌트의 라이프 사이클 이벤트를 사용한다.
=> componentDidMount()에서 이벤트 리스너를 추가하고,
componentWillUnmount()에서 제거한다.
* 이처럼 별도로 추가한 이벤트 리스너는 언마운팅 이벤트에서 제거해야 한다.
제이쿼리 UI 이벤트 등 다른 라이브러리와 리액트를 통합할때는,
제이쿼리 UI 플러그인에 이벤트 리스너를 설정하고,
리액트 라이프 사이클 이벤트(마운트,언마운트)에 window 객체에 이벤트를 연결하여
느스한 결합으로 통합하는 것이 좋다.
<리액트에서 폼 다루기>
전통적인 HTML의 폼 요소는 사용자 입력에 의해 요소의 상태가 변경된다.
그렇지만 리액트는 UI를 묘사하기 위해 선언형 스타일을 사용하므로 상태를 적절히 반영하려면 입력이 동적이어야 한다.
따라서 리액트의 render() 메서드를 폼 요소의 데이터를 포함한 실제 DOM에 최대한 밀접하게 유지하는 것이다.
// render() 에서 상태값을 이용해 엘리먼트를 정의한다.
render(){
return <input type="text" name="title" value={this.state.title} />
}
리액트가 변경을 감지할 수 있도록 onChange에 이벤트 핸들러를 추가해야 한다.
// onChange를 이용해서 폼요소에서 발생하는 변경사항을 감지하고,
// 이벤트 핸들러에서 내부상태를 갱신한다
handleChange(event){
this.setState({ title: event.target.value })
}
// 새로운 값이 상태에 저장되면 새로운 render()가 실행되어 뷰를 갱신한다.
render(){
return <input type="text" name="title" value={this.state.title} onChange={this.handleChange.bind(this)} />
}
=> 정리: 리액트에서 입력영역을 다루는 방법은, 변경을 감지한후 상태에 반영하여 뷰를 갱신시키는 것이다.
일반적으로 입력 요소를 DOM의 아무곳에나 무작위로 두지 않고, 공통목적을 가진 항목끼리 묶어서 form 요소로 감싼다. 물론 필수는 아니고, 간단한 UI에서는 폼 요소를 따로 사용해도 괜찮다.
리액트에서는 표준 React DOM 이벤트와 함께 폼 요소를 위한 세가지 이벤트를 지원한다.
* onChange: 폼의 입력 요소에 변경이 생기면 발생
* onInput: <textarea>,<input> 요소의 값이 변경될때 발생 (*사용을 권장하지 않음)
* onSubmit: 폼 제출시 발생 (엔터 눌렀을때)
리액트는 변경 가능한 속성인 value, checked, selected를 두어 폼 요소를 특별하게 다루고 있다.
이 특별한, 변경 가능한 속성을 대화형 속성 이라고 부르고, 다음과 같다.
* value: <input>,<textarea>,<select>에 적용
* checked: <input>에 type="checkbox" 또는 type="radio"인 경우 적용
* selected: <select>와 함께 <option>을 사용할때 적용
input 요소에서 value를 변경 가능한 속성으로 사용하지 않는 두가지 예외는 체크박스와 라디오 버튼이다.
HTML요소당 값을 하나만 가지므로 값이 변경되지 않지만, checked 또는 selected 속성이 변경되는데,
다음과 같이 라디오 버튼 3개를 한 그룹으로 정의할 수 있다.
사용자 조작에 의해 처리되는 라디오 버튼, 체크박스의 경우ㅡ value는 변경할 필요가 없이 하드코딩 되어있지만, 사용자 조작에 의해 변경되는 요소의 checked 속성으로 다음 예제를 통해 라디오 버튼의 렌더링과 변경처리를 살펴보자.
constructor(props){
super(props)
this.handleRadio = this.handleRadio.bind(this)
...
this.state = {
...
radioGroup = {
angular: false,
react: true,
polymer: false
}
}
}
handleRadio(event){
let obj = {}
obj[event.target.value] = event.target.checked // true
this.setState = ({radioGroup:obj})
}
render(){
return <form>
<input type="radio"
name="radioGroup"
value="angular"
checked={this.state.radioGroup['angular']}
onChange={this.handleRadio} />
<input type="radio"
name="radioGroup"
value="react"
checked={this.state.radioGroup['react']}
onChange={this.handleRadio} />
<input type="radio"
name="radioGroup"
value="polymer"
checked={this.state.radioGroup['polymer']}
onChange={this.handleRadio} />
</form>
}
체크박스도 라디오 버튼과 비슷한 방법을 사용한다.
checked 속성을 사용하고, 상태에 불값을 저장한다. 체크박스 그룹 상태도 저장한다.
라디오 버튼은 선택값이 하나이므로 상태에서 할당할 필요가 없어서 빈 객체에 사용할 수 있지만,
체크박스의 경우는 선택값이 여러가지 이므로 교체하는 대신 병합해야 한다.
cloneData = JSON.parse(JSON.stringify(originData))
* 자바스크립트에서 객체는 참조를 통해 전달 및 할당된다. 따라서 Object.assign()을 이용해서 값을 할당(복제)하는 것이 좋다.
* 상태에서 객체 대신 배열을 사용하는 경우는 Array.from(originArray) 또는 originArray.slice()를 사용할 수 있다.
handleCheckbox() 이벤트핸들러를 사용해서 event.target.value 에서 값을 가져올 수 있다.
<textarea> 요소는 장문 입력을 감지하고, 보여주기 위해 사용된다.
리액트에서는 value 속성을 사용한다. (input과 동일: value={this.state.텍스트값}, 변경감지 onChange 사용)
* 리액트에서는 textarea의 자식으로 텍스트를 넣거나 innerHTML로 값을 설정하는 것은 안티패턴이다.
<select>와 <option> 요소 역시 value를 사용하는데, 다중 선택 요소를 사용해야 하는 경우도 있다.
다중요소 사용시 multiple 속성시 true로 처리한다. 또는 명시적으로 값을 주어도 된다.
전반적으로 리액트에서 폼요소 정의하는 것은 value를 더 자주 사용한다는 점을 제외하면 일반적인 HTML과 크게 다르지 않다.
폼요소의 변경을 감지할 때는 onChange 이벤트 리스너를 이용한다.
onChange 이벤트는 모든 새로운 입력에 대해 발생하는데, 발생 요인은 요소에 따라 차이가 있다.
* input, textarea, select: value가 변경될때
* input type 체크박스와 라디오버튼: checked가 변경될때
읽기전용이 필요한 경우라면, 명시적으로 readOnly 속성을 추가하는 것이 좋다.
정보를 서버나 다른 컴포넌트로 볼때, 값을 상태에 깔끔하게 정리해야 한다.
폼요소 렌더링 예)
constuctor(props){
super(props)
this.handleInput = this.handleInput.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
...
}
handleFirstNameChange(event){
this.setState({firstName: event.target.value})
}
handleSubmit(){
fetch(this.props['data-url']), {method: 'POST', body: JSON.stringify(this.state)})
.then((response) => {return response.json()})
.then((data) => {console.log('Submitted: ', data)})
}
render(){
return <form>
<input name="firstName"
onChange={this.handleFirstNameChange}
type="text" />
...
<input
type="button"
onClick={this.handleSubmit}
value="Submit" />
}
*fetch api는 프러미스 기반의 ajax/xhr 요청을 수행할 수 있는 실험적인 브라우저 내장 메서드이다.
뷰의 DOM 상태와 React의 내부 상태에 차이가 있을 수 있다는 점을 들어 비제어 컴포넌트를 권장하지 않는다고 설명하기도 했지만, 서버에 전달할 간단한 폼을 만들 때는 비제어 컴포넌트가 유용하다.
비제어 컴포넌트를 사용하려면, 폼에서 제출 이벤트를 정의(버튼에 onClick, 폼의 onSubmit 이벤트를 사용)해야 한다.
비제어 컴포넌트는 value속성을 리액트에서 설정하지 않는 것을 의미한다.
컴포넌트의 내부 값 또는 상태가 컴포넌트의 표현 또는 뷰와 서로 다를 수 있다.
비제어 컴포넌트를 사용하는 경우에는 사용자가 폼 요소에 무엇이든 입력할 수 있으므로 뷰와 상태 사이에 차이가 발생한다. 예를 들어 다음 입력 영역은 리액트에서 value를 설정하지 않았으므로 비제어 컴포넌트 이고, 사용자 입력이 즉시 뷰에 렌더링 된다.
render(){
<input type="text" />
}
1. input에 value를 설정하지 않고, 이벤트 리스너(onChange)만 설정한다.
2. 변경을 감지하지 않고 비제어 엘리먼트를 사용하려면 다른 엘리먼트에 접근해서 데이터를 가져올 수 있는 방법이 필요하다. (refs를 통해 참조로 값에 접근한다.)
* redner 메서드에서 반환하는 엘리먼트의 ref 속성에 문자열을 전달하는 경우 카멜 표기법으로 작성되어 있어야 한다. 예) email: <input ref="userEmail" />=> 지정한 이름으로 다른 메서드에서 DOM인스턴스에 접근한다. (this.refs.userEmail)
위처럼 참조를 사용하는 것은 비제어 엘리먼트를 사용하는 경우처럼 흔하지 않은 경우이고, 참조를 과하게 사용하는 것은 좋지 않다. 대부분의 제어 엘리먼트에서는 참조를 사용하는 대신 컴포넌트 상태를 사용한다.
(ref 속성으로 함수를 할당하는 것도 가능하다. )
입력 영역의 기본값은 defaultValue를 사용해서 설정하거나 상태(this.state.이름)를 사용 할 수 있다.
* 폼을 다루는 방법은 제어 컴포넌트를 사용하는 방법(변경 감지후 이벤트리스너로 상태에 데이터를 저장)이 권장된다.
* 참조와 기본값은 모든 경우 사용할 수 있지만, 제어 컴포넌트의 경우에는 사용할 필요가 없다.
---
<확장성을 고려한 리액트 컴포넌트>
다른 개발자가 만든 컴포넌트 속성 제대로 전달 어찌 암? => 개발 확장성의 문제
리액트에서는 확장성 고려시 도움 되는 기능과 패턴 있음
즉, 복잡한 리액트 애플리케이션을 효과적으로 개발하는 방법이다.
=> 고차 컴포넌트는 컴포넌트의 기능을 향상시킬 수 있고,
속성 타입은 안정적인 타입 검사를 제공하고 컴포넌의 온전성을 보장한다.
속성 타입을 사용해서 좀 더 개발자 친화적인 코드로,
고차 컴포넌트로 효율적인 작업을 수행할 수 있게 해준다.
컴포넌트 기본 속성
- 만약 다른 개발자가 속성 누락하거나ㅡ, 잘못입력(숫자를 문자열로 등) 하면...?
- defaultProps로 속성 기본값!
class Component { ... }
Component.defaultProps = {...}
속성 타입과 유효성 검사
- propTypes 정적 속성을 이용하면 속성 타입을 설정할 수 있다.
- 개발 단계에서 잘못 사용한 자료형에 대해 경고하는 편의 기능임
- PropTypes 종류: string, symbol, number, bool, object, array, func, shape, any.isRequired, objectOf(...), arrayOf(...), node, instanceOf(message), element, oneOf([...]), oneOfType([...])
class Component { ... }
Component.propTypes = {
currentDate: PropTypes.string,
rows: PropTypes.number,
locale: PropTypes.oneOf(['US','CA',...])
}
자식 엘리먼트를 얼마든지 추가할 수 있는 범용 컴포넌트를 생성하는 경우
아래의 두가지는 모두 Content를 사용하지만 서로 다른 자식을 전달한다.
1.
<Content>
<자식1></>
</Content>
2.
<Content>
<자식2></>
</Content>
자식을 전달하면 무엇이든 렌더링 할 수 있는 훌룡한 기능이 children 속성이다.
children 속성은 모든 자식을 {this.props.children} 으로 렌더링 할 수 있는 간편한방법이다.
this.props.children을 사용하면 유연하고 강력하며 범용적인 컴포넌트를 생성하여 얼마든지 자식을 추가할 수 있다.
class Content extends React.Component{
render(){
return {
<div className="content">
{this.props.children}
</div>
}
}
}
자식이 하나 이상이면 children은 배열이 된다.
{this.props.children[0]}
{this.props.children[1]}
...
자식 다룰때 사용할 수 있는 헬퍼 메소드도 있다.
React.Children.map()
React.Children.forEach()
React.children.toArray()
코드 재사용을 위한 리액트 고차 컴포넌트
- 여러컴포넌트에 서로 다른 시각적 표현? => 이럴때 고차컴포넌트 사용
- 고차컴포넌트는 컴포넌트에 추가적인 로직을 적용해서 컴포넌트를 향상시킬 수 있고,
다른 컴포넌트가 기능을 상속받는 패턴이다.
- 한마디로 고차컴포넌트(HDC)는 재사용이 가능한 코드이다.
- 고차컴포넌트는 원래 컴포넌트 렌더링하면서 추가적인 기능을 포함시키도록 하는 컴포넌트 클래스이다.
- 고차 컴포넌트는 함수이고, 화살표함수를 이용해서 다음과 같이 선언한다.
// 인자는 향상되기 전의 원래의 컴포넌트이다.
const 고차컴포넌트이름 = (Component) => {}
고차컴포넌트를 구현하려면 두가지 새로운 기능(displayName, 펼침연산자...)를 사용한다.
- 컴포넌트의 이름을 변경하는 경우 displayName 정적 속성을 사용할 수 있다.
(엘리먼트의 이름을 컴포넌트의 이름과 다르게 해야할때 displayName을 설정한다)
- 펼침 연산자를 사용해서 모든 속성을 전달 할 수 있다.
- 다음과 같이 객체(obj)의 모든 속성을 엘리먼트의 속성으로 전달할 수 있다.
// 펼침 연산자는 객체 또는 변수의 모든 데이터를 전달하는 포괄문이다.
<Component {...obj} />
고차컴포넌트의 사용법
1. 고차컴포넌트를 이용해서 새로운 컴포넌트를 생성한다.
const EnhancedButton = LoadWebsite(Button)
const EnhancedLink = LoadWebsite(Link)
const EnhancedLogo = LoadWebsite(Logo)
2. 코드 재사용을 위해 세컴포넌트(Button,Link,Logo)를 구현한다
class Button extends React.Component{
render(){
return <button
className="btn"
onClick={this.props.handleClick}>
{this.props.label}
</button>
}
}
Link 컴포넌트도 고차컴포넌트를 이용해서 생성하므로 handleClick과 label 속성을 사용할 수 있다.
class Link extends React.Component {
render(){
return <a onClick={this.props.handleClick} href="#">{this.props.label}</a>
}
}
Logo 컴포넌트도 같은 속성을 사용한다.
세가지 컴포넌트의 렌더링은 모두 다르지만, LoadWebsite에서 this.props.handleClick과 this.props.label을 받았다.
부모컴포넌트가 세가지 엘리먼트를 렌더링 하게 된다. 기능은 같다
const EnhancedButton = LoadWebsite(Button)
const EnhancedLink = LoadWebsite(Link)
const EnhancedLogo = LoadWebsite(Logo)
class Content extends React.Component = {
render(){
<div>
<EnhancedButton />
<EnhancedLink />
<EnhancedLogo />
</div>
}
}
마지막으로 Content를 렌더링한다.
ReactDOM.render(
<Content />
document.getElementById("content")
)
리액트 코드의 확장성을 고려하면,
컴포넌트를 프레젠테이션 컴포넌트와 컨테이너 컴포넌트로 구분할 수 있다.
프레젠테이션 컴포넌트는 DOM과 스타일에 구조만 추가한것으로 상태를 갖는 경우는 없다. 상태비저장 프리젠테이션 컴포넌트를 함수로 작성할 수 있다.
데이터나 상태를 다루는 것은 컨테이너 컴포넌트의 역할이다.
프리젠테이션컴포넌트나 프리젠테이션 컴포넌트는 모두 다른 프레젠테이션 컴포넌트나 컨테이너 컴포넌트을 가질 수 있다.
***
<프로젝트 구조>
JSX를 자바스크립트로 변환할때 사용하는 Bable같은 개발 의존 모듈을 위해
package.json 파일의 내용에 빌드를 위한 npm 스크립트와 Bable 설정, 의존 모듈, 그외의 메타데이터를 설정한다.
{
"name": "tooltip"
...
"script": {
"build": "./node_modules/.bin/babel script.jsx -o script.js -w"
},
...
"babel": {
"presets": ["react"]
},
devDependencies: {
"babel-cli": "6.9.0",
"bable-preset-react": "6.5.0"
}
}
package.json을 생성한후에는 반드시 npm i 또는 npm install을 실행해야 한다.
예를들어,
Timer 앱을 개발한다고 가정했을때, 앱을 세조각으로 분리해야 개발할것이다.
1. TimerWrapper 컴포넌트(로직): 가장 많은 동작을 수행, 다른 컴포넌트를 렌더링하는 상위 컴포넌트
2. Timer 컴포넌트(프레젠테이션): 남은 시간이 몇초인지 보여주는 컴포넌트
3. Button 컴포넌트(프레젠테이션): 버튼 세개를 렌더링하고, 타이머를 실행 또는 초기화하는 컴포넌트
프레젠테이션 컴포넌트와 로직을 분리하면, 변화에 더 유연한 앱을 만들수 있고, 버튼 같은 요소를 다른 앱에서 재사용 할 수도 있을 것이다.
즉, 리액트를 다루는 최선의 방법은 표현과 비즈니스 로직을 분리하는 것이다.
세가지 컴포넌트와 사용자 간의 상호작용은 다음과 같다.
1. TimerWrapper 컴포넌트에서 상태를 프리젠테이션 컴포넌트(버튼, 타이머)의 속성으로 전달하여 렌더링 한다.
2. 사용자가 버튼을 조작하여 버튼에서 이벤트가 발생한다.
3. 버튼의 이벤트에서 TimerWrapper 컴포넌트의 메서드에 선택한 시간을 전달하여 호출한다.
4. TimerWrapper 컴포넌트가 시간 간격을 설정하여 매초마다 Timer컴포넌트를 갱신한다.
5. 0초가 남을때까지 갱신이 이어진다.
class TimerWrapper extends React.Component{
constructor(props){ ... }
setTimer(timeLeft){ ... }
render(){
return (
<div>
<Button time="5" startTimer={this.setTimer} />
...
</div>
)
}
}
버튼 컴포넌트에는 반드시 onClick 이벤트를 두어 사용자가 버튼을 클릭하는 것을 감지해야 한다.
타이머를 실행하는 메서드는 버튼 컴포넌트에서 구현하지 않고 부모 컴포넌트인 Wrapper 컴포넌트에서 구현하여
버튼 컴포넌트로 전달되어 this.props.setTimer로 접근한다.
<button onClick ={()=>{this.props.startTimer(this.props.time)}} >
컴포넌트를 단순하고 최대한 표현에 가깝게 유지해야 한다.
속성 값으로 데이터는 물론 함수도 전달 할 수 있다.
부모 컴포넌트를 사용하여 두 컴포넌트 간에 데이터를 교환할 수 있다.
<리액트 아키텍쳐>
최신 웹 개발 기법의 필수적인 빌드도구(또는 번들러)를 활용해서 애플리케이션을 실행할 수 있는 최소단위의 파일로 묶고, 편리하게 배포하도록 준비할 것이다.
우리가 살펴볼 빌드 도구는 Webpack 이다.
* 빌드도구로는 Grunt, Gulp, Bower 등이 있다.
Webpack에는 실시간 서버에서 작동중인 모듈의 변경 사항을 즉시 반영시킬 수 있는 핫모듈 대체(HMR)라는 유용한 기능이 있다.
Webpack
- 우리가 작성한 자바스크립트 파일을 최적화하여 적은 수의 파일로 사용자 요청을 처리하는 것이다. 서버 부담을 줄이고, 페이지를 불러오는 시간도 줄일 수 있다.
- 자바스크립트는 종종 재사용이 편리한 모듈로 작성되는데, 다른 모듈에 의존하거나, 의존한 모듈이 또다시 다른 모듈을 의존하고 있기도 하다. 이렇게 불러올 모듈을 파악하기 위해 의존 관계를 추적하는 작업을 도와주는 도구가 Webpack 이다.
- Webpack은 모든 의존 모듈을 올바른 순서로, 한번씩만 불러오도록 하고 자바스크립트 파일이 가능한 적은 파일로 묶여지도록 한다.
- 코드 분리와 정적 자원에 대한 해시 적용 기능도 지원하므로, 특정 상황에서만 필요한 코드 블록을 정의하여 필요한 시점에 불러올 수 있다.
- 한마디로, 자바스크립트의 배포를 좀 더 최적화 할 수 있는 것이다.
- Webpack은 자바스크립트만을 위한 도구는 아니고, 일종의 전처리 기능, 트랜스파일링 기능, 이미지 최적화 기능도 지원하는 다양한 용도의 도구라고 할 수 있다.
- webpack.config.js 파일을 이용하면 Webpack의 로드, 전처리, 번들링 과정을 설정할 수 있다.
- Webpack을 사용하기 위해서는 일반적으로 다음 과정을 따른다.
1. Webpack을 설치
=> 몇가지 의존 모듈을 추가: webpack(번들러도구), 로더(스타일, HMR, Bable, 전처리기), Webpack 개발서버(webpack-dev-server)
2. 의존모듈 설치후, package.json에 저장
=>
3. webpack.config.js를 통해 Webpack 설정
4. 개발 서버(webpack-dev-server)와 핫 모듈 대체 설정하기