External Store

ํ•™์Šตํ‚ค์›Œ๋“œ

  • ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ

  • Architecture

    • Layered Architecture

    • Flux Architecture

      • MVC ํŒจํ„ด

  • External Store

๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ

๐Ÿ“– ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ(Separation of Concerns)

์ปดํ“จํ„ฐ ํ”„๋กœ๊ทธ๋žจ์„ ๊ตฌ๋ณ„๋œ ๋ถ€๋ถ„์œผ๋กœ ๋ถ„๋ฆฌ์‹œํ‚ค๋Š” ๋””์ž์ธ ์›์น™์œผ๋กœ, ๊ฐ ๋ถ€๋ฌธ์€ ๊ฐœ๊ฐœ์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค.

ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ(ํ˜น์€ ํ•จ์ˆ˜)๊ฐ€ ์—ฌ๋Ÿฌ๊ฐ€์ง€์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด ๋ณต์žก์„ฑ์ด ๋†’์•„์ง€๋‹ˆ ๊ด€์‹ฌ์‚ฌ๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ํ•˜๋‚˜์˜ ๊ด€์‹ฌ์‚ฌ๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์„(์—ญํ™œ๋งŒ) ๊ฐ€์ง€๋„๋ก ๊ตฌ์„ฑํ•œ๋Š” ๊ฒƒ

๐Ÿค” ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ์ด์œ ๋Š”?!?

ํ•˜๋‚˜์˜ ํ”„๋กœ๊ทธ๋žจ์€ ์ž‘์€ ๋ถ€ํ’ˆ์ด ๋ชจ์—ฌ์„œ ๋งŒ๋“ค์–ด์ง„๋‹ค. React๋Š” ์ž‘์€ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์กฐ๋ฆฝํ•˜์—ฌ ๋” ํฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜ํƒ€ ๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ๊ธฐ๋Šฅ์„ ์œ„์ฃผ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค.

-  App
  - Header
  - Main
    - Greeting
    -  Counter
    -  Posts
        - PostForm
            - TextField 
  -  Footer

์ด๋Ÿฐ ์‹์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ? ์„œ๋กœ์˜ ๊ด€์‹ฌ์‚ฌ๊ฐ€ ๋‹ค๋ฅด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, App์—์„œ๋Š” TextField๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค. ๊ฐ ๋ถ€๋ถ„์€ ํ•˜๋‚˜์˜ ์—ญํ• , ํ•˜๋‚˜์˜ ๊ด€์‹ฌ์‚ฌ๋กœ ๊ฒฉ๋ฆฌ ๋จ์œผ๋กœ์จ, ๋ณต์žก๋„๋ฅผ ๋‚ฎ์ถ”๊ฒŒ ๋œ๋‹ค.

Architecture

์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜(system Architecture)๋Š” ์‹œ์Šคํ…œ์˜ ๊ตฌ์กฐ, ํ–‰์œ„, ๋” ๋งŽ์€ ๋ทฐ๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฐœ๋…์  ๋ชจํ˜•์ด๋‹ค. ์‹œ์Šคํ…œ ๋ชฉ์ ์„ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์‹œ์Šคํ…œ์˜ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฌด์—‡์ด๋ฉฐ ์–ด๋–ป๊ฒŒ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š”์ง€, ์ •๋ณด๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ตํ™˜๋˜๋Š”์ง€๋ฅผ ์„ค๋ช…ํ•œ๋‹ค.

โ‡’ ์ฆ‰, ์„œ๋น„์Šค์˜ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ ์„ ์˜๋ฏธํ•œ๋‹ค.

์†Œํ”„ํŠธ์›จ์–ด ์•„ํ‚คํ…์ฒ˜

  • ์‹œ์Šคํ…œ์˜ ์ „์ฒด์ ์ธ ๊ตฌ์กฐ์™€ ๊ตฌ์„ฑ์š”์†Œ๋“ค ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ

์‹œ์Šคํ…œ์˜ ์ „์ฒด์ ์ธ ๋™์ž‘์„ ๊ฒฐ์ •ํ•˜๊ณ , ์‹œ์Šคํ…œ์˜ ํ’ˆ์งˆ ์„ฑ๋Šฅ(์„ฑ๋Šฅ, ํ™•์ž‘์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ๋ณด์•ˆ ๋“ฑ)์— ์ง์ ‘์ ์œผ๋กœ ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค.

