๐Ÿ–ฅFrontEnd/React

๋ฆฌ์•กํŠธ ๋ Œ๋”๋ง ์ตœ์ ํ™”ํ•˜๊ธฐ, useMemo์˜ ํ•„์š”์„ฑ, useCallback, useCallback๊ณผ ์ฐธ์กฐ ๋™๋“ฑ์„ฑ

hellohailie 2022. 7. 27. 15:38

 

๋ Œ๋”๋ง ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ Hook, useMemo, useCallback

 

 useMemo๋ž€?

ํŠน์ • ๊ฐ’์„ ์žฌ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” Hook์ด๋‹ค. 

 

 

 

์˜ˆ์‹œ ์ฝ”๋“œ ํ•ด์„

๐Ÿ‘‰ props๋กœ ๋„˜์–ด์˜จ value๊ฐ’์„ calculate๋ผ๋Š” ํ•จ์ˆ˜์— ์ธ์ž๋กœ ๋„˜๊ฒจ์„œ result ๊ฐ’์„ ๊ตฌํ•œ ํ›„, <div> ์—˜๋ฆฌ๋จผํŠธ๋กœ ์ถœ๋ ฅ์„ ํ•˜๊ณ  ์žˆ๋‹ค.

function Calculator({value}){

	const result = calculate(value);

	return <>
      <div>
					{result}
      </div>
  </>;
}

 

๋งŒ์•ฝ ์œ„์˜ calculate๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ณต์žกํ•œ ์—ฐ์‚ฐ์„ ํ•ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜๋ผ ๊ณ„์‚ฐ๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์— ์‹œ๊ฐ„์ด ๋ช‡ ์ดˆ ์ด์ƒ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •ํ•œ๋‹ค๋ฉด,

ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋ง์„ ํ•  ๋•Œ๋งˆ๋‹ค ์ด ํ•จ์ˆ˜๋ฅผ ๊ณ„์†ํ•ด์„œ ํ˜ธ์ถœํ•  ๊ฒƒ์ด๊ณ , ๊ทธ ๋•Œ๋งˆ๋‹ค ์‹œ๊ฐ„์ด ๋ช‡ ์ดˆ ์ด์ƒ ์†Œ์š”๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค.

 

์œ ์ €์—๊ฒŒ ์ข‹์ง€ ์•Š์€ UX๋ฅผ ์ฃผ๊ฒŒ ๋œ๋‹ค. 

 

๐Ÿ‘‡ ์ด๋•Œ useMemo()๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž! ๐Ÿ‘‡

/* useMemo๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์—๋Š” ๊ผญ importํ•ด์„œ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. */
import { useMemo } from "react";

function Calculator({value}){

	const result = useMemo(() => calculate(value), [value]);

	return 
    	<>
     	 <div>
			{result}
      	</div>
  		</>;
}

useMemo๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ calculate๋ฅผ ๊ฐ์‹ธ์ฃผ๋ฉด, ์ด์ „์— ๊ตฌ์ถ•๋œ ๋ Œ๋”๋ง๊ณผ ์ƒˆ๋กœ์ด ๊ตฌ์ถ•๋˜๋Š” ๋ Œ๋”๋ง์„ ๋น„๊ตํ•ด value๊ฐ’์ด ๋™์ผํ•  ๊ฒฝ์šฐ์—๋Š” ์ด์ „ ๋ Œ๋”๋ง์˜ value๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. (๋ฉ”๋ชจ์ด์ œ์ด์…˜(Memoization) ๊ฐœ๋…)

 


useMemo์˜ ํ•„์š”์„ฑ

๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์žˆ๋Š” state์ค‘ ํ•˜๋‚˜๋ผ๋„ ๋ณ€ํ™”๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ์ปดํฌ๋„ŒํŠธ ์ „์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š”๋ฐ, ์ด๋Š” ๋ถˆํ•„์š”ํ•˜๋‹ค!!

์ด๋•Œ useMemo๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ํ˜ธ์ถœ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค! ๐Ÿ‘

//App.js

import React, { useState } from "react";
import "./styles.css";
import { add } from "./add";

