타입스크립트: 리액트환경에서 사용하기
1. 배열
let 변수명:string[] = ['a', 'b'];
let fruits:string[] = ['apple', 'banana'];
let age:number[]= [ 1, 2, 3 ];
2. 튜플 (tuple)
서로 다른 타입의 원소를 순서에 맞게 가질 수 있는 특수한 형태의 배열.
배열은 number[], string[] 처럼 같은 타입의 원소만 가질 수 있었지만, tuple로 인해 서로 다른 타입의 원소를 가진 배열을 만들수있게 됨.
정의된 데이터 타입의 개수와 순서에 맞추어 저장을 하는 것이 필수
const person: [string, number, boolean] = ['Spartan', 25, false];
const person2: [string, number, boolean] = [25, 'Spartan', false]; // 오류!
여기서 잠깐! person2에서 정의된 데이터 타입의 개수보다 더 저장할 순 있다. 하지만 억지로 데이터를 더 넣으면 튜플 구조가 내부적으로 변경되어 좋은 선택은 아니다.
3. enum
= 열거형 데이터 타입
다양한 상수를 보다 더 이해하기 쉬운 문자열 이름으로 접근하고 사용할 수 있게 하는 타입.
enum 안에 있는 각 요소는 값이 설정되어 있지 않으면 기본적으로 숫자 0으로 시작.
enum 안에 있는 요소에는 number 혹은 string타입의 값만을 할당할 수 있다.
하지만
자바스크립트는 열거형(Enum)을 직접 지원하지 않는다. 대신, 객체를 활용하여 비슷한 효과를 낼 수 있다.
먼저, 각각의 상수를 객체의 속성으로 정의한다. 이때, 속성값은 해당 상수를 나타내는 값을 할당한다.
const Days = {
Sunday: 'Sunday',
Monday: 'Monday',
Tuesday: 'Tuesday',
Wednesday: 'Wednesday',
Thursday: 'Thursday',
Friday: 'Friday',
Saturday: 'Saturday'
};
이제 정의한 Enum 객체를 사용하여 변수에 상수값을 할당하고, 필요한 곳에서 이를 활용할 수 있다.
// 사용 예시
let today = Days.Wednesday;
console.log('Today is: ' + today);
4. readonly
TypeScript에서 객체의 속성이나 배열을 불변으로 만드는 데 사용되는 키워드. 클래스의 속성이나 인터페이스의 속성을 변경할 수 없게 만든다.
- 객체의 속성에 readonly 사용하기
// Person 객체를 정의합니다.
interface Person {
readonly name: string;
age: number;
}
// Person 객체를 생성합니다.
const person: Person = { name: 'John', age: 30 };
// 읽기 전용 속성인 name의 값을 변경하려고 하면 오류가 발생합니다.
// person.name = 'Jane'; // 에러: Cannot assign to 'name' because it is a read-only property.
- 배열에 readonly 사용하기
// 숫자로 이루어진 배열을 정의합니다.
const numbers: readonly number[] = [1, 2, 3, 4];
// 읽기 전용 배열이므로 값을 변경하려고 하면 오류가 발생합니다.
// numbers.push(5); // 에러: Property 'push' does not exist on type 'readonly number[]'.
// numbers[0] = 0; // 에러: Index signature in type 'readonly number[]' only permits reading.
5. any
TypeScript에서 any 타입은 모든 타입의 슈퍼 타입으로, 어떤 타입의 값이든 저장할 수 있다. 하지만, 프로그램의 타입 안정성을 저해할수있기때문에 가급적 사용을 권장하지 않는다.
let anything: any;
anything = 5; // 숫자도 ok
anything = 'Hello'; // 문자열 ok
anything = { id: 1, name: 'John' }; // JSON도 ok
6. unknown
앞서말한 any와 비슷한 역할을 하지만 더 안전한 방식으로 동작한다. 모든 타입의 값을 저장하며, 만약다른 타입의 변수에 할당하려면 명시적으로 타입을 확인해야 한다. unknown 타입은 런타임에 어떤 값이 될지 알 수 없는 경우에 유용하게 활용된다. 다른 타입들과 마찬가지로 unknown에 대한 값에 대해 작업하기 위해서는 명시적인 타입 체크나 타입 단언이 필요하다.
// 예제 1: 함수에서 unknown 사용하기
function printMessage(message: unknown) {
if (typeof message === 'string') {
// message가 문자열인 경우에만 출력
console.log(message.toUpperCase());
} else {
console.log('메시지는 문자열이 아닙니다.');
}
}
printMessage('Hello, TypeScript!'); // 출력: HELLO, TYPESCRIPT!
printMessage(42); // 출력: 메시지는 문자열이 아닙니다.
// 예제 2: 변수에 unknown 할당하고 타입 단언 사용하기
let userInput: unknown = 'user input';
let userName: string;
// 타입 단언을 통해 unknown을 명시적으로 string으로 변환
userName = userInput as string;
console.log(userName.length); // 이제 userName은 string 타입이므로 문자열 길이에 접근 가능
// 예제 3: 타입 가드 활용하기
function isString(value: unknown): value is string {
return typeof value === 'string';
}
if (isString(userInput)) {
console.log(userInput.length); // 이제 userInput은 string 타입이므로 문자열 길이에 접근 가능
} else {
console.log('userInput은 문자열이 아닙니다.');
}
7. union
TypeScript에서 두 개 이상의 타입을 허용하고자 할 때 사용된다. union을 사용하면 여러 타입 중 하나가 될 수 있는 변수, 매개변수 또는 반환 값 등을 표현할 수 있다.
// 예제 1: 문자열 또는 숫자를 받는 함수
function printId(id: string | number) {
console.log(`ID: ${id}`);
}
printId('user123'); // 출력: ID: user123
printId(456); // 출력: ID: 456
// 예제 2: 유니언 타입을 활용한 변수 선언
let value: string | number;
value = 'TypeScript';
console.log(value.length); // 문자열 속성에 접근 가능
value = 123;
console.log(value.toFixed(2)); // 숫자 속성에 접근 가능
// 예제 3: 타입 가드를 사용한 조건부 동작
function processInput(input: string | number) {
if (typeof input === 'string') {
// 문자열인 경우
console.log(input.toUpperCase());
} else {
// 숫자인 경우
console.log(input.toFixed(2));
}
}
processInput('Hello'); // 출력: HELLO
processInput(3.1415); // 출력: 3.14
string | number는 문자열이나 숫자 중 하나가 될 수 있는 타입을 나타낸다. 함수의 매개변수나 변수 등에 이러한 유니언 타입을 사용하면 여러 종류의 값을 다룰 때 유연성을 가질 수 있다. 조건부 동작을 수행할 때, typeof 연산자를 사용하거나 타입 가드를 활용하여 각 타입에 맞는 작업을 수행할 수 있다.
8. 함수
function addNumber (a:number,b:number):number {
return a + b;
}
addNumber(3, 7); // 10
// 괄호 안 input 옆에 들어가는 것은 input의 대한 type
// 괄호 후에 작성하는 type은 output data에 대한 type
9. 리액트 컴포넌트
기본형식
import React from 'react'
const App:React.FC = () => {
return (
<div>App</div>
)
}
export default App;
상황: 만약 App 컴포넌트의 data를 Store 컴포넌트 ui에 보여주려고 한다면
**rafce (arrow 함수로 컴포넌트 정의 스니펫)
//App.tsx
import React from 'react'
let data = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
return (
<div>App</div>
)
}
export default App;
import React from 'react'
import Store from './Store';
let data = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
return (
<div>
<Store info={data} /> //어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다.
</div>
)
}
export default App;
어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다.
10. type 새로 만들기
src > model > restaurant.ts 파일 만들기
타입을 새로만드는 방법은 총 두 가지. 둘의 큰 차이는 없지만, syntax가 조금 다르다.
1. type
export type Restaurant = {
name: string;
category: string;
address: {
city: string;
detail:string;
zipCode:number;
}
menu: {
name: string;
price:number;
category:string;
}[]
}
// let data = {
// name:'누나네 식당',
// category: 'western',
// address:{
// city: 'incheon',
// detail: 'somewhere',
// zipCode: 111132
// },
// menu: [
// {
// name:"pasta",
// price:2000,
// category: "PASTA"
// },
// {
// name:"pizza",
// price:3000,
// category: "PIZZA"
// }
// ]
// }
이제 App.tsx 로 다시 가서, let으로 선언한 data를 아래와 같이 바꿔준다.
import React from 'react'
import Store from './Store';
import { Restaurant } from './model/restaurant';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
return (
<div>
<Store info={data} /> //어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다.
</div>
)
}
export default App;
여기서 멈추는것이 아니라, restaurant.ts 에서 Restaurant라는 타입 안에 또 타입을 만들 수도 있다. Restaurant 안의 address와 menu를 를 타입으로 만들어보자.
export type Restaurant = {
name: string;
category: string;
address: Address;
menu: Menu[];
}
export type Address = {
city: string;
detail:string;
zipCode:number;
}
export type Menu = {
name: string;
price:number;
category:string;
}
App.tsx 로 돌아와서, data를 useState에 넣는다.
import React, { useState } from 'react'
import Store from './Store';
import { Restaurant } from './model/restaurant';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
const [myRestaurant, SetMyRestaurant] = useState(data);
return (
<div>
<Store myRestaurant={data} /> //어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다.
</div>
)
}
export default App;
useState도 타입을 명시해줘야 한다!
그럼 아래처럼 변한다. 이것을 Generic 이라고 부른다.
const [myRestaurant, SetMyRestaurant] = useState<Restaurant>(data);
import React, { useState } from 'react'
import Store from './Store';
import { Restaurant } from './model/restaurant';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
const [myRestaurant, SetMyRestaurant] = useState<Restaurant>(data);
return (
<div>
<Store info={myRestaurant} /> //어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다.
</div>
)
}
export default App;
이제 App.tsx 에서 넘길건 다 넘기고, 데이터를 받는 Store.tsx 에서 타입표기를 해줘야 한다.
2. interface
//Store.tsx
import React from 'react'
const Store:React.FC = (props) => {
return (
<div>Store</div>
)
}
export default Store
interface를 이용해 props 타입을 정해준다.
//Store.tsx
import React from 'react'
import { Restaurant } from './model/restaurant'
interface OwnProps {
info:Restaurant
}
const Store:React.FC<OwnProps> = (props) => {
return (
<div>Store</div>
)
}
export default Store
구조분해할당을 통해 props를 넘겨줄수도 있다. (아래참고)
//Store.tsx
import React from 'react'
import { Restaurant } from './model/restaurant'
interface OwnProps {
info:Restaurant
}
const Store:React.FC<OwnProps> = ({info}) => {
return (
<div>Store</div>
)
}
export default Store
ui 에 받아온 데이터 보여주기
import React from 'react'
import { Restaurant } from './model/restaurant'
interface OwnProps {
info:Restaurant
}
const Store:React.FC<OwnProps> = ({info}) => {
return (
<div>{info.name}</div>
)
}
export default Store
++ 함수도 보내보자!
식당의 주소를 바꾸는 함수를 만들기
const changeAddress = (address: Address) => {
SetMyRestaurant({...myRestaurant, address: address});
}
return (
<div>
<Store info={myRestaurant} changeAddress={changeAddress}/>
{/* 어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다. */}
</div>
)
// App.tsx
import React, { useState } from 'react'
import Store from './Store';
import { Address, Restaurant } from './model/restaurant';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
const [myRestaurant, SetMyRestaurant] = useState<Restaurant>(data);
const changeAddress = (address: Address) => {
SetMyRestaurant({...myRestaurant, address: address});
}
return (
<div>
<Store info={myRestaurant} changeAddress={changeAddress}/>
{/* 어떤 데이터를 보내는지도 type 명시해야함. 하지만 현재 data는 다양한 타입들의 데이터가 모인 한마디로 정의할 수 없는 타입이다. 따라서 우리가 data를 위한 타입을 새로 만들어줘야 한다. */}
</div>
)
}
export default App;
이제 Store.tsx 에서 받아온 데이터 타입 정해주기
//Store.tsx
import React from 'react'
import { Address, Restaurant } from './model/restaurant'
interface OwnProps {
info:Restaurant,
changeAddress(address:Address):void
// return type이 없을경우 void 를 적고, 다른 타입이 있을경우 적어준다 예(boolean)
}
const Store:React.FC<OwnProps> = ({info}) => {
return (
<div>{info.name}</div>
)
}
export default Store
11. extends
만약 restaurant의 Menu 타입에 다른 것들을 더 붙이고 싶을 때 사용한다.
1) interface안에서 extends
1) interface안에서 extends
best menu를 보여주는 컴포넌트를 새로 만들어준다.
src > BestMenu.tsx
import React from 'react'
const BestMenu:React.FC = () => {
return (
<div>BestMenu</div>
)
}
export default BestMenu
App 에서 BestMenu 컴포넌트로 데이터를 넘겨준다.
import React, { useState } from 'react'
import Store from './Store';
import { Address, Restaurant } from './model/restaurant';
import BestMenu from './BestMenu';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
const [myRestaurant, SetMyRestaurant] = useState<Restaurant>(data);
const changeAddress = (address: Address) => {
SetMyRestaurant({...myRestaurant, address: address});
}
return (
<div>
<Store info={myRestaurant} changeAddress={changeAddress}/>
<BestMenu name="불고기피자" category="피자" price={1000}/>
</div>
)
}
export default App;
데이터를 받는 BestMenu.tsx 에서는 받는 데이터에 대한 타입을 interface로 정의하면 되는데, 우리가 넘겨받는 데이터의 타입은 이미 이전에 restuarant.ts에서 type으로 만들어놓았기 때문에! extends Menu {} 를 써준다. 그리고 이것 이외에 또 추가하고 싶은 것들을 {} 안에 넣어준다. 예를 들어 함수를 추가해준다고 가정해보자.
import React from 'react'
import { Menu } from './model/restaurant'
interface OwnProps extends Menu{
// 추가하고싶은 것들만 적기
}
const BestMenu:React.FC<OwnProps> = () => {
return (
<div>BestMenu</div>
)
}
export default BestMenu
App.tsx에서 아래함수를 추가해보자.
const showBestMenuName = (name:string) => {
return name
}
잘 넘겨주고
<BestMenu name="불고기피자" category="피자" price={1000} showBestMenuName={showBestMenuName}/>
import React, { useState } from 'react'
import Store from './Store';
import { Address, Restaurant } from './model/restaurant';
import BestMenu from './BestMenu';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
const [myRestaurant, SetMyRestaurant] = useState<Restaurant>(data);
const changeAddress = (address: Address) => {
SetMyRestaurant({...myRestaurant, address: address});
}
const showBestMenuName = (name:string) => {
return name
}
return (
<div>
<Store info={myRestaurant} changeAddress={changeAddress}/>
<BestMenu name="불고기피자" category="피자" price={1000} showBestMenuName={showBestMenuName}/>
</div>
)
}
export default App;
BestMenu에서 잘 받아오자.
// Store.tsx
import React from 'react'
import { Menu } from './model/restaurant'
interface OwnProps extends Menu{
showBestMenuName(name:string):string
}
const BestMenu:React.FC<OwnProps> = ({name, price, category, showBestMenuName}) => {
return (
<div>BestMenu</div>
)
}
export default BestMenu
화면에 잘 그려주자
//BestMenu.tsx
import React from 'react'
import { Menu } from './model/restaurant'
interface OwnProps extends Menu{
showBestMenuName(name:string):string
}
const BestMenu:React.FC<OwnProps> = ({name, price, category, showBestMenuName}) => {
return (
<div>
<h1>Best Menu</h1>
<p>{name}</p>
<p>{price}</p>
<p>{category}</p>
</div>
)
}
export default BestMenu
2) type안에서 extends
2) type안에서 extends
type OwnProps = menu & {
showBestMenuName(name:string):string
}
그럼 type과 interface의 차이는 무엇인가요?
type에서는 omit 기능이 있다!
2-1) Omit
type에서 특정 요소를 제외하고 쓰고싶을 때!
예를 들어 Address의 타입에서 zipcode를 빼고 싶다고 하면
restuarnt.ts
export type AddressWithoutZipcode = Omit<Address, 'zipCode'>
상황 예 2) App 에서 BestMenu로 넘겨주는 데이터중 price를 빼고싶을 때
먼저 App 에서
import React, { useState } from 'react'
import Store from './Store';
import { Address, Restaurant } from './model/restaurant';
import BestMenu from './BestMenu';
let data:Restaurant = {
name:'누나네 식당',
category: 'western',
address:{
city: 'incheon',
detail: 'somewhere',
zipCode: 111132
},
menu: [
{
name:"pasta",
price:2000,
category: "PASTA"
},
{
name:"pizza",
price:3000,
category: "PIZZA"
}
]
}
const App:React.FC = () => {
const [myRestaurant, SetMyRestaurant] = useState<Restaurant>(data);
const changeAddress = (address: Address) => {
SetMyRestaurant({...myRestaurant, address: address});
}
const showBestMenuName = (name:string) => {
return name
}
return (
<div>
<Store info={myRestaurant} changeAddress={changeAddress}/>
<BestMenu name="불고기피자" category="피자" price={1000} showBestMenuName={showBestMenuName}/>
</div>
)
}
export default App;
price 빼기
<BestMenu name="불고기피자" category="피자" showBestMenuName={showBestMenuName}/>
BestMenu 수정
// BestMenu.tsx
interface OwnProps extends Menu{
showBestMenuName(name:string):string
}
interface OwnProps extends Omit<Menu, 'price'> {
showBestMenuName(name:string):string
}
그 후, BestMenu 컴포넌트에서 구조분해로 받아온 price는 삭제
const BestMenu:React.FC<OwnProps> = ({name, price, category, showBestMenuName}) => {
return (
<div>
<h1>Best Menu</h1>
<p>{name}</p>
<p>{price}</p>
<p>{category}</p>
</div>
)
}
const BestMenu:React.FC<OwnProps> = ({name, category, showBestMenuName}) => {
return (
<div>
<h1>Best Menu</h1>
<p>{name}</p>
<p>{price}</p>
<p>{category}</p>
</div>
)
}
2-2) Pick
선택해서 사용하는 것
상황: Restaurant 타입에서 category만 가져오고 싶다!
// restaurant.ts
export type RestaurantOnlyCatetory = Pick<Restaurant, 'category'>
12. '?'
Omit과 비슷하게 쓰일 수 있는 방법으로
export type Address = {
city: string;
detail:string;
zipCode?:number;
}
zipCode가 있을수도 있고, 없을 수도 있다는 뜻이다.
13. API 콜을 타입스크립트로 처리하기
제네릭을 이용하여 'T'라는 이름으로 input이 들어온다는 가정
export type ApiResponse<T> = {
data: T[],
totalPage: number,
page: number
}
export type RestaurantResponse = ApiResponse<Restaurant> // data에서 Restaurant에 해당하는 값
export type MenuResponse = ApiResponse<Menu> // data에서 Menu에 해당하는 값
'부트캠프 개발일지 2023-2024 > TypeScript 타입스크립트' 카테고리의 다른 글
[11주차] 타입스크립트 : 타입을 왜 제대로 알아야 하는가 (0) | 2023.12.13 |
---|