티스토리 뷰

1. React18에서 업데이트 된 기능

ㄱ. Automatic Batching

Batching이란 리액트가 더 나은 성능을 위해 여러 개의 상태 업데이트를 한 번의 리렌더링(re-render)으로 묶는 작업.
이전의 React에서는 React Event Handler만이 state 업데이트를 Batching 처리했다.
18 버전 이후로는 React Event Handler뿐만 아니라 promise, setTimeout, native event handler 등 다양한 로직에서도 Batching 작업이 가능해졌다.

// Before: only React events were batched.
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will render twice, once for each state update (no batching)
}, 1000);

// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}, 1000);

setTimeout, promise, native event handler 등에서 발생하는 모든 state 업데이트는, React 에서 발생하는 이벤트 내부의 업데이트와 동일한 방식으로 state 업데이트 들을 Batching 하여 여러 번 수행 했어야 했던 랜더링 과정을 단 한 번만 처리할 수 있게 해줬고, 이는 리랜더링 횟수를 최소화하여, 애플리케이션의 성능 향상을 기대할 수 있게 되었습니다.

 

+ native event handler가 뭘까?

더보기

SyntheticEvent와 nativeEvent

리액트에서 이벤트가 발생할 시, 이벤트 핸들러는 SyntheticEvent의 인스턴스를 전달한다.

즉 리액트로 개발 시 우리는 native event가 아니라 래핑된 이벤트(SyntheticEvent)를 사용 하게 되는데, 이것은 우리가 흔히 사용하는 stopPropagation  preventDefault 를 포함하여 브라우저의 기본 이벤트(nativeEvent)와 동일한 인터페이스를 가진다.

브라우저의 고유 이벤트가 필요하다면 nativeEvent 어트리뷰트를 참조해야 한다. → evt.nativeEvent.stopImmediatePropagation() 이렇게 접근한다.

 

stopPropagation은 오직 동일한 SyntheticEvent(동일한 큐)에 속한 dispatch 들의 실행만 방지한다. 즉 같은 큐에 속한 dispatch 들의 실행만 방지하는 것. evt.stopPropagation() 으로도 막을 수 없는 이벤트들이 있다. 플러그 인(큐)의 종류에 관계없이 동작하는 것이 필요 했기 때문에 nativeEvent 에 접근하는 것을 남겨둔 것이 아닐까 추측된다고 한다.

 

https://medium.com/tapjoykorea/%EB%A6%AC%EC%95%A1%ED%8A%B8-react-%EC%9D%98-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%9F%AC-event-handler-syntheticevent-nativeevent-3a0da35e9e3f

 

리액트(React)의 이벤트 핸들링 내부 살펴보기

<EventPlugin & EventPluginHub> 부터 <SyntheticEvent & nativeEvent> 까지

medium.com

https://ko.reactjs.org/docs/events.html

 

합성 이벤트(SyntheticEvent) – React

A JavaScript library for building user interfaces

ko.reactjs.org

https://velog.io/@pks787/TIL-43%EC%9D%BC%EC%B0%A8-SyntheticEvent%EC%99%80-nativeEvent

 

(TIL 43일차) SyntheticEvent와 nativeEvent

이벤트 핸들러 SyntheticEvent 와 nativeEvent

velog.io

 

ㄴ. Transitions

Transitions 긴급 업데이트와 긴급하지 않은 업데이트를 구분하기 위한 React의 새로운 개념
이전까지는 상태 업데이트를 긴급과 전환 업데이트로 명시하는 방법이 없었다. 모든 상태는 긴급 업데이트로 적용하기 때문에, setTimeout이나 throttle, debounce 등의 테크닉을 사용해 긴급 업데이트 방해를 우회하는 것이 최선이었다.
하지만, React 18부터는 startTransitionAPI를 제공함으로써 전환 업데이트를 명시적으로 구분하여 상태 업데이트를 진행할 수 있게 되었다.

import {startTransition} from 'react';

// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input);
});

startTransition으로 포장된 업데이트는 긴급하지 않은 것으로 처리되며 클릭이나 키 누르기와 같은 긴급 업데이트가 더 많이 들어오면 중단됩니다. 사용자가 한 줄에 여러 문자를 입력하여 전환을 중단하면 React는 완료되지 않은 오래된 렌더링 작업을 삭제하고 최신 업데이트만 렌더링합니다.

 

- useTransition: a hook to start transitions, including a value to track the pending state.
- startTransition: a method to start transitions when the hook cannot be used.

ㄷ.  New Suspense Features

Suspense 를 사용하면 아직 표시할 준비가 되지 않은 구성 요소 트리의 일부에 대해 로딩 상태를 선언적으로 지정할 수 있습니다.

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

