๐ฑ React Native(Expo)์์ ์ธ๋งํ UI Framework ์ ๋ฆฌ
๐ฏ ์ ์
- ์คํ์ผ์ ์ง์ ์ ์ดํด์ผ ํจ โ 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