1. History πŸ‘©β€πŸ’»

  1. 인터넷 μ›Ήμ„œν•‘μ˜ 초창기의 λΈŒλΌμš°μ €λŠ” μ›Ή ν‘œμ€€μ΄ μ œλŒ€λ‘œ μ •μ˜λ˜μ§€ μ•Šμ•„ 크둜슀 λΈŒλΌμš°μ§• μ΄μŠˆκ°€ 개발의 κ°€μž₯ 큰 λΆ€λΆ„ 쀑 ν•˜λ‚˜μ˜€λ‹€.
  2. jQuery 의 λ“±μž₯으둜 크둜슀 λΈŒλΌμš°μ§• 이슈λ₯Ό 해결함은 λ¬Όλ‘ , 반볡적인 UI 처리 μž‘μ—…μ„ 라이브러리λ₯Ό 톡해 μ²˜λ¦¬ν•  수 있게 λ˜μ—ˆλ‹€.
  3. μ›Ήμ˜ νŠΉμ„±μƒ μˆ˜μ‹œλ‘œ μ„œλ²„μ˜ 데이터λ₯Ό λ°›μ•„ μž¬λ Œλ”λ§μ„ ν•΄μ€˜μ•Όν•œλ‹€. 이λ₯Ό μžλ™μœΌλ‘œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ Angular.JS 와 Backbone.JS λŠ” 데이터 바인딩을 λ„μž…ν•˜κ³ , μž¬μ‚¬μš©μ„ μœ„ν•΄ μ»΄ν¬λ„ŒνŠΈ λ‹¨μœ„ κ°œλ°œμ„ λ„μž…ν–ˆλ‹€.
  4. μ–‘λ°©ν–₯ 데이터 λ°”μΈλ”©μœΌλ‘œ μΈν•œ λΈŒλΌμš°μ €μ˜ μžμ› μ†Œλͺ¨ 및 ν”„λ ˆμž„μ›Œν¬λ³΄λ‹€ κ°€λ²Όμš΄ 라이브러리λ₯Ό λͺ©ν‘œλ‘œ 단방ν–₯ 데이터 바인딩이 κ°€λŠ₯ν•œ React κ°€ 생겨났닀. λ Œλ”λ§μ˜ μ„±λŠ₯ μ €ν•˜κ°€ ν¬λ‹ˆ Virtual DOM을 μ‚¬μš©ν•΄ λΉ„κ΅ν•˜κ³  ν•œ λ²ˆμ— λ Œλ”λ§ν•˜λ„λ‘ ν•΄ μ»΄ν¬λ„ŒνŠΈ λ‹¨μœ„ κ°œλ°œμ— λΈŒλΌμš°μ§• μ„±λŠ₯을 λ†’μ˜€λ‹€. λ¦¬μ•‘νŠΈμ˜ 영ν–₯으둜 Angular.JS λŠ” 버전2둜 λ„˜μ–΄μ˜€λ©° Angular λΌλŠ” μ΄λ¦„μœΌλ‘œ μž¬νƒ„μƒν–ˆκ³ , Angular 의 ν”„λ ˆμž„μ›Œν¬μ˜ μž₯점과 React 의 μž₯점을 λͺ¨λ‘ κ°–λŠ” 라이브러리의 κ°œλ°œμ„ λͺ©ν‘œλ‘œ Vue.JS κ°€ 생겨났닀.
  5. Svelte 와 같은 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λ‹€μ‹œ μ»΄ν“¨νŒ… μ„±λŠ₯ 상ν–₯ ν‰μ€€ν™”λ‘œ 인해 Virtual DOM 을 μ“°λŠ”κ²Œ 였히렀 느릴 수 있으며, κΈΈκ³  λ³΅μž‘ν•œ 문법듀을 μ΅œλŒ€ν•œ κ°„μ†Œν™” ν•˜κ³ μž ν•˜λŠ” λͺ©μ μœΌλ‘œ 생겨났닀.


Npm Trends

ν•˜μ§€λ§Œ 졜근 νŠΈλ Œλ“œλ₯Ό 보면 μ—¬μ „νžˆ React λŠ” 압도적인 μ μœ μœ¨μ„ λ³΄μ—¬μ€Œκ³Ό λ™μ‹œμ— μœ μΌν•˜κ²Œ κΎΈμ€€ν•œ μƒμŠΉμ„ ν•˜κ³  μžˆμŒμ„ μ•Œ 수 μžˆλ‹€. μ§€κΈˆκΉŒμ§€, 그리고 μ•žμœΌλ‘œλ„ κ°€κΉŒμš΄ λ―Έλž˜μ—λŠ” μ—¬μ „νžˆ React κ°€ μŠΉμžλΌλŠ” 것을 μ•Œ 수 μžˆλ‹€.


2. Limitation of Class Components πŸ‘©β€πŸ’»

클래슀 μ»΄ν¬λ„ŒνŠΈμ˜ μ£Όμš” λ¬Έμ œμ μ„ μ •λ¦¬ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

  • Boilerplate Code : λ‹€λ₯Έ 언어와 같은 Class κ°€ μ‹€μ œλ‘œλŠ” JavaScript 에 μ‘΄μž¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— 이λ₯Ό 톡해 κ΅¬ν˜„ν•œ 클래슀 μ»΄ν¬λ„ŒνŠΈ μ—­μ‹œ λ§Žμ€ Boilerplate Code λ₯Ό ν•„μš”λ‘œ ν•œλ‹€.
  • this 바인딩 : JavaScript 의 νŠΉμ΄ν•œ this λ™μž‘ 방식에 λŒ€ν•œ 이해λ₯Ό ν•„μš”λ‘œ 함에 따라 잘λͺ»λœ 코딩을 ν•˜λŠ” κ²½μš°κ°€ λ§Žλ‹€.
  • 비동기 μ½œλ°±μ‹œ κ°’ λ³€μ‘° : instance μž¬ν™œμš©μœΌλ‘œ μΈν•œ 비동기 ν•¨μˆ˜ λ‚΄ props, state 의 값이 변쑰될 수 μžˆλ‹€.

1. this binding of Event call

class MatchPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentCard: createMockCard(),
      matches: [],
    };
  }

  next() {
    this.setState({
      ...this.state,
      currentCard: createMockCard(),
    });
  }

  like() {
    this.next();

    checkIsMatched().then(({ data: { isMatched } }) => {
      if (isMatched) {
        this.setState({
          ...this.state,
          matches: [this.state.currentCard, ...this.state.matches],
        });
      }
    });
  }

  render() {
    const { state: { currentCard, matches }, next, like } = this;
    const matchControllerProps = { next, like };

    return (
        <main style={commonStyles.flexCenter}>
          <section style={pageStyles.pageWrap}>
            <img src='/logo.png' alt='logo' style={pageStyles.logo} />
            <MatchCard style={commonStyles.flex1} card={currentCard} />
            <MatchController {...matchControllerProps} />
            <MatchList matches={matches}/>
          </section>
        </main>
    );
  }
}

JavaScript β€˜this’ μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜λ“―μ΄ μ΄λ²€νŠΈμ— μ˜ν•΄ ν˜ΈμΆœλ˜λŠ” 경우 thisκ°€ 이벀트 elements κ°€ ν˜ΈμΆœν•˜λŠ” 객체가 되기 λ•Œλ¬Έμ— this binding이 ν•„μš”ν•˜λ‹€. λ”°λΌμ„œ μƒμ„±μž ν•¨μˆ˜κ°€ Closures λ₯Ό 생성해 자기 μžμ‹ μ˜ properties κ°€ 항상 자기 μžμ‹ μ„ 가리킬 수 μžˆλ„λ‘ bindλ₯Ό μ‚¬μš©ν•΄ 영ꡬ적으둜 바인딩 λ˜λ„λ‘ constructor에 μ •μ˜ν•΄μ€€λ‹€.

class MatchPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentCard: createMockCard(),
      matches: [],
    };

    this.next = this.next.bind(this)
    this.like = this.like.bind(this)
  }
  //...
}

λ‹€λ₯Έ λ°©λ²•μœΌλ‘œλŠ” Lexical Scopeλ₯Ό κ°–λŠ” Arrow Functionsλ₯Ό μ‚¬μš©ν•˜λŠ” 것이닀.

class MatchPage extends Component {
  //...
  next = () => {
    this.setState({
      ...this.state,
      currentCard: createMockCard(),
    });
  }

  like = () => {
    this.next();

    checkIsMatched().then(({ data: { isMatched } }) => {
      if (isMatched) {
        this.setState({
          ...this.state,
          matches: [this.state.currentCard, ...this.state.matches],
        });
      }
    });
  }
  //...
}

이 λ¬Έμ œλŠ” JavaScript β€˜this’ μ—μ„œ μ‚΄νŽ΄λ³Έ κ²ƒμ²˜λŸΌ Object Literal λ‚΄λΆ€μ—μ„œ Arrow Functions λ₯Ό μ‚¬μš©ν•˜λŠ” 것에 λ¬Έμ œκ°€ μžˆλŠ” 것이지 Prototype을 μ‚¬μš©ν•˜κ±°λ‚˜ ES6 Class문법을 μ‚¬μš©ν•  경우 일일해 bind()λ₯Ό ν•˜λŠ” 것 보닀 Arrow Functionsλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€.

2. Value Mutation in Asynchronous Callback

클래슀 μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” 비동기 콜백이 ν¬ν•¨λœ 경우 ν˜„μž¬ state 값을 ν•„μš”λ‘œ ν•˜λŠ”λ° 비동기 콜백의 κ°’μœΌλ‘œ state κ°€ λ³€μ‘°λ˜λŠ” λ¬Έμ œκ°€ μžˆλ‹€. 즉, Like λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ ν˜„μž¬ 이미지에 μ’‹μ•„μš”λ₯Ό μ²˜λ¦¬ν•œ λ‹€μŒ λ‹€μŒ 사진을 κ°€μ Έμ™€μ•Όν•˜λŠ”λ° λ‹€μŒ 사진을 κ°€μ Έμ˜€λŠ” 비동기 콜백이 state λ₯Ό λ³€κ²½ν•˜κΈ° λ•Œλ¬Έμ— μ’‹μ•„μš”κ°€ λ³€κ²½λœ state 값을 μ΄μš©ν•΄ μ’‹μ•„μš”λ₯Ό μ²˜λ¦¬ν•΄λ²„λ¦¬λŠ” λ¬Έμ œλ‹€. 이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ©”μ„œλ“œ μ •μ˜λ₯Ό render() λ©”μ„œλ“œ λ‚΄λΆ€λ‘œ μ΄λ™μ‹œν‚¨λ‹€. 참고둜 render λ‚΄λΆ€μ—μ„œ λ‹€μ‹œ bind λ₯Ό ν•˜μ§€ μ•ŠκΈ° μœ„ν•΄ ν•¨μˆ˜λŠ” Arrow Functions 둜 μ„ μ–Έν•œλ‹€.

class MatchPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentCard: createMockCard(),
      matches: [],
    };
  }

  render() {
    const { state: { currentCard, matches } } = this;

    const next = () => {
      this.setState({
        ...this.state,
        currentCard: createMockCard(),
      });
    }

    const like = () => {
      next();

      checkIsMatched().then(({ data: { isMatched } }) => {
        if (isMatched) {
          this.setState({
            ...this.state,
            matches: [currentCard, ...this.state.matches],
          });
        }
      });
    }

    const matchControllerProps = { next, like };

    return (
        <main style={commonStyles.flexCenter}>
          <section style={pageStyles.pageWrap}>
            <img src='/logo.png' alt='logo' style={pageStyles.logo} />
            <MatchCard style={commonStyles.flex1} card={currentCard} />
            <MatchController {...matchControllerProps} />
            <MatchList matches={matches}/>
          </section>
        </main>
    );
  }
}

참고둜 클래슀 μ»΄ν¬λ„ŒνŠΈλŠ” ν•¨μˆ˜ μžμ²΄κ°€ renderλ‹€.

