앱에서 고유 ID(Unique ID)를 생성해야 할 때, 가장 널리 쓰이는 방식이 바로 UUID (Universally Unique Identifier)입니다.
Flutter에서는 uuid 패키지를 통해 간편하게 다양한 방식의 UUID를 생성할 수 있습니다.

이 글에서는:

  • uuid 패키지 설치 방법
  • UUID 버전(v1, v4, v5)의 차이점
  • 실전 예제와 사용 팁

까지 하나씩 정리해보겠습니다.


📦 1. uuid 패키지 설치하기

flutter pub add uuid

혹은 pubspec.yaml에 직접 추가:

dependencies:
  uuid: ^4.2.1  # 최신 버전 확인

그리고 import:

import 'package:uuid/uuid.dart';

🧬 2. UUID 버전별 차이점 정리

버전 방식 설명 특징

v1 Time-based 시간 + 기기(MAC 주소) 기반 순차적 생성 가능, MAC 유출 위험
v2 DCE Security v1 + 사용자/그룹 ID 거의 사용되지 않음
v3 Name-based (MD5) 문자열 + 네임스페이스 → MD5 해시 입력이 같으면 UUID도 같음
v4 Random 무작위(random) 값 기반 일반적으로 가장 많이 사용됨
v5 Name-based (SHA-1) v3와 동일하나 SHA-1 해시 사용 더 강력한 보안 해시

⚙️ 3. UUID 생성 예제 (Dart 코드)

final uuid = Uuid();

// ✅ v1 – 시간 + MAC 주소 기반
String idV1 = uuid.v1(); // 예: 'f64c8b26-3e46-11ec-8d3d-0242ac130003'

// ✅ v4 – 랜덤 기반 (가장 많이 사용)
String idV4 = uuid.v4(); // 예: 'c3c3bc94-b070-4787-9f7d-b2592b4d51ef'

// ✅ v5 – 고정된 문자열로 UUID 생성 (SHA-1)
String idV5 = uuid.v5(Uuid.NAMESPACE_URL, 'https://example.com');

📌 v3도 동일 방식으로 가능:

String idV3 = uuid.v3(Uuid.NAMESPACE_URL, 'https://example.com'); // MD5 기반

✅ 실전에서는 언제 어떤 버전?

상황 추천 버전 이유

단순한 고유 ID가 필요할 때 v4 간단하고 충돌 확률 매우 낮음
시간 순서대로 정렬이 필요할 때 v1 생성 시간 정보 포함됨
입력값이 항상 같을 때 동일한 ID가 필요할 때 v5 (또는 v3) 해시 기반 고정 UUID 생성

❗️주의할 점 (v1의 MAC 주소 노출)

  • v1 UUID는 내부적으로 기기의 MAC 주소를 사용합니다.
  • 이 정보는 유출되면 기기 식별 가능성이 있으므로 보안이 중요한 앱에서는 피하는 것이 좋습니다.
  • 대부분의 경우 v4를 사용하는 것이 안전하고 충분합니다.

✨ 실전 사용 예: 모델 ID 생성

class Task {
  final String id;
  final String title;

  Task({required this.title}) : id = const Uuid().v4();
}

→ 매 Task 인스턴스를 생성할 때 자동으로 고유한 UUID를 부여합니다.


🧩 요약

  • uuid 패키지를 설치하면 다양한 방식으로 고유 식별자를 생성할 수 있음
  • 대부분의 경우 v4 (랜덤 기반)가 안정적이고 보편적
  • v5는 동일 입력 → 동일 UUID가 필요할 때 매우 유용
반응형

📝 요약 설명 

Flutter로 macOS 앱을 개발할 때 기본 창 크기를 설정하고, 앱을 "항상 위에 고정(Always on Top)"으로 띄우는 방법을 소개합니다. 에디터와 앱을 나란히 띄우기 위한 실사용 팁!


🖥️ 문제 상황

Flutter로 macOS 앱을 개발할 때, 앱을 항상 에디터 옆에 띄워놓고 싶었어요.
하지만 앱을 실행하면 매번 이상한 위치에 뜨거나 다른 창 뒤로 가버리는 게 너무 불편했습니다.

