원작자인 sudheerj 의 동의를 구하고 진행하는 번역본 입니다 :)
영어 원본: https://github.com/sudheerj/reactjs-interview-questions#what-is-reactjs
처음 해보는 번역이라 어색한 문맥이 많습니다 :) ..
하루에 5 ~ 10 개 정도의 질문이 추가됩니다.
도움이 되셨다면 star 눌러주세요 :)
React는 SPA (Single Page Application) 즉, 단일 페이지 응용 프로그램에서 사용자 인터페이스를 구성는데 사용되는 오픈 소스 프론트엔드 JS 라이브러리 입니다.
웹 및 모바일 앱의 Layer 를 다루는데 사용됩니다. 리액트는 페이스북의 소프트웨어 엔지니어 Jordan Walke 에 의해 만들어졌습니다.
리액트는 2011 년 페이스북 뉴스피드에 발표되었고, 2012년 인스타그램에 처음 구축되었습니다.
React 의 주요 특징은 아래와 같습니다.
- RealDOM 을 조작하는데 많은 비용이 들어간다는 점을 고려하여 리액트는 RealDOM 대신 VirtualDOM을 사용합니다.
- 서버 사이드 렌더링을 지원합니다.
- 단방향 데이터 흐름 또는 데이터 바인딩을 따릅니다.
- UI 구성 요소를 재사용할 수 있도록 개발할 수 있습니다.
JSX 는 JS XML (ECMAScript로 XML 유사 구문 확장) 의 구문 표기법입니다. JSX 는 JS XML의 약자입니다.
HTML과 같은 문법과 함께 JS를 표현할 수 있습니다.
예를 들면 아래의 h1 태그안에 text 는 render 함수에 의해 JS 함수로 반환됩니다.
render() {
return (
<div>
<h1> Welcome to React world!!</h1>
</div>
);
}
element는 DOM 노드 또는 다른 component들과 관련하여 화면에 표시 할 내용을 표현하는 일반 객체입니다. elements는 다른 elements 들을 포함할 수 있습니다. React element를 만드는 비용은 저렴합니다. element 는 생성되면 변형되지 않습니다. 리액트 element 의 표현은 아래와 같습니다.
const element = React.createElement(
'div',
{id: 'login-btn'},
'Login'
)
위에서 만들어진 element 는 아래와 같은 객체로 리턴됩니다.
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
마지막으로 아래와 같이 ReactDOM.render 를 이용하여 DOM 으로 렌더링합니다.
<div id='login-btn'>Login</div>
반면 component 는 여러 방법으로 선언될 수 있습니다. render 메서드가 있는 class 일 수도 있습니다. 간단한 component 일 경우 function으로 정의 될 수 있습니다. 입력된 component 를 바탕으로 element 트리를 만듭니다. 마지막에 JSX 는 createElement로 변환됩니다.
function Button ({ onLogin }) {
return React.createElement(
'div',
{id: 'login-btn', onClick: onLogin},
'Login'
)
}
ReactJS 는 Components 생성하는 두 가지 방법이 있습니다.
- Functional components ReactJS 에서 component를 생성하는 가장 간단한 방법입니다. props 를 받을 수 있고 React elements 를 리턴할 수 있습니다. 이런 방법을 pure 한 JS의 function 이기 때문에 functional한 생성법이라고 부릅니다.
function Greeting(props) {
return <h1> Hello, {props.message}</h1>
}
- Class components ES6 Class를 사용하여 component 를 정의할 수 있습니다. 위에서 본 functional 선언법을 아래와 같이 적용할 수 있습니다.
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.message}</h1>;
}
}
Component 에서 state 또는 life cycle methods 를 필요로 한다면 Class component를 사용하고 그렇지 않다면 Functional component를 사용할 수 있습니다.
PureComponent는 동일한 상태에서는 동일한 결과를 반환합니다. shouldComponentUpdate 메서드를 다룰 수 있다는 점을 제외하고는 component와 동일합니다.
props 또는 state 가 변경될 때 PureComponent 는 state 와 props 에 대해 Shallow Compare을 수행합니다.
반면 component는 현재 props와 변형될 state를 비교하지 않습니다. 그렇기 때문에 component는 shouldComponentUpdate가 호출 될 때 마다 다시 render 됩니다. (shouldComponentUpdate의 기본값은 true 이기 때문에)
Component State 는 component의 life cycle 동안 변경될 수 있는 정보를 담고 있는 객체입니다. 우리는 state를 가능한 단순하게 만들고 state 의 구성 요소의 수를 최소화하기 위해 노력해야 합니다.
message state를 가진 user component 를 생성해보겠습니다.
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "Welcome to React world",
}
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
</div>
);
}
}
Props 는 HTML 태그 속성과 유사한 규칙을 사용하여 React component 에 전달되는 값을 포함하는 단일 값 또는 객체입니다. 부모 component 에서 자식 component 로 전달되는 데이터 입니다.
React에서 props 의 목적은 아래와 같은 기능을 component에 제공하는 것 입니다.
- 사용자 정의 데이터를 React component 로 전달할 수 있습니다.
- State 변경을 Trigger 할 수 있습니다.
- Component 의 render 메서드 안에서 this.props.reactProp 로 접근하여 사용할 수 있습니다.
예를 들어, reactProp를 받는 element 를 만들고
<Element reactProp = "1" />
이 "reactProp" (또는 사용자가 찾은 정의한) props는 React를 사용하여 생성된 ccomponent에서 접근이 가능하고, React의 native props 영역에 포함된 속성이 됩니다.
props.reactProp;
props와 state는 모두 JavaScript 객체입니다. 두가지 다 렌더링 결과에 영향을 주는 정보를 가지고 있지만, component 와 관련된 기능면에서 차이가 있습니다.
props는 함수 매개변수와 같이 component 요소로 전달됩니다. state 는 component 안에서 관리되고 사용할 변수 선언과 비슷합니다.
state를 직접 업데이트 하려고 한다면 component 는 re-render 되지 않습니다.
//Wrong
this.state.message =”Hello world”;
그 대신 setState 메서드를 사용해야합니다. setState는 component state 업데이트에 대한 예약을합니다. state 가 변경되면 component는 re-rendering 할 것 입니다.
//Correct
this.setState({message: ‘Hello World’});
Note: 상태를 할당할 수 있는 곳은 constructor 가 유일합니다. (외부에서 다이렉트로 state를 할당하지 말라는 뜻 같습니다.)
callback 함수는 setState가 끝난 후 그리고 component 가 re-rendering 된 후 호출됩니다. setState는 비동기적으로 동작합니다. callback 함수는 모든 작업이 마무리된 후 사용됩니다.
setState({name: 'sudheer'}, () => console.log('The name has updated and component re-rendered'));
아래는 HTML과 React 의 이벤트처리의 몇 가지 차이점입니다.
- HTML 에서는 이벤트 이름이 소문자여야 합니다.
<button onclick="activateLasers()">
반면 React는 camelCase 를 사용합니다.
<button onClick={activateLasers}>
- HTML 에서는 기본 이벤트 동작을 막기위해 false 를 반환할 수 있습니다.
<a href="#" onclick="console.log('The link was clicked.'); return false"/>
반면 React는 preventDefault 메서드를 호출해야합니다.
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
3가지의 방법이 있습니다.
- 생성자에서의 바인딩: JS 의 Class 에서 메서드는 기본적으로 바인딩 되지 않습니다. 클래스 메서드로 지정된 React의 event handlers 에서도 마찬가지 입니다.
일반적으로 다음과같이 constructor 바인딩합니다.
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// Perform some logic
}
- 공통 클래스 필드 구문: 생성자에서의 바인딩 방법을 원하지 않는다면, 공용 클래스 필드 구문을 이용하여 callback 을 올바르게 바인딩할 수 있습니다.
handleClick = () => {
console.log('this is:', this);
}
<button onClick={this.handleClick}> Click me </button>
- arrow function을 이용한 바인딩 아래와 같이 바로 arrow function 을 이용하여 바인딩 해줄 수 있습니다.
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
Note: 만약 callback 이 하위 component 에 props 로 전달되면 component는 re-rendering 될 수 있습니다. 이럴 경우 성능을 고려하였을때 1번 또는 2번의 방식을 사용하는 것이 좋습니다.
arrow function 으로 감싸서 event handler 에게 매개변수를 전달할 수 있습니다.
<button onClick={() => this.handleClick(id)} />
위의 방법은 아래와 같이 method 를 사용하는 것과 같습니다
<button onClick={this.handleClick.bind(this, id)} />
synthetic event 는 브라우저의 기본 이벤트를 감싼 cross-browser wrapper 입니다. API는 모든 브라우저에서 동작한다는 점을 제외하면, stopPropagation () 및 preventDefault () 를 포함해 브라우저 네이티브 이벤트와 같은 인터페이스를 가지고 있습니다.
조건부 표현식을 표현하기 위해 if 문 또는 삼항 연산자를 사용할 수 있습니다. 이런 표현법 외에도 중괄호로 묶은 다음 JS의 논리 연산자 (&&) 를 붙여 JSX 표현식에 포함시킬 수 있습니다.
<h1>Hello!</h1>
{messages.length > 0 &&
<h2>
You have {messages.length} unread messages.
</h2>
"Key"는 목록을 만들때 포함시켜야하는 특수한 속성입니다. "Key"는 목록의 변경사항, 추가 또는 제거된 항목을 분별할 수 있도록 도와줍니다.
예를 들어, 데이터의 키를 자주 목록의 "Key"로 사용합니다.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
What is ReactJS? (React란 무엇인가요?) 렌더링 된 목록에 안정적인 ID가 없을 경우 마지막 수단으로 index 값을 이용할 수 있습니다.
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);
Note:
- 항목 순서가 변경 될 가능성이 있는 경우 index를 이용하는 것은 좋지 않습니다. 성능에 부정적인 영향을 미치고 component state에 문제가 발생할 수 있습니다.
- list를 별도의 component로 뽑아 사용하는 경우 li 태그 대신 list component 요소에 key를 적용하세요
list 에 key 가 없으면 콘솔에 경고가 표시됩니다.
두 가지의 접근법이 있습니다.
최근에 추가된 접근 방식입니다. create ref는 element 요소에 대한 참조를 반환하는데 사용할 수 있습니다. DOM의 요소나 compoennt 에 직접 접근해야 될 경우 유용할 수 있습니다.
Ref는 React.createRef() 메서드를 사용하여 생성합니다. ref attribute 을 통해 React elements 에 첨부됩니다. component 전체에서 사용하고 싶다면 constructor에서 instance property 로 ref를 할당하면 됩니다.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
- React의 버전과 관계없이 ref callback 을 사용할 수 있습니다. 예를들어 serach bar component의 input element는 다음과 같이 접근합니다.
class SearchBar extends Component {
constructor(props) {
super(props);
this.txtSearch = null;
this.state = { term: '' };
this.setInputSearchRef = e => {
this.txtSearch = e;
}
}
onInputChange(event) {
this.setState({ term: this.txtSearch.value });
}
render() {
return (
<input
value={this.state.term}
onChange={this.onInputChange.bind(this)}
ref={this.setInputSearchRef} />
);
}
}
Ref Forwarding 은 ref를 받아 child component 에게 전달하는 기능을합니다.
const ButtonElement = React.forwardRef((props, ref) => (
<button ref={ref} className="CustomButton">
{props.children}
</button>
));
// Create ref to the DOM button:
const ref = React.createRef();
<ButtonElement ref={ref}>{'Forward Ref'}</ButtonElement>
findDOMNode API 보다 callback refs를 사용하는 것이 좋습니다. findDOMNode는 향후 개선사항에서 React의 특성을 막기 떄문입니다.
findDOMNode 를 사용하는 legacy 한 방식입니다.
class MyComponent extends Component {
componentDidMount() {
findDOMNode(this).scrollIntoView()
}
render() {
return <div />
}
}
권장하는 방법입니다.
class MyComponent extends Component {
componentDidMount() {
this.node.scrollIntoView()
}
render() {
return <div ref={node => this.node = node} />
}
}
(왜 String Refs 는 legacy가 되었나요?)
만약 예전 React에서 ref 를 다뤄봤다면, ref={'textInput'} 과 같은 ref 의 속성이 string 이고 DOM Node가 refs.textInput 으로 접근하는 API 방식에 익숙할 것 입니다.
String refs 는 아래와 같은 문제들이 있기 떄문에 legacy 를 고려하였습니다. React v16 에서 String refs는 제거되었습니다.
- String refs 는 실행중인 component 요소를 추적하도록 강제화합니다. 또 react module 을 stateful 하게 만듭니다. bundle시 react module 들이 중복될 때 이상한 오류를 발생시킵니다.
- 라이브러리를 추가하여 String Refs를 Child component 에 전달하면, 사용자는 다른 ref를 추가할 수 없게됩니다. Callback refs를 이용한다면 이런 문제를 해결할 수 있습니다.
- Flow 와 같은 static 분석에서는 동작하지 않습니다. Flow 는 string refs를 this.refs 같은 형태로 표시하도록 만드는 마법을 추측할 수 없습니다. callback ref는 String refs 보다 Flow 와 친밀합니다.
- 대부분의 사람들이 "render callback" 패턴으로 동작하기를 기대하지만 그렇게 동작하지 않습니다.
class MyComponent extends Component {
renderRow = (index) => {
// 이것은 동작하지 않습니다. Ref는 MyComponent가 아닌 DataTable 에 연결될 것입니다.
return <input ref={'input-' + index} />;
// 이것은 동작합니다. Callback refs는 굉장합니다.
return <input ref={input => this['input-' + index] = input} />;
}
render() {
return <DataTable data={this.props.data} renderRow={this.renderRow} />
}
}
가상돔이란 in-memory 즉 Real DOM의 메모리상에서의 표현입니다. UI의 표현은 메모리에 유지되고 Render함수와 화면을 표시하는 사이에 Real DOM 과 동기화됩니다. 이런 단계를 reconciliation 라고 부릅니다.
가상돔은 세 가지의 간단한 단계로 동작합니다.
- 데이터가 변경될 때 전체 UI 는 가상돔안에서 re-rendered 됩니다.
- 그런 다음 변경되기전 DOM 과 새로운 변경된 DOM 의 변경점을 계산합니다.
- 계산이 완료되면 실제 DOM 에서 계산된 변경점들만 업데이트됩니다.
Shadow DOM은 web component의 scope 및 CSS scope 지정을 위해 설계된 web browser 기술입니다. Virtual DOM 은 browser API를 기반으로 JS 라이브러리에서 구현되는 개념입니다.
React Fiber란 React v16 에서 핵심 알고리즘을 재구현 한것입니다. React Fiber 의 목표는 애니메이션, 레이아웃, 제스처, 작업을 일시정지하고, 중단 또는 재사용, 여러 유형의 업데이트 우선순위 조절, 동시성등 여러 기본사항에 대한 성능을 높이는 것 입니다.
React Fiber 의 목표는 애니메이션, 레이아웃, 제스처등의 성능을 높이는 것 입니다. 주요 목적은 incremental rendering 입니다.
incremental rendering: 렌더링 작업을 청크로 쪼개고 여러 프레임으로 분산 시키는 기능입니다.
입력 요소를 제어하는 component를 controlled components 라고 부릅니다. 모든 상태 변경에는 연관된 handler funciton 이 있습니다.
예를 들어, 이름을 대문자로 쓰려면 아래 handleChange을 이용할 수 있습니다.
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()})
}
uncontrolled components란 내부적으로 자기 자신의 state를 가지고 있는 component입니다. 현재 필요한 값을 찾기 위해 ref를 사용하여 DOM query를 할 수 있습니다. 이것은 전통적인 HTML 과 비슷합니다.
아래의 UserProfile Component 에서의 이름 입력은 ref를 사용하여 접근합니다.
class UserProfile extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.input = React.createRef()
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
{'Name:'}
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
대부분의 경우 forms 을 제어할 때는 controlled component 를 사용하는 것을 추천합니다.
UI 의 object representation으로 사용될 react element를 만들기 위해 JSX element 는 React.createElement() 함수로 변환됩니다. cloneElement는 element 를 복사하고 새로운 props 를 전달하는데 사용됩니다.
여러 component 들이 동일한 변경 데이터를 공유해야하는 경우 가까운 부모 component 로 state를 올리는 것이 좋습니다. 즉, 두개의 자식 component가 부모에 있는 동일한 데이터를 공유할 때 두개의 자식 component 들은 local state를 유지하는 대신, 부모로 state를 올려야 합니다.
라이프 사이클에는 4가지 단계가 있습니다.
- Initialization: component 는 초기 state 및 props 를 세팅을 준비합니다.
- Mounting: component 가 브라우저 DOM에 mount 될 준비가 되었습니다. 이 단계에서는 componentWillMount, componentDidMount 를 사용할 수 있습니다.
- Updating: 새로운 props를 보내거나 state를 업데이트하여 component 를 두가지 방법으로 update 할 수 있습니다. 이 단계에서는 shouldComponentUpdate (), componentWillUpdate () 및 componentDidUpdate 를 사용할 수 있습니다.
- Unmounting: 브라우저 DOM에서 component 가 필요하지 않을때 mount를 해제시킵니다. 이 단계에서는 componentWillUnmount 를 사용할 수 있습니다.
- componentWillMount: rendering 전에 실행됩니다.
- componentDidMount: 첫 rendering 후에 실행됩니다. 이 단계에서 모든 AJAX 요청, DOM 또는 State의 update, event listener 가 설정되어야합니다.
- componentWillReceiveProps: 특정 props update가 state 변화를 트리거할 때 실행됩니다.
- shouldComponentUpdate: 컴포넌트의 업데이트 여부를 결정합니다. 기본적으로 return 값은 true 입니다. state 또는 props 가 업데이트 된 후 component 를 render 할 필요가 없을 경우 false를 return 할 수 있습니다. component가 새로운 props를 받으면서 생기는 rerender 을 막을 수 있습니다. 성능을 향상시키는데 좋습니다.
- componentWillUpdate: shouldComponentUpdate에서 true가 반환되었을 때 state & props 의 변화를 확인하고 re-rendering 되기전에 실행합니다.
- componentDidUpdate: 주로 props 나 state 변경에 대한 response 로 DOM을 업데이트할 때 사용됩니다.
- componentWillUnmount: 네트워크 요청을 취소하거나, component와 관련된 모든 event listeners 를 제거하는데 사용됩니다.
Higher-Order component(HOC)는 component를 받아서 새로운 component를 return 하는 함수입니다. HOC는 React의 특성에서 파생된 패턴입니다. 동적으로 제공되는 하위 component들을 그대로 사용하지만 입력받은 component를 수정하거나 복사하지 않기 때문에 "순수 components" 라고 부릅니다.
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC는 아래와 같은 많은 use cases 에 사용할 수 있습니다.
- Code reuse, logic and bootstrap abstraction
- Render High jacking
- State abstraction and manipulation
- Props manipulation
다음과 같이 component에 전달된 props를 props proxy로 추가 / 편집 할 수 있습니다.
function HOC(WrappedComponent) {
return class Test extends Component {
render() {
const newProps = {
title: 'New Header',
footer: false,
showFeatureX: false,
showFeatureY: true
};
return <WrappedComponent {...this.props} {...newProps} />
}
}
}
Context 는 모든 레벨에 수동으로 props를 전달하지 않고 component tree를 통해 데이터를 전달할 수 있도록 제공해줍니다. 예를 들어, 인증된 유저, locale 설정, UI 테마는 많은 application 들에서 접근해야합니다.
const {Provider, Consumer} = React.createContext(defaultValue);
Children prop는 component를 data로 다른 component로 전달할 수 있도록 해주는 prop(this.prop.children)입니다.
당신이 사용하는 prop 처럼 사용할 수 있습니다. React API에는 이 props와 함께 사용할 수 있는 React.Children.map, React.Children.forEach, React.Children.count, React.Children.only, React.Children.toArray등 여러가지 방법이 있습니다.
children prop의 간단한 사용법은 아래와 같습니다.
var MyDiv = React.createClass({
render: function() {
return <div>{this.props.children}</div>;
}
});
ReactDOM.render(
<MyDiv>
<span>Hello</span>
<span>World</span>
</MyDiv>,
node
);
ReactJS / JSX 에서의 주석은 JavaScript와 유사합니다. 한 줄 및 여러 줄 주석들은 모두 중괄호로 감쌉니다.
<div>
{/* Single-line comments */} // In vanilla JavaScript, the single-line comments are represented by double slash(//)
Welcome {user}, Let's play React
</div>
<div>
{/* Multi-line comments for more than
one line */}
Welcome {user}, Let's play React
</div>
자식 클래스의 constructor 는 super() 메서드가 호출되기 전까지 this 참조 사용할 수 없습니다. es6 의 sub-classes 에서도 동일하게 적용됩니다. super()에 props를 파라미터로 전달하는 이유는 child constructors에서 this.props로 접근하기 위해서입니다.
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // Prints { name: 'sudheer',age: 30 }
}
}
class MyComponent extends React.Component {
constructor(props) {
super();
console.log(this.props); // Prints undefined
// But Props parameter is still available
console.log(props); // Prints { name: 'sudheer',age: 30 }
}
render() {
// No difference outside constructor
console.log(this.props) // Prints { name: 'sudheer',age: 30 }
}
}
위의 code snippets은 this.props의 동작이 constructor 만 다른 것을 보여줍니다. 생성자 밖에서의 동작은 동일합니다.
(reconciliation란 무엇인가요?)
Component 의 props 또는 state가 변경될 때 React는 새로 반환된 component를 이전에 rendered 된 component와 비교하여 DOM update가 필요한지 결정합니다. 두개의 component가 같지 않을때 React는 DOM을 update합니다. 이를 프로세스 reconciliation 라고합니다.
만약 ES6 나 babel transpiler 사용하여 JSX 코드를 변환하는 경우 computed property names 으로 작업을 할 수 있습니다.
handleInputChange(event) {
this.setState({ [event.target.id]: event.target.value })
}
function을 parameter로 전달하는 동안 function 이 호출되지 않았는지 확인해야합니다.
render() {
// Wrong: handleClick is called instead of passed as a reference!
return <button onClick={this.handleClick()}>{'Click Me'}</button>
}
괄호 없이 function 을 전달하세요
render() {
// Correct: handleClick is passed as a reference!
return <button onClick={this.handleClick}>{'Click Me'}</button>
}
Component 들은 DOM element가 아니기 때문에 대문자 표기가 필요합니다. component들은 constructors입니다. 또한 JSX 에서의 소문자 태그 이름은 component가 아닌 HTML 요소를 나타내기 때문입니다.
class는 Javascript 의 키워드입니다. JSX는 Javascript 의 확장입니다. 그것이 React가 class 대신 className을 사용하는 이유입니다. classNames prop 로 문자열을 전달하세요
render() {
return <span className={'menu navigation-menu'}>{'Menu'}</span>
}
fragments는 여러 Component 들을 반환하는데 사용되는 React의 일반적인 패턴입니다. fragments를 사용하면 DOM에 node 들을 추가하지 않고 하위 목록을 그룹화 할 수 있습니다.
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
)
}
더 짧은 구문도 있지만, 많은 tool 들에서 지원되지 않습니다.
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}
-
Fragments 는 빠르고 DOM node를 만들지 않기 때문에 메모리를 적게 사용합니다. 이것은 매우 크고 깊은 트리에서 이익을 가집니다.
-
Flexbox 나 CSS Grid과 같은 CSS 메커니즘에는 특별한 부모 - 자식 관계를 가지고 있습니다. 중간에 div 들을 추가하게 된다면 layout을 유지하기 어려워집니다.
-
DOM Inspector은 덜 복잡합니다.
portals 은 상위 Component 의 DOM 계층 구조 외부에 존재하는 DOM 노드로 자식을 render 하는데 권장되는 방법입니다.
ReactDOM.createPortal(child, container)
첫 번째 인자는 렌더링 가능한 React 하위요소 (element, string, fragment) 입니다. 두 번째 인자는 DOM element 입니다.
Component의 동작과 상태가 독립적이라면 stateless component 를 구성할 수 있습니다. stateless component 는 class나 function 을 이용하여 만들 수 있습니다. lifecycle hook을 사용해야하는 경우가 아니라면 function component를 사용할 수 있습니다. function component를 사용한다면 많은 이점이 있습니다. 쓰기, 이해 그리고 테스트가 빠르고 쉽습니다.
Component 의 동작이 component의 state에 의존한다면 stateful component라고 부를 수 있습니다. stateful component 는 항상 class components 이고, constructor에서 초기화합니다.
class App extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
render() {
// ...
}
}
Application 이 개발 모드에서 실행될 때 React는 자동으로 component에 설정된 모든 props 들의 유효성을 체크합니다. 타입이 올바르지 않다면 React는 console에 경고 메세지를 생성할 것 입니다. 성능에 영향이 가기 때문에 production 모드에서는 사용하지 않습니다. 필수 props는 isRequired로 정의됩니다.
정의 된 props 타입
- PropTypes.number
- PropTypes.string
- PropTypes.array
- PropTypes.object
- PropTypes.func
- PropTypes.node
- PropTypes.element
- PropTypes.bool
- PropTypes.symbol
- PropTypes.any
User component 에 대한 PropTypes 를 아래와 같이 정의할 수 있습니다.
import React from 'react'
import PropTypes from 'prop-types'
class User extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
}
render() {
return (
<>
<h1>{`Welcome, ${this.props.name}`}</h1>
<h2>{`Age, ${this.props.age}`}</h2>
</>
)
}
}
Note: React v15.5 에서 PropType은 React.PropTypes 에서 prop-types 라이브러리로 옮겨졌습니다.
-
가상 DOM을 이용하여 application 의 성능을 향상시킵니다.
-
JSX는 코드를 읽고 쓰는 것을 쉽게 만들어줍니다.
-
클라이언트와 서버측 (SSR) 모두 render 됩니다.
-
오직 view library 이기 때문에 다른 프레임워크들 (Angular, Backbone)과 손쉽게 통합 할 수 있습니다.
-
Jest 와 같은 도구를 사용하여 단위 및 통합테스트를 쉽게 작성할 수 있습니다.
-
React 전체 framework 가 아닌 view를 위한 library 일 뿐입니다.
-
웹 개발을 처음 접하는 초심자들에게는 학습 곡선이 있습니다.
-
기존의 MVC framework 에 React 를 합치기 위해서는 몇 가지 추가 구성이 필요합니다.
-
인라인 템플릿 및 JSX를 사용하면 코드가 복잡해집니다.
-
과도한 엔지니어링 또는 boilerplate 로 이어지는 작은 components 들이 너무 많습니다.
Error boundaries 는 하위 component tree 에서 Javascript error 를 catch 하고, 에러를 기록하고, 오류가 발생한 component tree가 아닌 대체 UI를 표현해 주는 component 입니다.
class component 는 componentDidCatch 라는 새로운 lifecycle 메서드를 정의하면 Error boundaries 됩니다
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true })
// You can also log the error to an error reporting service
logErrorToMyService(error, info)
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>{'Something went wrong.'}</h1>
}
return this.props.children
}
}
그 다음 일반 component 로 사용합니다.
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
React v15 에서는 unstable_handleError 메서드를 사용하여 error boundaries 에 대한 기본적인 지원을 했습니다. React v16에서 componentDidCatch로 이름이 변경되었습니다.
일반적으로 우리는 React application 에서 타입 검사를 하기 위해 PropTypes library (React.PropTypes를 React v15.5 부터는 prop-types package 로 옮겼습니다)를 사용합니다. 대규모의 코드 베이스에서는 compile time 에서 타입 체크를 하고 자동 완성 기능을 제공해주는 Flow 나 TypeScript 와 같은 static type checkers 를 이용하는 것이 좋습니다.
React-dom 패키지는 App의 최상위 레벨에서 사용할 수 있는 DOM-specific 메서드를 제공합니다. 대부분의 Component 들은 이 모듈을 사용할 필요가 없습니다. 패키지의 일부 메서드는 다음과 같습니다.
- render()
- hydrate()
- unmountComponentAtNode()
- findDOMNode()
- createPortal()
render 메서드는 제공된 컨테이너의 DOM에 React element를 render 하고 Component에 대한 참조를 반환하는데 사용됩니다. React element가 이전에 렌더링 되었다면 update 를 수행하고 최근의 변경사항을 반영하기 위해 필요에 따라 DOM을 변경합니다.
ReactDOM.render(element, container[, callback])
만약 optional callback 이 제공된다면, callback은 component 가 렌더링되거나 업데이트 된 후에 실행됩니다.
ReactDOMServer 객체를 사용하면 component를 static markup(일반적으로 노드 서버에서 사용됩니다.) 으로 렌더링 할 수 있습니다. 이 객체는 주로 서버사이드 렌더링(SSR) 에서 사용됩니다. 다음의 메서드는 서버 및 브라우저 환경 모두 사용할 수 있습니다.
- renderToString()
- renderToStaticMarkup()
예를들어 Express, Hapi, Koa 같은 노드 기반의 웹서버를 실행할때 renderToString을 호출하여 root component 를 문자열로 렌더링 한 다음 response를 보낼 수 있습니다
// using Express
import { renderToString } from 'react-dom/server'
import MyPage from './MyPage'
app.get('/', (req, res) => {
res.write('<!DOCTYPE html><html><head><title>My Page</title></head><body>')
res.write('<div id="content">')
res.write(renderToString(<MyPage/>))
res.write('</div></body></html>')
res.end()
})
dangerouslySetInnerHTML 속성은 브라우저 DOM 에서 innerHTML 을 사용하기 위한 React 의 대체 속성입니다. innerHTML과 마찬가지로 XXS (Cross-Site Scripting) 공격을 고려했을때 브라우저 DOM 에서 이 속성을 사용하는 것은 위험합니다. __html 객체를 키로 HTML 텍스트를 값으로 전달하면됩니다.
예제에서 MyComponent는 HTML 마크업을 설정하기 위해 dangerouslySetInnerHTML 속성을 사용하고 있습니다.
function createMarkup() {
return { __html: 'First · Second' }
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />
}
style 속성은 CSS 문자열 대신 camelCased 속성의 Javascript 객체를 허용합니다. 이것은 DOM Style Javascript 속성과 일치하며 효율적이고, XSS 보안의 허점을 방지할 수 있습니다.
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')'
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>
}
Style의 key는 Javascript DOM Node (예 : node.style.backgroundImage)의 속성에 접근하는 것과 일관되게 하기 위해 camelCased 를 사용합니다.
React element 에서의 이벤트 처리는 몇가지 문법상 차이점이 있습니다.
- React 이벤트 핸들러는 소문자보다는 camelCase 를 사용합니다.
- JSX 에서는 문자열이 아닌 이벤트 핸들러를 통해 function 을 전달합니다.
setState() 를 사용할 때 객체 상태가 할당되고 또한 component 와 모든 자식들을 재렌더링 합니다.
이런 에러를 봤을겁니다. Can only update a mounted or mounting component
. 그래서 우리는 this.state 를 사용하여 constructor 내부의 변수를 초기화 해야합니다.
React element 가 추적할 수 있도록 key 는 안정적, 예측가능, 고유 하여야합니다. 아래 코드 조각에서의 각 element 의 key 는 데이터를 따르지 않고 순서에 따라 결정됩니다. 이것은 React 가 할 수 있는 최적화를 제한합니다.
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
만약 element 에 data 의 고유한 키값을 사용한다면, todo.id 가 목록에서 고유하고, 안정적이라고 가정한다면 React 는 elements 를 재평가 할 필요없이 element 를 재정렬 할 수 있습니다.
{todos.map((todo) =>
<Todo {...todo}
key={todo.id} />
)}
componentWillMount
안에서는 비동기적인 초기화는 피하는 것이 좋습니다.
componentWillMount
는 마운트가 실행되기 직전에 호출됩니다.
render () 전에 호출되기 때문에 상태를 설정하더라도 re-render 되지 않습니다.
componentWillMount
에서는 side-effects or subscriptions 을 피해야합니다.
우리는 componentWillMount
대신 componentDidMount
에서 component 초기화를 위한 비동기 호출을 실행해야 합니다.
componentDidMount() {
axios.get(`api/todos`)
.then((result) => {
this.setState({
messages: [...result.data]
})
})
}
만약 component 를 새로 고치지 않고 component 의 props 를 변경한다면 생성자 함수가 component 의 현재 상태를 업데이트 하지 않습니다. 새로운 props 의 값이 표시되지 않습니다. state 의 초기화는 component 를 처음 만들 때만 실행됩니다.
아래의 component 는 업데이트 된 input 의 값을 표시하지 않습니다.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
records: [],
inputValue: this.props.inputValue
};
}
render() {
return <div>{this.state.inputValue}</div>
}
}
render 메소드 안에서 props 를 사용하면 값이 업데이트 됩니다.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
record: []
}
}
render() {
return <div>{this.props.inputValue}</div>
}
}
경우에 따라서 state 에 의존하여 다르게 component 를 렌더링 하기를 원할것이다. JSX 는 false 또는 undefined 때는 렌더링하지 않으므로 조건을 사용하여 특정 조건이 true 인 경우에만 component의 특정 부분을 렌더링 할 수 있습니다.
const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address &&
<p>{address}</p>
}
</div>
)
만약 if-else 를 사용하기를 원한다면 삼항연산자를 쓰세요
const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address
? <p>{address}</p>
: <p>{'Address is not available'}</p>
}
</div>
)
props 를 spreading 할 때 알려지지않은 HTML 속성을 추가 할 위험이 있습니다. 그것은 좋지않습니다. 대신 ...rest 연산자를 사용하여 prop destructuring 을 할 수 있으므로 필요한 props 만 추가할 수 있습니다.
const ComponentA = () =>
<ComponentB isDisplay={true} className={'componentStyle'} />
const ComponentB = ({ isDisplay, ...domProps }) =>
<div {...domProps}>{'ComponentB'}</div>
Class component 를 하나의 function 으로 변환하는 방식으로 decorate 할 수 있습니다. Decorators 는 component 의 기능을 수정하기 위한 유연하고 읽기 쉬운 방법입니다.
@setTitle('Profile')
class Profile extends React.Component {
//....
}
const setTitle = (title) => (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
document.title = title
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
NOTE: Decorators 는 ES7 에 포함되지 않았습니다. 현재 stage 2 에 제안되어 있습니다.
function component 에 사용할 수 있는 memoize 라이브러리가 있습니다.
예를 들면 moize
라이브러리는 다른 component 에 component 를 memoize 할 수 있습니다.
import moize from 'moize'
import Component from './components/Component' // this module exports a non-memoized component
const MemoizedFoo = moize.react(Component)
const Consumer = () => {
<div>
{'I will memoize the following entry:'}
<MemoizedFoo/>
</div>
}
React 는 이미 Node 서버에서 렌더링을 처리할 수 있도록 준비되어 있습니다. 특수한 버전의 DOM renderer 를 사용할 수 있으며, client side 와 동일한 패턴을 따릅니다.
import ReactDOMServer from 'react-dom/server'
import App from './App'
ReactDOMServer.renderToString(<App />)
이 method 는 HTML 을 문자열로 내보내고, 문자열은 서버 응답의 일부로 페이지 본문 내부에 배치 할 수 있습니다. client side 에서 React 는 이미 렌더링된 content 를 감지하여 중단된 부분을 원활히 처리합니다.
Webpack 의 DefinePlugin 메서드를 사용하여 NODE_ENV 를 production 으로 설정해야 propType 의 유효성 검사 및 추가적인 경고 같은 것들을 제거할 수 있습니다.
production 모드와는 별도로 코드를 minify 하면 Uglify 의 dead-code 의 제거와 주석을 제거하여 bundle 크기를 줄일 수 있습니다.
Create-Reaction-App CLI 사용하면 설정 단계 없이 React applications 생성하고 실행할 수 있습니다.
CRA 를 사용하여 Todo App 을 만듭니다
# Installation
$ npm install -g create-react-app
# Create new project
$ create-react-app todo-app
$ cd todo-app
# Build, test and run
$ npm run build
$ npm run test
$ npm start
lifecycle 메서드는 component 의 instance 가 생성되어 DOM에 삽입 될 때 호출됩니다.
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
안전하지 않은 코딩 방법이 될 수 있는 lifecycle 메서드는 비동기적으로 렌더링시 문제가 될 것 입니다.
- componentWillMount()
- componentWillReceiveProps()
- componentWillUpdate()
React v16.3 부터는 UNSAFE_가 prefix 붙고, prefix 가 없는 버전은 React v17 에서 제거됩니다.
새로운 정적 getDerivedStateFromProps() lifecycle 메서드는 component 가 인스턴스화 된 후, 다시 렌더링 되기 전에 호출됩니다.
object 를 반환하여 state 를 update 하거나, null 을 반환하여 새로운 props 에서 state update 가 필요하지 않음을 나타낼 수 있습니다.
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}
이 lifecycle 메서드는 componentDidUpdate() 와 함께 componentWillReceiveProps() 의 사용 사례에 적용할 수 있습니다.
새로운 getSnapshotBeforeUpdate() lifecycle 메서드는 DOM update 직전에 호출됩니다. 이 메서드의 반환값은 componentDidUpdate() 의 세번째 파라미터로 전달됩니다.
class MyComponent extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}
이 lifecycle 메서드는 componentDidUpdate()와 함께 componentWillUpdate () 의 사용 사례에 적용할 수 있습니다.
JSX 에서 React element 는 UI element 를 타내는 React.createElement() 변환됩니다.
React.cloneElement ()는 element 를 복제하고 새로운 props를 전달하는데 사용됩니다.
displayName 대신 reference 를 이용하여 component 의 이름을 정하는 것을 추천한다.
displayName 사용
export default React.createClass({
displayName: 'TodoApp',
// ...
})
추천 방법:
export default class TodoApp extends React.Component {
// ...
}
마운트에서 부터 렌더링 단계까지의 권장 메서드 지정 순서
- static methods
- constructor()
- getChildContext()
- componentWillMount()
- componentDidMount()
- componentWillReceiveProps()
- shouldComponentUpdate()
- componentWillUpdate()
- componentDidUpdate()
- componentWillUnmount()
- click handlers or event handlers like onClickSubmit() or onChangeDescription()
- getter methods for render like getSelectReason() or getFooterContent()
- optional render methods like renderNavigation() or renderProfilePicture()
- render()
switching component는 여러 component 중 하나의를 렌더링 하는 component 입니다. 우리는 props 값을 매핑하기 위해 object 를 사용해야합니다.
예를 들어, switching component 는 페이지의 props 를 기반으로 다른 component 를 보여줍니다.
import HomePage from './HomePage'
import AboutPage from './AboutPage'
import ServicesPage from './ServicesPage'
import ContactPage from './ContactPage'
const PAGES = {
home: HomePage,
about: AboutPage,
services: ServicesPage,
contact: ContactPage
}
const Page = (props) => {
const Handler = PAGES[props.page] || ContactPage
return <Handler {...props} />
}
// The keys of the PAGES object can be used in the prop types to catch dev-time errors.
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired
}
이유는 setState 가 비동기적인 작업이기 때문입니다. 성능상의 이유로 state 의 변경은 일괄적으로 일어납니다, 그래서 setState() 의 호출 직후에 state가 변경되지 않을 수 있습니다.
state 를 확실할 수 없기 때문에 setState()를 호출 할 때 현재의 state에 의존하면 안됩니다.
해결책은 이전 state를 인자로 사용하는 setState()에 함수를 전달하는 것입니다.
그렇게 하면 setState()가 가진 비동기성으로 인해 사용자가 접근할때 이전 state의 값을 가져오는 문제를 피할 수 있습니다.
초기의 count 값이 0 이라고 가정하겠습니다. 세번의 증가연산 후의 값은 오직 1만 증가합니다.
// assuming this.state.count === 0
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
// this.state.count === 1, not 3
setState() 에 함수를 전달하면 올바르게 증가합니다.
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}))
// this.state.count === 3 as expected
React.StrictMode 는 application 의 잠재적인 문제를 강조 표시하는데 유용한 component 입니다. <Fragment>
처럼, <StrictMode>
도 추가적으로 DOM을 렌더링하지 않습니다.
자식에 대한 추가적인 확인과 경고를 활성화합니다. 검사는 개발 모드에만 적용됩니다.
import React from 'react'
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
)
}
위의 예제에서 strict mode 는 와 에만 적용됩니다.
는 다음과 같은 도움을 줍니다.
- 안전하지 않은 lifecycle 메서드를 식별합니다.
- 레거시 string ref API 에 대한 경고
- 예상치 못한 사이드 이펙트 감지
- legacy context API 감지
Mixins은 공통적인 기능을 가질 수 있도록 component를 분리하는 방법입니다. Mixins은 사용하지 말아야합니다. higher-order components 또는 decorators 로 대체할 수 있습니다.
props와 state가 이전의 props 와 state 가 얕게 같을때 불필요한 재 렌더링을 방지하기 위해 component 에서 사용할 수 있습니다.
const PureRenderMixin = require('react-addons-pure-render-mixin')
const Button = React.createClass({
mixins: [PureRenderMixin],
// ...
})
isMounted() 의 사용 사례는 component의 마운트가 해제 된 후에 setState()를 호출하지 않도록 하는 것입니다. component 마운트가 해제된 후 경고가 발생되기 때문입니다.
if (this.isMounted()) {
this.setState({...})
}
setState() 를 호출하기 전에 isMounted() 를 검사한다면 경고를 제거할 수 있지만, 경고의 목적을 잃어버릴 수 있습니다. component의 마운트가 해제된 후에 reference 를 가지고 있다고 생각하기 때문에 isMounted()를 사용하는 것은 code smell 입니다.
최적의 해결법은 component의 마운트가 해제된 후 setState() 가 호출될 수 있는 위치를 찾아 수정하는 것입니다. 이러한 상황은 component가 데이터를 기다리고 데이터가 도착하기전 마운트가 해제될 때, 콜백으로 인해 많이 발생됩니다. 콜백은 마운트가 해제되기전에 componentWillUnmount 단계에서 취소되어야합니다.
Pointer Events 는 모든 입력 이벤트를 다루는 통일된 방법을 제공합니다. 옛날에는 마우스와 각각 event listeners 를 다뤘지만, 요즘에는 터치스크린, 펜이 있는 휴대전화와 같이 마우스와 상관이 없는 많이 장치가 있습니다. 이런 이벤트들은 Pointer Events 를 지원하는 브라우저에서만 동작하는 것을 기억해야합니다.
React DOM 에서는 다음과 같은 event 를 사용할 수 있습니다.
- onPointerDown
- onPointerMove
- onPointerUp
- onPointerCancel
- onGotPointerCapture
- onLostPointerCaptur
- onPointerEnter
- onPointerLeave
- onPointerOver
- onPointerOut
만약 JSX 를 사용하여 component 를 렌더링하는 경우 component 의 이름은 대문자로 시작하여야 합니다. 그렇지 않으면 React 는 알 수 없는 태그로 인식하여 오류가 발생될 것 입니다. 이 규칙은 HTML 과 SVG 태그만 소문자로 시작할 수 있기 때문에 사용됩니다.
이름이 소문자로 시작하는 component class 를 정의할 수 있지만 가져올떄는 대문자여야 합니다.
여기 소문자는 괜찬습니다.
class myComponent extends Component {
render() {
return <div />
}
}
export default myComponent
다른 파일에서 가져올때는 대문자로 시작해야합니다.
import MyComponent from './MyComponent'
네 지원됩니다. 과거 React 는 알려지지 않은 DOM 속성은 무시되었었습니다. React 가 인식하지 못하는 속성을 가진 JSX 를 작성한다면 React 는 건너뛸 것 입니다.
예를 들면 다음과 같습니다.
<div mycustomattribute={'something'} />
React v15 에서 빈 div 를 DOM 에 렌더링합니다.
<div />
React v16 에서 알 수 없는 속성은 DOM 에 포함됩니다.
<div mycustomattribute='something' />
이 기능은 브라우저 별로 비표준 속성을 제공하고, 새로운 DOM API 를 시도, 다른 라이브러리와 같이 사용할 때 유용합니다.
ES6 클래스를 사용할 떄는 constructor에서 state를 초기화할 수 있고, React.createClass 를 사용할 때는 getInitialState() 에서 상태를 초기화해야합니다.
ES6 클래스 사용:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { /* initial state */ }
}
}
React.createClass() 사용:
const MyComponent = React.createClass({
getInitialState() {
return { /* initial state */ }
}
})
Note: React.createClass() 는 이제 사용되지 않고, React v16 에서 제거됩니다. 대신 JS class 를 사용하세요
기본적으로, component 의 state 또는 props 가 변경되면 component 는 다시 렌더링됩니다. render() 메소드가 다른 data 에 의존성이 있는 경우, forceUpdate() 를 호출하여 component에게 다시 렌더링이 필요하다고 알려줄 수 있습니다.
component.forceUpdate(callback)
forceUpdate() 의 사용은 피하고 render() 안의 this.props 와 this.state 는 읽는 용도로 사용하는 것이 좋습니다.
constructor() 에서 this.props 에 접근하고 싶을때 super() 메서드에 props 를 전달해야합니다.
super(props)를 사용:
class MyComponent extends React.Component {
constructor(props) {
super(props)
console.log(this.props) // { name: 'John', ... }
}
}
super() 를 사용:
class MyComponent extends React.Component {
constructor(props) {
super()
console.log(this.props) // undefined
}
}
constructor() 의 밖에서의 this.props 에는 같은 값이 표시됩니다.
ES6 arrow function 과 함께 Array.prototype.map 를 사용할 수 있습니다. 예를 들어, 객체의 items 배열은 component 의 배열로 맵핑됩니다.
<tbody>
{items.map(item => <SomeComponent key={item.id} name={item.name} />)}
</tbody>
for loop 를 사용하여 반복할 수 없습니다.
<tbody>
for (let i = 0; i < items.length; i++) {
<SomeComponent key={items[i].id} name={items[i].name} />
}
</tbody>
JSX 태그는 함수 호출로 변환되어지기 떄문에 표현식 안에서 JS 표현법을 사용할 수 없습니다. 이것은 stage 1 에 제안되어 변경되어질 수 있습니다.
React (또는 JSX) 는 속성 값 안에 써넣은 값은 지원되지 않습니다. 아래의 표현식은 동작하지 않습니다.
<img className='image' src='images/{this.props.image}' />
그러나 중괄호 안에 JS 표현식을 감싸 속성 값으로 넣을 수 있습니다. 아래의 표현식은 동작합니다.
<img className='image' src={'images/' + this.props.image} />
템플릿 문자열을 사용해도 동작합니다.
<img className='image' src={`images/${this.props.image}`} />
특정 모양을 가진 component 에 객체 배열을 전달하고 싶다면 React.PropTypes.arrayOf()의 인자로 React.PropTypes.shape() 를 사용하세요
ReactComponent.propTypes = {
arrayWithShape: React.PropTypes.arrayOf(React.PropTypes.shape({
color: React.PropTypes.string.isRequired,
fontSize: React.PropTypes.number.isRequired
})).isRequired
}
따옴표안에 중괄호를 사용하면 string 으로 인식되기 때문에 중괄호를 사용하면 안됩니다.
<div className="btn-panel {this.props.visible ? 'show' : 'hidden'}">
중괄호 밖으로 옮겨야 합니다. (class 의 이름 사이에 공백을 포함하는 것을 잊으면 안됩니다.)
<div className={'btn-panel ' + (this.props.visible ? 'show' : 'hidden')}>
템플릿 문자열을 사용해도 동작합니다.
<div className={`btn-panel ${this.props.visible ? 'show' : 'hidden'}`}>
React package 에는 React.createElement (), React.Component, React.Children 및 element 와 component class 관련된 helper 들이 포함되어 있습니다. 이런 기능들은 component 를 구축하는데 필요한 helpers 로 생각할 수 있습니다. react-dom package 에는 ReactDOM.render () 가 포함되어있고, react-dom/server 에는 ReactDOMServer.renderToString() 과 ReactDOMServer.renderToStaticMarkup() 포함되어있어 서버사이드 렌더링을 지원합니다.
React 팀은 모든 DOM 과 관련된 기능들을 ReactDOM 이라는 라이브러리로 분리하였습니다. React v0.14 는 ReactDOM 이 분리된 첫번째 릴리즈입니다. React-native, Reaction-Art, Reaction-Canvas, React-3 등 패키지의 일부를 보면 React 의 아름다움과 본질적인 부분이 브라우저나 DOM 과 아무런 관계가 없다는 것을 알 수 있습니다. React 가 렌더링할 수 있는 많은 환경들을 만들기 위해, React 팀은 React 와 React-dom 을 분리할 계획을 세웠습니다. 이것은 웹 버전의 React 와 React native 사이의 공유할 수 있는 component 를 작성하는 방법을 개척합니다.
표준 for
속성을 사용하는 text input 에 바인드된 label element 를 렌더링하려고하면 속성이없는 HTML이 생성되고 console 에 경고가 출력됩니다.
<label for={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />
for 는 JS 의 예약된 키워드입니다, 대신 htmlFor 를 사용하세요
<label htmlFor={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />
React 에서 spread operator 를 사용할 수 있습니다.
<button style={{...styles.panel.button, ...styles.panel.submitButton}}>{'Submit'}</button>
React Native 를 사용하고 있다면 배열 표기법을 사용할 수 있습니다.
<button style={[styles.panel.button, styles.panel.submitButton]}>{'Submit'}</button>
componentDidMount() 에서 resize 이벤트를 listen 할 수 있고, 크기(width, height)를 update 할 수 있습니다. componentWillUnmount() 에서 listener 를 제거해줘야합니다.
class WindowDimensions extends React.Component {
componentWillMount() {
this.updateDimensions()
}
componentDidMount() {
window.addEventListener('resize', this.updateDimensions)
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateDimensions)
}
updateDimensions() {
this.setState({width: $(window).width(), height: $(window).height()})
}
render() {
return <span>{this.state.width} x {this.state.height}</span>
}
}
setState() 를 사용하면 이전 state 와 다음 state 가 병합됩니다. replaceState()는 현재 state 를 버리고 당신이 제공한 state 로 바꿉니다. 이전의 key 들을 모두 제거해야되는 경우가 아니라면 보통 setState() 가 사용됩니다. replaceState()를 사용하는 대신 setState()에서 state 를 flase/null 로 설정할 수 있습니다.
state 가 변경될때 다음의 lifecycle 메서드들이 호출됩니다. 이전에 제공된 state 와 props 의 값을 현재의 state와 props와 비교하여 의미있는 변화가 있는지 확인할 수 있습니다.
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
좋은 방법은 Array.prototype.filter() 메서드를 사용하는 것입니다.
예를 들어, state update를 위한 removeItem () 메서드를 만들겠습니다.
removeItem(index) {
this.setState({
data: this.state.data.filter((item, i) => i !== index)
})
}
latest version (최신버전 >= 16.2) 부터 가능합니다. 가능한 옵션은 다음과 같습니다.
render() {
return false
}
render() {
return null
}
render() {
return []
}
render() {
return <React.Fragment></React.Fragment>
}
render() {
return <></>
}
<pre>
태그를 사용하여 JSON.stringify() 형식이 유지되도록 할 수 있습니다.
const data = { name: 'John', age: 42 }
class User extends React.Component {
render() {
return (
<pre>
{JSON.stringify(data, null, 2)}
</pre>
)
}
}
React.render(<User />, document.getElementById('container'))
React 의 철학은 props 는 immutable(불변) 이어야하고 top-down (부모 -> 자식) 방식입니다. 부모는 모든 props 값을 자식에게 보낼 수 있지만, 자식은 받은 props 를 변경할 수 없습니다.
input element 를 위한 ref 를 생성하여 componentDidMount() 에서 사용하면 됩니다.
class App extends React.Component{
componentDidMount() {
this.nameInput.focus()
}
render() {
return (
<div>
<input
defaultValue={'Won\'t focus'}
/>
<input
ref={(input) => this.nameInput = input}
defaultValue={'Will focus'}
/>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
- state 와 합쳐질 object 를 사용하여 setState() 호출
- Object.assign() 을 사용하여 object 의 복사본을 만듭니다.
const user = Object.assign({}, this.state.user, { age: 42 })
this.setState({ user })
- spread operator 를 사용:
const user = { ...this.state.user, age: 42 }
this.setState({ user })
- function 을 이용하여 setState() 호출:
this.setState(prevState => ({
user: {
...prevState.user,
age: 42
}
}))
React 는 성능을 위해 여러 setState() 호출들을 일괄 처리 합니다. 이유는 this.props 와 this.state는 비동기로 update 될 수 있습니다. 다음 state 를 계산할 때 계산된 값을 신뢰하면 안됩니다.
카운터 예제는 기대한대로 update 되지 않습니다.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
})
추천되는 접근방법은 object 가 아닌 function 으로 setState() 를 호출하는 것입니다. function 은 첫번째 인자로 이전 state를 받고 두 번째 인자로 update가 적용될 때의 props 를 받습니다.
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))
React.version 을 사용하면 버전을 가져올 수 있습니다.
const REACT_VERSION = React.version
ReactDOM.render(
<div>{`React version: ${REACT_VERSION}`}</div>,
document.getElementById('app')
)
- core-js 에서 수동 import:
- polyfills.js 파일을 만들어 root index.js 에서 import 합니다.
npm install core-js
또는yarn add core-js
를 추가하고 필요한 기능을 import 합니다.
import 'core-js/fn/array/find'
import 'core-js/fn/array/includes'
import 'core-js/fn/number/is-nan'
- Polyfill 서비스 사용:
- polyfill.io CDN 을 사용하여 다음의 줄을 index.html 에 추가하고, 사용자 지정 브라우저별 Polyfill을 검색합니다.
<script src='https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes'></script>
위의 스크립트에서 Array.prototype.includes 는 기본 스펙에 포함되지 않았기 때문에 기능을 요청했습니다.
HTTPS = true
설정이 필요합니다. package.json 에 scripts 부분을 편집할 수 있습니다.
"scripts": {
"start": "set HTTPS=true && react-scripts start"
}
또는 set HTTPS=true && npm start
을 실행하세요
프로젝트의 root 에 env
파일을 만들고 import path 를 작성해주세요.
NODE_PATH=src/app
설정후 개발서버를 재시작 해주세요. 상대 경로없이 src/app 안에 있는 모든것을 import 할 수 있습니다.
history 객체에 listener 를 추가하여 각 페이지 뷰를 기록하세요.
history.listen(function (location) {
window.ga('set', 'page', location.pathname + location.search)
window.ga('send', 'pageview', location.pathname + location.search)
})
setInterval() 를 사용하여 변경을 트리거해야합니다. 오류 및 메모리 누수를 방지하려면 component unmount 시 타이머를 제거해야합니다.
componentDidMount() {
this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000)
}
componentWillUnmount() {
clearInterval(this.interval)
}
React 에서는 vendor prefixes 를 자동으로 적용해주지 않습니다. vendor prefixes 를 수동으로 추가해야합니다.
<div style={{
transform: 'rotate(90deg)',
WebkitTransform: 'rotate(90deg)', // note the capital 'W' here
msTransform: 'rotate(90deg)' // 'ms' is the only lowercase vendor prefix
}} />
component 를 export 하려면 default 를 사용해야합니다.
import React from 'react'
import User from 'user'
export default class MyProfile extends React.Component {
render(){
return (
<User type="customer">
//...
</User>
)
}
}
export 를 사용하면 MyProfile 이 모듈로 내보내집니다. 다른 component 의 이름을 사용하지 않아도 동일한 파일을 import 할 수 있습니다.
JSX 에서 소문자 태그 이름은 HTML 태그로 간주됩니다. 그러나 점 ((property accessors) 이 있는 대문자, 소문자 태그 이름은 HTML 태그로 간주되지 않습니다.
- compiles to React.createElement('component') (i.e, HTML tag)
- <obj.component /> compiles to React.createElement(obj.component)
- compiles to React.createElement(Component)
React의 reconciliation 알고리즘에서는 custom component 가 다음 렌더링의 같은 위치에 나타나면 이전 component 와 동일하므로 인스턴스를 새로 만들지 않고 다시 사용한다고 가정합니다.
ES7 의 static 필드를 사용하여 상수를 정의 할 수 있습니다.
class MyComponent extends React.Component {
static DEFAULT_PAGINATION = 10
}
static 필드는 statge 3 의 제안된 부분입니다.
callback 을 통한 ref prop 를 사용하여 HTMLInputElement
객체에 대한 참조를 가져와 class property 로 저장하고,
나중에 저장된 참조를 사용를 이용하여 HTMLElement.click
메서드를 사용해 이벤트 핸들러에서 클릭 이벤트를 트리거 할 수 있습니다.
이 작업은 두 단계로 나눌 수 있습니다.
- render 메서드 안에서 ref 생성
<input ref={input => this.inputElement = input} />
- 이벤트 핸들러에서 클릭 이벤트 제공:
this.inputElement.click()
- ###(Is it possible to use async/await in plain React?
React 에서 async/await 을 사용하고 싶다면 Babel 과 transform-async-to-generator 플러그인이 필요합니다.
여기 React project 의 파일 구조에 대한 두 가지의 사례가 있습니다.
- 기능 또는 경로별로의 그룹: 프로젝트를 구조화하는 하나의 일반적인 방법으로는 기능이나 경로별로 그룹화된 CSS, JS, test 들을 함께 구성하는 것입니다.
common/
├─ Avatar.js
├─ Avatar.css
├─ APIUtils.js
└─ APIUtils.test.js
feed/
├─ index.js
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
└─ FeedAPI.js
profile/
├─ index.js
├─ Profile.js
├─ ProfileHeader.js
├─ ProfileHeader.css
└─ ProfileAPI.js
- 파일 형식의 그룹화 프로젝트를 구조화하는 다른 인기있는 방법으로는 유사한 파일들을 그룹화하는 것입니다.
api/
├─ APIUtils.js
├─ APIUtils.test.js
├─ ProfileAPI.js
└─ UserAPI.js
components/
├─ Avatar.js
├─ Avatar.css
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
├─ Profile.js
├─ ProfileHeader.js
└─ ProfileHeader.css
React 생태계에서 인기있는 애니메이션 패키지는 React Transition Group과 React Motion 입니다.
styles modules 은 component 에서 style 의 값을 하드 코딩하는 것을 피하기 위해 추천됩니다. 서로 다른 UI component 에서 사용될 수 있는 값들은 자체 모듈에서 받아와야 합니다.
예를 들어, 이런 style 들은 별도의 component 로 받아낼 수 있습니다.
export const colors = {
white,
black,
blue
}
export const space = [
0,
8,
16,
32,
64
]
다른 component 들에서 개별적으로 가져올 수 있습니다.
import { space, colors } from './styles'
ESLint 는 인기있는 Javascript linters 입니다. ESLint 는 코드 스타일을 분석할 수 있는 플러그인입니다.
React 에서 대부분 사용하는 npm 패키지 중 하나는 eslint-plugin-react
입니다.
기본적으로 몇가지의 best practices 를 확인하여 규칙을 바탕으로 iterators 의 key 에서 전체 prop type 까지 확인합니다.
다른 인기있는 플러그인은 eslint-plugin-jsx-a11y
이며, 접근성을 통해 문제를 해결하는데 도움을 줍니다.
JSX 는 일반적인 HTML 문법과 약간 다르게 제공하므로, alt
text 그리고 tabindex
같은 문제는 플러그인으로 선택되지 않습니다.
AJAX 라이브러리인 Axios, jQuery AJAX, 그리고 브라우저에 내장된 fetch 를 사용할 수 있습니다.
componentDidMount()
lifecycle 메서드에서 데이터를 불러와야합니다.
setStaet() 를 사용하여 데이터를 검색할때 component 를 update 할 수 있습니다.
예를 들어, API 에서 직원목록을 가져와 local state 를 설정합니다:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
employees: [],
error: null
}
}
componentDidMount() {
fetch('https://api.example.com/items')
.then(res => res.json())
.then(
(result) => {
this.setState({
employees: result.employees
})
},
(error) => {
this.setState({ error })
}
)
}
render() {
const { error, employees } = this.state
if (error) {
return <div>Error: {error.message}</div>;
} else {
return (
<ul>
{employees.map(item => (
<li key={employee.name}>
{employee.name}-{employees.experience}
</li>
))}
</ul>
)
}
}
}
Render Props 는 값이 함수인 prop 를 이용하여 component 간에 코드를 공유하는 기술입니다. 아래 component 는 render prop 을 사용하여 React element 를 반환합니다.
<DataProvider render={data => (
<h1>{`Hello ${data.target}`}</h1>
)}/>
React Router 와 DownShift 라이브러리는 이 패턴을 사용합니다.
React Router 는 React 에 구현된 강력한 routing 라이브러리 입니다. 페이지에 보여지는 내용과 URL 을 동기화된 상태로 유지해주고, 애플리케이션에 새로운 화면과 흐름을 추가할 수 있게 도와줍니다.
React router 는 history 라이브러리를 감싼 래퍼입니다. React router는 브라우저의 window.history
과 상호작용을 하고, 브라우저 및 hash history 을 다룹니다.
또 모바일 앱 개발 (React Native) 및 Node 의 unit testing 처럼 global history 가 없는 환경에 유용한 memory 히스토리를 제공합니다.
React router v4는 아래와 같은 3가지 component 를 제공합니다.
- <BrowserRouter>
- <HashRouter>
- <MemoryRouter>
위의 components 들은 browser, hash, 그리고 memory history 인스턴스를 만듭니다. React Router v4 는 Router Object 의 context 를 통해 history 인스턴스의 속성과 메서드를 이용할 수 있게 해줍니다.
history 인스턴스에는 navigation 을 위한 두 가지 메서드가 있습니다.
- push()
- replace()
방문한 위치의 history 를 배열로 생각한다면, push() 는 배열에 새 위치를 추가하는 것이고 replace() 는 현재의 위치를 새 위치로 변경하는 것입니다.
component 안에서 프로그래밍 방식으로 routing/navigation 을 수행하는 세가지의 다른 방법이 있습니다.
- withRouter() higher-order function 을 사용하기: withRouter () higher-order function 은 history object 를 component 의 prop 로 삽입합니다. 이 객체는 context 의 사용을 피하기 위해 push() 그리고 replace() 메서드를 제공합니다.
import { withRouter } from 'react-router-dom' // this also works with 'react-router-native'
const Button = withRouter(({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
{'Click Me!'}
</button>
))
- component 그리고 render props 패턴 사용하기: component 는 withRouter() 와 같은 prop 를 전달하므로, history prop 를 통해 history 메서드에 접근할 수 있습니다.
import { Route } from 'react-router-dom'
const Button = () => (
<Route render={({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
{'Click Me!'}
</button>
)} />
)
- context 사용 이 옵션은 추천되지 않으며 불안정한 API 로 처리됩니다.
const Button = (props, context) => (
<button
type='button'
onClick={() => {
context.history.push('/new-location')
}}
>
{'Click Me!'}
</button>
)
Button.contextTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired
})
}
수년동안 다른 구현 지원에 대한 사용자들의 요청이 있었기 때문에 React Router v4의 query strings 을 parse 하는 기능이 제거되었습니다. 그래서 사용자들이 선호하는 구현방식을 선택하도록 결정되었습니다. 추천 방법은 query strings 라이브러리를 사용하는것 입니다.
const queryString = require('query-string');
const parsed = queryString.parse(props.location.search);
native 방식을 원한다면 URLSearchParams 을 사용할 수 있습니다.
const params = new URLSearchParams(props.location.search)
const foo = params.get('name')
IE11에는 polyfill을 사용해야합니다.
는 route 를 독점적으로 렌더링하기 때문에 으로 Route 들을 감싸야합니다.
먼저 Switch 를 가져와야 추가해야 합니다.
import { Switch, Router, Route } from 'react-router'
블록안에 routes 를 정의해아합니다.
<Router>
<Switch>
<Route {/* ... */} />
<Route {/* ... */} />
</Switch>
</Router>
navigating 하는 동안 history object 에 props 를 전달할 수 있습니다.
this.props.history.push({
pathname: '/template',
search: '?name=sudheer',
state: { detail: response.data }
})
search
속성은 push() 메서드에 query params 을 전달하는데 사용됩니다.
<Switch>
는 일치하는 첫번째 자식 <Route>
를 렌더링합니다. 경로가 없는 <Route>
는 항상 일치합니다.
아래와 같이 경로를 속성을 제거해주면됩니다.
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/user" component={User}/>
<Route component={NotFound} />
</Switch>
- history object 를 내보내고 이 모듈을 전체 프로젝트에 가져오는 모듈을 만드세요.
예를 들면, history.js 파일을 만듭니다.
import { createBrowserHistory } from 'history'
export default createBrowserHistory({
/* pass a configuration object here if needed */
})
- 기본 구현된 routers 대신
<Router>
component 를 사용해야합니다. index.js 에서 위의 history.js 를 가져왔습니다.
import { Router } from 'react-router-dom'
import history from './history'
import App from './App'
ReactDOM.render((
<Router history={history}>
<App />
</Router>
), holder)
- 기본 구현된 history object 와 유사하 history object 의 push 메서드를 사용할 수 있습니다.
// some-other-file.js
import history from './history'
history.push('/go-here')
react-router
패키지는 React Router 에 <Redirect>
component 를 제공합니다. <Redirect>
을 렌더링하면 새로운 위치로 이동합니다.
server-side redirect 와 같이, history stack 의 현재 위치를 새로운 위치로 덮어씌웁니다.
import React, { Component } from 'react'
import { Redirect } from 'react-router'
export default class LoginComponent extends Component {
render() {
if (this.state.isLoggedIn === true) {
return <Redirect to="/your/redirect/page" />
} else {
return <div>{'Login Please'}</div>
}
}
}
React Intl 라이브러리는 문자열, 날짜와 숫자, 다중화 formatting 을 다룰 수 있는 component 와 api 를 제공합니다. React Intl 는 components 와 API 를 통해 React 에 바인딩을 제공하는 FormatJS 의 일부분입니다.
- 구분자와 함께 숫자를 표시합니다
- 날짜와 시간을 정확하게 표시합니다.
- "now" 를 기준으로 날짜를 표시합니다.
- 150 개 이상의 언어를 지원합니다.
- 브라우저와 노드에서 실행됩니다.
- 표준을 기반으로 구축되었습니다.
라이브러리는 문자, 숫자와 날짜의 형식을 지정하는 두 가지 방법을 제공합니다. : React component 또는 API
<FormattedMessage
id={'account'}
defaultMessage={'The amount is less than minimum balance.'}
/>
const messages = defineMessages({
accountMessage: {
id: 'account',
defaultMessage: 'The amount is less than minimum balance.',
}
})
formatMessage(messages.accountMessage)
react-intl 의 <Formatted... /> component 는 elements 를 리턴하므로, placeholders, alt text, etc 등을 사용할 수 없습니다. 이런 경우에는 하위 레벨의 API formatMessage() 를 사용해야합니다. higher-order component 인 injectIntl() 를 사용하여 component 에 intl object 를 주입하고 object 에서 사용할 수 있는 formatMessage() 를 사용하여 message 형식을 지정할 수 있습니다.
import React from 'react'
import { injectIntl, intlShape } from 'react-intl'
const MyComponent = ({ intl }) => {
const placeholder = intl.formatMessage({id: 'messageId'})
return <input placeholder={placeholder} />
}
MyComponent.propTypes = {
intl: intlShape.isRequired
}
export default injectIntl(MyComponent)
injectIntl()
를 사용하면 application 의 모든 component 에서 현재 locale 을 가져올 수 있습니다.
import { injectIntl, intlShape } from 'react-intl'
const MyComponent = ({ intl }) => (
<div>{`The current locale is ${intl.locale}`}</div>
)
MyComponent.propTypes = {
intl: intlShape.isRequired
}
export default injectIntl(MyComponent)
higher-order component 인 injectIntl()
는 component 의 props 를 통해 formatDate ()
메서드에 대한 접근을 제공합니다.
이 메서드는 FormatedDate 의 인스턴스 내부에서 사용되며 형식이 지정된 날짜의 문자 표현을 반환합니다.
import { injectIntl, intlShape } from 'react-intl'
const stringDate = this.props.intl.formatDate(date, {
year: 'numeric',
month: 'numeric',
day: 'numeric'
})
const MyComponent = ({intl}) => (
<div>{`The formatted date is ${stringDate}`}</div>
)
MyComponent.propTypes = {
intl: intlShape.isRequired
}
export default injectIntl(MyComponent)