console.log์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ๋Š” toast ๋งŒ๋“ค๊ธฐ

console.log์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ๋Š” toast๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๊ณ ๋ฏผํ•œ ๊ณผ์ •์— ๋Œ€ํ•œ ๊ธฐ๋ก์ž…๋‹ˆ๋‹ค.

Profile Picture
milk717
2023-12-06

์ด ๊ฒŒ์‹œ๊ธ€์˜ ์›๋ณธ ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.

๊ณฐํ„ฐ๋ทฐ ์„œ๋น„์Šค์—์„œ๋Š” ์ข€ ๋” ๋‚˜์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด ํ† ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„์ž…ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ† ์ŠคํŠธ๋ฅผ ๋„์ž…ํ•  ์ˆ˜๋„ ์žˆ์—ˆ์ง€๋งŒ, ํ† ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋Š” ๊ฒƒ์€ ์ด์ „๋ถ€ํ„ฐ ์ •๋ง ํ•ด๋ณด๊ณ ์‹ถ์—ˆ๋˜ ์ž‘์—…์ด๋ผ์„œ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ํ† ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•  ๊ฒƒ์ธ๊ฐ€?

ํ† ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ์— ์•ž์„œ ์ œ๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ํ† ์ŠคํŠธ์˜ ์ •์˜๋ž€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ํ† ์ŠคํŠธ๋Š” ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜๋“ฏ์ด ๊ฐ„๋‹จํ•œ ๋ฌธ์ž์—ด๋งŒ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธฐ๋”๋ผ๋„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ† ์ŠคํŠธ๋Š” ๋‹ค๋ฅธ DOM ์š”์†Œ์— ๊ด€๊ณ„ ์—†์ด ์ผ์ •ํ•œ ์œ„์น˜์— ๋žœ๋”๋ง๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ† ์ŠคํŠธ๋Š” ์œ„์น˜์— ์ œ์•ฝ ์—†์ด ๋ชจ๋“  ๊ณณ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ† ์ŠคํŠธ๊ฐ€ ํ›…์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ๋‚˜ ํ›… ๋‚ด๋ถ€์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒ ์ฃ . console.log๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๊ณณ์ด๋ผ๋ฉด ํ† ์ŠคํŠธ ๋˜ํ•œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

1๋ฒˆ, 2๋ฒˆ ์กฐ๊ฑด์„ ๋งŒ์กฑ์‹œํ‚ค๋Š” ๊ฒƒ์€ ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ css๋งŒ ์กฐ๊ธˆ ์กฐ์ •ํ•˜๋ฉด ๋˜๋‹ˆ ๊ทธ๋‹ค์ง€ ์–ด๋ ต์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ 3๋ฒˆ ์กฐ๊ฑด์€ ์ข€ ๊นŒ๋‹ค๋กœ์šด๋ฐ์š”. ํ† ์ŠคํŠธ๋„ ๊ฒฐ๊ตญ ๋‚ด๋ถ€๋ฅผ ๋ณด๋ฉด ์ปดํฌ๋„ŒํŠธ์ธ๋ฐ, ์ปดํฌ๋„ŒํŠธ์˜ ๋žœ๋”๋ง์„ ์ปดํฌ๋„ŒํŠธ๋‚˜ ํ›…์ด ์•„๋‹Œ ๊ณณ์—์„œ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์ฃ . ๊ทธ๋ž˜์„œ ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋กœ ํ† ์ŠคํŠธ๋ฅผ ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ† ์ŠคํŠธ์˜ ๊ตฌ์กฐ

ํ† ์ŠคํŠธ๋ฅผ console.log์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด eventManger ๋„์ž…

ToastContainer์™€ ToastItem์€ ๊ทธ๋ƒฅ ์ปดํฌ๋„ŒํŠธ๊ฐ„ ๋ถ€๋ชจ ์ž์‹ ๊ด€๊ณ„๋ผ์„œ ์ต์ˆ™ํ•˜์ง€๋งŒ eventManger๊ฐ€ ์•ฝ๊ฐ„ ์ƒ์†Œํ• ํ…๋ฐ์š”. eventManger๋ฅผ ๋„์ž…ํ•œ ์ด์œ ๋Š” ์‚ฌ์šฉ์ฒ˜์™€ ์ƒ๊ด€ ์—†์ด toast.info("ํ† ์ŠคํŠธ์˜ ๋‚ด์šฉ")๊ณผ ๊ฐ™์€ ๋ฌธ๋ฒ•์œผ๋กœ ํ™”๋ฉด์— ๋žœ๋”๋ง๋˜๋Š” ์š”์†Œ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

