본문 바로가기
Programming/JavaScript

Ts study

/*
공식 문서: https://www.typescriptlang.org/tsconfig/

tsconfig.json 기본 내용

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "noImplicitAny": true,   // any type이 'any' 입력 없이 검출될 때 에러 출력(선택)
        "strictNullChecks": true  // null, undefineㅇ에 이상한 조작시 에러 출력(선택)
    }
}

이게 기본형태(IE 호환성 측면에서 무난함.)

target: js 버전 설정 (es5, es2016, esnext; 최신)
module: js 파일간 import 문법 설정 (commonjs: require,  ex2015 이후: import)



자주 사용하는 것만 정리


{
 "compilerOptions": {

  "target": "es5", // 'es3', 'es5', 'es2015', 'es2016', 'es2017','es2018', 'esnext' 가능
  "module": "commonjs", //무슨 import 문법 쓸건지 'commonjs', 'amd', 'es2015', 'esnext'
  "allowJs": true, // js 파일들 ts에서 import해서 쓸 수 있는지 
  "checkJs": true, // 일반 js 파일에서도 에러체크 여부 
  "jsx": "preserve", // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
  "declaration": true, //컴파일시 .d.ts 파일도 자동으로 함께생성 (현재쓰는 모든 타입이 정의된 파일)
  "outFile": "./", //모든 ts파일을 js파일 하나로 컴파일해줌 (module이 none, amd, system일 때만 가능)
  "outDir": "./", //js파일 아웃풋 경로바꾸기
  "rootDir": "./", //루트경로 바꾸기 (js 파일 아웃풋 경로에 영향줌)
  "removeComments": true, //컴파일시 주석제거 

  "strict": true, //strict 관련, noimplicit 어쩌구 관련 모드 전부 켜기
  "noImplicitAny": true, //any타입 금지 여부
  "strictNullChecks": true, //null, undefined 타입에 이상한 짓 할시 에러내기 
  "strictFunctionTypes": true, //함수파라미터 타입체크 강하게 
  "strictPropertyInitialization": true, //class constructor 작성시 타입체크 강하게
  "noImplicitThis": true, //this 키워드가 any 타입일 경우 에러내기
  "alwaysStrict": true, //자바스크립트 "use strict" 모드 켜기

  "noUnusedLocals": true, //쓰지않는 지역변수 있으면 에러내기
  "noUnusedParameters": true, //쓰지않는 파라미터 있으면 에러내기
  "noImplicitReturns": true, //함수에서 return 빼먹으면 에러내기 
  "noFallthroughCasesInSwitch": true, //switch문 이상하면 에러내기 
 }
}




*/



// 터미널: tsc -w   입력시 자동변환(갱신)모드 작동 -> 켜놓고 ts에 개발하면서, 실제 파일 사용시 js만 사용
let 이름: string = 'kim';
let 어레이: string[] = ['kim', 'park'];
let 오브젝트: { name? : string } = { name : 'kim'};     // ?는 'undefined'도 가능하다는 의미(optional 느낌)
let 다양함: string[] | number = 123;   //  | = uniton type(다양한 type 허용)

// 커스텀 type(type alias) 만들기: 복잡한 type (특히 object)에 쓰임
type Custom = string | number;  // custom type은 CamelCase
type FixedLastName = 'kim' | 'park';  // 이 경우 'kim', 'park'만 입력 가능 
let 특수함: Custom = 123;
// 이미 만든거 위에있는데 또 재할당 불가  type Custom = number; (X)

type Member = { [key: string] : string}  // key - object 둘다 string 으로 고정 (object 일괄 적용)

// custom type 합치기(= extend)도 가능
type Name = string;
type Age = number;
type NewOne = Name | Age;  

// object 재활용해서 합치기
type PositionX = { x: number };
type PositionY = { y: number };
type XandY = PositionX & PositionY // { x : number, y : number }
let 좌표 :XandY = { x : 1, y : 2 }  // { x : number, y : number }