Layered Architecture

  • ์†Œํ”„ํŠธ์›จ์–ด ์‹œ์Šคํ…œ์„ ๊ด€์‹ฌ์‚ฌ ๋ณ„๋กœ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ณ„์ธต๋กœ ๋ถ„๋ฆฌ(๊ณ„์ธตํ™”)ํ•œ ์•„ํ‚คํ…์ฒ˜

๊ฐ ๊ณ„์ธต์€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ ํŠน์ • ์—ญํ• ๊ณผ ์ฑ…์ž„์ด ์žˆ๋‹ค. ๊ทธ๋“ค์€ ์ž์‹ ์˜ ์—ญํ• ์—๋งŒ ์ง‘์ค‘ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ„ ๊ด€์‹ฌ์‚ฌ๊ฐ€ ๋ถ„๋ฆฌ๋˜์—ˆ๋‹ค๋Š” ์ ์ด๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์— ๋”ฐ๋ฅธ ํ๋ฆ„๋„

Layered Architecture ์žฅ์ 

  • ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ

    • ๊ฐ ๊ณ„์ธต์ด ๊ด€์‹ฌ์‚ฌ๋ณ„๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ

  • ๋ณ€ํ™”์— ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์ฒ˜

    • ๊ฐ ๊ณ„์ธต์ด ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœ,ํ™•์žฅ,๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

  • ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ

    • ๊ฐ ๊ณ„์ธต์ด ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋‚˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์šฉ์ดํ•˜๊ฒŒ ์ˆ˜ํ–‰

Layered Architecture ๋‹จ์ 

  • ์˜ค๋ฒ„ํ—ค๋“œ

    • ๊ณ„์ธต๊ฐ„์˜ ํ†ต์‹ ์„ ํ†ตํ•ด ๋™์ž‘ํ•˜๊ธฐ ๋–„๋ฌธ์—,(๋‹จ๋ฐฉํ–ฅ ์˜์กด์„ฑ) ๋ฐ์ดํ„ฐ์˜ ์ „๋‹ฌ ๋ฐ ๋ณ€ํ™” ๊ณผ์ •์—์„œ ์ผ๋ถ€ ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ

  • ๋ณต์žก์„ฑ

    • ๊ณ„์ธต๊ฐ„์˜ ํ†ต์‹ ์„ ์œ„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผํ•˜๋ฏ€๋กœ ๋ณต์žก์„ฑ์ด ์ฆ๊ฐ€

Flux Architecture

๐ŸŒŽ Flux Architecture ํƒ„์ƒ ๋ฐฐ๊ฒฝ

Facebook์—์„œ ์–˜๊ธฐํ•œ ๊ตฌ์กฐ์˜ ๋ณต์žก์„ฑ

๊ธฐ์กด์˜ 2-way binding(์–‘๋ฐฉํ–ฅ ๋ฐ”์ธ๋”ฉ)์„ ์ผ์„ ๋•Œ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋Š” Model-View์˜ ๋ณต์žกํ•œ ๊ด€๊ณ„(์ „ํ†ต์ ์ธ MVC์—์„  ์ด๋Ÿฐ ์ƒํ™ฉ์„ ์ง€์–‘)๋ฅผ ๊ฒจ๋ƒฅํ•˜์—ฌ Facebook(ํ˜„ Meta)์—์„œ MVC ํŒจํ„ด์˜ ํ•œ๊ณ„์— ๋Œ€์•ˆ์œผ๋กœ ๋‚ด์„ธ์šด ์•„ํ‚คํ…์ฒ˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

๐Ÿค” 2-way binding ์–‘๋ฐฉํ–ฅ ๋ฐ”์ธ๋”ฉ?

๐Ÿ“– ๋ฐ์ดํ„ฐ(Data Binding)์ด๋ž€? ํ™”๋ฉด์ƒ์— ๋ณด์—ฌ์ง€๋Š” ๋ฐ์ดํ„ฐ(View)์™€ ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ(Model)๋ฅผ ๋ฌถ์–ด์„œ(Binding) ์„œ๋กœ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋™๊ธฐํ™” ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