export default function App() {
  const [name, setName] = useState("");
  const [val1, setVal1] = useState(0);
  const [val2, setVal2] = useState(0);
  const answer = add(val1, val2);

  return (
    <div>
      <input
        className="name-input"
        placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
        value={name}
        type="text"
        onChange={(e) => setName(e.target.value)}
      />
      <input
        className="value-input"
        placeholder="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
        value={val1}
        type="number"
        onChange={(e) => setVal1(Number(e.target.value))}
      />
      <input
        className="value-input"
        placeholder="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
        value={val2}
        type="number"
        onChange={(e) => setVal2(Number(e.target.value))}
      />
      <div>{answer}</div>
    </div>
  );
}

// add.js

export const add = (num1, num2) => {
  console.log("์ˆซ์ž๊ฐ€ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.");
  return Number(num1) + Number(num2);
};

 

โžฅ const [name, setName] = useState(""); ์—์„œ name ์ด ๋ณ€๊ฒฝ๋˜๋„, ์ปดํฌ๋„ŒํŠธ ์ „์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋œ๋‹ค. 

 

 


 

๐Ÿ‘‡useMemo ํ™œ์šฉํ•œ ์ฝ”๋“œ๐Ÿ‘‡

 

โžฅ `useMemo`๋ฅผ ์ด์šฉํ•˜์—ฌ `add `ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ์„ ์ตœ์†Œํ™”ํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ฆ„์„ ์ž…๋ ฅํ•  ๋•Œ๋Š” `add` ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์•„์•ผ ์ตœ์ ํ™”๊ฐ€ ๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋˜์—ˆ๋‹ค. 

//App.js

import React, { useState } from "react";
import "./styles.css";
import { add } from "./add";

export default function App() {
  const [name, setName] = useState("");
  const [val1, setVal1] = useState(0);
  const [val2, setVal2] = useState(0);
  
  const answer = useMemo(() => add(val1, val2), [val1, val2]); // changed here!!!!!!

  return (
    <div>
      <input
        className="name-input"
        placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
        value={name}
        type="text"
        onChange={(e) => setName(e.target.value)}
      />
      <input
        className="value-input"
        placeholder="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
        value={val1}
        type="number"
        onChange={(e) => setVal1(Number(e.target.value))}
      />
      <input
        className="value-input"
        placeholder="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
        value={val2}
        type="number"
        onChange={(e) => setVal2(Number(e.target.value))}
      />
      <div>{answer}</div>
    </div>
  );
}

// add.js

export const add = (num1, num2) => {
  console.log("์ˆซ์ž๊ฐ€ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.");
  return Number(num1) + Number(num2);
};

์ˆซ์ž๋ฅผ ์ž…๋ ฅํ–ˆ์„๋•Œ๋งŒ ์ฝ˜์†”์ฐฝ์— ๋ณ€ํ™”๊ฐ€ ๋œ๋‹ค. (๋ Œ๋”๋ง)

 


useCallback์ด๋ž€?

useCallback๋„ useMemo์™€ ๋™์ผํ•˜๊ฒŒ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ๊ธฐ๋ฒ•์„ ์ด์šฉํ•œ ๋ฆฌ์•กํŠธ ๋ Œ๋”๋ง ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ Hook์ด๋‹ค. 

 

useMemo ๐Ÿ‘‰ ๊ฐ’์˜ ์žฌ์‚ฌ์šฉ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•œ Hook

useCallback ๐Ÿ‘‰ ํ•จ์ˆ˜์˜ ์žฌ์‚ฌ์šฉ์˜ ์œ„ํ•ด ์‚ฌ์šฉํ•œ Hook

 

useCallback์€ useMemo์™€ ๋น„๊ตํ•˜์—ฌ ๋™๋“ฑํ•œ ์ˆ˜์ค€์˜ ์ตœ์ ํ™”๋ฅผ ๋Š๋‚„ ์ˆ˜ ์—†๋‹ค.

์™œ๋ƒ๋ฉด useCallback์€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ์„ ํ•˜์ง€ ์•Š๋Š” Hook์ด ์•„๋‹ˆ๋ผ, ๊ทธ์ € ๋ฉ”๋ชจ๋ฆฌ ์–ด๋”˜๊ฐ€์— ํ•จ์ˆ˜๋ฅผ ๊บผ๋‚ด์„œ ํ˜ธ์ถœํ•˜๋Š” Hook์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

 

์˜ˆ์‹œ ์ฝ”๋“œ ํ•ด์„

