<Activity>๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ UI์™€ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์ˆจ๊ธฐ๊ณ  ๋ณต์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<Activity mode={visibility}>
<Sidebar />
</Activity>

๋ ˆํผ๋Ÿฐ์Šค

<Activity>

Activity๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ผ๋ถ€๋ฅผ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<Activity mode={isShowingSidebar ? "visible" : "hidden"}>
<Sidebar />
</Activity>

Activity ๊ฒฝ๊ณ„๊ฐ€ ์ˆจ๊ฒจ์ง€๋ฉด, React๋Š” display: "none" CSS ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•ด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์ˆจ๊น๋‹ˆ๋‹ค. ๋˜ํ•œ Effect๋ฅผ ํด๋ฆฐ์—…ํ•˜๊ณ  ํ™œ์„ฑ ๊ตฌ๋…์„ ๋ชจ๋‘ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.

์ˆจ๊ฒจ์ง„ ์ƒํƒœ์—์„œ๋„ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” ์ƒˆ๋กœ์šด props์— ๋ฐ˜์‘ํ•˜์—ฌ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€๋งŒ, ๋‚˜๋จธ์ง€ ์ฝ˜ํ…์ธ ๋ณด๋‹ค ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

๊ฒฝ๊ณ„๊ฐ€ ๋‹ค์‹œ ๋ณด์ด๊ฒŒ ๋˜๋ฉด, React๋Š” ์ด์ „ ์ƒํƒœ๋ฅผ ๋ณต์›ํ•œ ์ƒํƒœ๋กœ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ‘œ์‹œํ•˜๊ณ  Effect๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ Activity๋Š” โ€œ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…โ€์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ํ‘œ์‹œ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์ฝ˜ํ…์ธ ๋ฅผ ์™„์ „ํžˆ ์‚ญ์ œํ•˜๋Š” ๋Œ€์‹ , Activity๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ์ฝ˜ํ…์ธ ์˜ UI์™€ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ณ  ๋ณต์›ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋™์‹œ์— ์ˆจ๊ฒจ์ง„ ์ฝ˜ํ…์ธ ๊ฐ€ ์›์น˜ ์•Š๋Š” ๋ถ€์ž‘์šฉ์„ ์ผ์œผํ‚ค์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์—์„œ ๋” ๋งŽ์€ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

Props

  • children: ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ์ˆจ๊ธธ UI์ž…๋‹ˆ๋‹ค.
  • mode: 'visible' ๋˜๋Š” 'hidden' ์ค‘ ํ•˜๋‚˜์˜ ๋ฌธ์ž์—ด ๊ฐ’์ž…๋‹ˆ๋‹ค. ์ƒ๋žตํ•˜๋ฉด ๊ธฐ๋ณธ๊ฐ’์€ 'visible'์ž…๋‹ˆ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ

  • Activity๊ฐ€ ViewTransition ๋‚ด๋ถ€์—์„œ ๋ Œ๋”๋ง๋˜๊ณ , startTransition์œผ๋กœ ์ธํ•œ ์—…๋ฐ์ดํŠธ์˜ ๊ฒฐ๊ณผ๋กœ ๋ณด์ด๊ฒŒ ๋˜๋ฉด ViewTransition์˜ enter ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ์ˆจ๊ฒจ์ง€๋ฉด exit ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.
  • ํ…์ŠคํŠธ๋งŒ ๋ Œ๋”๋งํ•˜๋Š” Activity๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฐ€์‹œ์„ฑ ๋ณ€๊ฒฝ์„ ์ ์šฉํ•  ๋Œ€์‘ํ•˜๋Š” DOM ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด const ComponentThatJustReturnsText = () => "Hello, World!"์ธ ๊ฒฝ์šฐ, <Activity mode="hidden"><ComponentThatJustReturnsText /></Activity>๋Š” DOM์— ์•„๋ฌด๋Ÿฐ ์ถœ๋ ฅ๋„ ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

์ˆจ๊ฒจ์ง„ ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ ๋ณต์›ํ•˜๊ธฐ

React์—์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐ๊ฑด๋ถ€๋กœ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ์ˆจ๊ธฐ๋ ค๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ ํ•ด๋‹น ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋งˆ์šดํŠธํ•˜๊ฑฐ๋‚˜ ๋งˆ์šดํŠธ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.

