직접 조작
컴포넌트 전체 하위 트리의 리렌더링을 트리거하지 않고도 직접 변경해야 할 때가 있다. 예를 들어 브라우저에서 React를 사용할 때 DOM 노드를 직접 수정해야 하는 경우가 있는데, 모바일 앱의 뷰에서도 마찬가지다. setNativeProps
는 DOM 노드에 직접 속성을 설정하는 것과 동일한 역할을 React Native에서 수행한다.
setNativeProps
는 빈번한 리렌더링이 성능 병목 현상을 일으킬 때 사용한다!
직접 조작은 자주 사용하는 도구가 아니다. 일반적으로 컴포넌트 계층 구조를 렌더링하고 많은 뷰를 조정하는 오버헤드를 피하기 위해 지속적인 애니메이션을 만들 때만 사용한다. setNativeProps
는 명령형이며 상태를 네이티브 레이어(DOM, UIView 등)에 저장한다. React 컴포넌트 내부에 저장하지 않기 때문에 코드를 이해하기 어려워질 수 있다.
사용하기 전에 setState
와 shouldComponentUpdate
로 문제를 해결할 수 있는지 먼저 시도해 보자.
TouchableOpacity와 setNativeProps
TouchableOpacity는 내부적으로 setNativeProps
를 사용해 자식 컴포넌트의 투명도를 업데이트한다:
const viewRef = useRef<View>();
const setOpacityTo = useCallback(value => {
// 애니메이션 관련 코드 생략
viewRef.current.setNativeProps({
opacity: value,
});
}, []);
이렇게 하면 아래와 같은 코드를 작성할 수 있고, 자식 컴포넌트가 그 사실을 알 필요 없이 탭에 반응해 투명도가 업데이트된다. 또한 자식 컴포넌트의 구현을 변경할 필요도 없다:
<TouchableOpacity onPress={handlePress}>
<View>
<Text>Press me!</Text>
</View>
</TouchableOpacity>
만약 setNativeProps
를 사용할 수 없다면, 투명도 값을 상태에 저장하고 onPress
가 발생할 때마다 그 값을 업데이트하는 방식으로 구현할 수 있다:
const [buttonOpacity, setButtonOpacity] = useState(1);
return (
<TouchableOpacity
onPressIn={() => setButtonOpacity(0.5)}
onPressOut={() => setButtonOpacity(1)}>
<View style={{opacity: buttonOpacity}}>
<Text>Press me!</Text>
</View>
</TouchableOpacity>
);
이 방식은 원래 예제에 비해 계산 비용이 많이 든다. React는 투명도가 변경될 때마다 컴포넌트 계층을 다시 렌더링해야 한다. 뷰와 그 자식들의 다른 속성이 변경되지 않았더라도 말이다. 보통은 이런 오버헤드가 문제가 되지 않지만, 연속적인 애니메이션을 수행하거나 제스처에 반응할 때는 컴포넌트를 신중하게 최적화하면 애니메이션의 정확도를 높일 수 있다.
setNativeProps
의 구현을 NativeMethodsMixin에서 살펴보면, 이는 RCTUIManager.updateView
를 감싼 래퍼임을 알 수 있다. 이 함수 호출은 리렌더링 시에도 동일하게 발생한다. 자세한 내용은 ReactNativeBaseComponent의 receiveComponent를 참고하라.
합성 컴포넌트와 setNativeProps
합성 컴포넌트는 네이티브 뷰로 구성되지 않기 때문에 setNativeProps
를 직접 호출할 수 없다. 다음 예제를 살펴보자:
- TypeScript
- JavaScript
이 코드를 실행하면 즉시 Touchable child must either be native or forward setNativeProps to a native component
라는 오류가 발생한다. 이 오류는 MyButton
이 직접적으로 네이티브 뷰로 구성되지 않았기 때문에 발생한다. createReactClass
로 컴포넌트를 정의할 때 스타일 프로퍼티를 설정할 수 없는 것과 마찬가지로, 이 경우에도 스타일 프로퍼티를 자식 컴포넌트로 전달해야 한다. 단, 네이티브 컴포넌트를 감싸는 경우는 예외다. 따라서 setNativeProps
를 네이티브 뷰로 구성된 자식 컴포넌트로 전달해야 한다.
setNativeProps
를 자식 컴포넌트로 전달하기
View
컴포넌트의 ref에는 setNativeProps
메서드가 존재한다. 따라서 커스텀 컴포넌트에서 ref를 전달하려면, 해당 컴포넌트가 렌더링하는 <View />
컴포넌트 중 하나로 ref를 전달하면 된다. 이렇게 하면 커스텀 컴포넌트에서 setNativeProps
를 호출했을 때, 내부에 감싸진 View
컴포넌트에서 직접 setNativeProps
를 호출한 것과 동일한 효과를 얻을 수 있다.
- TypeScript
- JavaScript
이제 TouchableOpacity
안에서 MyButton
을 사용할 수 있다!
{...props}
를 통해 모든 props를 자식 뷰로 전달한 이유를 눈치챘을 것이다. TouchableOpacity
는 사실 합성 컴포넌트이기 때문에, 자식 컴포넌트가 setNativeProps
를 지원할 뿐만 아니라 터치 처리를 수행해야 한다. 이를 위해 TouchableOpacity
는 다양한 props를 자식 컴포넌트로 전달한다. 이 props는 TouchableOpacity
컴포넌트로 다시 콜백을 호출한다. 반면 TouchableHighlight
는 네이티브 뷰를 기반으로 하며, setNativeProps
만 구현하면 된다.
setNativeProps로 TextInput 값 수정하기
setNativeProps
의 또 다른 일반적인 사용 사례는 TextInput의 값을 수정하는 것이다. TextInput의 controlled
prop은 bufferDelay
가 낮고 사용자가 매우 빠르게 입력할 때 문자를 누락시킬 수 있다. 일부 개발자는 이 prop을 완전히 건너뛰고, 대신 필요할 때 setNativeProps
를 사용해 TextInput 값을 직접 조작하는 것을 선호한다. 예를 들어, 다음 코드는 버튼을 탭할 때 입력을 수정하는 방법을 보여준다.
- TypeScript
- JavaScript
clear
메서드를 사용해 현재 입력 텍스트를 지우는 것도 동일한 방식으로 가능하다.
렌더 함수와의 충돌 방지
렌더 함수가 관리하는 속성을 업데이트하면 예측하기 어렵고 혼란스러운 버그가 발생할 수 있다. 컴포넌트가 리렌더링될 때마다 해당 속성이 변경되면, setNativeProps
로 이전에 설정한 값이 완전히 무시되고 덮어쓰여지기 때문이다.
setNativeProps와 shouldComponentUpdate
shouldComponentUpdate
를 지능적으로 적용하면 변경되지 않은 컴포넌트 서브트리를 재조정하는 데 드는 불필요한 오버헤드를 피할 수 있다. 이를 통해 setNativeProps
대신 setState
를 사용해도 충분히 성능을 유지할 수 있다.
다른 네이티브 메서드
여기서 설명하는 메서드들은 React Native에서 제공하는 기본 컴포넌트 대부분에서 사용할 수 있다. 하지만 네이티브 뷰로 직접 지원되지 않는 합성 컴포넌트에서는 사용할 수 없다는 점에 주의해야 한다. 일반적으로 여러분이 직접 정의한 대부분의 컴포넌트는 여기에 해당한다.
measure(callback)
주어진 뷰의 화면 상 위치, 너비, 높이를 측정하고 비동기 콜백을 통해 값을 반환한다. 성공하면 콜백이 다음과 같은 인자와 함께 호출된다:
- x
- y
- width
- height
- pageX
- pageY
이 측정값은 네이티브 렌더링이 완료된 후에만 사용할 수 있다. 가능한 한 빨리 측정값이 필요하고 pageX
와 pageY
가 필요하지 않다면, onLayout
속성을 사용하는 것을 고려해보자.
또한 measure()
가 반환하는 너비와 높이는 뷰포트 내 컴포넌트의 크기다. 컴포넌트의 실제 크기가 필요하다면 onLayout
속성을 사용하는 것이 좋다.
measureInWindow(callback)
주어진 뷰의 윈도우 내 위치를 결정하고, 비동기 콜백을 통해 값을 반환한다. React 루트 뷰가 다른 네이티브 뷰에 포함된 경우, 이 메서드는 절대 좌표를 제공한다. 성공 시 콜백은 다음과 같은 인자와 함께 호출된다:
- x
- y
- width
- height
measureLayout(relativeToNativeComponentRef, onSuccess, onFail)
measure()
와 유사하지만, relativeToNativeComponentRef
로 지정한 상위 컴포넌트를 기준으로 뷰의 위치를 측정한다. 이는 반환된 좌표가 상위 뷰의 원점 x
, y
를 기준으로 상대적임을 의미한다.
이 메서드는 relativeToNativeNode
핸들러를 사용해 호출할 수도 있지만, 이 방법은 새로운 아키텍처에서는 더 이상 사용하지 않는다.
- TypeScript
- JavaScript
focus()
주어진 입력 또는 뷰에 포커스를 맞춘다. 트리거되는 정확한 동작은 플랫폼과 뷰의 타입에 따라 달라진다.
blur()
입력 필드나 뷰에서 포커스를 제거한다. focus()
와 반대되는 기능이다.