๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

TypeScript ํด๋ž˜์Šค vs JavaScript ํด๋ž˜์Šค

ยท ์•ฝ 9๋ถ„

class ์ž‘์„ฑ ์‹œ์— TypeScript์˜ ํด๋ž˜์Šค ๋ฌธ๋ฒ•๊ณผ JavaScript์˜ ๋ฌธ๋ฒ•์„ ๋น„๊ตํ•ด๋ณด์ž.

JS ํด๋ž˜์Šค ๋ฌธ๋ฒ•#

JS์—์„œ ํด๋ž˜์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค.

class MyClass {  publicProp = 'value'; // public ํ”„๋กœํผํ‹ฐ  #privateProp = 'value'; // private ํ”„๋กœํผํ‹ฐ. ES2019 ๋ถ€ํ„ฐ ์ง€์›.  _protectedProp = 'value'; // protected ํ”„๋กœํผํ‹ฐ. ๊ธฐ๋Šฅ์ ์œผ๋กœ ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ, ๊ด€๋ก€์ ์œผ๋กœ _๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‘œํ˜„ํ•˜๊ณ  ์•ฝ์†ํ•˜์—ฌ ์‚ฌ์šฉํ•จ.
  constructor(...) { // ์ƒ์„ฑ์ž ๋ฉ”์„œ๋“œ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์„ ๋ฐ›์•„ ํ”„๋กœํผํ‹ฐ๋ฅผ ์„ค์ •ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.    // ...  }
  static staticMethod(...) {} // static ๋ฉ”์„œ๋“œ
  publicMethod(...) {} // public ๋ฉ”์„œ๋“œ  #privateMethod(...) {} // private ๋ฉ”์„œ๋“œ
  get publicProp(...) {} // getter ๋ฉ”์„œ๋“œ  set publicProp(...) {} // setter ๋ฉ”์„œ๋“œ
  get privateProp(...) {} // getter ๋ฉ”์„œ๋“œ  set privateProp(...) {} // setter ๋ฉ”์„œ๋“œ
  get protectedProp(...) {} // getter ๋ฉ”์„œ๋“œ  set protectedProp(...) {} // setter ๋ฉ”์„œ๋“œ}

ํด๋ž˜์Šค ์ƒ์†#

JS์—์„œ๋„ ํด๋ž˜์Šค ์ƒ์†์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ์กด ํด๋ž˜์Šค๋ฅผ ํ™•์žฅํ•˜์—ฌ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ• ์ˆ˜์žˆ๋‹ค.

class Animal {    constructor(name) {        this.speed = 0;        this.name = name;    }    run(speed) {        this.speed = speed;        alert(`${this.name} ์€/๋Š” ์†๋„ ${this.speed}๋กœ ๋‹ฌ๋ฆฝ๋‹ˆ๋‹ค.`);    }    stop() {        this.speed = 0;        alert(`${this.name} ์ด/๊ฐ€ ๋ฉˆ์ท„์Šต๋‹ˆ๋‹ค.`);    }}
let animal = new Animal('๋™๋ฌผ');
class Rabbit extends Animal {    hide() {        alert(`${this.name} ์ด/๊ฐ€ ์ˆจ์—ˆ์Šต๋‹ˆ๋‹ค!`);    }}
let rabbit = new Rabbit('ํฐ ํ† ๋ผ');
rabbit.run(5); // ํฐ ํ† ๋ผ ์€/๋Š” ์†๋„ 5๋กœ ๋‹ฌ๋ฆฝ๋‹ˆ๋‹ค.rabbit.hide(); // ํฐ ํ† ๋ผ ์ด/๊ฐ€ ์ˆจ์—ˆ์Šต๋‹ˆ๋‹ค!

ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ด๋‹

ํด๋ž˜์Šค Rabbit์œผ๋กœ ๋งŒ๋“  ๊ฐ์ฒด๋Š” rabbit.hide()์™€ ๊ฐ™์€ Rabbit์— ์ •์˜๋œ ๋ฉ”์„œ๋“œ์—๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ , rabbit.run()๊ณผ ๊ฐ™์€ Animal์— ์ •์˜๋œ ๋ฉ”์„œ๋“œ์—๋„ ์ ‘๊ทผํ• ์ˆ˜์žˆ๋‹ค.

extends ํ‚ค์›Œ๋“œ๋Š” ์ด๋ ‡๊ฒŒ ํ”„๋กœํ† ํƒ€์ž… ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜์—ฌ, ๊ฐ์ฒด rabbit -> Rabbit.prototype -> Animal.prototype ์ˆœ์œผ๋กœ ๋ฉ”์†Œ๋“œ๋ฅผ ์ฐพ์•„์„œ ์‹คํ–‰ํ•œ๋‹ค.

์ ‘๊ทผ ์ œ์–ด์ž(Access Modifier)#

๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ์ ‘๊ทผ ์ œ์–ด์ž๋ฅผ ํ™œ์šฉํ•ด ๋‚ด๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค์™€ ์™ธ๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ์บก์Šํ™”ํ•œ๋‹ค.

  • public : ์–ด๋””์„œ๋“ ์ง€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์™ธ๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.
  • protected : ํด๋ž˜์Šค ์ž์‹ ๊ณผ ์ž์† ํด๋ž˜์Šค์—์„œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋‚ด๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.
  • private : ํด๋ž˜์Šค ์ž์‹ ์—์„œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋‚ด๋ถ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.

JS์—์„œ๋Š” public, protected ์— ๋Œ€ํ•œ ๋ฌธ๋ฒ•์  ์ง€์›์ด ์—†์œผ๋ฉฐ, ๊ด€์Šต์ ์œผ๋กœ _ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ protected ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

private ์†์„ฑ์€ ES2019๋ถ€ํ„ฐ ์ง€์›ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

static#

์ •์  ํ”„๋กœํผํ‹ฐ์™€ ๋ฉ”์„œ๋“œ๋Š” ์–ด๋–ค ํŠน์ •ํ•œ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ํด๋ž˜์Šค์— ์†ํ•œ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ ์žํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

์ •์  ๋ฉค๋ฒ„๋Š” ์ƒ์†๋œ๋‹ค.

class Article {    constructor(title, date) {        this.title = title;        this.date = date;    }
    static createTodays() {        // this๋Š” Article์ž…๋‹ˆ๋‹ค.        return new this("Today's digest", new Date());    }}
let article = Article.createTodays();
alert(article.title); // Today's digest

TS ํด๋ž˜์Šค ๋ฌธ๋ฒ•#

TS๋Š” JS์˜ ๋ชจ๋“  ํด๋ž˜์Šค ๋ฌธ๋ฒ•์„ ์ง€์›ํ•˜๋ฉฐ, ๊ทธ์— ๋”ํ•ด์„œ ๋ช‡ ๊ฐ€์ง€ ๋ฌธ๋ฒ•์ ์ธ ์ง€์›์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. ํ•˜์ง€๋งŒ ๋ชจ๋“  ๋ฌธ๋ฒ•์€ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋Œ€ํ•ด์„œ๋งŒ ์ ์šฉ๋˜๊ณ , ๋Ÿฐํƒ€์ž„์—๋Š” ๊ฒฐ๊ตญ JS๊ฐ€์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ๋งŒ์„ ์‚ฌ์šฉํ•ด ๋™์ž‘ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์ž.

๋‹ค์Œ์€ JS์—์„œ ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ, TS์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Class์™€ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ๋“ค์ด๋‹ค.

readonly#

ํ•„๋“œ์—๋Š” readonly ์ œ์–ด์ž๋ฅผ ์ ‘๋‘์‚ฌ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ƒ์„ฑ์ž ํ•จ์ˆ˜๊ฐ€์•„๋‹Œ ๊ณณ์—์„œ ํ• ๋‹น์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

class Greeter {    readonly name: string = 'world';
    constructor(otherName?: string) {        if (otherName !== undefined) {            this.name = otherName;        }    }
    err() {        this.name = 'not ok';        // Cannot assign to 'name' because it is a read-only property.    }}const g = new Greeter();g.name = 'also not ok';// Cannot assign to 'name' because it is a read-only property.

overload#

JS๋Š” ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๊ฐฏ์ˆ˜์™€ ์ž๋ฃŒํ˜•์ด ์ž์œ ๋กญ๊ธฐ ๋•Œ๋ฌธ์—, ์˜ค๋ฒ„๋กœ๋“œ์— ๋Œ€ํ•œ ๊ฐœ๋…์ด์‚ฌ์‹ค์ƒ ์—†๋‹ค. (๋‹จ์ง€, ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ •๋„)

TS์—์„œ๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์— ๋Œ€ํ•ด ๊ตฌ์ฒด์ ์œผ๋กœ ์˜ค๋ฒ„๋กœ๋“œ signature๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

class Point {    // Overloads    constructor(x: number, y: string);    constructor(s: string);    constructor(xs: any, y?: any) {        // TBD    }}
class Util {    // Overloads    len(s: string): number;    len(arr: any[]): number;    len(x: any) {        return x.length;    }}

override#

TypeScript์—์„œ๋Š” ํŒŒ์ƒ ํด๋ž˜์Šค๊ฐ€ ํ•ญ์ƒ ๊ธฐ๋ณธ ํด๋ž˜์Šค์˜ ํ•˜์œ„ ์œ ํ˜•์ด ๋˜๋„๋ก ํ•œ๋‹ค.

class Base {    greet() {        console.log('Hello, world!');    }}
class Derived extends Base {    greet(name?: string) {        if (name === undefined) {            super.greet();        } else {            console.log(`Hello, ${name.toUpperCase()}`);        }    }}
const d = new Derived();d.greet();d.greet('reader');

ํŒŒ์ƒ ํด๋ž˜์Šค๊ฐ€ ๊ธฐ๋ณธ ํด๋ž˜์Šค์˜ ๊ทœ์น™์„ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•œ๋ฐ, ๊ธฐ๋ณธ ํด๋ž˜์Šค๊ฐ€ ํŒŒ์ƒ ํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

// Alias the derived instance through a base class referenceconst b: Base = d;// No problemb.greet();

๋งŒ์•ฝ ํŒŒ์ƒ ํด๋ž˜์Šค๊ฐ€ ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ๋”ฐ๋ฅด์ง€ ์•Š๋Š”๋‹ค๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

class Base {    greet() {        console.log('Hello, world!');    }}
class Derived extends Base {    // Make this parameter required    greet(name: string) {        // Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'.        // Type '(name: string) => void' is not assignable to type '() => void'.        console.log(`Hello, ${name.toUpperCase()}`);    }}
const b: Base = new Derived();// Crashes because "name" will be undefinedb.greet();

์ ‘๊ทผ ์ œ์–ด์ž(Access Modifier)#

Member Visibility

  • public : ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ๋ฉค๋ฒ„๋Š” public์œผ๋กœ ์–ด๋””์„œ๋‚˜ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.
  • protected : ํด๋ž˜์Šค ์ž์‹ ๊ณผ ์ž์† ํด๋ž˜์Šค์—์„œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.
  • private : protected์™€ ์œ ์‚ฌํ•˜๊ฒŒ ํด๋ž˜์Šค ์ž์‹ ์—์„œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ž์† ํด๋ž˜์Šค์—์„œ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.

Why No Static Classes?#

TS(JS)๋Š” Java๋‚˜ C#์—์„œ ์‚ฌ์šฉํ•˜๋Š” static class ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. (static class๋Š” ์ธ์Šคํ„ด์Šคํ™” ํ•  ์ˆ˜ ์—†๋‹ค.)

๋”ฐ๋ผ์„œ, static class ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•˜๋‹ค. ๋‹จ์ง€ ๋‹จ์ˆœ ๋ฆฌํ„ฐ๋Ÿด ๊ฐ์ฒด๋ฅผ์“ฐ๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•˜๋‹ค.

// Unnecessary "static" classclass MyStaticClass {    static doSomething() {}}
// Preferred (alternative 1)function doSomething() {}
// Preferred (alternative 2)const MyHelperObject = {    dosomething() {},};

abstract#

์•„์ง ๊ตฌํ˜„ํ•˜์ง€ ์•Š์€ ๋ฉ”์†Œ๋“œ์™€ ํ”„๋กœํผํ‹ฐ์— abstract ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. abstract ๋ฉค๋ฒ„๋ฅผ ๊ฐ€์ง„ ํด๋ž˜์Šค๋Š” ๋ฐ˜๋“œ์‹œ abstarct class ์—ฌ์•ผ ํ•œ๋‹ค.

abstract class์˜ ์—ญํ• ์€ abstract ๋ฉค๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•  ์„œ๋ธŒ ํด๋ž˜์Šค์˜ ๊ธฐ์ดˆ ํด๋ž˜์Šค๊ฐ€๋˜๋Š” ๊ฒƒ์ด๋‹ค.

abstract class Base {    abstract getName(): string;
    printName() {        console.log('Hello, ' + this.getName());    }}
class Derived extends Base {    getName() {        return 'world';    }}
const d = new Derived();d.printName();

์ฐธ๊ณ ์ž๋ฃŒ#