{isShowingSidebar && (
<Sidebar />
)}

ํ•˜์ง€๋งŒ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งˆ์šดํŠธ ํ•ด์ œํ•˜๋ฉด ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ์‚ฌ๋ผ์ง€๋Š”๋ฐ, ์ด๊ฒƒ์ด ํ•ญ์ƒ ์›ํ•˜๋Š” ๋™์ž‘์€ ์•„๋‹™๋‹ˆ๋‹ค.

Activity ๊ฒฝ๊ณ„๋ฅผ ์‚ฌ์šฉํ•ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆจ๊ธฐ๋ฉด React๋Š” ๋‚˜์ค‘์„ ์œ„ํ•ด ์ƒํƒœ๋ฅผ โ€œ์ €์žฅโ€ํ•ฉ๋‹ˆ๋‹ค.

<Activity mode={isShowingSidebar ? "visible" : "hidden"}>
<Sidebar />
</Activity>

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆจ๊ธด ํ›„ ๋‚˜์ค‘์— ์ด์ „ ์ƒํƒœ ๊ทธ๋Œ€๋กœ ๋ณต์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์˜ˆ์‹œ์—๋Š” ํŽผ์น  ์ˆ˜ ์žˆ๋Š” ์„น์…˜์ด ์žˆ๋Š” ์‚ฌ์ด๋“œ๋ฐ”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. โ€œOverviewโ€๋ฅผ ๋ˆ„๋ฅด๋ฉด ์•„๋ž˜์— ์„ธ ๊ฐœ์˜ ํ•˜์œ„ ํ•ญ๋ชฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋ฉ”์ธ ์•ฑ ์˜์—ญ์—๋Š” ์‚ฌ์ด๋“œ๋ฐ”๋ฅผ ์ˆจ๊ธฐ๊ณ  ํ‘œ์‹œํ•˜๋Š” ๋ฒ„ํŠผ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Overview ์„น์…˜์„ ํŽผ์นœ ๋‹ค์Œ ์‚ฌ์ด๋“œ๋ฐ”๋ฅผ ๋‹ซ์•˜๋‹ค๊ฐ€ ๋‹ค์‹œ ์—ด์–ด๋ณด์„ธ์š”.

import { useState } from 'react';
import Sidebar from './Sidebar.js';

export default function App() {
  const [isShowingSidebar, setIsShowingSidebar] = useState(true);

  return (
    <>
      {isShowingSidebar && (
        <Sidebar />
      )}

      <main>
        <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}>
          Toggle sidebar
        </button>
        <h1>Main content</h1>
      </main>
    </>
  );
}

Overview ์„น์…˜์€ ํ•ญ์ƒ ์ ‘ํžŒ ์ƒํƒœ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. isShowingSidebar๊ฐ€ false๋กœ ๋ฐ”๋€Œ๋ฉด์„œ ์‚ฌ์ด๋“œ๋ฐ”๋ฅผ ๋งˆ์šดํŠธ ํ•ด์ œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ์†์‹ค๋ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ๋ฐ”๋กœ Activity๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์™„๋ฒฝํ•œ ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค. ์‹œ๊ฐ์ ์œผ๋กœ ์ˆจ๊ธฐ๋ฉด์„œ๋„ ์‚ฌ์ด๋“œ๋ฐ”์˜ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๋ณด์กดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์ด๋“œ๋ฐ”์˜ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์„ Activity ๊ฒฝ๊ณ„๋กœ ๊ต์ฒดํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

// Before
{isShowingSidebar && (
<Sidebar />
)}

// After
<Activity mode={isShowingSidebar ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>

์ƒˆ๋กœ์šด ๋™์ž‘์„ ํ™•์ธํ•ด๋ณด์„ธ์š”.

import { Activity, useState } from 'react';

import Sidebar from './Sidebar.js';

export default function App() {
  const [isShowingSidebar, setIsShowingSidebar] = useState(true);

  return (
    <>
      <Activity mode={isShowingSidebar ? 'visible' : 'hidden'}>
        <Sidebar />
      </Activity>

      <main>
        <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}>
          Toggle sidebar
        </button>
        <h1>Main content</h1>
      </main>
    </>
  );
}

์ด์ œ ์‚ฌ์ด๋“œ๋ฐ”์˜ ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ๊ตฌํ˜„์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ ๋„ ๋ณต์›๋ฉ๋‹ˆ๋‹ค.


