1. useCallback
useCallback이라는 Hook함수는 useMemo와 같이 함수를 Memozation하기 위해서 사용됩니다. useCallback은 첫번째 인자로 넘어온 함수를 두번째 인자로 넘어온 배열 내의 값이 변경될 때 까지 저장해 놓고 재사용 할 수 있게 해줍니다.
const callback = useCallback(함수, 배열);
만약, 어떤 React 컴포넌트 함수 안에 함수가 선언이 되어 있다면 이 함수는 해당 컴포넌트가 랜더링될 때 마다 새로운 함수가 생성됩니다.
const add = () => x + y;
하지만 useCallback()을 사용하면, 해당 컴포넌트가 랜더링되더라도 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환합니다. 즉, x 또는 y 값이 바뀌면 새로운 함수가 생성되어 add 변수에 할당되고, x와 y 값이 동일하다면 다음 랜더링 때 이 함수를 재사용합니다.
const add = useCallback(() => x + y, [x, y]);
2. 배열과 같이 사용
많은 React hook 함수들이 불필요한 작업을 줄이기 위해서 두 번째 인자로, 첫 번째 함수가 의존해야하는 배열을 받습니다.
예를 들어, useEffect() 함수는 두 번째 인자로 넘어온 의존 배열이 변경될 때만 첫 번째 인자로 넘어온 함수를 호출합니다.
예를 들어, 다음과 컴포넌트에서 API를 호출하는 코드는 fetchUser 함수가 변경될 때만 호출됩니다. 여기서 위에서 설명드린 자바스크립트가 함수의 동등성을 판단하는 방식 때문에 예상치 못한 무한 루프에 발생하게 됩니다. fetchUser는 함수이기 때문에, userId 값이 바뀌든 말든 컴포넌트가 랜더링될 때 마다 새로운 참조값으로 변경이 됩니다. 그러면 useEffect() 함수가 호출되어 user 상태값이 바뀌고 그러면 다시 랜더링이 되고 그럼 또 다시 useEffect() 함수가 호출되는 악순환이 반복됩니다.
import React, { useState, useEffect } from "react";
function Profile({ userId }) {
const [user, setUser] = useState(null);
const fetchUser = () =>
fetch(`https://your-api.com/users/${userId}`)
.then((response) => response.json())
.then(({ user }) => user);
useEffect(() => {
fetchUser().then((user) => setUser(user));
}, [fetchUser]);
// ...
}
이와 같은 상황에서 useCallback() hook 함수를 이용하면 컴포넌트가 다시 랜더링되더라도 fetchUser 함수의 참조값을 동일하게 유지시킬 수 있습니다. 따라서 의도했던 대로, useEffect()에 넘어온 함수는 userId 값이 변경되지 않는 한 재호출 되지 않게 됩니다.
import React, { useState, useEffect } from "react";
function Profile({ userId }) {
const [user, setUser] = useState(null);
const fetchUser = useCallback(
() =>
fetch(`https://your-api.com/users/${userId}`)
.then((response) => response.json())
.then(({ user }) => user),
[userId]
);
useEffect(() => {
fetchUser().then((user) => setUser(user));
}, [fetchUser]);
// ...
}
3. 빈 배열과 의존성 배열
아래 코드에는 useCallback이 사용된 change와 insert라는 함수가 있습니다.
change에는 의존성 배열이 빈 배열이고, insert에는 의존성배열에 string과 stringList가 들어가 있습니다.
const change = useCallback((e) => {
setString(e.target.value);
}, []);
const insert = useCallback((e) => {
const newList = stringList.slice();
newList.push(string);
setStringList(newList);
}, [string, stringList]);
둘의 차이가 무엇이길래, 하나는 의존성 배열이 비어있고, 하나는 값이 포함되어 있을까요?
change는 두번째 인자에 빈 배열을 주었기 때문에, 최초 렌더링 시에만 함수가 생성되고 이후에는 생성되지 않고, insert는 string과 stringList가 변경될 때만 함수를 재생성합니다.
즉, insert의 경우 string과 stringList가 변하면 함수도 재생성되어야 하는데, 배열로 전달받은 두 state에 의존적이라고 볼수 있습니다.
하지만 change는 state를 사용하지 않고, 단지 state를 변경하는 것이므로, state에 의존적이지 않기 때문에 state가 변함에 따라 change 함수를 재생성해줄 필요는 없어서 빈 배열을 넣어주게 됩니다.
정리하자면, 해당 함수에서 state를 사용한다면, 말그대로 state에 의존하는 함수이므로 두번째 인자의 배열 안에 state를 추가해줘야 하고, state에 의존적이지 않은 함수라면 빈 배열로 넣어주면 됩니다.
참고
'FrontEnd > React 기본' 카테고리의 다른 글
[React] React.js 강좌 18. useReducer (0) | 2022.03.08 |
---|---|
[React] React.js 강좌 17. 불변성 (0) | 2022.03.08 |
[React] React.js 강좌 15. useMemo (0) | 2022.03.08 |
[React] React.js 강좌 14. useEffect (0) | 2022.03.07 |
[React] React.js 강좌 13. useState (0) | 2022.03.07 |