18 버전부터는 응답이 오래걸리는 컴포넌트는 <Suspense>를 적용하여 초기 렌더링 속도를 개선할 수 있습니다.

 

+ 더 알아보기

더보기

ㄹ. New Client and Server Rendering APIs

1) React DOM Client

These new APIs are now exported from react-dom/client:

createRoot: New method to create a root to render or unmount. Use it instead of ReactDOM.render. New features in React 18 don’t work without it.
hydrateRoot: New method to hydrate a server rendered application. Use it instead of ReactDOM.hydrate in conjunction with the new React DOM Server APIs. New features in React 18 don’t work without it.

createRoot : 렌더링 또는 언마운트할 루트를 만드는 새로운 메소드

hydrateRoot : 서버사이드 렌더링 애플리케이션에서 hydrate하기 위한 새로운 메소드

2) React DOM Server

These new APIs are now exported from react-dom/server and have full support for streaming Suspense on the server:

renderToPipeableStream: for streaming in Node environments.
renderToReadableStream: for modern edge runtime environments, such as Deno and Cloudflare workers.

The existing renderToString method keeps working but is discouraged.

renderToPipableStream

node 환경에서 스트리밍 지원

  • <Suspense>와 함께 사용 가능
  • 콘텐츠가 잠시 사라지는 문제없이 'lazy'와 함께 코드 스플리팅 가능
  • 지연된 콘텐츠 블록이 있는 HTML 스트리밍이 나중에 뜰 수 있음

renderToReadableStream

Cloudflare, deno와 같이 모던 엣지 런타임 환경에서 스트리밍 지원

'renderToString'는 여전히 존재하지만, 사용하는 것이 권장되지는 않는다.

ㅁ. New Strict Mode Behaviors

React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.

2. React18에서 추가된 hook

ㄱ. useId

const id = useId()

클라이언트와 서버간의 hydration의 mismatch를 피하면서 유니크 아이디를 생성할 수 있는 새로운 훅.

주로 고유한 ID를 요구하는 접근성 API와 통합되는 컴포넌트에 유용할 것이다. React18에서 새로운 스트리밍 렌더러가 HTML을 순서에 어긋나지 않게 전달해 줄 수 있다.

 

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  console.log('Generated identifier:', passwordHintId)
  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <h2>Choose password</h2>
      <PasswordField />
    </>
  );
}

ㄴ. useTransition + startTransition

  const [isPending, startTransition] = useTransition();

useTransition 과 startTransition 를 사용하면 일부 상태 업데이트를 긴급하지 않은 것(not urgent)로 표시할 수 있다. 긴급 상태 업데이트(예: 텍스트 입력 업데이트)가 긴급하지 않은 상태 업데이트(예: 검색 결과 목록 렌더링)를 중단할 수 있습니다.

import { useState, useTransition } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';

export default function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);      
    });
  }

  return (
    <>
      <TabButton
        isActive={tab === 'about'}
        onClick={() => selectTab('about')}
      >
        About
      </TabButton>
      <TabButton
        isActive={tab === 'posts'}
        onClick={() => selectTab('posts')}
      >
        Posts (slow)
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => selectTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}

공식문서에서 The difference between useTransition and regular state updates 를 따라해봤다. Posts를 누르고 Contact를 누를때 지연없이 즉시 표시된다. 이 상태 업데이트는 전환으로 표시되기 때문에 느린 다시 렌더링으로 사용자 인터페이스가 정지되지 않았다.

ㄷ. useDeferredValue

const deferredValue = useDeferredValue(value)

useDeferredValue로 트리의 긴급하지 않은 부분을 다시 렌더링하는 것을 지연시킬 수 있음. debouncing보다 장점이 많다. 고정된 시간 지연은 없으므로 React는 첫 번째 렌더링이 화면에 반영된 직후 지연된 렌더링을 시도합니다. 지연된 렌더링은 인터럽트 가능하며 사용자 입력을 차단하지 않습니다.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );

ㄹ. useSyncExternalStore

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

스토어에 대한 업데이트를 강제로 동기화하여 external store 외부 스토어가 concurrent read를 지원할 수 있도록하는 새로운 훅. 외부 데이터 소스에 대한 구독을 구현할 때 useEffect가 필요하지 않으며 React 외부의 상태와 통합되는 모든 라이브러리에 권장됩니다.

import { useOnlineStatus } from './useOnlineStatus.js';

function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}

function SaveButton() {
  const isOnline = useOnlineStatus();

  function handleSaveClick() {
    console.log('✅ Progress saved');
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? 'Save progress' : 'Reconnecting...'}
    </button>
  );
}