์ˆจ๊ฒจ์ง„ ์ปดํฌ๋„ŒํŠธ์˜ DOM ๋ณต์›ํ•˜๊ธฐ

Activity ๊ฒฝ๊ณ„๋Š” display: none์„ ์‚ฌ์šฉํ•ด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆจ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์—, ์ˆจ๊ฒจ์ง„ ์ƒํƒœ์—์„œ๋„ ์ž์‹์˜ DOM์ด ๋ณด์กด๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์‹œ ์ƒํ˜ธ์ž‘์šฉํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” UI ๋ถ€๋ถ„์˜ ์ž„์‹œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด ์˜ˆ์‹œ์—์„œ Contact ํƒญ์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” <textarea>๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•œ ํ›„ Home ํƒญ์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ Contact ํƒญ์œผ๋กœ ๋Œ์•„์˜ค๋ฉด ์ž…๋ ฅํ•œ ๋ฉ”์‹œ์ง€๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

export default function Contact() {
  return (
    <div>
      <p>Send me a message!</p>

      <textarea />

      <p>You can find me online here:</p>
      <ul>
        <li>admin@mysite.com</li>
        <li>+123456789</li>
      </ul>
    </div>
  );
}

App์—์„œ Contact๋ฅผ ์™„์ „ํžˆ ๋งˆ์šดํŠธ ํ•ด์ œํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Contact ํƒญ์ด ๋งˆ์šดํŠธ ํ•ด์ œ๋˜๋ฉด <textarea> ์—˜๋ฆฌ๋จผํŠธ์˜ ๋‚ด๋ถ€ DOM ์ƒํƒœ๊ฐ€ ์†์‹ค๋ฉ๋‹ˆ๋‹ค.

Activity ๊ฒฝ๊ณ„๋ฅผ ์‚ฌ์šฉํ•ด ํ™œ์„ฑ ํƒญ์„ ํ‘œ์‹œํ•˜๊ณ  ์ˆจ๊ธฐ๋„๋ก ์ „ํ™˜ํ•˜๋ฉด ๊ฐ ํƒญ์˜ DOM ์ƒํƒœ๋ฅผ ๋ณด์กดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๋‹ค์‹œ ํƒญ์„ ์ „ํ™˜ํ•ด๋ณด๋ฉด ์ž…๋ ฅํ•œ ๋ฉ”์‹œ์ง€๊ฐ€ ๋” ์ด์ƒ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { Activity, useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Contact from './Contact.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('contact');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'contact'}
        onClick={() => setActiveTab('contact')}
      >
        Contact
      </TabButton>

      <hr />

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'contact' ? 'visible' : 'hidden'}>
        <Contact />
      </Activity>
    </>
  );
}

๋‹ค์‹œ ํ•œ๋ฒˆ, Activity ๊ฒฝ๊ณ„๋ฅผ ํ†ตํ•ด Contact ํƒญ์˜ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๊ตฌํ˜„ ๋ณ€๊ฒฝ ์—†์ด ๋ณด์กดํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


ํ‘œ์‹œ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์ฝ˜ํ…์ธ  ์‚ฌ์ „ ๋ Œ๋”๋งํ•˜๊ธฐ

์ง€๊ธˆ๊นŒ์ง€ Activity๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ์ž„์‹œ ์ƒํƒœ๋ฅผ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ์ˆจ๊ธฐ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ Activity ๊ฒฝ๊ณ„๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ์ฒ˜์Œ ๋ณด์ง€ ๋ชปํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ์ค€๋น„ ํ•˜๋Š” ๋ฐ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<Activity mode="hidden">
<SlowComponent />
</Activity>

