concurrent mode 에서 발생할 수 있는 tearing 이란 무엇일까?
ref - https://github.com/reactwg/react-18/discussions/69
TL;DR
-
UI의 일부가 다른 부분과 일관되지 않게 표시되는 현상
-
동일한 상태를 참조하는 여러 컴포넌트가, 서로 다른 값을 보여주는 문제
-
JavaScript 는 싱글스레드이기 때문에 일반적으로 tearing 은 웹에서 발생하지 않았음. 하지만 React18에서는 Concurrent mode이 렌더링 중에 yield 하기 때문에, 이 문제가 발생할 수 있음!
- 즉, startTransition 이나, Suspense 같은 concurrent 기능을 사용 할 때, React 가 다른 작업이 일어나도록 일시 중지할 수 있음.
- 이러한 일시 중지 사이에, 렌더링에 사용되는 데이터를 변경하는 업데이트가 발생 할 수 있으며, 이로 인하여 UI가 동일한 데이터에 대해 두 가지의 다른 값을 보여주는 엣지 케이스가 발생 할 수 있는 것.
-
이는 React 에만 국한 된 문제는 아니고, 동시성의 필연적인 결과임. 더 reactive 한 경험을 위해 사용자 입력에 응답하기 위하여 다른 렌더링을 중단하면, 렌더링 중인 데이터가 변경되어 사용자 인터페이스가 tearing 되는 상황에 대비해야함.
Example
A버튼을 눌렀을 때 외부 스토어의 상태가 변경된다고 해보자. 그리고 해당 스토어의 값을 구독하고 있는 컴포넌트는 A버튼을 포함하여, B,C 가 있다.
동기적 렌더링일 경우..
- A버튼을 클릭 가능한 것은 B,C 컴포넌트가 모두 렌더링되고난 후 일것이다.
- 따라서 A버튼이 리액트 컴포넌트 트리가 생성되는 도중에 클릭된다고 하여도 스토어의 값이 즉시 변경될 수 없고, 또한 B,C 컴포넌트의 렌더링이 멈추지 않는다.
- 따라서 A버튼 컴포넌트와, B,C 컴포넌트는 동일한 스토어의 값을 참조한 상태로 렌더링된다.
동시성 모드를 지원할 경우..
- A버튼은 바로 클릭 가능하게 된다. (이게 동시성의 핵심임.) 즉, B,C 컴포넌트 트리를 마저 렌더링하지 않아도 A버튼이 바로 responsive 하게 됨.
- 따라서 이 시점에 A버튼은 변경되지 않은 스토어의 값을 참조한 상태로 렌더링 되고, 이 A버튼을 사용자가 클릭하게 되면 스토어의 값은 변경된다.
- 그리고, 이 후에 B,C 컴포넌트의 렌더링이 다시 시작되는데 이 때 B,C 컴포넌트는 변경된 스토어의 값을 참조하고, 이에 따라 렌더링된다.
- 따라서 A버튼과 B,C 컴포넌트가 보여주는 데이터의 값이 달라진다.
그런데 여기서 좀 의문점이 드는게, A버튼을 클릭하면 외부 스토어의 값이 변경되는데, 이에 따라 A버튼이 왜 바로 리렌더링이 안되는가? 였음. 즉, A버튼의 리렌더링이 B,C 컴포넌트의 렌더링보다 우선순위가 낮은가?에 대한 의문이 들었음.
- 이는 당연히 A버튼의 리렌더링이 새로운 렌더링 사이클에서 발생하기 때문에 B,C 버튼의 렌더링보다 시간적으로 늦게 실행됨.
- 즉, 현재 그리고있는 렌더트리를 먼저 처리해야함.
그래서 react 는 useSyncExternalStore 를 만들었음
- 외부 스토어의 tearing 문제를 해결하기 우해 useSyncExternalStore 를 만들었음.
- 동기적 업데이트 (sync) 를 강제하여 tearing 을 방지하고자 하는 목적이이었음.
- 즉, 이건 time-slicing과는 호환되지 않는 결정임. 따라서, de-opt(de-optimization)임.