ToastContainer์—์„œ ํ† ์ŠคํŠธ์˜ ์ถ”๊ฐ€, ์‚ญ์ œ์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋กœ์ ํŠธ์˜ ์–ด๋Š ๊ณณ์—์„œ๋“  toast ์ด๋ฒคํŠธ dispatch ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ด์„œ UI๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š”๊ฒƒ์ด์ฃ .

EventManger

import { EventManager } from '@foundation/Toast/type';

export const eventManager: EventManager = {
  list: new Map(), // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ €์žฅ
  emitQueue: new Map(), // ์ด๋ฒคํŠธ ์ง€์—ฐ์„ ์œ„ํ•œ ํ

  // ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
  on(event, callback) {
    this.list.has(event)
      ? this.list.get(event)!.push(callback)
      : this.list.set(event, [callback]);

    return this;
  },

  // ์ฝœ๋ฐฑ์ด ์žˆ๋Š”๊ฒฝ์šฐ ํ•ด๋‹นํ•˜๋Š” ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ, ์—†๋Š” ๊ฒฝ์šฐ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ๋ชจ๋“  ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ
  off(event, callback) {
    if (callback) {
      const cb = this.list.get(event)?.filter((cb) => cb !== callback);
      cb && this.list.set(event, cb);
      return this;
    }
    this.list.delete(event);

    return this;
  },

  //๋Œ€๊ธฐ์ค‘์ธ ์ด๋ฒคํŠธ๋ฅผ ์ทจ์†Œ์‹œํ‚ฌ ๋•Œ ํ•„์š”ํ•จ
  cancelEmit(event) {
    const timers = this.emitQueue.get(event);
    if (timers) {
      timers.forEach(clearTimeout);
      this.emitQueue.delete(event);
    }

    return this;
  },

  // ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ
  // ํƒ€์ž…๋ฌธ์ œ๋Š” ์ผ๋‹จ ๋ณด๋ฅ˜์ค‘! (๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด๋ถ€๋„ ํ•ด๊ฒฐ ์•ˆ๋˜์–ด์žˆ์Œ)
  emit(event, ...args: never[]) {
    this.list.has(event) &&
      this.list.get(event)!.forEach((callback) => {
        const timer = setTimeout(() => {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-expect-error
          callback(...args);
        }, 0);

        this.emitQueue.has(event)
          ? this.emitQueue.get(event)!.push(timer)
          : this.emitQueue.set(event, [timer]);
      });
  },
};

ํ† ์ŠคํŠธ ์ด๋ฒคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์œ„์™€๊ฐ™์€ eventManger๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. on, off ๋ถ€๋ถ„์€ ์ผ๋ฐ˜์ ์ธ ์ด๋ฒคํŠธ ๋งค๋‹ˆ์ € ๊ตฌ์กฐ์™€ ๋™์ผํ•˜๋‹ˆ ์„ค๋ช…ํ•˜์ง€ ์•Š๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์‹ฌ์ ์œผ๋กœ ๋ด์•ผํ•  ๊ฒƒ์€ emitQueue ์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ list์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์™œ ๋ณ„๋„์˜ emitQueue๊ฐ€ ํ•„์š”ํ• ๊นŒ์š”? ๋ฐ”๋กœ ์ด๋ฒคํŠธ์˜ ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ๋™๊ธฐํ•จ์ˆ˜๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

emit์œผ๋กœ ์ด๋ฒคํŠธ dispatch์‹œ ์ด๋ฒคํŠธ list์—์„œ ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ setTimeout์— ๋„ฃ์–ด์„œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ด๋ฒคํŠธ ๋ฃจํ”„ ๋‚ด์—์„œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ๋‹ค์Œ ์ฃผ๊ธฐ๋กœ ๋ฐ€์–ด๋‚ด์„œ ํ˜„์žฌ ์‹คํ–‰์ค‘์ธ ๋™๊ธฐ ์ž‘์—…์ด ๋ชจ๋‘ ์ข…๋ฃŒ๋œ ์ดํ›„์— ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ emitQueue๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํƒ€์ด๋จธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ณ„๋„์˜ ํ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , cancelEmit์—์„œ ๋Œ€๊ธฐ์ค‘์ธ ์ด๋ฒคํŠธ๋ฅผ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„์ง์€ ํ† ์ŠคํŠธ๋ฅผ ๋„์šฐ๊ณ  ์ œ๊ฑฐํ•  ๋•Œ ๋™๊ธฐ ํ•จ์ˆ˜๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— emitQueue์™€ cancelEmit์€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ถ”ํ›„ ํ† ์ŠคํŠธ์—์„œ Promise์˜ ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ธฐํš์ด ์ถ”๊ฐ€๋  ๊ฒƒ์„ ์—ผ๋‘ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

