CS/프레임워크&라이브러리

React v18 - 1

H.E 2024. 2. 17. 20:04

[ React ]

▶useId

const id = useId()

 

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

이는 주로 고유한 'id'가 필요한 접근성 API와 사용되는 컴포넌트에 유용할 것으로 기대됨

고유 ID를 생성하려면 구성 요소의 최상위 수준에서 호출해야함

 

굳이 전역 변수를 증가시키는 것보다 useId를 권장하는 이유는 클라이언트 구성 요소가 hydration(수화)되는 순서가 서버 HTML이 생성된 순서와 일치하지 않을 수 있으므로 증가하는 카운터로 이를 보장하는 것은 매우 어려움

useId를 호출하면 하이드레이션이 작동하고 출력이 서버와 클라이언트 간에 일치하는지 확인할 수 있음

 

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  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 />
      <h2>Confirm password</h2>
      <PasswordField />
    </>
  );
}

PasswordField이제 화면에 여러 번 나타나 더라도 생성된 ID가 충돌하지 않음

 

Hydration

https://blog.hwahae.co.kr/all/tech/13604

일반적인 React를 활용한 애플리케이션의 경우 body가 비어있는 html을 다운받고 CSR 방식으로 UI를 그림

 

https://blog.hwahae.co.kr/all/tech/13604

반면 Next.js는 기본적으로 모든 페이지를 미리 렌더링(pre-render)함

생성된 각 HTML은 해당 페이지에 필요한 최소한의 JS 코드와 연결되며, 브라우저가 페이지를 로드하면 JS 코드가 완전 인터렉티브하게 만들어지고 이 과정을 hydration이라고 부름

 

사전렌더링으로 SEO나 초기 로딩 성능 향상 등의 장점을 취할 수 있는 사전 렌더링은 next.js에서 두가지 방식을 제공함

  • Static Generation(SG)
    • 사전 렌더링 결과를 빌드 타임에 HTML로 생성
    • Incremental Static Regeneration(ISR) 방식을 활용할 경우에는 배포한 이후에도 HTML을 생성하거나 업데이트 할수 있으며 이때도 사전렌더링
  • Server Side Rendering(SSR)
    • 페이지를 요청하면 HTML을 생성하고 hydration함

Hydration mismatch

react를 통해 렌더링 될 최초의 결과물이 hydration 할 html과 반드시 일치해야함

react에서는 hydration할 html과 react 렌더 트리의 불일치에 따른 hydration mismatch 에러 메세지를 발생시킴

서비스가 멈춘다거나 하는 문제가 발생하지는 않지만 공식문서에서는 반드시 고쳐야 하는 오류로 보고 이를 없애라고 권장

 

▶ startTransition, useTransition

이 두 메소드를 사용하면 일부 상태 업데이트를 긴급하지 않은 것 (not urgent)로 표시할 수 있음

이것으로 표시되지 않은 상태 업데이트는 긴급한 것으로 간주됨

긴급한 상태 업데이트(input text 등)가 긴급하지 않은 상태 업데이트(검색 결과 목록 렌더링)를 중단할 수 있음

상태 업데이트를 긴급한 것과 긴급하지 않은 것으로 나누어 개발자에게 렌더링 성능을 튜닝하는데 많은 자유를 주었다고 볼 수 있음

 

function TabContainer() {
  const [isPending, startTransition] = useTransition({ timeoutMs: 3000 });
  const [tab, setTab] = useState('about');

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

 

useTransition정확히 두 개의 항목이 포함된 배열을 반환( timeoutMs는 옵션으로 최대 얼마간 이전 화면을 유지할것인지)

  1. isPending보류 중인 전환이 있는지 여부를 알려주는 플래그
    • 진행 여부로 트랜지션이 진행중인지 알수 있음
  2. 상태 업데이트를 전환으로 표시할 수 있는 기능 startTransition
    • 함수로 리액트에 어떤 상태변화를 지연하고 싶은지 지정할 수 있음

 

 useDeferredValue

useDeferredValue를 사용하면 트리에서 급하지 않은 부분의 재렌더링을 지연할 수 있음

 

이는 debounce와 비슷하지만, 몇가지 더 장점이 있음

  • 고정된 지연시간이 없으므로, 리액트는 첫번째 렌더링이 반영되는 즉시 지연 렌더링을 시도함
  • 지연된 렌더링은 인터럽트가 가능하며, 사용자 입력을 차단하지 않음
import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
}
import { useDeferredValue } from 'react'