// 합칠때 중복있으면 자동으로 제거되면서 합쳐짐. key가 같은데 type이 다른 경우 불가능을 표시하는 'never' type으로 아무것도 입력못받는 형태로 변경

// 타입문제 1
type Type1 = {color? : string, size : number, readonly position : number[]}
let 테스트용변수1 : Type1 = {size : 123, position : [1,2,3]}

// 타입문제 2
type Validation = {name: string, phone: number, email: string}

// 타입문제 3
type StrictValidation = Validation & {underage : boolean}




let 임시 = 123;  // 사실 type 지정 일일이 안해도 자동으로 커서올리면 씌워진거 확인 가능 -> 간단한 변수는 타입 지정 생략하자
// 에러메세지는 실행중인 tsc -w 의 터미널에 출력됨
let 좋아하는거 :{ song : string, singer : string } = { song : 'ㅇㅇ', singer : 'ㅇㅎ' }
let project :{
  member : string[],
  days : number,
  started : boolean,
} = {
  member : ['kim', 'park'],
  days : 30,
  started : true,
}
let 여러타입어레이: (string|number)[] = [1, '2', 3];
let 여러타입obj: {a : string | number} = { a : '123'};
let 아무거나: any;  // type 지정 해제 문법 -> 이것을 다른 곳에 할당하면 할당된 곳의 type 지정도 해제됨 -> type 지정 해제가 필요할때 사용하자
let 타입용아무거나: unknown   // -> 그래서 any대신 unknown을 쓰면 그 변수만 unknown이고 다른 곳에 영향주지 않으니 좀 더 안정적
// 실질적으로는 any나 unknown은 남이 만든 코드 리뷰할 때 안되면 임시로 사용하는 용도

// 숫자연산의 경우 number만 가능 -> unknown, any, 또는 union에 number를 포함하더라도 연산 안됨
// let 나이: string|number;
// 나이 + 1;
 
// let 나이: unknown = 1;
// 나이 + 1;



let 학교: {
    score: (number | boolean)[],
    teacher: string,
    friend: string | string[]
}
 = {
    score : [100, 97, 84],
    teacher : 'Phil',
    friend : 'John'
}
학교.score[4] = false;
학교.friend = ['Lee' , 학교.teacher]






// 함수
function 함수(x: number): number {   // number -> number
 return x * 2
}

// return이 없는 함수 타입지정
function 노리턴함수(x?: number): void { // JAVA, C#의 void 개념과 유사, 파라미터가 옵션인 경우에는 변수?: 타입형태로 입력
}
 // x? 의미 -> x: number | undefined  로 정의하는 것과 같음

// union이나 ? 쓴 경우 아래의 함수 다 에러 표기 (엄격한 number가 아니기 때문 -> narrowing; type 조건이 맞으면 연산하도록 조건 설계 필요)
// function 자릿수세기(x :number | string){ 
//   return x + 1 
// } 

// function 내함수(x? :number) :number { 
//   return x * 2 
// }  


//연습문제 1
function 안녕이름(name?: string): void {
    name
    ? console.log(`안녕하세요 ${name}`)
    : console.log("이름이 없습니다.");
    // console.log(name ? `안녕하세요 ${name}` : "이름이 없습니다.");   // 한 줄 코드
}
const 안녕이름2 = (name?: string) => console.log(name ? `안녕하세요 ${name}` : "이름이 없습니다.");  // 화살표 함수 사용
안녕이름2()

