RN docs - Gesture responder system

Exploring the Gesture Responder System in React Native

August 27, 2024


제스처 응답 시스템(Gesture Responder System)

제스처 응답 시스템은 앱 내에서 제스처의 생명 주기를 관리합니다. 사용자의 터치가 어떤 의도인지 파악하는 과정에서 터치는 여러 단계를 거칠 수 있습니다. 예를 들어, 앱은 터치가 스크롤을 위한 것인지, 위젯을 슬라이드하는 것인지, 혹은 탭을 하는 것인지 결정해야 합니다. 심지어 터치 도중에도 이러한 의도가 변할 수 있습니다. 또한, 여러 개의 터치가 동시에 발생할 수도 있습니다.

이러한 터치 상호작용을 부모 또는 자식 컴포넌트에 대한 추가적인 정보 없이도 컴포넌트들이 협상할 수 있도록 하기 위해 터치 응답 시스템이 필요합니다.

베스트 프랙티스

앱의 사용성을 높이기 위해 각 액션은 다음과 같은 속성을 가져야 합니다:

피드백/하이라이트 - 사용자가 터치를 통해 어떤 요소가 반응하고 있는지, 제스처를 해제했을 때 어떤 일이 일어날지를 시각적으로 보여주어야 합니다.
취소 가능성 - 사용자가 액션을 실행할 때, 터치 중간에 손가락을 다른 곳으로 드래그하여 액션을 취소할 수 있어야 합니다.
이러한 기능들은 사용자가 앱을 사용할 때 실수에 대한 두려움 없이 실험하고 상호작용할 수 있도록 하여 더 편안하게 느끼게 만듭니다.

TouchableHighlight와 Touchable*

응답자 시스템은 복잡할 수 있기 때문에, 우리는 "탭할 수 있는" 요소들을 위한 추상적인 Touchable 구현을 제공하고 있습니다. 이 구현은 응답자 시스템을 사용하며, 탭 상호작용을 선언적으로 구성할 수 있게 해줍니다. 웹에서 버튼이나 링크를 사용할 때와 동일한 방식으로 TouchableHighlight를 사용할 수 있습니다.

응답자 생명 주기(Responder Lifecycle)

View는 적절한 협상 메서드를 구현함으로써 터치 응답자가 될 수 있습니다. View가 응답자가 되기를 원하는지 묻기 위해 두 가지 메서드를 사용할 수 있습니다:

View.props.onStartShouldSetResponder: evt => true - 이 View가 터치 시작 시 응답자가 되기를 원하는가?
View.props.onMoveShouldSetResponder: evt => true - View가 응답자가 아닐 때, 터치가 이동할 때마다 호출됩니다. 이 View가 터치 응답성을 "요청"하기를 원하는가?
만약 View가 true를 반환하고 응답자가 되려고 시도하면, 다음 중 하나가 발생합니다:

View.props.onResponderGrant: evt => - 이제 View가 터치 이벤트에 응답합니다. 이 시점에서 하이라이트를 표시하여 사용자가 무슨 일이 일어나고 있는지 보여줄 수 있습니다.
View.props.onResponderReject: evt => - 현재 다른 요소가 응답자이며, 이를 해제하지 않을 것입니다.
View가 응답자일 경우, 다음 핸들러들이 호출될 수 있습니다:

View.props.onResponderMove: evt => - 사용자가 손가락을 움직이고 있습니다.
View.props.onResponderRelease: evt => - 터치가 종료될 때, 즉 "터치업" 시점에 호출됩니다.
View.props.onResponderTerminationRequest: evt => true - 다른 요소가 응답자가 되기를 원합니다. 이 View가 응답자를 해제해야 할까요? true를 반환하면 해제가 허용됩니다.
View.props.onResponderTerminate: evt => - View에서 응답자가 해제되었습니다. onResponderTerminationRequest 호출 이후 다른 View가 응답자가 될 수 있으며, iOS에서 제어 센터나 알림 센터로 인해 시스템에서 강제로 해제될 수도 있습니다.

evt는 다음과 같은 형태의 합성 터치 이벤트입니다:
nativeEvent
changedTouches - 마지막 이벤트 이후 변경된 모든 터치 이벤트의 배열
identifier - 터치의 ID
locationX - 요소에 상대적인 터치의 X 위치
locationY - 요소에 상대적인 터치의 Y 위치
pageX - 루트 요소에 상대적인 터치의 X 위치
pageY - 루트 요소에 상대적인 터치의 Y 위치
target - 터치 이벤트를 수신하는 요소의 노드 ID
timestamp - 터치의 시간 식별자, 속도 계산에 유용합니다.
touches - 화면의 현재 모든 터치의 배열
캡처 ShouldSet 핸들러
onStartShouldSetResponder와 onMoveShouldSetResponder는 가장 깊은 노드가 먼저 호출되는 버블링 패턴으로 호출됩니다. 이는 여러 View가 *ShouldSetResponder 핸들러에 대해 true를 반환할 때 가장 깊은 컴포넌트가 응답자가 된다는 것을 의미합니다. 이는 대부분의 경우에 바람직한데, 그 이유는 모든 컨트롤과 버튼이 사용 가능하도록 보장해주기 때문입니다.

하지만, 부모 컴포넌트가 반드시 응답자가 되도록 하고 싶을 때도 있습니다. 이 경우 캡처 단계를 사용하여 처리할 수 있습니다. 응답자 시스템이 가장 깊은 컴포넌트에서 버블링되기 전에 캡처 단계가 실행되며, 이때 on*ShouldSetResponderCapture가 호출됩니다. 따라서 부모 View가 자식이 터치 시작 시 응답자가 되는 것을 막고 싶다면, onStartShouldSetResponderCapture 핸들러를 사용해 true를 반환해야 합니다.

View.props.onStartShouldSetResponderCapture: evt => true,
View.props.onMoveShouldSetResponderCapture: evt => true,

이러한 방식을 통해 부모 컴포넌트가 자식보다 우선적으로 응답자가 되도록 제어할 수 있습니다.