직접 조작
전체 서브트리의 리렌더링을 트리거하기 위해 상태나 프로퍼티를 사용하지 않고 컴포넌트를 직접 변경해야 할 때가 있다. 예를 들어 브라우저에서 React를 사용할 때, DOM 노드를 직접 수정해야 하는 경우가 있으며, 모바일 앱의 뷰에서도 마찬가지다. setNativeProps
는 DOM 노드에 직접 프로퍼티를 설정하는 것과 동일한 기능을 React Native에서 제공한다.
빈번한 리렌더링으로 인해 성능 병목 현상이 발생할 때만 setNativeProps
를 사용한다!
직접 조작은 자주 사용할 도구가 아니다. 일반적으로 컴포넌트 계층 구조를 렌더링하고 여러 뷰를 조정하는 오버헤드를 피하기 위해 지속적인 애니메이션을 만들 때만 사용한다. setNativeProps
는 명령형이며 상태를 네이티브 레이어(DOM, UIView 등)에 저장한다. 이는 React 컴포넌트 내부가 아니므로 코드를 이해하기 어렵게 만든다.
setNativeProps
를 사용하기 전에 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
로 컴포넌트를 정의할 때 스타일 prop을 설정해도 작동하지 않는 것과 유사하다. 스타일 prop을 자식 컴포넌트로 전달해야 하거나, 네이티브 컴포넌트를 감싸지 않는 한 작동하지 않는다. 마찬가지로, setNativeProps
를 네이티브 뷰로 지원되는 자식 컴포넌트로 전달해야 한다.
setNativeProps
를 자식 컴포넌트에 전달하기
View
컴포넌트의 ref에는 setNativeProps
메서드가 존재하므로, 커스텀 컴포넌트의 ref를 렌더링하는 <View />
컴포넌트 중 하나로 전달하기만 하면 된다. 이는 커스텀 컴포넌트에서 setNativeProps
를 호출하는 것이, 감싸진 View
컴포넌트 자체에서 setNativeProps
를 호출하는 것과 동일한 효과를 가진다는 의미이다.
- TypeScript
- JavaScript
이제 TouchableOpacity
안에서 MyButton
을 사용할 수 있다!
{...props}
를 사용해 모든 props를 자식 뷰에 전달한 것을 눈치챘을 것이다. 이는 TouchableOpacity
가 실제로 합성(composite) 컴포넌트이기 때문이다. 따라서 자식 컴포넌트의 setNativeProps
에 의존할 뿐만 아니라, 자식 컴포넌트가 터치 처리를 수행하도록 요구한다. 이를 위해 TouchableOpacity
컴포넌트로 다시 호출되는 다양한 props를 전달한다. 반면 TouchableHighlight
는 네이티브 뷰를 기반으로 하며, 오직 setNativeProps
만 구현하면 된다.
TextInput 값 수정을 위한 setNativeProps 사용
setNativeProps
의 또 다른 일반적인 사용 사례는 TextInput의 값을 수정하는 것이다. TextInput의 controlled
prop은 bufferDelay
가 낮고 사용자가 매우 빠르게 입력할 때 문자를 누락시킬 수 있다. 일부 개발자는 이 prop을 완전히 생략하고, 필요할 때 setNativeProps
를 사용해 TextInput 값을 직접 조작하는 것을 선호한다. 예를 들어, 다음 코드는 버튼을 탭할 때 입력 값을 수정하는 방법을 보여준다:
- TypeScript
- JavaScript
clear
메서드를 사용해 TextInput을 지울 수도 있다. 이 방법은 동일한 접근 방식을 사용해 현재 입력된 텍스트를 지운다.
렌더 함수와의 충돌 방지
렌더 함수에서 관리하는 속성을 업데이트하면 예측하기 어렵고 혼란스러운 버그가 발생할 수 있다. 컴포넌트가 리렌더링되고 해당 속성이 변경될 때마다, 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()
와 반대되는 기능을 수행한다.