Modern React Deep Dive 2장 리액트 핵심 요소 깊게 살펴보기

류지승·2023년 12월 30일
0

Modern React Deep Dive

목록 보기
1/2

2.1 JSX란?

JSX는 자바스크립트 표준 코드가 아닌 자바스크립트 내부에 표현하기 까다로웠던 XML 스타일의 트리 구문을 작성하는 데 많은 도움을 주는 새로운 문법이다. 자바스크립트 표준 코드가 아니기 때문에 트랜스파일러(BABEL)를 이용하여 자바스크립트 코드로 변환해야한다.

2.1.1 JSX 정의

JSX는 기본적으로 JSXElement, JSXAttributes, JSXChildren, JSXStrings라는 컴포넌트로 구성되어 있다.

JSXElement

JSX를 구성하는 가장 기본 요소로서, HTML에서 element 역할을 한다.

JSXElement

JSXOpeningElement, JSXClosingElement, JSXSelfClosingElement, JSXFragment
ex) <JSXOpeningElement> <JSXClosingElement />
<JSXSelfClosingElement JSXAttributes(optional) />
<>JSXChildren(optional)</> <- JSXFragment

React에서 HTML 구문 이외에 사용자가 컴포넌트를 만들어 사용할 때에는 HTML 태그명과 구분 짓기 위해서 반드시 대문자로 시작하는 컴포넌트를 만들어야만 사용 가능하다.

JSXElementName
JSXElementNameJSXElement의 요소 이름으로 쓸 수 있는 것을 의미한다.

  • JSXIdentifier : JSX 내부에서 사용할 수 있는 식별자를 의미한다. 자바스크립트 식별자 규칙(숫자, $ _ 외의 다른 특수문자 시작 불가)과 동일
    단, 특수문자 $, _는 React에서 안 쓰임

  • (안 쓰임) JSXNameSpacedName : JSXIdentifier:JSXIdentifier의 조합

  • (안 쓰임) JSXMemberExpression : JSXIdentifier.JSXIdentifier의 조합

JSXAttributes

JSXElement에 부여할 수 잇는 속성을 의미한다. 모든 경우에 필수 값이 아니고, 존재하지 않아도 에러가 발생하지 않는다.

JSXAttributes

  • JSXSpreadAttributes : 자바스크립트의 전개 연산자(...)와 동일한 역할
  • JSXAttribute : 속성을 나타내는 키(JSXAttributeName)값(JSXAttributeValue)로 짝을 이루어서 표현한다.
  • JSXAttributeName : 속성의 키 값 주로 JSXIdentifier이 온다.
  • JSXAttributeValue : 속성의 키에 할당할 수 있는 값
    큰따옴표("") / 작은따옴표('') / { AssignmentExpression } / JSXElement / JSXFragment

JSXChildren

JSXElement의 자식 값을 나타낸다.

JSXChildren

  • JSXChild : JSXChildren의 기본 단위 JSXChild가 없어도 상관 없다.
  • JSXText : JSXChild에 들어가는 기본적인 문자열 {} / <>JSX 문법과 혼동을 주기 때문에 따로 문자열로 묶어서 사용해야 한다.
  • JSXElement : 값으로 다른 JSX 요소가 들어갈 수 있다.
  • JSXFragment : 값으로 빈 JSX 요소가 들어갈 수 있따.
  • { JSXChildExpression (optioncal) }

JSXStrings

"큰따옴표로 구성된 문자열", '작은따옴표로 구성된 문자열', JSXText

2.1.2 JSX 예제

// 하나의 요소로 구성된 가장 단순한 형태
const ComponentA = <A>안녕하세요.</A>

// 자식이 없이 SelfClosingTag로 닫혀 있는 형태도 가능하다.
const ComponentB = <A />

// 옵션을 { }와 전개 연산자로 넣을 수 있다.
const ComponentC = <A {...{ required: true }} />

// 속성만 넣어도 가능하다.
const ComponentD = <A required />

// 속성과 속성을 넣을 수 있다.
const ComponentE = <A required={false} />

const ComponentF = (
	<A>
  		<B text="react" /> // 문자열은 큰따옴표 & 작은 따옴표 가능
    </A>
)

const ComponentG = (
	<A>
  		<B optionalChildren={<>안녕하세요</>} />
  /* 옵션의 값으로 JSXElement를 넣을 수 있다. */
  	</A>
)

