Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

[Typescript] 고급 타입스크립트, React 18 본문

프론트엔드 부트캠프

[Typescript] 고급 타입스크립트, React 18

heyday2024 2024. 12. 21. 16:34

고급 타입스크립트 기법 설명


조건부 타입

개념

조건부 타입은 extends 키워드를 사용하여 특정 조건에 따라 다른 타입을 반환하는 유연하고 강력한 타입 시스템입니다. 삼항 연산자(? :)와 비슷한 구문을 사용하며, 타입 수준에서 분기 처리를 수행합니다.

기본 문법

T extends U ? X : Y
  • T가 U의 하위 타입이면 X를 반환.
  • 그렇지 않으면 Y를 반환.

예제

type IsString<T> = T extends string ? "string" : "not string";

type A = IsString<string>;  // "string"
type B = IsString<number>;  // "not string"

활용 예제

배열인지 확인하는 조건부 타입

type IsArray<T> = T extends any[] ? true : false;

type Test1 = IsArray<number[]>;  // true
type Test2 = IsArray<string>;    // false

함수에서의 조건부 타입

조건부 타입은 함수 반환 타입을 동적으로 지정할 때 유용합니다.

function process<T>(value: T): T extends string ? string[] : T[] {
  return (typeof value === 'string' ? value.split('') : [value]) as any;
}

const result1 = process("hello");  // string[]
const result2 = process(123);      // number[]

장점

  • 정밀한 타입 체크: 타입에 따라 다른 로직을 적용 가능.
  • 유연성: 복잡한 타입 제어 로직을 컴파일 단계에서 구현 가능.

고급 제네릭 기법

제네릭(Generic)은 재사용 가능한 타입을 정의할 때 사용됩니다. 제네릭은 타입을 일반화하여 다양한 데이터 구조에 적용할 수 있습니다.


1. 제네릭 인터페이스

기본 개념

  • 제네릭은 인터페이스에서도 활용 가능하며, 매개변수와 반환값의 타입을 동일하게 유지하는 데 유용합니다.
