[Pre Onboarding] 8월 23일 5주차

김종원·2021년 8월 23일
0

[Pre Onboarding]

목록 보기
9/12
post-thumbnail

[Pre Onboarding] 8월 23일 월요일

TypeScript

공부방법 :
기존 리액트 코드를 → TypeScript 버전으로 처음부터 다시 만들면서 기본 타입들을 익혀보는것이 좋습니다.
일단 써보면서 익숙해지는 것이 중요합나더!
(어차피 제대로 된 타입을 사용하지 않으면 에러가 나서 잘못된 방향으로 개발하게 되지는 않을겁니다.)

함수

1) 함수정의

function formatDate(d: Date): string {
	let year = d.getFullYear();
	let month = d.getMonth() + 1;
	let day = d.getDate();

	return `${year}년 ${month}월 ${day}일`
}

const today = formatDate(new Date());
  • 매개변수의 우측에 콜론(:)과 매개변수의 타입을 정의합니다.
  • return 값의 타입은 소괄호()우측에 타입을 정의합니다.
  • formatDate의 매개변수 d는 Date 객체 타입을 받아야하고, return 값은 문자열이어야 합니다.

2) 다양한 매개변수 타입

let formatDate = (d: Date | number, lang = "ko", delimiter?: string): string => {
	// 로직 생략
};

formatDate(new Date());
formatDate(20201028, "en");
formatDate(20201028, "en", "-");
  • 두 번째 매개변수인 lang의 경우 인자를 전달하지 않으면 "ko"라는 문자열 값이 할당되도록 했습니다.
  • delimiter는 물음표를 사용하여 선택적(넣어도되고 안넣어도 되고)으로 인자를 전달하도록 했습니다.

새로운 타입 - 기본

1) Any

any는 어떤 타입도 지정할 수 있습니다. 자바스크립트로 컴파일된 코드를 보면 아무 타입도 지정하지 않은 원래의 자바스크립트 코드와 똑같습니다. 즉, any 타입을 사용하면 타입스크립트를 쓸 이유가 없는 것과 다름 없습니다.

가끔 개발은 바쁜데 어떤 타입을 지정해야 할지 몰라서 any를 쓰는 경우도 있는데, 웬만하면(무조건) 쓰지 않도록 노력해야합니다.

let value: any;

// 타입 에러 없음
value = 'kim';
value = 1234;
value = [1,2,3,4];

2) void

void는 any와는 반대로 어떤 타입도 없다는 뜻입니다. 보통 함수에서 반환값이 없는 경우 사용합니다.

3) Enum

Enum(열거형)은 이름이 있는 정해진 값의 세트라고 생각하시면 됩니다

// Category라는 이름의 enum 타입을 정의한다
// Category의 enum 값에 Past, Pizza, Dessert 가 있다.

enum Category {
	Pasta,
	Pizza,
	Dessert
}

// menuCategory 변수는 Category 타입으로, 해당 enum 값 중 하나를 가질 수 있다.
let menuCategory: Category = Category.Pasta;

Category의 Pasta이라는 값을 menuCategory 변수에 할당하면 0이 저장됩니다.
특정 값이 없는 Enum의 경우 0부터 값이 주어지기 때문입니다.

// enum 값은 0부터 차례대로 숫자 값을 가진다.

enum Category {
	Pasta,  
	Pizza,  
	Dessert 
}

console.log(Category.Pasta);    // 0
console.log(Category.Pizza);    // 1
console.log(Category.Dessert);  // 2
// enum에 값 부여

enum Category {
	Pasta = 'pasta',
	Pizza = 'pizza',
	Dessert = 'dessert'
}

let menuCategory: Category = Category. Pasta;

상수로된 세트 값이 필요할 때 사용합니다.

ts는 자바스크립트코드가 있는것
tsx는. ....

4) union

union타입은 여러 타입이 올 수 있을 때 사용합니다.

function formatDate(date: string | number | Date): string {
	// 로직 생략

	return `${year}년 ${month}월 ${day}일`
}

formatDate(20201028);
formatDate(new Date());
formatDate('20201028');

