
프론트엔드는 단순히 UI만 그리는 영역이라고 생각하기 쉽지만,
실제로는 렌더링 과정에서 자료구조와 알고리즘 개념이 굉장히 많이 사용된다.
특히 React를 사용하다 보면:
useMemo를 쓰는지.map() 남발이 성능에 안 좋은지key가 중요한지Set으로 중복 제거를 하는지같은 것들이 결국 자료구조/알고리즘 개념과 연결된다.
useMemo가 필요한가핵심은:
같은 계산을 매 렌더마다 다시 하지 않기 위해
자료구조/알고리즘 관점에서는:
개념에 가깝다.
예를 들어:
const sorted = users.sort(...)
이 코드가 렌더마다 실행되면:
렌더 횟수 × 정렬 비용
이 계속 발생하게 된다.
정렬은 일반적으로:
O(n log n)
복잡도를 가지기 때문에 데이터가 많아질수록 부담이 커진다.
useMemo는 내부적으로 보면:
{
deps: [...],
cachedValue: result
}
처럼 이전 계산 결과를 저장해두는 개념에 가깝다.
예를 들면:
const sorted = useMemo(() => {
return users.sort(...)
}, [users])
이렇게 작성하면:
users가 변경되지 않는 동안하게 된다.
즉:
불필요한 O(n log n) 연산을 줄이는 것
이 핵심이다.
.map() 남발하면 느릴까핵심은:
배열 순회는 공짜가 아니다
.map()은 기본적으로:
O(n)
복잡도를 가진다.
예를 들어:
users.map(...)
posts.map(...)
comments.map(...)
이런 코드가 렌더마다 반복되면:
O(n) + O(n) + O(n)
비용이 계속 발생한다.
예:
users.map(user => {
posts.map(post => ...)
})
이 경우는:
O(n²)
까지 증가할 수 있다.
React에서 특히 문제가 되는 이유는:
등으로 렌더링이 자주 발생하기 때문이다.
즉 .map()이 렌더마다 계속 반복 실행된다.
const items = useMemo(() => {
return data.map(...)
}, [data])
불필요한 반복 계산을 줄인다.
예:
화면에 보이는 아이템만 렌더링해 렌더 비용을 줄인다.
배열 대신 객체나 Map 형태로 관리한다.
예:
usersById[user.id]
이런 방식은 탐색 비용을 줄일 수 있다.
key가 중요한가이건 React의 Diffing 알고리즘과 연결된다.
React는 이전 배열과 새 배열을 비교할 때:
"누가 누구인지"
판별해야 한다.
예:
[
{ id: 1 },
{ id: 2 },
{ id: 3 }
]
React는 key 기반으로 비교한다.
key={item.id}
index를 쓰면?예를 들어:
A B C
상태에서 앞에 D가 추가되면:
D A B C
가 된다.
하지만 key={index}를 사용하면 React는:
0 = A → D로 착각
1 = B → A로 착각
2 = C → B로 착각
하게 된다.
그 결과:
같은 문제가 발생할 수 있다.
자료구조 관점으로 보면 결국:
고유 식별자(primary key)
문제에 가깝다.
DB에서 id가 중요한 이유와 비슷하다.
Set으로 중복 제거를 할까배열에서 중복 검사를 할 때 보통:
arr.includes(x)
를 사용한다.
하지만 includes는:
O(n)
복잡도를 가진다.
예를 들어:
const result = []
for (const item of items) {
if (!result.includes(item)) {
result.push(item)
}
}
이 방식은 최악의 경우:
O(n²)
까지 증가할 수 있다.
반면 Set은 해시 기반 자료구조라 탐색 비용이 매우 낮다.
const set = new Set(items)
검색은 평균적으로:
O(1)
에 가깝다.
그래서 실무에서는:
const unique = [...new Set(items)]
패턴을 굉장히 자주 사용한다.
예를 들어:
onlineUsers = [...new Set(users)]
처럼 websocket 중복 유저 제거에도 많이 사용된다.
결국 React 성능 최적화도 대부분 자료구조와 알고리즘 개념 위에서 동작한다.