
React 시작하기.pdf
1.35MB
하단의 내용을 문서로 먼저 작성 후, 발표용 PDF 를 만들었다. 예시는 React 와 Vue 를 비교하면서, Vue 사용자들이 React 를 처음 사용할 때 당황할 법한 내용들을 담고 있다. 아래의 문서들이 너무 읽기 힘들다면 시각화된 자료로 간단하게 만든 PDF 파일을 보는 것을 권장.
1. React 의 고민
1-1) Angular.js 의 불편함
- React 가 등장하기 전, 모던 프레임워크로 Angular.js 가 등장했다.
- Angular.js 는 작은 컨테이너들을 결합해 애플리케이션을 구축하는 형태다.
- 하지만, 컨테이너들이 결합하고 동일한 데이터를 사용하는 컨테이너들이 데이터를 조작하면서 “의도치 않은 데이터 변경” 이 이곳저곳에서 생기기 시작했다.
- 애플리케이션의 구조는 더욱 커져 갔고, 데이터의 흐름을 파악하기 어렵고, 문제가 어디에서 발생했는지 디버깅하기 어려운 상황에 봉착했다.
1-2) DOM 조작의 불편함 (JQuery 의 불편함)
- DOM 내부에 노드의 수가 많아질수록 DOM 의 반응 속도가 느려진다.
- DOM 을 조작할 때마다 렌더링 트리는 각 변경 사항을 하나씩 해석하면서 렌더링하는 구조기 때문
- 물론, DOM 에서도 Batch 를 하긴 하지만, 어쨌든, 데이터 하나 변경에 여러 번 렌더링 하는 것을 막을 수는 없음
- DOM 에서 비용이 가장 많이 드는 과정이 “리플로우, 페인팅” 과정이기 때문에 최소한으로 할 수록 사용자 경험이 좋아진다.
- 문제는, SPA 의 특징상, DOM 의 복잡도가 늘어나고, 이에 따라 최적화와 유지보수가 더 어려워졌다.
- DOM 을 조작할 할 수 있는 방법이 너무 많다
- 당장의 노드를 집는 방법도 여러가지 존재한다.
1-3) UI/UX 의 중요성
- 웹 어플리케이션의 규모가 커지고, 웹 디자인이 대두되면서 UI/UX 가 점차 중요해짐
- 디자인을 쉽게 관리해야 할 필요성 증대
2. React 의 해결 방법
2-1) 단방향 데이터 흐름
- React 의 핵심 아이디어
- 데이터를 제공하는 곳과 조작하는 곳을 단 한 곳에서만 하는 것으로 컨셉을 잡는다