// 연습문제 2
const 자릿수세기 = (input_data: number | string): number => String(input_data).length
function 자릿수세기2(x :number | string) :number {
  return x.toString().length
} 
/**연습문제 3
 * 결혼 가능 확률을 알려주는 함수를 만들어봅시다.

1. 함수의 파라미터로 월소득(만원단위), 집보유여부(true/false), 매력점수 ('상' or '중' or '하') 를 입력할 수 있어야합니다. 
2. 월소득은 만원 당 1점, 집보유시 500점 & 미보유시 0점, 매력점수는 '상'일 때만 100점으로 계산합니다. 
3. 총 점수가 600점 이상일 경우 "결혼가능"을 return 해줘야합니다. 그 외엔 아무것도 return하지 않습니다.
 */

const 결혼가능하냐 = function(월소득: number, 집보유여부: boolean, 매력점수: '상' | '중' | '하'): string|null {
    let incomeScore = Math.floor(월소득 / 10000);
    let homeownershipStatus = 집보유여부? 500 : 0;
    let attractiveness = (매력점수 === '상')? 100 : 0;
    return (incomeScore + homeownershipStatus + attractiveness >= 600)? "결혼가능" : null;
};

const 결혼가능하냐1 = (월소득: number, 집보유여부: boolean, 매력점수: '상' | '중' | '하'): string | null => {
    const score = Math.floor(월소득 / 10000) + (집보유여부 ? 500 : 0) + (매력점수 === '상' ? 100 : 0);
    return score >= 600 ? "결혼가능" : null;
};

const 결혼가능하냐2 = (월소득: number, 집보유여부: boolean, 매력점수: '상' | '중' | '하'): string | null => 
    Math.floor(월소득 / 10000) + (집보유여부 ? 500 : 0) + (매력점수 === '상' ? 100 : 0) >= 600 ? "결혼가능" : null;




// Narrowing: 여러 타입이 있는 것에 대해서는 type을 하나로 확정시켜야 코드 동작
if (typeof 다양함 === 'number') {  // 이후 숫자 연산 가능
    다양함 + 1 };
// 속성명 in obj자료 , 인스턴스 instanceof 부모  와 같은 검사로 narrowing도 가능
// assertion: 길어지는 if 대신 짧게 type 확정(여러 개 중 하나로 확정 가능하지만 이미 확정된걸 변경하는 용도는 아님!)
다양함 as number + 1  // 단 as는 해당 타입으로 100% 들어온다는 전제하에 사용해야 함 (그렇지 않다면 if문이 적절)
// -> as는 남이 짠 코드 수정할 때, 왜 에러나는지 모르겠을 때 비상용으로 주로 씀


// 연습문제 4
function 클리닝함수(데이터: (number | string)[]): number[] {
    return 데이터.map((x: number | string): number => Number(x));
}
const 클리닝함수2 = (데이터: (number | string)[]): number[] => 데이터.map((x) => Number(x));




// 연습문제 5
const 담당과목 = (선생님: {'subject': string | string[]}): string => 
    (typeof 선생님.subject === 'string') ? 선생님.subject : 선생님.subject[선생님.subject.length-1]

const 담당과목2 = (선생님: { subject: string | string[] }): string => {
    const { subject } = 선생님;
    return Array.isArray(subject) ? subject[subject.length - 1] : subject;
};




// readonly: 특정 속성 수정 못하게 에디터상에서 에러 출력시키기  -> readonly '대상'
type Girlfriend = {
  readonly name : string,
}

let 여친 :Girlfriend = {
  name : '엠버'
}

// 여친.name = '유라' //readonly라서 에러남
// 컴파일, 에디터 에러일 뿐 js는 잘 동작함. 에디터상에서만 빨갛게 만듦




// Literal Types: 특정 값만 입력받는 type -> 변수에 뭐가 들어오는지 엄격한 관리 + 자동완성 힌트 명료함
let 접니다: '대머리' | '솔로' 
function 아무개함수(a : 'hello'): 0 | 1 { return 1} // a는 'hello'만 입력받고 return도 0과 1만 가능
function 가위바위보(뭐낼까: '가위' | '바위' | '보'): '가위'|'바위'|'보' {return '가위'};



