1. ExternalStore

useState ๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ด๋ณด์ž

React์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์–ด๋–ค ๋ณ€์ˆ˜์˜ ๊ฐ’์ด ๋ณ€ํ™”ํ•˜์˜€์„ ๋•Œ(๋ณดํ†ต ์œ ์ €์˜ ์กฐ์ž‘์œผ๋กœ ์ธํ•œ), UI์— ํ•ด๋‹น ๊ฐ’์„ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” useStore ๋ผ๋Š” React์—์„œ ์ œ๊ณตํ•˜๋Š” hook์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ ‡์ง€ ์•Š๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•œ ํ›„์— ์–ด๋–ค ์ด๋ฒคํŠธ์— ๋”ฐ๋ผ์„œ ๊ฐ’์„ ๋ณ€ํ™”์‹œ์ผฐ์„ ๋•Œ๋Š” UI์— ํ•ด๋‹น ๊ฐ’์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์—†๋‹ค. ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

React์—์„œ๋Š” ๋กœ์ปฌ state, ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ๋ณ€์ˆ˜์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ด์„œ๋Š” ์•ˆ๋˜๋ฉฐ, useState๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅํ•œ๋‹ค.

let count = 0;

export default function Counter() {
  const handleClick = () => {
    count += 1;
  };

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

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

import { useState } from 'react';

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

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

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

๊ทธ๋Ÿฌ๋ฉด useState๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฐ•์ œํ•˜์—ฌ UI์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ? useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ€๋Šฅํ•˜๋‹ค.

import { useReducer } from 'react';

let count = 0;

function increase() {
  count += 1;
}

export default function Counter() {
  const [ignored, forceUpdate] = useReducer((x) => x + 1, 0);

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

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

๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ฆ๊ฐ€ํ•œ count ์˜ ํ™”๋ฉด์— ์ž˜ ๋ฐ˜์˜์ด ๋œ๋‹ค. ํฅ๋ฏธ๋กœ์šด ๊ฒƒ์€ useReducer ์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ ํ•จ์ˆ˜์—์„œ ํ•˜๊ณ ์žˆ๋Š” ์—ญํ• ์€ ์˜๋ฏธ๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. +1์„ ํ•ด๋„ ๋˜๊ณ , ๋นˆ๋ฌธ์ž์—ด์„ ๋”ํ•ด๋„ ๋œ๋‹ค. ์ฆ‰, ์—ฌ๊ธฐ์„œ useReducer ๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ›…์œผ๋กœ์จ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค๋Š” ์ ์ด๋‹ค.

์ฐธ๊ณ ๋กœ useState ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ useReducer๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ฝ”๋“œ๋ฅผ ๊ณ ์ณ๋ณด์ž.

import { useEffect, useReducer, useState } from 'react';

let count = 0;

function increase() {
  count += 1;
}

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

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

  useEffect(() => {
    console.log('test');
  });

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

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

useReducer๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , forceUpdate๋ผ๋Š” ํ•จ์ˆ˜ ์•ˆ์—์„œ setState ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ, ๊ฐ•์ œ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋ง ์‹œํ‚ฌ์ˆ˜๋„ ์žˆ๋‹ค. ํ™”๋ฉด์— ๋ณด์ด๋Š” ๊ฐ’์€ increase ํ•จ์ˆ˜์—์„œ ์ฒ˜๋ฆฌํ•œ ์ฆ๊ฐ€๋œ ๋ณ€์ˆ˜์˜ ๊ฐ’์ด๋‹ค. forceUpdate๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ Hook ์œผ๋กœ ๋งŒ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋ชจ์–‘์ด ๋œ๋‹ค.

// hooks/useForceUpdate.ts
import { useState } from 'react';

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

  const forceUpdate = () => {
    // useState ์˜ ๊ธฐ๋ณธ ๊ฐ’์„ 0์ด ์•„๋‹Œ object ๋กœ ์žก๊ณ , setState ์—์„œ object ๋ฅผ spread
    // ์—ฐ์‚ฐ์ž๋กœ destructing ํ•˜๋ฉด ์ƒˆ๋กœ์šด ๊ฐ์ฒด๊ฐ€ ๋˜๊ธฐ ๋–„๋ฌธ์—, ๋” ๋‹จ์ˆœํ•œ ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค.
    // setState({ ...state });

    // ์œ„์˜ ์ฝ”๋“œ ์ฃผ์„์ฒ˜๋ฆฌํ•˜๊ณ ... ์•„๋ž˜์™€ ๊ฐ™์ด๋งŒ ์จ์ค˜๋„, ์ƒˆ๋กœ์šด ๊ฐ์ฒด๊ฐ€ ๋˜์–ด ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์—ฌ,
    // ๋ฆฌ๋ Œ๋”๋ง ๋˜๋„๋ก ํ•œ๋‹ค.
    setState({});
  };

  return forceUpdate;
}

// component/Counter.tsx
import useForceUpdate from '../hooks/useForceUpdate';

let count = 0;

function increase() {
  count += 1;
}

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

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

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

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Counter ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ๋”์ด์ƒ useState ๊ฐ€ ํ•„์š”์—†์–ด์ง€๊ณ  ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋กœ์ปฌ ๋ณ€์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์—ฌ UI์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ํ›…์œผ๋กœ ๋ถ„๋ฆฌํ•œ useReducer ์˜ ํ•จ์ˆ˜ ์ธ์ž์—์„œ ๊ณ„์† setState ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด์„œ, ์˜๋ฏธ์—†๋Š” ๊ฐ’์˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์„œ useReducer ์˜ ์—ญํ• ์€ ๋‹จ์ˆœํžˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ•์ œ ๋ฆฌ๋ Œ๋”๋ง ํ•˜๋Š” ๊ฒƒ์ด๊ณ , useReducer ์˜ ํ•จ์ˆ˜ ์ธ์ž ๊ฐ’์—์„œ ๋ณ€๊ฒฝํ•˜๊ณ  ์žˆ๋Š” ์ƒํƒœ๋Š” ํŠน๋ณ„ํ•œ ์ผ์„ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค.

useForceUpdate ๋ฅผ ๋ฆฌํŽ™ํ† ๋งํ•ด๋ณด์ž.

// hooks/useForceUpdate.ts
import { useState, useCallback } from 'react';

export default function useForceUpdate() {
  const [, setState] = useState({});
  return useCallback(() => setState({}), []);
}

useCallback ์„ ์‚ฌ์šฉํ•˜์—ฌ, useCallback ์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ ํ•จ์ˆ˜๊ฐ€ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋„๋ก ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ, ์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š” ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง๊ณผ UI์— ๋Œ€ํ•œ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ๊ฐ€ ์ด๋ฃจ์–ด์กŒ๋‹ค.

Last updated