(1) 컴포넌트 내의 데이터 흐름
- 기존에 양방향 데이터 흐름에서는 데이터를 조작할 수 있는 방법이 JavaScript 로 조작하는 것과 데이터를 넘겨 받은 HTML 에서 조작하는 방법, 두 가지가 있었다.
- 여기서 HTML 에서 조작하는 방법은, 데이터를 받은 쪽에서 데이터를 변경해 다시 위로 넘기는 방식이므로 데이터 양방향이 된다.
- React 는 단방향으로 맞추기 위해서, HTML 에 의해 데이터가 조작되지 않도록 막고 JavaScript 에 의해서만 변경되도록 만들었다.
- JavaScript 에서 원본 데이터를 HTML (View) 에 전달한다
- 사용자로부터 HTML (View) 조작을 감지한다
- JavaScript 의 이벤트 함수를 호출한다
- 해당 이벤트 함수가 HTML (View) 에 주고 있던 데이터를 변형한다.
- 변형된 데이터를 다시 HTML (View) 에 전달한다
(2) 컴포넌트 간의 데이터 흐름
- 데이터의 흐름은 컴포넌트 간에서도 흐른다.
- 하지만, 단방향에서는 props 로 데이터와, 데이터를 변경할 함수를 모두 전달한다
- 양방향 데이터 흐름에서는 자식 컴포넌트에서 데이터의 변경을 감지하면 데이터를 변형하고 이 데이터를 부모 컴포넌트에 전달하는 방식을 사용했다.
- 이 방법을 사용하면, 데이터를 변경할 때 부모로부터 받은 함수를 사용하기 때문에, 데이터의 변경이 부모 컴포넌트에서 이뤄진다
- (첨언) 현재 LIME Plus 구조로 생각하면 데이터 양방향과 단방향의 차이점이 쉽게 와 닿지 않는다. 사용하는 입장에서 이해하기보다 React 의 구조가 자식 컴포넌트에서 자체적으로 데이터를 수정할 수 없다는 것만 인지하면 될 것 같다.
2-2) 가상 DOM (Virtual DOM)
- React 가 DOM 의 문제점을 해결하기 위해서 선택한 전략은 개발할 때 “DOM 을 조작할 일을 없게 만들자” 다.
- 즉, 개발자가 DOM API 를 호출해서 조작하지 않고 UI 를 선언하면, React 가 선언된 UI 를 DOM 에 Reflow, Painting 을 하는 방식.
- 이때, React 에서 Painting 하는 양과 횟수를 줄이기 위해(최적화하기 위해) 사용하는 것이 가상 DOM 이다.
- 변경 사항이 생기면, 그 변경 사항을 먼저 가상 DOM 에서 수정을 한다
- 수정된 가상 DOM 으로 UI 를 그린다
- 이때, 사용하는 알고리즘이 diff Algorithm 이다.
- 가상 DOM 은 실제 DOM Tree 를 가진 것은 아니고, DOM 을 추상화한 JavaScript 객체다.
2-3) 컴포넌트와 JSX
- UI/UX 가 중요해짐에 따라 통일성과 UI 에 대한 효율적인 관리가 중요해졌고, UI 와 해당 UI 와 매핑되는 기능을 캡슐화한 것이 컴포넌트.
- React 에서 컴포넌트를 만들 때 React.createElement() 를 사용하는 방법도 있지만, 더 편하고 일반적인 방법으로 JSX 문법을 사용한다
- JSX 문법은 HTML 마크업 언어처럼 보이지만, 사실 JavaScript 의 구문 확장이다.
<h1>Hedy Lamarr's Todos</h1> <img src="https://i.imgur.com/yXOvdOSs.jpg" alt="Hedy Lamarr" class="photo" > <ul> <li>Invent new traffic lights <li>Rehearse a movie scene <li>Improve the spectrum technology </ul>
export default function TodoList() {
return (
<>
<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve the spectrum technology</li>
</ul>
</>
);
}
- HTML 과 JSX 는 서로 다른 문법이기 때문에 JSX 를 작성할 때 HTML 작성하면 에러를 발생시킨다.(다행히 이런 종류의 에러는 에러 설명이 친절하게 잘 나와서 핸들링하기 쉽다.)
2-4) JSX 규칙
(1) 단일 루트 엘리먼트를 반환할 것.
- 위의 HTML 마크업을 보면 여러 태그들이 나열됐지만, JSX 코드를 보면 그 여러 태그들이 하나의 Fragment(<> </>) 에 감싸져 있다.
- 꼭 Fragment 가 아니더라도 <div></div> 태그로 감싸도 상관 없으며, 중요한 것은 return 되는 JSX 코드에는 반드시 단일 루트 엘리먼트로 (하나의 태그로) 반환돼야 한다.
- Fragment 는 HTML 과 매칭되는 태그가 없어서 DOM 에 따로 추가되는 태그 없이 Render Tree 를 형성한다.
(2) 모든 태그를 닫을 것
- HTML 에서는 태그를 열어둔 채로 둘 수 있지만 JSX 에서는 반드시 닫아야 한다.
- <img> 태그도 <img /> 로 self closing 을 해야 한다.
(3) 대부분의 HTML Attribute 는 카멜 케이스로 작성한다
- class 는 className 으로 속성을 줘야 한다.
- 그 외 속성 이름이 stroke-width 와 같이 하이픈(-) 으로 연결돼 있으면 카멜 케이스를 사용해 strokeWidth 속성을 부여해야 한다.
(4) 컴포넌트를 만들 때는 반드시 대문자로 시작할 것
- 컴포넌트의 이름을 todoList 이렇게 만들면 안 되고, 반드시 TodoList 로 만들어야 한다.
(5) { } 을 열면 자바스크립트 문법을 사용할 수 있다
- JSX 내부에서 { } 를 열면, 자바스크립트를 사용할 수 있는 환경이 열린다.
- { } 내부에서 자바스크립트에서 평가될 수 있는 모든 값, 표현식을 넣을 수 있다.
2-5) ADVANCED) React 의 구조
App.js
/* @jsx createElement */
import { createElement, render, Component } from './react'
// Title 안에서 JSX 문법을 사용하고
// h1 태그는 Transpiling 되면서 createElement 를 호출한다.
// vdom 스코프 내에서 <Title></Title> 이 호출하는 것은 createElement 함수
// 하지만 추가적인 작업 없이는 <Title></Title> 함수로 호출하지는 못한다
// Babel Try it out 에서 다음의 코드를 한 번 쳐보고 <Title></Title> 이 어떻게 바뀌는지 찾아볼 것
/**
* const Title = () => <h1>React 만들기</h1>
* const app = <p><Title></Title></p>
*
* 이렇게 될 경우
*
* const Title = () => React.createElement("h1", null, "React \\uB9CC\\uB4E4\\uAE30")
* const app = React.createElement("p", null, React.createElement(Title, null))
*
* 이렇게 되면 저기 React.createElement(Title, null) 에서 Title 이 함수인지 체크하고,
* 함수라면 그 함수를 호출했을 때 return 되는 값은 JSX 를 반환한다는 것만을 By Design 으로 지정하면 될 것
*
* const title = () => <h1>React 만들기</h1>
* const app = <p><title></title></p>
*
* 이렇게 하면 아래처럼 트랜스파일링이 된다
*
* const app = React.createElement("p", null, React.createElement("title", null));
*
* 소문자로 하게 되면 "문자열" 로 받고, 대문자로 하게 되면 "함수" 로 받는 것
* 문자열로 처리할지, 함수로 처리할 지. 그것을 구분하기 위해서 컴포넌트 이름의 첫 시작을 소문자, 대문자로 설정한 것
*/
class TitleClass extends Component {
render() {
return <h1>{this.props.children}</h1>
}
}
function Title(props) {
return <h1>{ props.children }</h1>
}
function Description(props) {
return <li style={`color:${props.color}`}>{props.children}</li>
}
const App = () => <p>
<Title>React를 잘 만들어 볼게요</Title>
<TitleClass>React를 클래스형 컴포넌트로 만들어 볼게요</TitleClass>
<ul>
<Description color="red">React 만들기 빨강</Description>
<Description color="blue">React 만들기 파랑</Description>
<Description color="green">React 만들기 초록</Description>
</ul>
</p>
render(<App />, document.querySelector('#root'));
react.js
const hooks = [];
let currentComponent = 0;
// DOM API 를 조작하는 것은 대부분 여기에서 컨트롤
// 바깥 쪽(app.js)에서 내부 구조에 관심을 두지 않도록 만드는 것
export function createDom(node) {
if(typeof node === 'string') {
return document.createTextNode(node);
}
const element = document.createElement(node.tag);
Object.entries(node.props)
.forEach(([name, value]) => element.setAttribute(name, value))
node.children
.map(createDom)
.forEach(element.appendChild.bind(element));
return element;
}
function makeProps(props, children) {
return {
...props,
children: children.length === 1 ? children[0] : children
}
}
function useState(initValue) {
// currentComponent++ 이 있은 후에 tag() 함수가 호출되므로 -1 을 해주어야 정상적으로 tag() 가 호출하는 hooks 를 호출할 수 있다
let position = currentComponent - 1;
if(!hooks[currentComponent]) {
hooks[currentComponent] = initValue
}
const modifier = nextValue => {
hooks[currentComponent] = nextValue
}
return [hooks[currentComponent], modifier]
}
// 함수형 컴포넌트
// children 은 배열
export function createElement(tag, props, ...children) {
props = props || {}
// typeof 연산자는 함수와 Class 모두 'function'으로 처리를 한다
if(typeof tag === 'function') {
// 실제 React 는 고유의 Symbol을 Class 에 만들어서 있는지 없는지를 체크
// 애초에 이렇게 만들지도 않을 것.
// 함수형 컴포넌트라면 호출할 때마다 새로운 스코프를 만들어 동일한 결과를 반환
// 클래스형 컴포넌트라면 인스턴스에서 지속적으로 유지되는 State 를 만들 수 있음
// 그런데 아래의 코드는 instance 가 계속 만들어지니 함수형이랑 별반 다를바 없음
if(tag.prototype instanceof Component) {
const instance = new tag(makeProps(props, children));
return instance.render();
}
else {
hooks[currentComponent] = null
currentComponent++
// React 의 디자인 방식은 children 도 props 의 일부분으로 들어가게 하는 것
if(children.length > 0) {
return tag(makeProps(props, children))
} else {
// 함수를 호출하면 JSX 문법이 return 되고 그 JSX 는 createElement 함수를 호출하게 될 것
return tag(props);
}
}
} else {
// 얘가 Virtual DOM의 Input Data 역할. 이 객체의 특징은 Tree 구조라는 것
// 단일 객체를 가지고 Real DOM을 만들고 있다 (createDOM 으로)
return { tag, props, children }
}
}
// 클래스형 컴포넌트
export class Component {
// Instance 를 만들 때, 생성자가 props 를 받는다. Constructor 는 함수형 컴포넌트의 함수 역할
constructor(props) {
this.props = props;
}
}
/**
* 함수 컴포넌트 VS 클래스 컴포넌트
*
* [클래스 컴포넌트]
* 클래스 컴포넌트는 인스턴스를 만들고 컴포넌트가 삭제될 때까지 계속 유지하면서 render 함수를 호출
* 그래서 함수 컴포넌트는 상태를 가질 수 없고 클래스 컴포넌트는 상태를 가질 수 있다
*
* 실제로 instance 가 한 번 만들어지고 컴포넌트가 사라지기 전까지 React 가 모든 것을 관장하므로
* 컴포넌트가 업데이트 되거나 props 가 새로 전달되거나 컴포넌트가 삭제될 때의 타이밍을 React 가 알게 된다
* 거기에 맞는 라이프 사이클 메소드를 만들어서 호출
*
* [함수 컴포넌트]
* 그러나 이제는 함수 컴포넌트가 대세가 된다
* Hook 이라는 마법을 제공. 결과론적으로 함수 컴포넌트도 상태를 가질 수 있게 됨
* 라이프 사이클 메소드나 instance 를 만들어서 언제 마운트되고, 언제 삭제되고를 신경쓰지 않아도 됨
*/
/**
* VDOM 의 장점?
* => UI 가 업데이트될 때. render 메소드가 새로 돈다
* => VDOM 이 이미 새롭게 만들어져 있고, 그것으로 기존의 DOM 과 교체돼야 함
* => 기존의 DOM 으로 만들게 된 객체도 갖고 있고 VDOM의 입력 내용도 받고 있다
* => Real DOM 과 새롭게 Real DOM으로 만들 VDOM 둘 다 갖고 있어서 이것을 비교할 수 있게 된다
* => 두 객체를 ㅂ교해서 다른 점만 Real DOM 에 반영한다
*
* => 그 구조로 만들 수 있는 지점이 render
*/
export function basicRender(vdom, container) {
container.appendChild(createDom(vdom));
}
export const render = (function () {
// 클로저를 만듦
let prevDom = null;
return function(vdom, container) {
if(prevDom === null) {
prevDom = vdom
}
// diff 로직이 있는 후에
// real dom
container.appendChild(createDom(vdom))
}
})()
3. Hooks 알아보기
3-1) useState : 상태 관리
- React 에서 상태관리를 하기 위해서는 useState 라는 함수를 사용한다.


