스터디의 두 번째 주제로 정한 hook은 use였다.
이번 주는 갓생 살기로 다짐해서 나름 열심히 공부해 봤다.
1. Use
const value = use(resource);
Promise나 Context와 같은 데이터를 참조하는 hook.
다른 React hook과 달리 use는 조건문과 반복문 내부에서 호출이 가능하다.
다만, 컴포넌트 또는 훅 안에서만 호출해야 한다.
- resource : 참조 데이터. Promise나 Context일 수 있다.
- Promise나 Context에서 참조한 값을 반환한다.
1) Context 참조
import { createContext, useState } from "react";
import ThemeDisplay from "../components/ThemeDisplay";
export const ThemeContext = createContext("light");
const Use = () => {
const [theme, setTheme] = useState("light");
return (
<div>
<h1>Use</h1>
<div>
<button
onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}
>
<span>테마 변경</span>
</button>
</div>
<ThemeContext.Provider value={theme}>
<ThemeDisplay />
</ThemeContext.Provider>
</div>
);
};
export default Use;
우선 ThemeContext를 만든 후, 해당 context를 사용할 컴포넌트를 감싸준다.
import { use } from "react";
import { ThemeContext } from "../pages/Use";
const ThemeDisplay = () => {
// use로 Context 참조
const theme = use(ThemeContext);
return (
<div>
<p>현재 테마 : {theme}</p>
</div>
);
};
export default ThemeDisplay;
그리고 이렇게 use를 통해 가져와서 사용할 수 있다.
use를 통해서 Context를 참조할 수 있다는거에 큰 장점을 느끼진 못했다.
기존에도 useContext를 통해 충분히 참조할 수 있었으니까.
function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}
근데 위 예시처럼 if문이나 반복문에서 사용 가능하다는 점이 편리할 수 있을 것 같다.
context 값에 따라서 원하는 컴포넌트를 렌더링 해야 하는 경우라면, 충분히 활용 가능 할 것 같다는 생각이 들었다.
2) Promise 참조
react에 있는 기본 예시를 따라해봤는데 뭔가 특별한 게 없는 느낌이었다.
도대체 뭐가 바뀐지 모르겠어서 예시를 만든 후, 만약 use가 없었다면을 가정하고 실습해 봤다.
① 단순 데이터 페칭
우선 단순한 데이터 페칭을 예시로 들어보자.
아래 예시는 버튼을 클릭하면 일정 시간이 지난 후, 메시지를 출력해 준다.
// 기존
import { useEffect, useState } from "react";
import { fetchMessage } from "./UsePromise";
const MessageContainer = ({ messagePromise }) => {
const [message, setMessage] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
messagePromise.then((result) => {
setMessage(result);
setLoading(false);
});
}, [messagePromise]);
if (loading) {
return <p>⌛Downloading message...</p>;
}
return <p>Here is the message: {message}</p>;
};
const UsePrePromise = () => {
const [messagePromise, setMessagePromise] = useState(null);
const [show, setShow] = useState(false);
function download() {
setMessagePromise(fetchMessage());
setShow(true);
}
if (show) {
return <MessageContainer messagePromise={messagePromise} />;
} else {
return <button onClick={download}>Download message</button>;
}
};
export default UsePrePromise;
원래라면 useEffect를 사용해서 api를 호출한 값을 받아오고,
그 후에 setMessage, setLoading처럼 state를 업데이트해서 구현해야 했다.
// use
import { useState } from "react";
import { use, Suspense } from "react";
const Message = ({ messagePromise }) => {
// use로 Promise 참조
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
};
const MessageContainer = ({ messagePromise }) => {
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
};
const UsePromise = () => {
const [messagePromise, setMessagePromise] = useState(null);
const [show, setShow] = useState(false);
function download() {
setMessagePromise(fetchMessage());
setShow(true);
}
if (show) {
return <MessageContainer messagePromise={messagePromise} />;
} else {
return <button onClick={download}>Download message</button>;
}
};
export default UsePromise;
하지만 Use를 사용하면, state와 useEffect 없이 use 만으로 구현이 가능하다.
또한, Suspense를 사용할 수 있어 더 깔끔하게 코드를 짤 수 있다.
그러나 위 기능 같은 경우, 이미 useQuery로도 구현이 가능하다.
특별한 뭔가가 생겼다기 보단 외부 라이브러리를 사용하지 않고 hook만으로 구현 가능하다는 것에 초점을 둬야 할 것 같다.
* Suspense 컴포넌트
use를 공부하면서 Suspense를 처음 알게 되었다.
공부한 김에 알아두면 좋을 거 같아서 기록한다.
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<SlowComponent />
</Suspense>
);
}
Suspense는 데이터 로딩이나 컴포넌트가 준비되는 동안 대체 UI를 보여줄 수 있게 해주는 리액트 기능이다.
fallback prop에 로딩 중 보여줄 UI를 지정하면, 기다리는 동안 해당 UI가 출력된다.
const Message = ({ messagePromise }) => {
// use로 Promise 참조
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
};
// Suspense는 반드시 promise 바깥에서 감싸야 함
const MessageContainer = ({ messagePromise }) => {
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
};
Suspense와 use를 같이 사용할 때 주의해야 할 점은,
Suspense를 use를 사용하는 컴포넌트 바깥에서 사용해야 한다는 것이다.
컴포넌트가 Promise를 반환하면,
React가 이를 감지해 상위로 올라가며 가장 가까운 Suspense 바운더리를 찾아 fallback UI를 렌더링 한다.
그리고 Promise가 resolve 되면 React는 원래의 컴포넌트를 다시 렌더링 한다.
function MessageContainer() {
// Promise가 pending 상태일 때
const messageContent = use(messagePromise);
// ⬆️ 여기서 React는 "아직 데이터가 준비되지 않았다"는 것을 감지하고
// 이 시점에서 컴포넌트 실행을 바로 멈춥니다
// 아래 코드는 실행되지 않음
return (
<Suspense /> // 실행 X
<div /> // 실행 X
);
}
만일 use와 Suspense가 같은 컴포넌트에 있다고 가정해 보자.
use의 Promise 값이 peding 상태라면 리액트는 컴포넌트 실행을 중단한다.
그리고 상위 컴포넌트에서 Suspense 바운더리를 찾게 된다.
따라서 해당 컴포넌트 안에 있는 Suspense는 아예 실행되지 않는다.
실습을 해보다가 위 경우엔 Suspense가 제대로 실행되지 않길래 찾아봤더니 위와 같은 이유가 있었다.
use의 경우 Suspnese와 함께 작동하기 위해 Promise 값이 peding이면 컴포넌트 실행이 자체적으로 중단된단다!
같이 쓸 수 있으면 좋을 것 같은데, 괜히 컴포넌트 두 개 만들기 같기도 하고.. 쪼개서 좋은 거 같기도 하고..
② 서버 컴포넌트와의 통신
//messageContainer
"use client";
import { use, Suspense } from "react";
const Message = ({ messagePromise }) => {
// use로 Promise 참조
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
};
const MessageContainer = ({ messagePromise }) => {
return (
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
};
export default MessageContainer;
// api
const fetchMessage = () => {
return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️"));
};
두 번째로 use를 사용하면 Promise를 서버 컴포넌트에서 클라이언트 컴포넌트로 전달이 가능하다.
export default async function App() {
const messageContent = await fetchMessage(); //렌더링 차단
return <Message messageContent={messageContent} />
}
하지만 위처럼 원래 await을 사용하여 데이터를 클라이언트 컴포넌트에 Prop으로 전달할 수 있었다.
그래서 Promise를 자체를 전달하는 게 왜 장점이지?라는 생각이 들었다.
그래서 클로드의 도움을 조금 받았다.
await을 사용하면 서버에서 데이터 fetch가 완료될 때까지 렌더링을 차단한다.
따라서 초기 로딩 시간이 길어질 수 있다.
하지만 Promise로 전달하게 되면, 서버 렌더링이 차단되지 않는다.
또한 Suspense를 통해 로딩 상태 처리를 좀 더 쉽게 할 수 있고, 초기 페이지 로드도 빠르다.
*주의할 점
1) 서버 컴포넌트에서 데이터를 가져올 땐 async await을 사용해야 한다.
// async/await
async function ServerComponent() {
console.log('1. 렌더링 시작');
console.log('2. await 호출 - 렌더링 중단');
const data = await fetchData();
console.log('3. 데이터 수신 - 렌더링 재개');
return <div>{data}</div>;
}
1. 렌더링 시작
2. await 호출 - 렌더링 중단
(2초 대기)
3. 데이터 수신 - 렌더링 재개
// use
function ServerComponent() {
console.log('1. 렌더링 시작');
console.log('2. use 호출 - Suspense로 전환');
const data = use(fetchData());
console.log('3. 데이터 수신 - 새로 렌더링');
return <div>{data}</div>;
}
1. 렌더링 시작
2. use 호출 - Suspense로 전환
(2초 대기)
1. 렌더링 시작
2. use 호출 - Suspense로 전환
3. 데이터 수신 - 새로 렌더링
use도 서버 컴포넌트에서 사용할 수 있지만, 효율성을 위해 async await을 사용하는 것이 좋다.
async await은 await 호출 시점에서 렌더링을 시작하지만,
use는 데이터 수신 후, 처음부터 다시 렌더링 한다.
use : 렌더링 시도 > use(Promsie)에서 pending 상태, reject 발생 > Suspense 찾음 > resolve 시 컴포넌트 다시 렌더링
2) Promise 생성은 서버 컴포넌트에서 하는 것이 좋다.
위 예시와 마찬가지로 use는 데이터를 다 받아오면 처음부터 다시 렌더링 하게 된다.
클라이언트 컴포넌트에서 생성된 Promise는 렌더링 할 때마다 다시 생성되기 때문에
Promise를 서버 컴포넌트에서 생성해서 클라이언트 컴포넌트로 전달하는 것이 좋다.
3) use는 try-catch 블록에서 호출할 수 없다.
"use client";
import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
export function MessageContainer({ messagePromise }) {
return (
<ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
<Suspense fallback={<p>⌛Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</ErrorBoundary>
);
}
function Message({ messagePromise }) {
const content = use(messagePromise);
return <p>Here is the message: {content}</p>;
}
use는 try-catch 대신 컴포넌트를 Error Boundary로 래핑 하거나,
Promise의 catch 메서드를 사용하여 대체 값을 제공해야 한다.
try {
const data = use(promise); // Promise reject 발생
} catch (error) {
// 여기서 에러를 잡아버림 -> Suspense가 동작할 수 없음
}
use는 Promise reject를 발생시키고, 이를 Suspense가 잠아내는 방식으로 동작하기 때문에
try catch를 사용하면 위 예시처럼 Suspense가 동작할 수 없게 된다.
따라서 에러바운더리 컴포넌트를 사용하는 것이 좋다.
리액트 use 공식문서 url : https://ko.react.dev/reference/react/use#streaming-data-from-server-to-client
use – React
The library for web and native user interfaces
ko.react.dev
이렇게 react19 use에 대해 공부를 해봤다.
뭔가에 집중하면서, 그 원인까지 찾아가며 공부한 건 진짜 오랜만이었던 거 같다.
그리고 이걸 다른 누군가와 대화하면서 고민하는 것도 처음 경험해 보는 것이었다.
생각이 더 깊어지고, 다양한 의견을 나눌 수 있어서 좋았다.
결론 : 스터디하길 잘했음^^
솔직히 처음엔 하기도 싫었고, 아침에 일어나서 공부하는 것도 적응 안 됐는데
이젠 출근 전 아침에 50분 정도는 공부하는 게 점차 익숙해졌다.
이번 연도 목표가 아침, 저녁 공부 습관 들이는 것이었는데 첫 시작이 좋은 거 같다.
다음 스터디도 열심히 해봐야지,,!
다음 스터디 때에는 내거만 집중하지 말고 다른 사람것도 열심히 공부하는게 목표다!!
'front > react' 카테고리의 다른 글
React 19 톺아보기 - useTrasition (0) | 2025.02.02 |
---|---|
[React] React Hooks (0) | 2024.10.09 |
[React] VirtualDOM (2) | 2024.10.02 |