DOM: Document Object Model로 HTML 문서를 프로그래밍적으로 접근 가능하게 해주는 인터페이스
"변화(Mutation)"라는것은 상당히 복잡한 작업:
특정 이벤트가 발생했을때, 모델에 변화를 일으키고, 변화를 일으킴에 따라 어떤 DOM 을 가져와서 어떠한 방식으로 뷰를 업데이트 해줄 지 로직을 정해줘야 하는데 페이스북은 변화를 주는 것이 아니라 뷰를 날려버리고 새로 만드는 것을 고려했다.
변화를 줄 때 마다 뷰를 새로 만들어버리면 성능적으로 엄청난 문제가 있을 것...
그래서 등장하는 것이 Virtual DOM
이다.
Virtual DOM: 말 그대로 가상의 DOM이다. 변화가 일어나면, 실제로 브라우저의 DOM 에 새로운걸 넣는것이 아니라, 자바스크립트로 이뤄진 가상 DOM 에 한번 렌더링을 하고, 기존의 DOM 과 비교를 한 다음에 정말 변화가 필요한 곳에만 업데이트를 해주는 것
즉, 데이터가 바뀌었을 때 어떻게 업데이트를 할 지를 고려하는 것이 아닌, 일단 바뀐 데이터로 그려놓고 비교를 한 다음, 바뀐 부분만 바꾸는 것이다.
RealDOM 을 조작하는데 많은 비용이 들어간다는 점을 고려하여 리액트는 RealDOM 대신 VirtualDOM을 사용한다
단방향 데이터 흐름으로 데이터를 추적하기 쉽고 디버깅을 하기 쉽다
단방향 데이터 흐름이란, 데이터는 항상 일정한 장소에 위치해있고, 그 장소에서만 변경이 가능하다. 반면 양방향 데이터 흐름일 경우에 데이터는 view나 model로 변한다. 즉, Data가 UI를 변경시키는 것이다. 반대로 UI가 Data를 변경시킬 수는 없다.
UI Component 기반으로, UI를 component로 쪼개서 사용하여 재사용성 및 유지보수의 이점이 있다
JSX 는 JavaScript XML (ECMAScript로 XML 유사 구문 확장) 의 구문 표기법이다. HTML과 같은 문법과 함께 JavaScript를 표현할 수 있다.
아래의 h1 태그안에 text 는 render 함수에 의해 JS 함수로 반환된다.
render() {
return (
<div>
<h1> Welcome to React world!!</h1>
</div>
);
}
DOM 노드, 또는 다른 component들과 관련하여 화면에 표시 할 내용을 표현하는 일반 객체다. 가볍고, stateless 하고, 불변하며 가상 DOM 안에 포함되어 있다.
Element
는 바로 사용되지는 않으며, Component
에서 리턴 받아서 사용 되곤 한다.
function
혹은 class
이며, state를 가지고 있다. props
를 받아 Elements
를 출력하는 함수와 같다.
Component
의state
가 바뀌면,Component
를 리턴 받아서가상 DOM
에 만든다(Element
를 만드는 비용은 저렴하다).가상 DOM
과Real DOM
을 비교하여Real DOM
에서Component
의 바뀐 부분을 업데이트 한다.
두가지의 방법이 있다.
Component
를 생성하는 가장 간단한 방법이며 state
와 LifeCycle
이 없다. 대신 props
를 받을 수 있고 React elements
를 리턴할 수 있다. 초기 마운트가 빠르고 메모리 자원을 덜 쓴다.
import React from 'react';
function Hello(props) {
return (
<div>Hello {props.name}</div>
);
}
export default Hello;
EcmaScipt 6
에 도입된 class 문법을 사용한다. 컴포넌트에서 LifeCycle API
를 사용해야 하거나, state
를 사용하는 경우에는 꼭 이렇게 정의를 해야한다.
import React, { Component } from 'react';
class Hello extends React.Component {
render() {
return (
<div>Hello {this.props.name}</div>
);
}
}
export default Hello;
결론만 말하자면 두 개는 shouldComponentUpdate 라이프 사이클 메소드를 다루는 방식을 제외하곤 같다. 즉, PureComponent
는 shouldComponentUpdate
라이프 사이클 메소드가 이미 적용 된 버전의 React.Component 클래스라고 보면 된다.
React.Component
를 확장(extends)해서 컴포넌트를 만들 때, shouldComponentUpdate
메서드를 별도로 선언하지 않았다면, 컴포넌트는 props
, state
값이 변경되면 항상 리렌더링(re-render) 하도록 되어 있다.
하지만, React.PureComponent
를 확장해서 컴포넌트를 만들면, shouldComponentUpdate
메서드를 선언하지 않았다고 하더라도, PureComponent
내부에서 props
와 state
를 shallow level 안에서 비교 하여, 변경된 값이 있을 시에만 리렌더링 하도록 되어 있다.
props
는 부모 컴포넌트가 자식 컴포넌트에게 주는 값입니다. 자식 컴포넌트에서는 props 를 받아오기만하고, 받아온 props 를 직접 수정 할 수 는 없습니다.
반면에 state
는 컴포넌트 내부에서 선언하며 내부에서 값을 변경 할 수 있습니다.
props
는 스마트폰의 볼륨버튼이라면 사용자가 볼륨버튼을 누르면state
는 스마트폰안에서 스스로의 상태인 볼륨이 바뀌게 해놓은 모든 조치(회로,프로그래밍 등등)라고 할 수 있습니다.- 상위 컴포넌트는 하위 컴포넌트에게 props를 통해 값을 전달해 내부의 state를 바꾸기 때문에 컴포넌트 스스로 외부에서 전달되는 props를 변경하는 것은 금지되어 있습니다. 또한, 하위 컴포넌트가 상위 컴포넌트를 동작시키려면 props를 전달하는 것이 아니라 상위 컴포넌트 안에 이벤트를 심고 그 안에 setState로 값을 바꿔야 합니다.
LifeCycle
API 는 Component 가 브라우저에서 나타날때, 사라질때, 그리고 업데이트 될 때, 호출되는 API 이다. Component 가 생성 되고 소멸 될 동안 단계별로 Component 를 컨트롤 할 수 있도록 이 API를 오버라이드 해서 사용 할 수 있다.
컴포넌트가 생성될 때: constructor - getDerivedStateFromProps - render - componentDidMount
컴포넌트의 Props가 변경될 때: getDerivedStateFromProps -shouldComponentUpdate - render - getSnapshotBeforeUpdate - componentDidUpdate
컴포넌트의 State가 변경될 때: shouldComponentUpdate - render - getSnapshotBeforeUpdate -componentDidUpdate
constructor(props) {
super(props);
}
컴포넌트가 새로 만들어질 때마다 이 함수가 호출된다.
위 코드의 constructor 에서
super(props)
를 호출 한 이유는, 컴포넌트를 만들게 되면서, Component 를 상속했으며, 이렇게 constructor 를 작성하게 되면 기존의 클래스 생성자를 덮어쓰게 된다. 그렇기에, 리액트 컴포넌트가 지니고있던 생성자를 super 를 통하여 미리 실행하고, 그 다음에 우리가 할 작업 (state 설정) 을 해주는 것이다.
componentDidMount(nextProps) {
// 외부 라이브러리 연동: D3, masonry, etc
// 컴포넌트에서 필요한 데이터 요청: Ajax, GraphQL, etc
// DOM 에 관련된 작업: 스크롤 설정, 크기 읽어오기 등
}
이 API 는 컴포넌트가 화면에 나타나게 됐을 때 호출된다. 여기선 주로 D3, masonry 처럼 DOM 을 사용해야하는 외부 라이브러리 연동을 하거나, 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch 등을 통하여 ajax 요청을 하거나, DOM 의 속성을 읽거나 직접 변경하는 작업을 진행한다.
static getDerivedStateFromProps(nextProps, prevState) {
// 여기서는 setState 를 하는 것이 아니라
// 특정 props 가 바뀔 때 설정하고 설정하고 싶은 state 값을 리턴하는 형태로
// 사용됩니다.
/*
if (nextProps.value !== prevState.value) {
return { value: nextProps.value };
}
return null; // null 을 리턴하면 따로 업데이트 할 것은 없다라는 의미
*/
}
v16.3 이후에 만들어진 LifeCycle
API
이 API 는 props
로 받아온 값을 state
로 동기화 하는 작업을 해줘야 하는 경우에 사용된다.
shouldComponentUpdate(nextProps, nextState) {
// return false 하면 업데이트를 안함
// return this.props.checked !== nextProps.checked
return true;
}
리액트에서는 변화가 발생하는 부분만 업데이트를 해줘서 성능이 잘나온다. 하지만, 변화가 발생한 부분만 감지해내기 위해서는 Virtual DOM 에 한번 그려줘야한다.
즉, 현재 컴포넌트의 상태가 업데이트되지 않아도, 부모 컴포넌트가 리렌더링되면, 자식 컴포넌트들도 렌더링 된다.
변화가 없으면 물론 DOM 조작은 하지 않게 된다. 그저 Virutal DOM 에만 렌더링 할 뿐. 이 작업은 그렇게 부하가 많은 작업은 아니지만, 컴포넌트가 무수히 많이 렌더링 된다면 얘기가 조금 달라진다. CPU 자원을 어느정도 사용하고 있는것은 명백한 사실!
쓸데 없이 낭비되고 있는 이 CPU 처리량을 줄여주기 위해서 shouldComponentUpdate
를 작성 하여 Virtual DOM 에 불필요한 리렌더링을 방지한다.
이 함수는 기본적으로 true 를 반환한다. 따로 작성을 해주어서 조건에 따라 false 를 반환하면 해당 조건에는 render 함수를 호출하지 않는다.
이 API 가 발생하는 시점은 다음과 같다.
이 API를 통해서, DOM 변화가 일어나기 직전의 DOM 상태를 가져오고, 여기서 리턴하는 값은 componentDidUpdate
에서 3번째 파라미터로 받아올 수 있게 된다.
이 라이프 사이클 함수는 많이 활용 되지는 않지만, 화면을 갱신하는 동안 수동으로 스크롤 위치를 고정하는 경우 등에 사용될 수 있다.
이 API는 컴포넌트에서 render()
를 호출하고난 다음에 발생하게 된다. 이 시점에선 this.props
와 this.state
가 바뀌었다. 그리고 파라미터를 통해 이전의 값인 prevProps
와 prevState
를 조회 할 수 있다. 그리고, getSnapshotBeforeUpdate
에서 반환한 snapshot
값은 세번째 값으로 받아온다.
잘 활용되지 않지만 getSnapshotBeforeUpdate 함수와 함께 사용하여 스크롤을 수동으로 고정시킬 때 활용되기도 한다.
이벤트, setTimeout, 외부 라이브러리 인스턴스 제거