ToastContainer ๊ตฌํ˜„

useToastContainer

ToastContainer์—์„œ๋Š” ์•„๋ž˜ useToastContainer ํ›…์„ ์‚ฌ์šฉํ•ด ํ† ์ŠคํŠธ์˜ ์ƒ์„ฑ, ์‚ญ์ œ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•ด ์ด์— ๋งž๋Š” ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  toastList๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ํ™”๋ฉด์— ๋„์›Œ์ง„ ํ† ์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

const useToastContainer = () => {
  const [toastList, setToastList] = useState(new Map<string, ToastProps>());

  // ํ† ์ŠคํŠธ ์ถ”๊ฐ€
  const addToast = (props: ToastProps) => {
    setToastList((prev) => new Map(prev).set(props.toastId, props));
  };

  // ํ† ์ŠคํŠธ ์‚ญ์ œ
  const deleteToast = (id: string) => {
    setToastList((prev) => {
      const newMap = new Map(prev);
      newMap.delete(id);
      return newMap;
    });
  };

  useEffect(() => {
    eventManager.on(ToastEvent.Add, addToast);
    eventManager.on(ToastEvent.Delete, deleteToast);

    // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ๋ฆฌ์Šค๋„ˆ ํ•ด์ œ
    return () => {
      eventManager.off(ToastEvent.Add, addToast);
      eventManager.off(ToastEvent.Delete, deleteToast);
    };
  }, []);

  const toastListToArray = () => {
    return Array.from(toastList);
  };

  const getToastPositionGroupToRender = () => {
    const list = toastListToArray();
    const positionGroup = new Map<ToastPosition, ToastProps[]>();
    list.forEach(([_, toastProps]) => {
      const position = toastProps.position || 'topRight';
      positionGroup.has(position)
        ? positionGroup.get(position)!.push(toastProps)
        : positionGroup.set(position, [toastProps]);
    });
    return positionGroup;
  };

  return { getToastPositionGroupToRender };
};

export default useToastContainer;

ToastContainer

export const ToastContainer = () => {
  const { getToastPositionGroupToRender } = useToastContainer();
  const positionGroup = getToastPositionGroupToRender();

  return Array.from(positionGroup).map(([position, toasts]) => (
    <div
      key={position}
      css={[
        css`
					position: fixed;
					display: flex;
					flex-direction: column;
					row-gap: 0.5rem;
					z-index: 9999;
					`,
					ToastPositionStyle[position],
					]} >
					{toasts.map((toastProps) => (
						<ToastItem key={toastProps.toastId} {...toastProps} />
		      ))}
    </div>
  ));
};

ํ† ์ŠคํŠธ ์ปจํ…Œ์ด๋„ˆ์—์„œ๋Š” ์œ„ ํ›…์„ ์‚ฌ์šฉํ•ด์„œ ํ˜„์žฌ ๋žœ๋”๋งํ•ด์•ผํ•˜๋Š” ํ† ์ŠคํŠธ์˜ ์ •๋ณด๋ฅผ position๋ณ„ Map ํ˜•ํƒœ๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ ํฌ์ง€์…˜์— ๋งž๋Š” ์ปจํ…Œ์ด๋„ˆ ๋ฐ•์Šค๋ฅผ ์ƒ์„ฑํ•ด์„œ ์•Œ๋งž์€ ์œ„์น˜์— ToastItem์„ ๋„์›Œ์ค๋‹ˆ๋‹ค.

toast ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„

ํ† ์ŠคํŠธ๊ฐ€ ์ผ์ • ์‹œ๊ฐ„๋งŒํผ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚ด๊ณ  ์ดํ›„์— ์‚ฌ๋ผ์ง€๊ธฐ