그래서 macOS 앱을 항상 위에 고정시키고, 기본 창 크기도 지정하는 방법을 정리해봤습니다.


🔧 해결 방법 요약

  • MainFlutterWindow.swift에서 macOS 앱의 창 속성을 직접 설정할 수 있습니다.
  • 여기서 창 크기, 창 위치, 항상 위에 고정(floating)을 커스터마이징하면 됩니다.

📁 수정할 파일

macos/Runner/MainFlutterWindow.swift


✅ 최종 코드 예시

import Cocoa
import FlutterMacOS

class MainFlutterWindow: NSWindow {
  override func awakeFromNib() {
    let flutterViewController = FlutterViewController()

    // 원하는 창 크기
    let windowWidth: CGFloat = 800
    let windowHeight: CGFloat = 600

    // 화면 중앙에 창 위치 설정
    if let screenFrame = NSScreen.main?.frame {
      let originX = (screenFrame.width - windowWidth) / 2
      let originY = (screenFrame.height - windowHeight) / 2
      let newFrame = NSRect(x: originX, y: originY, width: windowWidth, height: windowHeight)
      self.setFrame(newFrame, display: true)
    }

    // 항상 위에 설정
    self.level = .floating

    self.contentViewController = flutterViewController

    RegisterGeneratedPlugins(registry: flutterViewController)

    super.awakeFromNib()
  }
}

📌 추가 팁

  • self.level = .floating → 이걸 설정하면 앱 창이 항상 다른 창 위에 떠 있어요.
    특히 코드 에디터 + 앱 창 나란히 테스트할 때 엄청 편합니다.
  • self.setFrame(...) → 앱의 기본 위치와 크기를 지정할 수 있습니다.

✨ 마무리

에디터와 앱을 동시에 띄워놓고 작업할 때, 이 설정 하나로 스트레스가 확 줄었어요.
macOS 앱에서 UI 디버깅 자주 하시는 분들께 꼭 추천드립니다.


필요하면 이미지나 gif 첨부용 예시도 만들어줄 수 있어.
티스토리에 바로 붙여넣을 HTML 포맷도 원해?

반응형

🔍 문제 상황

React에서 컴포넌트 리스트를 .map()으로 렌더링할 때, 다음과 같은 에러가 자주 발생합니다:

Warning: Each child in a list should have a unique 'key' prop.

그래서 보통은 아래처럼 key를 추가하죠.

items.map((item) => (
  <MyComponent key={item.id} item={item} />
))

하지만! 정상적으로 key를 넣었음에도 불구하고 위와 같은 경고가 계속 발생하는 경우가 있습니다.


❓ 대체 왜?

이 문제의 진짜 원인은… 바로 Fragment (<>...</>) 때문입니다.


💥 문제 코드 예시

items.map((item) => (
  <>
    <MyComponent key={item.id} item={item} />
  </>
))

위 코드는 MyComponent에 key를 넣었지만,
사실상 무시되고 있습니다.

왜냐하면, React는 .map()으로 직접 리턴하는 최상위 노드에 key가 있는지를 보기 때문입니다.

즉, 위의 코드는:

  • Fragment가 최상위인데 key 없음 → ❌
  • 내부 MyComponent에 있는 key → 무시됨 → ❌ 경고 발생

✅ 해결 방법

방법 1: Fragment 없애기 (권장)

items.map((item) => (
  <MyComponent key={item.id} item={item} />
))

방법 2: Fragment에 key 넣기 (가능하지만 비권장)

items.map((item) => (
  <React.Fragment key={item.id}>
    <MyComponent item={item} />
  </React.Fragment>
))

💡 실전 예: React Native ScrollView 안에서 발생한 사례

<ScrollView>
  {items.map((item) => (
    <>
      <TimelineItem key={item.id} item={item} />
    </>
  ))}
</ScrollView>

이 구조에서 Fragment가 key를 무시하면서 경고가 발생했습니다.
→ Fragment를 제거하거나, key를 Fragment로 올리면 해결됩니다!