add ํ•จ์ˆ˜๋Š” props๋กœ ๋„˜์–ด์˜จ x์™€ y ๊ฐ’์„ ๋”ํ•ด <div> ํƒœ๊ทธ์— ๊ฐ’์„ ์ถœ๋ ฅํ•˜๊ณ  ์žˆ๋‹ค.

์ด ํ•จ์ˆ˜๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“ค์–ด์ง„๋‹ค.

 

function Calculator({x, y}){

	const add = () => x + y;

	return <>
      <div>
					{add()}
      </div>
  </>;
}

 

 

๋งŒ์•ฝ ์œ„์˜ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋”๋ผ๋„ ๊ทธ ํ•จ์ˆ˜๊ฐ€ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๊ฐ’์ธ x์™€ y๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค๋ฉด! ํ•จ์ˆ˜ ๋˜ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์–ด๋”˜๊ฐ€์— ์ €์žฅํ•ด ๋’€๋‹ค๊ฐ€ ๋‹ค์‹œ ๊บผ๋‚ด์„œ ์“ธ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

๐Ÿ‘‡ ์ด๋•Œ useCallback()๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž! ๐Ÿ‘‡

useCallback Hook์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ ํ•จ์ˆ˜๊ฐ€ ์˜์กดํ•˜๋Š” ๊ฐ’๋“ค์ด ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ํ•œ ๊ธฐ์กด ํ•จ์ˆ˜๋ฅผ ๊ณ„์†ํ•ด์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค. 

์ฆ‰, x์™€ y๊ฐ’์ด ๋™์ผํ•˜๋‹ค๋ฉด ๋‹ค์Œ ๋ Œ๋”๋ง ๋•Œ ์ด ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•œ๋‹ค.

 

/* useCallback๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์—๋Š” ๊ผญ importํ•ด์„œ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. */
import React, { useCallback } from "react";

function Calculator({x, y}){

	const add = useCallback(() => x + y, [x, y]);

	return <>
      <div>
			{add()}
      </div>
  </>;
}

 

useCallback๋Š” ํ•จ์ˆ˜์„ ๋‹จ์ˆœํžˆ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ํฐ ์˜๋ฏธ๊ฐ€ ์—†๊ฑฐ๋‚˜ ์˜คํžˆ๋ ค ์†ํ•ด์ธ ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค.

useCallback ๋ฅผ ๋” ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๐Ÿ‘‰ ํ•จ์ˆ˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ props๋กœ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์ค„ ๋•Œ ์ด useCallback์„ ์‚ฌ์šฉํ•˜๊ธฐ!!

 

JavaScript์—์„œ ๊ฐ์ฒด๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•  ๋•Œ ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๊ฐ’์˜ ์ฃผ์†Œ๋ฅผ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด ๊ฐ™์„ ์ง€๋ผ๋„ ์ผ์น˜์—ฐ์‚ฐ์ž๋กœ ๋น„๊ตํ–ˆ์„ ๋•Œ false๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. 

React๋Š” JavaScript ์–ธ์–ด๋กœ ๋งŒ๋“ค์–ด์ง„ ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์œผ๋กœ JavaScript์˜ ๋ฌธ๋ฒ•์„ ๋”ฐ๋ผ๊ฐ„๋‹ค. 

 

๋”ฐ๋ผ์„œ useCallback์„ ์ด์šฉํ•ด ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ์ €์žฅํ•ด์„œ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๋ฉด ํ•จ์ˆ˜์˜ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ ๊ฐ’์„ ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ React ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋‹ค๋ฅธ ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ๋„˜๊ธฐ๊ฑฐ๋‚˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ prop์œผ๋กœ ๋„˜๊ธธ ๋•Œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

 

=> useCallback๊ณผ ์ฐธ์กฐ ๋™๋“ฑ์„ฑ

 


 

useCallback์˜ ํ•„์š”์„ฑ

๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์žˆ๋Š” state์ค‘ ํ•˜๋‚˜๋ผ๋„ ๋ณ€ํ™”๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ์ปดํฌ๋„ŒํŠธ ์ „์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š”๋ฐ, ์ด๋Š” ๋ถˆํ•„์š”ํ•˜๋‹ค!!