interface GenericIdentityFn<T> {
  (arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

2. 제네릭 클래스

클래스에 제네릭 적용

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

3. 제네릭 제약 조건 (Constraints)

특정 조건을 만족하도록 제약

extends 키워드를 사용해 제네릭 타입의 조건을 지정합니다.

function logLength<T extends { length: number }>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("Hello");        // 출력: 5
logLength([1, 2, 3]);      // 출력: 3

4. 맵드 타입 (Mapped Types)

맵드 타입은 기존 타입을 변환하여 새로운 타입을 생성합니다.

예제

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

type Person = {
  name: string;
  age: number;
};

type ReadonlyPerson = Readonly<Person>;  // 모든 필드 읽기 전용
type OptionalPerson = Optional<Person>;  // 모든 필드 선택 가능

고급 예제

제네릭 함수

function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const mergedObj = merge({ name: "Alice" }, { age: 25 });
console.log(mergedObj);  // { name: "Alice", age: 25 }

제네릭 클래스

class Stack<T> {
  private items: T[] = [];

  push(item: T) {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const stack = new Stack<number>();
stack.push(10);
console.log(stack.pop());  // 10

모듈과 네임스페이스

TypeScript는 코드의 조직화를 위해 모듈과 네임스페이스를 제공합니다.


1. 모듈

모듈 정의와 사용

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

// app.ts
import { add } from './math';
console.log(add(2, 3)); // 5

2. 네임스페이스

관련 코드를 그룹화

namespace MathUtils {
  export function add(a: number, b: number): number {
    return a + b;
  }
}

console.log(MathUtils.add(2, 3));  // 5

3. 글로벌 타입과 declare

declare 키워드를 사용해 전역 객체나 변수에 커스텀 타입을 추가할 수 있습니다.

declare global {
  interface Window {
    myCustomProperty: string;
  }
}

window.myCustomProperty = 'Hello World';
console.log(window.myCustomProperty);  // Hello World

타입 좁히기 (Type Narrowing)

개념

TypeScript에서 타입 좁히기는 변수의 타입을 보다 구체적인 타입으로 명확히 하는 과정입니다. 이를 통해 타입 안정성과 정확도를 높일 수 있습니다.


타입 가드(Type Guards)

타입 가드는 특정 조건에 따라 변수의 타입을 좁히는 방법입니다. 조건문 내에서 변수의 타입을 좁히기 위해 typeof, instanceof, 사용자 정의 타입 가드 등을 사용할 수 있습니다.

typeof를 이용한 타입 가드

function print(value: string | number) {
  if (typeof value === "string") {
    console.log(`String: ${value}`);
  } else {
    console.log(`Number: ${value}`);
  }
}

instanceof를 이용한 타입 가드

class Dog {
  bark() {
    console.log("Woof!");
  }
}

function makeSound(animal: Dog | string) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    console.log(animal);
  }
}

 

in 연산자 이용한 타입 가드

interface Fish {
    swim: () => void;
}

interface Bird {
    fly: () => void;
}

function move(animal: Fish | Bird) {
    if ("swim" in animal) {
        animal.swim();
    } else {
        animal.fly();
    }
}

const fish: Fish = { swim: () => console.log("Swimming") };
const bird: Bird = { fly: () => console.log("Flying") };

move(fish); // Swimming
move(bird); // Flying

 

===>  타입 좁히기를 통한 안전한 코드 작성 : 타입 좁히기를 통해 변수가 특정 타입임을 보장할 수 있습니다. 이를 통해 컴파일러가 코드를 더 정확하게 분석하고, 타입 오류를 사전에 방지할 수 있습니다.


<React 18>
https://ko.react.dev/blog/2022/03/29/react-v18

 

React v18.0 – React

The library for web and native user interfaces

ko.react.dev

React 18 소개 및 주요 변경 사항 정리


1. React 18의 주요 개선사항

React 18은 동시성(Concurrency)과 성능 최적화, 개발 편의성을 목표로 여러 새로운 기능과 변경 사항을 도입했습니다. 아래는 주요 변경 사항들입니다.


2. React 18의 새로운 기능

(1) useId

  • 유니크한 ID를 생성하는 훅
  • 컴포넌트별로 클라이언트와 서버 간 불일치 없이 고유한 ID를 생성합니다.
  • 사용 이유:
    • 서버 사이드 렌더링(SSR)과 클라이언트에서 동일한 ID를 생성해 하이드레이션 오류를 방지합니다.
  • 사용 예제:
    import { useId } from 'react';
    
    function UniqueIdComponent() {
      const id = useId();
      return <div id={id}>My unique ID: {id}</div>;
    }
    

(2) useTransition

  • UI 변경을 블로킹하지 않고 상태 업데이트를 처리하는 훅
  • 주요 목적: 렌더링이 느릴 때 로딩 화면을 표시하거나 새로운 상태로 다시 렌더링을 트리거.
  • 사용 예제:
    import { useState, useTransition } from 'react';
    
    function App() {
      const [isPending, startTransition] = useTransition();
      const [count, setCount] = useState(0);
    
      const handleClick = () => {
        startTransition(() => {
          setCount(c => c + 1);
        });
      };
    
      return (
        <div>
          <button onClick={handleClick}>Increment</button>
          {isPending ? <p>Loading...</p> : <p>Count: {count}</p>}
        </div>
      );
    }
    
  • 주의점:
    1. startTransition 내부는 상태 업데이트 함수만 사용.
    2. 상태 업데이트가 다른 동기 업데이트보다 지연될 수 있음.
    3. 비동기 함수는 사용할 수 없음.

(3) useDeferredValue

  • 급하지 않은 렌더링 작업을 지연.
  • 주요 차이점:
    • useTransition은 상태 업데이트 함수 전체를 감싸지만,
    • useDeferredValue는 상태 값만 감쌉니다.
  • 사용 예제:
    import { useState, useDeferredValue } from 'react';
    
    function App() {
      const [input, setInput] = useState('');
      const deferredInput = useDeferredValue(input);
    
      return (
        <div>
          <input value={input} onChange={(e) => setInput(e.target.value)} />
          <p>Deferred: {deferredInput}</p>
        </div>
      );
    }
    

(4) Automatic Batching (자동 배치)

  • React 18부터 여러 상태 업데이트를 하나의 리렌더링으로 묶어서 처리.
  • 이전 버전: React 이벤트 핸들러 내부에서만 상태 업데이트를 일괄 처리.
  • React 18: 이벤트 핸들러, 비동기 작업 등 모든 업데이트를 자동으로 배치 처리.
  • 사용 예제:
    function handleClick() {
      sleep(3000).then(() => {
        setCount(c => c + 1);
        setFlag(f => !f);
      });
    }
    
  • 자동 배치를 해제하려면 flushSync를 사용:
    import { flushSync } from 'react-dom';
    
    function handleClick() {
      sleep(3000).then(() => {
        flushSync(() => setCount(c => c + 1));
        flushSync(() => setFlag(f => !f));
      });
    }
    

(5) Suspense

  • 비동기 작업 중 로딩 상태(fallback)를 제공하는 기능.
  • 데이터 페칭, 코드 분할, 비동기 UI 구현에 유용.
  • 사용 예제:
    import React, { Suspense } from 'react';
    
    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    
    function App() {
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <LazyComponent />
        </Suspense>
      );
    }
    
  • React 18에서 개선된 점:
    • Next.js와 같은 프레임워크에서 지원.
    • useLayoutEffect가 컴포넌트가 실제로 화면에 나타날 때 실행.
    • 중첩된 Suspense의 fallback이 쓰로틀링으로 자연스럽게 처리.