JS는 동적 언어(dynamic language)이기 때문에 타입 지정을 하지 않아 안정성이 떨어진다.
TS는 타입 시스템을 제공하여 버그를 감소시키고 런타임 에러를 예방하며 생산성을 증가시킬 수 있다.
JS / TS 안정성 예시
1.console.log([1,2,3,4] + false) // 결과: 1,2,3,4false
2. function divide(a,b){
console.log(a/b);
}
divide('xxxx') // NaN
// JS는 인자가 하나만 들어가도 실행을 막지 않음.(안정성 감소)
// TS는 런타임 전에 미리 에러를 띄워서 실행을 막음(안정성 증가)
// 런타임 에러
const nico = { name : "nico"}
nico.hello() // Uncaugt TypeError : ~~ is not a function
// JS는 nico.hello()가 에러가 날 것을 예상못해서 그냥 실행시켰고 런타임 에러가 발생함.
// TS는 실행하기 전에 알려줌.
그렇다면 결굴 JS파일로 변환되는데 어떻게 에러를 줄여서 개발자를 보호한다는 거지?
const nico = {
nickname: "nick",
}
nico.hello();
// Error : 'hello' 프로퍼티는 nico 객체에 없다.
----------
[1,2,3,4] + false;
// Error : 연산자 + 는 배열과 불린 타입에는 적용불가
const nico = {
nickname: "nick",
}
nico.hello();
// Error : 'hello' 프로퍼티는 nico 객체에 없다.
[1,2,3,4] + false;
// Error : 연산자 + 는 배열과 불린 타입에는 적용불가
TS는 개발자가 타입 지정을 하지 않아도 타입을 추론하여 타입 지정을 해준다.(JS는 위 코드처럼 짜도 에러 안띄움)
1. 타입을 추론하는 방법 Implicit(함축적인)
let a = "hello";
a= 'bye';
a = 1;
//Error in code : Type 'number' is not assignable to type 'string'.
JS에서는 위처럼 에러를 잡아내지 못한다.
2. 타입을 명시적으로 정의 Explicit(명시적인)
let b : boolean = "x"
//Type 'string' is not assignable to type 'boolean'.
let c = [1,2,3];
c.push("1");
//Argument of type 'string' is not assignable to parameter of type 'number'.
=> let c : number[] = []
명시적 표현은 TS가 타입추론을 하지 못할 때만 최소한으로 사용하는 것을 권장한다.
let c = [] => any 아무거나 집어 넣을 수 있음.
let a : number = 1;
let b : string[] = "i1" // Error
let c :boolean[] = [true]
-----
const player: {
name: string;
age: number;
} = {
name: 'nico',
};
/*Erro : Property 'age' is missing in type '{ name: string; }'
but required in type '{ name: string; age: number; }'.*/
age?: number; 타입에 옵셔널 연산자를 추가해주면
age 프로퍼티가 있을 때 타입 체크를 하게 됨.
-------
if(player.age < 10){}
// player.age is possibly 'undefined'
-> if(player.age && player.age < 10) {}
// 에러 해결
// Union 타입 - 타입 두개를 합친다.
let 회원: number | string |boolean= 123; // 타입 넘버
회원 = 'string'; // 타입 스트링
회원 = false;
let 회원들: (number | string)[] = [1, '2', 3];
let 오브젝트: {
a: number | string;
} = { a: '123' };
오브젝트.a = 1;
퀴즈
- 1번
let user = 'kim'; let age = undefined; let married = false; let 철수 = [user, age, married];
- 2번
let 학교 = { score : [100, 97, 84], teacher : 'Phil', friend : 'John' } 학교.score[4] = false; 학교.friend = ['Lee' , 학교.teacher]
답
let user: string = 'kim'; let age: undefined | number = undefined; let married: boolean = false; let 철수: (string | number | undefined | boolean)[] = [user, age, married]; let 학교: { score: (number | false)[]; teacher: string; friend: string | string[]; } = { score: [100, 97, 84], teacher: 'Phil', friend: 'John', }; 학교.score[4] = false; 학교.friend = ['Lee', 학교.teacher];
// 기존 방식
const playerNico : {
name : string,
age ?: number, // age : number | undefined 와 같음
} = {
name : "nico"
}
// Alias 방식
type Player = {
name : string,
age ?: number,
// "?"(optional type)의 경우 객체에 age 프로퍼티가 없을 때 에러 띄우는 것을 해결해주고
// 해당 프로퍼티가 있을 때만 type 체크를 해준다.
}
const Lynn : Player ={
name: 'lynn',
age : 10
}
-------
type Animal = string | number | undefined;
let 동물: Animal;
--------
type Age = number;
type Name = string;
type Player = {
name : Name,
age ?: Age,
}
--------
// 이런식으로도 가능하지만 너무 과하게 사용하는거라
// 코드가 복잡해보이니 지양하도록.
type Girlfriend = {
readonly name: string;
};
const 여친: Girlfriend = {
name: '엠버',
};
여친.name = '뽀로'; // 에러
type Name = string;
type Age = number;
type Person = Name | Age;
type PositionX = { x: number };
type PositionY = { y: number };
// { x: number, y: number } 이런 타입 만들고 싶다.
type NewType = PositionX & PositionY;
let position: NewType = { x: 10, y: 10 };
type Player = {
name: string,
age?:number
}
function 함수이름(매개변수:타입지정):리턴타입지정{
return 리턴타입인 값;
}
function playerMaker1(name:string):Player{
return {
name
}
}
const nico = playerMaker1("nico")
nico.age = 12
//{ name: 'nico', age: 12 }
function 함수(x?:number):void{ // 옵셔널 타입 사용하면 된다.
}
x?:number === x: number|undefined
function 함수(x: number | string) {
console.log(x + 3);
// + 연산자는 넘버나 스트링 타입만 허용, 유니온은 허용 X
}
function 내함수(x? :number) :number {
return x * 2
}
위와 같은 문제를 해결하는 것이 narrowing이라는 문법임.
function 함수(x:number|string):void{ if(typeof x === 'number'){ console.log(x + 3); }//내가 미리 조건으로 검사해서 타입을 확실히 해주면 에러 안남. }
😽❓ 응용해보기
Q) 이름 들어올 때만 이름을 불러주고 없으면 없다고 콘솔 function 함수(name?: string): void { if (name) { console.log(name); } else { console.log('이름이 없수'); } } Q) 숫자가 넘버든 스트링이든 들어오면 길이를 반환해라 function 함수2(value: number | string): number { return value.toString().length; } Q) 월소득은 만원당 1점, 집보유는 500점, 매력점수는 "상"일 때만 100점 총 600점 이상이면 통과 아니면 아무것도 반환 X function 함수3(월소득: number, 집보유: boolean, 매력점수: string): string { return 월소득 + (집보유 && 500) + (매력점수 === '상' && 100) >= 600 ? '결혼가능' : undefined; }
const fonc = (name:string) : Player =>{
return {
name:name,
}
}
const naco = fonc(1) // error
type Player = {
readonly name:string,
age?:number
}
const playerMaker = (name: string): Player => ({name})
const nico = playerMaker("nico")
🚫 nico.name = "aa"
//Cannot assign to 'name' because it is a read-only property.
--------
const numbers: readonly number[] = [1, 2, 3, 4]
🚫 numbers.push(1)
const playe = [string, number, boolean]
= ["Cho", 12, false];
player[0] = 1 // Error
const player: readonly [string, number, boolean] = ["nico", 12, false];
const a: any[] = [1,2,3]
const b : any = true
a + b // 에러 발생 X
let 이름: unknown = 'kim';
이름 = 123;
이름 = undefined;
이름 = [];
-------
let 이름: unknown;
let 변수1: string = 이름;
let 변수2: boolean = 이름;
let 변수3: number = 이름;
// 다른 타입 변수에 할당하면 error 남.
하지만 any 타입은 할당하는 변수의 TS 보호장치도 꺼버림
타입 스크립트는 엄격!
간단한 수학연산도 타입이 맞아야 하며, unknown은 number 타입이 아님.
let 이름: unknown;
이름 -1 // error
❓ 아래의 경우 왜 안되나
let 나이: string | number;
나이 + 1;
// + 연산자는 넘버나 스트링만 허용하는 것이기 때문
// 위와 같은 유니온 타입은 새로운 타입을 하나 만든 것이라서
// TS는 허용하지 않음.
let 나이: string;
나이 + 1; // 가능. -는 불가.
let a: unknown;
//예시1)
let b = a + 1; //에러
if (typeof a === 'number') {
let b = a + 1; //해결
}
//예시2)
let b = a.toUpperCase();//에러
//해결
if (typeof a === 'string') {
a.toUpperCase();
}
function add(x:number):void{
1 + 1;
return; // 리턴 쓰면 에러 뜸.
}
-----------
function hello(){
console.log('x')
} // hello 에 마우스 갖다대보면 hello() : void 라고 뜬다.
hello().toUpperCase() // return 값이 없으니 작동할리 없지.
function 함수():never{}
function 함수():never{
while(true){
console.log(123)
}
}
function 함수():never{
throw new Error('에러메시지')
}
강제로 에러 띄우면 전체 코드 실행이 중단되니
2번 조건을 나름 충족하는 것이라 사용가능.
조건 1,2를 충족하는 함수를 만들고 싶을 때 never타입을 지정함
근데 거의 쓸 일이 없다.
걍 return 안하고 싶으면 void 쓰면 됨
하지만 가끔 코드를 이상하게 짜면 자동으로 등장하기에
이럴 때 never가 무엇을 의미하는지 이해만 하면 됨.
❓ 언제 등장함?
function 함수(parameter: string) { if ( typeof parameter === "string"){ parameter + 1; } else { parameter; } }
- narrowing을 이용하여 파라미터의 타입이 string이면 무언가 해달라고 해놨음.
- else를 써놨는데 string말고는 매개변수로 못들어 오는데 ...?
- 이렇게 narrowing이 잘못 사용되면 파라미터 타입이 never로 변함.
- never뜨면 코드 수정을 하면 됨
function 함수(){ throw new Error() } // 함수 선언문 - let 함수2 = function (){ throw new Error() } // 함수 표현식
- 둘 다 아무것도 return 하고 있지 않음
- 이 때 자동으로 return 타입이 선언문은 void, 표현식은 never가 지정됨.
🧨🙀 또한 tscoinfig.json 에서 strict 옵션을 켜두면
함부로 any 타입을 지정하지 못하게 한다.
이 때 array 등을 타입지정하지 않고 생성하면
원래는 any[] 이런식으로 되는데 any를 금지시켜놔서
never[] 로 지정이 되버림.
let 이름 :string = 'kim';
let 나이 :number = 20;
let 결혼했니 :boolean = false;
let 회원들 :string[] = ["kim", "park"];
let 내정보 : { age : number } = { age : 20 }
let 이름 :string = 'kim';
이름 = 30;
//Type 'number' is not assignable to type 'string'.(2322)
😽 이런 에러는 TS 파일에서만 발생하고 변환된 js파일에서는 이상 없이 작동함.
let 이름 = 'kim';
let 나이 = 20;
이렇게 타입을 수동으로 지정하지 않아도 TS가 알아서 타입을 추론해서 지정해준다.
간단한 변수 타입들은 생략해도 됨.
추가 예시
var 좋아하는거 :{ song :string, singer :string }
= { song : '사랑하기때문에', singer : '유재하' }
let project :{
member : string[],
days : number,
started : boolean,
} = {
member : ['kim', 'park'],
days : 30,
started : true,
}
function 내함수(x: number | string) {
if (typeof x === 'number') return x + 1;
else return x;
}
function 내함수(x: number | string) {
let array: number[] = [];
if (typeof x === 'number') array[0] = x;
}
- Q) 적용 예시 배열 내 스트링을 숫자로 바꾸기.
function convert(array: (number | string)[]): number[] { return array.map((v: number | string) => +v); } console.log(convert([1, '2', 3]));
- Q2) 한 과목이면 그거 여러 과목이면 마지막 과목 없으면 없다
let 철수쌤 = { subject: 'math' }; let 영희쌤 = { subject: ['science', 'english'] }; let 민수쌤 = { subject: ['science', 'art', 'korean'] }; let 사짜 = { hello: 'hi' }; function lecture(x: { subject: string | string[] }): string { if (typeof x.subject === 'string') { return x.subject; } else if (Array.isArray(x.subject)) { return x.subject[x.subject.length - 1]; } else { return '없다'; } }
console.log(lecture(사짜));
#### 대체 방법 Assertion
- narrowing 할 때 사용
- 얘도 any랑 비슷하게 TS의 보호장치를 꺼버림.
```js
function 내함수(x: number | string) {
let array: number[] = [];
array[0] = x as number; // 최신문법
// 옛날 문법
<string>x + 1;
}
언제 쓰면 좋을까?
하지만 대부분 Narrowing으로 해결 가능하다.
추가) 이럴 때도 유용하다
type Person = {
name : string
}
function 변환기<T>(data: string): T {
return JSON.parse(data) as T;
}
const jake = 변환기<Person>('{"name":"kim"}');
함수의 인자 타입과 리턴하는 타입을 지정해두는 것을 ‘Call Signature’라고 한다.
type Add = (a:number, b:number) => number //Call signature
const add:Add = (a, b) => a + b
아래처럼 블록을 씌우고 return을 안 넣어준다면?
const add:Add = (a, b) => {a + b}
// Type 'void' is not assignable to type 'number'.
type Add는 number를 return하는 타입인데 아무것도 return 하지않는 ‘void’타입 꼴이 되어서
위와 같은 Error가 발생함. a+b
앞에 return 넣어주면 error 해결 됨.
type Add = (a:number, b:number) => number
// 이런 Call Signature을 길게 쓰는 방법은 아래 코드와 같다.
type Add = {
// 인자타입 : 리턴타입
(a:number, b:number) : number
}
// 이 방식은 Overloading을 위한 것이다.
type Add = {
(a:number, b:number) : number
(a:number, b:string) : number
}
// 매개변수 a는 number b는 number | string
// 이 때는 아래처럼 narrowing 문법 사용하면 됨.
const add:Add = (a,b) => {
if(typeof b === "string") return a
return a+b;
}
```
type Config = {
path:string
state: object
}
type Push = {
(path:string):void
(obj:Config):void
}
const push:Push = (config) =>{
if(typeof config === "string") console.log(config) // string
else{
console.log(config.path, config.state) // string, object
}
}
//이 경우 c는 옵션임을 나타내는 오버로딩이다.
type Add = {
(a:number, b:number): number
(a:number, b:number, c:number): number
}
const add:Add = (a,b,c) =>{
return a + b
}
----------------------------
//옵션이니 ? 옵셔널을 붙여줘서 c 파라미터가 들어오면 number임을 지정해줘야 함.
const add:Add = (a,b,c?:number) =>{
if(c) return a + b + c;
return a + b
}
add(1,2) //3
add(1,2,3) // 6
```tsx
type SuperPrint = {
(arr:number[]):void
(arr:boolean[]):void
}
const superPrint: SuperPrint = (arr) =>{
arr.forEach(i=>console.log(i))
}
superPrint([1,2,3,4])
superPrint([true, false, true])
superPrint(["a","b","c"])
// <= 스트링 배열이 들어가야 한다면 (arr:string[]):void를 추가해야하는 것이 아니다.
```
---
```tsx
// concreate type - number, string, boolean, unknown...
// generic type - 타입의 placeholder 같은 것.
generic으로 placeholder와 같은 것을 추가해서 그게 무엇인지 추론해서 함수를 사용한다.
type SuperPrint = {
(arr:number[]):void
(arr:boolean[]):void
(arr:string[]):void // 콘크리트 타입.
(arr:(number|boolean)[]):void
}
const superPrint: SuperPrint = (arr) =>{
arr.forEach(i=>console.log(i))
}
superPrint([1,2,3,4])
superPrint([true, false, true])
superPrint([1,2,true,'hello'])
// generic type은 콜 시그니쳐를 작성할 때 시그니쳐에 들어올 확실한 타입을 모를 때 사용한다.
type SuperPrint = {
<Generic>(arr: Generic[]):void
// 라이브러리에서 T, V를 많이 씀 아무거나 넣어도 ㄱㅊ
}
superPrint([1,2,true,'hello']) 함수에 커서 올려보면 TS가 타입 추론한 것 볼 수 있음.
//const superPrint: <string | number | boolean>(arr: (string | number | boolean)[]) => void
// 일일이 콜 시그니쳐를 추가하던 것을 TS가 대신 유추해준다는 것이다.
```
---
```tsx
// Q) superPrint의 리턴 타입을 바꾸고 싶다면?
type SuperPrint = {
// (arr: number[]):number // X
<TypePlaceholder>(arr: TypePlaceholder[]):TypePlaceholder // O
}
const superPrint: SuperPrint = (arr) => arr[0]
const a = superPrint([1,2,3,4]) //const a: number
const b =superPrint([true, false, true]) //const b: boolean
const c = superPrint([1,2,true,'hello']) //const c: string | number | boolean
```
👍🌼 `type SuperPrint = <t>(a: T[]) => T` 이렇게 쓰면 간단!!
type SuperPrint = (a: any[]) => any
const superPrint: SuperPrint = (arr) => arr[0]
const a = superPrint([1,2,3,4])
const b =superPrint([true, false, true])
const c = superPrint([1,2,true,'hello'])
에러가 뜨진 않는다 하지만!!c.toUpperCase() // Cannot read properties of undefined (reading 'toUpperCase')
c는 arr[0] === 1 number가 할당되어 있기 때문에 **"코드 실행** 시" Error 발생.
generic을 적용하면 TS가 알아서 타입 유추를 해주기 때문에 코드가 실행되기 전에 보호장치가 발동!Property 'toUpperCase' does not exist on type 'number'.
에러 띄운다.type SuperPrint = <T, M>(a: T[], b:M) => T
const superPrint: SuperPrint = (arr) => arr[0]
const a = superPrint([1,2,3,4], "x") // T는 숫자배열, M은 스타링임을 유추함.
const b =superPrint([true, false, true]) //error 인자 하나가 부족함.
const c = superPrint([1,2,true,'hello']) //error
function superPrint<T>(a: T[]){
return a[0]
}
const a = superPrint([1,2,3,4])
-------- overwrite도 됨
const a = superPrint<boolean>([1,2,3,4]) // error : boolean타입이라고 덮어씀
--------- 보호 못 받는 any 쓰지 말고
type Player<E> = {
name:string
extraInfo:E
}
type Player<E> = {
name:string
extraInfo:E
}
const lynn:Player<null> = {
name: "lynn",
extraInfo:null
}
// generic을 사용하면 타입 재사용이 가능해진다
type NicoExtra = {
favFood : string
}
type NicoPlayer = Player<NicoExtra>
const nico:NicoPlayer = {
name:"nico",
extraInfo:{
favFood:"kimchi"
}
}
type A = Array<number> // 기본 타입 Array에 number 덮어쓰기.
let a:A = [1,2,3,4]
function printAllNums(arr: Array<number>){}
useState<number>() // 제네릭을 보내면 number 타입의 useState가 되는 것.