🧠 정리

구조 key 인식 여부 추천 여부

<Component key={...} /> ✅ 강력 추천
<><Component key={...} /></> ❌ 경고 발생
<Fragment key={...}><Component /></Fragment> ⚠️ 가능하지만 가독성 저하 우려

✅ 마무리

React에서 key 경고가 사라지지 않을 때는
"내가 넣은 key가 최상위 엘리먼트에 있는가?" 를 꼭 확인하세요!

특히, 아무 생각 없이 쓴 <> ... </> 가 문제의 원인일 수 있습니다 😉

 

 

반응형

⚙️ 시작: uuid 설치 및 사용

React Native 앱에서 일정 추가 시 고유 ID가 필요해서
보통 많이 사용하는 uuid 패키지를 설치하고 다음처럼 사용했습니다:

pnpm add uuid
import { v4 as uuidv4 } from 'uuid';

const id = uuidv4();

그리고 이 값을 일정 객체에 사용했죠:

addSchedule({
  id: uuidv4(), // ❌ 여기서 에러 발생
  title: '주사 맞기',
  ...
});

그런데 앱을 실행하자 다음과 같은 에러가 발생했습니다:

Error: crypto.getRandomValues() not supported.

👀 이 에러는 웹 브라우저에서 제공하는 crypto.getRandomValues()를
React Native에서 사용할 수 없기 때문에 발생한 것입니다.


🔍 원인 요약

  • uuid 패키지는 웹 브라우저 환경을 기준으로 만들어졌고
  • 내부적으로 crypto.getRandomValues()를 사용함
  • 하지만 React Native는 웹이 아니므로 해당 API가 없음

⚠️ 원인: uuid 패키지의 내부 동작

많은 개발자들이 사용하는 uuid 패키지는 브라우저 환경에서 잘 작동합니다.
하지만 이 패키지의 v4 구현은 내부적으로 crypto.getRandomValues()를 사용하기 때문에 React Native에서는 동작하지 않습니다.


💡 해결 방법: React Native에 맞는 UUID 패키지 사용

✅ 방법 1: react-native-uuid 사용 (권장)

  1. 설치
pnpm add react-native-uuid
  1. 사용법
import uuid from 'react-native-uuid';

const id = uuid.v4() as string;

uuid.v4()는 string | number[] 형태를 반환하므로, 타입스크립트에서는 as string으로 명시해주는 것이 좋습니다.


✅ 방법 2: expo-crypto 사용 (Expo 사용자)

  1. 설치
pnpm add expo-crypto
  1. 사용법
import * as Crypto from 'expo-crypto';

const id = await Crypto.getRandomUUID();

이 방식은 async/await이 필요하기 때문에 함수 구조를 비동기적으로 변경해야 합니다.


🧠 정리

패키지 RN 지원 여부 특징

uuid ❌ (웹 전용) crypto.getRandomValues() 사용 → 에러 발생
react-native-uuid 동기 방식, 간단하게 사용 가능
expo-crypto ✅ (Expo 전용) 공식 지원, 비동기 방식 필요

✨ 마무리

React Native에서는 브라우저의 crypto API를 사용할 수 없습니다.
따라서 UUID가 필요할 경우, RN에 맞는 패키지를 선택해서 사용하는 것이 중요합니다.

이번 경험을 통해 웹과 네이티브의 차이를 잘 이해하게 되었고, 앞으로는 환경에 맞는 라이브러리 선택이 얼마나 중요한지 다시 한 번 느꼈습니다. 💪

 

반응형

 

🔍 에러 상황

React Native 프로젝트에서 아래와 같은 코드를 작성했습니다:

import { colors } from '@repo/constants';

const styles = StyleSheet.create({
  container: {
    backgroundColor: colors.green[900],
  },
});

그런데 앱을 실행했더니 다음과 같은 에러가 발생했습니다:

❌ Cannot read property 'green' of undefined

🤔 왜 이런 에러가 발생할까?

에러의 원인은 default export를 named import로 불러왔기 때문입니다.