// 이건 왜 그럴까? (Literal type의 문제점)
var 자료 = {name : 'kim'} as const
자료.name // 'kim'
function 이상해(a:'kim'){}
// 이상해(자료.name)  // 에러출력!   -> 함수는 'kim'이라는 type을 요구하는데 자료.name은 string type의 'kim'을 넣었기 때문
// 대응법: var 자료에 대한 type을 제대로 {name : string} 으로 하던가 자료.name as 'kim' 하던가 아니면 object 끝에 'as const' (= literal type 지정 알아서 하기)쓰던가

// as const의 효과: 포괄적 자동 type이 아닌 value 자체를 type으로 만듦 + object안의 모든 속성에 readonly로 적용


// 함수 형태를 type으로 만들고 사용하려면?
type NumOut = (x : number, y : number ) => number ;   // 함수 type 지정은 arrow function만 되고 변수명 지정 필수임
let 하암수 :NumOut = function(x,y){return x + y}   // 함수 type을 쓰려면 함수 표현식/화살표함수 형태로 작성해야 쓸 수 있음  <-> function 함수() 이건 '함수 선언식'


// 함수 type 연습문제1
let 회원정보: {
    name : string,
    age : number,
    plusOne: (x: number) => number,
    changeName: () => void
}
 = {
  name : 'kim',
  age : 30,
  plusOne (x){
    return x + 1
  },
  changeName : () => {
    console.log('안녕')
  }
}

// 함수 type 연습문제2
type fuc1 = (input_data: string) => string 
type fuc2 = (input_data: string) => number
const cutZero: fuc1 = function(input_data) {
    return (input_data[0]) === '0'? input_data.slice(1) : input_data
}
const removeDash: fuc2 = (input_data) => Number(input_data.replace(/-/g, ''))



// 함수 type 연습문제3
function 만들함수(a: string, func1: fuc1, func2: fuc2){
  let result = func1(a);
  let result2 = func2(result);
  console.log(result2)
}
만들함수('010-1111-2222', cutZero, removeDash)  //1011112222 출력잘됨








// 클래스
class Person {
  name;  // 미리 변수 만들어야 constructor에서 this.name 사용 가능
  constructor(name :string){
    this.name = name;
  }
}

 

 

/*
(index.html)

<h4 id="title">안녕하세요</h4>
<a href="naver.com">링크</a>
<button id="button">버튼</button>

<script src="변환된 자바스크립트파일.js"></script>



에 대해서 TS로 HTML 조작하기
*/


// h4 바꾸기 
let 제목 = document.querySelector('#title');
// 제목.innerHTML = '반가워요'    // 이렇게 쓰면 에러 -> 제목: Element | null 임  -> narrowing
if (제목 != null) {제목.innerHTML = '반가워요'}  // 방법 1
if (제목 instanceof Element) (제목.innerHTML = '반가워요') // 방법 2 (가장 권장되는 방법)
if (제목?.innerHTML != undefined) {제목.innerHTML = '반가워요'} // 방법 3


/*

html 태그 종류별로 정확한 타입명칭이 있습니다.
a 태그는 HTMLAnchorElement
img 태그는 HTMLImageElement
h4 태그는 HTMLHeadingElement
..
백만개가 있는데 이런 정확한 타입으로 narrowing 해주셔야 html 속성 수정을 제대로할 수 있습니다.
전부 외울 필요는 없고 자동완성 잘 될걸요
*/

let 링크 = document.querySelector('#link');
if (링크 instanceof HTMLAnchorElement) {
  링크.href = 'https://kakao.com'  //잘됨
}


let 버튼 = document.getElementById('button');
if (버튼 instanceof HTMLAnchorElement) {
    버튼.addEventListener('click', function(){
  console.log('안녕')
}) 
}

// eventlistener는 이게 더 나음

let 버튼1 = document.getElementById('button');
버튼1?.addEventListener('click', function(){
  console.log('안녕')
})

 

 

