posts

๐Ÿ“ฑ React Native(Expo)์—์„œ ์“ธ๋งŒํ•œ UI Framework ์ •๋ฆฌ

Oct 1, 2025 updated Oct 1, 2025 architectureexporeact-native

๐ŸŽฏ ์ „์ œ

  • ์Šคํƒ€์ผ์€ ์ง์ ‘ ์ œ์–ดํ•ด์•ผ ํ•จ โ†’ Headless / ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์นœํ™”๋„ ์ค‘์š”
  • DOM ๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ฐฐ์ œ
  • Expo ํ˜ธํ™˜์„ฑ ๊ณ ๋ ค

๐Ÿ”‘ ์ฃผ์š” ํ›„๋ณด ๋น„๊ต

ํ›„๋ณด ์„ฑ๊ฒฉ ํ—ค๋“œ๋ฆฌ์Šค/์ ‘๊ทผ์„ฑ ํ…Œ๋งˆ/์Šคํƒ€์ผ ์ฒด๊ณ„ Expo ํ˜ธํ™˜ RN/Web ๊ณต์œ  ๋น„๊ณ 
React Native Aria Headless ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ โญโญโญโญ ์Šคํƒ€์ผ ์—†์Œ (์ง์ ‘ ์ž…ํž˜) โœ… โ–ณ ์ ‘๊ทผ์„ฑยทํฌ์ปค์Šค ๋กœ์ง ์ œ๊ณต
gluestack-ui Aria ๊ธฐ๋ฐ˜ UI ํ‚ท โญโญโญโญ Styled System + Tokens โœ… โ–ณ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์œ ์—ฐ
NativeWind Tailwind ์œ ํ‹ธ๋ฆฌํ‹ฐ โ€” Tailwind ์œ ํ‹ธ โœ… โ–ณ ๋น ๋ฅธ ์Šคํƒ€์ผ๋ง, ํ† ํฐํ™” ์šฉ์ด
Shopify Restyle ํƒ€์ž… ์•ˆ์ „ ์Šคํƒ€์ผ๋ง โ€” Theme + Variant โœ… โ–ณ ํ† ํฐ/Variant ์„ค๊ณ„ ์ตœ์ 
Tamagui RN/Web ํ†ตํ•ฉ UI/์Šคํƒ€์ผ โญโญโญ Variants + Tokens โœ… โœ… RN/Web ํฌ๋กœ์Šค, ์ตœ์ ํ™”
Moti ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ ˆ์ด์–ด โ€” โ€” โœ… โœ… Reanimated ๊ธฐ๋ฐ˜
@gorhom/bottom-sheet ๋ฐ”ํ…€์‹œํŠธ ์ „์šฉ โ€” โ€” โœ… โ–ณ ์„ฑ๋Šฅ/์ œ์Šค์ฒ˜ ํ‘œ์ค€
React Native Paper Material UI ํ‚ท โญโญ ํ…Œ๋งˆ ๋‚ด์žฅ โœ… โ–ณ ๋น ๋ฅธ ๊ตฌ์ถ•, ๋จธํ‹ฐ๋ฆฌ์–ผ ๊ณ ์ •
React Native Elements ๋ฒ”์šฉ ์ปดํฌ๋„ŒํŠธ โญโญ ํ…Œ๋งˆ ๋‚ด์žฅ โœ… โ–ณ ์Šคํƒ€์ผ ๊ฐ„์„ญ ์žˆ์Œ
UI Kitten Eva Design ๊ธฐ๋ฐ˜ โญโญ ํ…Œ๋งˆ/์Šคํ‚ค๋งˆ โœ… โ–ณ ํ…Œ๋งˆ ๊ฐ•๋ ฅ, ์œ ์—ฐ์„ฑ ๋‚ฎ์Œ
Dripsy styled-system ๊ณ„์—ด โ€” Theme + sx โœ… โ–ณ ๊ฐ€๋ณ๊ณ  ๋‹จ์ˆœ

๐Ÿš€ ๊ถŒ์žฅ ์Šคํƒ (์ƒํ™ฉ๋ณ„)

1) ๋””์ž์ธ ์™„์ „ ์†Œ์œ  + ์ ‘๊ทผ์„ฑ ๋ณด์žฅ

  • React Native Aria + Shopify Restyle + NativeWind
  • @gorhom/bottom-sheet, Moti๋กœ ๋ณด๊ฐ•
  • โœ… ์ž์œ ๋„ ์ตœ๊ณ , โŒ ์ดˆ๊ธฐ ๊ณต์ˆ˜ ๋†’์Œ

2) ํ—ค๋“œ๋ฆฌ์Šค์— ๊ฐ€๊นŒ์šด ํ‚ท

  • gluestack-ui + (NativeWind or ์ž์ฒด ์Šคํƒ€์ผ)
  • โœ… ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ/๋ณ€ํ˜• ๊ตฌ์กฐ ์ž˜ ์„ค๊ณ„๋จ