- 다음은 간단하게 input 창에서 입력되는 정보를 상태로 저장하는 코드다.
- input 창에 보여줄 정보는 각각의 state 를 저장하는 “ref”, “useState” 에 담았고, 이 상태가 <span> 태그로 화면에 렌더링된다. 이 코드의 결과는 다음 영상에서 확인할 수 있다.
[ 1 ] 상태 관리에 있어 데이터 단방향과 양방향의 차이
- 왼쪽이 React 로 작성한 화면, 오른쪽이 Vue 로 작성한 화면이다.
- 왼쪽에 작성한 코드는 Input 창에 아무리 입력을 많이 하더라도 State 에 변화가 없다
- 반면, 오른쪽에 작성한 코드는 바로 State 가 변경된다.
- 이 두 가지의 차이는 바로, 데이터의 흐름이 React는 단방향, Vue 는 양방향이기 때문에 발생하는 차이다.
- 이 예제에서도 알 수 있듯, 양방향에서는 한 가지 문제점이 존재하는데, HTML 의 기능으로 State 를 조작할 수 있는데 JavaScript 의 데이터가 이것을 추적하지 못할 때가 존재한다는 것이다.
- 방금, <button type=”reset”> 을 사용해서, 분명 Input 의 value 를 모두 삭제했지만, state 는 바뀌지 않았다.
- 이는, Input 의 value 와 자바스크립트의 State 가 정확히 일치하지 않음을 의미하며, 이는 사용자의 입력이 개발자의 예측을 벗어날 수 있다는 것과 개발자가 알지 못하는 에러로 번질 수 있다는 문제점으로 이어진다.
[ 2 ] 제대로 된 상태 관리 코드 작성
- 이제 우리가 원하는 방향으로 코드를 작성해보자.
- input 창에 입력을 받으면 그 때마다 State 를 변경해줘야 하고, 초기화 버튼을 누르면 그 상태가 reset 돼야 한다.
- React 에서 state 는 반드시 useState 두 번째 요소인 상태 업데이트 함수를 사용해야만 한다.