Activity ๊ฒฝ๊ณ„๊ฐ€ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ค‘์— ์ˆจ๊ฒจ์ง„ ์ƒํƒœ๋ผ๋ฉด, ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” ํŽ˜์ด์ง€์— ๋ณด์ด์ง€ ์•Š์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๋ณด์ด๋Š” ์ฝ˜ํ…์ธ ๋ณด๋‹ค ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ๋ Œ๋”๋ง๋˜๋ฉฐ, Effect๋Š” ๋งˆ์šดํŠธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์‚ฌ์ „ ๋ Œ๋”๋ง ์„ ํ†ตํ•ด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•œ ์ฝ”๋“œ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋‚˜์ค‘์— Activity ๊ฒฝ๊ณ„๊ฐ€ ๋ณด์ด๊ฒŒ ๋  ๋•Œ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ์ค„์–ด๋“ค์–ด ๋” ๋น ๋ฅด๊ฒŒ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด ๋ฐ๋ชจ์—์„œ Posts ํƒญ์€ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ํƒญ์„ ๋ˆ„๋ฅด๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ Suspense ํด๋ฐฑ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

import { useState, Suspense } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Posts from './Posts.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('home');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'posts'}
        onClick={() => setActiveTab('posts')}
      >
        Posts
      </TabButton>

      <hr />

      <Suspense fallback={<h1>๐ŸŒ€ Loading...</h1>}>
        {activeTab === 'home' && <Home />}
        {activeTab === 'posts' && <Posts />}
      </Suspense>
    </>
  );
}

App์ด ํƒญ์ด ํ™œ์„ฑํ™”๋  ๋•Œ๊นŒ์ง€ Posts๋ฅผ ๋งˆ์šดํŠธํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

App์„ ์ˆ˜์ •ํ•˜์—ฌ Activity ๊ฒฝ๊ณ„๋กœ ํ™œ์„ฑ ํƒญ์„ ํ‘œ์‹œํ•˜๊ณ  ์ˆจ๊ธฐ๋„๋ก ํ•˜๋ฉด, ์•ฑ์ด ์ฒ˜์Œ ๋กœ๋“œ๋  ๋•Œ Posts๊ฐ€ ์‚ฌ์ „ ๋ Œ๋”๋ง๋˜์–ด ๋ณด์ด๊ธฐ ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ Posts ํƒญ์„ ํด๋ฆญํ•ด๋ณด์„ธ์š”.

import { Activity, useState, Suspense } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Posts from './Posts.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('home');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'posts'}
        onClick={() => setActiveTab('posts')}
      >
        Posts
      </TabButton>

      <hr />

      <Suspense fallback={<h1>๐ŸŒ€ Loading...</h1>}>
        <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
          <Home />
        </Activity>
        <Activity mode={activeTab === 'posts' ? 'visible' : 'hidden'}>
          <Posts />
        </Activity>
      </Suspense>
    </>
  );
}

์ˆจ๊ฒจ์ง„ Activity ๊ฒฝ๊ณ„ ๋•๋ถ„์— Posts๊ฐ€ ๋” ๋น ๋ฅธ ๋ Œ๋”๋ง์„ ์ค€๋น„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


์ˆจ๊ฒจ์ง„ Activity ๊ฒฝ๊ณ„๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์ „ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์Œ์— ์ƒํ˜ธ์ž‘์šฉํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” UI ๋ถ€๋ถ„์˜ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ์ค„์ด๋Š” ๊ฐ•๋ ฅํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

์‚ฌ์ „ ๋ Œ๋”๋ง ์ค‘์—๋Š” Suspense๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

  • Relay์™€ Next.js ๊ฐ™์ด Suspense๊ฐ€ ๊ฐ€๋Šฅํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ.
  • lazy๋ฅผ ํ™œ์šฉํ•œ ์ง€์—ฐ ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ.
  • use๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์บ์‹œ๋œ Promise ๊ฐ’ ์ฝ๊ธฐ.

Activity๋Š” Effect ๋‚ด๋ถ€์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ง€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์œ„์˜ Posts ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ •ํ™•ํ•œ ๋ฐฉ๋ฒ•์€ ํ”„๋ ˆ์ž„์›Œํฌ์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. Suspense๊ฐ€ ๊ฐ€๋Šฅํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ๊ด€๋ จ ๋ฌธ์„œ์—์„œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋…์ž์ ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” Suspense๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๊ธฐ๋Šฅ์€ ์•„์ง ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Suspense ์ง€์› ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์š”๊ตฌ ์‚ฌํ•ญ์€ ๋ถˆ์•ˆ์ •ํ•˜๊ณ  ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ Suspense์™€ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•œ ๊ณต์‹ API๋Š” ํ–ฅํ›„ React ๋ฒ„์ „์—์„œ ์ถœ์‹œ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.