클래슀 μ»΄ν¬λ„ŒνŠΈλŠ” λ¦¬νŒ©ν† λ§μ„ 톡해 render() ν•¨μˆ˜ λ‚΄ JSX μ½”λ“œλ₯Ό Stateless ν•˜λ„λ‘ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ‘œ λΆ„λ¦¬μ‹œν‚€λŠ” 것이 μ’‹λ‹€.

class MatchPageComponent extends Component {
  //...

  render() {
    //...
    const props = { currentCard, matches, next, like };

    return <MatchPage {...props} />
  }
}

const MatchPage = ({currentCard, matches, next, like}) => (
    <main style={commonStyles.flexCenter}>
      <section style={pageStyles.pageWrap}>
        <img src='/logo.png' alt='logo' style={pageStyles.logo}/>
        <MatchCard style={commonStyles.flex1} card={currentCard}/>
        <MatchController next={next} like={like}/>
        <MatchList matches={matches}/>
      </section>
    </main>
)

3. React Hooks Make It Easy to Create Stateless Components

React Hooks λ₯Ό μ‚¬μš©ν•˜λ©΄ μœ„μ™€ 같이 state 에 μ’…μ†λ˜λŠ” MatchPageComponent 와 MatchPage λ₯Ό λ‚˜λˆ„μ§€ μ•Šμ•„λ„ λœλ‹€.

const MatchPage = () => {
  const [currentCard, setCurrentCard] = useState(createMockCard())
  const [matches, setMatches] = useState([])

  const next = () => setCurrentCard(createMockCard())

  const like = () => {
    next();

    checkIsMatched().then(({data: {isMatched}}) => {
      if (isMatched) {
        setMatches(prevState => [currentCard, ...prevState])
      }
    });
  }

  return (
      <main style={commonStyles.flexCenter}>
        <section style={pageStyles.pageWrap}>
          <img src='/logo.png' alt='logo' style={pageStyles.logo}/>
          <MatchCard style={commonStyles.flex1} card={currentCard}/>
          <MatchController next={next} like={like}/>
          <MatchList matches={matches}/>
        </section>
      </main>
  )
}

3. React Hooks Action πŸ‘©β€πŸ’»

클래슀 μ»΄ν¬λ„ŒνŠΈμ˜ ν•œκ³„λ₯Ό κ·Ήλ³΅ν•˜λŠ” κ³Όμ •μ—μ„œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμ— React Hooks κ°€ λ„μž…λ˜λ©΄μ„œ ν•΄κ²°ν•œ 문제점 μ—μ„œ ν•˜λ‚˜μ˜ μ»΄ν¬λ„ŒνŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄μ„œ μƒνƒœκ΄€λ¦¬λ₯Ό λΆ„λ¦¬μ‹œν‚€κ³ , 좔상화 μ‹œν‚¨ useState 훅은 μ‚΄νŽ΄λ³΄μ•˜λ‹€. 이 κ³Όμ •μ—μ„œ μƒκ²¨λ‚œ React Hooks κ°€ μ–΄λ–€ λ°©μ‹μœΌλ‘œ λ™μž‘ν•˜λŠ”μ§€ μ•Œμ•„μ•Ό λ¦¬μ•‘νŠΈ μ΅œμ ν™”λ₯Ό μ΄ν•΄ν•˜κ³  이에 λ°©ν•΄λ˜μ§€ μ•Šλ„λ‘ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€.

const ReactDOM = (function () {
  let _container;
  let _elementOrComponent;

  function _render() {
    const elementTree = React.render();

    const run = (reactElement, parent) => {
      parent = parent ?? document.createElement('div');
      Object.entries(reactElement).forEach(([key, value]) => {
        // JSX νƒœκ·Έ
        if (
            key.includes('div') ||
            key.includes('button') ||
            key.includes('input')
        ) {
          const _el = document.createElement(key.replace(/[0-9].?/, ''));
          parent.appendChild(_el);
          if (typeof value === 'object') {
            run(value, _el);
          }
        } else if (key === 'text') {
          parent.innerHTML = parent.innerHTML += value;
        } else if (key === 'value') {
          parent.value = value;
        } else if (key === 'onClick') {
          parent.addEventListener('click', value);
        } else if (key === 'onChange') {
          parent.addEventListener('change', value);
        } else {
          if (typeof value === 'function') {
            // ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈμΌ 경우
            run(value(), parent);
          }
        }
      });
      return parent;
    };

    _container.innerHTML = '';
    _container.appendChild(run(elementTree, null));
  }

  return {
    render(elementOrComponent, container) {
      if (!container instanceof Element)
        throw new Error('ReactDOM should be rendered on DOM Element');

      _container = container;
      _elementOrComponent = elementOrComponent;
      React._setRenderer(ReactDOM);
      React._setElementOrComponent(elementOrComponent);

      _render();
    },
    _render,
  };
})();