ํ† ์ŠคํŠธ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฐ„๋‹จํ•œ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•ด์ฃผ๊ธฐ ์œ„ํ•œ UI๋กœ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์‚ฌ๋ผ์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์„ ํƒ์ง€๋Š” ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ์š”. ์ฒซ ๋ฒˆ์งธ๋Š” setTimeout์„ ์‚ฌ์šฉํ•ด ์ผ์ • ์‹œ๊ฐ„ ์ดํ›„ ํ† ์ŠคํŠธ๊ฐ€ ๋”์—์„œ ์ œ๊ฑฐ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๊ณ , ๋‘ ๋ฒˆ์งธ๋Š” animationend ์ด๋ฒคํŠธ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์ด ์ค‘์— ์ €๋Š” animationend ์ด๋ฒคํŠธ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. toast ํ•˜๋‹จ์— ๋‚˜ํƒ€๋‚˜๋Š” ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ”๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ด์— ๋Œ€ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ผ์ • ์‹œ๊ฐ„๋งŒํผ ๋ณด์—ฌ์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ณ„๋„์˜ ํƒ€์ด๋จธ๋ฅผ ๋‘๋Š” ๊ฒƒ ๋ณด๋‹ค ํ•˜๋‚˜์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„์— ์ข…์†์„ฑ๋˜๋„๋ก ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

ํ† ์ŠคํŠธ์— ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ” ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋‹ฌ๊ธฐ

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์„ค๋ช…์„ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋‚จ๊ธฐ๊ณ  ํ† ์ŠคํŠธ๋ฅผ ๊ฐ„์†Œํ™”ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

export const ToastProgressBarAnimation = keyframes`
  from {
    transform: scaleX(1);
  }
  to {
    transform: scaleX(0);
  }
`;

const ToastItem: React.FC<ToastProps> = ({
  toastId,
  text,
  autoClose = 3000,
  closeOnClick = true,
  type = 'default',
  pauseOnHover = true,
}) => {
  const toastRef = useRef<HTMLDivElement>(null);
  const [isExiting, setIsExiting] = useState(false);
  const [isPaused, setIsPaused] = useState(false);

  const handleExitingAnimationEnd = () => {
	  // ๋‹ค์Œ ๋‚ด์šฉ์—์„œ ์„ค๋ช…
  };

	const handleProgressAnimationEnd = () => {
	  autoClose && setIsExiting(true);
	};

	const handleClick = () => {
	  closeOnClick && handleProgressAnimationEnd();
	};

	const handleMouseEnter = () => {
	  pauseOnHover && autoClose && setIsPaused(true);
	};

	const handleMouseLeave = () => {
	  if (pauseOnHover && autoClose) {
		setIsPaused(false);
	  }
	};

  return (
    <div ref={toastRef}>
      <Box
        onClick={handleClick}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
        onAnimationEnd={handleExitingAnimationEnd}
        css={css`
          animation: ${isExiting
            ? css`${ToastFadeOutUpAnimation} 0.8s forwards`
            : 'none'};
        `}
      >
        {text}
        <div
          onAnimationEnd={handleProgressAnimationEnd}
          css={css`
            transform-origin: left;
            animation: ${autoClose
              ? css`${ToastProgressBarAnimation} ${autoClose}ms linear forwards`
              : 'none'};
            animation-play-state: ${isPaused ? 'paused' : 'running'};
          `}
        />
      </Box>
    </div>
  );
};

export default ToastItem;

ํ† ์ŠคํŠธ์˜ ์‹œ๊ฐ„์ด ๊ฒฝ๊ณผํ•จ์— ๋”ฐ๋ผ progressBar๋ฅผ ํ‘œ์‹œํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ToastProgressBarAnimation ์„ keyframe์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ToastProgressBarAnimation์€ width ์†์„ฑ์ด ์•„๋‹ˆ๋ผ scaleX ์†์„ฑ์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ์š”. width์†์„ฑ์€ element์˜ ์‹ค์ œ ํฌ๊ธฐ๋ฅผ ๋ณ€ํ™”์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ง„ํ–‰๋˜๋Š” ๋™์•ˆ reflow ๊ณผ์ •์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด scaleX ์†์„ฑ์€ transform ์†์„ฑ์˜ ์ผ๋ถ€๋กœ, ์‹ค์ œ ํฌ๊ธฐ๋ฅผ ๋ณ€ํ™”์‹œํ‚ค์ง€ ์•Š์€ ์ฑ„ ๋ณ€ํ˜•๋งŒ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ GPU ๊ฐ€์†์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ ์œผ๋กœ ๋” ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.