const ComponentH = (
	<A>
  		안녕하세요
  		<B text="react" />
  	</A>
)

2.1.3 JSX는 어떻게 자바스크립트에서 변환될까?

2.1.4 정리

2.2 가상 DOM과 리액트 파이버

React의 가장 큰 특징 중 하나는 실제 DOM이 아닌 가상 DOM을 운영한다는 것이다. 이번에는 가상 DOM이 무엇이며, 실제 DOM에 비해 어떤 이점이 있는지 살펴보고 가상 DOM을 다룰 때 주의할 점에 대해 알아본다.

2.2.1 DOM과 브라우저 렌더링 과정

DOM(Document Object Model)은 웹페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 담고 있다.

브라우저가 웹사이트 접근 요청을 받고 화면을 그리는 과정
1. 브라우저가 사용자가 요청한 주소를 방문해 HTML 파일을 다운로드한다.
2. 브라우저의 렌더링 엔진은 HTML을 파싱(해석)해 DOM 노드로 구성된 트리(DOM)를 만든다.
3. 2번 과정에서 CSS 파일을 만나면 해당 CSS 파일도 다운로드한다.
4. 브라우저의 렌더링 엔진은 이 CSS도 파싱해 CSS 노드로 구성된 트리(CSSOM)을 만든다.
5. 브라우저는 2번에서 만든 DOM 노드를 순회하는데, 모든 노드를 순회하는 것이 아닌, 사용자 눈에 보이는 노드만 순회한다.
6. 5번에 제외된, 눈에 보이는 노드를 대상으로 해당 노드에 대한 CSSOM 정보를 찾고 CSS 정보를 노드에 적용한다.

  • 레이아웃(layout) : 각 노드가 브라우저 화면의 어느 좌표에 정확히 나타나야 하는지 계산하는 과정
  • 페인팅(painting) : 레이아웃 단계를 거친 노드에 색과 같은 실제 유효한 모습을 그리는 과정

예시

#text {
	background-color: red;
	color: white;
}
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" />
    <title>Hello React</title>
  </head>
  <body>
    <div style="width: 100%;">
      <div id="text" style="width: 50%">Hello World!</div>
    </div>
  </body>
</html>
  1. HTML을 다운로드한다. 다운로드와 함께 HTML을 분석하기 시작한다.
  2. 스타일시트가 포함된 link 태그를 발견해 style.css를 다운로드한다.
  3. body 태그 하단의 divwidth: 100%이므로 뷰포트로 좌우 100% 너비로 잡는다.
  4. 3번 하단의 divwidth: 50%, 즉 부모 너비를 50%를 너비로 잡아야 하므로 전체 영역의 50%를 너비로 잡는다.
  5. 2번에서 다운로드한 CSSid="text"에 대한 스타일 정보를 결합한다.
  6. 화면에 HTML 정보를 그리기 위한 모든 정보가 준비됐으므로 위 정보를 바탕으로 렌더링을 수행한다.

2.2.2 가상 DOM의 탄생 배경

브라우저가 웹페이지를 렌더링하는 과정은 매우 복잡하고 많은 비용이 든다. 또한 요즘 대다수의 앱은 렌더링된 이후 정보를 보여주는데 그치지 않고 사용자의 인터랙션을 통해 다양한 정보를 노출한다. 인터랙션에 따라 DOM은 계속해서 변경될 것이고, 특히 SPA(Single Page Application)에서는 더욱 더 심할 것이다. 이는 DOM을 관리하는 비용 또한 증가할 것이며 모든 인터랙션을 추적하는 것은 개발자에게 힘든 일이다.

이러한 문제점을 해결하기 위해 나온 것이 바로 가상 DOM이다. 가상 DOM은 웹페이지가 표시해야 할 DOM을 일단 메모리에 저장하고 리액트가 실제 변경에 대한 준비가 완료됐을 때 실제 브라우저의 DOM에 반영한다. 모든 렌더링 과정을 브라우저에서 하는 게 아닌 메모리에서 계산하고 최종 결과를 브라우저 DOM에 보여준다면, 렌더링 과정을 최소화하여 비용을 줄일 수 있고, 브라우저와 개발자의 부담을 덜 수 있다.

2.2.3 가상 DOM을 위한 아키텍처, 리액트 파이버

리액트 파이버(React Fiber)는 가상 DOM을 관리하고 렌더링 과정 최적화를 하는 역할을 한다.