const React = (function () {
  let _currentIndex = 0;
  let _hooks = []; // νŠΉμ • μ»΄ν¬λ„ŒνŠΈλ₯Ό μ»¨ν…Œμ΄λ„ˆλ‘œ states 듀을 μ €μž₯ν•˜λŠ” 배열을 closures 둜 κ°–λŠ”λ‹€.

  let _renderer = null;
  let _elementOrComponent = null;

  return {
    _setRenderer(renderer) {
      _renderer = renderer;
    },
    _setElementOrComponent(elementOrComponent) {
      _elementOrComponent = elementOrComponent;
    },
    render() {
      let result = null;
      // μ»΄ν¬λ„ŒνŠΈ μΈμŠ€ν„΄μŠ€ λ˜λŠ” ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ μ‹€ν–‰ ...
      if (typeof _elementOrComponent !== 'function') {
        return _elementOrComponent;
      }
      const component = _elementOrComponent();

      if (component.render !== undefined) {
        result = component.render(); // 클래슀 μ»΄ν¬λ„ŒνŠΈλŠ” render λ©”μ„œλ“œλ₯Ό μ‹€ν–‰
      } else {
        result = component; // ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλŠ” μ»΄ν¬λ„ŒνŠΈ μžμ²΄κ°€ render
      }

      // μ»΄ν¬λ„ŒνŠΈλ₯Ό μƒˆλ‘œ 그릴 λ•Œλ§ˆλ‹€ useState λ₯Ό λ‹€μ‹œ μˆ˜ν–‰ν•œλ‹€.
      // 각 states κ°€ μˆœμ„œλŒ€λ‘œ μžμ‹ μ˜ _hooks 의 λ°°μ—΄ index λ₯Ό 갖도둝 λ Œλ”λ§μ΄ λλ‚˜λ©΄ 인덱슀λ₯Ό μ΄ˆκΈ°ν™” ν•΄μ•Όν•œλ‹€.
      _currentIndex = 0;

      return result;
    },
    useState(initialValue) {
      const currentIndex = _currentIndex;
      _hooks[currentIndex] = _hooks[currentIndex] ?? initialValue; // μ΄ˆκΈ°κ°’ 없을 경우 ν• λ‹Ή

      const setState = (cbOrValue) => {
        if (typeof cbOrValue === 'function') {
          _hooks[currentIndex] = cbOrValue(_hooks[currentIndex]);
        } else {
          _hooks[currentIndex] = cbOrValue;
        }
        // state κ°€ λ³€κ²½λ˜λ©΄ 항상 render λ₯Ό ν˜ΈμΆœν•˜λ„λ‘ ν•œλ‹€.
        // Observable 의 notify 역할을 ν•˜λ©° 항상 render μ—κ²Œ μ•Œλ¦¬λ„λ‘ λ‚΄λΆ€μ μœΌλ‘œ μ •μ˜λ₯Ό ν•˜λŠ” 것이닀.
        _renderer._render();
      };
      _currentIndex++;

      return [_hooks[currentIndex], setState];
    },
    useEffect(cb, dependencies) {
      const prevDependencies = _hooks[_currentIndex];
      const isChanged =
          prevDependencies &&
          !dependencies.every((el, i) => el === prevDependencies[i]);
      // 졜초 λ Œλ”λ§ || states κ°€ λ³€ν•œ 경우
      if (isChanged || !prevDependencies) {
        cb();
        // states λ₯Ό λͺ¨λ‘ _hooks 에 μ €μž₯ν•œ ν›„ λ§ˆμ§€λ§‰ μΈλ±μŠ€μ— states λ₯Ό 담은 dependency 배열을 μ €μž₯ν•œλ‹€.
        _hooks[_currentIndex] = dependencies;
      }
      _currentIndex++; // useState 와 λ§ˆμ°¬κ°€μ§€λ‘œ useEffect 도 μ—¬λŸ¬ 개 μ„ μ–Έν•  수 μžˆμœΌλ‹ˆ index λ₯Ό μ¦κ°€μ‹œν‚¨λ‹€.
    },
  };
})();

const Greeting = () => ({
  div: {
    text: 'Hello World!!!!!!!!!!',
  },
});

const App = () => {
  const [foo, setFoo] = React.useState(0);
  const [bar, setBar] = React.useState(0);
  const [baz, setBaz] = React.useState('');

  React.useEffect(() => {
    console.log('something changed!!?!?', foo, bar, baz);
  }, [foo, baz]);
  /*
  useEffect κ°€ states foo, baz λ₯Ό λ°°μ—΄λ‘œ κ°–λŠ” [foo, baz] λ₯Ό 
  dependencies 둜 μΆ”κ°€ν•˜κΈ° μ „ _hooks λŠ” λ‹€μŒκ³Ό κ°™λ‹€.
  _hooks = [foo, bar, baz];

  useEffect κ°€ dependency [foo, baz] λ₯Ό μΆ”κ°€ν•˜λ©΄ 이제 _hooks λŠ” λ‹€μŒκ³Ό κ°™λ‹€.
  _hooks = [foo, bar, baz, [foo, baz]];      
  */

  /*
  <div>
    <div>
      foo : {foo} + {"  "}
      <button onclick={ () => setFoo((val) => val + 1) }>foo + 1</button>
    </div>
    <>
      bar : {bar} + {"  "}
      <button onclick={ () => setBar((val) => val + 1) }>bar + 1</button>
    </div>
    <div>
      baz : {baz} + {"  "}
      <input value={baz} onChange={ (event) => setBaz(event.target.value) } />
    </div>
    <Greeting />
  </div>
  */
  const reactElement = {
    div: {
      div1: {
        text: `foo : ${foo}  `,
        button: {
          onClick: () => setFoo((val) => val + 1),
          text: 'foo + 1',
        },
      },
      div2: {
        text: `bar : ${bar}  `,
        button: {
          onClick: () => setBar(bar + 1),
          text: 'bar + 1',
        },
      },
      div3: {
        text: 'onChange  ',
        input: {
          value: baz,
          onChange: (event) => setBaz(event.target.value),
        },
      },
      Greeting,
    },
  };

  return reactElement;
};

ReactDOM.render(App, document.getElementById('root'));

μ—¬κΈ°μ„œ μ€‘μš”ν•œ 것은 μ»΄ν¬λ„ŒνŠΈλΌλŠ” ν•¨μˆ˜ μ»¨ν…Œμ΄λ„ˆ μ•ˆμ— Closures λ₯Ό μ‚¬μš©ν•΄ States λ₯Ό κ΄€λ¦¬ν•œλ‹€λŠ” 것이닀.

ν•˜μ§€λ§Œ μœ„ 예제 μ½”λ“œλŠ” λŒ€λž΅μ μΈ κ°œλ…μΌ 뿐 μ‹€μ œ λ¦¬μ•‘νŠΈλŠ” μœ„μ™€ 같이 λ‹¨μˆœν•˜κ²Œ μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. μœ„ 예제 μ½”λ“œλŠ” 항상 λͺ¨λ“  λ¦¬μ•‘νŠΈ 트리λ₯Ό λ‹€μ‹œ λ Œλ”λ§ ν•  뿐 μ•„λ‹ˆλΌ 값이 μ‹€μ œλ‘œ λ³€ν•˜λŠ”μ§€ 상관 없이 μ΄λ²€νŠΈκ°€ λ°œμƒν•  경우 λ‹€μ‹œ λ Œλ”λ§μ„ ν•˜λŠ” ν˜„μƒμ„ 확인할 수 μžˆλ‹€. 이전 κ°’κ³Όμ˜ λΉ„κ΅λŠ” 였직 useEffect μ—μ„œλ§Œ 처리되고 μžˆλŠ”λ°, 이 λ§ˆμ €λ„ root μ»΄ν¬λ„ŒνŠΈ 자체λ₯Ό λͺ¨λ‘ μƒˆλ‘œ κ·Έλ €λ‚Έλ‹€.

