Skip to content

React Transition Group ๋œฏ์–ด๋ณด๊ธฐ

๋””์ž์ธํ•™๊ณผ ์กธ์—…์ „์‹œ ์›น์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด์„œ, ์ „์ฒด ํŽ˜์ด์ง€์— ์ˆ˜ํ‰ ์•„์ฝ”๋””์–ธ ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋””์ž์ธํ•™๊ณผ ์กธ์—…์ „์‹œ ์›น์‚ฌ์ดํŠธ

์ฒ˜์Œ ์ ‘๊ทผ์€ ๋‹จ์ˆœํžˆ ๋‹ค๋ฅธ ์•„์ฝ”๋””์–ธ ์• ๋‹ˆ๋ฉ”์ด์…˜์ฒ˜๋Ÿผ ๋‹จ์ˆœํ•˜๊ฒŒ ์ ‘๊ทผํ–ˆ์Šต๋‹ˆ๋‹ค.
๋ชจ๋“  ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ์— ์ „๋ถ€ ๋งˆ์šดํŠธํ•ด๋‘๊ณ , ๊ฐ ์„น์…˜์˜ max-width ๋งŒ ์กฐ์ ˆํ•˜๋ฉด์„œ ์—ด๋ฆฌ๊ณ  ๋‹ซํžˆ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿฉธ ์‚ฌ๊ฑด ํ˜„์žฅ โ€‹

๊ทธ๋Ÿฐ๋ฐ ์›ฌ๊ฑธ.. Lighthouse์—์„œ DOM ์š”์†Œ๊ฐ€ 3,495๊ฐœ๋ผ๋Š” ๊ฒฝ๊ณ ๊ฐ€ ๋œจ๊ณ , TBT(Total Blocking Time)๋„ ๋†’๊ณ  ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰๋„ ๊ณ„์† ๋Š˜์–ด๋‚˜๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๊ฒ ์Šต๋‹ˆ๊นŒ?

๋ฌธ์ œ ์ƒํ™ฉ 1๋ฌธ์ œ ์ƒํ™ฉ 2

๋ฌด์—‡๋ณด๋‹ค ์•ˆ ๋ณด์ด๋Š” ํŽ˜์ด์ง€๋„ ๊ณ„์† DOM ์š”์†Œ๋กœ์จ ๋‚จ์•„์žˆ๋‹ค๋Š” ์ ์ด ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.
์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚˜๋„ ์ปดํฌ๋„ŒํŠธ๋Š” ์–ธ๋งˆ์šดํŠธ ๋˜์ง€ ์•Š์œผ๋‹ˆ๊นŒ, ๋ธŒ๋ผ์šฐ์ € ์ž…์žฅ์—์„œ๋Š” ๊ทธ๋ƒฅ ํŽ˜์ด์ง€ ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๋™์‹œ์— ๋Œ๋ฆฌ๊ณ  ์žˆ๋Š” ์…ˆ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์œ ์ง€ํ•˜๋˜, ํ™”๋ฉด ๋ฐ”๊นฅ์œผ๋กœ ๋‚˜๊ฐ„ ํŽ˜์ด์ง€๋Š” DOM ์—์„œ ์น˜์›Œ๋ฒ„๋ฆด ์ˆ˜ ์—†์„๊นŒ?

FramerMotion, React Spring ๋“ฑ ์—ฌ๋Ÿฌ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ดํŽด๋ดค์ง€๋งŒ, ์•„์ฝ”๋””์–ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ•˜๋‚˜๋ฅผ ์œ„ํ•ด์„œ ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ๊ฐ€ ํฌ๊ธฐ๋„ ํ–ˆ๊ณ , ๋””์ž์ด๋„ˆ๊ฐ€ ๋”ฑ ์›ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ React Transition Group ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ, ๊ณต์‹๋ฌธ์„œ๊ฐ€ ๋ถˆ์นœ์ ˆ ํ•ด์„œ ๋‚ด๋ถ€ ๋™์ž‘ ๋ฐฉ์‹์„ ์ง์ ‘ ์‚ดํŽด๋ณด๊ณ , ์–ด๋–ป๊ฒŒ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ข…๋ฃŒ๋œ ์ดํ›„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ ๋˜๋Š”์ง€ ๋’ค์ ๊ฑฐ๋ ค๋ดค์Šต๋‹ˆ๋‹ค.

