useMemo와 useCallback

Y·2021년 5월 20일
0
post-thumbnail
post-custom-banner

useMemo와 useCallback

useMemo와 useCallback은 공통적으로, 불필요한 렌더링을 막고 최적화하기 위해서 사용되는 리액트 훅이다. 이 둘을 이해하기 위해서 memoization(메모이제이션)을 알면 좋을 것 같다.

memoization(메모이제이션)

컴퓨터 프로그램이 동일한 계산을 반복해야 할 때 똑같은 계산을 반복 수행하는 것이 아닌, 이전에 계산한 값을 메모리에 저장해두고 사용함으로써 프로그램 실행 속도를 빠르게 하는 기술을 의미한다.

😀 useMemo

필요성

React는 state가 변경될 때마다 다시 렌더링 되는데, 간혹 필요없는 것들을 계속해서 불러오곤 한다. 이는 자원의 낭비로 이어질 수 있고, 더 복잡한 서비스에서는 성능을 저하시키는 요인이 된다. 따라서 특정 상황에만 특정 동작을 하도록 유도할 필요가 있는데, 이때 useMemo라는 hook을 사용하여 성능을 최적화할 수 있다.

사용법

const memoizedValue = useMemo(()=> computeExpensiveValue(a,b), [a,b]);

useMemo (function, deps);
function: 어떻게 연산할지 담는 함수
deps: 검사할 특정 값을 담은 배열

배열 안의 값이 바뀌면 함수를 호출하여 연산하고, 값이 바뀌지 않았다면 이전에 연산한 값을 재사용한다

메모이제이션된 값을 반환한다. 의존성(dependency)가 변경되었을때만 메모이제이션된 값을 다시 계산한다. 이를 통해 불필요한 연산을 줄일 수 있다.

적용예시

문제?
input 값이 변할때 (string의 state 변화로 다시 렌더링 될 때) 마다 sum() 함수가 호출된다 이 sum()함수는 stringList에 새로운 문자열이 추가될때에만 불려지는 함수인데, 계속 호출되므로 비효율적이라고 할 수 있다. 이때 useMemo를 적용해보겠다.

적용
stringList가 변경될때에만 sum() 함수가 호출되도록 했습니다. 첫번째 파라미터에는 어떻게 연산할지 정의하는 함수, 두번째 파라미터에는 deps배열 안에 stringList를 넣어줬습니다. 이 배열 안에 넣은 내용이 바뀌면 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용하게 되는 것이죠.

const result = useMemo(()=> sum(stringList), [stringList]);

return (
  <div>
    <input type="text" onChange={(e) => {setString(e.target.value)}/>
    <button onClick={insert}>문자열 추가</button>
    {result}
  </div>
)

정리

성능 최적화를 위해 사용할 수는 있지만 의미상으로 보장이 있다고 생각하지는 마세요. useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하고 그것을 추가하여 성능을 최적화하세요

😀 useCallback

필요성

useCallbackuseMemo와 비슷한 함수이다. React에서 컴포넌트가 다시 렌더링될때 컴포넌트 안에 선언된 함수들을 새로 생성된다. 컴포넌트의 렌더링이 자주 발생하거나 렌더링해야할 컴포넌트의 개수가 많아지면 최적화해주는 것이 효율적이며 이때 useCallback hook을 활용할 수 있다.

사용법

const memoizedCallback = useCallback (()=>{
	doSomething(a,b);
}, [a,b]);

메모이제이션된 콜백을 반환
메모이제이션된 콜백은 콜백의 의존성(dependency)이 변경되었을때만 변경된다

useCallback (function, deps);
function: 생성하고 싶은 함수
deps: 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시하는 배열

🎈 두번째 파라미터가 빈배열 []일 경우
deps 배열이 비어있는 경우, 컴포넌트가 렌더링될 때 만들었던 함수를 계속해서 재사용하게 된다. 반대로, 배열 안에 값이 있으면 해당 값들이 변경될때 새로 만들어진 함수를 사용하게 되는 것이다.

적용예시

아까와 동일한 예시입니다. 이 예시에서 insert, handleChange, sum 등의 함수는 렌더링될때마다 계속 재생성된다.
이러한 비효율적인 문제를 해결하기위해 useCallback hook을 사용할 수 있다.

이렇게 각 함수를 useCallback으로 감싸줬다.
handleChangesum 함수는 최초로 렌더링될때만 함수가 생성되고 그 이후에서 생성되지 않는 반면, insert함수는 deps 배열 안에 있는 stringstringList가 변경될 때만 함수를 재생성한다.

정리하면

  • 두번째 인자에 빈 배열인 경우: 최초의 렌더링 시에만 함수가 생성되고 이후에는 생성되지 않음 (어떤 상태값에도 반응하지 않음)
  • 두번째 인자에 아무것도 넣지 않은 경우: 모든 상태변화에 반응
  • 두번째 인자에 변수가 들어간 배열의 경우 : 해당 변수의 값이 변경될 때에만 함수를 재생성

즉,  해당 함수안에서 state를 사용할때 (특정 값에 의존할때) 반드시 두번째 인자인 배열 안에 해당 변수를 추가해줘야한다!

😀 Custom Hooks

지금의 react hooks도 충분히 유용하지만, 자신만의 hook을 만들어서 사용하는 경우도 종종 있다. 자신이 필요한 부분에 있어서 기존의 hook이나 기능들을 사용하여 새로운 hook을 만들어 사용해볼 수 있다.

적용예시

//LoginForm.js
import React, { useState, useCallback } from "react";

const LoginForm = () => {
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  const onChangeId = useCallback((e) => {
    setId(e.target.value);
  }, []);

  const onChangePassword = useCallback((e) => {
    setPassword(e.target.value);
  }, []);
  
  return (
    <input name="user-id" value={id} onChange={onChangeId}></input>
    <input name="user-password" type="password" value={password} onChange={onChangePassword}></input>
  )
}

export default LoginForm;

이와 같이 input의 값이 변경될 때 set함수를 통해서 값을 변경하는 비슷한 코드가 반복되는 경우가 있다.
useInput이라는 이름으로 커스텀 훅을 만들어보겠다.

// hooks폴더 > useInput.js

import { useState, useCallback } from "react";
const useInput = (initialValue) => {
  const [value, setValue] = useState(initialValue);
  const handler = useCallback((e)=> {
    setValue(e.target.value);
  }, []);
  return [value, handler];
};

export default useInput;

hooks폴더 안에 useInput 커스텀 훅을 만들었고, LoginForm에서 사용해보겠다.

//LoginForm.js
import React, { useState, useCallback } from "react";
import useInput from "../hooks/useInput";

const LoginForm = () => {
  const [id, onChangeId] = useInput("");
  const [password, onChangePassword] = useInput("");
  
  return (
    <input name="user-id" value={id} onChange={onChangeId}></input>
    <input name="user-password" type="password" value={password} onChange={onChangePassword}></input>
  )
}

export default LoginForm;
profile
기록중
post-custom-banner

0개의 댓글