onAnimationEnd๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ข…๋ฃŒ์‹œ ์ข…๋ฃŒ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” isExiting ์ƒํƒœ๋ฅผ true๋กœ ์—…๋ฐ์ดํŠธ ํ•ด์คฌ์Šต๋‹ˆ๋‹ค. ํ† ์ŠคํŠธ๋ฅผ ๋ฐ”๋กœ ์ œ๊ฑฐํ•˜์ง€ ์•Š๊ณ  isExiting๋ฅผ ๋”ฐ๋กœ ๋‘” ์ด์œ ๋Š” ์•„๋ž˜์—์„œ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ฒ ์ง€๋งŒ, ์š”์•ฝํ•˜์ž๋ฉด ํ† ์ŠคํŠธ๊ฐ€ ์„œ์„œํžˆ ์‚ฌ๋ผ์ง€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ฃผ๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

animation-play-state ์†์„ฑ๊ณผ onMouseEnter, onMouseLeave ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ† ์ŠคํŠธ์— ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„์‹œ ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ •์ง€ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

toast๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ fade out ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋„ฃ๊ธฐ

ํ† ์ŠคํŠธ์˜ ์ง€์†์‹œ๊ฐ„์ด ์ข…๋ฃŒ๋˜์—ˆ์„ ๋•Œ ๊ฐ‘์ž๊ธฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ "ํœ™~" ์‚ฌ๋ผ์ ธ๋ฒ„๋ฆฌ๋ฉด ๋ถ€์ž์—ฐ์Šค๋Ÿฝ๊ณ  ์ข‹์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํ† ์ŠคํŠธ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ "์ƒค์ƒค์ƒฅ~" ํ•˜๋ฉฐ ์‚ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋„๋ก keyframe์„ ์‚ฌ์šฉํ•ด์„œfade-out ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.

animation: ${isExiting
            ? css`${ToastFadeOutUpAnimation} 0.8s forwards`
            : 'none'};

ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ข…๋ฃŒ๋˜๋ฉด isExiting ์ƒํƒœ๊ฐ€ true๊ฐ€ ๋˜๊ณ , ToastFadeOutUpAnimation ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์‹คํ–‰๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

export const ToastFadeOutUpAnimation = keyframes`
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-1.5rem);
  }
`;

// ์œ„ ์• ๋‹ˆ๋ฉ”์ด์…˜์— ๋Œ€ํ•œ animationend ์ด๋ฒคํŠธ ์ฝœ๋ฐฑํ•จ์ˆ˜
const handleExitingAnimationEnd = () => {
  eventManager.emit(ToastEvent.Delete, toastId);
};

ํ† ์ŠคํŠธ ์•„์ดํ…œ์ด ์ ์  ํˆฌ๋ช…ํ•ด์ง€๋ฉด์„œ ์œ„๋กœ ์‚ฌ๋ผ์ง€๋„๋ก fade-out ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.. ๊ทธ๋ฆฌ๊ณ  ์ด ์• ๋‹ˆ๋ฉ”์ด์…˜์— ๋Œ€ํ•œ animationend ์ด๋ฒคํŠธ๊ฐ€ ๊ฐ์ง€๋˜๋ฉด ์‹ค์ œ ๋”์—์„œ๋„ toast๊ฐ€ ์ œ๊ฑฐ๋„๋˜๋ก ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ์š”. ์—ฌ๊ธฐ์„œ ์‚ด์ง ์ด์Šˆ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

fade-out์—์„œ ๋Š๊น€ ํ˜„์ƒ ๋ฐœ์ƒ

fade-out์‹œ ๋š๋š ๋Š๊ธฐ๋Š” ํ˜„์ƒ ๋ฐœ์ƒ

