heyday2024 님의 블로그
[Typescript] 고급 타입스크립트, React 18 본문
고급 타입스크립트 기법 설명
조건부 타입
개념
조건부 타입은 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 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> ); }
- 주의점:
- startTransition 내부는 상태 업데이트 함수만 사용.
- 상태 업데이트가 다른 동기 업데이트보다 지연될 수 있음.
- 비동기 함수는 사용할 수 없음.
(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이 쓰로틀링으로 자연스럽게 처리.
'프론트엔드 부트캠프' 카테고리의 다른 글
[Typescript] 유틸리티 타입 (0) | 2024.12.21 |
---|---|
[Typescript] 타입 스크립트 동작 원리, 타입 추론, 제네릭 (0) | 2024.12.21 |
Next.js 렌더링 패턴과 구현 방식 (2) | 2024.12.10 |
팀프로젝트 피드백 (2) | 2024.12.05 |
유효성 검사와 supabase storage (0) | 2024.12.03 |