ํŽ˜์ด์ง€ ๋กœ๋“œ ์ค‘ ์ƒํ˜ธ์ž‘์šฉ ์†๋„ ๋†’์ด๊ธฐ

React์—๋Š” ์„ ํƒ์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์ด๋ผ๋Š” ๋‚ด๋ถ€ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์•ฑ์˜ ์ดˆ๊ธฐ HTML์„ ์ฒญํฌ ๋‹จ์œ„ ๋กœ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ํ•˜์—ฌ, ํŽ˜์ด์ง€์˜ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฝ”๋“œ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ์•„์ง ๋กœ๋“œํ•˜์ง€ ์•Š์•˜๋”๋ผ๋„ ์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

Suspense ๊ฒฝ๊ณ„๋Š” ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ์„œ๋กœ ๋…๋ฆฝ์ ์ธ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ„๊ธฐ ๋•Œ๋ฌธ์— ์„ ํƒ์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์— ์ฐธ์—ฌํ•ฉ๋‹ˆ๋‹ค.

function Page() {
return (
<>
<MessageComposer />

<Suspense fallback="Loading chats...">
<Chats />
</Suspense>
</>
)
}

์—ฌ๊ธฐ์„œ MessageComposer๋Š” Chats๊ฐ€ ๋งˆ์šดํŠธ๋˜์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์—๋„ ํŽ˜์ด์ง€์˜ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ค‘์— ์™„์ „ํžˆ ํ•˜์ด๋“œ๋ ˆ์ด์…˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๊ฐœ๋ณ„ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ„๋ฉด React๊ฐ€ ์•ฑ์˜ ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ HTML์„ ์ฒญํฌ ๋‹จ์œ„๋กœ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์–ด, ์•ฑ์˜ ์ผ๋ถ€๊ฐ€ ๊ฐ€๋Šฅํ•œ ํ•œ ๋น ๋ฅด๊ฒŒ ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด Suspense๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํŽ˜์ด์ง€๋Š” ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?

๋‹ค์Œ ํƒญ ์˜ˆ์‹œ๋ฅผ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

function Page() {
const [activeTab, setActiveTab] = useState('home');

return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>

{activeTab === 'home' && (
<Home />
)}
{activeTab === 'video' && (
<Video />
)}
</>
)
}

์—ฌ๊ธฐ์„œ React๋Š” ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ํ•œ ๋ฒˆ์— ํ•˜์ด๋“œ๋ ˆ์ด์…˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Home์ด๋‚˜ Video๊ฐ€ ๋ Œ๋”๋ง์ด ๋А๋ฆฌ๋‹ค๋ฉด ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์ค‘์— ํƒญ ๋ฒ„ํŠผ์ด ๋ฐ˜์‘ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ™œ์„ฑ ํƒญ ์ฃผ์œ„์— Suspense๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function Page() {
const [activeTab, setActiveTab] = useState('home');

return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>

<Suspense fallback={<Placeholder />}>
{activeTab === 'home' && (
<Home />
)}
{activeTab === 'video' && (
<Video />
)}
</Suspense>
</>
)
}

โ€ฆํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ดˆ๊ธฐ ๋ Œ๋”๋ง์—์„œ Placeholder ํด๋ฐฑ์ด ํ‘œ์‹œ๋˜๊ธฐ ๋•Œ๋ฌธ์— UI๊ฐ€ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.

๋Œ€์‹  Activity๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Activity ๊ฒฝ๊ณ„๋Š” ์ž์‹์„ ํ‘œ์‹œํ•˜๊ณ  ์ˆจ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฏธ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๋…๋ฆฝ์ ์ธ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ•๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Suspense์ฒ˜๋Ÿผ ์ด ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์„ ํƒ์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์— ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ํ™œ์„ฑ ํƒญ ์ฃผ์œ„์— Activity ๊ฒฝ๊ณ„๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

function Page() {
const [activeTab, setActiveTab] = useState('home');

return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>

<Activity mode={activeTab === "home" ? "visible" : "hidden"}>
<Home />
</Activity>
<Activity mode={activeTab === "video" ? "visible" : "hidden"}>
<Video />
</Activity>
</>
)
}

