항해 플러스 부트캠프가 완료되고, 항해 사람들끼리 비대면 스터디를 진행하기로 했다.
주제는 React19 톺아보기로 한 달 동안 진행하기로 했다!
신규 메서드 혹은 hook을 살펴보고,
실무에 도입할만한 메서드가 있는지, 기존 코드를 어떻게 효율적으로 개선할 수 있을지 고민하는 것이 목표이다.
첫 주자차에는 hook을 살펴보며 어떤 변화가 있었는지 이야기하는 시간을 가졌다.
대체로 "굳이 왜 사용해야 하는지 모르겠다"는 의견이 강했다.
그래서 그 중 메서드들을 몇 개 꼽아서 실제로 구현해 보기로 했다.
사용해 보면 다를 수도 있으니까!
처음 선택한 메서드는 useTransition이다.
1. UseTransition
const [isPending, startTransition] = useTransition()
useTransition은 UI를 차단하지 않고, 상태를 업데이트할 수 있는 React Hook을 말한다.
isPending, startTransition 두 가지를 반환한다.
- startTransition : 상태 업데이트를 Transition으로 표시할 수 있게 도와주는 함수
- isPending : 대기 중인 Transition이 있는지 알려줌
*transition : 상태 변화 과정
1) useTransition 사용 이유
function SearchPage() {
const [검색어, 검색어설정] = useState('');
const [검색결과, 검색결과설정] = useState([]);
// 사용자가 입력할 때마다 실행됨
function 검색어변경(새검색어) {
검색어설정(새검색어); // 1) 입력창 업데이트
검색결과설정(엄청무거운검색함수(새검색어)); // 2) 검색 실행
}
}
만약 위와 같은 상황이 있다고 생각해보자.
React는 상태 변경의 우선순위가 동일하기 때문에 사용자가 검색어를 입력할 때마다 화면이 버벅거릴 것이다.
이를 해결하기 위해 만든 것이 useTransition이다.
function SearchPage() {
const [isPending, startTransition] = useTransition();
const [검색어, 검색어설정] = useState('');
const [검색결과, 검색결과설정] = useState([]);
function 검색어변경(새검색어) {
검색어설정(새검색어); // 빨리 처리!
startTransition(() => {
// 천천히 처리해도 괜찮아
검색결과설정(엄청무거운검색함수(새검색어));
});
}
return (
<div>
<input value={검색어} onChange={검색어변경} />
{isPending ? (
<p>검색 중...</p>
) : (
<검색결과목록 결과={검색결과} />
)}
</div>
);
}
이런 식으로 빨리 해도 되는 것과 천천히 해도 되는 것의 우선순위를 나누어서 작업을 진행하는 것이다.
startTransition에 있는 동작은 우선순위가 낮아져 높은 우선순위에 있는 것 먼저 동작한다.
검색어 설정이 먼저 실행되고 그 후에 검색 결과 설정이 진행되는 것이다.
만일, 사용자가 빠르게 타이핑한다면 이전 검색은 중단되고 새로운 검색이 시작된다.
그리고 isPending을 통해 처리 결과를 확인할 수 있다.
이로 인해 UI를 차단하지 않고, 상태를 업데이트할 수 있게 한다.
2) react18 vs react19
// 비동기 작업은 useEffect로 처리
useEffect(() => {
const fetchData = async () => {
const result = await fetch('/api/data');
const data = await result.json();
// 데이터를 받아온 후 무거운 처리나 렌더링을 지연
startTransition(() => {
setProcessedData(heavyProcessing(data)); // 무거운 계산
});
};
fetchData();
}, []);
react18에서는 startTransition 안에서 비동기를 직접적으로 사용할 수 없었다.
따라서 useEffect 안에서 비동기 작업을 처리한 후,
그 결과로 발생하는 무거운 작업들을 useTransition으로 지연시키는 방식으로 사용했었다.
startTransition(async () => {
// 서버에서 데이터를 가져오는 것도 가능!
const 결과 = await 서버에서_데이터_가져오기();
검색결과설정(결과);
});
그러나 react19에서는 비동기도 사용 가능해졌기 때문에 useEffect 없이 하나로 처리할 수 있다.
3) useTransition 실습
import { useState, useTransition } from "react";
const updateCount = async (count: number) => {
return new Promise<number>((resolve) => {
setTimeout(() => resolve(count + 1), 2000);
});
};
const UseTransition = () => {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const handleClick = async () => {
startTransition(async () => {
const newCount = await updateCount(count);
startTransition(() => {
setCount(newCount);
});
});
};
return (
<div>
<h1>useTransition</h1>
<h2>Count: {count}</h2>
<button onClick={handleClick} disabled={isPending}>
Increment
</button>
{isPending ? "로딩 중..." : "카운트 증가"}
</div>
);
};
export default UseTransition;
startTransition으로 update 부분을 감싸주고, isPending을 통해 값을 읽어오면 된다.
4) state로 input value를 제어하는 경우
const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ 제어된 입력 state에 Transition 을 사용할 수 없습니다.
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />;
state로 상태를 제어하고 있는 input의 경우 transition과 함께 사용할 수 없다.
위처럼 state로 input의 값을 제어하고 있는 경우, input의 value와 state는 완벽하게 동기화되어야 한다.
즉, 사용자가 타이핑할 때마다 즉시 화면에 반영되어야 한다.
하지만 useTransition은 업데이트를 지연시키는 특성이 있기 때문에 함께 사용하지 않는 게 좋다.
만일 useTransition을 사용해야 하는 경우라면 input state를 따로 선언하는 것이 좋다.
2. 궁금한 점
1) 이중 startTransition
리액트19 공식 홈페이지에 들어가 보면, 위와 같은 글이 있다.
실제로 예시에서도 startTransition을 두 번 사용하는 것을 볼 수 있다.
import { useState, useTransition } from "react";
const updateCount = async (count: number) => {
return new Promise<number>((resolve) => {
setTimeout(() => resolve(count + 1), 2000);
});
};
const UseTransition = () => {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const handleClick = async () => {
startTransition(async () => {
const newCount = await updateCount(count);
setCount(newCount);
});
};
return (
<div>
<h1>useTransition</h1>
<h2>Count: {count}</h2>
<button onClick={handleClick} disabled={isPending}>
Increment
</button>
{isPending ? "로딩 중..." : "카운트 증가"}
</div>
);
};
export default UseTransition;
그래서 startTransition을 하나만 사용해 봤더니 동작에 차이는 없었다.
스터디 원 중 한 분이 reddit에 올려봤는데, 아직 정확한 답변은 달리지 않았다.
클로드를 통해서 잘못된 예시를 알려달라고 해도 전부 제대로 실행된다ㅠ
정확한 답변을 찾을 때까지 좀 더 고민해봐야 할 것 같다..
리액트 19 스터디를 시작하면서, 내가 원했지만 솔직히 처음에는 진짜 너무 귀찮았다.
이제 막 이직한 지 얼마 안돼서 정신없었던 게 컸던 것 같다.
처음에는 이것보다 급한 게 많은데 라는 생각도 들었다.
그런데 막상 시작하고 나니 내가 이렇게 한 가지에 궁금점을 갖고 고민해 본 적이 언제인가 싶었다.
그냥 구현하기에 급급했지 공부라는 걸 하지 않았던 거 같다.
블로그까지 써보니까 이런 공부 습관을 들일 수 있다는 게 좋다고 느껴졌다.
무엇보다 강제성이 있으니까 하기 싫어도 하게 되고, 팀원들 의견도 들으면서 생각도 나눌 수 있어서 일석 삼조ㅎㅎ
1월엔 생각보다 공부를 많이 못했지만,, 이제 어느 정도 회사생활도 익숙해졌으니까 2월엔 진짜 열심히 해봐야겠다.
'front > react' 카테고리의 다른 글
React 19 톺아보기 - use (0) | 2025.02.10 |
---|---|
[React] React Hooks (0) | 2024.10.09 |
[React] VirtualDOM (2) | 2024.10.02 |