Javascript(Model)์™€ HTML(View) ์‚ฌ์ด์— ViewModel์ด ์กด์žฌํ•˜์—ฌ ํ•˜๋‚˜๋กœ ๋ฌถ์—ฌ์„œ(Binding)๋˜์–ด์„œ ๋‘˜ ์ค‘ ํ•˜๋‚˜๋งŒ ๋ณ€๊ฒฝ๋˜์–ด๋„ ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

HTML(View) ๐Ÿ” ViewModel ๐Ÿ” Javascript(Model)

Data Binding

๐Ÿค– Flux ์‹œ์Šคํ…œ์˜ ๊ตฌ์กฐ

Flux์˜ ์‹œ์Šคํ…œ ๊ตฌ์กฐ

unidirectional data flow(๋‹จ๋ฐฉํ–ฅ ํ๋ฆ„)์„ ๊ฐ€์ง€๋Š” Architecture์ด๋‹ค. ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์€ Dispatcher โ†’ Store โ†’ View ํ๋ฅด๊ณ  View์—์„œ ์ž…๋ ฅ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฉด Action์„ ํ†ตํ•ด ๋‹ค์‹œ Dispatcher๋กœ ํ–ฅํ•˜๊ฒŒ ๋œ๋‹ค.

  • Action : ๋ฐ์ดํ„ฐ ํ๋ฆ„์— ๋ณ€ํ™”๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•œ ๋™์ž‘ ๋ฐœ์ƒ โ†’ ์ด๋ฒคํŠธ/๋ฉ”์‹œ์ง€ ๊ฐ™์€ ๊ฐ์ฒด

  • Dispatcher : Action์„ ๋ฐœ์†กํ•˜๋Š” ์—ญํ™œ โ†’ (์—ฌ๋Ÿฌ)Store๋กœ Action์„ ์ „๋‹ฌ

  • Store : ๋ฐ์ดํ„ฐ๋“ค์„ ์ €์žฅํ•œ๋Š” ๊ณต๊ฐ„ โ†’ ๋ฐ›์€ Action์— ๋”ฐ๋ผ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ/์ƒํƒœ ๋ณ€๊ฒฝ์„ ์•Œ๋ฆผ.

    • Dispatcher๋ฅผ ํ†ตํ•ด ๋ณ€ํ™”๋œ ๋ฐ์ดํ„ฐ๊ฐ€ Store ์ €์žฅ

  • View : Store์˜ ์ƒํƒœ๋ฅผ ๋ฐ˜์˜, ๋˜ ๋‹ค๋ฅธ Action์„ ์ „๋‹ฌ ํ•ด์ค€๋‹ค.

External Store

External Store : (React ์ž…์žฅ์—์„œ) Store๊ฐ€ React์˜ ์•ˆ์— ์žˆ์ง€ ์•Š๋Š”๋‹ค๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

useState ๋กœ ์ƒํƒœ ๊ด€๋ฆฌ

Increase ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด count ๊ฐ€ 1์”ฉ ์ฆ๊ฐ€ํ•œ๋‹ค. ํ™”๋ฉด์—๋„ ์ฆ๊ฐ€๋˜๋Š” ์ƒํƒœ ๊ฐ’์ด ๋ณด์ธ๋‹ค.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>{count}</p>
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

๐Ÿšจ ์ผ๋ฐ˜์ ์ธ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•œ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œ ๋ฌธ์ œ ๋ฐœ์ƒ

useState ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ผ๋ฐ˜์ ์ธ ๋ณ€์ˆ˜๋กœ ์„ ์–ธ๋œ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ–ˆ์ง€๋งŒ ํ™”๋ฉด์— ๋ณ€ํ™”๋œ ์ƒํƒœ๊ฐ’์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.


import { useEffect } from 'react';

let count = 0;

