๐Ÿ“– TIL - TypeScript ํƒ€์ž… ๋‹จ์–ธ๊ณผ ํƒ€์ž… ๊ฐ€๋“œ

์Š˜ยท2025๋…„ 7์›” 8์ผ

๐Ÿ“– TIL

๋ชฉ๋ก ๋ณด๊ธฐ
82/90

๐Ÿ“Œ ์˜ค๋Š˜ ๋ณต์Šตํ•œ ๊ฐœ๋…๋“ค

1. ํƒ€์ž… ๋‹จ์–ธ (Type Assertion)

2. ํƒ€์ž… ๊ฐ€๋“œ (Type Guard)

3. ์ธํ„ฐํŽ˜์ด์Šค (Interface)

4. ํƒ€์ž… ๋ณ„์นญ (Type Alias)

5. ํ•จ์ˆ˜ ์˜ค๋ฒ„๋กœ๋”ฉ (Function Overloading)

6. ์ œ๋„ค๋ฆญ (Generics)


๐Ÿค” ํ•™์Šต ๋ฐฐ๊ฒฝ

DOM ์กฐ์ž‘์ด๋‚˜ ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ null์ด๋‚˜ undefined ๊ฐ€๋Šฅ์„ฑ ๋•Œ๋ฌธ์— ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ์ด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์„ ๋ฐฐ์›Œ๋ณด์ž!


๐Ÿ’ก ํ•ต์‹ฌ ๊ฐœ๋… ์ •๋ฆฌ

1. ํƒ€์ž… ๋‹จ์–ธ (Type Assertion)

๋‹จ์–ธ = ์ฃผ์ €ํ•˜์ง€ ์•„๋‹ˆํ•˜๊ณ  ๋”ฑ ์ž˜๋ผ ๋งํ•จ

๋‹จ์–ธ ํ‚ค์›Œ๋“œ: as์™€ Non-null ๋‹จ์–ธ ์—ฐ์‚ฐ์ž: !

// 1) DOM ์กฐ์ž‘์—์„œ์˜ ํƒ€์ž… ๋‹จ์–ธ
const el = document.querySelector('body') 
el.textContent = 'hello' // โŒ ์—๋Ÿฌ ๋ฐœ์ƒ!

์œ„ ์ฝ”๋“œ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋Š” el์ด HTMLBodyElement | null ์œ ๋‹ˆ์˜จ ํƒ€์ž…์œผ๋กœ ์ง€์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ!

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•๋“ค:

// ๋ฐฉ๋ฒ• 1: as ํ‚ค์›Œ๋“œ๋กœ ์ง์ ‘ ํƒ€์ž… ๋‹จ์–ธ
const el = document.querySelector('body') as HTMLBodyElement;

// ๋ฐฉ๋ฒ• 2: Non-null ๋‹จ์–ธ ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ
el!.textContent = "hello"; // ๋А๋‚Œํ‘œ๋Š” "์ ˆ๋Œ€ null์ด ์•„๋‹ˆ๋‹ค"๋ผ๊ณ  ํ‘œ์‹œ

// ๋ฐฉ๋ฒ• 3: ํƒ€์ž… ๊ฐ€๋“œ ์‚ฌ์šฉ (๊ฐ€์žฅ ์•ˆ์ „!)
if(el){
    el.textContent = "hello";
}
// 2) ํ•จ์ˆ˜์—์„œ์˜ ํƒ€์ž… ๋‹จ์–ธ
function getNumber(x: number | null | undefined){
    return Number(x.toFixed(2)); // โŒ ์—๋Ÿฌ! toFixed๋Š” null/undefined ์ฒ˜๋ฆฌ ๋ถˆ๊ฐ€
}

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:

// ํƒ€์ž… ๋‹จ์–ธ ์‚ฌ์šฉ
return Number((x as number).toFixed(2));
// ๋˜๋Š”
return Number(x!.toFixed(2));

// ํ•˜์ง€๋งŒ getNumber(null) ํ˜ธ์ถœ ์‹œ TypeError ๋ฐœ์ƒ! (์ž˜๋ชป๋œ ํƒ€์ž… ๋‹จ์–ธ)