- React 에서는 일반적으로 태그에 이벤트 핸들러 함수를 넣고 싶을 때 “on + 이벤트명” props 에 원하는 함수를 넣으면 된다.
- vue 라면 “@ + 이벤트명” 으로 emits 을 받는다.
[ 3 ] 객체 상태 관리
- 그렇다면 이번에는 만약, 상태가 객체인 경우를 알아보자.

- 방금 전과는 다르게 state 가 객체 값이고, key 가 content 인 값이 input 의 value 로 들어갔다
- 현재는 객체를 mutation 하는 방식으로 수정하고 있는 상태라면 어떻게 될까.
- 결과는 재밌게도, React 는 바꿔주지 못하고 있으며, Vue 는 바뀐다. 이는 React 와 Vue 의 가상 DOM 이 동작하는 방식이 다르기 때문에 발생하는 차이다.
- React 의 가상 DOM 은 state 의 값이 바뀌었는가를, “Object.is, ===” 을 통해서 알아낸다.
- 이는 곧, 주소 값이 바뀌지 않았다면 state 가 바뀌지 않은 것으로 간주하는 것이다
- 이 방식은 Vue 의 shallowRef 의 쓰임과 거의 동일하다
- 이 방식은 React 가 최적화된 방식으로 React 의 가상 DOM 을 동작시키기 위해 일부러 정해놓은 컨벤션이다
- 만약, 요소 하나 바뀌는 것을 일일이 감지하기 위해서는 모든 값을 캐싱하거나 Proxy 로 모든 객체의 바뀌는 점을 파악해야 한다 (현재 Vue 는 이렇게 하고 있다)
- React 는 이런 방식을 좀 더 심플하고 간단하게 주소 값 만을 확인해서 변화를 감지한다.
- 이런 이유로 React 에서 객체의 상태를 바꾸기 위해서는 새로운 객체를 만들어서 값을 넣어야 한다.