리액트 파이버란?

가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하며, USER에 의한 인터랙션으로 두 DOM이 차이가 생기면, 파이버를 기준으로 화면에 렌더링을 요청하는 역할을 한다.

리액트 파이버의 역할

  • 작업을 작은 단위로 분할하고 쪼갠 다음, 우선순위를 매긴다.
  • 이러한 작업을 일시 중지하고, 나중에 다시 시작할 수 있다.
  • 이전에 했던 작업을 다시 재사용하거나 필요하지 않은 경우에는 폐기할 수 있다.

자바스크립트의 특징인 싱글 스레드로 인한 진행과정을 동기적(스택 알고리즘)으로 처리하기에는 너무 비효율적이여서, 리액트 파이버는 비동기적으로 처리해 사용자 인터랙션에 따른 동시 다발적인 이벤트와 애니메이션을 효율적으로 처리하고 있다.

파이버는 state가 변경되거나 생명 주기 메서드가 실행되거나 DOM의 변경이 필요한 시점 등에 실행된다.
파이버 동작 과정은 생략함. 사실 이해하기 너무 난해함

리액트 파이버 트리

리액트 파이버 트리는 현재 DOM 모습을 담은 파이버 트리, 작업 중인 상태를 나타내는 workInProgress 트리다. 리액트 파이버 작업이 끝나면 workInProgress 트리를 포인터만 변경하여 현재 DOM을 담은 파이버 트리로 변경한다. 이 과정을 더블 버퍼링이라 한다.

리액트는 작업이 완료되지 않은 상태를 DOM로 노출시키지 않기 위해 더블 버퍼링 기법을 사용한다.

파이버의 작업 순서

// JSX
<A1>
  <B1>안녕하세요</B1>
  <B2>
    <C1>
      <D1 />
      <D2 />
    </C1>
  </B2>
  <B3 />
</A1>

파이버 생성 흐름
1. A1의 beginWork()가 수행된다.
2. A1은 자식이 있으므로 B1로 이동해 beginWork()를 수행한다.
3. B1은 자식이 없으므로 completeWork()가 수행됐다. 자식은 없으므로 형제(sibling)인 B2로 넘어간다.
4. B2의 beginWork()가 수행된다. 자식이 있으므로 C1로 이동한다.
5. C1의 beginWork()가 수행된다. 자식이 있으므로 D1로 이동한다.
6. D1의 beginWork()가 수행되고, 자식이 없으므로 형제(sibling)인 D2로 넘어간다.
7. D2의 beginWork()가 수행되고, 자식이 없으므로 completeWork()가 수행됐다.
8. D2는 자식도 형제도 없으므로, 위로 이동해 C1, B2 순으로 completeWork()를 호출한다.
9. B2는 형제(sibling)인 B3으로 이동해 beginWork()를 수행한다.
10. B3의 completeWork()가 수행되면 반환해 상위로 타고 올라간다.
11. A1의 completeWork()가 수행된다.
12. 루트 노드가 완성되면 commitWork()가 수행되고 이 중 변경 사항을 비교해 업데이트가 필요한 변경사항이 DOM에 반영된다.

setState로 인한 업데이트가 발생하면 어떻게 될까? 이미 리액트에는 앞서 만든 current 트리가 존재하고 setState로 인한 업데이트 요청을 받아 workInProgress 트리를 다시 빌드하기 시작한다. 최초 렌더링 시에는 모든 파이버를 새롭게 만들어야 했지만, 이미 파이버가 존재하므로 새로 생성하지 않고 기존의 파이버에서 업데이트된 props를 받아 파이버 내부에서 처리한다.

새로운 파이버를 만드는 것은 리소스 낭비라고 볼 수 있다. 따라서 기존의 파이버 객체를 재활용하여 내부 속성값만 초기화하거나 바꾸는 형태로 트리를 업데이트한다.

2.2.4 파이버와 가상 DOM

리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍쳐 내부에서 비동기로 이뤄진다. 하지만 실제 DOM에 반영하는 것은 동기적으로 일어나야하고, 처리하는 작업이 많아 SPA를 활용하는 React에서는 화면이 불완전하게 보일 수 있다. 따라서, 이러한 과정 및 작업을 가상(Memory)상에서 먼저 수행하여 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것이다.

2.2.5 정리

