- 함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드
- class가 아닌 function으로만 React를 사용할 수 있게 해주는 것이기 때문에 클래스형 컴포넌트에서는 동작하지 않는다.
...
render(){
/* 클래스 컴포넌트는 render() 안에서 변수를 작성할 수 있습니다. */
const [counter, setCounter] = useState(0);
...
}
❗️클래스 컴포넌트에서는 useState() 훅을 호출할 수 없다는 에러
1. 리액트 함수의 최상위에서만 호출해야 한다. (반복문, 조건문, 중첩된 함수 내에서는 사용이 불가능)
...
if(counter) {
const [sample, setSample] = useState(0);
}
...
count가 있을 때 sample이라는 state를 사용하고 싶어서 조건문 안에 useState() hook을 불러왔다고 가정 => ❌
컴포넌트 안에는 useState나 useEffect 같은 Hook들이 여러 번 사용될 수 있는데,
React는 이 Hook을 호출되는 순서대로 저장을 해놓는다.
그런데 조건문, 반복문에서 Hook을 호출하게 되면 호출되는 순서대로 저장을 하기 어려워지고,
결국 예기치 못한 버그를 초래하게 될 수 있다.
...
window.onload = function () {
useEffect(() => {
// do something...
}, [counter]);
}
...
window의 요소가 모두 준비가 되면 useEffect()가 호출되었으면 좋겠다고 생각해서 함수를 작성했다고 가정 => ❌
애초에 Hook은 React의 함수 컴포넌트 내에서 사용되도록 만들어진 메소드이기 때문에
근본적으로 일반 JavaScript 함수 내에서는 정상적으로 돌아가지 않는다.
function Calculator({value}){
const result = calculate(value);
return <>
<div>
{result}
</div>
</>;
}
렌더링 마다 위 함수를 계속 호출, 로딩시간이 길어지게 됨
값이 계속 바뀌는 게 아니라면, 어딘가에 저장해서 필요시마다 꺼내 쓸 수 있게 하는 것이 useMemo
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
이런 식으로 useMemo를 호출하여 calculate를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 value값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용할 수 있게 된다.
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>
);
}
이때 이름을 입력할 때도 콘솔창에 계속 찍히는 걸 볼 수 있다
import React, { useState, useMemo } 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]);
... 생략
useMemo(() => add(val1, val2), [val1, val2]);
은
val1, val2
값이 바뀌었을 때만 ()=> add(val1, val2)
을 실행해 반환하고,
바뀌지 않았을 때는 이전에 계산했던 값을 그대로 사용한다는 의미를 갖는다.
여기까지 진행하고 나면, 이제 이름을 입력할 때는 콘솔에 찍히지 않는 걸 확인할 수가 있다
useMemo는 값의 재사용을 위해 사용하는 Hook이라면, useCallback은 함수의 재사용을 위해 사용하는 Hook
function Calculator({x, y}){
const add = () => x + y;
return <>
<div>
{add()}
</div>
</>;
}
이 add 함수는 props로 넘어온 x와 y 값을 더해
useMemo
에서 했던 것처럼 어딘가에 저장해 그 값을 사용할 수 있게 하는 것이 useCallback
이다.useCallback
을 사용하면 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환한다
/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import React, { useCallback } from "react";
function Calculator({x, y}){
const add = useCallback(() => x + y, [x, y]);
return <>
<div>
{add()}
</div>
</>;
}
그러나 useCallback
은 useMemo
처럼 효율적이지는 않다.
오히려 손해인 경우도 있음.
useCallback
은 자식 컴포넌트의 props로 함수를 전달해줄 때 사용하기가 좋다.
false
가 출력function doubleFactory(){
return (a) => 2 * a;
}
const double1 = doubleFactory();
const double2 = doubleFactory();
double1(8); // 16
double2(8); // 16
double1 === double2; // false
double1 === double1; // true
두개의 함수는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에, 메모리 주소에 의한 참조 비교 시 다른 함수로 본다.
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>
</>
);
}
옆의 button dark mode
를 눌러도 “아이템을 가져옵니다.”
가 콘솔에 출력된다.
❓ 버튼을 누를 때도 앱이 리렌더링 되므로, App 내부의 getItems()
함수가 다시 만들어진다.
새로이 만들어진 함수는 이전의 함수와 참조 비교시 다른 함수이기 때문에 List 구성 요소 내에서 useEffect Hook
은 setItems
를 호출하고 종속성이 변경됨에 따라 “아이템을 가져옵니다.”
를 출력하는 것❗️
import { useState, useCallback } 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(() => {
return [input + 10, input + 100];
}, [input]);
... 생략