**formateDate 함수**의 매개변수로 문자열, 숫자, Date 객체 타입으로 모두 받을 수 있다면 → 수직선(|)을 사용해서 정의할 수 있습니다.

Tuple과 Union의 사용 차이??????????

5) Type 별칭 (Type Alias)

type 이라는 키워드를 사용하여 타입에 이름을 붙여서 사용할 수도 있습니다. 타입을 재사용하거나, 객체를 위한 타입을 정의할 때 많이 사용됩니다.

type ID = number | string;

function checkInfo(info: { id: ID; pw: string }) {

}

let id: ID = "1010";
checkInfo({ id, pw: "password" });
type ID = number | string;
type Info = {
	id: ID;
	pw: string;
};

function checkInfo(info: Info) {

}

let id: ID = "1010";
checkInfo({ id, pw: "password" });

checkInfo 함수의 매개변수 info 객체를 위해 Info라는 타입을 만들어주었습니다.

객체의 타입을 정의하는 법이 두 가지 있는데 하나는 위와 같이 type으로 정의하는 것이고, 나머지 하나는 Interface 타입을 사용하는 것입니다. 인터페이스는 뒤에서!

타입추론 (Type Inference)

정적타입언어는 원래 모든 변수에 정확히 타입을 정의하지 않으면 에러가 나지만, 타입스크립트에서는 타입을 표기하지 않아도 타입스크립트 컴파일러가 변수에 할당된 값을 보고 타입을 추측합니다.

이러한 특성이 타입스크립트를 더욱 사용하기 편리하게 해줍니다. 코드량이 줄어들고 불필요한 타입 정의를 생략해서 가독성을 높이기도 합니다. 굳이 필요없는 타입 정의는 생략하되, 나중에 유지보수를 위해서는 타입을 정의하는 것이 스스로에게 좋습니다.

Interface

자바스크립트에는 인터페이스가 존재하지 않지만, 자바와 같은 언어에서는 자주 사용되는 개념입니다. 자바에서의 인터페이스는 클래스에서 꼭 구현해야하는 메서드를 정의할 때 사용됩니다.

타입스크립트에서의 인터페이스는 리액트 프로젝트에서 가장 많이 쓰이는 타입 중 하나일 것입니다. state와 props의 타입을 정의하는데 주로 사용합니다.

1) 객체 타입 정의

인터페이스는 객체의 타입을 정의할 때 사용됩니다. 아래는 매개변수로 name 프로퍼티가 포함된 객체를 받습니다.

function getInfo(restaurant: { name: string }) {
	console.log(restaurant.name);
}

위와👆🏼 같이 객체 타입을 정의하기 위해 interface를 바로 작성할 수도 있지만 아래와 같이 타입에 이름을 붙여 사용할 수 있습니다.

interface Restaurant {
	name: string;
	star: 5;
}

function getInfo(restaurant: Restaurant) {
	console.log(restaurant.name);
}

const chroad3 = {
	name: "취향로3가",
	star: 5
};

getInfo(chroad3);

첫 번째 코드와 같이 프로퍼티가 하나인 경우에는 바로 사용해도 되지만, 프로퍼티가 두 개 이상일 때는 두 번째 예제처럼 타입을 정의하고 사용하는 것이 더 깔끔합니다.

아래와 같이 예정되어있지 않은 프로퍼티를 추가하면 에러가 발생합니다!

interface Restaurant {
	name: string;
	star: number;
}

const chroad3: Restaurant = {
	name: "취향로3가",
	star: 5,
	address: "을지로3가",
};

"Type '{ name: string; star: number; address: string; }' is not assignable to type 'Restaurant'. Object literal may only specify known properties, and 'address' does not exist in type 'Restaurant'. ".

Restaurant 타입인 chroad3에 타입이 정의되지 않은 프로퍼티를 추가할 수 없다는 메시지입니다.
이는 객체 리터럴로 값을 바로 할당할 때만 나오는 메시지인데 아래와 같이 코드를 작성하면 오류 메시지가 나오지 않습니다.