코드를 이런 식으로 바꿔주면 우리가 원하는 결과를 얻을 수 있다.
[ 4 ] Snapshot 으로서의 state
- React 로 상태를 관리할 때 종종 맞이할 수 있는 에러는 React 가 state 를 Snapshot 으로 취급한다는 점에 있다.

- 위의 코드를 보면, 버튼을 클릭했을 때 바뀌는 상태는 총 두 개다.
- setCount(count + 1) 을 세 번 호출한다.
- setNewCount(count) 로 count 의 값을 newCount 에 담는다.
- 이렇게 코드를 작성했다는 것은 원하는 결과 값은
- count 값이 3으로 바뀌고
- 1초 후에, newCount 의 값이 0 으로 바뀌는 것이다.
- 그러나 결과는?
- count 는 1이 되었고, newCount 값은 0 이 되었다. 이게 왜 이렇게 동작할까?
- 상태 업데이트 함수가 Trigger 되는 이벤트 함수 블록 내에서 상태 업데이트 함수는 Batching 된다.
- 이 때, 상태 업데이트 함수가 저장된 Queue 의 상태는 다음과 같이 된다Queue 에 업데이트 된 내용 count 값 returns
count + 1 값으로 교체하기 0 0 + 1 = 1 count + 1 값으로 교체하기 0 0 + 1 = 1 count + 1 값으로 교체하기 0 0 + 1 = 1 - 상태는 Snapshot 으로 기록한다. 이 말은, 기존에 렌더링 되어 있는 상태, 즉 리렌더링이 되기 전의 상태를 Snapshot 으로 찍어서 상태 업데이트 함수에 사용한다는 말이다.
- count 값은 0 + 1 인 1 값이 되어 다음 Queue 의 상태 업데이트에 반영되는 것이 아니라, Snapshot 으로 저장된 0 이 계속해서 사용되는 것이다.
- 이는, setTimeout 을 걸어서 update 한 함수도 마찬가지다.
- setNewCount 에 업데이트 하기로 한 count 는, setNewCount 가 실행될 때의 count 값을 가져와서 사용하는 것이 아니라, Rendering 되기 이전의 state 값인 0 이 값으로 들어가는 것이다.
- 이런 상황을 막고 싶다면 다음의 방법으로 해결할 수 있다.