ํ˜„์žฌ ๊ฐ position์— ๋”ฐ๋ฅธ toastContainer์˜ ์œ„์น˜๋Š” fixed ์†์„ฑ์œผ๋กœ ๊ด€๋ฆฌ๋˜์ง€๋งŒ, ๋‚ด๋ถ€์— ์žˆ๋Š” ํ† ์ŠคํŠธ๋“ค์˜ ๋ฆฌ์ŠคํŠธ๋Š” flexbox ์†์„ฑ์œผ๋กœ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. top ์†์„ฑ์„ ๊ฐ€์ง„ ํ† ์ŠคํŠธ๋“ค์˜ ๋ฐฐ์—ด์—์„œ๋Š” ์‹œ๊ฐ„ ์ˆœ์„œ์— ๋”ฐ๋ผ ์œ„์ชฝ์— ์žˆ๋Š” toast๊ฐ€ ๋จผ์ € ์ œ๊ฑฐ๋˜๋Š”๋ฐ์š”. ์ด ๋•Œ๋ฌธ์— fade-out ์ดํ›„ DOM์—์„œ ํ† ์ŠคํŠธ ์ œ๊ฑฐ์‹œ flexbox์˜ ๋†’์ด๊ฐ€ ์ค„์–ด๋“ค๋ฉด์„œ ๋ Œ๋” ํŠธ๋ฆฌ์˜ ๊ฐ ์š”์†Œ์˜ ํฌ๊ธฐ์™€ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” reflow๊ณผ์ •์ด ์ผ์–ด๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ํ† ์ŠคํŠธ์˜ ๋†’์ด๊ฐ€ ๋ณ€ํ•˜๋Š” ๋™์ž‘๊ณผ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ•ฉ์ณ์ ธ์„œ ์ €๋Ÿฐ ๋š๋š ๋Š๊ธฐ๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜์—ˆ์ฃ .

requestAnimationFrame ์ ์šฉํ•˜๊ธฐ

์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด requestAnimationFrame์„ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค. requestAnimationFrame์€ ๋ธŒ๋ผ์šฐ์ €์˜ ๋ Œ๋”๋ง ์‚ฌ์ดํด์— ๋งž์ถฐ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐ์ด๋‚˜ DOM ์กฐ์ž‘ ์—ฐ์‚ฐ์„ ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ requestAnimationFrame ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const handleAnimationEnd = () => {
  requestAnimationFrame(() =>
    eventManager.emit(ToastEvent.Delete, toastId)
  );
};

requestAnimationFrame์„ ์ ์šฉํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์—ฌ์ „ํžˆ ๋š๋š ๋Š๊ธฐ๋Š” ๋“ฏํ•œ ๋Š๋‚Œ์ด ์‚ฌ๋ผ์ง€์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ทผ๋ณธ์ ์œผ๋กœ flexbox์˜ ์‚ฌ์ด์ฆˆ๊ฐ€ ์ค„์–ด๋“ค๋ฉด์„œ ํ† ์ŠคํŠธ ์œ„์น˜๊ฐ€ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ„๋‹ค๋Š” ์‚ฌ์‹ค์€ ๋ณ€ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ์š”.

collapseToast ์ ์šฉํ•˜๊ธฐ

export function collapseToast(
  node: HTMLDivElement,
  done: () => void,
  duration = 20000
) {
  const { scrollHeight, style } = node;

  requestAnimationFrame(() => {
    style.height = scrollHeight + 'px';
    style.transition = `all ${duration}ms`;

    requestAnimationFrame(() => {
      style.height = '0';
      setTimeout(done, duration);
    });
  });
}

์œ„ ์œ ํ‹ธ์„ ์ƒ์„ฑํ•ด์„œ, ํ† ์ŠคํŠธ์˜ ๋†’์ด๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋™์ž‘๋„ ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. height ์†์„ฑ์„ requestAnimationFrame ์„ ํ†ตํ•ด ์ ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ € ๋žœ๋”๋ง ์‚ฌ์ดํด์— ๋งž๊ฒŒ ์Šค๋ฌด์Šคํ•œ ๋ฐ•์Šค ์‚ฌ์ด์ฆˆ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค.

const handleExitingAnimationEnd = () => {
  collapseToast(toastRef.current!, () => {
    eventManager.emit(ToastEvent.Delete, toastId);
  });
};

handleExitingAnimationEnd ํ•จ์ˆ˜์— collapseToast ์œ ํ‹ธ์„ ์ ์šฉํ•œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

์Šค๋ฌด์Šคํ•ด์ง„ ํ† ์ŠคํŠธ ์ œ๊ฑฐ