interface Restaurant {
	name: string;
	star: number;
}

const chroad3Value = {
	name: "취향로3가",
	star: 5,
	address: "을지로3가",
};

const chroad3: Restaurant = chroad3Value;

다양한 프로퍼티 타입

객체의 프로퍼티 타입을 정의할 때 다양하게 사용할 수 있습니다.

// readonly는 읽기만 가능한 프로퍼티로, 값을 수정할 수 없습니다.
// 프로퍼티명 다음에 물음표(?)를 사용하면 해당프로퍼티를 추가해도 되고 안해도 됩니다.
interface Restaurant {
	readonly name: string;
	star: number;
	address?: string;
}

const chroad3: Restaurant = {
	name: "취향로3가",
	star: 5,
};

chroad3.name = "chroad3";  // 에러: Cannot assign to 'name' because it is a read-only property.
chroad3.star = 4;

const sipboon: Restaurant = {
	name: "십분의일",
	star: 5,
	address: "을지로3가",
};

인터페이스 확장

extends 키워드를 사용하여 기존에 정의된 인터페이스를 확장해서 사용할 수도 있습니다.

interface BasicInfo {
	name: string;
	star: number;
}

interface DetailInfo extends BasicInfo {
	address: string;
	phone: string;
	position: number[];
}

const sipboon: BasicInfo = {
	name: "십분의일",
	star: 5,
};

const chroad3: DetailInfo = {
	name: "취향로3가",
	star: 5,
	address: "을지로3가",
	phone: "123-456-7890",
	position: [37.565496, 126.99142],
};

교차 타입(Intersection Types) 활용

interface BasicInfo {
	name: string;
	star: number;
}

interface DetailInfo {
	position: number[];
}

type Info = BasicInfo & DetailInfo;

합쳐서 새로운 인터페이스를 만드는 것은 아니고, & 기호를 사용하여 기존에 정의했던 인터페이스를 "합쳐서" 새로운 타입을 만드는 것입니다.

타입호환성 (Type Compatibility)

interface Restaurant {
	name: string;
	star: number;
}

let chroad3: Restaurant;  // chroad3 변수 선언

let chroad3withAddress = {   // chroad3withAddress 변수 선언하고 초기화
	name: "십분의일",
	star: 5,
	address: "을지로3가",
};

chroad3 = chroad3withAddress;

Restaruant 타입은 name과 star 두 가지 프로퍼티만 있기 때문에, **chroad3** 변수에 address를 추가한 객체를 할당했으면 오류가 났을 것입니다.

하지만 address 프로퍼티가 추가된 chroad3withAddress를 chroad3 변수에 할당하면 오류가 없습니다.
왜그럴까요? 타입호환성 때문입니다.

chroad3withAddress는 앞에서 배웠던 타입추론에 따라 자기만의 타입을 갖게 됩니다. Restaurant와 같이 인터페이스 타입으로서 타입 이름이 있는 것은 아니지만, 이름이 없는 아래와 같은 구조의 타입이 됩니다.

{
	name: string;
	star: number;
	address: string;
}

Restaruant 타입은 name과 star 두 가지 프로퍼티만 있기 때문에, **chroad3** 변수에 address를 추가한 객체를 할당했으면 오류가 났을 것입니다.

하지만 address 프로퍼티가 추가된 chroad3withAddress를 chroad3 변수에 할당하면 오류가 없습니다.
왜그럴까요? 타입호환성 때문입니다.

chroad3withAddress는 앞에서 배웠던 타입추론에 따라 자기만의 타입을 갖게 됩니다. Restaurant와 같이 인터페이스 타입으로서 타입 이름이 있는 것은 아니지만, 이름이 없는 아래와 같은 구조의 타입이 됩니다.

제네릭 (Generic)

타입을 정의할 때 특정 타입으로 고정된 것이 아니라, 내가 원하는 타입을 자유롭게 지정해서 계속 사용하고 싶을 때 제네릭을 사용합니다.
한 번 더 설명하자면, 제네릭은 타입을 마치 재사용할 수 있는 변수처럼 취급해서 여러 군데에서 사용하고 싶을 때 사용합니다.

