-
[React] 18버전의 추가된 새로운 기능 (step- 1)구버전/React 2024. 2. 17. 19:59
📋 개요
- useId
- startTransition, useTransition
- useDefferedValue
- useInesrtoinEffect
✅ useId
최상위 수준에서 호출되어 고유 ID를 생성하는 React Hook으로, 접근성 속성에 전달될 수 있다.
- key 는 데이터 식별을 위해 사용되므로 데이터에서 생성되는 것이 더 바람직하며, 너무 많은 호출이 일어나기 때문이다.
📦 사용하는 이유)
- 하드코딩의 횟수를 줄이자
- 컴포넌트를 여러번 사용하더라도 id 속성이 겹치지 않는다.
더보기예시)
사용자의 이름 정보를 받기 위해 input을 컴포넌트로 받아서 id값을 name이라고 하였다. 이때 이 컴포넌트를 반복해서 사용하게 된다면 id가 name인 input이 여러 개 생기게 되는데 이는 좋은 상황이 아니다. 그렇기에 이러한 id 속성의 중복을 막을 수 있다. 또한 id를 설정을 할 때 일일히 하드 코딩으로 구현하는 것은 바람직하지 않다고 react-ko에서 말한다.
📦 사용 예시)
- input을 반복해서 많이 사용하는 경우에 사용할 수 있으며 회원가입이나 로그인 상황에서 에러문구를 띄울 때 유용하게 사용될 것이다.
더보기import { useId } from 'react'; function InputField() { const id = useId(); return ( <> <label> ID: <input type="text" aria-describedby={id} /> </label> <p id={id}> The ID should contain at least 18 characters </p> </> ); }
✅ starTransition, useTransition 🔗
isPending과 startTransition 두개의 항목이 있는 배열을 반환하는 훅으로 UI를 차단하지 않고 상태를 업데이트 할 수 있다.
- startTransition()을 통해 우선순위가 낮은 상태 업데이트들을 transition이라고 표기해 우선순위를 정해준다. (transition이라고 적히면 낮은 우선순위)
- 낮은 우선순위를 가진 상태는 다른 상태 업데이트가 호출되면 중단된다.
📦 사용하는 이유)
- 느린기기에서도 사용자 인터페이스 업데이트를 반응성 있게 유지할 수 있다.
- UI 랜더링 시 우선순위에 따라 업데이트를 할 수 있다.
- Suspense와 연계하여 불필요한 로딩 인디케이터(Spinner와 같은 것)의 노출을 막을 수 있어 UX를 개선시킨다.
더보기예시)
느린 기기에서 사용자가 탭을 클릭했다가 마음이 바뀌어 다른 탭을 클릭하게 되었을 때 첫 번째 리렌더링이 완료될 때까지 기다린 이후 다음 클릭할 수 있었을텐데 Hook의 사용을 통해서 기다릴 필요 없이 다른 탭을 클리갛ㄹ 수 있게 되었다.
📦 사용 예시)
- 느린 사용자 기기에서 탭의 전환 시에 사용하면 유용하다.
더보기const App = () => { const [tab, setTab] = useState('about'); return ( <> {/* 탭을 클릭하면 렌더링할 탭 컴포넌트가 설정된다 */} <TabButton isActive={tab === 'about'} onClick={() => setTab('about')}> About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => setTab('posts')}> Posts (slow) </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => setTab('contact')}> Contact </TabButton> <hr /> {/* 현재 탭에 따라 탭 컴포넌트가 렌더링 된다 */} {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </> ); }; const TabButton = ({ children, isActive, onClick }) => { const [isPending, startTransition] = useTransition(); // 현재 탭이 활성화 되면 isActive 상태가 된다. if (isActive) { return <b>{children}</b>; } // 대기 중인 transition이 있다면 isPending이 된다. if (isPending) { return <b className="pending">{children}</b>; } /** * props로 받은 onClick 함수를 startTransition으로 감싸주기 때문에 * onClick 함수(setTab)은 transition으로 설정되어 렌더링시 우선순위에서 밀리게 된다. * 그 결과 오랜시간이 걸리는 PostsTab 컴포넌트를 렌더링 하는 도중 다른 탭을 누르게 되면 * PostsTab 컴포넌트의 렌더링을 멈추고 다른 컴포넌트를 렌더링하게 된다. **/ const handleButtonClick = () => { startTransition(() => { onClick(); }); }; return <button onClick={handleButtonClick}>{children}</button>; }; const AboutTab = () => { return <p>Welcome to my profile!</p>; }; const PostsTab = () => { const startTime = performance.now(); while (performance.now() - startTime < 1) { // 1 ms 동안 아무것도 하지 않음으로써 매우 느린 코드를 실행한다. } return <p>PostsTab</p>; }; const ContactTab = () => { return <p>ContactTab</p>; }; const ContactTab = () => { return <p>ContactTab</p>; };
📦 주의 사항)
1️⃣ startTransition
- 동기 함수여야 한다.
- transition으로 표시된 setState는 다른 setState 업데이트 시 중단된다.
- 다른 상태 업데이트가 있을 경우 우선 순위에서 밀린다.
- 텍스트 입력을 제어하는데 사용할 수 없다.
- input에 사용하게 되면 사용자의 입력이 즉각적으로 반영이 되지 않는 경우도 생기기에 좋지 못한 선택이다.
✅ useDefferedValue 🔗
UI 일부의 업데이트를 지연시킬 수 있는 React Hook이다.
- 지연시키려는 값을 매개변수로 받으며, 초기 랜더링 시에는 매개변수로 제공한 값과 동일하며, 업데이트가 발생하면 백그라운드에서 새 값으로 리랜더링을 시도하기 전까지 이전 값을 반환하여 UX를 개선시킨다.
📦 사용하는 이유)
- Debounce와 비슷한 효과를 낼 수 있으며, 그보다 뛰어난 성능을 보인다.
- Suspense와 통합되어 새 값으로 인한 백그라운드 업데이트로 UI가 일시 중단되면 fallback이 표시되지 않고 기존의 값이 유지되어 UX에 좋다.
📦 사용 예시)
- 검색을 통해서 리스트를 가져오는 상황에서 다른 검색동안 이전 값을 유지하는데 유용하게 사용될 수 있다.(Debouncing + a).
더보기import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
📦 주의 사항)
- 전달하는 값은 원시값이거나 컴포넌트의 외부에서 생성된 객체여야 한다.
- 랜더링 중에 생성되는 객체(컴포넌트 안에서 생성되는 객체)를 생성하고 즉시 전달하면 렌더링 때마다 값이 달라져 불필요한 백그라운드 리렌더링이 발생할 수 있다.
- 사용하는 자체로 추가 네트워크 요청을 방지하지 않기에 주의해야 한다.
- Effect의 실행은 useDefferedValue로 인해 백그라운드 리랜더링이 완료되고 화면에 커밋되어야 실행된다.
✅ useSyncExternalStore 🔗
외부 스토어를 구독할 수 있는 React Hook 즉, 외부 스토어(external store)와 싱크(sync)를 맞추는 훅(use)이다.
- concurrnet 렌더링이 등장하면서 렌더링이 렌더링을 일시중지할 수 있게되었는데 이 일시 중지가 발생하는 문제로 인해 UI에 동일한 데이터가 다른 값을 표시하는 경우가 생겨났고 이러한 문제를 해결하는 방법이다.
- Redux와 MobX와 같은 외부 상태 저장소와 React와의 동기화를 쉽고 안정적으로 수행할 수 있도록 한다.
- 옵저버 패턴을 이용하며 컴포넌트가 store를 구독하게 하여 store가 바뀔 때마다 컴포넌트를 리렌더링하게 할 수 있다.
📦 사용하는 이유)
- concurrent Mode의 문제점을 지속적인 외부 상태와 React 컴포넌트 사이의 상태 동기화를 통해 해결해 줄 수 있다.
더보기예시)
리액트 뿐만아니라 다양한 라이브러리 및 프레임워크가 사용된 프로젝트가 있다. 이때 여기서 사용하고 있는 스토어(외부 라이브러리)와 리액트에 상태를 통합해서 사용하기 위해서 사용하며, 주로 기존의 비 React 코드와 통합을 할 때 유용하다.
📦 사용 예시)
- 필수로 2개의 매개변수와 선택적으로 1개의 매개변수가 있다.
- subscribe의 경우 함수 내에서 구독과 구독취소를 모두 선언해주어야 한다.
- getSnapshot는 스토어 데이터의 스냅샷을 반환하는 함수로 저장소가 변경되어 반환값이 달라지면 컴포넌트를 리랜더링 한다.
- getServerSnapshot은 데이터의 초기 스냅샷을 반환하는 함수로 hydrate하는 동안에만 사용된다.
더보기// App.js import { useSyncExternalStore } from 'react'; import { todosStore } from './todoStore.js'; export default function TodosApp() { const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); return ( <> <button onClick={() => todosStore.addTodo()}>Add todo</button> <hr /> <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); } // todoStore.js let nextId = 0; let todos = [{ id: nextId++, text: 'Todo #1' }]; let listeners = []; export const todosStore = { addTodo() { todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }] emitChange(); }, subscribe(listener) { listeners = [...listeners, listener]; return () => { listeners = listeners.filter(l => l !== listener); }; }, getSnapshot() { return todos; } }; function emitChange() { for (let listener of listeners) { listener(); } }
📦 주의 사항)
- snapshot을 캐시해야한다는 오류가 발생할 수 있다.
- 이는 함수가 호출될 때마다 새 객체를 반환한다는 의미이며, getSnapshot 함수는 반환값이 지난번과 다르면 컴포넌트를 리랜더링하기에 실제 변경 사항이 있을 때에만 다른 객체를 반환해야 한다.
- subscribe 함수는 컴포넌트 안에 만들면 안된다.
- subscribe 함수는 실제 변화가 있을 시에만 새롭게 전달되야한다. 그렇지 않으면 구독과 구독취소를 반복해서 일으켜 무한루프에 빠질 수 있다.
✅ useInsertionEffect
useEffect의 버전 중 하나로 DOM 변이 전에 실행된다.
- Effect 로직이 포함되어 컴포넌트가 DOM에 추가되기 전에 setup 함수를 실시하고 제거되기 전에 cleanup 함수를 실행한다.
- undefined를 반환한다.
- 동적으로 스타일을 삽입하는 경우에 사용된다.
📦 사용하는 이유)
1️⃣ CSS-in-JS 라이브러리에서 동적 스타일 삽입하기
일반적으로 사용되는 접근 방식은 3가지가 있다.
1. 컴파일러 사용해서 CSS 파일로 정적 추출
2. 인라인 스타일로 적용 ex) <div style={{ opacity: 1 }}>
3. 런타임에서 <style> 태그 삽입 (권장되지 않음, 스타일 계산 빈도가 훨씬 증가하며, 잘못된 시점에 발생하면 속도 저하의 원인)
이 중 useInsertionEffect는 런타임에 스타일을 주입해야 하는 경우 2번째 문제를 해결하며 사용할 수 있는 방법이다.2️⃣ 일반적으로 대부분의 개발자는 필요로하지 않는다.
- CSS-in-JS 라이브러리와 같은 제작자를 위해 설계됨
📦 주의 사항)
- 서버 랜더링 중에 실행되지 않고 클라이언트에서만 실행된다.
- 사용이 매우 한정적이므로 필요한지 여부를 잘 확인하고 사용해야 한다.
'구버전 > React' 카테고리의 다른 글
[React] ErrorBuondary의 사용방법 (0) 2024.05.21 [React] useLayoutEffect의 활용 (0) 2024.04.02 [React] 고차 컴포넌트 (HOC, High Order Component) (0) 2024.01.20 [Vite] 절대경로 설정 (0) 2024.01.03 [React] useState의 동작원리 (0) 2023.12.16