Skip to main content
Version: Next

직접 조작

컴포넌트 전체 하위 트리의 리렌더링을 트리거하지 않고도 직접 변경해야 할 때가 있다. 예를 들어 브라우저에서 React를 사용할 때 DOM 노드를 직접 수정해야 하는 경우가 있는데, 모바일 앱의 뷰에서도 마찬가지다. setNativeProps는 DOM 노드에 직접 속성을 설정하는 것과 동일한 역할을 React Native에서 수행한다.

caution

setNativeProps는 빈번한 리렌더링이 성능 병목 현상을 일으킬 때 사용한다!

직접 조작은 자주 사용하는 도구가 아니다. 일반적으로 컴포넌트 계층 구조를 렌더링하고 많은 뷰를 조정하는 오버헤드를 피하기 위해 지속적인 애니메이션을 만들 때만 사용한다. setNativeProps는 명령형이며 상태를 네이티브 레이어(DOM, UIView 등)에 저장한다. React 컴포넌트 내부에 저장하지 않기 때문에 코드를 이해하기 어려워질 수 있다.

사용하기 전에 setStateshouldComponentUpdate로 문제를 해결할 수 있는지 먼저 시도해 보자.

TouchableOpacity와 setNativeProps

TouchableOpacity는 내부적으로 setNativeProps를 사용해 자식 컴포넌트의 투명도를 업데이트한다:

tsx
const viewRef = useRef<View>();
const setOpacityTo = useCallback(value => {
// 애니메이션 관련 코드 생략
viewRef.current.setNativeProps({
opacity: value,
});
}, []);

이렇게 하면 아래와 같은 코드를 작성할 수 있고, 자식 컴포넌트가 그 사실을 알 필요 없이 탭에 반응해 투명도가 업데이트된다. 또한 자식 컴포넌트의 구현을 변경할 필요도 없다:

tsx
<TouchableOpacity onPress={handlePress}>
<View>
<Text>Press me!</Text>
</View>
</TouchableOpacity>

만약 setNativeProps를 사용할 수 없다면, 투명도 값을 상태에 저장하고 onPress가 발생할 때마다 그 값을 업데이트하는 방식으로 구현할 수 있다:

tsx
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를 직접 호출할 수 없다. 다음 예제를 살펴보자:

이 코드를 실행하면 즉시 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를 호출한 것과 동일한 효과를 얻을 수 있다.

이제 TouchableOpacity 안에서 MyButton을 사용할 수 있다!

{...props}를 통해 모든 props를 자식 뷰로 전달한 이유를 눈치챘을 것이다. TouchableOpacity는 사실 합성 컴포넌트이기 때문에, 자식 컴포넌트가 setNativeProps를 지원할 뿐만 아니라 터치 처리를 수행해야 한다. 이를 위해 TouchableOpacity는 다양한 props를 자식 컴포넌트로 전달한다. 이 props는 TouchableOpacity 컴포넌트로 다시 콜백을 호출한다. 반면 TouchableHighlight는 네이티브 뷰를 기반으로 하며, setNativeProps만 구현하면 된다.

setNativeProps로 TextInput 값 수정하기

setNativeProps의 또 다른 일반적인 사용 사례는 TextInput의 값을 수정하는 것이다. TextInput의 controlled prop은 bufferDelay가 낮고 사용자가 매우 빠르게 입력할 때 문자를 누락시킬 수 있다. 일부 개발자는 이 prop을 완전히 건너뛰고, 대신 필요할 때 setNativeProps를 사용해 TextInput 값을 직접 조작하는 것을 선호한다. 예를 들어, 다음 코드는 버튼을 탭할 때 입력을 수정하는 방법을 보여준다.

clear 메서드를 사용해 현재 입력 텍스트를 지우는 것도 동일한 방식으로 가능하다.

렌더 함수와의 충돌 방지

렌더 함수가 관리하는 속성을 업데이트하면 예측하기 어렵고 혼란스러운 버그가 발생할 수 있다. 컴포넌트가 리렌더링될 때마다 해당 속성이 변경되면, setNativeProps로 이전에 설정한 값이 완전히 무시되고 덮어쓰여지기 때문이다.

setNativeProps와 shouldComponentUpdate

shouldComponentUpdate를 지능적으로 적용하면 변경되지 않은 컴포넌트 서브트리를 재조정하는 데 드는 불필요한 오버헤드를 피할 수 있다. 이를 통해 setNativeProps 대신 setState를 사용해도 충분히 성능을 유지할 수 있다.

다른 네이티브 메서드

여기서 설명하는 메서드들은 React Native에서 제공하는 기본 컴포넌트 대부분에서 사용할 수 있다. 하지만 네이티브 뷰로 직접 지원되지 않는 합성 컴포넌트에서는 사용할 수 없다는 점에 주의해야 한다. 일반적으로 여러분이 직접 정의한 대부분의 컴포넌트는 여기에 해당한다.

measure(callback)

주어진 뷰의 화면 상 위치, 너비, 높이를 측정하고 비동기 콜백을 통해 값을 반환한다. 성공하면 콜백이 다음과 같은 인자와 함께 호출된다:

  • x
  • y
  • width
  • height
  • pageX
  • pageY

이 측정값은 네이티브 렌더링이 완료된 후에만 사용할 수 있다. 가능한 한 빨리 측정값이 필요하고 pageXpageY가 필요하지 않다면, onLayout 속성을 사용하는 것을 고려해보자.

또한 measure()가 반환하는 너비와 높이는 뷰포트 내 컴포넌트의 크기다. 컴포넌트의 실제 크기가 필요하다면 onLayout 속성을 사용하는 것이 좋다.

measureInWindow(callback)

주어진 뷰의 윈도우 내 위치를 결정하고, 비동기 콜백을 통해 값을 반환한다. React 루트 뷰가 다른 네이티브 뷰에 포함된 경우, 이 메서드는 절대 좌표를 제공한다. 성공 시 콜백은 다음과 같은 인자와 함께 호출된다:

  • x
  • y
  • width
  • height

measureLayout(relativeToNativeComponentRef, onSuccess, onFail)

measure()와 유사하지만, relativeToNativeComponentRef로 지정한 상위 컴포넌트를 기준으로 뷰의 위치를 측정한다. 이는 반환된 좌표가 상위 뷰의 원점 x, y를 기준으로 상대적임을 의미한다.

note

이 메서드는 relativeToNativeNode 핸들러를 사용해 호출할 수도 있지만, 이 방법은 새로운 아키텍처에서는 더 이상 사용하지 않는다.

focus()

주어진 입력 또는 뷰에 포커스를 맞춘다. 트리거되는 정확한 동작은 플랫폼과 뷰의 타입에 따라 달라진다.

blur()

입력 필드나 뷰에서 포커스를 제거한다. focus()와 반대되는 기능이다.