ν•˜μ§€λ§Œ μ‹€μ œ λ¦¬μ•‘νŠΈλŠ” Fiber 와 같은 Reconciliation 엔진도 μ‘΄μž¬ν•˜κ³ , 더 μ‰½κ²Œ, 더 μ•ˆμ „ν•˜κ²Œ μ½”λ“œ μž‘μ„±μ„ ν•  수 μžˆλ„λ‘ λ§Žμ€ κΈ°μˆ λ“€μ΄ ν¬ν•¨λ˜μ–΄μžˆλ‹€. λΉ„μŠ·ν•˜κ²Œ λ™μž‘ν•˜μ§€λ§Œ μœ„ 예제 μ½”λ“œμ—μ„œ κ°–λŠ” λ¬Έμ œμ μ€ 웹이 컀질수둝 μ„±λŠ₯ μ €ν•˜κ°€ μ‹¬κ°ν•˜κ²Œ λ°œμƒν•  κ²ƒμž„μ„ μ•Œ 수 μžˆλ‹€. λ¦¬μ•‘νŠΈμ— λ Œλ”λ§ μ΅œμ ν™”κ°€ ν•„μš”ν•œ μ΄μœ λ‹€.


4. Render Optimization πŸ‘©β€πŸ’»

1. SCU and Virtual DOMs Equivalent

shouldComponentUpdate In Action

μœ„ κ·Έλ¦Όμ—μ„œ κΈ€μ”¨λ‚˜ 그림의 녹색은 true λ₯Ό λ‚˜νƒ€λ‚΄λ©°, 빨간색은 false λ₯Ό λ‚˜νƒ€λ‚Έλ‹€. SCU와 vDOMEqλΌλŠ” 2개의 μ§€ν‘œκ°€ μžˆλ‹€. SCUλŠ” shouldComponentUpdate 둜 μ»΄ν¬λ„ŒνŠΈκ°€ μ—…λ°μ΄νŠΈ λ˜μ–΄μ•Ό ν•˜λŠ”μ§€λ₯Ό λ‚˜νƒ€λ‚Έλ‹€. λ”°λΌμ„œ 녹색이면 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ—…λ°μ΄νŠΈ 해야함을 λ‚˜νƒ€λ‚Έλ‹€. 반면 vDOMEqλŠ” Virtual DOM 이 이전과 같은지λ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ 빨간색이면 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ—…λ°μ΄νŠΈλ₯Ό 해야함을 λ‚˜νƒ€λ‚Έλ‹€.

  • C1 : SCU κ°€ true λ₯Ό, vDOMEs κ°€ false λ₯Ό λ°˜ν™˜ν–ˆμœΌλ―€λ‘œ μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈλ₯Ό ν•΄μ•Όν•œλ‹€.
  • C2 : SCU κ°€ false λ₯Ό λ°˜ν™˜ν–ˆκΈ° λ•Œλ¬Έμ— μ»΄ν¬λ„ŒνŠΈλ₯Ό μ—…λ°μ΄νŠΈ ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • C4, C5 : C2 의 SUC κ°€ false μ΄λ―€λ‘œ 더이상 탐색을 ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • C3 : SCU κ°€ true λ₯Ό, vDOMEs κ°€ false λ₯Ό λ°˜ν™˜ν–ˆμœΌλ―€λ‘œ μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈλ₯Ό ν•΄μ•Όν•œλ‹€.
  • C6 : SCU κ°€ true λ₯Ό, vDOMEs κ°€ false λ₯Ό λ°˜ν™˜ν–ˆμœΌλ―€λ‘œ μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈλ₯Ό ν•΄μ•Όν•œλ‹€.
  • C7 : SCU κ°€ false λ₯Ό λ°˜ν™˜ν–ˆκΈ° λ•Œλ¬Έμ— μ»΄ν¬λ„ŒνŠΈλ₯Ό μ—…λ°μ΄νŠΈ ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • C8 : SCU κ°€ true λ₯Ό λ°˜ν™˜ν–ˆμ§€λ§Œ vDOMEs κ°€ true λ₯Ό λ°˜ν™˜ν–ˆμœΌλ―€λ‘œ μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈ ν•˜μ§€ μ•ŠλŠ”λ‹€.

λ¦¬μ•‘νŠΈμ˜ λ Œλ”λ§ μ΅œμ ν™”μ—μ„œ λ°”λ‘œ 이 C8 이 ν₯미둜운 뢀뢄이닀. 이둜써 λ¦¬μ•‘νŠΈλŠ” C1, C3, C6 만 re-render λ₯Ό μˆ˜ν–‰ν•œλ‹€. 즉, SCU && !vDOMEsκ°€ true 일 κ²½μš°μ—λ§Œ μ΅œμ’…μ μœΌλ‘œ μ»΄ν¬λ„ŒνŠΈμ˜ re-render λ₯Ό μˆ˜ν–‰ν•œλ‹€λŠ” 것을 μ•Œ 수 μžˆλ‹€.

2. Prevent Unnecessary Reconciliation

λ¦¬μ•‘νŠΈλ‚˜ λ·°λŠ” 기본적으둜 μ•΅κ·€λŸ¬λ³΄λ‹€λ„ λΉ λ₯΄κΈ° λ•Œλ¬Έμ— λŒ€λΆ€λΆ„μ˜ μ΅œμ ν™”λ₯Ό 크게 κ³ λ €ν•˜μ§€ μ•Šμ•„λ„ 300ms μ΄ν•˜μ˜ λ°˜μ‘ μ†λ„λ‘œ 잘 μž‘λ™ν•œλ‹€.

