어? 이거 Kotlin이랑 똑같잖아?

GlossyBigbro·2025년 12월 16일
post-thumbnail

💭 프리랜서 개발자를 준비하며

8년간 Android 개발을 해왔습니다.

Kotlin, Jetpack Compose를 주로 사용했고,
Android 앱 개발에는 익숙했습니다.

그런데 최근 프리랜서 개발자를 준비하면서 고민이 생겼습니다.

"Android만으로 충분할까?"


🤔 왜 웹 개발인가?

프로젝트를 찾아보니

프리랜서 프로젝트를 검색해봤습니다.

많이 보이는 요구사항:

  • "웹 + 모바일 앱 함께 개발"
  • "React Native 경험자 우대"
  • "풀스택 개발 가능자"

Android만 할 줄 아는 건 부족했습니다.

웹 개발을 배워야겠다

프리랜서로 일하려면 다양한 기술이 필요합니다.

특히 웹 개발은:

  • 프로젝트가 많음
  • Android와 함께 요구되는 경우 많음
  • 자신을 표현할 수 있는 수단

웹 개발을 배우기로 결심했습니다.


🎯 무엇을 만들까?

고민

웹 개발을 배우려면 실전 프로젝트가 필요했습니다.

처음엔 이것저것 생각했습니다:

  • 블로그? → 너무 흔함
  • 쇼핑몰? → 너무 복잡함
  • 게시판? → 재미없음

이력서 웹사이트

그러다 생각했습니다.

"개발자라면 자기가 만든 이력서 웹사이트가 필요하지 않을까?"

이력서 웹사이트를 만들기로 했습니다.

이유:

  1. 실용적 - 프리랜서에게 필수
  2. 적당한 난이도 - 너무 쉽지도, 어렵지도 않음
  3. 동기부여 - 내 이력서니까 열심히 하게 됨

🔍 어떤 기술을 배울까?

조사 시작

웹 기술 스택을 검색해보니... 너무 많았습니다.

React? Vue? Angular?
JavaScript? TypeScript?
CSS? Sass? Tailwind?
Webpack? Vite? Next.js?

머리가 복잡했습니다.

가장 인기 있는 것으로

검색하고, 비교하고, 고민한 끝에:

선택:

  • Next.js (React 프레임워크, 가장 인기)
  • TypeScript (타입 안전성)
  • Tailwind CSS (빠른 스타일링)

나중에 알았지만, 이 선택은 정말 잘한 것이었습니다.


😰 두려움

솔직히 걱정됐습니다

  • TypeScript는 처음인데...
  • React는 어떻게 배우지?
  • HTML, CSS도 잘 모르는데...
  • 웹 개발은 Android와 완전히 다른 세계 아닌가?

"할 수 있을까?"


💡 그 순간

Next.js 공식 문서를 열었습니다

일단 Next.js 공식 문서부터 읽어보기로 했습니다.

문서를 읽다가 TypeScript 예제 코드가 눈에 들어왔습니다.

interface User {
    name: string;
    age: number;
}

"잠깐... 이거..."

Kotlin과 비교해봤습니다

// Kotlin
data class User(
    val name: String,
    val age: Int
)
// TypeScript
interface User {
    name: string;
    age: number;
}

"어? 거의 똑같잖아?"

차이점을 찾아봤습니다

차이점:

  • val 없음 (interface는 구조만 정의)
  • Int → number
  • String → string

그게 다였습니다.

"이거... 할 수 있겠는데?"


🚀 시작

첫 프로젝트 생성

그날 바로 시작했습니다.

npx create-next-app@latest resume

터미널에 명령어를 입력했습니다.

프로젝트가 생성됐습니다.

npm run dev

localhost:3000을 열었습니다.

"와, 화면이 떴다!"

첫 TypeScript 코드 작성

이력서 데이터 타입을 정의했습니다.

// types/index.ts
export interface ExperienceItem {
    id: string;
    period: string;
    role: string;
    description: string[];
    tags: string[];
    playStoreUrl?: string;
}

