React Concurrent Mode 환경에서 signals의 Tearing 문제를 해결하고, component lifecycle에 안전하게 binding하는 패턴을 설명한다.
Signals in React (V): Avoiding Tearing, Remount Leaks, and Transition Pitfalls
AI 요약
Context
React Concurrent Mode에서는 render와 commit 사이에 snapshot을 여러 번 읽을 수 있다. snapshot이 React에 의해 제어되지 않으면 UI와 DOM이 불일치하는 Tearing 현상이 발생한다. 또한 derived values가 module scope에 장수存活하면 component remount 후에도 subscription이 남아 메모리 leak을 야기한다.
Technical Solution
- Tearing 방지를 위해 직접 .get() 호출 대신 useSyncExternalStore 기반의 useSignalValue hook을 사용한다.
- 불필요한 re-render를 줄이려면 useSignalSelector로 특정 필드만 subscription한다.
- component lifecycle에 binding하려면 useComputed hook을 사용하여 component unmount 시 자동 dispose한다.
- 전역 derived values는 Provider 패턴으로 관리하며 cleanup effect를 등록한다.
- computed 노드는 signal.get() 호출을 통해만 dependency를 tracking하므로 React snapshot을 직접 전달하면 안 된다.
- Transition/Suspense와 연동하려면 signals의 상태를 Suspense resource로 변환하거나 pending 상태를 별도 관리한다.
Impact
useComputed를 통한 lifecycle binding으로 불필요한 subscription 정리로 메모리 사용량 감소, useSignalSelector로 필요한 필드만 subscription하여 불필요한 re-render 방지 효과를 얻을 수 있다.
Key Takeaway
signals를 React에서 사용할 때 React와 signals 간의 책임 경계를 명확히 분리해야 한다. UI/DOM side effects는 React hooks에 맡기고, data side effects만 signals의 createEffect로 처리해야 한다.
실천 포인트
React 18+ 환경에서 signals의 Tearing-free subscription을 구현할 때 직접 .get() 호출 대신 useSyncExternalStore 기반의 useSignalValue hook을 반드시 사용하고, module scope의 derived values는 component lifecycle에 binding하거나 Provider 패턴으로 관리해야 한다.