์ด์ œ ์ดˆ๊ธฐ ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ HTML์€ ์›๋ž˜ ๋ฒ„์ „๊ณผ ๋™์ผํ•˜๊ฒŒ ๋ณด์ด์ง€๋งŒ, Activity ๋•๋ถ„์— React๋Š” Home์ด๋‚˜ Video๋ฅผ ๋งˆ์šดํŠธํ•˜๊ธฐ๋„ ์ „์— ํƒญ ๋ฒ„ํŠผ์„ ๋จผ์ € ํ•˜์ด๋“œ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋”ฐ๋ผ์„œ ์ฝ˜ํ…์ธ ๋ฅผ ์ˆจ๊ธฐ๊ณ  ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„, Activity ๊ฒฝ๊ณ„๋Š” ํŽ˜์ด์ง€์˜ ์–ด๋А ๋ถ€๋ถ„์ด ๋…๋ฆฝ์ ์œผ๋กœ ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅํ•ด์งˆ ์ˆ˜ ์žˆ๋Š”์ง€ React์— ์•Œ๋ ค์คŒ์œผ๋กœ์จ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์ค‘ ์•ฑ์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€๊ฐ€ ์ฝ˜ํ…์ธ ์˜ ์ผ๋ถ€๋ฅผ ์ˆจ๊ธฐ์ง€ ์•Š๋”๋ผ๋„, ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ํ•ญ์ƒ ๋ณด์ด๋Š” Activity ๊ฒฝ๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function Page() {
return (
<>
<Post />

<Activity>
<Comments />
</Activity>
</>
);
}

๋ฌธ์ œ ํ•ด๊ฒฐ

์ˆจ๊ฒจ์ง„ ์ปดํฌ๋„ŒํŠธ์— ์›์น˜ ์•Š๋Š” ๋ถ€์ž‘์šฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค

Activity ๊ฒฝ๊ณ„๋Š” ์ž์‹์— display: none์„ ์„ค์ •ํ•˜๊ณ  Effect๋ฅผ ํด๋ฆฐ์—…ํ•˜์—ฌ ์ฝ˜ํ…์ธ ๋ฅผ ์ˆจ๊น๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ถ€์ž‘์šฉ์„ ์ ์ ˆํžˆ ํด๋ฆฐ์—…ํ•˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์ž˜ ์ž‘์„ฑ๋œ React ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฏธ Activity์— ์˜ํ•ด ์ˆจ๊ฒจ์ง€๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ๊ฒฌ๊ณ ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ˆจ๊ฒจ์ง„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋œ ์ปดํฌ๋„ŒํŠธ์™€ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ์ƒํ™ฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ˆจ๊ฒจ์ง„ ์ปดํฌ๋„ŒํŠธ์˜ DOM์€ ์ œ๊ฑฐ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํ•ด๋‹น DOM์˜ ๋ถ€์ž‘์šฉ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ˆจ๊ฒจ์ง„ ํ›„์—๋„ ์ง€์†๋ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด <video> ํƒœ๊ทธ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์„ธ์š”. ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ฆฐ์—…์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ ์ค‘์ด๋”๋ผ๋„ ํƒœ๊ทธ๋ฅผ ๋งˆ์šดํŠธ ํ•ด์ œํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋น„๋””์˜ค์™€ ์˜ค๋””์˜ค ์žฌ์ƒ์ด ์ค‘์ง€๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒํ•œ ํ›„ ์ด ๋ฐ๋ชจ์—์„œ Home์„ ๋ˆŒ๋Ÿฌ๋ณด์„ธ์š”.

import { useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Video from './Video.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('video');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'video'}
        onClick={() => setActiveTab('video')}
      >
        Video
      </TabButton>

      <hr />

      {activeTab === 'home' && <Home />}
      {activeTab === 'video' && <Video />}
    </>
  );
}

๋น„๋””์˜ค๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ์žฌ์ƒ์„ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.

์ด์ œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ์‹œ์ฒญํ•œ ํƒ€์ž„์ฝ”๋“œ๋ฅผ ๋ณด์กดํ•˜์—ฌ ๋น„๋””์˜ค ํƒญ์œผ๋กœ ๋Œ์•„์™”์„ ๋•Œ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์ง€ ์•Š๋„๋ก ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค.

์ด๊ฒƒ์€ Activity๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์™„๋ฒฝํ•œ ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค!