- setCount 안에 표현식 혹은 값을 넣는 것이 아니라 함수를 넣었다.
- 상태 업데이트 함수의 인자로 들어가는 함수는, 그 인자로 이전 Queue 에서 return 된 값을 받는다. 따라서 위의 Queue 의 상태는 다음과 다Queue 에 업데이트 된 내용 count 값 returns
count + 1 값으로 교체하기 0 0 + 1 = 1 (count) ⇒ count + 1 1 2 (count) ⇒ count + 1 2 3 - setNewCount 하는 방법은 useEffect 를 사용해서 count State 가 변경될 때 해당 count 값을 사용하도록 변경했다. 이 방식의 자세한 부분은 useEffect 에서 다룬다
3-2) useEffect : 사이드이펙트 핸들링
- “컴포넌트는 순수해야 한다”
- 즉, 컴포넌트 내부의 코드는 순수하게, 동일한 입력에 동일한 결과물을 내야 한다.
- 하지만, 항상 그렇게 순수할 수는 없다.
- 예를 들어, API 요청을 하거나 브라우저 API 등을 활용하는 것들.
- 이럴 때 useEffect 훅을 사용하면 특정 이벤트가 아닌 렌더링 자체로 인해 발생하는 사이드 이펙트를 명시할 수 있다.
- Effect 는 화면이 모두 렌더링되고 DOM 에 커밋됐을 때 실행된다
[ 1 ] useEffect 는 Vue 의 watch 와 다르다
- React 에서 Effect 는 방금 전 서론에서도 얘기했듯이 “사이드이펙트” 를 핸들링하기 위해서 사용되는 훅이다.
- 또, useEffect 는 컴포넌트가 마운트 됐을 때, 업데이트 됐을 때, 마운트 해제했을 때 (Vue 의 LifeCycle 로 표현하자면 onMounted, onUpdated, onUnMounted) 동작한다.
- watch 가 동작하는 라이프 사이클과 다르다
- useEffect 에서 작성했던 코드를 watch 로 옮길 때는 mounted, onUnMounted 의 라이프사이클을 조작해야할 필요성을 느껴야 하고
- watch 에서 작성했던 코드를 useEffect 에 옮길 때는 이게 과연 useEffect 에서 핸들링하는 것이 맞는지를 따져봐야 한다.
- 물론, 대부분의 경우 둘이 비슷하게 쓰인다.
[ 2 ] useEffect 는 컴포넌트를 외부 시스템과 “동기화” 하기 위한 Hook 이다.
- 예를 들면, 채팅방을 개설해서 들어가고자 할 때
- 외부 API 를 호출할 때 (브라우저 API 포함)
- React 없이 작성된 컴포넌트(외부에서 불러들인 컴포넌트) 를 React 컴포넌트의 state 와 일치시키고자 할 때
- 예를 들어, 네이버 지도 Map 컴포넌트가 있다고 가정해보자
- 이 지도를 단계 별로 확대하는 Zoom 버튼을 만들었다면, 그 버튼은 React 내부에서 만든 컴포넌트다
- 이 컴포넌트의 조작으로 외부 컴포넌트인 네이버 지도 Map 을
- 이 내용들을 하나로 묶으면 다음으로 정리할 수 있다.
- Data Fetching
- DOM 접근 및 조작할 때
- 외부 서비스 혹은 API 를 구독할 때
[ 3 ] 예제 확인

