Skip to main content

Command Palette

Search for a command to run...

[React] 변화의 물결, 동시성 렌더링

Updated
•2 min read•View as Markdown
[React] 변화의 물결, 동시성 렌더링

이 포스트는 React 버전 18.3.1을 기준으로 합니다.

동시성 렌더링은 18 버전부터 정식 도입된 새로운 메커니즘입니다. 이는 아직 불완전하지만, 많은 가능성을 보여주었습니다. Suspense, useDeferredValue 등이 그 결과물이지요. 하지만 그와 동시에 이전에 없던 많은 문제를 불러왔습니다. 이번 포스트에서는 이것이 어떻게 동작하고, 또 어느 부분이 문제가 되는지 알아보도록 하겠습니다.

어떻게 동작하는가

동시성 적용에 가장 중요한 과제는 작업을 어떻게 분할하느냐일 것입니다. React 개발팀은 유휴 시간을 통해 이 문제를 해결했습니다. 동시성 렌더링은 일반 작업보다 우선순위가 낮은 것으로 평가됩니다. 그래서 곧바로 처리하지 않고 유휴 시간 동안에 이루어지도록 한 것이죠.

동시성 렌더링의 이러한 성격은 시간이 조금 소요되는 작업을 위한 것임을 알 수 있습니다. 즉, 결과가 곧바로 반영되어야 하는 작업은 이것이 필요치 않습니다. 오히려 불필요한 대기 시간을 만들어낼 뿐이지요.

이러한 성격을 나타내듯 동시성 렌더링은 반드시 커밋으로 이어지지 않습니다. 렌더링은 여러 번 반복될 수 있고, 불필요한 렌더링은 커밋되지 않고 폐기될 수 있습니다. 문제는 여기서 발생합니다.

무엇이 이를 위험하게 만드는가

렌더링이 커밋되지 않으면 무슨 문제가 생길까요? React에는 부작용 처리를 위한 별도의 기능이 존재합니다. useEffect가 대표적인데요. useEffect에 전달된 콜백은 반드시 커밋 이후 호출(정확히는 페인팅 이후)됩니다.

const state = useMemo(factoryWithSideEffect, deps);
useEffect(() => () => dispose(state), deps);

위 코드에서는 의존성 변경 외의 다른 이유로 useMemo의 캐시가 무효화되지 않음을 가정합니다. 실제로는 그렇지 않으므로 애초에 잘못된 방법입니다. 자세한 내용은 React 공식 문서를 통해 확인할 수 있습니다.

상태 보존의 강력한 보장을 원하는 경우, [React] 당신은 상태를 필요로 하지 않을 것입니다. #2 - 보존(with. useRef) 편을 읽어보세요!

factoryWithSideEffect는 이름에서도 알 수 있듯이 동작에 부작용을 포함합니다. 따라서 useEffect를 통해 정리 함수가 호출되도록 하고 있습니다. 기존에는 통할 법한 방법이지만 동시성 기능을 함께 사용한다면 매우 부적절한 방법입니다. 상술했듯이 렌더링은 반드시 커밋으로 이어지지 않습니다. 따라서 factoryWithSideEffect는 매우 여러 번 호출될 수 있는 데 반해 정리 함수는 단 한 번도 호출되지 않을 수 있습니다.

한 가지 더 유의할 점은 훅의 상태 유지 기능에 관한 것입니다. useState나 useRef 등의 상태 유지는 마운트 후에 효력을 가집니다. 즉, 마운트되지 않은 상황에서 폐기된 렌더링에 대해 상태를 유지하지 않습니다.

이 외에도 일부 기능은 동시성에 특히 취약할 수 있습니다. 대표적으로 useMemo는 마운트 후 상황일지라도 폐기된 렌더링에 대해 캐시를 유지하지 않습니다. 전체 목록은 React 공식 문서 중 StrictMode에서 두 번 호출되는 함수 목록과 동일합니다.

첨언하자면 동시성 렌더링 상황을 시뮬레이션하는 기능인 StrictMode에서는 마운트 시 첫 번째 렌더링은 폐기되고, 두 번째 렌더링만 두 번 커밋됩니다. 한 번의 렌더링을 두 번 커밋하는 상황이므로 버그인 것으로 보이지만 확실한 정보를 확인하지 못했습니다. 따라서 조금 비정상적으로 보일지라도 의도한 동작일 수 있습니다. 만약 의도한 동작일 경우, 프로덕션 환경에서도 발생할 수 있다는 의미가 됩니다. 현재로써는 렌더링과 커밋의 관계를 N:M(N >= 1, M >= 0)으로 상정해야 합니다.

읽어주셔서 감사합니다!

묻고 답하기

개인적인 판단에 의해 적절하다고 여겨지는 경우, 모두가 볼 수 있도록 이곳에 문답이 추가됩니다. 그렇지 않더라도 질문에 대한 답변은 별도로 이루어집니다.

More from this blog

[TypeScript] 자료 구조로 담아내기. #14 - 연결 리스트(with. 이중 연결)

이번 편은 이전 편으로부터 이어집니다. 연결 리스트의 또 다른 형태는 이중 연결 리스트입니다. 후임자의 참조만 저장하는 단일 연결 리스트와 달리 이중 연결 리스트는 선임자의 참조도 함께 저장합니다. interface DoublyLinkedListNode<T> extends DoublyLinkedList<T> { linkBefore(succ: DoublyLinkedListNode<T>): void; linkAfter(pred: D...

May 3, 20253 min read9
[TypeScript] 자료 구조로 담아내기. #14 - 연결 리스트(with. 이중 연결)

[TypeScript] 자료 구조로 담아내기. #12 - 연결 리스트(with. null 노드)

이번 편은 이전 편으로부터 이어집니다. 조금 예리하신 분들은 이전 편에서 다룬 헤드 노드만으로는 충분치 않다는 것을 느끼셨을 것입니다. 최소한 첫 노드에 예외를 부여하는 상황은 피했으나 후임자가 null임을 확인하는 구현 또한 만족스럽지는 않습니다. 카이로의 코끼리 카이로의 코끼리에 대해 알고 계십니까? 링크를 통해 확인하실 수 있습니다. 꽤 재밌으므로 한번 읽어 보시길 추천합니다. 여기서 중요한 아이디어는 끝 위치에 미리 코끼리를 둔다는 것...

Apr 19, 20252 min read12
[TypeScript] 자료 구조로 담아내기. #12 - 연결 리스트(with. null 노드)

[TypeScript] 자료 구조로 담아내기. #11 - 연결 리스트(with. 헤드 노드)

이번 편은 이전 편으로부터 이어집니다. 이전 편에서는 연결 리스트의 접근자는 첫 노드의 선임자가 적절하다는 것을 알았습니다. 얼핏 보기엔 문제없어 보이는데 어떤가요? 최상위 연결 리스트 즉, 가장 처음에 위치한 노드로 시작하는 경우를 생각해 봅시다. 가장 처음에 위치한 노드의 선임자는 무엇인가요? 맞습니다. 선임자가 존재하지 않겠지요. 그럼 어떻게 해야 할까요? 선임자를 가지지 않는 첫 노드를 갖는 연결 리스트에 대해 예외를 적용해야 할까요?...

Apr 12, 20252 min read13
[TypeScript] 자료 구조로 담아내기. #11 - 연결 리스트(with. 헤드 노드)

고라니드로의 블로그

47 posts