3) RN + Web ์ฝ”๋“œ ๊ณต์œ  ํ•„์š”

  • Tamagui + Moti
  • โœ… RN/Web ๋™์‹œ ์ง€์›, โŒ ๋Ÿฌ๋‹์ปค๋ธŒ ์žˆ์Œ

4) ํƒ€์ž„ํˆฌ๋งˆ์ผ“ ์ตœ์šฐ์„ 

  • React Native Paper ๋˜๋Š” Elements๋กœ ๋น ๋ฅธ ๊ตฌ์ถ•
  • ํ•ต์‹ฌ๋งŒ ํ—ค๋“œ๋ฆฌ์Šค ์žฌ์ž‘์„ฑ
  • โœ… ์†๋„, โŒ ๋””์ž์ธ ์œ ์—ฐ์„ฑ ํ•œ๊ณ„

๐Ÿงฉ ์กฐํ•ฉ ์˜ˆ์‹œ (์˜์‚ฌ์ฝ”๋“œ)

import { useButton } from '@react-native-aria/button';
import { createTheme, createBox, createText } from '@shopify/restyle';

const theme = createTheme({
  colors: { primary: '#3b82f6', text: '#111827', bg: '#fff' },
  spacing: { s: 8, m: 12, l: 16 },
  radii: { s: 8, m: 12, l: 16 },
});
const Box = createBox<typeof theme>();
const Text = createText<typeof theme>();

function Button(props) {
  const ref = React.useRef(null);
  const { buttonProps, isPressed } = useButton(props, ref);
  return (
    <Box
      {...buttonProps}
      ref={ref}
      bg={isPressed ? 'primary' : 'bg'}
      padding="m"
      borderRadius="m"
      className="shadow" // NativeWind ์œ ํ‹ธ ์ ์šฉ
    >
      <Text color="text">{props.children}</Text>
    </Box>
  );
}

๐Ÿ› ๏ธ ์ถ”๊ฐ€ ๊ณ ๋ ค ์š”์†Œ

1. ์Šคํƒ€์ผ๋ง ์ „๋žต

  • NativeWind: Tailwind ์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ธฐ๋ฐ˜ โ†’ ํŒ€์ด ์›น์—์„œ Tailwind ๊ฒฝํ—˜ ์žˆ๋‹ค๋ฉด ๊ฐ€์žฅ ๋น ๋ฅธ ์ ์‘
  • Restyle: ํƒ€์ž… ์•ˆ์ „ํ•œ ํ† ํฐ ๊ด€๋ฆฌ โ†’ ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ์ฝ”๋“œ๋กœ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Œ
  • Tamagui: RN/Web์„ ๋ชจ๋‘ ์ง€์›ํ•˜๋ฉด์„œ๋„ ๋นŒ๋“œ ์‹œ ์ตœ์ ํ™” โ†’ ํฌ๋กœ์Šคํ”Œ๋žซํผ ์ง€ํ–ฅ ์‹œ ๊ฐ•์ 

2. ์ ‘๊ทผ์„ฑ (A11y)

  • React Native Aria: ์›น ARIA ํŒจํ„ด์„ RN์— ์ด์‹ํ•œ ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ โ†’ ์Šคํฌ๋ฆฐ๋ฆฌ๋”, ํฌ์ปค์Šค, ํ‚ค๋ณด๋“œ ๋‚ด๋น„๊ฒŒ์ด์…˜๊นŒ์ง€ ์ง€์›
  • gluestack-ui: Aria ์œ„์— ์–น์€ ๊ตฌ์„ฑ์š”์†Œ ๋ชจ์Œ โ†’ ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผ์„ฑ ๋ณด์žฅ ๊ฐ€๋Šฅ

3. ์• ๋‹ˆ๋ฉ”์ด์…˜

  • Moti: Reanimated 2/3 ๊ธฐ๋ฐ˜, ์„ ์–ธ์  API โ†’ Skeleton, Presence, Transition ๋“ฑ์— ์œ ์šฉ
  • Reanimated + Gesture Handler: ๋กœ์šฐ๋ ˆ๋ฒจ ์ œ์–ด ํ•„์š”ํ•  ๋•Œ ํ•„์ˆ˜

4. ๋„ค์ดํ‹ฐ๋ธŒ ํ™•์žฅ

  • @gorhom/bottom-sheet, @gorhom/portal โ†’ ์ปค๋ฎค๋‹ˆํ‹ฐ ํ‘œ์ค€ ์ˆ˜์ค€์˜ ์•ˆ์ •์„ฑ๊ณผ ์„ฑ๋Šฅ
  • react-native-skia โ†’ ์ปค์Šคํ…€ ๋“œ๋กœ์ž‰/์• ๋‹ˆ๋ฉ”์ด์…˜