ํ•œ๋ฒˆ ๋” ์š”์•ฝํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ณผ์ •์ด ์ผ์–ด๋‚˜์„œ ์ œ๊ฑฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณ€ํ•ฉ๋‹ˆ๋‹ค.

  • fade-out ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์‹คํ–‰๋˜๋ฉด์„œ ํ† ์ŠคํŠธ๊ฐ€ ์ ์ฐจ ํˆฌ๋ช…ํ•ด์ง€๊ณ  ์œ„๋กœ ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค.
  • ์œ„ ์ด๋ฒคํŠธ๊ฐ€ ๋๋‚˜๋ฉด requestAnimationFrame์„ ์‚ฌ์šฉํ•ด์„œ height์˜ ์†์„ฑ์„ ๋ธŒ๋ผ์šฐ์ € ๋žœ๋”๋ง ์‚ฌ์ดํด์— ๋งž์ถ”์–ด ์ ์ฐจ ์ค„์ž…๋‹ˆ๋‹ค.
  • height๊ฐ€ ์ค„์–ด๋“ค๋ฉด์„œ toast container ์˜์—ญ์˜ flexbox๋„ ๋žœ๋”๋ง ์‚ฌ์ดํด์— ๋งž์ถฐ ์Šค๋ฌด์Šคํ•˜๊ฒŒ ์ค„์–ด๋“ค๋ฉฐ ๋š๋š ๋Š๊ธฐ๋Š” ๋Š๋‚Œ์ด ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

ToastUtils

const generateUniqueId = () => {
  return Date.now().toString(36) + Math.random().toString(36).substring(4);
};

const emitToast = (type: ToastType, toastProps: ToastFunctionProps) => {
  const id = generateUniqueId();
  eventManager.emit(ToastEvent.Add, {
    ...toastProps,
    toastId: id,
    type,
  });
};

export const toast = {
  default: (text: string, toastOptions?: ToastOptions) =>
    emitToast('default', { text: text, ...toastOptions }),
  info: (text: string, toastOptions?: ToastOptions) =>
    emitToast('info', { text: text, ...toastOptions }),
  success: (text: string, toastOptions?: ToastOptions) =>
    emitToast('success', { text: text, ...toastOptions }),
  warning: (text: string, toastOptions?: ToastOptions) =>
    emitToast('warning', { text: text, ...toastOptions }),
  error: (text: string, toastOptions?: ToastOptions) =>
    emitToast('error', { text: text, ...toastOptions }),
};

์ด์ œ ํ† ์ŠคํŠธ๋ฅผ console.log์™€ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋Š” ์ผ๋งŒ ๋‚จ์•˜์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด toast ์œ ํ‹ธ ํ•จ์ˆ˜๋ฅผ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ์„ ์–ธํ•˜๊ณ , ๊ฐ ํƒ€์ž…์— ๋งž๋Š” ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ† ์ŠคํŠธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

์ด์ œ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ํ† ์ŠคํŠธ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

toast.defualt("ํ† ์ŠคํŠธ์— ๋“ค์–ด๊ฐˆ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค");
toast.info("ํ† ์ŠคํŠธ์— ๋“ค์–ด๊ฐˆ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค");
toast.success("ํ† ์ŠคํŠธ์— ๋“ค์–ด๊ฐˆ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค");
toast.warning("ํ† ์ŠคํŠธ์— ๋“ค์–ด๊ฐˆ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค");
toast.error("ํ† ์ŠคํŠธ์— ๋“ค์–ด๊ฐˆ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค");

ํ† ์ŠคํŠธ ์ด์˜์ฃ ?

์œ„์ชฝ๋ถ€ํ„ฐ ์ฐจ๋ก€๋Œ€๋กœ default, info, success, warning, error ์— ๋Œ€ํ•œ ๋””์ž์ธ์ž…๋‹ˆ๋‹ค.

ํ† ์ŠคํŠธ ์˜ต์…˜

ํ† ์ŠคํŠธ ์ถœ๋ ฅ์‹œ ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ ์˜ต์…˜ ๊ฐ์ฒด๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ต์…˜์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ค์ •์ด ์žˆ์Šต๋‹ˆ๋‹ค.