📁 colors.ts

const colors = {
  green: {
    900: '#064E3B'
  },
  ...
};

export default colors;

📁 index.ts

export * from './colors';

colors.ts는 default export지만,
index.ts에서는 named export로 넘기지 않았기 때문에
import { colors } from '@repo/constants'는 undefined가 됩니다.


✅ 해결 방법 3가지


✅ 방법 1. default → named export로 바꾸기 (가장 추천)

// colors.ts
export const colors = {
  ...
};
// index.ts
export * from './colors';
// 사용처
import { colors } from '@repo/constants';

✅ 가장 직관적이고 안전한 방식입니다.


✅ 방법 2. default export 유지하면서 export 이름 지정

// colors.ts
const colors = { ... };
export default colors;
// index.ts
export { default as colors } from './colors';
// 사용처
import { colors } from '@repo/constants';

✅ default export를 유지하면서도 named import처럼 쓸 수 있습니다.


⚠️ 방법 3. default import로 직접 경로 import (비추천)

import colors from '@repo/constants/colors';

⚠️ 경로를 직접 노출하게 되어 리팩토링이 어려워질 수 있습니다.


💡 결론

목적 추천 방식

import { colors } 하고 싶다 export const colors = ... 사용
default export 꼭 유지하고 싶다 export { default as colors } 추가
하위 경로로 import 가능은 하지만 비추천

 

반응형

React Native로 앱을 만들다 보면 버튼이나 카드처럼 누를 수 있는 영역을 만들 때 TouchableOpacity 또는 Pressable 중 어떤 걸 써야 할지 고민될 때가 있다.

두 컴포넌트 모두 유저의 터치 입력을 감지하는 역할을 하지만, 사용 방식과 유연성에서 차이가 있다.
이 글에서는 두 컴포넌트의 차이점과 사용 시점을 정리해 본다.


🔸 TouchableOpacity

TouchableOpacity는 React Native 초창기부터 존재한 컴포넌트로,
사용자가 터치하면 **불투명도(opacity)**가 낮아지는 시각적 피드백을 준다.

import { TouchableOpacity, Text } from 'react-native';

<TouchableOpacity onPress={() => alert('Pressed')}>
  <Text>Click me</Text>
</TouchableOpacity>

✅ 장점

  • 사용법이 매우 간단하다
  • 기본적인 눌림 효과(투명도 변화)가 내장돼 있어 빠르게 UI 구성 가능

❌ 단점

  • 스타일 커스터마이징이 제한적이다
  • 눌림 효과는 opacity 하나뿐

🔸 Pressable

Pressable은 React Native 0.63부터 도입된 최신 터치 컴포넌트로,
눌림 상태에 따라 스타일을 완전히 커스터마이징할 수 있는 것이 가장 큰 특징이다.

import { Pressable, Text } from 'react-native';

<Pressable
  onPress={() => alert('Pressed')}
  style={({ pressed }) => ({
    backgroundColor: pressed ? '#eee' : '#fff',
    padding: 10,
  })}
>
  <Text>Click me</Text>
</Pressable>

✅ 장점

  • pressed 상태를 이용해 배경색, 그림자, 테두리 등 다양한 스타일 변경 가능
  • 눌렀을 때 외에도 onPressIn, onPressOut 등 다양한 콜백 제공

❌ 단점

  • 코드가 상대적으로 길고 복잡할 수 있다
  • 간단한 버튼엔 오히려 과할 수 있음

🆚 한눈에 비교

항목 TouchableOpacity Pressable
도입 시기 오래됨 최신 (RN 0.63+)
눌림 효과 opacity 자동 적용 자유롭게 스타일 커스터마이징
스타일 제어 제한적 pressed로 조건부 스타일링
콜백 이벤트 기본적 onPressIn, onPressOut 등 다양
추천 사용처 간단한 터치 UI 다양한 효과가 필요한 경우

💡 언제 어떤 걸 써야 할까?

  • 단순한 버튼, 리스트 아이템 → TouchableOpacity
  • 눌렀을 때 배경색, 테두리 등 커스텀 UI 효과 → Pressable
  • ✅ 최신 React Native 기준의 코드 스타일을 따르고 싶다면 → Pressable 선호