๐Ÿ•ต๏ธโ€โ™€๏ธ React Transition Group โ€‹

React Transition Group์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ค์–ด์˜ค๊ณ  ๋‚˜๊ฐˆ ๋•Œ(๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ์‹œ์ )์˜ ์ „ํ™˜์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฌ๋Ÿฌ ๊ฐ„๋‹จํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

React Transition Group์€ React-Motion ๊ฐ™์€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž์ฒด๊ฐ€ ์Šคํƒ€์ผ์„ ์ง์ ‘ ์• ๋‹ˆ๋ฉ”์ด์…˜์‹œํ‚ค์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

๋Œ€์‹ ์—, ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ „ํ™˜(transition)์˜ ๊ฐ ๋‹จ๊ณ„ ์ •๋ณด๋ฅผ ๋…ธ์ถœํ•˜๊ณ , ํด๋ž˜์Šค ์ด๋ฆ„์„ ๋ถ™์ด๊ฑฐ๋‚˜ ๋–ผ๊ณ , ์š”์†Œ๋“ค์„ ๊ทธ๋ฃนํ™”ํ•˜๊ณ , DOM์„ ์ ์ ˆํžˆ ์กฐ์ž‘ํ•ด ์ค๋‹ˆ๋‹ค. ๋•๋ถ„์— ์‹ค์ œ ์‹œ๊ฐ์ ์ธ ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•˜๋Š” ์ž‘์—…์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

React Transition Group ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋“ค์–ด๊ฐ€๋ณด๋ฉด ํฌ๊ฒŒ Transition, CSSTransition, SwitchTransition, TransitionGroup ๋„ค ๊ฐ€์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•œ๋‹ค๊ณ  ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ๋ถ€ํ„ฐ ์ด ๋„ค๊ฐ€์ง€ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

1๏ธโƒฃ <Transition/> : ์ด ์š”์†Œ๋ฅผ ์ง€๊ธˆ ๋ณด์—ฌ์ค„๊บผ์•ผ? ์ˆจ๊ธธ๊บผ์•ผ? โ€‹

<Transition/> ์ปดํฌ๋„ŒํŠธ๋Š” React Transition Group ์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ด ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ ์ž…๋‹ˆ๋‹ค.
์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง€๊ธˆ ๋ณด์—ฌ์ค„๊บผ์•ผ? ์ˆจ๊ธธ๊บผ์•ผ? ๋ผ๋Š” ์ƒํƒœ๋ฅผ in prop ์œผ๋กœ ๋ฐ›์•„์„œ, ์ปดํฌ๋„ŒํŠธ์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ์ƒํƒœ๋จธ์‹ ์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ in: boolean โ€‹

  • in prop ์€ boolean ํƒ€์ž…์œผ๋กœ, true ๋ฉด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , false ๋ฉด ์ˆจ๊น๋‹ˆ๋‹ค.
  • ์ด ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ <Transition/> ์€ ๋‚ด๋ถ€์ ์œผ๋กœ entering, entered, exiting, exited ๋„ค ๊ฐ€์ง€ ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.

โš™๏ธ mountOnEnter: boolean, unmountOnExit: boolean โ€‹

  • mountOnEnter : in ์ด ์ฒ˜์Œ์œผ๋กœ true ๊ฐ€ ๋˜๊ธฐ์ „์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งˆ์šดํŠธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • unmountOnExit : in ์ด false ๊ฐ€ ๋œ ํ›„์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์–ธ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค.

์ด ๋‘ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๋ณด์ผ ๋•Œ๋งŒ ๋งˆ์šดํŠธ๋˜๊ณ , ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์งˆ ๋•Œ ์–ธ๋งˆ์šดํŠธ ๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โš™๏ธ appear: boolean โ€‹

  • ์ฒ˜์Œ ๋งˆ์šดํŠธ๋  ๋•Œ๋„ ๋“ฑ์žฅ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ ์šฉํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’์€ false)

