
함수 매개변수의 타입은 명시적으로 정의합니다.특별한 상황을 제외하면 매개변수 타입은 추론하지 않는다고 합니다.함수 생성자를 통한 함수 생성을 제외한 나머지 모든 문법을 안전하게 지원합니다.함수 생성자를 통한 함수 생성의 경우 아래의 예시처럼
매개변수 타입을 지정하지 않았음으로 어떤 인수를 건네서도 호출이 가능
모든 매개변수 타입의 필수 어노테이션, 반환 타입의 선택형 어노테이션에 적용하는 규칙을 가진다.function add(a:number, b:number){
return a+b
}
// 생성자 함수
let test = new Function('hello', 'bye', 'Good Morning');
console.log(test('chiman'));
console.log(test(true));
console.log(test(5));
// 일반 함수
let test2 = function (name: string) {
return 'hi' + name;
};
console.log(test2('chiman'));
console.log(test2(5)); // 에러발생
함수 선언할때 사용하며, 정형 매개변수라고 함함수를 호출할 때 사용하며 실질 매개변수 라고 함function add(a:number, b:number){ // 여기서 a,b를 매개변수
return a+b
}
add(3,5) // 여기서 3,5를 인수
선택적 매개변수
? 기호를 사용 function greet(name: string, greeting?: string) {
if (greeting) {
console.log(`${greeting}, ${name}!`);
} else {
console.log(`Hello, ${name}!`);
}
}
기본 매개변수
= 기호를 사용 매개변수의 기본값을 지정해주는 것 function add(a: number, b: number, c = 0, d = 5) {
return a + b + c + d;
}
// console.log(add(1)); // 에러발생 : 2개의 인수가 필요한데 1개를 가져왔습니다.
// console.log(add(1, 2)); // 8
// console.log(add(1, 2, 3)); // 11
// console.log(add(1, 2, 3, 4)); // 10
인수가 고정된 경우가 아닌 인수의 개수가 달라지는 경우 처리를 해야 할 때가 있다.
이 경우 arguments를 사용할 수 있지만 문제점이 있다.
배열이 아니다. arguments객체는 배열이 아닌 유사 배열 객체 이므로 배열 메서드를 사용할 수 없다.
안전하지 않다.
// arguments 사용시
function sumVariadic() {
const arr = Array.from(arguments);
return arr.reduce((total, n) => total + n, 0); // n,total 모두 any로 추론
}
console.log(sumVariadic(1, 2, 3)); // 에러발생 : 0개의 인수가 필요한데 3개를 가져왔습니다.
나머지 매개변수(Rest 파라미터)를 사용하여 해결한다.function sumVariadic(...arr: number[]) {
return arr.reduce((total, n) => total + n, 0);
}
console.log(sumVariadic(1, 2, 3)); // 6
console.log(sumVariadic(1, 2, 3, 4)); // 10
console.log(sumVariadic(1, 2, 3, 4, 5)); // 15
공통점은 첫번째 인수는 this의 값을, 두번째 인수는 호출할 함수의 매개변수들을 전달하는 것차이점은 call의 경우 여러개의 인수들로 받지만, apply는 배열로 받는다는 차이가 있다.function test(add1: string, add2: string) {
console.log(this, 'add1+add2 => ', add1, add2);
}
const obj1 = {
value: 'value1',
};
const obj2 = {
value: 'value2',
};
test('Good','Morning');
test.call(obj1, 'Good', 'Morning'); // 여러개의 단어들로 호출
test.apply(obj2, ['Good', 'Morning']); // 여러개의 단어들을 배열에 담아서 호출
bind는 call, bind와 달리 함수를 호출 하는 것이 아닌 첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 새로운 함수를 반환한다.function test(add1: string, add2: string) {
console.log(this, 'add1+add2 => ', add1, add2);
}
const obj1 = {
value: 'value1',
};
const obj2 = {
value: 'value2',
};
const obj3 = {
value: 'value3',
};
test.call(obj1, 'Good', 'Morning'); // test함수 호출, this는 {value:'value1'}
test.apply(obj2, ['Good', 'Morning']); // test함수 호출, , this는 {value:'value2'}
test('1', '2'); // test함수 호출, 그러나 this는 전역객체
const bi = test.bind(obj3, 'Good', 'Morning'); // 함수 호출이 아닌 함수를 생성하기 때문에 아래와 같이 함수를 호출해야함
bi(); // this는 {value:'value3'}
어떻게 호출했는지에 따라 값이 달라진다.let obj = {
test() {
return this;
},
};
console.log(obj.test()); // this는 obj객체
const newObj = obj.test;
console.log(newObj()); // 전역객체
----
function handleDate(this:Date) {
return this.getDate();
}
handleDate.call(new Date());
handleDate(); // 에러발생
function* createFibonacciGenerator() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let maker = createFibonacciGenerator();
console.log(maker);
{
next: [Function (anonymous)],
throw: [Function (anonymous)],
return: [Function (anonymous)],
[Symbol(Symbol.iterator)]: [Function (anonymous)]
}
console.log(maker.next()); // { value: 0, done: false }
console.log(maker.next()); // { value: 1, done: false }
console.log(maker.next()); // { value: 1, done: false }
const iterable = [1, 2, 3]; // Symbol.iterator 프로퍼티를 가진 객체
const iterator = iterable[Symbol.iterator](); // 이터러블을 Symbol.iterator를 키로 호출한 객체
console.log(iterator); // Object [Array Iterator] {next:...} // next를 가짐
for (let i = 0; i <= iterable.length; i++) {
const iteratorResultObj = iterator.next();
console.log(iteratorResultObj); // iteratorResultObj :next()호출하여 얻는 객체
}
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }
값이 아닌 타입 정보만 포함한다.기본값을 표현할 수 없다.type Log = (message: string, userId?: string) => void;
const log:Log = (message, userId = 'chiman') => {
let time = new Date().toISOString();
console.log(time, message, userId);
};
// message의 타입을 따로 지정하지 않아도 추론한다.
type Log = (message: string, userId?: string) => void;
const log:Log = (message, userId = 'chiman') => {
let time = new Date().toISOString();
console.log(time, message, userId);
};
-------
function time(fn: (index: number) => void, n: number) {
for (let i = 0; i < n; i++) {
fn(i);
}
}
time((n) => console.log(n), 5); // time를 호출할 때 함수 선언을 인라인으로 제공하면 인수로 전달하는 함수의 타입을 명시할 필요가 없다.
// 인라인이 아닌 경우 타입을 추론할 수 없다.
function f2(n) {
console.log(n);
}
const f3 =(n) =>console.log(n);
time(f2, 5); // n은 any
time(f3, 5); // n은 any
type Log = (message:string, userId?:string) => void함수 오버로딩이란
함수의 이름은 같지만, 매개변수 개수, 매개변수 타입이 다른 경우
type Reservation = {
// 예약 정보...
};
type Reserve = {
// 왕복의 경우
(from: Date, to: Date, destination: string): Reservation;
(from: Date, destination: string): Reservation;
// 편도의 경우
};
let reserve: Reserve = (from: Date, to: Date, destination?: string) => { // 에러발생
return {
// 예약정보
};
};
const roundTripReservation = reserve(new Date(), new Date(), 'New York');
const oneWayReservation = reserve(new Date(), 'Paris');
호출자 관점에서는 함수의 타입은 오버로드 시그니처들의 유니온이 된다.함수를 구현하는 관점에서는 단일한 구현으로 조합된 타입을 나타낼 수 있어야 한다.type Reservation = {
// 예약 정보...
};
type Reserve = {
(from: Date, to: Date, destination: string): Reservation;
(from: Date, destination: string): Reservation;
};
// reserve를 구현할 때 2번째 매개변수로 2가지가 올 수 있기 때문에 Date | string으로 처리를 해준다.
let reserve: Reserve = (from: Date, toOrDestination: Date | string, destination?: string) => {
console.log(from);
// 또한 toOrDestination의 타입이 Date, string에 따라 처리를 다르게 하기 때문에 정제해서 사용한다.
if (toOrDestination instanceof Date && destination !== undefined) {
console.log(toOrDestination.getDate());
}
return {
// 예약정보
};
};
const roundTripReservation = reserve(new Date(), new Date(), 'New York');
const oneWayReservation = reserve(new Date(), 'Paris');
다형성이란 : 하나의 타입에 여러 객체를 대입할 수 있는 성질입니다.
기대하는 타입을 정확하게 알고 있다면 구체타입이 유용하다.
-> 하지만 어떤 타입을 사용할지 미리 알 수 없는 상황에서는 구체타입을 사용하기 어렵다.
그럼 호출 시그니처를 여러개 하면 되지 않나?
-> 아래의 경우 처럼 여러개를 사용하기 위해 시그니처를 여러개 지정할 수도 있지만 가독성이 좋지도 않고, 객체의 경우 문제가 발생한다.
type Filter = {
(array:number[], f:(item):number=>boolean):number[]
(array:string[], f:(item):string=>boolean):string[]
(array:[], f:(item):string=>boolean):string[]
(array:object[], f:(item):object=>boolean):object[] // 에러발생 : object타입은 객체의 실제 형태에 대해서는 알려주지 않는다.
...
}
제네릭이다.지금은 타입을 알 수 없으니 누군가 filter를 호출할 때마다 타입스크립트가 타입을 추론해주기 바란다. 라는 뜻언제 제네릭 타입이 한정되는가? 에서 확인type Filter = {
<T>(array: T[], f: (item: T) => boolean): T[];
};
let filter: Filter = (arr, f) => {
let result = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (f(item)) {
result.push(item);
}
}
return result;
};
let names = [{ lastName: 'KIM' }, { lastName: 'LEE' }, { lastName: 'PARK' }];
filter([1, 2, 3], (item) => item > 2); // T는 number로 한정
filter(['a', 'b', 'c'], (item) => item !== 'b'); // T는 string으로 한정
filter(names, (item) => item.lastName.startsWith('K')); // T는 {lastName:string}으로 한정
<T>(array: T[], f: (item: T) => boolean): T[];
// 위의 코드를 아래와 같이 대치한다. 즉 T를 number로 대치
---> <number>(array: number[], f: (item: number) => boolean): number[];
위에서 제네릭 타입의 선언 위치에 따라 타입의 범위뿐 아니라 타입스크립트가 제네릭 타입을 언제 구체 타입으로 한정하는지도 결정된다.
보통 제네릭 타입을 사용하는 순간에 제네릭과 구체 타입을 한정한다.
이때 사용하는 순간 이란
크게 3가지가 있지만 구체적으로는 아래의 5가지로 나눌 수 있습니다.
// 1. T를 한 시그니처 범위로 한정했으므로 타입스크립트는 filter의 함수를 호출할 때 이 시그니처의 T를 구체타입으로 한정
// 각각의 함수 앞에 제네릭 선언
// 호출 시그니처
type Filter = {
<T>(array: T[], f: (item: T) => boolean): T[];
};
// 선언 : 선언시에 따로 제네릭을 지정해줄 필요 X, 타입스크립트가 추론
let filter: Filter = (arr, f) => {
let result = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (f(item)) {
result.push(item);
}
}
return result;
};
// 호출(사용)
filter([1, 2, 3], (item) => item > 2); // T는 number로 한정
// 2. T를 filter타입의 일부로 선언했으므로 타입스크립트는 Filter 타입의 함수를 선언할 때 T를 한정
// type명 뒤에 제네릭 선언
// 호출 시그니처
type Filter2<T> = {
(array: T[], f: (item: T) => boolean): T[];
};
// 선언 : 선언시 제네릭에 원하는 타입 선언
let filter2: Filter2<number> = (arr, f) => {
let result = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (f(item)) {
result.push(item);
}
}
return result;
};
// 호출(사용)
filter2([1, 2, 3], (item) => item > 2);
// filter2(['1', '2', '3'], (item) => item > 2); // 에러발생 : string 형식은 number 형식에 할당할 수 없음
// 3. 1과 비슷하지만 전체호출이 아닌 단축 호출 시그니처로 가능
type Filter3 = <T>(array: T[], f: (item: T) => boolean) => T[];
// 4. 2와 비슷하지만 전체호출이 아닌 단축 호출 시그니처로 가능
type Filter4<T> = (array: T[], f: (item: T) => boolean) => T[];
// 5. 이름을 갖는 함수 호출 시그니처, filter를 호출할 때 T를 타입으로 한정하므로 각 filter 호출은 자신만의 T 한정값을 가짐
// 함수 선언식에선 호출 시그니처를 따로 지정할 필요없음, 선언시 제네릭을 선언
// 선언
function map<T, U>(array: T[], f: (item: T) => U): U[] {
let result = [];
for (let i = 0; i < array.length; i++) {
result[i] = f(array[i]);
}
return result;
}
// 호출(사용), 추론, 직접명시 둘 다 가능
map([1, 2, 3], (item) => item >= 2);
map<number, boolean>([1, 2, 3], (item) => item >= 2); // 되긴 됨
단 명시할때는 모든 제네릭 타입을 지정하거나, 아무것도 명시하지 않도록 해야한다.function map<T, U>(array: T[], f: (item: T) => U): U[] {
let result = [];
for (let i = 0; i < array.length; i++) {
result[i] = f(array[i]);
}
return result;
}
map([1, 2, 3], (item) => item >= 2); // 타입 지정 X
map<number, boolean>([1, 2, 3], (item) => item >= 2); // 타입 모두 지정
map<number>([1, 2, 3], (item) => item >= 2); // 에러 발생 : 2개의 형식 인수가 필요한데 1개를 가져왔습니다.
let promise = new Promise((resolve) => resolve(77));
promise.then(result=>result*4); // result는 unknown입니다.
// 타입을 명시해서 문제를 해결
let promise = new Promise<number>((resolve) => resolve(77));
promise.then(result=>result*4);
type MyEvent<T> = {
target: T;
type: string;
};
let test1: MyEvent<HTMLButtonElement | null> = {
target: document.querySelector('#myButton'),
type: 'click',
};
이런 상황을 U가 T의 상한 한계라고 한다function test<T extends string | number>(prop: T) {
console.log(prop);
}
test('hi');
test(5);
test(true); // 에러발생
test([1]); // 에러발생
test({ key: 'qwe' }); // 에러발생
type HassSides = { numberOfSizes: number };
type SidesHaveLength = { sideLength: number };
function logPerimter<Shape extends HassSides & SidesHaveLength>(s: Shape) {
console.log(s);
}
logPerimter({ numberOfSizes: 3 , sideLength: 5 });
logPerimter({ numberOfSizes: 3 }); // 에러 발생
logPerimter({ sideLength: 5 }); // 에러 발생
function call(f: (...argus: unknown[]) => unknown, ...args: unknown[]): unknown {
return f(...args);
}
function fill(length: number, val: string): string[] {
return Array.from({ length }, () => val);
}
call(fill, 10, 'a'); // unknown 형식은 number 형식에 담을 수 없다.
// extends를 통해 T는 unknown[]의 서브타입, 즉 어떤 타입의 배열 또는 튜플이다.
// arg는 T 타입이며, T는 배열타입이어야 하므로, T에 걸맞는 튜플 타입으로 추론함
// 함수 선언문의 경우 호출시에 타입이 결정됨
function call2<T extends unknown[], R>(f: (...argus: T) => R, ...args: T): R {
return f(...args);
}
call2(fill, 10, 'a');
call2(fill, 10);
call2(fill, 10, 11, 12);
type MyEvent<T = HTMLElement> = {
target: T;
type: string;
};
type MyEvent2<T extends HTMLElement =HTMLElement> = {
target: T;
type: string;
};
type MyEvent3<Type extends string, Target extends HTMLElement = HTMLElement> = {
target:Target,
type:Type
}