class Person {
    name : string // this.name 전에 여기서 타입 지정해줘야 함 (또는 name;)
    constructor(a: string){  // return 타입지정 불필요
        this.name = a
    }

    func(){
        console.log("함수" + this.name)
    }
    

}

// class에 무언가 입력값을 만드려면 -> constructor로 만듦


let p1 = new Person('kim');

console.log(p1.func())




class Car{
    model: string
    price: number
    constructor(model: string, price: number){
        this.model = model;
        this.price = price;
    }
    tax(){
        return (this.price/10)
        
    }
}

let car1 = new Car('소나타', 3000)

console.log(car1) //콘솔창 출력결과는 { model : '소나타', price : 3000 }
console.log(car1.tax()) //콘솔창 출력결과는 300



class Word {
  num: number[] = [];
  str: string[] = [];

  constructor(...inputData: (number | string)[]) {
    for (const item of inputData) {
      if (typeof item === 'number') {
        this.num.push(item);
      } else {
        this.str.push(item);
      }
    }
  }
}
let obj = new Word('kim', 3, 5, 'park');
console.log(obj.num) //[3,5]
console.log(obj.str) //['kim', 'park']


//-----------------------------------------
// interface명: 영문대문자로 시작

interface Square { color: string, width: number}
let sq: Square = {color : 'red', width: 100}

interface St {name: string}
interface Tch extends St {age: number} // type 방식))) type Tch = St & {age: number}
// 사실 interface도 extend없이 개별값만해서 let variable: St & Tch = {~} 로도 됨
let stu: St = {name : 'kim'}
let tchr: Tch = {name: 'kim', age: 20}

// 주요 차이점: type은 중복선언(재선언) 불가, interface는 가능(내용이 다르면 자동으로 extends처럼 반영해줌)
// -> 다른 사람이 나의 코드를 사용하는 경우가 많다면 유연한 interface 사용이 적절 (type은 엄격한 정의 용)



interface Goods {brand: string, serialNumber: number, model: string[]}
interface Cart {product: string, price: number}
interface SpCart extends Cart {card: boolean}

interface p {plus: (a: number, b: number) => number, minus: (a: number, b: number) => number}

const objp: p = {
    plus: (a, b) => a+b,
    minus: (a, b) => a-b 
}


const maxNum = (...nums: number[]) => {
    let maxVal = nums[0];
    for (let i = 1; i < nums.length; i++ ) {
        if (nums[i] > nums[0]) maxVal = nums[i]
    }
    return maxVal
}


const desfunc = ({user, comment, admin}: {user: string, comment: number[], admin: boolean}) => {
    return {user, comment, admin}
}

console.log(desfunc(  { user : 'kim', comment : [3,5,4], admin : false } ) )

type 어레이 = (number | string | boolean)[];

function 함수([a,b,c]:어레이){
  console.log(a,b,c)
}

함수( [40, 'wine', false] ) // array destructuring할 때는 자유작명이 가능

type Fish = { swim: string };
type Bird = { fly: string };
function 함수(animal: Fish | Bird) {
  if ("swim" in animal) {   // 파라미터 unique 속성으로 narrowing
    return animal.swim
  }
  return animal.fly
} 

// obj instanceof 부모 클래스  구조로도 narrowing 가능
let date = new Date();
if (date instanceof Date) {
}

// 특정 고정값 = literal type -> narrowing 근거로 사용 가능
type Cars = {wheel: 4, color: string}  
type Bike = {wheel: 2, color: string}
function chkfunc(x: Cars | Bike){
}


// never type: 1. 무언가 return 하지 않고 2. 끝나지도 않는(endpoint 없음) 함수를 표현하고 싶을 때 never type
// 2번 조건의 함수를 만들 일이 거의 없기 때문에 never 타입은 쓸 일이 없음 -> 무언가를 return하고싶지 않을 경우 그냥 void 쓰면되므로 굳이 never 쓸 필요 없음
// -> never는 코드 이상하게 짜다보면 자동 등장하므로 이때 그 의미 파악, 즉 모순적이거나 문제가 있을 때 등장
// function 함수() :never{
//   while ( true ) {
//     console.log(123)
//   }
// }

