좋습니다. 이번 페이지의 <ViewTransition>은 React 상태 변화에 맞춰 UI의 “이전 모습 → 다음 모습”을 브라우저가 자연스럽게 애니메이션하도록 연결해주는 컴포넌트입니다. 단순한 CSS transition wrapper가 아니라, React의 Transition, Suspense, Activity와 브라우저 View Transition API를 연결하는 경계라고 이해하면 정확합니다. (React)
1. 가장 먼저 잡아야 할 핵심
기본 형태는 아주 단순합니다.
import { ViewTransition } from 'react';
<ViewTransition>
<Page />
</ViewTransition>
하지만 이것만 쓴다고 모든 setState가 자동으로 애니메이션되는 것은 아닙니다.
공식 문서의 중요한 caveat가 있습니다.
일반적인 즉시
setState업데이트는<ViewTransition>을 활성화하지 않는다.
주로 다음과 연결된 업데이트에서 활성화됩니다.
startTransition / useTransition
Suspense
useDeferredValue
즉 <ViewTransition>은 React의 Transition 계열 업데이트와 함께 생각해야 합니다. (React)
2. 왜 startTransition이 같이 나오는가?
예를 들어:
function App() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(true)}>
Show
</button>
{show && (
<ViewTransition>
<Panel />
</ViewTransition>
)}
</>
);
}
이걸 보고:
Panel이 나타날 때 View Transition이 실행되겠네?
라고 생각하면 안 됩니다.
일반적인:
setShow(true);
는 즉시 업데이트이기 때문에 기본적으로 View Transition을 활성화하지 않습니다.
대신:
startTransition(() => {
setShow(true);
});
처럼 React Transition으로 업데이트해야 합니다. (React)
<button
onClick={() => {
startTransition(() => {
setShow(true);
});
}}
>
Show
</button>
여기서 중요한 연결은:
startTransition
= 이 업데이트를 Transition으로 처리
ViewTransition
= 그 Transition에서 시각적인 변화가 있다면
브라우저 View Transition 애니메이션과 연결
입니다.
3. 내부적으로는 브라우저 View Transition API를 쓴다
브라우저에는 원래:
document.startViewTransition(...)
이라는 API가 있습니다.
하지만 React의 <ViewTransition>을 사용할 때는 직접 이것을 호출하지 않아야 합니다.
React가 내부적으로 직접 호출하고 애니메이션 시점을 조율합니다. 공식 문서는 직접 브라우저 API를 호출하기보다는 React가 View Transition을 조정하도록 맡길 것을 권장합니다. (React)
즉:
React state 업데이트
↓
React가 DOM 변경 시점 결정
↓
React가 startViewTransition 호출
↓
이전 화면 snapshot
↓
DOM commit
↓
새 화면 snapshot
↓
브라우저가 두 상태 사이를 animation
정도로 이해하면 됩니다.
보충 설명
이 점 때문에 React의 <ViewTransition>은 단순한:
transition: all 300ms;
과 다릅니다.
브라우저가 변경 전 화면의 이미지와 변경 후 화면의 이미지를 캡처한 뒤 그 둘 사이를 애니메이션할 수 있습니다.
그래서 기본 동작도 자연스러운 cross-fade가 됩니다. (React)
4. 기본적으로 무엇을 애니메이션하는가?
React는 상황을 자동으로 네 종류로 분류합니다.
enter
exit
update
share
이 네 가지가 이번 페이지의 핵심 분류입니다. (React)
5. enter: 새로 등장하는 경우
예를 들어:
function Child() {
return (
<ViewTransition>
<div>Hello</div>
</ViewTransition>
);
}
그리고:
function Parent() {
const [show, setShow] = useState(false);
return (
<>
<button
onClick={() => {
startTransition(() => {
setShow(true);
});
}}
>
Show
</button>
{show && <Child />}
</>
);
}
Child가 Transition 안에서 처음 추가되면:
enter
가 활성화됩니다. (React)
기본적으로 브라우저의 View Transition 애니메이션, 즉 cross-fade가 적용됩니다.
6. exit: 제거되는 경우
반대로:
startTransition(() => {
setShow(false);
});
로 <ViewTransition>이 제거되면:
exit
애니메이션이 활성화될 수 있습니다.
따라서:
없음 → 등장
= enter
존재 → 제거
= exit
입니다.
7. 중요한 함정: ViewTransition 바깥에 DOM wrapper를 두면 enter/exit가 안 될 수 있다
공식 문서가 꽤 강조하는 부분입니다.
이건 정상적인 구조입니다.
function Item() {
return (
<ViewTransition>
<div>Item</div>
</ViewTransition>
);
}
하지만:
function Item() {
return (
<div>
<ViewTransition>
Item
</ViewTransition>
</div>
);
}
처럼 <ViewTransition>보다 먼저 DOM 노드가 존재하면 해당 컴포넌트의 enter/exit가 활성화되지 않습니다. (React)
즉 컴포넌트 트리 기준으로:
Component
└─ ViewTransition
└─ div
는 괜찮지만:
Component
└─ div
└─ ViewTransition
은 enter/exit 측면에서 문제가 됩니다.
공식 문서는 이것이 “너무 많거나 너무 적은 영역이 실수로 애니메이션되는 것”을 방지하기 위한 제약이라고 설명합니다. (React)
8. update: 같은 UI가 위치나 내용이 바뀌는 경우
컴포넌트가 제거됐다 다시 등장하는 게 아니라 같은 <ViewTransition> 내부 DOM이 변경됐다고 해봅시다.
예를 들어:
<ViewTransition>
<div className={expanded ? 'large' : 'small'}>
Content
</div>
</ViewTransition>
Transition 중 expanded가 바뀌면:
update
애니메이션이 활성화될 수 있습니다.
내용 변경뿐 아니라 해당 boundary의 위치나 크기가 형제 변화 때문에 달라지는 경우도 update가 될 수 있습니다. (React)
즉:
enter
= 새로 생김
exit
= 사라짐
update
= 같은 UI가 변화함
입니다.
9. 리스트 reorder가 아주 좋은 update 예제다
예를 들어:
{items.map(item => (
<ViewTransition key={item.id}>
<Card item={item} />
</ViewTransition>
))}
에서 순서를:
A B C
↓
C A B
로 바꿉니다.
그리고 업데이트를:
startTransition(() => {
setItems(reorderedItems);
});
로 수행합니다.
그러면 각 항목은 제거됐다 새로 만들어진 것이 아닙니다.
key가 유지되므로 같은 컴포넌트가 새 위치로 이동한 것입니다.
따라서 각각의 <ViewTransition>에 update 애니메이션을 적용할 수 있습니다. (React)
여기서 key가 굉장히 중요합니다.
우리가 이미 배운:
key는 React에게 리스트 항목의 identity를 알려준다.
가 animation에서도 그대로 연결됩니다.
10. 왜 리스트에서는 wrapper가 문제가 될 수 있나?
이 구조:
items.map(item => (
<ViewTransition key={item.id}>
<Card item={item} />
</ViewTransition>
))
에서는 각각의 항목 이동을 React가 애니메이션할 수 있습니다.
그런데:
items.map(item => (
<div key={item.id}>
<ViewTransition>
<Card item={item} />
</ViewTransition>
</div>
))
처럼 DOM wrapper가 먼저 있으면 개별 ViewTransition이 이동 animation의 대상이 되지 않을 수 있습니다.
결국 부모 ViewTransition 전체가 cross-fade하는 식으로 동작할 수 있습니다. (React)
이전 Fragment 페이지에서 배운 “불필요한 DOM wrapper가 실제 레이아웃 구조에 영향을 준다”는 점이 여기서도 연결됩니다.
11. share: Shared Element Transition
이게 View Transition에서 가장 재미있는 기능입니다.
예를 들어 목록 화면:
[썸네일 A] [썸네일 B]
에서 A를 클릭하면:
[큰 이미지 A]
상세 화면으로 이동한다고 해봅시다.
사용자는:
작은 이미지가 자연스럽게 커져서 상세 이미지가 되는 것처럼
보이게 만들고 싶을 수 있습니다.
이때 동일한 name을 사용할 수 있습니다.
목록:
<ViewTransition name="video-123">
<Thumbnail />
</ViewTransition>
상세:
<ViewTransition name="video-123">
<FullscreenVideo />
</ViewTransition>
Transition 하나에서 기존 tree가 제거되고 새로운 tree가 추가될 때, 같은 name을 가진 두 ViewTransition을 React가 연결합니다.
이것이:
share
입니다. (React)
12. share는 서로 다른 컴포넌트 사이에서도 가능하다
이게 중요한 부분입니다.
목록에서는:
<Thumbnail />
이었는데,
상세 페이지에서는:
<FullscreenVideo />
일 수 있습니다.
React 컴포넌트 자체가 완전히 다르더라도:
<ViewTransition name="video-123">
라는 동일한 이름을 갖고 있다면 하나의 visual element가 이동한 것처럼 연결할 수 있습니다. (React)
그래서:
Thumbnail
↓ 확대/이동
Fullscreen image
같은 애니메이션이 가능합니다.
13. name은 Shared Element Transition에서만 쓰는 게 좋다
보통:
<ViewTransition>
만 사용하면 React가 자동으로 고유한 이름을 만들어줍니다.
따라서 name을 무조건 줄 필요가 없습니다.
공식 문서는 명확하게:
name은 shared element transition이 필요한 경우에만 사용하라.
고 권장합니다. (React)
잘못하면 같은 이름이 앱 여러 곳에 동시에 존재할 수 있습니다.
예를 들어:
function Item() {
return (
<ViewTransition name="item">
...
</ViewTransition>
);
}
을 리스트에서 여러 번 렌더링하면:
item
item
item
item
이 동시에 존재합니다.
이건 오류입니다.
14. name은 앱 전체에서 동시에 하나만 있어야 한다
따라서 리스트에서는:
<ViewTransition name={`item-${item.id}`}>
처럼 고유하게 만들어야 합니다. (React)
예를 들어:
item-42
item-73
item-91
같이요.
여기서 중요한 구분:
key
→ React component identity
name
→ View Transition의 visual identity
입니다.
둘은 목적이 다릅니다.
15. share가 enter/exit보다 우선한다
같은 Transition에서:
old tree 제거
new tree 추가
가 발생해서 원래라면:
old → exit
new → enter
가 될 상황이라고 합시다.
그런데 둘이 동일한:
name="video-123"
을 갖고 있다면 React는:
share
로 판단합니다.
즉 Shared Element Transition이 enter/exit보다 우선합니다. (React)
16. Activity와 결합하면 state를 유지하면서 enter/exit 할 수 있다
바로 이전 페이지와 연결됩니다.
<Activity mode={visible ? 'visible' : 'hidden'}>
<ViewTransition>
<Counter />
</ViewTransition>
</Activity>
그리고:
startTransition(() => {
setVisible(v => !v);
});
라고 하면:
Activity visible → hidden
= ViewTransition exit
Activity hidden → visible
= ViewTransition enter
가 활성화됩니다. (React)
Activity는 state를 보존하므로:
Counter count = 7
↓ hide
↓ show
Counter count = 7
인 상태로 enter/exit animation까지 줄 수 있습니다.
즉:
조건부 렌더링 + ViewTransition
→ animation
→ state는 unmount되므로 사라질 수 있음
Activity + ViewTransition
→ animation
→ state 보존
입니다.
17. Suspense와도 연결된다
이전 페이지의 Suspense도 그대로 이어집니다.
<ViewTransition>
<Suspense fallback={<Skeleton />}>
<Content />
</Suspense>
</ViewTransition>
Skeleton → Content로 바뀌면 같은 ViewTransition boundary 내부에서의 변화이므로:
update
로 처리됩니다.
기본적으로 두 snapshot 사이를 cross-fade합니다. (React)
18. ViewTransition 위치를 바꾸면 Suspense animation 의미도 바뀐다
이번엔:
<Suspense
fallback={
<ViewTransition>
<Skeleton />
</ViewTransition>
}
>
<ViewTransition>
<Content />
</ViewTransition>
</Suspense>
라고 합시다.
이 경우 Skeleton과 Content는 서로 다른 ViewTransition입니다.
따라서:
Skeleton
→ exit
Content
→ enter
로 처리됩니다. (React)
비교하면:
ViewTransition 바깥
<ViewTransition>
<Suspense
fallback={<Skeleton />}
>
<Content />
</Suspense>
</ViewTransition>
Skeleton → Content
= update
Suspense 양쪽에 각각 ViewTransition
<Suspense
fallback={
<ViewTransition>
<Skeleton />
</ViewTransition>
}
>
<ViewTransition>
<Content />
</ViewTransition>
</Suspense>
Skeleton = exit
Content = enter
입니다.
boundary 위치가 animation의 의미를 결정합니다.
이건 Suspense에서 boundary 위치가 reveal sequence를 결정했던 것과 굉장히 비슷합니다.
19. View Transition은 이미지/폰트 로딩도 일부 기다린다
React는 Transition 중 새 데이터와 stylesheet가 준비될 때까지 기다릴 수 있습니다.
또 View Transition에서는 flicker를 줄이기 위해 새 폰트를 최대 약 500ms 기다리고, ViewTransition으로 감싼 이미지도 로딩을 기다릴 수 있습니다. (React)
이유는 간단합니다.
animation 시작
↓
이미지 나중에 나타남
이면 애니메이션 중 화면이 튀어 보이기 때문입니다.
React가:
필요한 visual resource 준비
↓
snapshot
↓
animation
순서를 더 자연스럽게 조율해주는 겁니다.
20. 기본 animation은 cross-fade
아무 설정도 하지 않으면:
<ViewTransition>
<Content />
</ViewTransition>
브라우저의 기본 View Transition animation을 사용합니다.
대체로:
old snapshot
↓ fade out / fade in
new snapshot
형태의 부드러운 cross-fade입니다. (React)
하지만 이것을 커스터마이징할 수 있습니다.
21. enter, exit, update, share, default
예를 들어:
<ViewTransition
enter="slide-in"
exit="slide-out"
update="cross-fade"
>
<Panel />
</ViewTransition>
처럼 각 상황별로 class를 지정할 수 있습니다.
가능한 주요 props는:
enter
exit
update
share
default
입니다. (React)
값으로는:
"auto"
"none"
커스텀 class 이름
등을 사용할 수 있습니다.
22. auto, none, custom class
auto
<ViewTransition enter="auto">
브라우저 기본 animation을 사용합니다.
none
<ViewTransition update="none">
해당 종류의 animation을 끕니다.
custom class
<ViewTransition enter="slide-in">
React가 활성화 시 slide-in View Transition class를 적용합니다. (React)
CSS에서는 일반 클래스 selector가 아니라 View Transition pseudo-element selector를 사용합니다.
::view-transition-new(.slide-in) {
/* animation */
}
23. 왜 view-transition-name 직접 스타일링을 권장하지 않을까?
브라우저 View Transition API 자료를 보면 흔히:
::view-transition-old(foo) {}
::view-transition-new(foo) {}
처럼 view-transition-name을 기준으로 스타일링하는 예제가 나옵니다.
하지만 React 공식 문서는 일반적인 styling에는 이를 권장하지 않고 View Transition Class를 사용하는 방식을 권장합니다. (React)
즉:
<ViewTransition enter="slide-in">
*
::view-transition-new(.slide-in) {
...
}
방식입니다.
이유 중 하나는 name은 shared-element identity 용도로 두고, animation style은 class로 분리하는 편이 훨씬 관리하기 쉽기 때문입니다.
24. default="none" 패턴
특정 animation만 허용하고 싶을 때 유용합니다.
<ViewTransition
default="none"
enter="slide-in"
exit="slide-out"
>
<Panel />
</ViewTransition>
여기서:
enter → slide-in
exit → slide-out
update → 없음
share → 없음
입니다.
공식 문서에 따르면 default="none"이면 따로 명시하지 않은 trigger들은 모두 비활성화됩니다. (React)
실무에서는 꽤 좋은 패턴입니다.
“모든 걸 animate”하기보다 어떤 변화만 animate할지 명확하게 opt-in할 수 있기 때문입니다.
25. 반대로 일부 하위 트리만 animation에서 제외할 수도 있다
페이지 전체를:
<ViewTransition>
<Page />
</ViewTransition>
로 감쌌다고 합시다.
그런데 페이지 내부 작은 state 변경까지 전부 cross-fade되면 너무 과합니다.
이때:
<ViewTransition>
<div className={theme}>
<ViewTransition update="none">
{children}
</ViewTransition>
</div>
</ViewTransition>
처럼 하위 영역의 update animation을 끌 수 있습니다. (React)
그러면:
theme 변경
→ outer ViewTransition animation
children 내부 update
→ animation 없음
같은 제어가 가능합니다.
26. Transition Type을 사용할 수도 있다
조금 더 고급 기능입니다.
navigation을 예로 들면:
forward
back
에 따라 애니메이션 방향을 다르게 하고 싶을 수 있습니다.
forward
→ 왼쪽으로 slide
back
→ 오른쪽으로 slide
이때 addTransitionType을 사용할 수 있습니다.
startTransition(() => {
addTransitionType('navigation-forward');
navigate();
});
그리고:
<ViewTransition
default={{
'navigation-back': 'slide-right',
'navigation-forward': 'slide-left',
}}
>
<Page />
</ViewTransition>
처럼 특정 Transition type에 따라 animation class를 바꿀 수 있습니다. (React)
27. 이것이 router와 잘 맞는 이유
페이지 navigation을 생각하면:
Home
→ Product List
→ Product Detail
에서 단순 cross-fade만 쓰고 싶지 않을 수 있습니다.
예를 들어:
forward navigation
→ 새 페이지가 오른쪽에서 들어옴
back navigation
→ 반대 방향
같은 UX입니다.
Router가:
startTransition(() => {
addTransitionType('navigation-forward');
setRoute(nextRoute);
});
같이 Transition type을 붙이면 ViewTransition이 animation을 그 상황에 맞춰 선택할 수 있습니다.
그래서 공식 문서에서도 router integration을 별도로 설명합니다. (React)
28. JavaScript로도 직접 animation을 제어할 수 있다
CSS로 충분하지 않다면:
<ViewTransition
onEnter={(instance) => {
const animation = instance.new.animate(
[
{ transform: 'scale(0.8)' },
{ transform: 'scale(1)' },
],
{
duration: 300,
easing: 'ease-out',
}
);
return () => animation.cancel();
}}
>
<Panel />
</ViewTransition>
처럼 Web Animations API를 사용할 수 있습니다. (React)
이벤트는:
onEnter
onExit
onUpdate
onShare
입니다.
29. instance.old와 instance.new
callback에 들어오는 instance는 실제 앱 DOM 노드를 직접 의미하지 않습니다.
View Transition의 pseudo-element에 접근할 수 있게 해주는 객체입니다.
대표적으로:
instance.old
instance.new
가 있습니다.
old
→ 이전 화면 snapshot
new
→ 새로운 화면 snapshot
이라고 생각하면 됩니다. (React)
그래서:
instance.new.animate(...)
로 새 화면 snapshot이 등장하는 애니메이션을 직접 제어할 수 있습니다.
30. JavaScript animation callback은 cleanup을 반환해야 한다
이건 Effects와 비슷합니다.
onEnter={(instance) => {
const animation = instance.new.animate(...);
return () => {
animation.cancel();
};
}}
공식 문서는 View Transition event에서 항상 cleanup 함수를 반환하라고 권장합니다. (React)
왜냐하면 애니메이션 도중 다른 Transition이 시작되어 현재 것이 중단될 수도 있기 때문입니다.
우리가 Effects에서 배웠던:
setup
→ cleanup
사고방식이 여기에도 그대로 적용됩니다.
31. 여러 Transition이 빠르게 발생하면 React가 조율한다
예를 들어:
A → B animation 진행 중
인데 사용자가 빠르게:
C
D
까지 이동했다고 합시다.
React는 첫 animation을 처리한 뒤 중간 업데이트들을 합쳐 다음 animation을:
B → D
처럼 처리할 수 있습니다. (React)
즉 <ViewTransition>의 장점 중 하나는 단순히 animation을 시작하는 것뿐 아니라 React의 업데이트 스케줄링과 animation lifecycle을 함께 조율한다는 것입니다.
32. flushSync와는 잘 맞지 않는다
공식 문서의 세부 caveat입니다.
View Transition 처리 과정 중:
flushSync(...)
가 끼어들면 React는 해당 Transition을 skip할 수 있습니다.
왜냐하면 View Transition은 React가 정해진 순서로 DOM update, layout 측정, animation 준비를 완료할 수 있어야 하기 때문입니다. (React)
일반 앱 개발에서 자주 만날 문제는 아니지만 flushSync가 필요한 저수준 코드를 작성한다면 알아둬야 합니다.
33. React는 animation 도중 Effect 실행도 조율한다
내부 동작 중 재미있는 부분입니다.
React는 View Transition 시작 과정에서:
DOM mutation
useInsertionEffect
layout 관련 처리
useLayoutEffect
refs
layout 측정
animation 시작
등을 조율합니다.
그리고 가능하면 일반 useEffect는 View Transition animation이 끝난 뒤 실행해서 animation 성능을 방해하지 않도록 합니다. 다만 다른 상태 업데이트가 발생하는 등의 경우에는 순서 보장을 위해 더 일찍 실행될 수도 있습니다. (React)
보충 설명
여기서 우리가 배운 Effect 종류의 차이가 다시 보입니다.
useLayoutEffect
→ layout/paint와 밀접
useEffect
→ 화면 표시 이후의 일반 side effect
ViewTransition 역시 이 차이를 이용해 animation 타이밍을 관리합니다.
34. ViewTransition은 요소 하나하나의 실제 DOM 위치를 animate하는 API는 아니다
이 부분도 중요합니다.
<ViewTransition>은 기본적으로 화면 snapshot을 사용합니다.
즉 Motion 라이브러리 같은 layout animation과 완전히 같은 방식이 아닙니다.
React 문서 표현대로 개념적으로:
old image snapshot
↓
이동 / 크기 변화 / cross-fade
↓
new image snapshot
방식입니다. (React)
이 방식은:
- 부드러운 animation
- 좋은 성능
- 연속적인 화면 전환
에 유리할 수 있습니다.
반대로 여러 내부 요소가 서로 독립적으로 움직여야 한다면 하나의 큰 ViewTransition으로 감싸기보다 각각 별도 ViewTransition boundary를 만들어야 할 수 있습니다.
35. boundary 크기는 animation 단위를 결정한다
예를 들어:
<ViewTransition>
<Card>
<Image />
<Title />
<Description />
</Card>
</ViewTransition>
이면 전체 Card가 하나의 snapshot 단위처럼 움직일 수 있습니다.
하지만:
<Card>
<ViewTransition>
<Image />
</ViewTransition>
<ViewTransition>
<Title />
</ViewTransition>
</Card>
처럼 나누면 Image와 Title을 독립적으로 animation할 여지가 생깁니다.
Suspense에서:
boundary = reveal 단위
였다면 ViewTransition에서는:
boundary = animation 단위
라고 볼 수 있습니다.
36. 접근성: prefers-reduced-motion을 반드시 고려한다
공식 문서가 별도의 Note로 강조합니다.
React는 사용자가 OS에서:
Reduce motion
설정을 켜놨다고 해서 ViewTransition animation을 자동으로 꺼주지 않습니다.
따라서 CSS에서:
@media (prefers-reduced-motion: reduce) {
/* animation 끄거나 약하게 만들기 */
}
를 사용해야 합니다. (React)
이건 단순 취향 문제가 아니라 접근성 문제입니다.
애니메이션에 민감한 사용자에게 큰 이동이나 확대/축소 효과는 불편함이나 멀미를 유발할 수 있습니다.
37. 실전에서 가장 자연스러운 사용처
ViewTransition을 어디에 쓸지 감을 잡아봅시다.
페이지 전환
목록
→ 상세
Shared element
thumbnail
→ fullscreen image
리스트 재정렬
A B C
→ C A B
탭/패널의 등장과 퇴장
Activity + ViewTransition
Suspense content reveal
Skeleton
→ Content
이런 UI가 이전 상태에서 다음 상태로 공간적으로 이어져야 할 때 가장 자연스럽습니다.
38. 반대로 모든 state update를 animate하면 안 된다
예를 들어:
좋아요 숫자 10 → 11
input 한 글자 입력
clock 1초마다 변경
hover state 변경
까지 모두 ViewTransition으로 만들면 오히려 UX가 산만합니다.
ViewTransition은:
사용자가 화면 구조의 변화를 이해하는 데 애니메이션이 도움이 되는 곳
에 사용하는 것이 좋습니다.
39. ViewTransition + 이전에 배운 API 전체 연결
이제 최근 배운 세 API가 한 번에 연결됩니다.
Suspense
아직 준비되지 않음
→ fallback
→ 준비되면 reveal
ViewTransition을 붙이면:
fallback
→ animation
→ content
으로 만들 수 있습니다.
Activity
hidden
↔
visible
하면서 state와 DOM을 유지합니다.
ViewTransition을 붙이면:
hidden
↔ animation ↔
visible
이 됩니다.
Transition
startTransition(() => {
setState(...);
});
React에게:
이 업데이트는 transition이다.
라고 알려줍니다.
ViewTransition은 그 Transition의 visual 변화에 animation을 붙입니다.
결국:
startTransition
│
│ 상태 변화
▼
React 새 UI 계산
│
├── Suspense라면 준비 기다림
├── Activity라면 visibility 변화
│
▼
ViewTransition boundary 변화 감지
│
├─ enter
├─ exit
├─ update
└─ share
│
▼
브라우저 View Transition animation
이 구조입니다.
40. 가장 헷갈리는 개념: Transition vs ViewTransition
이 둘은 이름이 비슷하지만 역할이 완전히 다릅니다.
React Transition
startTransition(() => {
setPage(nextPage);
});
의미:
업데이트의 우선순위와 처리 방식을 React에게 알려준다.
ViewTransition
<ViewTransition>
<Page />
</ViewTransition>
의미:
그 업데이트에서 발생하는 visual 변화의 animation boundary를 만든다.
따라서:
Transition
= update scheduling
ViewTransition
= visual animation
입니다.
이 차이는 반드시 구분하세요.
41. 자주 하는 실수
첫 번째:
<ViewTransition>
<Panel />
</ViewTransition>
만 넣고:
왜 animation이 안 나오지?
하는 경우입니다.
일반 setState가 아니라 Transition과 연결된 업데이트인지 확인해야 합니다. (React)
두 번째:
<div>
<ViewTransition>
<Panel />
</ViewTransition>
</div>
처럼 enter/exit를 기대하면서 위에 DOM wrapper를 두는 것입니다. (React)
세 번째:
모든 요소에:
name="item"
을 붙이는 것입니다.
동시에 같은 name이 여러 개 mount되어 있으면 안 됩니다. (React)
네 번째:
ViewTransition name
= React key
라고 생각하는 것입니다.
둘은 다릅니다.
key
→ React identity
name
→ shared visual identity
입니다.
다섯 번째:
document.startViewTransition()을 직접 같이 호출하는 것입니다.
React가 View Transition lifecycle을 직접 조율하도록 맡기는 것이 권장됩니다. (React)
42. 이번 페이지의 핵심 mental model
<ViewTransition>을 한 문장으로 설명한다면:
React Transition으로 인해 UI가 이전 상태에서 새로운 상태로 바뀔 때, 그 변화의 시각적 연속성을 브라우저 View Transition API와 연결하는 animation boundary입니다.
그리고 이 네 개를 기억하세요.
enter
새로운 UI 등장
exit
기존 UI 제거
update
같은 UI의 내용/위치/크기 변화
share
서로 다른 old/new UI를
하나의 visual element처럼 연결
핵심 정리
가장 중요한 내용만 압축하면 다음과 같습니다.
<ViewTransition>은 UI tree의 변화에 View Transition animation을 적용하는 React 컴포넌트입니다.- 일반적인 즉시
setState는 기본적으로 활성화하지 않고,startTransition, Suspense,useDeferredValue등의 Transition 흐름과 연결됩니다. (React) - React가 자동으로 브라우저의
startViewTransition을 호출하므로 직접 호출하는 것은 권장되지 않습니다. (React) - activation은
enter,exit,update,share네 종류로 구분됩니다. - 같은
name의 old/new ViewTransition을 연결하면 Shared Element Transition을 만들 수 있습니다. 단, 동일한name이 동시에 여러 개 mount되면 안 됩니다. (React) Activity와 결합하면 state를 유지하며 enter/exit할 수 있습니다.Suspense와 결합하면 fallback에서 실제 콘텐츠로의 reveal을 animate할 수 있습니다. (React)- CSS의 View Transition Class나
onEnter/onExit등의 JavaScript callback으로 animation을 커스터마이징할 수 있습니다. prefers-reduced-motion은 React가 자동 처리하지 않으므로 반드시 직접 고려해야 합니다. (React)
이해도 테스트
정답은 바로 알려주지 않겠습니다.
- 아래 코드에서
<ViewTransition>을 넣었는데도 animation이 나타나지 않을 수 있는 가장 중요한 이유는 무엇일까요?
function App() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(true)}>
Show
</button>
{show && (
<ViewTransition>
<Panel />
</ViewTransition>
)}
</>
);
}
enter,exit,update,share의 차이를 각각 한 문장으로 설명해보세요.목록의
<Thumbnail>이 상세 화면의<LargeImage>로 자연스럽게 확대되는 animation을 만들려면<ViewTransition name={...}>을 어떻게 배치해야 할까요?다음 두 값의 역할 차이는 무엇일까요?
<Component key={item.id} />
<ViewTransition name={`item-${item.id}`}>
Activity + ViewTransition조합이 일반적인 조건부 렌더링 + ViewTransition보다 유리할 수 있는 상황을 하나 설명해보세요.
'리액트 > 레퍼런스 (react 19ver)' 카테고리의 다른 글
| cache (0) | 2026.09.15 |
|---|---|
| addTransitionType (0) | 2026.09.15 |
| act (0) | 2026.09.15 |
| <Activity> (0) | 2026.09.14 |
| <Suspense> (1) | 2026.09.14 |