export default function Counter() {

  useEffect(() => {
    console.log(count);
  });

  const handleClick = () => {
    count += 1;
    //console.log(count);
  };

  return (
    <div>
      <p>{count}</p>
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

useReducer๋ฅผ ํ†ตํ•ด (feat.forceUpdate)

Class ์ปดํฌ๋„ŒํŠธ๋ฅผ ์“ฐ๋˜ ์‹œ์ ˆ์—๋Š” forceUpdate ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๊ฐ•์ œ๋กœ ๋ฆฌ๋ Œ๋”๋ง์„ ํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Function ์ปดํฌ๋„ŒํŠธ๋Š” useReducer hook์„ ์‚ฌ์šฉํ•ด์„œ ์ƒํƒœ๋ฅผ ๋ฆฌ๋ Œ๋”๋ง ํ•จ์œผ๋กœ์„œ ํ•ด๊ฒฐ


import { useEffect, useReducer } from 'react';

let count = 0;

export default function Counter() {

  // const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
  const [, forceUpdate] = useReducer(x => x + 1, 0);

  useEffect(() => {
    console.log(count);
  });

  const handleClick = () => {
    count += 1;
    // ๊ฐ•์ œ๋กœ ๋ฆฌ๋ Œ๋”๋ง
    forceUpdate();
  };

  return (
    <div>
      <p>{count}</p>
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

import { useEffect, useReducer } from 'react';

let count = 0;

function reducer(state){
  // return state + 1;
  return {...state, x : state.x + 1 }
}

export default function Counter() {

  // const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
  // const [, forceUpdate] = useReducer(reducer, 0);
  const [, forceUpdate] = useReducer(reducer, {x:0});

  useEffect(() => {
    console.log(count);
  });

  const handleClick = () => {
    count += 1;
    // ๊ฐ•์ œ๋กœ ๋ฆฌ๋ Œ๋”๋ง
    forceUpdate();
  };

  return (
    <div>
      <p>{count}</p>
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

๐Ÿค” ๊ทธ๋Ÿฌ๋‚˜ Counter ์ปดํฌ๋„ŒํŠธ๋Š” ํ™”๋ฉด์„ ์—…๋ฐ์ด๋Š” ํ•˜๋Š” ์ƒํƒœ์™€ count๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ƒํƒœ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

CustomHook์œผ๋กœ ๋ถ„๋ฆฌ

ํ™”๋ฉด์„ ๋ Œ๋”๋ง ํ•˜๋Š” useState๋ฅผ CustomHook์„ ํ†ตํ•ด ๋ถ„๋ฆฌํ–ˆ๋”๋‹ˆ, React๊ฐ€ ๋”์ด์ƒ ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ์ƒํƒœ๋Š” ๊ด€๋ฆฌ ํ•˜์ง€ ์•Š๊ฒŒ ๋˜์—ˆ๋‹ค. ์ด๋Ÿฐ์‹์œผ๋กœ ๋งŒ๋“œ๋Š”๊ฒŒ External Store์˜ ๊ธฐ๋ณธ์ ์ธ ์•„์ด๋””์–ด๋‹ค.

// hooks/useForceUpdate.ts

import { useState} from 'react';

export default function useForceUpdate() {
  const [state, setState] = useState(0);

  const forceUpdate = () => {
    setState(state + 1);
  };

  return forceUpdate;
}
// components/Counter.tsx

import useForceUpdate from '../hooks/useForceUpdate';

let count = 0;

export default function Counter() {
  const forceUpdate = useForceUpdate();

  const handleClick = () => {
    count += 1;
    forceUpdate();
  };

  return (
    <div>
      <p>{count}</p>
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

External Store + ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ

import useForceUpdate from '../hooks/useForceUpdate';

// Business Logic 
const state = {
  count : 0,
};

function increase(){
  state.count += 1;
}

// UI
export default function Counter() {

  const forceUpdate = useForceUpdate();

  const handleClick = () => {
    increase();
    forceUpdate();
  };

  return (
    <div>
      <p>{state.count}</p>
      <button type="button" onClick={handleClick}>
        Increase
      </button>
    </div>
  );
}

์ด๋Ÿฐ ์ ‘๊ทผ์„ ํ†ตํ•ด์„œ React๊ฐ€ UI๋ฅผ ๋‹ด๋‹นํ•˜๊ณ , ์ˆœ์ˆ˜ํ•œ TypeScript(๋˜๋Š” Javascript)๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ ํ†ตํ•ด ์ž์ฃผ ๋ฐ”๋€Œ๋Š” UI ์š”์†Œ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ๋Œ€์‹ , ์˜ค๋ž˜ ์œ ์ง€๋˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ์œ ์ง€๋ณด์ˆ˜์— ๋„์›€์ด ๋˜๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์น˜๋ฐ€ํ•˜๊ฒŒ ์ž‘์„ฑ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ”— ์ฐธ๊ณ 

Last updated