export default function App() {
  return (
    <>
      <SaveButton />
      <StatusBar />
    </>
  );
}
  • 'External Store' : 외부 스토어라는 것은 우리가 subscribe하는 무언가를 의미한다. 예를 들어 리덕스 스토어, 글로벌 변수, dom 상태 등이 될 수 있다.
  • 'Internal Store' : 'props', 'context', 'useState', 'useReducer' 등 리액트가 관리하는 상태를 의미한다.
  • 'Tearing' : 시각적인 비일치를 의미한다. 예를 들어, 하나의 상태에 대해 UI가 여러 상태로 보여지고 있는(=각 컴포넌트 별로 업데이트 속도가 달라서 발생하는) UI가 찢어진 상태를 말한다.

리액트 18부터 도입된 concurrent 렌더링이 등장하며서, 렌더링이 렌더링을 잠시 일시중지할 수 있게 됐다. 일시중지가 발생하는 사이에 업데이트는 렌더링에 사용되는 데이터와 이와 관련된 변경사항을 가져올 수 있어 UI는 동일한 데이터에 다른 값을 표시할 수 있게 되버렸다. 처음에는 리액트 팀에서 useMutableSource라는 훅을 만들어 안전하게 외부의 mutable한 소스를 읽어왔다. 그러나 개발을 시작하면서 API에 결함이 있다는 것을 알게 되었고 useMutableSource는 사용이 어려워 졌다. 많은 논의 끝에, useMutableSource는 useExternalStore로 변경되었다. useExternalStore리액트 18에서 스토어 내 데이터를 올바르게 가져올 수 있도록 도와준다.

ㅁ. useInsertionEffect

useInsertionEffet(setup, dependencies?)

CSS-in-JS 라이브러리가 렌더링 스타일을 주입하는 성능 문제를 해결할 수 있게 해주는 새로운 훅. (css-in-js 라이브러리를 사용하지 않는다면 사용할 필요없음) DOM이 한번 mutate된 이후에 실행되지만, layout effect가 일어나기 전에 새 레이아웃을 한 번 읽는다. 리액트 17 이하 버전에 있는 문제를 해결할 수 있으며, 리액트 18에서는 나아가 concurrent 렌더링 중에 브라우저에 리액트가 값을 반환하므로, 레이아웃을 한번 더 계산할 수 있는 기회가 생겨 더욱 중요하다.

 

// Inside your CSS-in-JS library
let isInserted = new Set();
function useCSS(rule) {
  useInsertionEffect(() => {
    // As explained earlier, we don't recommend runtime injection of <style> tags.
    // But if you have to do it, then it's important to do in useInsertionEffect.
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
}

function Button() {
  const className = useCSS('...');
  return <div className={className} />;
}

useEffect와 마찬가지로 useInsertionEffect는 서버에서 실행되지 않습니다.

+ useLayoutEffect에서 레이아웃을 읽기 전에 <style> 또는 SVG <defs>와 같은 전역 DOM 노드를 삽입하는 데 사용

 

https://react.dev/blog/2022/03/29/react-v18

 

React v18.0 – React

The library for web and native user interfaces

react.dev

https://tech.osci.kr/2022/05/03/react-18v/

 

React 18 버전의 실상을 파헤치다. - 오픈소스컨설팅 테크블로그

안녕하세요. Playce Dev팀 강동희 입니다. 22년 4월 React 의 버전이 18버전으로 메이저 업그레이드 되었습니다. 버전이 올라가면서 무엇이 어떻게 변화 했는지 집중적으로 살펴봅시다!

tech.osci.kr

https://yceffort.kr/2022/04/react-18-changelog

 

Home

yceffort

yceffort.kr

https://velog.io/@woodong/React-18-%EC%A3%BC%EC%9A%94-%EB%B3%80%EA%B2%BD%EC%A0%90

 

React 18 주요 변경점

React-18v 부터 상태 업데이트(setState)를 하나로 통합해서 배치처리를 한 후 리렌더링을 진행합니다.리렌더링 관련 성능 개선과거 React-17v 에서는 이벤트 핸들러 내부에서 발생하는 상태 업데이트

velog.io

https://itchallenger.tistory.com/653

 

[3분 리액트] React18의 useInsertionEffect 훅에 대해 알아보자

원문 : https://blog.saeloun.com/2022/06/02/react-18-useinsertioneffect Know about the useInsertionEffect hook in React 18 Ruby on Rails and ReactJS consulting company. We also build mobile applications using React Native blog.saeloun.com TLDR : useInsert

itchallenger.tistory.com