Kotlin이었다면:

data class ExperienceItem(
    val id: String,
    val period: String,
    val role: String,
    val description: List<String>,
    val tags: List<String>,
    val playStoreUrl: String? = null
)

"정말 똑같네!"

경력 계산 로직 작성

경력을 자동으로 계산하는 함수를 만들었습니다.

// utils/career.ts
export function calculateTotalCareer<T extends ExperiencePeriod>(
    data: T[], 
    language: 'ko' | 'en' = 'ko'
): string {
    const periods = data.map((item) => {
        const [startStr, endStr] = item.period.split('~');
        return {
            start: parseDate(startStr),
            end: endStr === "현재" ? new Date() : parseDate(endStr)
        };
    });
    
    // 겹치는 기간 병합
    periods.sort((a, b) => a.start.getTime() - b.start.getTime());
    
    // 총 개월 수 계산
    let totalMonths = 0;
    for (const period of mergedPeriods) {
        totalMonths += calculateMonthsBetween(period.start, period.end);
    }
    
    return formatDuration(totalMonths, language);
}

Kotlin이었다면 똑같이 작성했을 코드입니다.

  • map() → 동일
  • filter() → 동일
  • sort() → 동일
  • 제네릭 <T extends ...> → Kotlin의 <T : ...>와 동일

"Kotlin 할 줄 알면 TypeScript도 할 수 있구나!"


🎨 React 컴포넌트

첫 컴포넌트 작성

경력 섹션 컴포넌트를 만들었습니다.

// components/sections/ExperienceSection.tsx
export function ExperienceSection({ data }: ExperienceSectionProps) {
    const { language } = useLanguage();
    const totalCareer = useMemo(
        () => calculateTotalCareer(data, language),
        [data, language]
    );
    
    return (
        <section>
            <h2>Experience</h2>
            <p>총 경력: {totalCareer}</p>
            {data.map((item) => (
                <div key={item.id}>
                    <h3>{item.role}</h3>
                    <p>{item.period}</p>
                </div>
            ))}
        </section>
    );
}

Jetpack Compose였다면:

@Composable
fun ExperienceSection(data: List<ExperienceItem>) {
    val language by viewModel.language.collectAsState()
    val totalCareer = remember(data, language) {
        calculateTotalCareer(data, language)
    }
    
    Column {
        Text("Experience", style = MaterialTheme.typography.h2)
        Text("총 경력: $totalCareer")
        data.forEach { item ->
            Column(key = item.id) {
                Text(item.role, style = MaterialTheme.typography.h3)
                Text(item.period)
            }
        }
    }
}

"어? Compose랑 거의 똑같은데?"

  • useState → remember { mutableStateOf() }
  • useMemo → remember(deps) { }
  • map() → forEach
  • Props → 파라미터

"Compose 할 줄 알면 React도 할 수 있구나!"


📸 완성

그렇게 만들어진 이력서 웹사이트:
🔗 https://glossybigbro.github.io

데스크톱 - 라이트 모드데스크톱 - 다크 모드
모바일 - 라이트 모드모바일 - 다크 모드

💬 깨달음

TypeScript = Kotlin

타입 시스템:

  • interface = data class
  • type = typealias
  • 제네릭 동일
  • nullable 표기 동일

함수형 프로그래밍:

  • map, filter, reduce 동일
  • 화살표 함수 = 람다
  • 고차 함수 개념 동일

React = Jetpack Compose

컴포넌트:

  • Component = Composable
  • Props = 파라미터
  • JSX = Kotlin DSL

상태 관리:

  • useState = mutableStateOf
  • useMemo = remember
  • useEffect = LaunchedEffect

🎯 마치며

8년간의 Android 개발 경험이 웹 개발에 큰 도움이 됐습니다.

새로운 걸 배우는 게 아니라, 이미 아는 걸 다른 방식으로 쓰는 것이었습니다.

0개의 댓글