이전에 작성했던 코드를 보자. count 의 값을 변동시키는 onClickButtonHandler 함수를 사용하고 그 안에서 setTimeout 으로 newCount 의 값을 바꾸도록 코드를 짰다.
- 결과는 전에도 설명했듯, state 가 snapshot 으로 저장되기 때문에 렌더링되고 바뀐 후의 상태를 가져오지 않는다는 문제가 있었다.
- 또, 위의 코드에서 보면, setTimeout 을 동기적인 순서로 clear 할 수 없기 때문에 count 의 값이 바뀌면 그 횟수와 동일하게 setTimeout 함수가 동작해 해당 newCount 값이 바뀌고 있음을 확인할 수 있다.

- 그렇다면 이제 바뀐 코드로 확인해보자, 바뀐 코드에서는 count 상태가 바뀌고, DOM 에 커밋이 완료되면 Effect 내부 함수가 구현된다.
- 또, cleanUp 함수(useEffect 문이 시작하거나 컴포넌트가 unMount 됐을 때 동작하는 함수)로 기존에 setTimeout 함수를 clear 한다.
- newCount 에 들어가는 값이 우리가 원했던 것처럼 변경된 count 값이 잘 들어가고 있다. 이는 count 의 값이 Queue 의 결과물로 나온 새로운 상태 값을 DOM 커밋 이후에 찍어서 넣는 것이기 때문에 바로 반영되는 것이다.
- 또, clean 함수를 통해서 이전의 setTimeout 으로 걸어 놓은 newCount 업데이트 함수들은 다 clear 되어 사라지고, 가장 최신의 상태만을 업데이트 한다. 덕분에 경합 조건이 발생하지 않는다.
- 이 방식은 debounc 로도 구현할 수 있고, 반드시 사용자의 입력에 맞춰 화면을 렌더링 해줘야 한다는 정책에 의하면 불필요한 작업일 수는 있다.
- 하지만, 요지는 useEffect 를 활용해서 가장 이전의 상태가 아닌 DOM 에 커밋된 상태를 활용해 작업을 할 수 있으며 cleanUp 을 통해서 기존에 연결된 데이터들을 싹 없앨 수 있다는 장점이 있다는 것이다. (이 방식은 사실 ref 로 캐싱된 데이터를 clear 하는데 더욱 의미가 있다.
3-3) useRef : 렌더링을 촉발하지 않는 캐싱된 데이터
[ 1 ] ref 와 state
- ref 는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook 이다.
- 흔히, DOM 조작을 위해서 사용하기 위해 필요한 것으로 알려져 있지만 useRef 의 핵심 기능은 값이 바뀌어도 렌더링을 촉발하지 않다는 것이다.
- useRef 를 useState 를 사용해서 표현하면 다음과 같다.
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
- ref 값은 useState 의 상태와 마찬가지로 캐싱된다.
- 하지만, 상태 업데이트 함수가 리턴되지 않았으므로, 이 값을 수정하기 위해서는 직접적으로 current 에 담긴 데이터를 Mutation 해야 한다.
- 위의 state 에서도 확인했듯이, React 에서 state 값이 객체일 때, Mutation 을 해도 렌더링이 촉발되지 않은 것을 알 수 있었다.
- 즉, useRef 를 통해서 캐싱된 값은 직접 Mutation 을 통해서 수정할 수 있으며, 수정을 하더라도 렌더링을 촉발하지 않는다는 특징을 가진다.

