React 그리드에서 SelectBox 렌더링이 느렸던 이유와 해결 방법
그리드 셀마다 SelectBox 컴포넌트를 사용하는 화면에서
전체 구조를 정리하고 최적화를 적용했는데도
페이지 이동이나 렌더링이 여전히 느린 문제가 있었다.
원인을 추적해보니, 문제는 SelectBox 내부 로직에 있었다.
문제 상황
- 그리드의 각 셀에 SelectBoxD 컴포넌트 사용
- 셀 수가 많고 페이지 이동 / 필터 시 리렌더가 자주 발생
- 체감상 SelectBox가 원인처럼 느껴짐
SelectBoxD 내부를 살펴보니 다음과 같은 코드가 있었다.
문제 1. renderValue에서 매번 find 실행
const renderValue = () =>
value
? selectList.find(item => item.codeValue == value)?.codeDesc
: placeholder;
왜 문제인가?
- Array.find()는 선형 탐색(O(n))
- 그리드 셀 수 × 리렌더 횟수만큼 반복 실행
- 옵션 개수가 많을수록 성능 저하가 커짐
해결 1. Map으로 라벨 캐시
const labelMap = useMemo(() => {
const m = new Map<string, string>();
for (const item of selectList) {
m.set(item.codeValue, item.codeDesc);
}
return m;
}, [selectList]);
const renderValue = useCallback(() => {
if (!value) return placeholder;
return labelMap.get(value) ?? placeholder;
}, [value, placeholder, labelMap]);
개선 효과
- 렌더마다 find() 제거
- 값 → 라벨 조회를 O(1)로 고정
- 체감 성능 개선이 가장 큼
문제 2. 옵션 목록을 렌더링마다 map으로 생성
{selectList.map(({ codeDesc, codeValue }, index) => (
<MenuItem key={index} value={codeValue}>
{codeDesc}
</MenuItem>
))}
왜 문제인가?
- 렌더될 때마다 map() 실행
- MenuItem React 엘리먼트 배열이 매번 새로 생성됨
- 그리드에서는 이 비용이 누적됨
해결 2. 옵션 JSX를 useMemo로 메모이제이션
const menuItems = useMemo(() => {
return selectList.map(({ codeDesc, codeValue }) => (
<MenuItem key={codeValue} value={codeValue}>
{codeDesc}
</MenuItem>
));
}, [selectList]);
기존의 map 부분은 다음처럼 교체한다.
{menuItems}
주의할 점
- key={index} ❌
- key={codeValue} ⭕ (안정적인 key)
- selectList가 상위 컴포넌트에서 메모이제이션되어 있어야 효과가 있다
useMemo가 왜 메모이제이션인가?
const menuItems = useMemo(() => selectList.map(...), [selectList]);
이 코드는 컴포넌트가 리렌더되어도
selectList의 참조가 바뀌지 않으면
이전에 계산한 menuItems를 그대로 재사용한다.
즉,
렌더마다 새로 만들던 값을, 조건이 같으면 다시 만들지 않는다
이게 메모이제이션.
메모이제이션이 깨지는 경우
<SelectBoxD selectList={rawList.map(...)} />
이렇게 작성하면 매 렌더마다 새 배열이 생성되어
useMemo가 다시 실행된다.
반드시 상위에서 고정해야 한다.
const selectList = useMemo(() => rawList.map(...), [rawList]);
정리
느렸던 이유
- renderValue에서 매번 find() 실행
- 옵션 JSX를 렌더링마다 새로 생성
- 그리드 셀 수 × 리렌더 횟수로 비용 누적
적용한 개선
- find() 제거 → Map 캐시
- 옵션 JSX → useMemo로 메모이제이션
- 안정적인 key 사용
결론
SelectBox 자체가 느린 것이 아니라
렌더링마다 계산과 JSX 생성을 반복한 구조가 문제.