export type ToastOption = {
  autoClose?: false | number;
  closeOnClick?: boolean;
  pauseOnHover?: boolean;
  position?: ToastPosition;
};
  • autoClose: ํ† ์ŠคํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ๋‹ซํž์ง€ ์—ฌ๋ถ€. ๊ธฐ๋ณธ๊ฐ’์€ 3์ดˆ, false๋ฅผ ๋„ฃ์œผ๋ฉด ํด๋ฆญํ•  ๋•Œ ๊นŒ์ง€ ํ† ์ŠคํŠธ๊ฐ€ ๋‹ซํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • closeOnClick: ํด๋ฆญํ•ด์„œ ํ† ์ŠคํŠธ๋ฅผ ๋‹ซ์„ ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค.
  • pauseOnHover: ํ† ์ŠคํŠธ์— ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„์‹œ ํ† ์ŠคํŠธ๊ฐ€ ๋ฉˆ์ถœ์ง€ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค.
  • position: ํ† ์ŠคํŠธ์˜ ์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. (topLeft, topRight, topCenter, bottomLeft, bottomRight, bottomCenter)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ›„๊ธฐ

ํ† ์ŠคํŠธ๋ฅผ console.log ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด react-toastify ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๋ฉฐ ์œ„ ํ† ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด๋ถ€๋ฅผ ์ฒ˜์Œ ์—ด์–ด๋ดค์„ ๋•Œ ๊ฐ‘์ž๊ธฐ ๋“ฑ์žฅํ•œ eventManger๋กœ ์ธํ•ด ์—„์ฒญ ์–ด๋ ต๊ฒŒ๋งŒ ๋Š๊ปด์กŒ๋Š”๋ฐ์š”. ์™œ eventManger๋ฅผ ์‚ฌ์šฉํ–ˆ์–ด์•ผ ํ–ˆ๋Š”๊ฐ€์— ๋Œ€ํ•ด์„œ ์ค‘์ ์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด๋ณด๋‹ˆ ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์€ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. eventManger๋ฅผ ํ†ตํ•ด ํ† ์ŠคํŠธ์˜ UI๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ pub/sub ํŒจํ„ด์ด๋ผ๋Š” ๊ฒƒ์„ ๊นจ๋‹ซ๊ณ  ๋‚œ ์ดํ›„๋ถ€ํ„ฐ๋Š” ์ฝ”๋“œ๊ฐ€ ์ˆ ์ˆ  ์ฝํžˆ๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋•๋ถ„์— ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ํŽธ๋ฆฌํ•˜๋ฉด์„œ๋„ ์ด์œ ํ† ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์•„์ง UX๋ฅผ ๋‹ค๋“ฌ๊ธฐ ์ „์ด๋ผ toast๊ฐ€ ์ ๊ทน์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์ง€ ์•Š์•„์„œ ํ† ์ŠคํŠธ์˜ ์‚ฌ์šฉ ๊ฒฝํ—˜์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์€ ๋“ฃ์ง€ ๋ชปํ•œ ์ƒํ™ฉ์ธ๋ฐ์š”. ํ† ์ŠคํŠธ๋ฅผ ์ž ๊น ์‚ฌ์šฉํ•ด๋ณด์‹  ์„ฑ์ธ๋‹˜ ์•„์ฃผ ๊ทน์ฐฌ์„ ํ•ด์ฃผ์…”์„œ ๊ต‰์žฅํžˆ ๋ฟŒ๋“ฏํ•ฉ๋‹ˆ๋‹ค ใ…Žใ…Ž

ํ† ์ŠคํŠธ ์‚ฌ์šฉ ํ›„๊ธฐ!!!

์ถ”ํ›„ ํ”„๋กœ์ ํŠธ์—์„œ toast๊ฐ€ ์ ๊ทน์ ์œผ๋กœ ์‚ฌ์šฉ๋  ๋•Œ ํ† ์ŠคํŠธ์— ๋Œ€ํ•œ ํ›„๊ธฐ์— ๋Œ€ํ•ด ๋” ๋‚จ๊ฒจ๋ณด๋„๋ก ํ• ๊ฒŒ์š”!

์ด ํ† ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์•„๋ž˜ PR์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. gomterview

์ฐธ๊ณ  ๋งํฌ

React-toastify | React-Toastify fkhadra/react-toastify: React notification made easy ๐Ÿš€ ! requestAnimationFrame ํ™œ์šฉ (์ƒ)

ยฉ 2024 Adultlee. All rights reserved.Made with โค by ์ด์„ฑ์ธ