// ํƒ€์ž… ๊ฐ€๋“œ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
if(x) { // undefined์™€ null์€ ์กฐ๊ฑด๋ฌธ์—์„œ ์ฐธ์ด ์•„๋‹ˆ๋ฏ€๋กœ ์•ˆ์ „
    return Number(x.toFixed(2));
}
// 3) ์กฐ๊ฑด๋ถ€ ํƒ€์ž… ๋‹จ์–ธ
function getValue(x: string | number, isNumber: boolean){
    if(isNumber){
        return Number(x.toFixed(2)); // โŒ ์—๋Ÿฌ
    }
    return x.toUpperCase(); // โŒ ์—๋Ÿฌ
}

ํ•ด๊ฒฐ:

if(isNumber){
    return Number((x as number).toFixed(2));
}
return (x as string).toUpperCase();
// ์—ฌ๊ธฐ์„œ๋Š” null/undefined๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ Non-null ์—ฐ์‚ฐ์ž(!)๋Š” ๋ถˆํ•„์š”

ํ• ๋‹น ๋‹จ์–ธ (Assignment Assertion)

let num: number;
console.log(num); // โŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์—๋Ÿฌ (๊ฐ’์„ ํ• ๋‹นํ•˜์ง€ ์•Š์Œ)

// ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ๋Š” undefined๋กœ ์ถœ๋ ฅ๋˜์ง€๋งŒ TS์—์„œ๋Š” ์—๋Ÿฌ

ํ•ด๊ฒฐ: ํ• ๋‹น ๋‹จ์–ธ ์‚ฌ์šฉ

let num!: number; // ํ• ๋‹น ๋‹จ์–ธ - "์ถ”ํ›„ ๊ฐ’์„ ํ• ๋‹นํ•  ๊ฒƒ์ด๋‹ค"๋ผ๊ณ  TS์—๊ฒŒ ์•Œ๋ฆผ
console.log(num);
num = 123; // ๋‚˜์ค‘์— ํ• ๋‹น

2. ํƒ€์ž… ๊ฐ€๋“œ (Type Guard)

function logText(el: Element){
    console.log(el.textContent);
}
const h1El = document.querySelector('h1');
logText(h1El); // โŒ ์—๋Ÿฌ! h1El์ด null์ผ ์ˆ˜ ์žˆ์Œ

ํƒ€์ž… ๋‹จ์–ธ vs ํƒ€์ž… ๊ฐ€๋“œ:

// ํƒ€์ž… ๋‹จ์–ธ ์‚ฌ์šฉ (์œ„ํ—˜!)
const h1El = document.querySelector('h1') as HTMLHeadingElement;
// ์ฝ”๋“œ์ƒ ์—๋Ÿฌ๋Š” ์—†์ง€๋งŒ ์‹คํ–‰ ์‹œ "Cannot read properties of null" ์—๋Ÿฌ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

// ํƒ€์ž… ๊ฐ€๋“œ ์‚ฌ์šฉ (์•ˆ์ „!)
if(h1El){
    logText(h1El); // ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
}

// ๋” ๊ตฌ์ฒด์ ์ธ ํƒ€์ž… ๊ฐ€๋“œ
if(h1El instanceof HTMLHeadingElement){
    logText(h1El);
}

3. ์ธํ„ฐํŽ˜์ด์Šค (Interface)

ํŒŒ์Šค์นผ ์ผ€์ด์Šค๋กœ ์‹œ์ž‘

๊ธฐ๋ณธ ์ธํ„ฐํŽ˜์ด์Šค

interface User {
    name: string;
    readonly age: number;  // ์ฝ๊ธฐ ์ „์šฉ ์†์„ฑ
    isValid?: boolean;     // ์„ ํƒ์  ์†์„ฑ
}

const neo: User = {
    name: 'sum',
    age: 100,
    isValid: true
};

neo.age = 200; // โŒ ์—๋Ÿฌ! readonly ์†์„ฑ์€ ์ˆ˜์ • ๋ถˆ๊ฐ€

ํ•จ์ˆ˜ ํƒ€์ž… - ํ˜ธ์ถœ ์‹œ๊ทธ๋‹ˆ์ฒ˜ (Call Signature)

interface GetName {
    (mes: string): string; // ์†Œ๊ด„ํ˜ธ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ํ˜ธ์ถœ ์‹œ๊ทธ๋‹ˆ์ฒ˜
}

interface User {
    name: string;
    readonly age: number;
    getName: GetName;
}

const user1: User = {
    name: 'sum',
    age: 100,
    getName(mes: string) {
        console.log(mes);
        return this.name; // ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ํ˜ธ์ถœ ์‹œ this ํŒ๋ณ„ ๊ฐ€๋Šฅ
    }
};