1) 제네릭 - 함수

function makeArr<T>(el: T): T[] {
	return [el];
}

makeArr<number>(1);      // 1
makeArr<string>("1점");  // 2
makeArr<boolean>(true);  // 3

제네릭은 함수명 우측에 화살표괄호(<>)로 감쌉니다. T는 특별한 의미가 없는 변수명 같다고 생각하면 되는데, 제네릭을 사용할 때 보통 대문자 T를 자주 사용합니다. 내가 원하는 이름으로 자유롭게 작성할 수 있습니다.

각 반환 타입은 1번이 number[], 2번이 string[], 3번이 boolean[]이 됩니다.

makeArr(1);
makeArr(1점");
makeArr(true);

제네릭 타입을 명시하지 않아도 컴파일러는 전달되는 인자값을 통해 타입을 추론하여 T의 타입을 결정합니다.

코드가 훨씬 간결하고 가독성이 높아졌지만, 추후 유지보수를 위해서나 더 복잡한 예제의 예상치 못한 오류를 위해 제네릭 타입을 정확히 써주는게 좋을 때가 있습니다.

리액트를 위한 타입스크립트

1) type이냐 interface이냐!

리액트 프로젝트에서 객체로 관리되는 state, props 뿐 아니라 객체의 타입을 정의해야 할 때가 많습니다. 객체 타입을 정의하는 방법은 object, interface, type alias 세 개가 있습니다.

  • 공식문서에도 interface 사용하라고 나와있음
// object type으로 정의한 경우
function checkInfo(info: { id: string; pw: string }) {
	console.log(info);
}

object 타입을 사용한 경우는 → info의 타입은 재사용할 수 없으며, 프로퍼티가 많을 수록 가독성이 좋지 않으니 state나 props를 정의할 때 interface나 type을 사용하는 것이 좋습니다.

type과 interface는 확장, 병합 가능성이라던지 선언 등의 차이점이 있어서 상황에 따라 적절하게 사용하면 되지만, 타입스크립트 공식 문서에는 type보다 interface를 사용하라고 되어있습니다.
(타입스크립트 기반의 여러 오픈 소스 중에 type을 사용하는 것도 종종 보긴 했음)

2) 함수형 컴포넌트

리액트의 FC(FunctionComponen) 타입 사용

리액트에서 함수형 컴포넌트를 위한 FunctionComponent 타입을 제공합니다.
줄여서 FC로 사용할 수 있습니다.
컴포넌트 함수 타입을 정의한 FC에 제네릭으로 props 타입을 전달해야 합니다.

interface StarsProps {  // prop 타입 정의
	star: number;
}

const Stars: React.FC<StarsProps> = ({ star }) => {   // 2
	// 로직

	return ( 
		<div>
			{makeStars(star)}
		</div>
	)
}

Stars 컴포넌트에 return 타입은 따로 명시하지 않았는데 React.FC 타입에서 함수의 매개변수인 props와 반환값인 JSX가 이미 정의되어있기 때문입니다.

리액트 코드에서 FC를 정의한 부분을 찾아보면 더 이해하기가 쉬울것입니다.

// 리액트의 FC 타입 분석하기
// node_modules폴더 하위에 "@types/react/index.d.ts"경로

type FC<P = {}> = FunctionComponent<P>;  // 1

