useMemo useCallback

노영완·2023년 7월 29일
0

메모이제이션(Memoization)

메모이제이션(Memoization)은 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법 동일한 입력으로 여러 번 호출되는 함수 도는 컴포넌트가 있을 때 React에서 유용

useMemo

메모제이션된 값을 반환하는 함수.

useMemo(() => fn, [deps])

deps 즉, dependency가 바뀌면 () => fn 함수를 실행

// useState를 이용해 설명
const [plus, setPlus] = useState(0)
const [minus, setMinus] = useSate(0)
console.log(plus)
console.log(minus)
 return (
    <>
      <button onClick={() => setPlus((num) => (num + 1))}>+</button>
      <button onClick={() => setMinus((num) => (num - 1))}>-</button>
    </>
  );

숫자를 1씩 더하는 plus 버튼 숫자를 1씩 빼는 minus 버튼이 있고 두 버튼 중 하나를 클릭해서 state 값이 변해도 리랜더링에 의해 console은 둘 다 결과값이 나올 것이다.

// useMemo를 이용해 설명
const [plus, setPlus] = useState(0)
const [minus, setMinus] = useSate(0)
useMemo(() => {console.log(plus)}, [plus]);
useMemo(() => {console.log(minus)}, [minus]);
 return (
    <>
      <button onClick={() => setPlus((num) => (num + 1))}>+</button>
      <button onClick={() => setMinus((num) => (num - 1))}>-</button>
    </>
  );

useMemo를 사용하면 +버튼을 누르면 plus console만 찍히고, -버튼을 누르면 minus console만 찍힌다. useMemo를 쓰게되면 +, -, 부모 컴포넌트 어디에서든 리랜더링이 될 경우 deps에 의존해서 실행된다.
결과, 리렌더링이 발생할 경우, 특정 변수가 변할 때에만 useMemo에 등록한 함수가 실행되도록 처리하면 불필요한 연산을 하지 않게 된다. 그리고 useMemo에 값이 변화하지 않고 리랜더링이 되더라도 값은 그대로 가져오게 되는 Memoization 하게된다.

// useMemo가 메모제이션이 가능한 이유
// https://github.com/facebook/react/blob/1a106bdc2abc7af190b791d13b2ead0c2c556f7a/packages/react-server/src/ReactFizzHooks.js#L342-L369
function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
  currentlyRenderingComponent = resolveCurrentlyRenderingComponent()
  workInProgressHook = createWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  if (workInProgressHook !== null) {
    const prevState = workInProgressHook.memoizedState
    if (prevState !== null) {
      if (nextDeps !== null) {
        const prevDeps = prevState[1]
        if (areHookInputsEqual(nextDeps, prevDeps)) {
          return prevState[0]
        }
      }
    }
  }
  if (__DEV__) {
    isInHookUserCodeInDev = true
  }
  const nextValue = nextCreate()
  if (__DEV__) {
    isInHookUserCodeInDev = false
  }
  workInProgressHook.memoizedState = [nextValue, nextDeps]
  return nextValue
}

위 코드를 보면 이전의 값들과 현재의 값이 다른지 비교 후, 값이 다르지 않다면 저장되어있던 이전 상태를 보여주는 메모제이션을 한다.

useCalback

useCallback은 메모이제이션된 함수를 반환

useCallback(fn, [deps])
useMemo(() => console.log(),[test])
const Callback = useCallback(() => console.log(),[test])

useMemo와 useCallback의 모양이다 useCallback이 함수를 반환하기 때문에 그 함수를 가지는 const 변수에 초기화하는 것이 일반적인 모양 대개 useCallback 사용처는 1) 자식 컴포넌트에 props로 함수를 전달할 경우 2) 외부에서 값을 가져오는 api를 호출하는 경우

자식 컴포넌트에 props로 함수 전달한 경우

// 함수는 값이 아닌 참조로 비교된다
const functionOne = () => return 5;
const functionTwo = () => return 5;
console.log(functionOne === functionTwo); fasle

동일한 값을 반환하지만 참조가 다르기 때문에 false가 나온다. 위와 같이 컴포넌트에서 특정 함수를 정의할 경우 각각의 함수들은 모두 고유한 함수가 된다. 이런 고유한 함수가 생성될 경우, 부모를 통해 props에 함수를 전달받는 자식 컴포넌트에서는 props가 변경되었다고 판단해 리랜더링이 발생

const App = () => {
 const [changeState, setChangetState] = useState('')
 const onChange = () => {}
retrun(
<>
	<div><input onChange={(e) => setChangetState(e.target.value)}/></div>  
<ChildComponent onChange={onChange}/>
</>
)

지금 현 컴포넌트 App 컴포넌트는 ChildComponent에 부모 컴포넌트이며 setChangeState로 changeState가 변화되어 리랜더링 중이며, 이 때, 리랜더링은 onChange 함수를 새로 만들게 되는 현상을 발생하며, 이에 ChildComponent의 props로 onChange 함수가 새로 전달되게 된다. 결과는 ChildComponent의 props가 변경되었다고 판단해 리랜더링 된다. 이에 방지로 useCallback을 사용 onChange 함수를 재사용하는 것으로 자식 컴포넌트의 리렌더링을 방지할 수 있다.

외부에서 값을 가져와 api를 호출하는 경우

import React, {useState, useEffect, useCallback} from "react"
const ChildComponent = () => {
	const fetchUser = useCallback(
    () =>
      fetch(`https://your-api.com/users/${userId}`)
    [userId]
  );
	useEffect(() => {
    fetchUser()
  }, [fetchUser]);
}

deps에 설정에서만 변동시 fetchUser에 새로운 함수가 할당되도록 설정하고, 그것이 아니면 동일한 함수가 실행되게 되서 무한 루프에 빠지지 않게 해준다. 아무래도 이 때의 useCallback은 query 혹은 params에 변동이 있을 때 데이터를 불러와야 하는 시점에서 사용할 수 있을거 같다. 기본 get으로 불러오는 fetch는 useEffect로 한번만 불러와도 충분하다.

0개의 댓글