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

"TypeScript" ํƒœ๊ทธ๋กœ ์—ฐ๊ฒฐ๋œ 2๊ฐœ ๊ฒŒ์‹œ๋ฌผ๊ฐœ์˜ ๊ฒŒ์‹œ๋ฌผ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ํƒœ๊ทธ ๋ณด๊ธฐ

ยท ์•ฝ 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();

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

ยท ์•ฝ 7๋ถ„

TypeScript + React + Storybook์œผ๋กœ ๋””์ž์ธ ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ#

Design System vs Component Library#

โ˜ ๋””์ž์ธ ์‹œ์Šคํ…œ์€ ๊ณ„์† ์ง„ํ™”ํ•˜๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ ์š”์†Œ ๋ชจ์Œ์ด๊ณ , ์ผ๊ด€์„ฑ๊ณผ ์†๋„๋ฅผ ๋ณด์žฅํ•˜๋Š” ๊ทœ์น™์„ ๋”ฐ๋ฅด๋Š” ๋ชจ๋“  ์ œํ’ˆ์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•œ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(SSOT)์ด๋‹ค.

  • ๋””์ž์ธ ์‹œ์Šคํ…œ์€ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋„˜์–ด์„œ ๋””์ž์ธ ์›์น™, ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ, ํŒจํ„ด, ํ†ค, ๊ทœ์น™๊ณผ ๋ช…์„ธ์„œ ๋“ฑ์„ ํฌํ•จํ•œ๋‹ค.

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ#

  • .gitignore , package.json ์ƒ์„ฑ

Storybook ์„ค์น˜ํ•˜๊ธฐ#

npx -p @storybook/cli sb init --type react
  • storybook ์‹œ์ž‘ํ•˜๊ธฐ
yarn storybook

React peer dependencies#

@storybook/react ๊ฐ€ react , react-dom ์„ peer-dependency๋กœ ๊ฐ€์ง€๋ฏ€๋กœ react, react-dom ์„ ์„ค์น˜ํ•ด์•ผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์„ค์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋‹ค์Œ์˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

Error: Cannot find module 'react-dom/package.json'

ํ•˜์ง€๋งŒ, dependency ์— ์ง์ ‘ ์ถ”๊ฐ€ํ•˜๋ฉด, ๊ฐœ๋ฐœํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ ์œ ์ €๊ฐ€ react , react-dom ์—ญ์‹œ ์„ค์น˜ ๋ฐ›๊ฒŒ ๋œ๋‹ค. (๊ฒŒ๋‹ค๊ฐ€ ์ •ํ•ด์ง„ ๋ฒ„์ „์œผ๋กœ)

๋”ฐ๋ผ์„œ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ž‘์„ฑ์ž ์ž…์žฅ์—์„œ๋Š” storybook์„ ์‹คํ–‰ํ•˜๋Š” ๊ฐœ๋ฐœ ์‹œ์—๋งŒ ์ด ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ devDependency์— ๋ช…์‹œํ•ด์•ผํ•œ๋‹ค.

๋˜ํ•œ, ์ด ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” react , react-dom ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋ผ์„œ peerDependency์—๋„ ๋ช…์‹œํ•ด์•ผํ•œ๋‹ค.

peerDependency๋Š” ์ด ์˜์กด์„ฑ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋œป์ด๊ณ , ์œ ์ €๊ฐ€ ์ง์ ‘ ์„ค์น˜ ํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

devDependency๋Š” ํ”„๋กœ์ ํŠธ์˜ ๋กœ์ปฌ์—๋งŒ ์„ค์น˜๋˜๊ณ  ๋ฐฐํฌ์‹œ์—๋Š” ์œ ์ €๊ฐ€ ๋‹ค์šด๋กœ๋“œํ•˜์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์— peerDependency์™€ devDependency์— ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋„ ์ถ”๊ฐ€ํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.

yarn add -D react react-dom
// package.json"peerDependencies": {  "react": "17.0.1",  "react-dom": "17.0.1",  "styled-components": "5.2.1"}

storybookjs/storybook

Yarn

Duplicate same dependency in package.json devDependencies and peerDependencies?

TypeScript๋กœ ์ด์ „ํ•˜๊ธฐ#

  • typescript, react-docgen-typescript-loader ์„ค์น˜

    yarn add -D typescript react-docgen-typescript-loader
  • stories typescript ๋ฒ„์ „์œผ๋กœ ๋ณ€๊ฒฝ

    create-react-app typescript ํ…œํ”Œ๋ฆฟ์— sb init ์œผ๋กœ ์ƒ์„ฑํ•œ ts ๋ฒ„์ „ stories๋กœ ํ…Œ์ŠคํŠธ

  • .storybook/main.js ๋ณ€๊ฒฝ

    module.exports = {    stories: [        '../stories/**/*.stories.mdx',        '../stories/**/*.stories.@(js|jsx|ts|tsx)',    ],    addons: ['@storybook/addon-links', '@storybook/addon-essentials'],    typescript: {        check: false,        checkOptions: {},        reactDocgen: 'react-docgen-typescript',        reactDocgenTypescriptOptions: {            shouldExtractLiteralValuesFromEnum: true,            propFilter: (prop) =>                prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,        },    },};
  • tsconfig.json ์ถ”๊ฐ€

    {    "compilerOptions": {        "target": "es5",        "lib": ["dom", "dom.iterable", "esnext"],        "allowJs": true,        "skipLibCheck": true,        "esModuleInterop": true,        "allowSyntheticDefaultImports": true,        "strict": true,        "forceConsistentCasingInFileNames": true,        "noFallthroughCasesInSwitch": true,        "module": "esnext",        "moduleResolution": "node",        "resolveJsonModule": true,        "isolatedModules": true,        "noEmit": true,        "jsx": "react-jsx"    },    "include": ["stories"]}