const deferredValue = useDeferredValue(value, {
  timeoutMs: 5000,
})

value는 연기하려는 값으로 어떤 유형이든 가질 수 있음

useDefferredValue에 옵션으로 timeoutMs를 주어 최대 얼마간 지연될것인지 지정해줄 수 있음

 

▶ useSyncExternalStore△

useSyncExternalStore는 스토어에 대한 업데이트를 강제로 동기화 하여 외부 스토어가 concurrent read를 지원할 수 있도록 하는 새로운 훅

외부 데이터에 대한 원본에 대한 subscription을 필요로 할 때 더이상 useEffect가 필요하지 않고, 이는 리액트 외부 상태와 통합되는 모든 라이브러리에 권장됨

 

import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';

function TodosApp() {
  const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
  // ...
}

저장소에 있는 데이터의 스냅샷을 반환함. 두 가지 함수를 인수로 전달해야 함

  1. subscribe 함수는 스토어를 구독하고 구독을 취소하는 함수를 반환해야 함
  2. getSnapshot 함수는 저장소에서 데이터의 스냅샷을 읽어야 함

 

▶ useInsertionEffect(setup, dependencies?)

useInsertionEffect는 css-in-js 라이브러리가 렌더링 도중에 스타일을 삽입할 때 성능 문제를 해결할 수 있는 새로운 훅

css-in-js 라이브러리를 사용하지 않는다면 사용할 필요가 없음

이 훅은 dom이 한번 mutate된 이후에 실행되지만 layout effect가 일어나기전에 새 레이아웃을 한번 읽음

이는 리액트 17 이하 버전에 있는 문제를 해결할 수 있으며, 리액트 18에서는 나아가 concurrent 렌더링 중에 브라우저에 리액트가 값을 반환하므로, 레이아웃을 한번더 계산할 수 있는 기회가 생겨 매우 중요함

 

useLayoutEffect와 비슷한데, 차이가 있다면 DOM 노드에 대한 참조에 엑세스 할 수 있다는 것

클라이언트 사이드에서 <style> 태그를 생성해서 삽입할 때는 성능 이슈에 대해 민감하게 살펴보아야 함

CSS 규칙을 추가하고 삭제한다면 이미 존재하는 모든 노드에 새로운 규칙을 적용하는 것

이는 최적의 방법이 아니므로 많은 문제가 존재함

이를 피할 수 있는 방법은 타이밍. 리액트가 DOM을 변환한경우, 레이아웃에서 무언가를 읽기전(clientWidth와 같이) 또는 페인트를 위해 브라우저에 값을 전달하기 전에 DOM에 대한 다른 변경과 동일한 타이밍에 작업을 하면 됨

useLayoutEffect와 마찬가지로 서버에서 실행되지는 않음

 

function useCSS(rule) {
  useInsertionEffect(() => {
    if (!isInserted.has(rule)) {
      isInserted.add(rule)
      document.head.appendChild(getStyleForRule(rule))
    }
  })
  return rule
}
function Component() {
  let className = useCSS(rule)
  return <div className={className} />
}

 

매개변수

  • setup: Effect의 로직이 포함된 기능
    설정 함수는 선택적으로 정리 함수를 반환할 수도 있음. 구성 요소가 DOM에 추가되었지만 레이아웃 효과가 실행되기 전에 React는 설정 기능을 실행. 변경된 종속성을 사용하여 다시 렌더링할 때마다 React는 먼저 이전 값을 사용하여 정리 기능(제공한 경우)을 실행한 다음 새 값을 사용하여 설정 기능을 실행함. 구성 요소가 DOM에서 제거되면 React는 정리 기능을 실행함
  • 선택 사항 dependencies : 코드 내부에서 참조되는 모든 반응 값의 목록
    setup. 반응형 값에는 props, state 및 구성 요소 본체 내부에 직접 선언된 모든 변수와 함수가 포함됨. Linter가 React용으로 구성된 경우 모든 반응 값이 종속성으로 올바르게 지정되었는지 확인함. 종속성 목록은 항목 수가 일정해야 하며 처럼 인라인으로 작성되어야 함. 종속성을 전혀 지정하지 않으면 구성 요소를 다시 렌더링할 때마다 Effect가 다시 실행됨