// function 함수() :never{
//   throw new Error('에러메세지')
// }

// 함수 파라미터가 never

function weirdFunc(parameter: string) {
  if ( typeof parameter === "string"){
    parameter + 1;
  } else {
    parameter;  // 이게 never
  }
}

// 즉, never가 보이면 뭔가 코드가 이상하거나 모순적이란 의미


function 함수1(){ // return void
  throw new Error()
}

let 함수2 = function (){ // 이 경우 return이 never
  throw new Error()
}

// 그외 strict로 any 할당 불가시 never



// public(없는 기존의 형태가 public), private(해당 클래스 안에서만 동작 및 수정이 가능하면서 외부의 new 형태로 수정 불가능하도록 할 때)

// class User { 
//     name;
//     constructor(){  // 파라미터 입력할 수 있도록 해줌
//         this.name = 'kim'
//     }
// }

class User {
    constructor(public name: string){   // name;, this.name=name 없이 public으로 먼저 선언하여 이 과정 생략 가능
    }
}

// protected: private보다 약간 느슨한(상속된 class에서도 조작가능) 형태
// static: class 안의 간단한 메모 / 기본 설정 값 입력 / class 부산물 obj가 사용할 필요가 없는 경우에 사용  
// static은 private, protected, public과 같이 쓸 수 있음
class Users { 
  static skill = 'js'; 
  intro = Users.skill + '전문가입니다' // static은 원본 클래스를 호출해야함(this.skill 안됨)
}

var 철수 = new Users();
console.log(철수);

Users.skill = 'python';  // static은 원본 클래스를 호출한 경우 값 수정도 가능
var 민수 = new Users();
console.log(민수);

//__________________________________________________________________________________


class Users1 { // 누가 어디서 쓸 수 있음?
  private static x = 10; // Users1안에서 User1만 가능
  public static y = 20; // 외부/내부 상관없이 Users1만 가능
  protected z = 30; // Users1 및 상속된 클래스들에 대해서 Users1만 가능
}


class Users2 {
  private static x = 10;
  public static y = 20;
  static addOne(num: number){return num + Users2.x}
  static printX(){console.log(Users2.x)}
}
Users2.addOne(3) //이렇게 하면 x가 3 더해져야함
Users2.addOne(4) //이렇게 하면 x가 4 더해져야함
Users2.printX()  //이렇게 하면 콘솔창에 x값이 출력되어야함



// let 네모 = new Square(30, 30, 'red');
// 네모.draw()
// 네모.draw()
// 네모.draw()
// 네모.draw()

 

 

// __________________________________________________


// type도 import, export 가능
//(a.ts)
//export type Name = string | boolean;
//export type Age = (a :number) => number;

//(b.ts)
//import {Name, Age} from './a'
//let 이름 :Name = 'kim';
//let 함수 :Age = (a) => { return a + 10 } 

// 옛날 import, export 없었을 때의 문법


//(a.ts)

namespace MyNamespace {   // 이걸로 감싸서 export. 더 과거의 문법은 module MyNamespce
  export interface PersonInterface { age : number };
  export type NameType = number | string;
} 

// (b.ts)
  // 옛날 방식의 import
/// <reference path="./a.ts" />   

let 이름 :MyNamespace.NameType = '민수';   // 그냥 NameType이 아니라 MyNameSpace의 NameType이다.
let 나이 :MyNamespace.PersonInterface = { age : 10 };

type NameType = boolean; // 위의 것과 별도의 의미로 사용 가능
interface PersonInterface {} // 위의 것과 별도의 의미로 사용 가능 