TypeScript

Rollup์œผ๋กœ ๋ฒˆ๋“ค๋งํ•˜๊ธฐ#

โ˜ ์›นํŒฉ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ๋ฒˆ๋“ค๋Ÿฌ๋ผ๋ฉด, ๋กค์—…์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฒˆ๋“ค๋Ÿฌ๋‹ค.

Webpack and Rollup: the same but different

Rollup ์„ค์ •#

  • ์‚ฌ์šฉํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ
"devDependencies": {  "babel-preset-react-app": "10.0.0", // create-react-app์—์„œ ์‚ฌ์šฉํ•˜๋Š” babel ์„ค์ •  "rollup": "2.35.1",  "rollup-plugin-babel": "4.4.0", // babel ์‚ฌ์šฉ์„ ์œ„ํ•œ ํ”Œ๋กœ๊ทธ์ธ  "rollup-plugin-cleaner": "1.0.0", // build ์ „์— dist ํด๋” ์‚ญ์ œ  "rollup-plugin-commonjs": "10.1.0", // CommonJS์˜ ๋ชจ๋“ˆ ์ฝ”๋“œ๋ฅผ ES6๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฌผ์— ํฌํ•จ  "rollup-plugin-node-resolve": "5.2.0", // ์จ๋“œํŒŒํ‹ฐ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๊ธฐ์œ„ํ•œ ์šฉ๋„  "rollup-plugin-peer-deps-external": "2.2.4", // peerDependencies๋ฅผ ๋ฒˆ๋“ค๋ง๋œ ๊ฒฐ๊ณผ์— ํฌํ•จํ•˜์ง€ ์•Š์Œ}
  • rollup.config.js
import commonjs from 'rollup-plugin-commonjs';import cleaner from 'rollup-plugin-cleaner';import resolve from 'rollup-plugin-node-resolve';import babel from 'rollup-plugin-babel';import external from 'rollup-plugin-peer-deps-external';import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
process.env.BABEL_ENV = 'production';
export default {    input: './src/index.ts',    plugins: [        cleaner({targets: ['./dist/']}),        peerDepsExternal(),        resolve({extensions}),        commonjs({            include: 'node_modules/**',        }),        babel({            extensions,            include: ['src/**/*'],            presets: [['react-app', {flow: false, typescript: true}]],            runtimeHelpers: true,        }),    ],    output: [        {            file: pkg.module,            format: 'es',        },    ],};

tsconfig.json & package.json ์„ค์ •#

declaration์ด๋ž€, ์ปดํฌ๋„ŒํŠธ๋“ค์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ํƒ€์ž… ์ •๋ณด๋“ค์„ ์ง€๋‹ˆ๊ณ  ์žˆ๋Š” ํŒŒ์ผ.

์ด๋Š” ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์ƒ์„ฑ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

tsc --emitDeclarationOnly

์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— tsconfig.json ์„ ์ˆ˜์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

{    "compilerOptions": {        "target": "es5",        "lib": ["dom", "dom.iterable", "esnext"],        "skipLibCheck": true,        "esModuleInterop": true,        "allowSyntheticDefaultImports": true,        "strict": true,        "forceConsistentCasingInFileNames": true,        "module": "esnext",        "moduleResolution": "node",        "resolveJsonModule": true,        "jsx": "react",        "declaration": true,        "declarationDir": "dist/types"    },    "include": ["src"],    "exclude": ["**/*.stories.tsx"]}
  • declarationย ๊ฐ’์„ย trueย ,ย declarationDirย ๊ฒฝ๋กœ๋ฅผย "dist/types"ย ๋กœ,

  • allowJs: ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์™€ ํ˜ผ์šฉ์„ ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด declaration ํŒŒ์ผ์„ ๋งŒ๋“ค์ง€ ๋ชปํ•˜๋ฏ€๋กœ์ œ๊ฑฐ.

  • noEmit: ๊ฒฐ๊ณผ๋ฌผ์„ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค๋Š” ์˜ต์…˜์œผ๋กœ ์ œ๊ฑฐ.

  • isolatedModules: ์•„๋ฌด ๊ฐ’๋„ ๋‚ด๋ณด๋‚ด์ง€ ์•Š๋Š” ํŒŒ์ผ์„ ๋ฐฉ์ง€ํ•˜๋Š” ์˜ต์…˜. ์ œ๊ฑฐ

stories.tsxย ํ™•์žฅ์ž๋Š” ๋ชจ๋‘ ๋ฌด์‹œํ•˜๋„๋กย excludeย ์˜ต์…˜์„ ์„ค์ •

package.json์—๋Š” Build ์ปค๋งจ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

"build": "rollup -c && tsc --emitDeclarationOnly",

package.json์—์„œ module, types, files ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. name์€ scope๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

{    "name": "@younho9/design-system",    "module": "dist/index.js",    "types": "dist/types/index.d.ts",    "files": ["/dist"]}

๋ฐฐํฌ ๋ช…๋ น์–ด

# npm publishnpm publish --access public # scope๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ

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

Do you think your component library is your design system? Think again

TypeScript์™€ Storybook์„ ์‚ฌ์šฉํ•œ ๋ฆฌ์•กํŠธ ๋””์ž์ธ ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ

๋””์ž์ธ ์‹œ์Šคํ…œ ์†Œ๊ฐœ

storybookjs/design-system

storybookjs/storybook

How to create a react component library with TypeScript, rollup.js and Storybook

Building a Design System Package With Storybook, TypeScript, and React in 15 Minutes

Creating and publishing scoped public packages | npm Docs