✍️ 마무리

React Native에서 두 컴포넌트는 결국 터치 이벤트 처리를 위한 도구지만,
개발자의 의도에 따라 적절히 선택하는 것이 중요하다.

  • 빠르게 만들고 싶다면 TouchableOpacity
  • 정교한 UI 피드백이 필요하면 Pressable

 

반응형

 

🎨 Tailwind 색상 팔레트를 React Native로!

TailwindCSS의 색상 시스템은 직관적인 이름(blue_500, gray_900)과 단계별 명확한 색감으로 많은 개발자에게 사랑받고 있습니다.
React Native에서도 동일한 색상 시스템을 구성하면 일관된 디자인 시스템 구축, 다크모드 대응, alpha 값 처리 등 많은 이점을 누릴 수 있습니다.


🌈 전체 색상표 (100 ~ 900)

아래는 TailwindCSS 기준으로 React Native에서 사용할 수 있도록 구성한 색상 팔레트입니다.

글 하단에 요약된 코드를 넣어 두었습니다.

✅ 각 색상은 colors.색상이름[단계] 형태로 접근합니다.
예: colors.blue[500], colors.red[900]


🔵 blue

단계 HEX 코드

100 #DBEAFE
200 #BFDBFE
300 #93C5FD
400 #60A5FA
500 #3B82F6
600 #2563EB
700 #1D4ED8
800 #1E40AF
900 #1E3A8A

⚫ gray

단계 HEX 코드

100 #F3F4F6
200 #E5E7EB
300 #D1D5DB
400 #9CA3AF
500 #6B7280
600 #4B5563
700 #374151
800 #1F2937
900 #111827

🔴 red

단계 HEX 코드

100 #FEE2E2
200 #FECACA
300 #FCA5A5
400 #F87171
500 #EF4444
600 #DC2626
700 #B91C1C
800 #991B1B
900 #7F1D1D

🟢 green

단계 HEX 코드

100 #D1FAE5
200 #A7F3D0
300 #6EE7B7
400 #34D399
500 #10B981
600 #059669
700 #047857
800 #065F46
900 #064E3B

🟡 yellow

단계 HEX 코드

100 #FEF9C3
200 #FEF08A
300 #FDE047
400 #FACC15
500 #EAB308
600 #CA8A04
700 #A16207
800 #854D0E
900 #713F12

🟣 purple

단계 HEX 코드

100 #F3E8FF
200 #E9D5FF
300 #D8B4FE
400 #C084FC
500 #A855F7
600 #9333EA
700 #7E22CE
800 #6B21A8
900 #581C87

🌸 pink

단계 HEX 코드

100 #FCE7F3
200 #FBCFE8
300 #F9A8D4
400 #F472B6
500 #EC4899
600 #DB2777
700 #BE185D
800 #9D174D
900 #831843

🧊 cyan

단계 HEX 코드

100 #CFFAFE
200 #A5F3FC
300 #67E8F9
400 #22D3EE
500 #06B6D4
600 #0891B2
700 #0E7490
800 #155E75
900 #164E63

 


 