ν•˜μ§€λ§Œ 졜근 λΈŒλΌμš°μ €κ°€ ν•  수 μžˆλŠ” 일이 λ§Žμ•„μ§€λ©° three.js와 같은 데이터 μ‹œκ°ν™”, Google Analytics 와 같은 λŒ€λŸ‰μ˜ 데이터λ₯Ό μ²˜λ¦¬ν•΄μ•Όν•˜κ³  κΈ°λŠ₯이 많이 ν•„μš”ν•œ λ°±μ˜€ν”ΌμŠ€, 둜그 데이터 μ‹œκ°ν™”μ—μ„œ Fiber κ°€ ν•΄λ‹Ή λΌμ΄λΈŒλŸ¬λ¦¬μ— λŒ€ν•œ μ΅œμ ν™”λ₯Ό μ§€μ›ν•˜μ§€ λͺ» ν•  경우 reconciliation 에 λ§Žμ€ μ‹œκ°„μ΄ μ†Œμš”λ  수 μžˆμ–΄ 직접 μ΅œμ ν™”λ₯Ό ν•΄μ•Όν•˜λŠ” κ²½μš°κ°€ λ°œμƒν•  수 μžˆλ‹€.

μ„œλΉ„μŠ€ 규λͺ¨κ°€ 컀지기 μ „μ—λŠ” μ΅œμ ν™”μ— λ¬Έμ œκ°€ μžˆλŠ” μ½”λ“œλΌ ν•˜λ”λΌλ„ λΉ λ₯΄κ²Œ μž‘λ™ν•  수 μžˆλ‹€. ν•˜μ§€λ§Œ μ„œλΉ„μŠ€κ°€ 컀지고 λ¬Έμ œκ°€ 생긴 ν›„ 이λ₯Ό κ³ μΉ  λ•Œ λ„ˆλ¬΄ λ§Žμ€ μ‹œκ°„μ΄ μ†Œμš”λ  수 μžˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— 가급적 λ¦¬μ•‘νŠΈ λ Œλ”λ§ μ΅œμ ν™”λ₯Ό μ΄ν•΄ν•˜κ³  쒋은 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ„λ‘ μŠ΅κ΄€μ„ λ“€μ΄λŠ” 것이 μ€‘μš”ν•˜λ‹€.


1 ) ν•˜μœ„ μ»΄ν¬λ„ŒνŠΈλ‘œ μ „λ‹¬μ‹œ Reference μœ μ§€ν•˜κΈ°

useMemo, useCallback을 μ μš©ν•œλ‹€. λ―Έμ μš©μ‹œ ν•˜μœ„ μ»΄ν¬λ„ŒνŠΈκ°€ 맀번 μƒˆ Reference λ₯Ό λ°›μ•„ λΆˆν•„μš”ν•œ λ Œλ”λ§μ΄ λ°œμƒν•  수 μžˆλ‹€. 특히 Custom Input 에 onChange λ₯Ό 전달할 λ•Œ, memoization 을 μ²˜λ¦¬ν•˜μ§€ μ•ŠμœΌλ©΄ re-rendering 될 ν•„μš”κ°€ μ—†λŠ” μžμ‹ μ»΄ν¬λ„ŒνŠΈ λͺ¨λ‘κ°€ λ‹€μ‹œ λ Œλ”λ§λœλ‹€.


2 ) Object μƒνƒœ 비ꡐ

비ꡐ해야할 λŒ€μƒμ˜ νƒ€μž…μ΄ Reference Types 일 경우 Shallow Equality λ₯Ό 비ꡐ할건지, Deep Equality λ₯Ό 비ꡐ할건지 잘 νŒλ‹¨ν•΄μ•Όν•œλ‹€.

Bad Case

const {
  markers,
  mapConfig: { markerScale, markerTextScale: textScale },
} = useSelector((state) => state);


Good Case 1

const markers = useSelector((state) => state.markers);
const markerScale = useSelector((state) => state.mapConfig.markerScale);
const textScale = useSelector((state) => state.mapConfig.markerTextScale);

Good Case 2

const { markers, markerScale, textScale } = useSelector(
    (state) => ({
      markers: state.markers,
      markerScale: state.mapConfig.markerScale,
      textScale: state.mapConfig.markerTextScale,
    }),
    shallowEqual,
);

useSelector에 Object 와 같은 Reference Types λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” Good Case 1 처럼 각 μ›μ‹œκ°’λ³„λ‘œ 뢄리해 μ‚¬μš©ν•˜κ±°λ‚˜, Good Case 2 와 같이 각 μ›μ‹œκ°’μ„ Object 둜 μΆ”μΆœν•œ λ‹€μŒ λ°˜ν™˜ν•˜κ³  shallowEqualλ₯Ό μ μš©ν•΄μ•Όν•œλ‹€. 그렇지 μ•ŠμœΌλ©΄ λ¦¬μŠ€νŠΈμ™€ 같은 것을 λ Œλ”λ§ ν•  λ•Œ 무쑰건 λ Œλ”λ§μ„ μ‹€ν–‰ν•΄ μ‹¬κ°ν•œ μ„±λŠ₯ μ €ν•˜λ₯Ό μ•ΌκΈ°ν•  수 μžˆλ‹€.


또 λ‹€λ₯Έ Bad Case 의 μ˜ˆλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

Bad Case

const App = () => {
  // ...
  const { map, view, vectorSource } = useContext(OlContext);

  useEffect(() => {
    map.setTarget("map");

    view.setZoom(12);
    view.setCenter(transform(SEOUL_CITY_HALL_LONLAT, "EPSG:4326", "EPSG:3857"));
  }, [map, view]);

  markers.forEach((marker) => {
    console.log(`render ${marker.id} marker`);
    renderMarker(marker, vectorSource, { markerScale, textScale });
  });
  //...

  return (
      <>
        <div id="map" style={styles.MapRoot}></div>
      </>
  );
};

μœ„ μ½”λ“œλŠ” 지도에 마컀λ₯Ό ν•˜λ‚˜ μΆ”κ°€ν•  λ•Œλ§ˆλ‹€ μ»΄ν¬λ„ŒνŠΈκ°€ re-rendering 되기 λ•Œλ¬Έμ— 전체 마컀λ₯Ό μƒˆλ‘œ κ·Έλ¦°λ‹€. λ§ˆμ»€κ°€ 500개 μžˆλŠ”λ° 1개λ₯Ό μΆ”κ°€ν•˜λ©΄ 501개λ₯Ό μƒˆλ‘­κ²Œ 그리게 λœλ‹€.


