<ViewTransition>

2026. 9. 15. 01:57·리액트/레퍼런스 (react 19ver)

좋습니다. 이번 페이지의 <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처럼 연결

핵심 정리

가장 중요한 내용만 압축하면 다음과 같습니다.

  1. <ViewTransition>은 UI tree의 변화에 View Transition animation을 적용하는 React 컴포넌트입니다.
  2. 일반적인 즉시 setState는 기본적으로 활성화하지 않고, startTransition, Suspense, useDeferredValue 등의 Transition 흐름과 연결됩니다. (React)
  3. React가 자동으로 브라우저의 startViewTransition을 호출하므로 직접 호출하는 것은 권장되지 않습니다. (React)
  4. activation은 enter, exit, update, share 네 종류로 구분됩니다.
  5. 같은 name의 old/new ViewTransition을 연결하면 Shared Element Transition을 만들 수 있습니다. 단, 동일한 name이 동시에 여러 개 mount되면 안 됩니다. (React)
  6. Activity와 결합하면 state를 유지하며 enter/exit할 수 있습니다.
  7. Suspense와 결합하면 fallback에서 실제 콘텐츠로의 reveal을 animate할 수 있습니다. (React)
  8. CSS의 View Transition Class나 onEnter/onExit 등의 JavaScript callback으로 animation을 커스터마이징할 수 있습니다.
  9. prefers-reduced-motion은 React가 자동 처리하지 않으므로 반드시 직접 고려해야 합니다. (React)

이해도 테스트

정답은 바로 알려주지 않겠습니다.

  1. 아래 코드에서 <ViewTransition>을 넣었는데도 animation이 나타나지 않을 수 있는 가장 중요한 이유는 무엇일까요?
function App() {
  const [show, setShow] = useState(false);

  return (
    <>
      <button onClick={() => setShow(true)}>
        Show
      </button>

      {show && (
        <ViewTransition>
          <Panel />
        </ViewTransition>
      )}
    </>
  );
}
  1. enter, exit, update, share의 차이를 각각 한 문장으로 설명해보세요.

  2. 목록의 <Thumbnail>이 상세 화면의 <LargeImage>로 자연스럽게 확대되는 animation을 만들려면 <ViewTransition name={...}>을 어떻게 배치해야 할까요?

  3. 다음 두 값의 역할 차이는 무엇일까요?

<Component key={item.id} />

<ViewTransition name={`item-${item.id}`}>
  1. 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
'리액트/레퍼런스 (react 19ver)' 카테고리의 다른 글
  • addTransitionType
  • act
  • <Activity>
  • <Suspense>
배불뚱이
배불뚱이
  • 배불뚱이
    FE 개발자의 개발 기록
    배불뚱이
  • 전체
    오늘
    어제
    • 분류 전체보기 (24)
      • 리액트 (24)
        • 공식문서 강의 (19ver) (17)
        • 레퍼런스 (react 19ver) (7)
        • 레퍼런스 (react-dom 19ver) (0)
  • 공지사항

  • 인기 글

  • 태그

    react
    공식문서
    component
    Hook
    레퍼런스
  • hELLO· Designed By정상우.v4.10.6
배불뚱이
<ViewTransition>
상단으로

티스토리툴바