const colors = {
  gray: {
    100: '#F3F4F6',
    200: '#E5E7EB',
    300: '#D1D5DB',
    400: '#9CA3AF',
    500: '#6B7280',
    600: '#4B5563',
    700: '#374151',
    800: '#1F2937',
    900: '#111827',
  },
  blue: {
    100: '#DBEAFE',
    200: '#BFDBFE',
    300: '#93C5FD',
    400: '#60A5FA',
    500: '#3B82F6',
    600: '#2563EB',
    700: '#1D4ED8',
    800: '#1E40AF',
    900: '#1E3A8A',
  },
  green: {
    100: '#D1FAE5',
    200: '#A7F3D0',
    300: '#6EE7B7',
    400: '#34D399',
    500: '#10B981',
    600: '#059669',
    700: '#047857',
    800: '#065F46',
    900: '#064E3B',
  },
  red: {
    100: '#FEE2E2',
    200: '#FECACA',
    300: '#FCA5A5',
    400: '#F87171',
    500: '#EF4444',
    600: '#DC2626',
    700: '#B91C1C',
    800: '#991B1B',
    900: '#7F1D1D',
  },
  yellow: {
    100: '#FEF9C3',
    200: '#FEF08A',
    300: '#FDE047',
    400: '#FACC15',
    500: '#EAB308',
    600: '#CA8A04',
    700: '#A16207',
    800: '#854D0E',
    900: '#713F12',
  },
  indigo: {
    100: '#E0E7FF',
    200: '#C7D2FE',
    300: '#A5B4FC',
    400: '#818CF8',
    500: '#6366F1',
    600: '#4F46E5',
    700: '#4338CA',
    800: '#3730A3',
    900: '#312E81',
  },
  purple: {
    100: '#F3E8FF',
    200: '#E9D5FF',
    300: '#D8B4FE',
    400: '#C084FC',
    500: '#A855F7',
    600: '#9333EA',
    700: '#7E22CE',
    800: '#6B21A8',
    900: '#581C87',
  },
  pink: {
    100: '#FCE7F3',
    200: '#FBCFE8',
    300: '#F9A8D4',
    400: '#F472B6',
    500: '#EC4899',
    600: '#DB2777',
    700: '#BE185D',
    800: '#9D174D',
    900: '#831843',
  },
  teal: {
    100: '#CCFBF1',
    200: '#99F6E4',
    300: '#5EEAD4',
    400: '#2DD4BF',
    500: '#14B8A6',
    600: '#0D9488',
    700: '#0F766E',
    800: '#115E59',
    900: '#134E4A',
  },
  orange: {
    100: '#FFEDD5',
    200: '#FED7AA',
    300: '#FDBA74',
    400: '#FB923C',
    500: '#F97316',
    600: '#EA580C',
    700: '#C2410C',
    800: '#9A3412',
    900: '#7C2D12',
  },
  lime: {
    100: '#ECFCCB',
    200: '#D9F99D',
    300: '#BEF264',
    400: '#A3E635',
    500: '#84CC16',
    600: '#65A30D',
    700: '#4D7C0F',
    800: '#3F6212',
    900: '#365314',
  },
  cyan: {
    100: '#CFFAFE',
    200: '#A5F3FC',
    300: '#67E8F9',
    400: '#22D3EE',
    500: '#06B6D4',
    600: '#0891B2',
    700: '#0E7490',
    800: '#155E75',
    900: '#164E63',
  },
  zinc: {
    100: '#F4F4F5',
    200: '#E4E4E7',
    300: '#D4D4D8',
    400: '#A1A1AA',
    500: '#71717A',
    600: '#52525B',
    700: '#3F3F46',
    800: '#27272A',
    900: '#18181B',
  },
};

export default colors;

 

🏁 마무리

Tailwind의 색상 시스템은 미적으로 뛰어나고 확장도 쉽습니다.
React Native에서도 이 구조를 도입하면 다크모드 대응은 물론, 협업 시에도 큰 도움이 됩니다.

반응형

🔥 소개

TailwindCSS 스타일의 색상 시스템은 직관적이고 확장성이 뛰어나 React Native에도 잘 어울립니다.
이번 글에서는 blue_100, gray_900처럼 단계별 색상 정의, 다크모드 대응, alpha 값 추가, 그리고 자동완성 가능한 타입 정의까지 한 번에 구성하는 방법을 소개합니다.


📁 폴더 구조

먼저, 색상 관련 파일을 모아둘 theme 디렉토리를 만들어 아래처럼 구성합니다.

src/
└── theme/
    ├── colors.ts              # Tailwind 색상 팔레트
    ├── darkColors.ts          # 다크모드 색상
    ├── alphaColor.ts          # alpha 유틸
    ├── theme.ts               # 라이트/다크 테마 설정
    ├── useThemeColor.ts       # 현재 테마 기준 색상 반환
    ├── tailwindColors.d.ts    # 타입 정의 (자동완성 지원)
    └── index.ts               # export 모음