App์„ ์ˆ˜์ •ํ•˜์—ฌ ๋น„ํ™œ์„ฑ ํƒญ์„ ๋งˆ์šดํŠธ ํ•ด์ œํ•˜๋Š” ๋Œ€์‹  ์ˆจ๊ฒจ์ง„ Activity ๊ฒฝ๊ณ„๋กœ ์ˆจ๊ธฐ๊ณ , ์ด๋ฒˆ์—๋Š” ๋ฐ๋ชจ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด์„ธ์š”.

import { Activity, useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Video from './Video.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('video');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'video'}
        onClick={() => setActiveTab('video')}
      >
        Video
      </TabButton>

      <hr />

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
        <Video />
      </Activity>
    </>
  );
}

์ด๋Ÿฐ! ๋น„๋””์˜ค๊ฐ€ ์ˆจ๊ฒจ์ง„ ํ›„์—๋„ ๋น„๋””์˜ค์™€ ์˜ค๋””์˜ค๊ฐ€ ๊ณ„์† ์žฌ์ƒ๋ฉ๋‹ˆ๋‹ค. ํƒญ์˜ <video> ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ์—ฌ์ „ํžˆ DOM์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋น„๋””์˜ค๋ฅผ ์ผ์‹œ์ •์ง€ํ•˜๋Š” ํด๋ฆฐ์—… ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š” Effect๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export default function VideoTab() {
const ref = useRef();

useLayoutEffect(() => {
const videoRef = ref.current;

return () => {
videoRef.pause()
}
}, []);

return (
<video
ref={ref}
controls
playsInline
src="..."
/>

);
}

๊ฐœ๋…์ ์œผ๋กœ ํด๋ฆฐ์—… ์ฝ”๋“œ๊ฐ€ ์ปดํฌ๋„ŒํŠธ์˜ UI๊ฐ€ ์‹œ๊ฐ์ ์œผ๋กœ ์ˆจ๊ฒจ์ง€๋Š” ๊ฒƒ๊ณผ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— useEffect ๋Œ€์‹  useLayoutEffect๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜ effect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด (์˜ˆ๋ฅผ ๋“ค์–ด) ๋‹ค์‹œ suspend๋˜๋Š” Suspense ๊ฒฝ๊ณ„๋‚˜ View Transition์— ์˜ํ•ด ์ฝ”๋“œ๊ฐ€ ์ง€์—ฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒˆ๋กœ์šด ๋™์ž‘์„ ํ™•์ธํ•ด๋ณด์„ธ์š”. ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒํ•˜๊ณ  Home ํƒญ์œผ๋กœ ์ „ํ™˜ํ•œ ๋‹ค์Œ ๋‹ค์‹œ Video ํƒญ์œผ๋กœ ๋Œ์•„์™€๋ณด์„ธ์š”.

import { Activity, useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Video from './Video.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('video');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'video'}
        onClick={() => setActiveTab('video')}
      >
        Video
      </TabButton>

      <hr />

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
        <Video />
      </Activity>
    </>
  );
}

์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค! ํด๋ฆฐ์—… ํ•จ์ˆ˜๋Š” Activity ๊ฒฝ๊ณ„์— ์˜ํ•ด ์ˆจ๊ฒจ์งˆ ๋•Œ๋งˆ๋‹ค ๋น„๋””์˜ค ์žฌ์ƒ์ด ์ค‘์ง€๋˜๋„๋ก ๋ณด์žฅํ•˜๋ฉฐ, ๋” ์ข‹์€ ์ ์€ <video> ํƒœ๊ทธ๊ฐ€ ์ œ๊ฑฐ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž„์ฝ”๋“œ๊ฐ€ ๋ณด์กด๋˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์ฒญ์„ ๊ณ„์†ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ์ „ํ™˜ํ•  ๋•Œ ๋น„๋””์˜ค๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ฑฐ๋‚˜ ๋‹ค์‹œ ๋‹ค์šด๋กœ๋“œํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋Š” Activity๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆจ๊ฒจ์ง€์ง€๋งŒ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณง ๋‹ค์‹œ ์ƒํ˜ธ์ž‘์šฉํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” UI ๋ถ€๋ถ„์˜ ์ž„์‹œ DOM ์ƒํƒœ๋ฅผ ๋ณด์กดํ•˜๋Š” ์ข‹์€ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.


