
해결 방법 참조
TS 학습 도중, 보통 Tab키로 들여쓰기하고 HTML 코드를 Live server로 열어서 간단하게 확인하거나 코드 자동 정렬로 단축키로 진행했었는데, 갑자기 다 먹통이 되었다.
상속이라는 개념의 대표적인 형태class Apple {
protected version: string = "ios14";
public getVerson() { // public이 적용된 변수나 함수는 클래스 외부참조 가능.
return this.version;
}
public setVerson(version: string) {
this.version = version;
}
}
class IPhone extends Apple {
public printVersion() {
console.log(this.version);
}
}
let Iphone = new IPhone();
console.log(phone.printVersion()); // ios14
class Iphone extends Apple, phone
// Error, “Classes can only extend a single class”
오버라이드해서 사용해야 한다.⬆ class Dog implements Animal, Friend {...}
interface Animal {
dog: string;
getName(): void;
}
class Dog implements Animal {
constructor(public dog: string) {}
getName(): void {
console.log(this.dog);
}
}
function callWith(dog: Animal): void {
dog.getName();
}
callWith(new Dog("jindo"));
오버라이드
: 자식 클래스가 자신의 부모 클래스들 중 하나에 의해 이미 제공된 함수 등을 특정한 형태로 구현하는 것을 말한다.
즉, 부모 클래스에서 이미 정의된 함수 등을 자식 클래스에서 같은 이름으로 사용하되, 안에 들어가는 내용(기능, 속성)을 바꿔서 사용한다.흔히 부모와 자식 사이에서 형제들이 유전적으로 물려받는 부분은 같아도 어떻게 먹고, 어떻게 자고, 어떻게 생활하냐에 따라 키, 외모, 공부 성적 등이 달라지듯 자식 클래스도 상속 받은 부모 클래스의 기능들을 어떻게 바꿔서 사용하는지에 따라 그 기능이 달라지게 됩니다. 그것을 프로그래밍에서는 Overriding(오버라이딩)이라고 합니다.
참고 블로그
왜 사용하는 것인가?
1. 가장 일반적인 용도는 하위 클래스(=구체 클래스)에서 공통 동작을 찾는 것.
2. 추상 클래스는 객체 인스턴스화할 수 없다는 것을 알고 있다는 전제하에 사용된다.
추상화는 객체 지향 프로그래밍(OOP:Object-Oriented Programming)의 핵심 아이이디어 중 하나입니다. 복잡성을 최소화하고 고급 아키텍처 문제를 해결하는데 도움이 되는 기술이며, 하위 수준의 세부 사항을 미리 구현할 필요가 없습니다. 상위 수준에 집중하고 나중에 세부 사항을 구현합니다.
참고 블로그
내용이 없이 메소드 이름과 타입만이 선언된 메소드를 말한다.하나 이상의 추상 메소드를 포함하며, 일반 메소드도 포함할 수 있다.추상 클래스를 상속한 클래스는 추상 클래스의 추상 메소드를 반드시 구현해야 한다.abstract class Department {
constructor(public name: string) {}
abstract printName(): void; // must be implemented in derived classes
}
class Roundlab extends Department {
printName(): void {
console.log(this.name);
}
}
interface PeopleInterface {
name: string
age: number
}
const me1: PeopleInterface = {
name: 'yc',
age: 34,
}
type PeopleType = {
name: string
age: number
}
const me2: PeopleType = {
name: 'yc',
age: 31,
}
type처럼 interface는 객체의 타입 이름을 지정하는 또 다른 방법이다.
차이점
interface PeopleInterface {
name: string
age: number
}
interface StudentInterface extends PeopleInterface {
school: string
}
-------------------------------------------
type PeopleType = {
name: string
age: number
}
type StudentType = PeopleType & {
school: string
}
interface에서 할 수 있는 대부분의 기능을은 type에서 가능하지만, 중요한 차이점은 type은 새로운 속성을 추가하기 위해서 다시 같은 이름으로 선언할 수 있다.interface는 항상 선언적 확장이 가능하다는 것이다.interface Window {
title: string
}
interface Window {
ts: TypeScriptAPI
}
// 같은 interface 명으로 Window를 다시 만든다면, 자동으로 확장이 된다.
const src = 'const a = "Hello World"'
window.ts.transpileModule(src, {})
---------------------------------------------
type Window = {
title: string
}
type Window = {
ts: TypeScriptAPI
}
// Error: Duplicate identifier 'Window'.
// type으로 선언적 확장은 안된다.
interface FooInterface {
value: string
}
type FooType = {
value: string
}
type FooOnlyString = string
type FooTypeNumber = number
-----------------------------------
// 불가능
interface Foo2interface extends string {}
type은 사용가능하지만, interface는 불가능하다.type names = 'firstName' | 'lastName'
type NameTypes = {
[key in names]: string
}
const yc: NameTypes = { firstName: 'hi', lastName: 'yc' }
--------------------------------------------
interface NameInterface {
// error
[key in names]: string
}
여러 type 혹은 interface를&하거나extends할 때를 생각해보자.interface는 속성간 충돌을 해결하기 위해 단순한 객체 타입을 만든다.왜냐하면interface는 객체의 타입을 만들기 위한 것이고, 어차피객체만 오기 때문에 단순히 합치기만 하면 되기 때문이다.
그러나type의 경우,재귀적으로 순회하면서 속성을 머지하는데,이 경우에 일부 never가 나오면서 제대로 머지가 안될 수 있다.
interface와는 다르게,type은 원시 타입이 올수도 있으므로, 충돌이 나서 제대로 머지가 안되는 경우에는 never가 떨어진다.따라서 타입 간 속성을 머지 할 때는 주의를 필요로 한다.
어차피 객체에서만 쓰는 용도라면, interface를 쓰는 것이 훨씬 낫다.
type &(합성)의 경우, 합성 자체에 대한 유효성을 판단하기 전에, 모든 구성요소에 대한 타입을 체크하므로 컴파일 시에 interface보다 상대적으로 성능이 좋지 않음.
객체, 타입간의 합성등을 고려해 보았을 때 interface를 쓰는 것이 더 나을지 않을까 싶다.
Array Type === object
- 배열의 타입 유형은 object다.
// class 생성자
// class User {
// public email: string;
// private name: string;
// readonly city: string = "Busan";
// constructor(email: string, name: string) {
// this.email = email;
// this.name = name;
// }
// }
// const hwui = new User("sadfa@naver.com", "hwui");
// console.log(hwui);
// console.log(hwui.email.split("@")[1]);
// hwui.name
// 접근 수정자 public, private (class 생성자에 쓸 수 있음.)
// protected (보호)
// protected this는 단순히 이 속성에 배치하거나 이 속성을 상속하는 모든 클래스에서 접근 가능함.
class User {
protected _courseCount = 1;
readonly city: string = "Tokyo";
constructor(
public email: string,
public name: string,
private userId: string
) {}
private deleteToken() {
console.log("Token deleted");
}
get getAppleEmail(): string {
return `apple${this.email}`;
}
get courseCount(): number {
return this._courseCount;
}
set courseCount(courseNum) {
// type이 없음.
if (courseNum <= 1) {
throw new Error("Course count should be more than 1");
}
this._courseCount = courseNum;
}
}
const hwuiinn = new User("TS@naver.com", "Kim", "쟁반짜장먹고싶당");
console.log(hwuiinn);
// hwuiinn.deleteToken();
// class extends (클래스 상속)
// private 처리된 값은 상속되지 않는다. (비공개) 클래스 외부가 아닌 클래스 내에서만 접근할 수 있음.
class SubUser extends User {
isFamily: boolean = true;
changeCourseCount() {
this._courseCount = 4;
}
}
// 1. interface 선언
// 2. class SOMETHING implements InterfaceName {}
interface TakePhoto {
cameraMode: string;
filter: string;
burst: number;
}
interface Story {
createStory(): void;
}
class Instagram implements TakePhoto {
constructor(
public cameraMode: string,
public filter: string,
public burst: number
) {}
}
// 상속받은 것 이상으로 속성 추가는 허용되지만, 필요한 것 보다 적게 할 수는 없다.
class Youtube implements TakePhoto, Story {
constructor(
public cameraMode: string,
public filter: string,
public burst: number,
public short: string // 추가하는 것은 가능. 하지만 기본 상속한 클래스보다 속성이 작은 것은 안된다.
) {}
createStory(): void {
console.log("Create Story Complete");
}
}
// Generic은 구성 요소를 재사용 가능하게 만드는 것 중 하나이며, 시간 구성 요소는 내가 의미하는 것임.
// 배열도 제너릭이다.
const score: Array<number> = [];
const names: Array<string> = [];
function identityOne(val: boolean | number): boolean | number {
return val;
}
function identityTwo(val: any): any {
return val;
}
// 꺾쇠 <> 괄호에 Type을 지정하면 내가 넣는 모든 것에 Type이 자동으로 같은 것으로 지정된다.
function identityThree<Type>(val: Type): Type {
return val;
}
// identityThree(true);
// 데이터 유형을 전달할 수 있는 구문. 구식 숫자와 같은 기본 문자열은 이렇게 직접 전달할 수 있음.
function identityFour<T>(val: T): T {
return val;
}
// 자신만의 것을 만들려고 하면 해당 구문을 사용해야 한다. (interface로 데이터 유형을 넘겨줘야 함)
interface Bootle {
brand: string;
type: number;
}
identityFour<Bootle>({
brand: "Starbucks",
type: 554,
});
function getSearchProducts<T>(products: T[]): T {
// do some database operations
const myIndex = 3;
return products[myIndex];
}
// 화살표 함수에서 제너릭 사용 방법
// 일반적인 구문은 특히 react로 작업하는 사람들이 많이 찾을 것.
// 그들은 이러한 Generics을 사용하기 좋아하고 여기에 쉼표를 넣어 이것이 JSX 구문이 아니라 일반 구문이므로 이 쉼표가 꽤 많이 표시된다는 점을 명시할 것.
const getMoreSearchProducts = <T>(products: T[]): T => {
// do some database operations
const Iphone = 30;
return products[Iphone];
};
// 제네릭 제약 조건에서 유형 매개 변수를 사용한다고 말하는 문서는 왜 있는 것인가?
// >> 목적 : 구문에만 초점을 맞추는 대신, 이 이면의 사고방식을 이해해야 함.
// 사고방식을 암기하거나 왜 그것이 실제로 맨 처음에 속하는지 이해해야 함.
// 유형 매개 변수란?
// 코드로 보자 일단..
interface Database {
connection: string;
username: string;
password: string;
}
function Huckfunction<T, U extends number>(valOne: T, valTwo: U): object {
return {
valTwo,
valOne,
};
}
// Huckfunction(3, {})
interface Quiz {
name: string;
type: string;
}
interface Course {
name: string;
author: string;
subject: string;
}
class Sellable<T> {
public cart: T[] = [];
addTocart(product: T) {
this.cart.push(product);
}
}
abstract class TakePhoto {
constructor(public cameraMode: string, public filter: string) {}
abstract getSepia(): void;
getReelTime(): number {
// some complex calculation
return 8;
}
}
class Instagram extends TakePhoto {
constructor(
public cameraMode: string,
public filter: string,
public burst: number
) {
super(cameraMode, filter);
}
getSepia(): void {
console.log("sepia..");
}
}
const as = new Instagram("yeas", "밝게", 5);
console.log(as);
// typeof << type guard
function detectType(val: number | string) {
if (typeof val === "string") {
return val.toLowerCase();
}
return val + 3;
}
detectType("HELLO");
function provideId(id: string | null) {
if (!id) {
return confirm("Plz provide UR ID");
} else {
id.toLowerCase();
}
}
function printAll(strs: string | string[] | null) {
if (strs) {
if (typeof strs === "object") {
for (const s of strs) {
console.log("s : ", s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}
}
// narrowing (Type 축소)
// 예시
interface User {
name: string;
email: string;
}
interface Admin {
name: string;
email: string;
isAdmin: boolean;
}
function isAdmin(account: User | Admin) {
if ("isAdmin" in account) {
return account.isAdmin;
}
}
function logValue(x: Date | string) {
if (x instanceof Date) {
// instanceof 연산자를 사용하면 객체가 특정 클래스에 속하는지 아닌지를 확인할 수 있음. 상속 관계도 확인해준다. boolean값으로 알려줌.
console.log(x.toUTCString());
} else {
console.log(x.toUpperCase());
}
}
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function getFood(pet: Fish | Bird) {
if (isFish(pet)) {
pet;
return "fish food";
} else {
pet;
return "bird food";
}
}
// Discriminated Unions - 차별조합
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
side: number;
}
interface Rectangle {
kind: "rectangle";
length: number;
width: number;
}
type Shape = Circle | Square | Rectangle;
function getTrueShape(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
} else if (shape.kind === "square") {
return shape.side ** 2;
}
return shape.length * shape.width;
}
// default로 절대 사용되어선 안될 never 타입의 변수를 하나 적용해준다.
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
break;
case "square":
return shape.side ** 2;
case "rectangle":
return shape.length * shape.width;
default:
const _defaultforshape: never = shape;
return _defaultforshape;
}
}