โš™๏ธ enter: boolean, exit: boolean โ€‹

  • ๊ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์•„์˜ˆ ๋น„ํ™œ์„ฑํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • enter : false ์ธ ๊ฒฝ์šฐ ๋“ค์–ด์˜ฌ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์—†์ด ๋ฐ”๋กœ entered ์ƒํƒœ๋กœ ์ „์ด๋ฉ๋‹ˆ๋‹ค.
  • exit : false ์ธ ๊ฒฝ์šฐ ๋‚˜๊ฐˆ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์—†์ด ๋ฐ”๋กœ exited ์ƒํƒœ๋กœ ์ „์ด๋ฉ๋‹ˆ๋‹ค.

โš™๏ธ timeout: number ์™€ addEndListener โ€‹

  • React Transition Group ์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์–ธ์ œ ๋๋‚ฌ๋Š”์ง€ ์•Œ์•„์•ผ ๋‹ค์Œ ์ƒํƒœ๋กœ ์ „์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • timeout : number : ์ง€์ •๋œ ms ์ดํ›„์— ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚ฌ๋‹ค๊ณ  ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.
  • addEndListener(node, done) : DOM ๋…ธ๋“œ์˜ ์‹ค์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์™„๋ฃŒ ์ด๋ฒคํŠธ๋ฅผ ์žก์•„์„œ done() ์„ ํ˜ธ์ถœํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ด๋นˆ๋‹ค.
tsx
<Transition
    in={show}
    addEndListener={(node, done) => {
        node.addEventListener("transitioned", done);
    }}
/>

๐Ÿ” ์ƒํƒœ ์ „์ด โ€‹

๊ฒฐ๊ตญ <Transition/> ์ปดํฌ๋„ŒํŠธ์˜ ํ•ต์‹ฌ์€ ๊ฐ props ์— ๋”ฐ๋ฅธ ์ƒํƒœ ์ „์ด์ž…๋‹ˆ๋‹ค.
ํ…์ŠคํŠธ๋กœ๋งŒ ์„ค๋ช…ํ•˜๋ฉด ์ž˜ ์ดํ•ด๊ฐ€ ์•ˆ๋˜๋‹ˆ... ์ƒํƒœ ์ „์ด ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๋ณด๋ฉด ์ดํ•ด๊ฐ€ ์‰ฝ์Šต๋‹ˆ๋‹ค

์‚ฌ์‹ค ํ”„๋ก ํŠธ๊ฐ™์€๊ฑฐ ์ข‹์•„ํ•˜๋Š” ์ด์œ ๋„ ์ด๋ ‡๊ฒŒ ์‹œ๊ฐ์ ์œผ๋กœ ๋ญ”๊ฐ€ ์ •๋ฆฌํ•˜๋Š”๊ฒŒ ์ดํ•ด๊ฐ€ ์‰ฝ๊ณ  ์žฌ๋ฐŒ์–ด์„œ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž

Transition ์ƒํƒœ ์ „์ด ๋‹ค์ด์–ด๊ทธ๋žจ
๐Ÿ™‹โ€โ™‚๏ธ ์ƒํƒœ ์ „์ด ๋‹ค์ด์–ด๊ทธ๋žจ์—์„œ [] ๋Š” ๋ญ”๊ฐ€์š”? - Guard

[] ํ‘œ์‹œ๋Š” ๊ฐ€๋“œ(Guard) ์กฐ๊ฑด์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
๊ฐ€๋“œ ์กฐ๊ฑด์€ ์ƒํƒœ ์ „์ด๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ์กฐ๊ฑด์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ€์žฅ ์œ„์— ๋ณด์ด๋Š” [mountOnEnter === true] ๋Š” ์ดˆ๊ธฐ ์ƒํƒœ์—์„œ mountOnEnter props ๊ฐ€ true ์ผ ๋•Œ๋งŒ unmounted ์ƒํƒœ๋กœ ์ „์ด๋œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

2๏ธโƒฃ <CSSTransition/> : CSS ํด๋ž˜์Šค ์ด๋ฆ„ ์•Œ์•„์„œ ๋ถ™์—ฌ์ค„๊ฒŒ ~ โ€‹