- 위의 코드는 REF COUNT 버튼을 누르면 countRef 의 값이 하나씩 추가되도록 만들었다.
- useEffect 를 통해서, countRef 의 반응성을 감지해 변경되면 콘솔을 찍고, useMemo 를 활용해서 countRef 의 값이 바뀔 때마다 계산된 값을 캐싱해서 보여준다.
- 결과는 보는 것과 같이 Ref Count 값을 올리더라도 useEffect 에서 콘솔도 찍히지 않고, refMemo 로 계산된 값이 생성되질 않는다.
- 이는 React 에서 ref 의 값을 반응성이 있게 추적하지 못함을 의미한다.
- 하지만, state count 를 1 추가하면 REF 의 값이 갑자기 변동되는 것을 확인할 수 있다.
- 이는, ref 값이 사실 계속 변동되고 있었으며, State 가 변경되고 화면이 리렌더링 했을 때, 리렌더링될 때 메모리에 캐싱된 ref 의 값을 가져오기 때문에 발생하는 현상이다.
- 설령 이렇게 ref 의 바뀐 값이 DOM 에 반영된다 하더라도, Memo 의 값이나 Effect 의 콘솔에서는 별다른 반응이 없다. 이는, 이 두 Hook 의 동작원리는 ref 값이 변동됐을 때 계산하는데 React 가 이 ref 의 값이 변동됨을 추적하지 못했기 때문이다.
- 이런 특징은 다음과 같은 때에 유용하게 쓸 수 있다.
- timeout ID
- DOM 엘리먼트 저장 및 조작
- JSX 를 계산하는 데 필요하지 않는 다른 객체 저장
- 마치 채팅방 ID 와 같은 것.
[ 2 ] DOM 조작하기
- ref 로 DOM 을 조작하는 방법은 간단하다.
- Vue 와 동일하게 ref 를 생성한 후(초기 값은 null) 조작하고자 하는 엘리먼트에 ref 라는 props 로 생성한 ref 를 넘겨주면 된다

- 이후, ref 를 활용해서 원하는 코드를 작성하면 된다
- 이렇게 빌트인 컴포넌트, 즉 HTML 태그에 해당하는 컴포넌트들은 금방 ref 로 묶을 수 있다
- 하지만 사용자가 만든 컴포넌트는 문제가 발생한다

위의 코드처럼, MyInput 이라는 컴포넌트를 만들고 그 props 로 ref 를 넘겨주었다.
- 작성된 코드를 실행하면, 콘솔창에 에러가 뜨고, inputRef 의 값이 null 임을 확인할 수가 있다.
- 커스텀 컴포넌트에 ref 를 주입하기 위해서는 forwardRef 를 사용해야 한다

forwardRef 로 컴포넌트를 감싸면, props 다음으로 받는 두 번째 인자가 외부에서 넘겨 받을 ref 가 된다.
코드의 실행 결과, 매우 잘 작동하는 것을 확인할 수 있다.
'je개발 공유' 카테고리의 다른 글
[ je 개발 공유 ] 데이터 시각화 (입문) (0) | 2024.03.12 |
---|---|
[ je 개발 공유 ] UI/UX 이해하기 (0) | 2024.03.12 |
[ je 개발 공유 ] DB 공부 로드맵 (0) | 2024.03.12 |
[ je 개발 공유 ] 스토리북, Atomic 디자인 (0) | 2024.03.12 |
[ je 개발 공유 ] TDD (0) | 2024.03.12 |