์˜ˆ์‹œ์—์„œ ๋ณด๋“ฏ์ด <video>์™€ ๊ฐ™์€ ํŠน์ • ํƒœ๊ทธ์˜ ๊ฒฝ์šฐ ๋งˆ์šดํŠธ ํ•ด์ œ์™€ ์ˆจ๊ธฐ๊ธฐ๊ฐ€ ๋‹ค๋ฅธ ๋™์ž‘์„ ๋ณด์ž…๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ถ€์ž‘์šฉ์ด ์žˆ๋Š” DOM์„ ๋ Œ๋”๋งํ•˜๊ณ , Activity ๊ฒฝ๊ณ„๊ฐ€ ์ด๋ฅผ ์ˆจ๊ธธ ๋•Œ ํ•ด๋‹น ๋ถ€์ž‘์šฉ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํด๋ฆฐ์—…์„ ์œ„ํ•œ return ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š” Effect๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

๊ฐ€์žฅ ํ”ํ•œ ๊ฒฝ์šฐ๋Š” ๋‹ค์Œ ํƒœ๊ทธ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • <video>
  • <audio>
  • <iframe>

ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ ๋Œ€๋ถ€๋ถ„์˜ React ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฏธ Activity ๊ฒฝ๊ณ„์— ์˜ํ•ด ์ˆจ๊ฒจ์ง€๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ๊ฒฌ๊ณ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋…์ ์œผ๋กœ โ€œ์ˆจ๊ฒจ์ง„โ€ Activity๋Š” ๋งˆ์šดํŠธ ํ•ด์ œ๋œ ๊ฒƒ์œผ๋กœ ์ƒ๊ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ ์ ˆํ•œ ํด๋ฆฐ์—…์ด ์—†๋Š” ๋‹ค๋ฅธ Effect๋ฅผ ๋ฏธ๋ฆฌ ๋ฐœ๊ฒฌํ•˜๋ ค๋ฉด <StrictMode> ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Activity ๊ฒฝ๊ณ„๋ฟ๋งŒ ์•„๋‹ˆ๋ผ React์˜ ๋‹ค๋ฅธ ๋งŽ์€ ๋™์ž‘์—๋„ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.


์ˆจ๊ฒจ์ง„ ์ปดํฌ๋„ŒํŠธ์˜ Effect๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

<Activity>๊ฐ€ โ€œhiddenโ€ ์ƒํƒœ์ผ ๋•Œ ๋ชจ๋“  ์ž์‹์˜ Effect๊ฐ€ ํด๋ฆฐ์—…๋ฉ๋‹ˆ๋‹ค. ๊ฐœ๋…์ ์œผ๋กœ ์ž์‹์€ ๋งˆ์šดํŠธ ํ•ด์ œ๋˜์ง€๋งŒ, React๋Š” ๋‚˜์ค‘์„ ์œ„ํ•ด ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Activity์˜ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ˆจ๊ฒจ์ง„ UI ๋ถ€๋ถ„์— ๋Œ€ํ•œ ๊ตฌ๋…์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•„ ์ˆจ๊ฒจ์ง„ ์ฝ˜ํ…์ธ ์— ํ•„์š”ํ•œ ์ž‘์—…๋Ÿ‰์ด ์ค„์–ด๋“ค๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ์˜ ๋ถ€์ž‘์šฉ์„ ํด๋ฆฐ์—…ํ•˜๊ธฐ ์œ„ํ•ด Effect๊ฐ€ ๋งˆ์šดํŠธ๋˜๋Š” ๊ฒƒ์— ์˜์กดํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋Œ€์‹  ๋ฐ˜ํ™˜๋œ ํด๋ฆฐ์—… ํ•จ์ˆ˜์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก Effect๋ฅผ ๋ฆฌํŒฉํ„ฐ๋งํ•˜์„ธ์š”.

๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” Effect๋ฅผ ๋ฏธ๋ฆฌ ์ฐพ์œผ๋ ค๋ฉด <StrictMode> ์ถ”๊ฐ€๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ถ€์ž‘์šฉ์„ ํฌ์ฐฉํ•˜๊ธฐ ์œ„ํ•ด Activity ๋งˆ์šดํŠธ ํ•ด์ œ์™€ ๋งˆ์šดํŠธ๋ฅผ ๋ฏธ๋ฆฌ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.