<CSSTransition/> ์ปดํฌ๋„ŒํŠธ๋Š” <Transition/> ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™•์žฅํ•œ ์ปดํฌ๋„ŒํŠธ๋กœ, CSS ํด๋ž˜์Šค ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.
์ฐจ์ด์ ์€ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. <Transition/> ์ปดํฌ๋„ŒํŠธ๋Š” ํƒ€์ด๋ฐ๋งŒ ์ œ์–ดํ•˜๊ณ  ์Šคํƒ€์ผ์€ ์ง์ ‘ on* ์ฝœ๋ฐฑ props ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ ์ œ์–ดํ•ด์•ผ ํ•˜์ง€๋งŒ, <CSSTransition/> ์ปดํฌ๋„ŒํŠธ๋Š” ๊ฐ ์ƒํƒœ์— ๋งž๋Š” CSS ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€/์ œ๊ฑฐ ํ•ด์ค€๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์ž๋Š” ๊ทธ๋ƒฅ classNames prefix ๋ฅผ ๊ธฐ์ค€์œผ๋กœ CSS ๋งŒ ์ •์˜ํ•ด๋‘๋ฉด ๋ฉ๋‹ˆ๋‹ค.

tsx
<CSSTransition in={show} timeout={300} classNames="prefix" mountOnEnter unmountOnExit>
    <Component />
</CSSTransition>

์ด๋ ‡๊ฒŒ๋งŒ ์จ๋‘๋ฉด, ์ƒํƒœ ์ „์ด์— ๋”ฐ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํด๋ž˜์Šค๋“ค์ด ์ž๋™์œผ๋กœ ๋ถ™์Šต๋‹ˆ๋‹ค

  1. Enter ์‹œ
  • prefix-enter
  • ์ดํ›„ ๋ฐ”๋กœ prefix-enter-active
  • ์™„๋ฃŒ ํ›„ prefix-enter-done
  1. Exit ์‹œ
  • prefix-exit
  • ์ดํ›„ ๋ฐ”๋กœ prefix-exit-active
  • ์™„๋ฃŒ ํ›„ prefix-exit-done

โš™๏ธ appear: boolean ์— ๋”ฐ๋ฅธ classNames โ€‹

appear:true ๋ฅผ ์„ค์ •ํ•œ ๊ฒฝ์šฐ, ๋งˆ์šดํŠธ ์ดˆ๊ธฐ์—๋„ prefix-appear, prefix-appear-active, prefix-appear-done ํด๋ž˜์Šค๊ฐ€ ์ ์šฉ ๋ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์ฒซ ๋“ฑ์žฅ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์ชผ๊ธˆ ๋‹ค๋ฅด๊ฒŒ ์ค„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿคจ ์™œ ํด๋ž˜์Šค๊ฐ€ ๋‘๋ฒˆ์— ๋‚˜๋ˆ ์„œ ๋ถ™์ง€ (reflow ํŠธ๋ฆญ) โ€‹

์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ์ดˆ๊ธฐ ์ƒํƒœ ํด๋ž˜์Šค ์™€ ํ™œ์„ฑ ์ƒํƒœ ํด๋ž˜์Šค ๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์— ๋ถ™์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

<CSSTransition/> ์ปดํฌ๋„ŒํŠธ๋Š” ๋Œ€๋žต ์ด๋Ÿฐ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค

  1. onEnter ์‹œ์ ์— prefix-enter ํด๋ž˜์Šค๋ฅผ ๋ถ™์—ฌ๋‘”๋‹ค
  2. ๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ ๊ฐ•์ œ๋กœ ํ•œ๋ฒˆ reflow ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค (node.offset* ๊ฐ™์€ ์ฝ”๋“œ๋กœ ๊ฐ•์ œ๋กœ layout ์„ ์ฝ์–ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ˜„์žฌ ์Šคํƒ€์ผ์„ ๊ณ„์‚ฐํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค)
  3. ๊ทธ ๋‹ค์Œ์— prefix-enter-active ํด๋ž˜์Šค๋ฅผ ๋ถ™์ธ๋‹ค (์ตœ์ข… ์Šคํƒ€์ผ)

INFO