user1.getName('coco'); // ์ •์ƒ ์ž‘๋™

ํ•จ์ˆ˜ ํƒ€์ž…์„ ๋”ฐ๋กœ ๋งŒ๋“œ๋Š” ์ด์œ : ์ด๋ฆ„์„ ๊ฐ–๊ณ  ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋จ! ๋™์ผํ•œ ๊ตฌ์กฐ์˜ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ. ์žฌ์‚ฌ์šฉ์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ํ˜ธ์ถœ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ.

์ธ๋ฑ์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜ (Index Signature)

๋ฐฐ์—ด๊ณผ ๊ฐ์ฒด์—์„œ ๋Œ€๊ด„ํ˜ธ[]๋กœ ๊ฐ์‹ธ์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ

// ๋ฐฐ์—ด ํƒ€์ž… ์ •์˜
interface Fruits {
    [item: number]: string; // ์ธ๋ฑ์Šค๋Š” number, ๊ฐ’์€ string
}
const fruits: Fruits = ['apple', 'banana', 'cherry'];
// ์ฝ˜์†”์— ์ฐ์œผ๋ฉด: 0: apple, 1: banana, 2: cherry

// ๊ฐ์ฒด ํƒ€์ž… ์ •์˜
interface User {
    [key: string]: unknown; // ์–ด๋–ค ๊ฐ’์ด ์˜ฌ์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค๊ณ  ์ง€์ •
    name: string;
    age: number;
}
const user1: User = {
    name: 'sum',
    age: 100
};

// ์ธ๋ฑ์‹ฑ์œผ๋กœ ๊ฐ’ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
user1['isValid'] = true;
user1['emails'] = ['sum529@naver.com', 'coyasium@gmail.com'];

ํ™•์žฅ (์ƒ์†)

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

interface UserB extends UserA {
    isValid: boolean;
}

const user2: UserB = {
    name: 'sum',
    age: 100,
    isValid: true // UserA์˜ ์†์„ฑ + UserB์˜ ์†์„ฑ
};

์ธํ„ฐํŽ˜์ด์Šค ์ค‘๋ณต ์„ ์–ธ (Declaration Merging):

interface FullName {
    firstName: string;
    lastName: string;
}

interface FullName {
    middleName: string;
    // lastName: number; // โŒ ์—๋Ÿฌ! ๊ธฐ์กด ํƒ€์ž…์€ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€
}
// ๋‘ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ํ•ฉ์ณ์ง!

4. ํƒ€์ž… ๋ณ„์นญ (Type Alias)

// ์œ ๋‹ˆ์˜จ ํƒ€์ž… ๊ฐ€๋Šฅ
type TypeA = string | number | boolean;

// ํŠœํ”Œ ํƒ€์ž… ๊ฐ€๋Šฅ
type User1 = [string, number, boolean];

// ๋ณตํ•ฉ ํƒ€์ž…
type User2 = {
    name: string;
    age: number;
    isValid: boolean;
} | [string, number, boolean];

์ธํ„ฐํŽ˜์ด์Šค vs ํƒ€์ž… ๋ณ„์นญ:

  • ์žฌํ• ๋‹น ๊ฐ€๋Šฅ ์—ฌ๋ถ€๊ฐ€ ์ฐจ์ด์ 
  • ์ธํ„ฐํŽ˜์ด์Šค: ๊ฐ™์€ ๋ช…์นญ์œผ๋กœ ์žฌ์„ ์–ธ ์‹œ ์ž๋™์œผ๋กœ ํ•ฉ์ณ์ง
  • ํƒ€์ž… ๋ณ„์นญ: ์žฌ์„ ์–ธ ๋ถˆ๊ฐ€ (์˜ค๋ฅ˜ ๋ฐฉ์ง€ ์ธก๋ฉด์—์„œ ๋” ์•ˆ์ „)

5. ํ•จ์ˆ˜ - ๋ช…์‹œ์  this ํƒ€์ž…

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

const cat: Cat = {
    name: 'sum',
    age: 10
};

function fi(mes: string){
    console.log(`${this.name}, ${mes}`); // โŒ this์—์„œ ์—๋Ÿฌ
}

fi.call(cat, 'meow'); // call ๋ฉ”์†Œ๋“œ๋กœ cat ๊ฐ์ฒด์˜ ๋ฉ”์†Œ๋“œ์ฒ˜๋Ÿผ ํ˜ธ์ถœ