// type문제1)    export {type Car, interface Bike}  후 import {type Car, interface Bike}

// type문제2)

// const tempFunc = (inputData:specificType) => {
// }

// type문제3)
namespace StrTy {export type Dog = string;}
interface Dog { name : string };
let dog1: StrTy.Dog = "bark";




// generic func: type을 함수 파라미터처럼 가변적으로 입력하기, 보통 변수명 <T>로 사용


function 함수<MyType>(x: MyType[]) :MyType {
  return x[0];
}
// MyType에 입력에 따라 달라지는 type 적용

let ka = 함수<number>([4,2])
let kb = 함수<string>(['kim', 'park'])

// myType에 입력될 type 제한하기: extends로 제한
function 함수1<MyType extends number>(x: MyType){
    return x - 1
}


// 연습1) 문자를 집어넣으면 문자의 갯수, array를 집어넣으면 array안의 자료 갯수를 콘솔창에 출력해주는 함수
const CountingNum = <MyType extends string | []>(x: MyType):number => {
    return x.length
}


// 연습2)
interface Animal {
  name : string;
  age : number 
}
let data = '{"name" : "dog", "age" : 1 }'
function pj<T extends Animal>(x: T):T {
    return JSON.parse(data)
}




// 연습3) class 수정
class Person<T>{ //  type 구멍
  name;
  constructor(a: T) {   // 생성자함수는 입력부분만 가능. 
    this.name = a;
  }
}
let a = new Person('어쩌구');
a.name //any -> string


// array안에 string[] 그 이상으로 구체적으로 타입 지정 -> [] 안에 type 적는 형태(tuple)
let 멍멍이 :[string, boolean?, number?]; // ? : optional
멍멍이 = ['dog', true]

// spread에 type 붙이기
let arr = [1,2,3]
let arr2 :[number, number, ...number[]] = [4,5, ...arr]  


// 문2)
let arr3: [string, number, ...boolean[]] = ['동서녹차', 4000, true, false, true, true, false, true]


// 문3)
const tupFunc = (...x:[string, boolean, ...(string|number)[]]) => {}

// 문4)
const classify = (...x: (string|number)[]): [string[], number[]] => {
    const str: string[] = [];
    const num: number[] = [];
    for (let data of x) {
    typeof data === "string"? str.push(data): num.push(data);}
    return [str, num]
}

console.log(classify('b', 5, 6, 8, 'a')) //  [ ['b', 'a'], [5, 6, 8] ]



// ts로 작성되지않은 js의 변수/함수 등을 호출할 때
declare let 외부변수: number;  // 이렇게 사용. 디렉토리 어딘가에 정의되어있으니 그거 쓰라는 의미

// ambient module: ts는 import,export없이 전역적으로 다른파일에 선언된것 바로 사용 가능
// -> 이것을 막으려면 tsconfig.json에 "moduleDetection": "force" 설정 또는 각 파일마다 export {} 구문 추가(export내용 없어도 됨)
// 이렇게 제한된 상태에서 global 변수/타입 만드려면?
declare global {
  type Dog = string;
} 


// d.ts 의 의미? -> 타입 정의(type, interface) 보관용 파일들 -
// --> import { ~} from './~.d.ts';
// tsconfig.json에  "declaration": true 로 하면 ~d.ts 자동 생성 -> 굳이 수정 안해도 됨. 단 자동 global module 아님
// d.ts global화하려면 "typeRoots": ["./types/common/"]  근데 이거 쓰면 자동 type 반영 막힘
// 외부 라이브러리의 type(JQuery) import -> ts 공홈의 type search로 설치


// 특정 클래스가 interface의 type을 모두 포함하고 있는지 여부를 검사 -> implements 인터페이스타입
interface CarType {
  model : string,
  price : number
}

