개념
: useMemo 메모이제이션된 값을 반환하는
useMemo 는 특정 결과값을 재사용 할 때 사용한다. useMemo를 사용하여 값을 캐시하면 해당 값은 렌더링 중에 한번 계산이 되고 이후 렌더링에서는 의존성 배열의 값이 변하지 않는다면 이전에 계산한 값을 재사용한다.
React.memo는 Higher-Order Components(HOC)
Higher-Order Components(HOC) 란 컴포넌트를 인자로 받아 새로운 컴포넌트롤 다시 return해주는 함수이다.
props를 UI에 활용하는 반면에, higher-order component는 인자로 받은 컴포넌트를 새로운 별도의 컴포넌트로 만든다. HOC는 리액트의 API가 아니라 리액트가 컴포넌트를 구성하는데 있어서의 일종의 패턴이라고 보면된다.
메모이 제이션이란?
값비싼 함수 호출의 결과를 캐싱하고 동일한 입력이 다시 발생할 때 캐싱된 결과를 반환하는 프로그래밍 기술이다. 이 기술은 동일한 입력으로 여러 번 호출되는 함수 또는 컴포넌트가 있을 때 React에서 유용할 수 있다.
즉, 메모이제이션을 사용하면 동일한 결과를 불필요하게 계산하지 않고, 캐싱된 결과를 반환할 수 없다.
useCallback, useMemo를 사용하면 메모이제이션 훅을 통해 성능을 향상시키고 코드의 복잡성을 줄일 수 있다.
- 첫번째 인자에는 값을 연산하고 반환하는 함수를 넣어준다.
- 두번째 인자에는 의존성 배열을 넣어준다. (특정 값 a,b 가 변경되었을 때 다시 연산이 필요하다고 알려주는 배열임)
useMemo 의존성 배열에 있는 상태나 props가 변경되지 않는다면, 해당 함수는 다시 생성되지 않는다.
첫번째 인자로 넘긴 값을 두번째 인자로 넘긴 의존성 배열내의 값이 변경되기 전까지 저장하고 재사용할 수 있게 해준다.
만약 useMemo를 사용하지 않는다면, 아래와 같은 함수는 컴포넌트가 렌더링 될 때마다 새롭게 생성된다.
만약 컴포넌트가 같은 props를 받을 때 같은 결과를 렌더링한다면 React.memo를 사용하여 불필요한 컴포넌트 렌더링을 방지할 수 있다.
즉, 컴포넌트에 같은 props가 들어온다면 리액트는 컴포넌트 렌더링 과정을 스킵하고 마지막에 렌더링된 결과를 재사용한다.
React.memo는 오직 props가 변경됐는지 아닌지만 체크한다. 만약 React.memo에 감싸진 함수형 컴포넌트가 함수 내부에서 useState나 useContext같은 훅을 사용하고 있다면, state나 context가 변경될 때마다 리렌더링된다.
기본적으로 props로 들어온 object는 shallow compare(얕은 비교)로 비교한다. 즉, props로 들어온 number, string과 같은 scarlar 값은 실제 값이 동일한가를 비교하지만, object의 경우 scarlar 값과 달리 같은 값을 'reference(참조)'하고 있는지를 비교 한다.
// 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
}
위 코드를 보면 이전의 값들과 현재의 값이 다른지 비교를 한후, 값이 다르지 않다면 저장되어있던 이전 상태를 보여주는 메모이제이션을 한다.
구체적으로, memoizedState
에 [value
, deps
] 값을 저장하고, 이전의 PrevDeps
값과 비교한다. 만약 변화가 없다면 이전의 값을 그대로 반환하고, 아니라면 useMemo에 새로 들어온 값과 함수를 실행하여 연산을 하고 해당 값을 memoizedState
에 새로 저장하는 방식으로 동작한다.
React.memo와 useMemo 모두 props가 변하지 않으면(이전 props와 동일하면) 인자로 넘긴 함수는 재실행되지 않고, 이전의 메모이즈된 결과를 반환한다는 점에서 공통점이있다.
아래 React.memo와 useMemo를 사용한 코드를 보면 두가지 코드는 props.name의 값이 변하지 않는다면 리렌더링 되지 않고 이전의 값을 반환한다는 점에서 동일하게 동작한다.
React.memo는 HOC, useMemo는 hook이다.
React.memo는 HOC이기 때문에 클래스형 컴포넌트, 함수형 컴포넌트 모두 사용 가능하지만, useMemo는 hook이기 때문에 오직 함수형 컴포넌트 안에서만 사용 가능하다.
useMemo로 전달된 함수는 렌더링 중에 실행된다. 통상적으로 렌더링 중에는 하지 않는 것을 이 함수 내에서 하면 안된다. 예를 들어, 사이드 이펙트(side effects)는 useEffect에서 하는 일이지 useMemo에서 하는 일이 아닙니다.
사이드 이펙트(SideEffect)
개념
: 컴포넌트의 렌더링과 관련이 없는 작업을 의미한다. 컴포넌트의 상태나 UI외부에 영향을 미치는 작업
ex) 네트워크 요청, 데이터베이스 업데이트, DOM조작, 타이머 설정, 이벤트 헨들링
연산을 최소화 하고 효율적인 코드를 위해서 useMemo가 유용하다는 것을 알 수 있다. 하지만, useMemo를 사용하기 전 알아야 할 것이 있다.
- 계산 결과가 메모되어 있으므로 계산에 대한 입력이 변경되지 않으면 업데이트되지 않는다. 즉, useMemo는 계산에 대한 입력이 일정하게 유지되는 경우에만 유용하다.
- useMemo는 메모된 결과에 액세스할 때마다 계속 연산을 수행해야 하기 때문에 잦은 변동이 있는 경우 오히려 성능이 악화될 수 있다.
- useMemo는 값을 재활용하기 위해 따로 메모리를 사용하기 때문에 불필요한 값까지 메모이제이션 해버리면 오히려 메모리를 낭비할 수 있다.
연산이 매우 복잡한 계산식이 아닌곳에도 useMemo를 남발한다면, 성능상의 이점 보다는 오히려 코드를 복잡하게 만들어 유지보수를 어렵게 할 위험도 있다.