JavaScript UI Library
상태값(state, props, redux store 등)이 변경될 때마다 UI를 자동으로 업데이트해주는 JS Library (해당 컴포넌트 함수를 자동으로 재 호출하여 재 렌더링 해줌.)
이를 위해 "Virtual DOM 을 통해 변경된 부분만 효율적으로 Update" 해주는 구조 채택.
HTML, XML 문서의 프로그래밍 interface.
텍스트 파일이기만 했던 HTML이나 XML 문서(document)에 프로그래밍 언어가 접근할 수 있도록 해주는 문서 구조를 트리 형태의 객체(Object)로 표현한 것.(문서의 구조화된 표현(structured representation)을 제공) 특히 웹 브라우저는 DOM을 활용하여 자바스크립트와 CSS를 적용함.(문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.)
우리는 웹브라우저를 통해 원하는 페이지로 이동을 할 때 도메인을 이용. 도메인 주소로 접속을 하면 DNS 서버로 가서 실제 주소(서버)에 요청. 요청 후 서버는 해당 웹페이지 index.html 등의 파일을 Response 해줌. 이 Response 파일들은 이제 앞서 설명한대로 브라우저와 자바스크립트가 HTML을 이해하기 쉽게 트리 구조로 표현.
DOM은 새로운 요청, 변경사항이 있으면 위와 같은 형태를 거쳐 매번 리렌더링 하게 됨.
DOM API는 수많은 웹 브라우저에서 사용되어 왔지만, 이 DOM에는 동적 UI에 최적화되어 있지 않다는 치명적인 문제점 한 가지가 존재.
동적으로 웹 페이지를 변경하다 보면 element의 생성, 수정, 제거 등 DOM을 변화시키는 수많은 연산이 생김.
이 과정에서 문제가 되는 경우는 현대의 웹처럼 변경해야할 대상도 많고 변경도 많은 경우.
프로그래밍에 의해 DOM을 변경해야하고 변경할 구성 요소가 100개면 위의 과정을 100번 하는 비효율적인 작업을 해왔음.
DOM 자체를 처리하는 것은 그렇게 큰 성능 이슈를 발생시키지 않지만,
(DOM 속도는 느리지 않음. DOM 자체를 읽고 쓸 때의 성능은 자바스크립트 객체를 처리할 때의 성능과 비교하여 다르지 않음.)
정확히는 DOM을 변경하는게 문제가 아니고 렌더링을 여러번 하는 게 문제.
웹 브라우저는 DOM을 활용하여 객체에 JS와 CSS를 적용하게 되는데, 웹 브라우저 엔진의 성능이 좋아졌지만, CSS 연산을 다시 하고 페이지를 리 페인트 하는 과정이 매번 새롭게 반복되면(렌더링 되면) 시간이 오래 걸리면서 성능이 안 좋아짐.
하지만, 그렇다고 DOM을 사용하지 않을 수도 없음. 결과적으로 웹 브라우저는 최종적으로 DOM을 보고 웹 페이지를 그리기 때문.
그렇다면 작업의 결과물은 동일하게 유지하되 변경되는 DOM을 최소한으로 만들기 위한 방법이 필요했고, React는 Virtual DOM을 활용해 DOM 처리과정을 최적화.
그렇기 때문에 Virtual DOM을 사용해야 하며, 이 방식을 통해 DOM 업데이트를 추상화함으로써 DOM 처리 횟수를 최소화하고 효율적으로 진행하게 만들어줌. 여기서 이제 Virtual DOM의 장점이 나오게 됨.
Ps)
Browser도 무식하게 100번 Rendering 하지는 않음.
이해를 돕기 위한 예시고 사실은 DOM을 제어하는 메서드에 따라서 다르지만 어느정도 합쳐서 배치로 작업을 할 수 있음. (Batched DOM Update)
그냥 이해를 돕기 위해 100개가 변경되면 20번 렌더링이 된다고 가정.
그래도 여전히 비효율적임.
실제 DOM에 접근하여 조작하는 대신, 이를 추상화하여 메모리에 저장하고 관리하는 자바스크립트 객체(Virtual DOM)를 구성하여 사용.(JavaScript 객체를 따로 관리하므로 메모리 사용량이 늘어난다는 단점이 있음. 일종의 DOM 캐싱으로 볼 수 있음.)
in-memory에 존재해서 실제 Render 되지 않음.
HTML은 브라우저가 문서 객체 모델(DOM)을 구성하기 위해 따라야 하는 절차. HTML 문서를 이루는 Element는 Browser가 HTML 문서를 읽어 들이면 DOM Element가 되고, 이 DOM이 화면에 사용자 인터페이스를 표시. (Render Tree)
React는 DOM 엘리먼트를 직접 조작하지 않고, Virtaul DOM(React Element. React Element는 HTML Element와 비슷하지만 실제로는 Javascript 객체)을 다룸.
따라서 HTML DOM API를 직접 다루는 것보다 Javascript 객체인 React 엘리먼트를 직접 다루는 편이 훨씬 빠름.
{
type: 'div',
props: {
children: [
{
type: button,
props: {
children: "버튼입니다",
onClick: ()=>{}
}
},
{
type: input,
props: {children: "인풋입니다."}
},
{
type: CountButton,
props: {children: "리액트컴포넌트입니다"}
}
]
}
}
1) 렌더 단계(Render Phase. diffing 이라고도 함.)
2) 커밋 단계(Commit Phase)
위의 과정을 재조정(Reconcilation) 이라고 부름. 이렇듯 동적 UI를 가진 웹 서비스라면 React를 활용해 효율적으로 더 좋은 UX를 제공할 수 있는 것.
EX) 만약 어떤 게시판에서 다음 페이지를 눌러 리스트 10개(<ul>
태그안의 <li>
태그)를 변경하는데 DOM에 접근하여 <li>
10번을 바꾸지 않고 Virtual DOM을 통해 리스트 10개(<li>
태그)를 변경하고 Virtual DOM과 실제 DOM을 비교하여 변경된 부분(<ol>
통채로)을 1번만에 변경하여 렌더링도 1번만 일어나게 하는 것
위와 같은 과정을 React에서는 ReactDOM.render() 함수가 도맡아서 처리하고 있음. 만약 어떻게 render 함수가 변화를 감지하고 효과적으로 DOM을 업데이트하는지 알고 싶다면 React 공식 문서에 나와있는 Reconciliation 문서를 읽는 것을 추천.
React의 얕은 비교는 같은 레벨에서만 일어남.
그러므로 배열을 직접 수정하는 방식, 예를 들어, push, pop 과 같은 메소드로 배열을 수정한 뒤 setState에 담아주어도 기본적으로 같은 참조 위치를 가지고 있기 때문에 값의 변화를 감지하지 못함.
React에서 객체 불변성을 유지하기
React에서 배열 값을 변경할 때는 객체와 마찬가지로, 불변성을 지켜주어야 함.
이는 무슨 말이냐고 하면, 배열 혹은 객체의 원본을 수정하지 않고 상태 변경을 원하는 배열과 함수를 복사(깊은 복사, 얕은 복사는 안 됨)하고 나서 사용해야 한다는 뜻.
그래서 원본을 수정하는 메소드인 push(), pop()과 같은 메소드를 사용해서 원본을 직접 수정하는 것은 React를 사용할 때 지양해야 함.
대신, assign() 메소드를 사용하거나, 전개구문을 사용해서 복사를 한 뒤 그 복사값을 수정하고 setState에 담아주면 변경을 감지할 수 있음.
1) 리액트 라이브러리 불러오기
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
2) 리액트 컴포넌트 생성하기
// 함수 컴포넌트
function CountButton() {
// useState 사용
const [count, setCount] = React.useState(0);
// 리액트 요소 생성
return React.createElement(
'button', // button 태그 생성
{
onClick: () => {
// 클릭 이벤트 핸들러 설정
setCount(count + 1);
}
},
count // children 설정
);
}
3) 리액트 컴포넌트를 화면에 렌더링하기
const domContainer = document.getElementById('root');
ReactDOM.render(React.createElement(CountButton), domContainer);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- 리액트 라이브러리 로드 -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
<!-- 컨테이너 -->
<div id="root"></div>
<script>
// 리액트 함수 컴포넌트
function CountButton() {
// useState 사용
const [count, setCount] = React.useState(0);
return React.createElement(
'button', // button 태그 생성
{
onClick: () => {
// 클릭 이벤트 핸들러 설정
setCount(count + 1);
}
},
count // children 설정
);
}
// div#root 태그에 리액트 컴포넌트 렌더링하기
const domContainer = document.getElementById('root');
ReactDOM.render(React.createElement(CountButton), domContainer);
</script>
</body>
</html>
실제 리액트로 개발을 할 때는 JSX로 코딩하고, 여기 저기서 모듈들을 export, import 해서 불러오는데, 사실 이는 모두 바벨과 웹팩의 은총 덕분임.
1) Babel 이 JSX를 React.createElement() 로 변환해줌.
2) WebPack이 JS, CSS 파일을 번들링하여 모듈화해줌.
참고로, npx create-react-app 등을 사용하면 Babel, Webpack 등이 기본적으로 세팅되어있음.
참고
https://buyandpray.tistory.com/79
https://curryyou.tistory.com/484
https://velog.io/write?id=b6d68106-75d3-499b-aed0-02d9f73467a3
https://devbirdfeet.tistory.com/219
https://jw910911.tistory.com/24
https://jeong-pro.tistory.com/210
https://ljtaek2.tistory.com/137
https://babycoder05.tistory.com/entry/React-Virtual-DOM-%EA%B3%BC-%EB%B9%84%EA%B5%90-%EC%9B%90%EB%A6%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B9%84%EA%B5%90?category=1023016