가상 DOM과 파이버는 단순히 브라우저에 DOM을 변경하는 작업보다 빠르다는 이유로 만들어진 게 아니고, 개발자가 좀 더 편하게 DOM을 관리하기 위해서 리액트에 적용했다. 개발자가 아닌 리액트 내부 알고리즘에 의해 관리해줌으로써 조금 더 효율적으로 유지보수할 수 있게 되었다.

2.3 클래스형 컴포넌트와 함수형 컴포넌트

이번에는 클래스형 컴포넌트와 함수형 컴포넌트의 비교에 대해서 공부한다. 그리고 왜 함수형 컴포넌트가 클래스형 컴포넌트보다 각광받고 있는지 알아보고, 장단점에 대해 알아보자.

2.3.1 클래스형 컴포넌트

import React from 'react'

class sampleComponent extends React.Component {
	render() {
    	return <h2>Sample Component</h2>
    }
}

클래스형 컴포넌트는 클래스를 선언하고 extends로 만들고 싶은 컴포넌트를 extends 해야한다.

extends 구문에 넣을 수 있는 클래스

  • React.Component
  • React.PureComponent
import React from 'react'

// props 타입을 선언한다.
interface SampleProps {
	required?: boolean;
    text: string;
}

// state 타입을 선언한다.
interface SampleState {
	count: number;
    isLimited?: boolean;
}

// Component에 제네릭으로 props, state를 순서대로 넣어준다.
class SampleComponent extends React.Component<SampleProps, SampleState> {
	// consturctor에서 props를 넘겨주고, state의 기본값을 설정한다.
	private constructor(props: SampleProps) {
    	super(props);
        this.state = {
        	count: 0,
            isLimited: false
       	}
    };
    // render 내부에서 쓰일 함수를 선언한다.
    private handleClick = () => {
    	const newValue = this.state.count + 1;
        this.setState({count: newValue, isLimited: newValue >= 10})
    }
   	// render에서 이 컴포넌트가 렌더링할 내용을 정의한다.
    public render() {
    	// props와 state 값을 this, 즉 해당 클래스에서 꺼낸다.
        const {
        	props: { required, text },
            state: { count, isLimited },
        } = this

	return (
		<h2>
    		Sample Component
    		<div>{required ? '필수' : '필수 아님'}</div>
    		<div>문자: {text}</div>
    		<div>count: {count}</div>
    		<button onclick={this.handClick} disabled={isLimited}>증가</button>
        </h2>
    )
  }
}

코드 해석

  • consturctor( ) : 컴포넌트 내부에 이 생성자 함수가 있다면 컴포넌트가 초기화되는 시점에 호출된다. 내부에서 컴포넌트의 state를 초기화할 수 있다. super( )는 상위 컴포넌트에 접근할 수 있게 도와주는 역할을 한다.
  • props: 함수에 인수를 넣는 것과 비슷하게, 컴포넌트에 특정 속성을 전달하는 용도로 쓰인다. props는 { required?: boolean; text: string; } 형태이다.
  • state: 클래스형 컴포넌트 내부에서 관리하는 값을 의미한다. 이 값은 항상 객체여야만 한다. state 변경 시 리렌더링이 발생한다.
  • 메서드: 렌더링 함수 내부에서 사용되는 함수이며, 보통 DOM에서 발생하는 이벤트와 함께 사용된다. 주로 화살표 함수를 사용해서 Event를 처리한다. 일반 함수 / 화살표 함수 / 함수 내부에서 함수 총 세 가지 방법을 이용하여 메서드를 만드는데, 일반 함수는 직접 this를 바인딩해야 한다는 단점이 있고, 함수 내부에 함수를 만드는 방법은 렌더링 될 때마다, 새로운 함수를 생성하여 할당해야 하므로, 최적화 측면에서 비효율적이다. 따라서, 주로 화살표 함수를 이용하여 메서드를 만든다.

클래스형 컴포넌트의 생명주기
클래스형 컴포넌트를 사용하면서 가장 자주 언급되는 것이 생명 주기(Life Cycle)이다.
> 생명주기 메서드가 실행되는 시점
- 마운트(mount) : 컴포넌트가 마운팅(생성)되는 시점
- 업데이트(update) : 이미 생성된 컴포넌트의 내용이 변경(업데이트)되는 시점
- 언마운트(unmount) : 컴포넌트가 더 이상 존재하지 않는 시점
render()