์—๋Ÿฌ ์›์ธ: this์—๋Š” ํ˜•์‹ ์ฃผ์„์ด ์—†์œผ๋ฏ€๋กœ ์•”์‹œ์ ์œผ๋กœ any ํ˜•์‹ ํฌํ•จ (TS์˜ ์—„๊ฒฉํ•œ ๋ฌธ๋ฒ• ๊ฒ€์‚ฌ)

ํ•ด๊ฒฐ:

function fi(this: Cat, mes: string){ // this ํƒ€์ž… ๋ช…์‹œ (๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์•„๋‹Œ TS ๋ฌธ๋ฒ•)
    console.log(`${this.name}, ${mes}`);
}

6. ํ•จ์ˆ˜ ์˜ค๋ฒ„๋กœ๋”ฉ (Function Overloading)

๊ธฐ์กด ๋น„ํšจ์œจ์ ์ธ ์ฝ”๋“œ:

function add1(a: string, b: string) {
    return a + b;
}
function add2(a: number, b: number) {
    return a + b;
}
// ๋ถˆํ•„์š”ํ•œ ์ค‘๋ณต ์ฝ”๋“œ!

์˜ค๋ฒ„๋กœ๋”ฉ ์‚ฌ์šฉ:

function add(a: string, b: string): string;  // ํƒ€์ž… ์„ ์–ธ
function add(a: number, b: number): number;  // ํƒ€์ž… ์„ ์–ธ
function add(a: any, b: any) {               // ํ•จ์ˆ˜ ๊ตฌํ˜„
    return a + b;
}

add('h', 'i');  // ์ •์ƒ
add(2, 3);      // ์ •์ƒ
add('h', 1);    // โŒ ์—๋Ÿฌ

์˜ค๋ฒ„๋กœ๋”ฉ์ด๋ž€? ํ•˜๋‚˜์˜ ํ•จ์ˆ˜ ์ด๋ฆ„์— ์—ฌ๋Ÿฌ ํƒ€์ž…์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์„ ์–ธํ•ด ๋‹ค์–‘ํ•œ ์ž…๋ ฅ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ธฐ๋Šฅ. ์—ฌ๊ธฐ์„œ any๋Š” ์‹ค์ œ ํƒ€์ž…์ด ์•„๋‹ˆ๋ผ "ํƒ€์ž… ์„ ์–ธํ•œ ๋ฐฉ์‹ ์ค‘ ์–ด๋–ค ๊ฒƒ์ด๋“  ํ• ๋‹น๋  ์ˆ˜ ์žˆ๋‹ค"๋Š” ์˜๋ฏธ.

7. ์ œ๋„ค๋ฆญ (Generics)

ํ•จ์ˆ˜ ์ œ๋„ค๋ฆญ:

// ์˜ค๋ฒ„๋กœ๋”ฉ ๋Œ€์‹  ์ œ๋„ค๋ฆญ ์‚ฌ์šฉ
function add<T>(a: T, b: T) {
    return a + b;
}

์ธํ„ฐํŽ˜์ด์Šค ์ œ๋„ค๋ฆญ๊ณผ ์ œ์•ฝ ์กฐ๊ฑด:

// ๊ธฐ๋ณธ ์ œ๋„ค๋ฆญ ์ธํ„ฐํŽ˜์ด์Šค
interface MyData<T> {
    name: string;
    value: T;
}

const dataA: MyData<string> = {
    name: 'data',
    value: 'hello'
};

const dataB: MyData<number[]> = {
    name: 'data',
    value: [1, 2]
};

// ์ œ์•ฝ ์กฐ๊ฑด์ด ์žˆ๋Š” ์ œ๋„ค๋ฆญ
interface MyData<T extends string | number> {
    name: string;
    value: T;
}

const dataC: MyData<number[]> = { // โŒ ์—๋Ÿฌ! number[]๋Š” ์ œ์•ฝ ์กฐ๊ฑด์— ๋งž์ง€ ์•Š์Œ
    name: 'data',
    value: [1, 2]
};

8. ํŒจํ‚ค์ง€ ํƒ€์ž… ์„ ์–ธ

import _ from 'lodash';

const str = 'xxxx xxx xxx';
console.log(_.camelCase(str));
console.log(_.snakeCase(str));

ํƒ€์ž… ์„ ์–ธ ํŒŒ์ผ ์ƒ์„ฑ (ํŒจํ‚ค์ง€๋ช….d.ts):