Good Case

const Marker = ({ marker }) => {
  const { vectorSource } = useContext(OlContext);
  const { markerScale, textScale } = useSelector(
      (state) => ({
        markerScale: state.mapConfig.markerScale,
        textScale: state.mapConfig.markerTextScale,
      }),
      shallowEqual,
  );

  useEffect(() => {
    console.log(`render ${marker.id} marker`);
    renderMarker(marker, vectorSource, { markerScale, textScale });
  }, [marker, vectorSource, markerScale, textScale]);

  return null;
};

const App = () => {
  // ...
  const { map, view } = useContext(OlContext);

  useEffect(() => {
    map.setTarget("map");

    view.setZoom(12);
    view.setCenter(transform(SEOUL_CITY_HALL_LONLAT, "EPSG:4326", "EPSG:3857"));
  }, [map, view]);

  // Good Case
  const markerList = markers.map((marker) => (
      <Marker key={marker.id} marker={marker} />
  ));
  
  //...

  return (
      <>
        <div id="map" style={styles.MapRoot}></div>
        {markerList}
      </>
  );
};

마컀λ₯Ό Marker μ»΄ν¬λ„ŒνŠΈλ‘œ λΆ„λ¦¬μ‹œν‚€κ³ , useEffect둜 감싸 Memoization μ²˜λ¦¬ν–ˆλ‹€. λ”°λΌμ„œ 마컀의 λ Œλ”λ§μ„ μ „λΆ€ ν•˜μœ„ μ»΄ν¬λ„ŒνŠΈμ— μœ„μž„ν•˜κ³ , μƒμœ„ μ»΄ν¬λ„ŒνŠΈμ˜ λ‹¨μˆœ μƒˆ reference μ£Όμ†Œ 변경에 λŒ€ν•΄ 영ν–₯을 받지 μ•Šλ„λ‘ μ²˜λ¦¬ν–ˆλ‹€. 이제 기쑴에 그렀진 500개의 λ§ˆμ»€λŠ” reference 만 λ°”λ€Œκ³  값이 λ³€κ²½λ˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ—(=dependencies 에 μžˆλŠ” 기쑴의 marker λŠ” κ·ΈλŒ€λ‘œ μœ μ§€) μƒˆλ‘­κ²Œ μΆ”κ°€λœ 마컀만 λ Œλ”λ§ ν•˜λ©΄ λœλ‹€.


3 ) Redux λ₯Ό λŒ€μ²΄ν•˜λŠ” λ‹€λ₯Έ μƒνƒœ 라이브러리 λ„μž…μ„ κ³ λ €

κ³Όλ„ν•˜κ²Œ λŠ˜μ–΄λ‚œ Redux μƒνƒœ νŠΈλ¦¬λŠ” λΆˆν•„μš”ν•œ UI Elements λ₯Ό κ΄€γ„Ήν•˜κ³  μžˆμ„ 수 있으며, Popup, Toast, Toggle Button κ³Ό 같은 μ „μ—­ UI 의 μƒνƒœλŠ” Context, Atomic State λ“±μœΌλ‘œ λΆ„λ¦¬ν•œλ‹€.

λ˜ν•œ Redux λ₯Ό λ‹€λ₯Έ μƒνƒœκ΄€λ¦¬ 라이브러리둜 κ΅μ²΄ν•˜λŠ” 것이 κ°€λŠ₯ν•˜λ‹€λ©΄, Recoil, Jotai, Zustand 와 같은 라이브러리λ₯Ό λ„μž…ν•΄ μ „νŒŒ λ°›λŠ” μƒνƒœλ₯Ό μ€„μ΄λŠ” 것이 μ’‹λ‹€. 단, μƒνƒœ 관리 라이브러리λ₯Ό κ΅μ²΄ν•˜λŠ” 것은 κ²°μ½” μ‰¬μš΄ μž‘μ—…μ΄ μ•„λ‹ˆλ―€λ‘œ μ‹ μ€‘ν•΄μ•Όν•œλ‹€.


4 ) PureComponent, React.memo

  • PureComponent λŠ” 더이상 ꢌμž₯λ˜μ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈλ‘œ λ³€κ²½ν•˜λŠ” 것이 μ’‹λ‹€.
  • React.memo λŠ” 자주 μ—…λ°μ΄νŠΈ λ˜λŠ” μ»΄ν¬λ„ŒνŠΈμ— μ μš©μ‹œ 였히렀 μ„±λŠ₯이 μ €ν•˜λœλ‹€. λ”°λΌμ„œ λ°˜λ“œμ‹œ ν•„μš”ν•œμ§€λ₯Ό κ²€ν† ν•΄μ•Όν•œλ‹€.

5. Redux πŸ‘©β€πŸ’»

1. Classification of the State Management

1 ) Flux-based

μ „μ—­, 단일 μ €μž₯μ†Œλ₯Ό μ‚¬μš©ν•˜λ©° μ΅œμƒμœ„ Provider μ»΄ν¬λ„ŒνŠΈμ˜ Action 싀행을 μ œμ™Έν•œ μƒνƒœ 변경을 μ œν•œν•œλ‹€.
단일 Store λ₯Ό κ°μ‹œν•˜λ―€λ‘œ λ””λ²„κΉ…μ˜ 기쀀이 Action 의 μ‹€ν–‰ => μ „μ—­ State λ³€κ²½μœΌλ‘œ μœ μΌν•˜λ‹€.

  • Redux: Flux + Reducer
  • Zustand: Flux + Context


2 ) Context-based

지역 μƒνƒœ μ „νŒŒλ₯Ό μ‚¬μš©ν•΄ Provider 의 μœ„μΉ˜λΌ μ΅œμƒμœ„μ— κ΅­ν•œλ˜μ§€ μ•ŠμœΌλ©° μžμœ λ‘­λ‹€.
단일 Store κ°€ μ•„λ‹ˆλ―€λ‘œ, Key λ₯Ό 톡해 μ–΄λ–€ state 인지λ₯Ό ν‘œν˜„ν•΄μ•Όν•˜λ©°, 디버깅 기쀀점을 λ³„λ„λ‘œ μ œκ³΅ν•΄μ•Όν•œλ‹€.

  • React Context API: id, displayName(optional)
  • Jotai: debugLabel(optional)
  • Recoil: State 생성 μ‹œμ μ— Key κ°•μ œ