class Car implements CarType {  // 문제있으면 오류 발생시켜줌. 타입 속성 확인만 하므로 타입 할당/변형 의미 아님
  model : string;
  price : number = 1000;
  constructor(a :string){
    this.model = a
  }
}
let 붕붕이 = new Car('morning');


// -> 즉 implements는 복잡한 여러 class에서 확인이 필요할 때에 사용

// interface 한번에 type 지정 -> index signature
interface StringOnly {
  [key: string]: string   // 모든 키값의 value는 string이다.(한번에 지정)
}

let obj1 :StringOnly = {
  name : 'kim',
  age : '20',
  location : 'seoul'
}

// 길어진 재귀구조 한번에 type 지정하기
interface MyType {  // type도 재귀로 구성함
  'font-size': MyType | number
}

let obj3: MyType = {
  'font-size' : {
    'font-size' : {
      'font-size' : 14
    }
  }
}


// 문1: interface MyT { [key: string]: string | number}
// 문2: 
interface RecurT {
    'font-size': number,
    [key: string]: RecurT | number
}
let obj2: RecurT = {
  'font-size' : 10,
  'secondary' : {
    'font-size' : 12,
    'third' : {
      'font-size' : 14
    }
  }
} 


// keyof: object의 모든 key의 값을 union으로 합쳐서 출력
interface Person1 {
  age: number;
  name: string;
}
type PersonKeys1 = keyof Person1; // === age | name


interface Person2 {
  [key :string]: number;
}
type PersonKeys2 = keyof Person2;   // === string | number   (key값에 number넣어도 자동 String되기 때문)


// 타입 변환기: type 수정을 수동으로 하지않고 코드로 한 번에 하기

type Car1 = {
  color: boolean,
  model : boolean,
  price : boolean | number,
};


type TypeChanger<T> = {
    [key in keyof T]: string
}

type Car2 = TypeChanger<Car1> // Car2로 새로 생성 (모두 string type으로 일괄 변환 = 매핑)
// 세부적으로 특정 key는 다르게 -> 삼항 조건문 이용해서 조정
type Bus = {
  color : string,
  model : boolean,
  price : number
}

type TypeChanger2<T> = {
    [key in keyof T]: string | number
}
type Bus2 = TypeChanger2<Bus>

type TypeChanger3<MyT, T> = {
    [key in keyof MyT]: T     
}

type Bus3 = TypeChanger3<Bus, string[]>  // 입력한 값으로 반영하는 유연한 형태


type Age<T> = T extends string? T: unknown; // T 파라미터 조건문 검사는 extends로 수행
// T가 string이면 T, 아니면 unknown

let newA: Age<number>  // string 아니라서 unknown됨

type FirstItem<T> = T extends []? T[0]: any   // array오면 array의 첫번째 원소 타입을, 나머지는 any로


// 조건문에 쓸 수 있는 infer (자주 쓰진 않지만 읽을 줄은 알아야)
// infer = 왼쪽에서 type 정보를 추출해서 '변수;R'에 담아주세요  -> 그렇게 담아서 활용할 때 씀


type ExtractionT<T> = T extends (infer R)[] ? R : unknown   
type ext = ExtractionT<string[]>  // array의 내부 type만 추출

 
type ExtractionT2<T> = T extends (() => infer R) ? R : unknown   
type ext2 = ExtractionT2<() => void>  // 함수 return type만 추출
// 사실 이건 ReturnType<함수> 에 넣으면 return의 type만 추출하는 것과 동일함
type ext3 = ReturnType<() => void> // void


// 1. array 타입을 <> 안에 입력하면 array 타입의 0번째 타입이 string이면 string 타입을 그대로 남겨주고 array 타입의 0번째 타입이 string이 아니면 unknown 을 남겨주려면 어떻게 타입을 만들어놔야할까요?
type condT<T> = T extends [string, ...any]? T[0] : unknown


// 2. 함수 파라미터 추출
type extT<T> = T extends (x: infer R) => {}? R : unknown
728x90
반응형