declare module 'lodash' {
    interface Lodash {
        camelCase: (str: string) => string;
        snakeCase: (str: string) => string;
    }
    const _: Lodash;
    export default _;
}

์‚ผ์ค‘ ์Šฌ๋ž˜์‹œ ์ง€์‹œ์ž (Triple-slash directive):

/// <reference path="./main.d.ts"/>

๋” ํŽธํ•œ ๋ฐฉ๋ฒ•:

# ํƒ€์ž… ์กด์žฌ ํ™•์ธ
$ npm info @types/lodash

# ๊ฐœ๋ฐœ ์˜์กด์„ฑ์œผ๋กœ ํƒ€์ž… ์„ค์น˜
$ npm i @types/lodash -D

๐Ÿ” ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ

DOM ์กฐ์ž‘์—์„œ์˜ ํƒ€์ž… ์•ˆ์ „์„ฑ

// โŒ ์œ„ํ—˜ํ•œ ๋ฐฉ๋ฒ•
const button = document.querySelector('button');
button.addEventListener('click', () => {}); // ์—๋Ÿฌ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

// โœ… ํƒ€์ž… ๊ฐ€๋“œ ์‚ฌ์šฉ
const button = document.querySelector('button');
if (button) {
    button.addEventListener('click', () => {});
}

// โœ… ํƒ€์ž… ๋‹จ์–ธ ์‚ฌ์šฉ (ํ™•์‹คํ•  ๋•Œ๋งŒ!)
const button = document.querySelector('button') as HTMLButtonElement;
button.addEventListener('click', () => {});

โœจ ์ƒˆ๋กญ๊ฒŒ ๋ฐฐ์šด ์ 

  • ํƒ€์ž… ๋‹จ์–ธ vs ํƒ€์ž… ๊ฐ€๋“œ: ๋‹จ์–ธ์€ ๊ฐœ๋ฐœ์ž๊ฐ€ "ํ™•์‹ "ํ•  ๋•Œ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ. ํƒ€์ž… ๊ฐ€๋“œ๋Š” ๋” ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•!
  • ํ• ๋‹น ๋‹จ์–ธ (!): ๋ณ€์ˆ˜ ์„ ์–ธ ์‹œ ๋‚˜์ค‘์— ๊ฐ’์„ ํ• ๋‹นํ•  ๊ฒƒ์ž„์„ TypeScript์—๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ๋ฐฉ๋ฒ•
  • ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ค‘๋ณต ์„ ์–ธ: ๊ฐ™์€ ์ด๋ฆ„์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์„ ์–ธํ•˜๋ฉด ํ•ฉ์ณ์ง (Declaration Merging)
  • ์ œ๋„ค๋ฆญ์˜ ์ œ์•ฝ ์กฐ๊ฑด: extends ํ‚ค์›Œ๋“œ๋กœ ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ์ œํ•œ ๊ฐ€๋Šฅ

๐Ÿš€ ์ด๋Ÿฐ ์ ์ด ํšจ์œจ์ ์ด์—์š”

  1. ํƒ€์ž… ๊ฐ€๋“œ ์šฐ์„  ์‚ฌ์šฉ: ํƒ€์ž… ๋‹จ์–ธ๋ณด๋‹ค๋Š” ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ๋จผ์ € ๊ณ ๋ คํ•˜์ž. ๋Ÿฐํƒ€์ž„ ์•ˆ์ „์„ฑ์ด ๋†’์•„์ง!
  2. ์ธํ„ฐํŽ˜์ด์Šค vs ํƒ€์ž… ๋ณ„์นญ:
    • ์ธํ„ฐํŽ˜์ด์Šค: ํ™•์žฅ ๊ฐ€๋Šฅ, ์„ ์–ธ ๋ณ‘ํ•ฉ ๊ฐ€๋Šฅ
    • ํƒ€์ž… ๋ณ„์นญ: ์œ ๋‹ˆ์˜จ ํƒ€์ž…, ํŠœํ”Œ ํƒ€์ž… ํ‘œํ˜„ ๊ฐ€๋Šฅ
  3. ์ œ๋„ค๋ฆญ ํ™œ์šฉ: ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๊ณ  ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ

โญ๏ธ ์ฐธ๊ณ  ์ž๋ฃŒ

profile
์ฃผ๋‹ˆ์–ด ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ์žฅ๊ธฐ ๊ธฐ๋ก๊ธฐ๋ก

0๊ฐœ์˜ ๋Œ“๊ธ€