3 ) Proxy-based

State μž¬ν• λ‹Ή 감지 및 μ „νŒŒλ₯Ό 직접 ν• λ‹Ή(state = newState)할지 Action 을 μ‚¬μš©ν• μ§€ 선택할 수 μžˆλ‹€.

  • MobX

2. Redux Middleware

Redux Middleware λŠ” dispatch λ₯Ό κ°μ‹œν•˜λŠ” μƒˆ dispatch ν•¨μˆ˜λ₯Ό ν™•μž₯ν•œ μƒˆ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄μ„œ μ‚¬μš©ν•œλ‹€(기쑴의 ν•¨μˆ˜λ₯Ό μˆ˜μ •ν•˜κ±°λ‚˜ μž¬μ •μ˜ ν•  경우 Monkeypatching λ¬Έμ œκ°€ λ°œμƒν•œλ‹€). 이λ₯Ό μœ„ν•΄μ„œ Redux Middleware λŠ” Currying 을 μ μš©ν•΄ 문제λ₯Ό ν•΄κ²°ν•œλ‹€.

λͺ¨λ“  Action 에 λŒ€ν•΄ log λ₯Ό λ‚¨κΈ°λŠ” 곡톡 λ‘œμ§μ„ λ§Œλ“œλŠ” 것을 κ°€μ •ν•΄λ³΄μž.


Bad Case - Monkeypatching 이 λ°œμƒν•œ dispatch

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
};


Good Case - Currying 을 μ μš©ν•΄ ν•¨μˆ˜λ₯Ό Chaining

const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
};

이제 μ–΄λ–€μ‹μœΌλ‘œ Middleware λ₯Ό λ§Œλ“€μ–΄μ•Όν•˜λŠ”μ§€ μ•Œμ•˜μœΌλ‹ˆ λͺ¨λ“  Action 에 λŒ€ν•΄ μ—λŸ¬λ₯Ό λ¦¬ν¬νŠΈν•˜λŠ” 곡톡 λ‘œμ§μ„ λ§Œλ“œλŠ” 것을 κ°€μ •ν•΄λ³΄μž.

const crashReporter = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Caught an exception!', err);
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    });
    throw err;
  }
}


Redux 의 applyMiddlewareλŠ” createStore()μ—μ„œ μž‘λ™ν•˜λ―€λ‘œ (...middlewares) => (createStore) => createStore의 νλ¦„μœΌλ‘œ μž‘λ™ν•œλ‹€. μœ„ 두 Middlewares λ₯Ό μ μš©ν•΄λ³΄μž.


import { createStore, combineReducers, applyMiddleware } from 'redux';

let createStoreWithMiddleware = applyMiddleware(logger, crashReporter)(createStore);

let todoApp = combineReducers(reducers);
let store = createStoreWithMiddleware(todoApp);

이제 Store λŠ” λͺ¨λ“  Action 의 흐름이 logger와 crashReporterλ₯Ό μ§€λ‚˜κ²Œ λœλ‹€.

store.dispatch(addTodo('Use Redux'));

λ‹€μ–‘ν•œ Middleware μ˜ˆμ‹œλŠ” Redux Middleware Examples μ—μ„œ 확인할 수 μžˆλ‹€. λ˜ν•œ Redux Middleware 에 λŒ€ν•œ GitHub μ„€λͺ…을 보면 λ―Έλ“€μ›¨μ–΄λŠ” 잠재적으둜 비동기적이기 λ•Œλ¬Έμ— Store 에 κΈ°λŠ₯을 μΆ”κ°€ν•˜κΈ° μœ„ν•΄ Composition Chain 을 ꡬ성할 λ•Œ 항상 μ•žμ— μœ„μΉ˜ν•΄μ•Όν•œλ‹€(First Store Enhancer)λŠ” κ·œμΉ™μ„ κ°–λŠ”λ‹€.

3. Redux Middleware Libraries

Redux λŠ” 동기 μž‘μ—…λ§Œ μ²˜λ¦¬ν•  수 μžˆλ‹€. ν•˜μ§€λ§Œ 웹은 ν†΅μ‹ μœΌλ‘œ μž‘λ™ν•˜κΈ° λ•Œλ¬Έμ— λ§Žμ€ API λŠ” λΉ„λ™κΈ°λ‘œ μž‘λ™ν•˜κ³ , 특히 μ„œλ²„μ™€μ˜ 톡신에 μžˆμ–΄ 비동기 μž‘μ—…μ€ ν•„μˆ˜λ‹€. 그리고 μ΄λŸ¬ν•œ 비동기 μž‘μ—…μ— ν•„μš”ν•œ 미듀웨어λ₯Ό λΌμ΄λΈŒλŸ¬λ¦¬ν™” ν•œ 것듀이 μ‘΄μž¬ν•˜λŠ”λ° λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ κ°–λŠ”λ‹€.

  • Redux-Saga: Generator 기반의 Redux 비동기 미듀웨어 라이브러리
  • Redux-Thunk: async/await 기반의 Redux 비동기 미듀웨어 라이브러리둜
  • Redux-Observable: Observable Sequence 둜 κ΅¬ν˜„λœ RxJS λ₯Ό 기반으둜 ν•˜λŠ” Redux 비동기 미듀웨어 라이브러리

Reference

  1. κ°•ν›ˆ, β€œν•œ λ²ˆμ— λλ‚΄λŠ” React의 λͺ¨λ“  것 초격차 νŒ¨ν‚€μ§€, Part 5. νŠΈλŸ¬λΈ”μŠˆνŒ…β€ fastcampus.co.kr. last modified unknown, Fast Campus.
  2. β€œλ―Έλ“€μ›¨μ–΄.” Redux Docs. accessed Dec. 19, 2023, Redux - 미듀웨어.
  3. β€œreduxjs/redux.”, Redux GitHub. accessed Dec. 19, 2023, Redux - applyMiddleware.