๐Ÿ“š ์ถ”์ฒœ ์กฐํ•ฉ (์‹ค์ „ ์˜ˆ)

๐Ÿ’ก ์ปค์Šคํ…€ยท๋””์ž์ธ์‹œ์Šคํ…œ ๊ตฌ์ถ• ํŒ€

  • ์Šคํƒ€์ผ: Restyle (ํ† ํฐ/ํƒ€์ž… ์•ˆ์ „) + NativeWind (์œ ํ‹ธ ์†๋„)
  • UI ๋กœ์ง: React Native Aria
  • ์• ๋‹ˆ๋ฉ”์ด์…˜: Moti
  • ํŠนํ™” ์ปดํฌ๋„ŒํŠธ: @gorhom/bottom-sheet

โšก MVP/ํ”„๋กœํ† ํƒ€์ž…

  • ์Šคํƒ€์ผ & UI: gluestack-ui
  • ์• ๋‹ˆ๋ฉ”์ด์…˜: Moti
  • ์ถ”๊ฐ€: ํ•„์š” ์‹œ NativeWind๋กœ ๋ณด์™„

๐ŸŒ RN + Web ๊ณต์œ 

  • UI & ์Šคํƒ€์ผ: Tamagui
  • ์• ๋‹ˆ๋ฉ”์ด์…˜: Moti
  • ๊ณต์œ  ์ „๋žต: Variants + Tokens ๊ธฐ๋ฐ˜ โ†’ RN/Web ๋™์‹œ์— ๊ด€๋ฆฌ

โœ… ๊ฒฐ๋ก 

  • ๋””์ž์ธ ์ž์œ ๋„๊ฐ€ ์ตœ์šฐ์„ ์ด๋ฉด โ†’ RN Aria + Restyle + NativeWind
  • ๊ตฌ์ถ• ์†๋„์™€ ์ ‘๊ทผ์„ฑ ๋ณด์žฅ์ด ํ•„์š”ํ•˜๋ฉด โ†’ gluestack-ui
  • RN๊ณผ Web ์ฝ”๋“œ ๊ณต์œ ๊ฐ€ ํ•„์ˆ˜๋ฉด โ†’ Tamagui
  • ์ดˆ๊ธฐ ์ถœ์‹œ ์†๋„๊ฐ€ ์ค‘์š”ํ•˜๋ฉด โ†’ React Native Paper/Elements

๐Ÿ‘‰ ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” Headless(Aria) + Token ๊ธฐ๋ฐ˜(Restyle/NativeWind) ์กฐํ•ฉ์ด ๊ฐ€์žฅ ์œ ์—ฐํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋†’์Œ.

@startuml
start

:React Native UI ์„ ํƒ ํ”Œ๋กœ์šฐ;

if ("์ถœ์‹œ ์†๋„๊ฐ€ ๊ฐ€์žฅ ์ค‘์š”?") then (์˜ˆ)
  :React Native Paper ๋˜๋Š” Elements;
  stop
else (์•„๋‹ˆ์˜ค)
  if ("RN & Web ์ฝ”๋“œ ๊ณต์œ ๊ฐ€ ํ•„์ˆ˜?") then (์˜ˆ)
    :Tamagui + Moti;
    stop
  else (์•„๋‹ˆ์˜ค)
    if ("๋””์ž์ธ ์ž์œ ๋„/๋ธŒ๋žœ๋”ฉ ํ†ต์ œ๊ฐ€ ์ตœ์šฐ์„ ?") then (์˜ˆ)
      :Headless ์กฐํ•ฉ ์‚ฌ์šฉ;
      :React Native Aria (๋กœ์ง/์ ‘๊ทผ์„ฑ);
      :Shopify Restyle (ํ† ํฐ/ํƒ€์ž… ์•ˆ์ „);
      :NativeWind (์œ ํ‹ธ๋ฆฌํ‹ฐ ์Šคํƒ€์ผ);
      :๋ณด๊ฐ•: @gorhom/bottom-sheet, Moti;
      stop
    else (์•„๋‹ˆ์˜ค)
      if ("์ ๋‹นํ•œ ์ƒ์‚ฐ์„ฑ๊ณผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•„์š”?") then (์˜ˆ)
        :gluestack-ui;
        :ํ•„์š”์‹œ NativeWind ๋ณ‘ํ–‰;
        stop
      else (์•„๋‹ˆ์˜ค)
        :์š”๊ตฌ์‚ฌํ•ญ ์žฌํ‰๊ฐ€(์ƒ์ถฉ ๊ฐ€๋Šฅ);
        stop
      endif
    endif
  endif
endif

@enduml