interface FunctionComponent<P = {}> {  // 2
	(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;  // 3
	propTypes?: WeakValidationMap<P>;
	contextTypes?: ValidationMap<any>;
	defaultProps?: Partial<P>;
	displayName?: string;
}
  1. FunctionComponent라는 타입에 P를 제네릭으로 받은 것이 타입 FC입니다. FC타입을 사용할 때 제네릭을 넘기지 않으면 빈객체를 기본값으로 할당합니다.
  2. FunctionComponent은 제네릭 P를 넘겨받는 interface 타입입니다. 총 5개의 프로퍼티를 갖고 있는데, 하나만 필수이고 물음표가 붙은 나머지 4개는 함수형 컴포넌트에 선택적으로 할당할 수 있습니다.
  3. 함수 타입 정의! props매개변수를 받아서 ReactElement 타입 or null을 반환합니다. PropsWithChildren과 ReactElement 타입도 분석해보세요!!

일반적인 함수정의 방식

함수형 컴포넌트는 곧 함수이므로, 이번에는 일반 함수를 선언하듯이 타입을 정의해보겠습니다.

interface StarsProps {   // prop 타입 정의
	star: number;
}

const Stars = ({ star }: StarsProps): JSX.Element => {  // 1
	// 로직

	return ( 
		<div>
			{makeStars(star)}
		</div>
		)
}
  • 함수형 컴포넌트는 props를 매개변수로 받고 JSX를 return 해야 합니다. 그래서 매개변수 타입을 StarsProps로, 반환하는 타입을 JSX.Element로 직접 명시했습니다. (return 타입을 작성하지 않아도 타입추론이 되므로 JSX.Element는 생략할 수 있습니다)

Hooks

useState

//특별한 타입 정의 없이

const [like, setLike] = useState(false);

//로직 생략
setLike(!like);

위와 같이 특별히 타입을 명시하지 않더라도 타입추론을 통하여 타입이 지정됩니다. like는 boolean 형이 되고, setLike 함수는 boolean 형만 인자로 받게 됩니다.

VSCode에서 useState 위에 마우스를 올려보면 추론된 타입을 확인할 수 있습니다.

useState<boolean>(initialState: boolean | (() => boolean)): [boolean, Dispatch<SetStateAction<boolean>>]

위와 같이 타입추론을 통해 특별히 타입을 명시하지 않아도 되지만, 아래와 같이 직접 명시해줄 수도 있습니다.

// state의 타입을 명시해서
const [like, setLike] = useState<boolean>(false);

//로직 생략
setLike (!like);

개발자가 코드에 작성된 초기값을 통해 타입을 알 수 있으므로 굳이 명시하지 않아도 괜찮다고 생각합니다.
타입 추론을 통해 지정된 타입은, 무조건 해당 타입만 사용할 수 있습니다. 즉 위의 like 예제는 무조건 boolean형만 쓸 수 있는 것입니다.

하지만 우리가 state의 초기값을 설정할 때 null을 자주 사용하기도 하죠. 그럴 때는 유니온 타입을 사용하여 타입을 명시해주면 됩니다.

// 유니온 타입으로 전달
const [like, setLike] = useState<boolean | null>(null);

//생략
if (like) {
	// 좋아요 true 일 때만 실행할 로직
}

state에 객체가 포함된 경우에는 자세한 프로퍼티 타입을 알 수 있도록 아래와 같이 정의해주면 됩니다.

// state 타입 정의
interface Comment {
	comment: string;
	username: string;
	date: string;
	star: number;
}

//생략

const [comments, setComments] = useState<Comment[]>([]);

위의 코드는 comments 상태가 객체 배열인 경우로, 아래와 같이 생긴 데이터를 받아야할 때를 대비하여 정의한 것입니다.

// comments 데이터
[{
	comment: '넘 맛있어요..',
	username: 'kim',
	date: '2020.07.01',
	star: 4
}, {
	comment: '가성비 최고!',
	username: 'lee',
	date: '2020.07.20',
	star: 5
}]

useRef

useRef 또한 useState처럼 타입을 명시하지 않아도 됩니다. 만약 코드의 명확성을 위해서 타입을 정의하고 싶은 경우 아래와 같이 ref의 태그에 맞는 제네릭을 넘겨주면 됩니다.

전역변수
window에 외부 라이브러리 변수를 추가하려면 아래와 같이 해당 라이브러리에서 사용하는 전역 변수명을 추가하면 됩니다.

declare global {
interface Window {
kakao: any;
}
}
예를 들어 kakao 로그인을 위해서 라이브러리를 추가하는 경우
-> config.js start.js 파일을 만들어서 처음에 올려놔야할곳에 올려놓고 import해올때 사용

profile
발전하기위한 기록

0개의 댓글