매 렌더링마다 새 객체 생성 방지 위해 useMemo 사용함

상황
WYSIWIG 에디터를 사용하는 AdminEditor.tsx에서 발생. 이미지 버튼 클릭 시 기존 내장 기능이 아니라 handleClickImage 함수 실행되게 핸들러 연결하는 중이었다.
이를 위해 modules 변수 선언을 컴포넌트 내부로 들여와야 했다.
그랬더니 다음과 같은 에러 발생:
# Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'delta')
## Call Stack
### ReactQuill.componentDidUpdatenode_modules\react-quilllibindex.js (174:1)문제 정의
modules 객체를 컴포넌트 내부에서 직접 정의했더니 렌더링할 때마다 ReactQuill에서 매번 새 객체가 생성되면서 발생한 문제.
참조 안정성 문제의 일종이다.
해결 방법
❌ modules 객체를 컴포넌트 외부로 옮기기
아까 왜 안 되는지 위에서 말했지. handler 함수 쓰려면 컴포넌트 내에 있어야 한다고.
⭕ useMemo 사용
useMemo: 계산 비용이 큰 값을 메모이제이션해서 불필요한 재계산을 방지하는 리액트 내장 훅.
const modules = useMemo(() => {
return {
toolbar: {
container: [
생략
],
handlers: {
image: handleClickImage,
},
},
};
}, []);맨 아래 의존성 배열에 빈 배열을 넣어 처음 한 번만 실행되게 한다. 의존성 배열 안에 든 게 바뀔 때마다 내부 콜백이 실행되는 거니까.
그래서 처음에 한 번만 객체가 생성되고, 이후로는 메모이제이션되어 렌더링 시마다 기존에 저장해 둔 값을 참조하게 된다.
내가 전에 알던 내용과 이번에 배운 거 비교
useMemo
불필요한 리렌더링을 줄여 리액트에서 성능을 최적화하는 한 방법으로서 메모이제이션에 대해 배웠었다. 메모이제이션은 이미 계산된 결과를 저장해두고 다음에 같은 계산을 반복하는 대신 불러오는 방법.
React.memo로 컴포넌트를 감싸면 부모 컴포넌트가 리렌더링되더라도 자식의 props는 변경되지 않았을 경우 리렌더링하지 않는다.하지만 props에 전달되는 값이 함수나 객체일 경우에는, 부모가 리렌더링될 때 새로운 함수나 객체가 생성되기 때문에 props가 변경된 것으로 인식되는 한계
useCallback: 함수 메모이제이션.useMemo: 객체, 또는 긴 배열의 필터링 결과 같이 비용이 큰 계산을 메모이제이션.
다만
메모이제이션에도 비용이 드니까, 많이 복잡한 계산이 아니면 오히려 성능이 저하되므로 사용 지양.
리액트가 점점 더 효율적인 리렌더링 방식으로 업데이트되고있기 때문에, 점점 사용이 줄어드는 추세.
라고 배웠었다.
특히 두 번째 줄 때문에 useMemo에 대해 그리 중요하게 다루지 않았더랬다.
이번엔 그동안 배웠던 성능 최적화가 아니라, 객체 참조 안정성 용도로 useMemo를 사용했다.
이럴 때 여전히 유용한 도구였군.
useRef
예전에 렌더링 영향 안 주는 값 저장하려고 useRef를 썼던 기억이 나서, 그거랑은 뭐가 다른 건지 정리해봤다.
useMemo: 의존성 변경 시에만 함수 실행 - 빈 배열일 땐 첫 한 번만 객체 생성됨.useRef: 매 렌더링마다 새 객체를 만들지만, 그 값의 변경이 리렌더링을 유발하지 않음.
