모던 리액트 deep dive 스터디 - 10장 발표 정리
주제: 10장 리액트 17과 18의 변경 사항 살펴보기 (655p ~ 714p)
리액트 17
특징 : 16 버전과 비교하면 새롭게 추가된 기능은 없으며 호환성이 깨지는 변경 사항, 즉 기존에 사용하던 코드의 수정을 필요로 하는 변경 사항을 최소화하였다.
16 버전에서 17 버전으로의 업그레이드는 큰 부담 없이 할 수 있다.
리액트 17부터는 점진적인 업그레이드가 가능하다.
*일부 트리와 컴포넌트에 대해서만 리액트 18을 선택하는 점전적인 버전 업이 가능
이전버전까지는 이전 버전에 머무르던 새로운 버전으로 완전히 넘어가버리던 둘 중 정했어야함.
관리측면에서는 비효율이지만, 새로운 버전으로 한꺼번에 업그레이드하기 부담스러울 때는 유용하다
이벤트 위임 방식의 변경
useEffect(() => {
if (buttonRef.current) {
buttonRef.current.onclick = function click() { alert("안녕하세요!")
}
}
}, [])
const handleClick = () => {
}
<button onClick={handleClick}>리액트 버튼</button>
<button ref={buttonRef}>그냥 버튼 </button>
'리액트 버튼'은 DOM에 이벤트를 추가하는 방식으로 onclick 이벤트 사용
- 버튼의 onClick 이벤트에 noop이라는 핸들러가 추가
'그냥 버튼'은 직접 DOM을 참조해서 DOM의 onclick에 직접 합수를 추가
- 해당 버튼의 이벤트 리스너에 click으로 추가
리액트는 이벤트 핸들러를 해당 이벤트 핸들러를 추가한 각각의 DOM 요소에 부탁하는 것이 아니라, 이벤트 타입당 하나의 핸들러를 루트에 부착한다. (이벤트 위임, 이벤트 핸들러를 각 요소가 아닌 document에 연결)
이벤트 위임은 다음의 이벤트 단계 원리를 활용해 이벤트를 상위 컴포넌트에만 붙이는 것을 의미한다.
1. 캡처: 이벤트 핸들러가 트리 최상단 요소에서부터 시작해서 실제 이벤트가 발생한 타깃 요소까지 내려가는 것
2. 타깃: 이벤트 핸들러가 타깃 노드에 도달하는 단계. 이 단계에서 이벤트 호출
3. 버블링: 이벤트가 발생한 요소에서부터 시작해 최상위 요소까지 다시 올라간다.
리액트 16 버전까지는 이벤트 위임이 모두 document에서 수행
=> 리액트 17부터는 이벤트 위임이 모두 document가 아닌 리액트 컴포넌트 최상단 트리, 즉 루트 요소로 변경
변경된 이유는 뭘까?
1. 점진적 업그레이드 지원을 용이하게 하기 위함
- 이전에는 React를 점진적으로 업그레이드하는 것이 어려웠다. React 16까지는 이벤트가 document에 위임되었기 때문에, 같은 페이지에서 React의 다른 버전을 사용하는 경우 이벤트 시스템이 충돌할 가능성이 있다.
- React 17에서는 여러 React 버전의 애플리케이션이 같은 페이지에 존재하더라도, 각 애플리케이션은 자신의 루트 컨테이너에서만 이벤트를 관리한다.
- document에 전역 이벤트 리스너를 추가하지 않기 때문에, 서로 다른 버전의 React가 충돌할 일이 없다.
2. 기존 DOM 이벤트와의 충돌 방지
- document에 이벤트 리스너를 등록하면 React 외부의 코드(다른 바닐라 자바스크립트 코드 또는 jQuery)와 충돌하거나 예상치 못한 버그가 발생할 수 있다. React 17에서 이벤트 위임을 루트 컨테이너로 이동하면, React 외부 코드와의 충돌 가능성이 줄어든다.
루트 요소로 변경됨에 따라 각 이벤트는 해당 리액트 컴포넌트 트리 수준으로 격리되므로 이벤트 버블링으로 인한 혼선을 방지할 수 있다.
import React from 'react'
17 버전부터는 import React from 'react' 코드 없이도 JSX를 변환할 수 있게 됐다.
useEffect 클린업 함수의 비동기 실행
16버전까지는 useEffect의 클린업 함수는 동기적으로 처리됐다.
17버전부터는 화면이 완전히 업데이트된 이후에 클린업 함수가 비동기적으로 실행된다.
(컴포넌트의 커밋 단계가 완료될 때까지 지연되어 화면이 업데이트가 완전히 끝난 이후에 실행된다.)
컴포넌트의 undefined 반환에 대한 일괄적인 처리
16버전 - forwardRef나 memo에서 undefined 반환하여도 에러 X
17버전 - 에러 발생
18버전 - 16버전과 동일
리액트 18
useId
컴포넌트 내부의 고유한 값 생성
Math.random()을 사용할 시 클라이언트와 서버의 불일치시 발생하는 하이드레이션 에러에서도 자유로움 (동일한 값 생성)
앞 글자가 R이면 서버에서 생성된 값, r이면 클라이언트에서 생성된 값
useTransition
UI 변경을 가로막지 않고 상태를 업데이트할 수 있는 리액트 훅
상태 업데이트를 긴급하지 않은 것으로 간주해 무거운 렌더링 작업을 조금 미룰 수 있다.
const [isPending, startTransition] = useTransition();
isPending : 상태 업데이트 진행 중인지 확인 가능한 boolean
startTransition : 긴급하지 않은 상태 업데이트로 간주할 set 함수를 담을 수 있다.
리액트 18의 변경 사항의 핵심 중 하나인 '동시성'을 다룰 수 있는 새로운 훅.
- startTransition 내부는 반드시 setState와 같은 상태를 업데이트하는 함수와 관련된 작업만 넘길 수 있다.
- startTransition으로 넘겨주는 함수는 반드시 동기 함수여야 한다.
useDeferredValue
리액트 컴포넌트 트리에서 리렌더링이 급하지 않은 부분을 지연할 수 있게 도와주는 훅
useTransition은 state 값을 업데이트하는 함수를 감싸서 사용하지만, useDeferredValue는 state 값 자체만을 감싸서 사용한다.
useSyncExternalStore
useTransition, useDeferredValue의 훅처럼 렌더링을 일시 중지하거나 뒤로 미루는 작업들이 생기면서
나타난 테어링 현상을 해결할 수 있는 훅이다.
*테어링 현상이란, 리액트에서 하나의 state 값이 있음에도 서로 다른 값을 기준으로 렌더링 되는 현상
useInsertionEffect
css-in-js 라이브러리를 위한 훅
useLayoutEffect와 동일하게 DOM이 렌더링되기 전에 실행
다만, useLayoutEffect는 모든 DOM의 변경 작업이 다 끝난 이후에 실행되는 반면
useInsertionEffect는 DOM 변경 작업 이전에 실행
실제 애플리케이션 코드에는 가급적 사용하지 않는 것이 좋다.
react-dom/client
createRoot
before
ReactDOM.render(<App />, container)
리액트 18
const root = ReactDOM.createRoot(container)
root.render(<App />)
hydrateRoot
서버 사이드 렌더링 애플리케이션에서 하이드레이션을 하기 위한 새로운 메서드
const root = ReactDOM.hydrateRoot(container, <App/>)
react-dom/server
renderToPipeableStream
리액트 컴포넌트를 HTML로 렌더링하는 메서드,
HTML을 점진적으로 렌더링하고 클라이언트에서는 중간에 script를 삽입하는 등의 작업이 가능하다.
renderToReadableStream
renderToPipeableStream가 Node.js 환경에서 렌더링을 위해 사용되었다면, renderToReadableStream은 웹 스트림을 기반으로 작동한다.
자동 배치
리액트가 여러 상태 업데이트를 하나의 리렌더링으로 묶어서 성능을 향상시키는 방법
17버전까지는 Promise, setTimeout 같은 비동기 이벤트에서는 자동 배치가 이뤄지지 않았는데
18버전부터 루트 컴포넌트를 createRoot를 사용해서 만들면 동기와 비동기 배치 작업 모두 일관성있게 동작한다.
더욱 엄격해진 엄격 모드
엄격 모드가 하는 일들
- 더 이상 안전하지 않은 특정 생명주기를 사용하는 컴포넌트에 대해 경고 (ex. componentWillMount, componentWillReeceiveProsp...)
- 문자열 ref 사용 금지
- findDOMNode에 대해 경고 출력 (클래스형 컴포넌트 인스턴스에서 실제 DOM 요소에 대한 참조를 가져올 수 있음)
- 구 Context API 사용 시 경고 발생 (ex. childContextTypes, getChildContext...)
- 컴포넌트가 항상 순수한 결과물을 내는지 확인하기위해 두번씩 실행
- 클래스형 컴포넌트의 constructor, render, shouldComponentUpdate, getDerivedStateFromProps
- 클래스형 컴포넌트의 setState의 첫 번째 인수
- 함수형 컴포넌트의 body
- useState, useMemo, useReducer에 전달되는 함수
리액트 18에서 추가된 내용
- useEffect의 이중 호출
향후 리액트에서는 컴포넌트가 마운트 해제된 상태에서도 컴포넌트 내부의 상태값을 유지할 수 있는 기능을 제공할 예정
Suspense 기능 강화
18 버전 이전의 Suspense 문제점
- 마운트 되기 직전의 컴포넌트의 useEffect가 실행되는 문제
- 서버에서는 사용할 수 없어서 클라이언트에서만 수행되었어야함.
18 버전부터는 위 문제점들이 해결됨.
인터넷 익스플로러 지원 중단에 따른 추가 폴리필 필요
Promise, Symbol, Object.assign
위 세 기능을 지원하지 않는 브라우저에서 서비스하는 경우 위 기능들을 위한 폴리필을 반드시 추가해야한다.
그렇지 않다면 정상적으로 작동하지 않을 수도 있다.