진짜 고민을 많이 해봤는데 지금 react 공부할 시간도 없는데 굳이 요새 잘 안 쓰이는 클래스형 컴포넌트 기반 react를 공부할 이유가 있나 싶어서 빠르게 포기하고 클래스형 컴포넌트가 어떻게 구성하고 왜 react에서는 클래스형 컴포넌트가 아닌 함수형 컴포넌트로 변경했는 지만 알고 넘어가자.

2.3.2 함수형 컴포넌트

import { useState } from 'react'

interface SampleProp {
	required?: boolean;
    text: string;
}

export function SampleComponent({ required, text }: SampleProps) {
	// < >는 Generic을 이용하기 위해서 사용된 건데, Velog에서는 읽지를 못하니
    // < />으로 처리했습니다.
	const [count, setCount] = useState<number />(0);
    const [isLimited, setIsLimited] = useState<boolean />(false);

    function handleClick(): void {
    	const newValue: number = count + 1;
        setCount(newValue);
        setIsLimited(newValue >= 10);
    }


return ( 
	<h2>
  		Sample Component
  		<div>{ required ? 필수 : 필수아님 }</div>
  		<div>문자: {text}</div>
  		<div>count: {count}</div>
  		<button onClick={handleClick} disabled={isLimited} />
  	</h2>
  )
}

클래스형 컴포넌트와 비교하면 함수형 컴포넌트는 확연히 코드가 간결하다는 사실을 알 수 있다. 또한 render 내부에서 this 바인딩을 조심할 필요가 없으며, state는 객체가 아닌 각각의 원시갑으로 관리되어 훨씬 사용하기가 편하다. 렌더링하는 코드인 return 또한 굳이 this를 사용하지 않아도, props와 state를 사용할 수 있다.

2.3.3 함수형 컴포넌트 vs 클래스형 컴포넌트

함수형 컴포넌트가 각광 받기 시작한 이유는 생명 주기 관리 담당인 react hook과 this 바인딩을 사용하지 않아도 되기 때문이다. 두 형태의 컴포넌트를 비교하고 차이점에 대해서 알아보자

생명주기 메서드의 부재

클래스형 컴포넌트의 생명주기 메서드가 함수형 컴포넌트에는 존재하지 않는다. 함수형 컴포넌트는 props를 받아 단순히 리액트 요소만 반환하는 함수인 반면 클래스형 컴포넌트는 render 메서드가 있는 React.Component를 상속 받아 구현하는 자바스크립트 클래스이기 때문이다. 함수형 컴포넌트 또한 useEffect 훅을 사용하여 생명주기 메서드를 비슷하게 구현할 수 있다. useEffect 컴포넌트의 state를 활용해 동기적으로 부수 효과를 만드는 메커니즘이다.

함수형 컴포넌트와 렌더링된 값

함수형 컴포넌트는 렌더링된 값을 고정하고, 클래스형 컴포넌트는 그렇지 못한다.

import React from 'react'

interface Props {
	user: string;
}

// 함수형 컴포넌트로 구현한 setTimeout 예제
export function FunctionalComponent(props: Props) {
	const showMessage = () => {
    	alert('hello '+ props.user);
    };

    const handleClick = () => {
    	setTimeout(showMessage, 3000);
    };

    return <button onClick={handleClick}>Follow</button>
}

export class ClassComponent extends React.Component<Props, {}> {
	private showMessage = () => {
    	alert('hello ' + this.props.user);
    }

    private handleClick = () => {
    	setTimeOut(this.showMessage, 3000);
    }

    public render() {
    	return <button onClick={handleClick}>Follow</button>
    }
)

위 코드는 handleClick을 클릭하면, props에 있는 useralert를 이용하여 띄어준다. 함수형 컴포넌트는 클릭했던 시점의 props의 값이고, 개발자가 의도한 것과 다르게 클래스형 컴포넌트는 클릭한 후 3초 뒤에 props의 값이다.
함수형 컴포넌트는 렌더링이 일어날 때마다 그 순간의 값이 props와 state를 기준으로 렌더링되고, 클래스형 컴포넌트는 시간의 흐름에 따라 변화하는 this를 기준으로 렌더링이 일어난다.

2.4 렌더링은 어떻게 일어나는가?