๋‘๊ฐœ์˜ ํด๋ž˜์Šค๋ฅผ ๋™์‹œ์— ๋ถ™์ด๋ฉด, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‹œ์ž‘ ์ƒํƒœ๋ฅผ ๋ชป ์žก๊ณ  ๊ทธ๋ƒฅ ์ตœ์ข… ์ƒํƒœ๋กœ ๋ฐ”๋กœ ๋„˜์–ด๊ฐ€ ๋ฒ„๋ฆฌ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด์„œ ์ผ๋ถ€๋Ÿฌ reflow ๋กœ ํƒ€์ด๋ฐ์„ ๋‚˜๋ˆ ์ค๋‹ˆ๋‹ค!

exit ๋„ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค

3๏ธโƒฃ <TransitionGroup/> : ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์˜ ์ž…์žฅ๊ณผ ํ‡ด์žฅ์„ ๊ด€๋ฆฌํ•ด์คญ โ€‹

<Transition/> ๊ณผ <CSSTransition/> ์ปดํฌ๋„ŒํŠธ๋Š” ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ค์–ด์˜ค๊ณ  ๋‚˜๊ฐ€๋Š” ์ˆœ๊ฐ„์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ทผ๋ฐ ์‹ค์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜์—์„œ๋Š” ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์‹œ์— ๋“ค์–ด์˜ค๊ณ  ๋‚˜๊ฐ€๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.
์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์˜ ์ง„์ž…, ํ‡ด์žฅ ํƒ€์ด๋ฐ์„ ๊ด€๋ฆฌํ•ด์ค˜์•ผ ํ•˜๋Š”๊ฑฐ์ฃ 

์ด๊ฑธ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ์นœ๊ตฌ๊ฐ€ <TransitionGroup/> ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

tsx
<TransitionGroup component={null}>
    {items.map((item) => {
        return (
            <CSSTransition
                key={item.id}
                timeout={300}
                classNames="prefix"
                mountOnEnter={true}
                unmountOnExit={true}
            >
                <ItemComponent item={item} />
            </CSSTransition>
        );
    })}
</TransitionGroup>

โš™๏ธ key โ€‹

<TransitionGroup/> ์ปดํฌ๋„ŒํŠธ๋Š” key ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒˆ๋กœ ๋“ค์–ด์™”๋Š”์ง€, ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜๊ฐ”๋Š”์ง€๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.

  1. TransitionGroup ์€ children ์˜ key ๋ชฉ๋ก์„ ๊ธฐ์–ตํ•ด ๋‘ก๋‹ˆ๋‹ค.
  2. ๋‹ค์Œ ๋ Œ๋”๋ง์‹œ key ์˜ ๋ชฉ๋ก์ด ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
    • ์ƒˆ๋กœ ์ƒ๊ธด key : ์ƒˆ๋กœ์šด children ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ค์–ด์˜จ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  in=true ๋กœ ์„ค์ •ํ•ด์„œ enter ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    • ์—†์–ด์ง„ key : ์‚ฌ๋ผ์งˆ children ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜๊ฐ„ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  DOM ์—์„œ ๋ฐ”๋กœ ์ œ๊ฑฐํ•˜์ง€ ์•Š๊ณ , in=false ๋ฅผ ์ฃผ๋ฉด์„œ exit ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋จผ์ € ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. (์‚ฌ๋ผ์ง€๋Š” ์• ๋ฅผ ๋ฐ”๋กœ ์•ˆ์ง€์šฐ๊ณ  ํ‡ด์žฅ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹คํ–‰ํ•  ์‹œ๊ฐ„์„ ์คŒ)
  3. exit ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚˜๋Š” ์‹œ์ ์—๋งŒ state ์—์„œ ์ œ๊ฑฐํ•ด์„œ ์‹ค์ œ DOM ์—์„œ๋„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ component โ€‹

๊ธฐ๋ณธ์ ์œผ๋กœ <TransitionGroup/> ์ปดํฌ๋„ŒํŠธ๋Š” <div> ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
null ์„ ์ฃผ๋ฉด ์ž์‹๋งŒ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4๏ธโƒฃ <SwitchTransition/> : ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ณด์—ฌ์ค„๊บผ์–‘ โ€‹