🎨 Tailwind 색상 정의

colors.ts에서 Tailwind 스타일로 색상을 정의합니다. 아래는 일부 예시이며, 필요에 따라 추가할 수 있습니다.

const colors = {
  blue: {
    100: '#DBEAFE',
    500: '#3B82F6',
    900: '#1E3A8A',
  },
  gray: {
    100: '#F3F4F6',
    500: '#6B7280',
    900: '#111827',
  },
  red: {
    100: '#FEE2E2',
    500: '#EF4444',
    900: '#7F1D1D',
  },
  // 추가 색상: green, yellow, indigo, etc...
}

export default colors

🌙 다크모드 색상 정의

다크모드에서는 gray 계열을 반전시켜 사용하면 좋습니다.

const darkColors = {
  ...colors,
  gray: {
    100: '#18181B',
    500: '#71717A',
    900: '#F4F4F5',
  },
  background: '#0f172a',
  foreground: '#f8fafc',
}

export default darkColors

💧 Alpha 값 유틸 함수

투명도를 추가하고 싶다면 아래처럼 alpha를 붙여주는 유틸을 만듭니다.

export function withAlpha(hex: string, alpha: number): string {
  const bounded = Math.max(0, Math.min(1, alpha))
  const alphaHex = Math.round(bounded * 255).toString(16).padStart(2, '0')
  return hex + alphaHex
}

🧠 현재 테마 기반 색상 가져오기

React Native의 useColorScheme() 훅을 활용해 현재 테마에 맞는 색상을 가져옵니다.

export function useThemeColor(color: 'blue' | 'gray', shade: 100 | 500 | 900) {
  const theme = useColorScheme() === 'dark' ? 'dark' : 'light'
  return themes[theme].colors[color][shade]
}

🧾 타입 선언으로 자동완성 지원

타입스크립트를 쓰는 경우, 색상 이름과 단계 타입을 미리 정의해두면 IDE 자동완성과 에러 방지에 큰 도움이 됩니다.

export type TailwindColorName =
  | 'gray' | 'blue' | 'green' | 'red' | 'yellow' | 'indigo' | 'purple'

export type TailwindColorShade = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900

✅ 사용 예시

const bgColor = useThemeColor('blue', 100)
const textColor = useThemeColor('gray', 900)
const transparent = withAlpha(bgColor, 0.5)

return (
  <View style={{ backgroundColor: transparent, padding: 16 }}>
    <Text style={{ color: textColor }}>Tailwind 스타일 색상 예제</Text>
  </View>
)

🏁 마무리

Tailwind 스타일 색상 시스템을 React Native에 도입하면 코드 일관성, 유지보수성, 확장성이 모두 향상됩니다.
이번 구성은 디자인 시스템의 기반이 되므로 프로젝트 초기부터 도입해보세요!

반응형

 

✍️ 본문

👋 이런 코드 본 적 있으세요?

import { type User } from './types';

처음 보면 이런 생각 들죠.

"User는 타입인 거 뻔히 아는데... 굳이 type을 붙여야 하나요?"

저도 처음엔 안 붙여도 되는 줄 알았어요.
그런데 알고 보니, 붙이는 게 더 안전하고 좋은 습관이더라고요.


✅ 일단 User는 타입이에요

예를 들어 이런 타입이 있다고 해볼게요:

// types.ts
export type User = {
  name: string;
  age: number;
};

그럼 우리가 사용할 때는 보통 이렇게 쓰죠.

import { User } from './types';

const me: User = { name: '홍길동', age: 30 };

이거... 아무 문제 없어 보여요. 실제로도 잘 돌아가고요.


❗ 근데 왜 type을 붙일까요?

import { type User } from './types';

이렇게 쓰면 "나는 User를 타입으로만 쓸 거야!" 라고 명확하게 알려주는 거예요.

이게 왜 중요하냐면…