์—ฌ๊ธฐ์„œ๋Š” button์ด ํด๋ฆญ๋˜๋ฉด ์ „ํ˜€ ๊ด€๋ จ์—†๋Š” input ์š”์†Œ๊ฐ€ ๋ Œ๋”๋ง ๋˜๊ณ  ์žˆ๋‹ค. 

์ด๋•Œ useCallback์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ํ˜ธ์ถœ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค! ๐Ÿ‘

//App.js

import { useState } from "react";
import "./styles.css";
import List from "./List";

export default function App() {
  const [input, setInput] = useState(1);
  const [light, setLight] = useState(true);

  const theme = {
    backgroundColor: light ? "White" : "grey",
    color: light ? "grey" : "white"
  };

  const getItems = () => {
    return [input + 10, input + 100];
  };

  const handleChange = (event) => {
    if (Number(event.target.value)) {
      setInput(Number(event.target.value));
    }
  };

  return (
    <>
      <div style={theme} className="wall-paper">
        <input
          type="number"
          className="input"
          value={input}
          onChange={handleChange}
        />
        <button
          className={(light ? "light" : "dark") + " button"}
          onClick={() => setLight((prevLight) => !prevLight)}
        >
          {light ? "dark mode" : "light mode"}
        </button>
        <List getItems={getItems} />
      </div>
    </>
  );
  
  // List.js
  
  import { useState, useEffect } from "react";

function List({ getItems }) {
  /* Initial state of the items */
  const [items, setItems] = useState([]);

  /* This hook sets the value of items if 
     getItems object changes */
  useEffect(() => {
    console.log("์•„์ดํ…œ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.");
    setItems(getItems());
  }, [getItems]);

  /* Maps the items to a list */
  return (
    <div>
      {items.map((item) => (
        <div key={item}>{item}</div>
      ))}
    </div>
  );
}

export default List;

 


 

๐Ÿ‘‡useCallback ํ™œ์šฉํ•œ ์ฝ”๋“œ๐Ÿ‘‡

 

โžฅ `useCallback`๋ฅผ ์ด์šฉํ•˜์—ฌ `getItem ` ํ˜ธ์ถœ์„ ์ตœ์†Œํ™”ํ–ˆ๋‹ค.

์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ props๋กœ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์ค„ ๋•Œ ์ด useCallback์„ ์‚ฌ์šฉํ•˜๊ธฐ๊ฐ€ ์ข‹๋‹ค.

//App.js

import { useState } from "react";
import "./styles.css";
import List from "./List";

export default function App() {
  const [input, setInput] = useState(1);
  const [light, setLight] = useState(true);

  const theme = {
    backgroundColor: light ? "White" : "grey",
    color: light ? "grey" : "white"
  };

  const getItems = useCallback(() => [input + 10, input + 100], [input]); // changed here!!!!!!

  const handleChange = (event) => {
    if (Number(event.target.value)) {
      setInput(Number(event.target.value));
    }
  };

  return (
    <>
      <div style={theme} className="wall-paper">
        <input
          type="number"
          className="input"
          value={input}
          onChange={handleChange}
        />
        <button
          className={(light ? "light" : "dark") + " button"}
          onClick={() => setLight((prevLight) => !prevLight)}
        >
          {light ? "dark mode" : "light mode"}
        </button>
        <List getItems={getItems} />
      </div>
    </>
  );
  
  // List.js
  
  import { useState, useEffect } from "react";

function List({ getItems }) {
  /* Initial state of the items */
  const [items, setItems] = useState([]);

  /* This hook sets the value of items if 
     getItems object changes */
  useEffect(() => {
    console.log("์•„์ดํ…œ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.");
    setItems(getItems());
  }, [getItems]);

  /* Maps the items to a list */
  return (
    <div>
      {items.map((item) => (
        <div key={item}>{item}</div>
      ))}
    </div>
  );
}

export default List;

๋ฒ„ํŠผ์„ ์•„๋ฌด๋ฆฌ ๋ˆŒ๋Ÿฌ๋„ ์ฝ˜์†”์ฐฝ์—๋Š” ๋ณ€ํ™”๊ฐ€ ์—†๋‹ค. 

 

 

๐Ÿ˜ƒ ์ž˜๋ชป๋œ ๊ฐœ๋… ์ „๋‹ฌ์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ €์˜ ์„ฑ์žฅ์— ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค๐Ÿค“