FrontEnd에서 rendering이라는 용어는 브라우저에서의 렌더링을 많이 생각할 것이다. 브라우저에서의 렌더링은 HTM & CSS 리소스를 기반으로 웹페이지에 필요한 UI를 그리는 과정을 의미한다. 리액트에도 렌더링이라는 과정이 존재하는데, 브라우저가 렌더링에 필요한 DOM 트리를 만드는 과정을 의미한다.

2.4.1 리액트의 렌더링이란?

리액트에서의 렌더링이란 리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 현재 자신들이 가지고 있는 props와 state의 값을 기반으로 어떻게 UI를 구성하고 이를 바탕으로 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 일련의 과정을 의미한다.

2.4.2 리액트의 렌더링이 일어나는 이유

리액트의 렌더링이 발생하는 시나리오
1. 최초 렌더링 : 사용자가 처음 애플리케이션에 진입하면 당연히 렌더링해야 할 결과물이 필요하다. 리액트는 브라우저에게 정보를 제공하기 위해 최초 렌더링을 수행한다.
2. 리렌더링 : 최초 렌더링 이후 발생하는 모든 렌더링을 의미한다.
클래스형 컴포넌트에서의 리렌더링은 생략

  • 함수형 컴포넌트의 useState()의 두 번째 배열 요소인 setter가 실행되는 경우, state를 업데이트 하는 함수
  • 함수형 컴포넌트의 useReducer()의 두 번째 배열 요소인 dispatch가 실행되는 경우
  • 컴포넌트의 key props가 변경되는 경우 : 리액트에서 key는 명시적으로 선언돼 있지 않더라도 모든 컴포넌트에서 사용할 수 있는 특수한 props이다.
  • props가 변경되는 경우 : 부모로부터 전달받은 값인 props가 달라지면 이를 사용하는 자식 컴포넌트에서도 변경이 필요하므로 리렌더링이 일어난다.
  • 부모 컴포넌트가 렌더링 경우 : 당연한 얘기지만 부모 컴포넌트가 리렌더링된다면 자식 컴포넌트는 당연히 리렌더링이 일어난다.

변수가 아무리 변경되었다고 해서 리렌더링이 발생하지 않으면 변경된 값을 렌더링된 DOM에서 확인할 수 없다.

2.4.3 리액트의 렌더링 프로세스

렌더링 프로세스가 시작되면 리액트는 컴포넌트의 루트에서부터 차근차근 아래쪽으로 내려가면서 업데이트가 필요하다고 지정돼 있는 모든 컴포넌트를 찾는다. 그 이후 렌더링 결과물을 수집한 다음, 리액트의 새로운 트리인 가상 DOM과 비교해 실제 DOM에 반영하기 위한 모든 변경 사항을 차례차례 수집한다.
리액트의 렌더링은 렌더 단계와 커밋 단계라는 총 두 단계로 분리되어 실행된다는 것이다.

2.4.4 렌더와 커밋

렌더 단계는 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업을 말한다. 렌더링 프로세스에서 컴포넌트를 실행해 이 결과와 이전 가상 DOM을 비교하는 과정을 거쳐 변경이 필요한 컴포넌트를 체크하는 단계이다.
다음 커밋 단계는 렌더 단계의 변경 사항을 실제 DOM에 적용해 사용자에게 보여주는 과정이다.
리액트는 리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니다. 렌더 단계에서 변경 사항이 감지되지 않는다면, 커밋 단계는 생략될 수 있다.
리액트의 렌더링은 항상 동기식으로 작동했다. 렌더링 프로세스 특징을 자세히 본다면 동기식으로 진행하는 것이 맞다. 순서가 보장되지 않는 비동기식으로 렌더링을 처리한다면 User에게 혼란을 야기할 수 있다.

2.4.5 일반적인 렌더링 시나리오 살펴보기

import { useState } from 'react'

export default function A() {
	return (
    	<div className="App">
  			<h1>Hello React!</h1>
  			<B />
  		</div>
    );
}

function B() {
	const [counter, setCounter] = useState(0);

    function handleButtonClick() {
    	setCounter((prev) => prev + 1)
    }

    return (
    	<>
        	<label>
  				<C number={counter} />
  			</label>
        	<button onClick={handleButtonClick}>+</button>
        </>
    );
}

function C({ number }) {
	return (
    	<div>
  			{number} <D />
  		</div>
    );
}

function D() {
	return <>리액트 재밌다.</>
}
profile
성실(誠實)한 사람만이 목표를 성실(成實)한다

0개의 댓글