⚠️ 안 붙이면 생길 수 있는 문제

  1. 불필요한 코드가 앱에 들어가요
    → 타입인데도 실제 JS 코드에 포함돼서 번들(최종 파일)이 커져요.
  2. 런타임 에러가 날 수 있어요
    → 어떤 환경(특히 React Native)에서는 User가 실제 JS 값인 줄 알고 실행 중에 에러가 나기도 해요.

✅ 그래서 이렇게 구분하면 좋아요

상황 어떻게 import?

타입만 쓰는 경우 import { type User } from '...'
값(함수, 변수 등)도 쓰는 경우 그냥 import { something } from '...'

💡 결론

  • type을 꼭 붙여야 하는 건 아니에요
  • 하지만 붙이면 더 안전하고, 번들 크기도 줄고, 코드도 명확해져요!
  • 특히 React Native나 Vite 같은 환경에서는 실행 중 오류를 막는 데 도움이 돼요

📌 기억할 것 하나!

"타입만 쓸 거면 type 붙이자!"
그게 깔끔하고, 나중에 에러도 줄여줘요. 😊

반응형

안드로이드 앱을 개발할 때, 삼성 갤럭시 기기에서 직접 테스트해보는 것은 매우 중요합니다. 다행히 안드로이드 스튜디오에서는 삼성에서 제공하는 갤럭시 에뮬레이터 스킨을 추가해 실제 기기와 유사한 테스트 환경을 구성할 수 있습니다. 이번 글에서는 각 단계별로 캡처 이미지를 함께 보며 갤럭시 에뮬레이터를 설정하는 방법을 안내드리겠습니다.


1️⃣ 삼성 개발자 사이트에서 스킨 다운로드

  • Samsung Developers - Galaxy Emulator Skin 페이지로 이동합니다.
  • 원하는 기종(예: Galaxy S25, Galaxy Z Flip 등)의 에뮬레이터 스킨을 선택하여 다운로드합니다. - 가입 필요


2️⃣ 압축 해제 및 스킨 폴더 복사

  • 다운로드한 .zip 파일의 압축을 해제합니다.
  • 해제된 폴더 전체를 Android SDK 디렉토리 내 skins 폴더에 복사합니다.
    • Windows: C:\Users\사용자명\AppData\Local\Android\Sdk\skins
    • macOS: /Users/사용자명/Library/Android/sdk/skins
  • 저는 편의를 위해 Documents 폴더에 압축을 해제하여 사용했습니다.


3️⃣ Android Studio에서 가상 디바이스 생성

  • Android Studio를 실행한 후 Projects > More Actions > Virtual Device Manager로 이동합니다.
  • 상단의 Create Device (+버튼) 을 클릭합니다.
  • 원하는 디바이스 (예: Phone > Pixel 5)를 선택하거나 New Hardware Profile을 통해 새 디바이스를 생성합니다.

좌측 상단 + 버튼 클릭
New Hardware Profile 클릭


4️⃣ 스킨 설정 적용하기

 

Galaxy S | Samsung Developer

The world runs on you.

developer.samsung.com

 

 


5️⃣ 시스템 이미지 선택 및 AVD 생성 완료

  • 원하는 Android API 버전의 시스템 이미지를 선택하고 다운로드합니다.
  • 이름과 설정을 확인한 후 Finish를 클릭해 AVD를 생성합니다.


6️⃣ 에뮬레이터 실행 및 확인

  • 생성한 갤럭시 에뮬레이터 옆의 ▶ 아이콘을 클릭하여 실행합니다.
  • 실제 갤럭시 디바이스처럼 UI 프레임이 보이며 실행되는 것을 확인할 수 있습니다.


🏁 마무리

이제 Android Studio에서도 삼성 갤럭시 디바이스와 유사한 환경에서 앱을 테스트할 수 있게 되었습니다. 특히 국내 사용자 비중이 높은 갤럭시 스마트폰을 기준으로 테스트하면 더 안정적인 품질 확보가 가능하겠죠.

이 과정을 잘 따라오셨다면, 갤럭시 에뮬레이터를 여러 기종으로 추가해가며 다양한 해상도 및 성능 조건도 함께 점검해 보세요! 🚀

반응형

+ Recent posts