2. TSyringe

TSyringe๋ž€?

TSyringe ๋Š” ๊ฐ€๋ฒผ์šด DI ์ปจํ…Œ์ด๋„ˆ์ด๋‹ค.

TypeScript์šฉ DI๋„๊ตฌ(IoC Container(์ œ์–ด์˜ ์—ญ์ „))์ด๋ฉฐ, External Store๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ํ™œ์šฉ ๊ฐ€๋Šฅ ํ•˜๋‹ค. React์—์„œ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” Prop Driling ๋ฌธ์ œ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

DI๋ž€?(Dependency Injection - ์˜์กด์„ฑ ์ฃผ์ž…)

์†Œํ”„ํŠธ์›จ์–ด ๊ณตํ•™์—์„œ ์ค‘์š”ํ•œ ๊ฐœ๋… ์ค‘ ํ•˜๋‚˜๋กœ, ํด๋ž˜์Šค๋‚˜ ๋ชจ๋“ˆ์ด ํ•„์š”๋กœ ํ•˜๋Š” ์˜์กด์„ฑ์„ ์ง์ ‘ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์™ธ๋ถ€์—์„œ ์˜์กด์„ฑ์„ ์ฃผ์ž… ๋ฐ›๋Š” ๋ฐฉ์‹์„ ์˜๋ฏธํ•œ๋‹ค.

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

DI๋Š” ์ด๋Ÿฌํ•œ ์˜์กด์„ฑ์„ ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋ฐ›๋„๋ก ์„ค๊ณ„ํ•จ์œผ๋กœ์จ, ์ฝ”๋“œ์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๊ณ  ์‘์ง‘๋ ฅ์„ ๋†’์ธ๋‹ค.

  • Coupling(๊ฒฐํ•ฉ๋„)

    • ๋‘ ๋ชจ๋“ˆ ๊ฐ„์˜ ์ƒํ˜ธ ์˜์กด์„ฑ.

    • ๊ฒฐํ•ฉ๋„๊ฐ€ ๋‚ฎ์„์ˆ˜๋ก ๋ชจ๋“ˆ ๊ฐ„์˜ ์˜์กด์„ฑ์ด ์ ์–ด์ ธ ๋ชจ๋“ˆ์˜ ๋ณ€๊ฒฝ์ด ๋‹ค๋ฅธ ๋ชจ๋“ˆ์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์ด ์ค„์–ด๋“ฌ.

    • ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’์œผ๋ฉด ๋ชจ๋“ˆ ๊ฐ„์˜ ์˜์กด์„ฑ์ด ๊ฐ•ํ•ด์ ธ, ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ์„ ๋ณ€๊ฒฝํ•  ๋•Œ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง

  • Cohesion(์‘์ง‘๋ ฅ)

    • ๋ชจ๋“ˆ ๋‚ด๋ถ€์˜ ์š”์†Œ๋“ค์ด ์„œ๋กœ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ •๋„.

    • ๋†’์€ ์‘์ง‘๋ ฅ์€ ํ•œ ๋ชจ๋“ˆ์ด ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์— ์ง‘์ค‘๋˜์–ด์žˆ๊ณ , ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๊ณผ๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘.

    • ๋‚ฎ์€ ์‘์ง‘๋ ฅ์€ ๋ชจ๋“ˆ ๋‚ด์˜ ์š”์†Œ๋“ค์ด ์„œ๋กœ ๋‹ค๋ฅธ ๋ชฉ์ ์„ ์œ„ํ•ด ํ˜ผํ•ฉ๋˜์–ด ์žˆ๊ฑฐ๋‚˜, ๊ด€๋ จ์—†๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ์„ ๋‚˜ํƒ€๋‚ด์–ด, ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ต๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋‚ฎ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค.


์„ค์น˜ ๋ฐ ์„ค์ •

npm i tsyringe reflect-metadata

main.tsx

์•ฑ์ด ์‹œ์ž‘๋˜๋Š” ๋ถ€๋ถ„์— reflect-metadata ์ž„ํฌํŠธ ์‹œ์ผœ์ค€๋‹ค. src/setupTests.ts ํŒŒ์ผ๋„ ์žˆ๋‹ค๋ฉด ์‹œ์ž‘์ ์— ๋˜‘๊ฐ™์ด ์ž„ํฌํŠธ ์‹œ์ผœ์ค€๋‹ค.

import 'reflect-metadata';

tsconfig.json

Tsyringe ์—์„œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, tsconfig.json ์ฝ”๋“œ ๋ณ€๊ฒฝ

{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
    ...
  }
}

Store ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด count ๋ณ€์ˆ˜์˜ ๊ด€๋ฆฌ์™€ UI ์—…๋ฐ์ดํŠธ๋ฅผ ํ•ด๋ณด์ž

// scr/stores
import { singleton } from 'tsyringe';

type Listener = () => void;

@singleton()
export default class CounterStore {
  count = 0;

  // ์™ธ๋ถ€ forUpdate ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ์šฉ๋„
  // set ์„ ํ•œ ์ด์œ ๋Š”, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ™์€ forceUpdate ๋ฅผ ๋“ฑ๋กํ–ˆ์„ ๋•Œ ๋ฎ์–ด์“ฐ๋Š” ๊ฒƒ์„
  // ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.
  listeners = new Set<Listener>();

  // ์™ธ๋ถ€์—์„œ ๋ฐ›์•„์˜จ forUpdate ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์šฉ๋„
  publish() {
    this.listeners.forEach((listener: Listener) => listener());
  }

  // ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง
  // ์ „์—ญ๊ฐ™์ด? ๊ด€๋ฆฌ๋˜๋Š” ํด๋ž˜์Šค์˜ ๋ฉค๋ฒ„๋ณ€์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ ํ›„์—,
  // publish ๋ฅผ ํ†ตํ•˜์—ฌ forceUpdate ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ, ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ•์ œ๋กœ ๋ฆฌ๋ Œ๋”๋งํ•œ๋‹ค.
  increase() {
    this.count += 1;
    this.publish();
  }

  // ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง
  decrease() {
    this.count -= 1;
    this.publish();
  }

  // ๋ฆฌ์Šค๋„ˆ(forceUpdate ํ•จ์ˆ˜) ๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ
  addListener(listener: Listener) {
    this.listeners.add(listener);
  }

  // ๋ฆฌ์Šค๋„ˆ(forceUpdate ํ•จ์ˆ˜) ๋ฅผ ๋“ฑ๋กํ•ด์ œํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ
  deleteListener(listener: Listener) {
    this.listeners.delete(listener);
  }
}

Store ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์—์„œ๋Š” ํ›จ์”ฌ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import useCounterStore from '../hooks/useCounterStore';

export default function CounterControl() {
  const store = useCounterStore();

  const handleIncrease = () => {
    store.increase();
  };

  const handleDecrease = () => {
    store.decrease();
  };

  return (
    <div>
      <p>{store.count}</p>
      <button type='button' onClick={handleIncrease}>
        Increase
      </button>
      <button type='button' onClick={handleDecrease}>
        Decrease
      </button>
    </div>
  );
}
import useCounterStore from '../hooks/useCounterStore';

export default function Counter() {
  const store = useCounterStore();

  return (
    <div>
      <p>Count {store.count}</p>
    </div>
  );
}

Last updated