<SwitchTransition/> ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ญ์ƒ ๋”ฑ ํ•˜๋‚˜์˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์กด์žฌํ•˜๊ณ  ๊ทธ๊ฑธ ๋ฐ”๊ฟ€ ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ•˜๊ณ  ์‹ถ๋‹ค๋Š” ์ƒํ™ฉ์— ์“ฐ์ด๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ๋“ค์–ด, ํ•œ์žฅ ์‚ฌ๋ผ์ง€๊ณ  ๋‹ค๋ฅธ ํ•œ์žฅ์ด ๋‚˜ํƒ€๋‚˜๋Š” ์Šฌ๋ผ์ด๋“œ์‡ผ ๊ฐ™์€ ์ƒํ™ฉ์ด ์žˆ๊ฒ ๋„ค์š”

์ด ์ปดํฌ๋„ŒํŠธ๋Š” mode ๋ผ๋Š” props ๋กœ ์ „ํ™˜ ์ˆœ์„œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ mode : "out-in" | "in-out" โ€‹

  • out-in
    • ํ˜„์žฌ ์ปดํฌ๋„ŒํŠธ์˜ exit ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์™„์ „ํžˆ ๋๋‚ด๊ณ 
    • ๋‹ค ์‚ฌ๋ผ์ง„ ๋‹ค์Œ ์ƒˆ๋กœ์šด ์ž์‹์ด enter ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค.
  • in-out
    • ์ƒˆ๋กœ์šด ์ž์‹์ด enter ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๋จผ์ € ๋“ค์–ด์˜ค๊ณ 
    • ๋‹ค ์ž๋ฆฌ์žก์€ ๋‹ค์Œ์— ๊ธฐ์กด ์ž์‹์—๊ฒŒ exit ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ฒ (์‹คํ—˜) ๋ชจ๋ฐ”์ผ ํ™”๋ฉด ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์ž โ€‹

์ด์ œ๊นŒ์ง€ ์‚ดํŽด๋ณธ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, ๋ชจ๋ฐ”์ผ ํ™”๋ฉด ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์š”๊ตฌ์‚ฌํ•ญ

  • ์ƒˆ๋กœ์šด ํ™”๋ฉด์€ ์˜ค๋ฅธ์ชฝ์—์„œ ์™ผ์ชฝ์œผ๋กœ ์Šฌ๋ผ์ด๋“œ ์ธ
  • ๊ธฐ์กด ํ™”๋ฉด์€ ์™ผ์ชฝ์œผ๋กœ ์Šฌ๋ผ์ด๋“œ ์•„์›ƒ
  • ๋‘ ํ™”๋ฉด์ด ๋™์‹œ์— ์ „ํ™˜๋จ

์—ฌ๋Ÿฌ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์‹œ์— ๋“ค์–ด์˜ค๊ณ  ๋‚˜๊ฐ€๋Š” ์ƒํ™ฉ์ด๋ฏ€๋กœ <TransitionGroup/> ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๊ฒ ๋„ค์š”

tsx
import { TransitionGroup, CSSTransition } from "react-transition-group";
import { Routes, Route, useLocation } from "react-router-dom";

function App() {
    const location = useLocation();

    return (
        <TransitionGroup className="page-wrapper">
            <CSSTransition
                key={location.key}
                classNames="slide"
                timeout={300}
                mountOnEnter
                unmountOnExit
            >
                <Routes location={location}>
                    <Route path="/" element={<HomePage />} />
                    <Route path="/about" element={<AboutPage />} />
                    {/* ... */}
                </Routes>
            </CSSTransition>
        </TransitionGroup>
    );
}

โš ๏ธ ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ํฌ์ธํŠธ โ€‹

  • key={location.key} : ๋ผ์šฐํŒ… ํ• ๋•Œ ๋งˆ๋‹ค key๊ฐ€ ๋ฐ”๋€Œ๋ฏ€๋กœ, <TransitionGroup/> ์ž…์žฅ์—์„œ "์ด์ „ ํ™”๋ฉด ํ•˜๋‚˜", "์ƒˆ ํ™”๋ฉด ํ•˜๋‚˜" ๋ผ๋Š” ๋‘ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™์‹œ์— ๊ด€๋ฆฌ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค
  • mountOnEnter, unmountOnExit : ํ˜„์žฌ ๋ณด์—ฌ์ค„ ํ™”๋ฉด๋งŒ DOM ์— ๋‚จ๊ธฐ๊ณ , ์ด์ „ ํ™”๋ฉด์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚˜๋Š” ์ˆœ๊ฐ„ ์–ธ๋งˆ์šดํŠธ ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค

๐ŸŽจ CSS ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž โ€‹

์ด์ œ prefix ์— ๋”ฐ๋ฅธ CSS ๋งŒ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค

css
/* ๋ถ€๋ชจ: ๊ฒน์ณ์„œ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋„๋ก relative */
.page-wrapper {
    position: relative;
    overflow: hidden;
}

/* ๊ฐ ํŽ˜์ด์ง€๋Š” ๊ฒน์น  ์ˆ˜ ์žˆ๋„๋ก absolute */
.page-wrapper > * {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

/* Enter (์ƒˆ ํ™”๋ฉด์ด ์˜ค๋ฅธ์ชฝ์—์„œ ๋“ค์–ด์˜ด) */
.slide-enter {
    transform: translateX(100%); /* ์‹œ์ž‘: ์˜ค๋ฅธ์ชฝ ๋ฐ”๊นฅ */
}
.slide-enter-active {
    transform: translateX(0%);
    transition: transform 300ms ease;
}
.slide-enter-done {
    transform: translateX(0%);
}

/* Exit (์ด์ „ ํ™”๋ฉด์ด ์™ผ์ชฝ์œผ๋กœ ๋ฐ€๋ ค๋‚˜๊ฐ) */
.slide-exit {
    transform: translateX(0%); /* ์‹œ์ž‘: ํ˜„์žฌ ์œ„์น˜ */
}
.slide-exit-active {
    transform: translateX(-100%); /* ์™ผ์ชฝ ๋ฐ”๊นฅ์œผ๋กœ ๋‚˜๊ฐ */
    transition: transform 300ms ease;
}
.slide-exit-done {
    transform: translateX(-100%);
}

๐Ÿš€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋‚˜? โ€‹

  1. ๋ผ์šฐํŠธ ๋ณ€๊ฒฝ => <CSSTransition key={...}> ๊ฐ€ ์ƒˆ๋กœ์šด ํ™”๋ฉด์„ ํ•˜๋‚˜ ๋” ๋ Œ๋” (TransitionGroup์ด ์ด์ „ ํ™”๋ฉด์€ in={false}, ์ƒˆ ํ™”๋ฉด์€ in={true} ์ƒํƒœ๋กœ ๊ด€๋ฆฌ)
  2. ์ด์ „ ํ™”๋ฉด์—๋Š” .slide-exit / ์ƒˆ ํ™”๋ฉด์—๋Š” .slide-enter ํด๋ž˜์Šค๊ฐ€ ๊ฐ๊ฐ ๋“ค์–ด๊ฐ
  3. ๋‹ค์Œ animation frame์—์„œ .slide-exit-active, .slide-enter-active ํด๋ž˜์Šค๊ฐ€ ๋ถ™์œผ๋ฉด์„œ
    • ์ด์ „ ํ™”๋ฉด: translateX(-100%)๋กœ ์™ผ์ชฝ์œผ๋กœ ๋ฐ€๋ ค๋‚˜๊ฐ€๊ณ 
    • ์ƒˆ ํ™”๋ฉด: translateX(0)๊นŒ์ง€ ์˜ค๋ฅธ์ชฝ์—์„œ ์Šฌ๋ผ์ด๋“œ ์ธ
  4. ์•ฝ 300ms ๋’ค transition์ด ๋๋‚˜๋ฉด
    • ์ด์ „ ํ™”๋ฉด์€ onExited => ์–ธ๋งˆ์šดํŠธ
    • ์ƒˆ ํ™”๋ฉด์€ .slide-enter-done ์ƒํƒœ๋กœ ์ •์ƒ์ ์œผ๋กœ ๋‚จ์Œ

๐Ÿ“– ์ฐธ๊ณ  ์ž๋ฃŒ โ€‹