이전 포스팅에 이어서, TS에서의 함수 표현법과 TS를 React에서 사용하는 방법에 대해 기록하려고 한다.✍

TS의 함수 선언
TS에서는 함수를 어떻게 선언하는지 설명하기에 앞서, JS와 TS의 차이부터 알아야 한다.💡
- JS는 매개변수를 넘겨주고 호출할 때, 넘겨준 인자만큼 전달하지 않아도 오류가 나지 않는다.
- TS는 매개변수가 2개면, 호출할 때도 반드시 2개의 파라미터를 전달해야 한다.
/* JS와 TS의 차이 */
// 1. JS의 함수
funtion sum (a, b, c){
return a + b + c;
}
sum(1, 2); // 파라미터가 하나 빠져도 에러 없음
// 2. TS의 함수
function sum(a:number, b:number, c:number){
return a + b + c;
}
sum(1, 2); // 파라미터가 모자라서 에러 발생
TS는 선언할 때 반드시 타입을 명시해야하기 때문에, 함수 역시 인자와 반환값의 타입을 설정해야 한다.
TS에서의 함수 선언 방법은 아래 코드와 같다.
/* 기본적인 선언방법 */
funtion sum(a: number, b: number): number {
return a + b;
}
const sum = (a: number, b: number): number => {
return a + b;
};
const sum = (a: number, b: number): number => a + b;
이처럼 return 키워드로 값을 반환하거나, 코드가 짧으면 스코프를 생략하고 화살표 함수로 작성할 수 있다.
만약 return 키워드가 없다면, 함수 자체의 타입을 아래와 같이 설정하면 된다.
Void
return 값이 없는 함수에 대한 데이터 타입.
앞서 TS에서 함수를 호출할 때는 넘겨주는 매개변수의 개수만큼 파라미터를 할당해야 오류가 생기지 않는다고 했는데, 만약 존재 유무가 상관이 없는 매개변수라면 물음표를 사용해 '선택적 매개변수(Optional Parameter)'로 설정하면 된다.
// void: return 값이 없는 함수
// return값 있는 경우 void 선언 불가
function consoleStr(str: string): void {
console.log(str);
}
function tsPrint(a: number, b?: number): void {
console.log(a);
console.log(b);
}
tsPrint(1, 2);
tsPrint(1); // 옵셔널한 값이어서 하나 빠져도 에러 없음
function myFunc(a: number): void {
console.log('function keyword 사용한 함수');
}
const myFunc2 = (a: number): void => {
console.log('함수 표현식으로 작성한 함수');
};
TS 함수의 interface
TS에서 객체를 만들때 사용했던 interface는 함수에서도 사용할 수 있다.
/* TS 함수와 interface */
interface Calc {
(a: number, b: number): number;
}
const sum: Calc = (a, b) => {
return a + b;
};
interface Greet {
name: string;
hi(): void; // 호출 시 hi라는 문자열을 콘솔에 출력, 리턴값 x
bye(str: string): string;
// 인자로 문자열을 받아서 리턴하므로 타입을 str으로 작성
}
const user: Greet = {
name: 'layla',
hi() {
console.log('hi!!');
},
bye(str: string) {
return `bye ${str}`;
},
};
user.hi();
user.bye('안녕');
- 가장 기본적인 사용방법은 위 코드의 Calc처럼 인자와 리턴값의 타입을 지정하고, 그걸 sum이라는 함수를 선언할 때 사용하는 것이다.
- 반대로 인터페이스의 key에 함수가 들어갈 수도 있다. 이런 경우에는 키워드나 스코프 없이 핵심적인 부분만 작성하면 된다.
Never
함수의 끝에 절대 도달하지 않는, 무한루프 함수인 경우에만 사용한다.
/* Never */
function neverEnd(): never {
while (true) {
console.log('ever after');
// if (a>5) break; -> ERROR: 인자가 조건이 충족되면 무한루프 중지됨
// break 가능성이 있으면 never 키워드 선언 못 함
}
}
Generic
함수를 호출하는 시점에 데이터 타입을 지정할 수 있도록 하는 문법.
다양한 데이터 타입을 받을 수 있다는 부분에서 Any와 비슷하게 느껴지지만 차이가 있다.
- Any는 들어올 수 있는 데이터 타입에 제한없이 '계속 열려있는' 타입.
- Generic은 함수가 호출된 후에 해당 함수에 들어오는 매개변수에 따라 데이터 타입도 같이 결정된다.
/* Generic */
function printSth<T>(x: T): T {
console.log(x);
return x;
}
printSth<string>('hello');
printSth<number>(1);
// 두 개의 다른 인자 generic으로 받기
function getTwoParams<T, K>(x: T, y: K): void {
console.log(x, y);
}
getTwoParams<number, string>(1, '2');
getTwoParams<number[], string[]>([1, 2, 3], ['1', '2']);
- 함수를 실행할 때, 전달받은 데이터 타입을 인자와 반환값에 모두 넣겠다는 의미를 갖고 있다.
함수 이름 뒤, 인자 뒤, 인자가 들어가는 소괄호 뒤에 Generic을 작성한다. - 위 코드의 printSth 함수는
→ 호출할 때 문자 데이터를 받겠다고 지정한 경우에는 인자로 'hello'가 들어갔고,
→ 숫자 데이터를 받겠다고 지정한 경우에는 인자로 1이 들어갔다. - 다수의 인자를 받는 경우에도 마찬가지로 작성한다.
interface에서의 generic
앞서 함수 이름에 <T>라고 표시하여 generic을 설정했던 것처럼, 인터페이스도 이름 뒤에 <T>라고 표시하여 설정한다.
interface Phone<T> {
name: string;
company: string;
price: number;
option: T; // 어떤 value가 들어올지 몰라서 T
}
const iphone15: Phone<string> = {
name: 'iphone15',
company: 'apple',
price: 130,
option: 'lightblue',
};
const flip2: Phone<string[]> = {
name: 'flip2',
company: 'samsung',
price: 180,
option: ['black', 'white', 'purple'],
};
- 위 코드에서는 phone이라는 인터페이스를 설정하면서 generic이 있다는 것을 명시했고, 해당 인터페이스의 key마다 어떤 데이터 타입을 가진 value가 들어오는지를 설정했다. '옵션'이라는 key는 어떤 값이 올지 몰라서 generic으로 설정했다.
- iphone15라는 객체를 만들면서, 앞서 만든 phone이라는 인터페이스를 가져오고, generic이 들어올 '옵션'의 값이 문자 데이터라고 지정했다.
- flip2라는 객체 역시 phone이라는 인터페이스를 가져오면서, generic이 들어올 '옵션'의 값으로 문자의 배열이 들어온다고 지정했다.
// generic으로 넘겨줄 T를 type으로 선언
type IphoneOption = {
color: string;
storage: number;
};
const iphone16: Phone<IphoneOption> = {
name: 'iphone16',
company: 'apple',
price: 180,
option: {
color: 'silver',
storage: 256,
},
};
- generic으로 넘겨줄 옵션의 값을 type으로 선언할 수도 있다.
- 위 코드에서는 IphoneOption이라는 타입을 설정해서 color와 storage에 어떤 데이터 타입이 들어오는지를 지정했고, iphone16이라는 객체를 만들면서 미리 만들어둔 IphoneOption 타입을 generic으로 사용했다.
TS with React
리액트 컴포넌트를 TS에서 사용하려면 터미널에서 아래 명령어로 새로운 프로젝트를 만들어야 한다.
// TS 리액트 프로젝트 설치
$ npx create-react-app {프로젝트 이름} --template typescript
만약 기존 프로젝트에 TS를 적용하려면 아래 명령어로 모듈을 설치하고,
js/jsx 확장자를 ts/tsx로 변경, tsconfig.json 파일을 추가해야 한다.
// 모듈 설치
$ npm install typescript @types/node @types/react @types/react-dom @types/jest
props, interface
부모 컴포넌트에서 props로 데이터를 받을 때는 어떤 형태로 넘어오는지 데이터 타입을 명시해야 한다.
// PropsTypes.tsx
interface Props {
name: string;
age?: number; // age props를 옵셔널하게 받고 있음
}
/* 방법 1. */
export default function PropsTypes({ name, age }: Props) {
return (
<>
<h2>props로 전달받은 데이터의 타입 interface로 지정하기</h2>
<div>name: {name}</div>
{age && <div>age: {age}</div>}
</>
);
}
- Props라는 인터페이스를 만들어서 이름과 나이 props의 데이터 타입을 명시했고, 나이의 경우에는 옵셔널하게 받고 있다.
- 함수형 컴포넌트 선언문에서 인자로 props를 구조분해할당된 상태로 바로 받았고, 인자를 받으면서 인터페이스로 만들어둔 데이터 타입을 명시했다.
- return 내부에 브라우저에서 실제로 보여질 부분을 작성하는데, 나이는 '조건부 렌더링'을 통해 나이에 value가 있을 때에만 보여지도록 했다.
// PropsTypes.tsx
interface Props {
name: string;
age?: number; // age props를 옵셔널하게 받고 있음
}
/* 방법 2. */
export default function PropsTypes(props: Props) {
const { name, age } = props;
return (
<>
<h2>props로 전달받은 데이터의 타입 interface로 지정하기</h2>
<div>name: {name}</div>
<div>age: {age}</div>
</>
);
}
- 인터페이스를 만들어서 props의 데이터 타입 명시하는건 앞선 코드와 같다.
- 함수형 컴포넌트 선언문에서 const 키워드로 props를 구조분해할당하여 변수에 담고, 해당 변수를 컴포넌트의 인자로 넣었다. 인자가 인터페이스로 만든 데이터 타입이라고 명시하는 과정은 앞선 코드와 같다.
- 여기의 return에서는 props를 바로 구조분해하여 사용하고 있다.
// PropsTypes.tsx
interface Props {
name: string;
age?: number; // age props를 옵셔널하게 받고 있음
}
/* 방법 3 */
export default function PropsTypes(props: Props) {
const name = props.name;
const age = props.age;
return (
<>
<h2>props로 전달받은 데이터의 타입 interface로 지정하기</h2>
<div>name: {name}</div>
<div>age: {age}</div>
</>
);
}
- 인터페이스를 만들어서 props의 데이터 타입 명시하는건 첫번째 코드와 같다.
- 여기서는 props를 점 접근법으로 접근해서 변수에 담고, return에서 구조분해하여 사용하고 있다.
// app.tsx
import PropsTypes from './components/PropsTypes';
function App() {
return (
<div className="App">
<PropsTypes name="layla" age={20} />
<PropsTypes name="layla" />
</div>
);
}
export default App;
- 앞선 3개의 코드 모두 app.tsx에서 불러와서 컴포넌트의 props로 입력해주면 브라우저에서 정상 출력된다.
useState generic
기존 JS React에서의 useState 기능과 비슷한데, TS이기 때문에 데이터 타입을 지정한다는 차이만 있다.
- 초기값에 대한 데이터 타입을 generic으로 설정하고, state의 변경도 generic으로 정해진 타입으로만 변경할 수 있다.
- state의 값이 null일 수도 있는 경우에는 반드시 generic으로 유니온 타입을 전달한다.
// useState.tsx
import { useState } from 'react';
export default function UseState() {
// count state 만들기 - 기본값 0
const [count, setCount] = useState<number>(0);
return (
<>
<h2>ts react에서 useState 사용하기</h2>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</>
);
}
useRef generic
- 초기값에 대한 데이터 타입을 generic으로 작성한다.
- DOM 객체에 접근하기 위한 useRef의 generic은 <HTMLElement>라고 명시한다.
// useRef.tsx
import { useRef } from 'react';
export default function UseRef() {
// 1. DOM 요소에 접근할 때
// input 요소의 경우
const inputRef = useRef<HTMLInputElement>(null); // null 전달 필수!
// 2. 리렌더링 되어도 유지되는 로컬 변수 필요할때
const localVal = useRef<string>('리렌더링해도 유지됨');
return (
<>
<h2>ts react에서 useRef 타입 지정하기</h2>
<input type="text" ref={inputRef} />
{/* inputRef.current가 없을 수 있기 때문에 ? 작성 */}
<button onClick={() => inputRef.current?.focus()}>
input에 focus 시키기
</button>
<hr />
<div>localVal: {localVal.current}</div>
</>
);
}
이벤트 객체의 타입
onClick, onKeyDown, onChange 등 이벤트를 HTML 요소에 적용할 때, 이벤트 함수를 만들어서 사용한다면 해당 이벤트의 타입을 반드시 지정해야 한다.
export default function EventObj() {
// 현재 이벤트는 클릭 이벤트 -> React.MouseEvent
// 이벤트가 발생하는 요소는 button이라는 html 요소 -> <HTMLElement>
const buttonClick = (event: React.MouseEvent<HTMLElement>) => {
alert(event.target);
};
const handleKeydown = (e: React.KeyboardEvent<HTMLInputElement>) => {
console.log(e.code);
console.log(e.key);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
return (
<>
<h2>ts react에서 e 객체 타입</h2>
<button onClick={() => alert('클릭!')}>event 객체 사용 x</button>
<button onClick={(event) => alert(event.target)}>
바로 event 객체 사용
</button>
<button onClick={buttonClick}>함수 만들어서 event 객체 사용</button>
<hr />
<div>키보드 이벤트</div>
<input type="text" onKeyDown={handleKeydown} />
<hr />
<div>변경 감지 이벤트</div>
<input type="text" onChange={handleChange} />
</>
);
}
여기까지가 TS와 관련된 내용이었다.
이제 3차 프로젝트가 본격적으로 시작이 될텐데, 지난 두 번의 프로젝트를 준비할 때와 마찬가지로 잘 해낼 수 있을지 걱정이 된다. 지난 프로젝트들도 만만하지는 않았지만, 그래도 이번에 진행될 React 응용 프로젝트보다는 쉬운 편이었던 것 같다. 이번에는 작업 기간도 길고 인원도 많아졌고, 좋은 팀원들을 만났으니 함께 열심히 하다보면 좋은 결과가 있을거라고 믿는다.

'💻 Frontend > JS, TS' 카테고리의 다른 글
TypeScript 개발환경 구성의 순서, 화살표함수 표현식의 차이 (1) | 2024.07.19 |
---|---|
JS - 반복문 "Do ~ While~" (0) | 2024.05.19 |
[코딩온] 프론트엔드 입문 Day 46~47 (TS 입문) (0) | 2024.04.25 |
[코딩온] 프론트엔드 입문 Day 38 (JS 전개구문, 구조분해할당, 클래스) (0) | 2024.04.02 |
[코딩온] 프론트엔드 입문 Day 29 (jQuery) (0) | 2024.03.11 |