Manipulação Direta
Às vezes é necessário fazer alterações diretamente em um componente sem usar state/props
para acionar uma nova renderização de toda a subárvore. Ao usar React no navegador, por exemplo, às vezes você precisa modificar diretamente um nó DOM, e o mesmo se aplica a visualizações em aplicativos móveis. setNativeProps
é o equivalente React Native para definir propriedades diretamente em um nó DOM.
ATENÇÃO
Use setNativeProps
quando a nova renderização frequentemente criar um gargalo de desempenho!
A manipulação direta não será uma ferramenta que você utiliza com frequência. Normalmente, você o usará apenas para criar animações contínuas para evitar a sobrecarga de renderizar a hierarquia de componentes e reconciliar muitas visualizações.
setNativeProps
é imperativo e armazena o estado na camada nativa (DOM, UIView, etc.) e não nos componentes do React, o que torna seu código mais difícil de compreender.
Antes de usá-lo, tente resolver seu problema com setState
e shouldComponentUpdate
.
setNativeProps com TouchableOpacity
TouchableOpacity usa setNativeProps
internamente para atualizar a opacidade de seu filho componente:
const viewRef = useRef<View>();
const setOpacityTo = useCallback(value => {
// Redigido: código relacionado à animação
viewRef.current.setNativeProps({
opacity: value,
});
}, []);
Isso nos permite escrever o código a seguir e saber que a criança terá sua opacidade atualizada em resposta aos toques, sem que a criança tenha conhecimento desse fato ou exija quaisquer alterações em sua implementação:
<TouchableOpacity onPress={handlePress}>
<View>
<Text>Press me!</Text>
</View>
</TouchableOpacity>
Vamos imaginar que setNativeProps
não estava disponível. Uma maneira de implementá-lo com essa restrição é armazenar o valor de opacidade no estado e, em seguida, atualizar esse valor sempre que onPress
for acionado:
const [buttonOpacity, setButtonOpacity] = useState(1);
return (
<TouchableOpacity
onPressIn={() => setButtonOpacity(0.5)}
onPressOut={() => setButtonOpacity(1)}>
<View style={{opacity: buttonOpacity}}>
<Text>Press me!</Text>
</View>
</TouchableOpacity>
);
Isso é computacionalmente intensivo em comparação com o exemplo original - o React precisa renderizar novamente a hierarquia de componentes cada vez que a opacidade muda, mesmo que outras propriedades da visualização e de seus filhos não tenham mudado. Normalmente, essa sobrecarga não é uma preocupação, mas ao executar animações contínuas e responder a gestos, otimizar criteriosamente seus componentes pode melhorar a fidelidade de suas animações.
Se você observar a implementação de setNativeProps
em NativeMethodsMixin, você notará que é um wrapper em torno de RCTUIManager.updateView
- esta é exatamente a mesma chamada de função que resulta da nova renderização - consulte [receiveComponent in ReactNativeBaseComponent](https://github.com/facebook/react-native /blob/fb2ec1ea47c53c2e7b873acb1cb46192ac74274e/Libraries/Renderer/oss/ReactNativeRenderer-prod.js#L5793-L5813).
Componentes compostos e setNativeProps
Os componentes compostos não são apoiados por uma visualização nativa, portanto você não pode chamar setNativeProps
neles. Considere este exemplo:
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
const MyButton = props => (
<View style={{marginTop: 50}}>
<Text>{props.label}</Text>
</View>
);
const App = () => (
<TouchableOpacity>
<MyButton label="Press me!" />
</TouchableOpacity>
);
export default App;
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
const MyButton = (props: {label: string}) => (
<View style={{marginTop: 50}}>
<Text>{props.label}</Text>
</View>
);
const App = () => (
<TouchableOpacity>
<MyButton label="Press me!" />
</TouchableOpacity>
);
export default App;
Se você executar isso, verá imediatamente este erro: "Touchable child must either be native or forward setNativeProps to a native component
" (O filho tocável deve ser nativo ou encaminhar setNativeProps para um componente nativo. Isso ocorre porque MyButton
não é apoiado diretamente por uma visualização nativa cuja opacidade deve ser definida. Você pode pensar assim: se você definir um componente com createReactClass
você não esperaria ser capaz de definir um suporte de estilo nele e fazer isso funcionar - você precisaria passar o suporte de estilo para um filho, a menos que você esteja agrupando um componente nativo. Da mesma forma, encaminharemos setNativeProps
para um componente filho com suporte nativo.
Encaminha setNativeProps para um filho
Como o método setNativeProps
existe em qualquer referência a um componente View
, é suficiente encaminhar uma referência em seu componente personalizado para um dos componentes <View />
que ele renderiza. Isso significa que uma chamada para setNativeProps
no componente personalizado terá o mesmo efeito como se você chamasse setNativeProps
no próprio componente View
encapsulado.
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
const MyButton = React.forwardRef((props, ref) => (
<View {...props} ref={ref} style={{marginTop: 50}}>
<Text>{props.label}</Text>
</View>
));
const App = () => (
<TouchableOpacity>
<MyButton label="Press me!" />
</TouchableOpacity>
);
export default App;
import React from 'react';
import {Text, TouchableOpacity, View} from 'react-native';
const MyButton = React.forwardRef<View, {label: string}>((props, ref) => (
<View {...props} ref={ref} style={{marginTop: 50}}>
<Text>{props.label}</Text>
</View>
));
const App = () => (
<TouchableOpacity>
<MyButton label="Press me!" />
</TouchableOpacity>
);
export default App;
Agora você pode usar MyButton
dentro de TouchableOpacity
!
Você deve ter notado que passamos todos os adereços para a visualização filha usando {...props}
. A razão para isso é que TouchableOpacity
é na verdade um componente composto e, portanto, além de depender de setNativeProps
em seu filho, também requer que o filho execute o tratamento de toque. Para fazer isso, ele passa vários adereços que chamam de volta para o componente TouchableOpacity
. TouchableHighlight
, por outro lado, é apoiado por uma visão nativa e requer apenas que implementemos setNativeProps
.
setNativeProps para editar o valor TextInput
Outro caso de uso muito comum de setNativeProps
é editar o valor do TextInput. A propriedade controlled
do TextInput às vezes pode descartar caracteres quando o bufferDelay
está baixo e o usuário digita muito rapidamente. Alguns desenvolvedores preferem ignorar totalmente esta propriedade e, em vez disso, usar setNativeProps
para manipular diretamente o valor TextInput quando necessário. Por exemplo, o código a seguir demonstra a edição da entrada quando você toca em um botão:
import React from 'react';
import {useCallback, useRef} from 'react';
import {
StyleSheet,
TextInput,
Text,
TouchableOpacity,
View,
} from 'react-native';
const App = () => {
const inputRef = useRef(null);
const editText = useCallback(() => {
inputRef.current.setNativeProps({text: 'Edited Text'});
}, []);
return (
<View style={styles.container}>
<TextInput ref={inputRef} style={styles.input} />
<TouchableOpacity onPress={editText}>
<Text>Edit text</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
input: {
height: 50,
width: 200,
marginHorizontal: 20,
borderWidth: 1,
borderColor: '#ccc',
},
});
export default App;
import React from 'react';
import {useCallback, useRef} from 'react';
import {
StyleSheet,
TextInput,
Text,
TouchableOpacity,
View,
} from 'react-native';
const App = () => {
const inputRef = useRef<TextInput>(null);
const editText = useCallback(() => {
inputRef.current?.setNativeProps({text: 'Edited Text'});
}, []);
return (
<View style={styles.container}>
<TextInput ref={inputRef} style={styles.input} />
<TouchableOpacity onPress={editText}>
<Text>Edit text</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
input: {
height: 50,
width: 200,
marginHorizontal: 20,
borderWidth: 1,
borderColor: '#ccc',
},
});
export default App;
Você pode usar o método clear
para limpar o TextInput
que limpa o texto de entrada atual usando a mesma abordagem.
Evitando conflitos com a função render
Se você atualizar uma propriedade que também é gerenciada pela função de renderização, poderá acabar com alguns bugs imprevisíveis e confusos porque sempre que o componente for renderizado novamente e essa propriedade mudar, qualquer valor que tenha sido definido anteriormente em setNativeProps
será completamente ignorado e substituído.
setNativeProps e shouldComponentUpdate
Ao aplicar de forma inteligente shouldComponentUpdate
, você pode evitar a sobrecarga desnecessária envolvida na reconciliação de subárvores de componentes inalteradas a ponto de ter um desempenho suficiente, basta usar setState
em vez de setNativeProps
.
Outros métodos nativos
Os métodos descritos aqui estão disponíveis na maioria dos componentes padrão fornecidos pelo React Native. Observe, entretanto, que eles não estão disponíveis em componentes compostos que não sejam diretamente apoiados por uma visualização nativa. Geralmente, isso incluirá a maioria dos componentes definidos em seu próprio aplicativo.
measure(callback)
Determina a localização na tela, largura e altura na janela de visualização da view fornecida e retorna os valores por meio de um retorno de chamada assíncrono. Se for bem-sucedido, o retorno de chamada será chamado com os seguintes argumentos:
- x
- y
- width
- height
- pageX
- pageY
Observe que essas medidas não estarão disponíveis até que a renderização seja concluída no modo nativo. Se você precisar das medidas o mais rápido possível e não precisar de pageX
e pageY
, considere usar a propriedade onLayout
.
Além disso, a largura e a altura retornadas por measure()
são a largura e a altura do componente na janela de visualização. Se você precisar do tamanho real do componente, considere usar a propriedade onLayout
.
measureInWindow(callback)
Determina a localização da visualização fornecida na janela e retorna os valores por meio de um retorno de chamada assíncrono. Se a visualização raiz do React estiver incorporada em outra visualização nativa, isso fornecerá as coordenadas absolutas. Se for bem-sucedido, o retorno de chamada será chamado com os seguintes argumentos:
- x
- y
- width
- height
measureLayout(relativeToNativeComponentRef, onSuccess, onFail)
Como measure()
, mas mede a visualização relativa a um ancestral, especificado com a referência relativeToNativeComponentRef
. Isso significa que as coordenadas retornadas são relativas à origem x
, y
da visão ancestral.
Observação
Este método também pode ser chamado com um manipulador relativeToNativeNode
(em vez de referência), mas esta variante está obsoleta.
import React, {useEffect, useRef, useState} from 'react';
import {Text, View, StyleSheet} from 'react-native';
const App = () => {
const textContainerRef = useRef(null);
const textRef = useRef(null);
const [measure, setMeasure] = useState(null);
useEffect(() => {
if (textRef.current && textContainerRef.current) {
textRef.current.measureLayout(
textContainerRef.current,
(left, top, width, height) => {
setMeasure({left, top, width, height});
},
);
}
}, [measure]);
return (
<View style={styles.container}>
<View ref={textContainerRef} style={styles.textContainer}>
<Text ref={textRef}>Where am I? (relative to the text container)</Text>
</View>
<Text style={styles.measure}>{JSON.stringify(measure)}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
textContainer: {
backgroundColor: '#61dafb',
justifyContent: 'center',
alignItems: 'center',
padding: 12,
},
measure: {
textAlign: 'center',
padding: 12,
},
});
export default App;
import React, {useEffect, useRef, useState} from 'react';
import {Text, View, StyleSheet} from 'react-native';
type Measurements = {
left: number;
top: number;
width: number;
height: number;
};
const App = () => {
const textContainerRef = useRef<View>(null);
const textRef = useRef<Text>(null);
const [measure, setMeasure] = useState<Measurements | null>(null);
useEffect(() => {
if (textRef.current && textContainerRef.current) {
textRef.current?.measureLayout(
textContainerRef.current,
(left, top, width, height) => {
setMeasure({left, top, width, height});
},
() => {
console.error('measurement failed');
},
);
}
}, [measure]);
return (
<View style={styles.container}>
<View ref={textContainerRef} style={styles.textContainer}>
<Text ref={textRef}>Where am I? (relative to the text container)</Text>
</View>
<Text style={styles.measure}>{JSON.stringify(measure)}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
textContainer: {
backgroundColor: '#61dafb',
justifyContent: 'center',
alignItems: 'center',
padding: 12,
},
measure: {
textAlign: 'center',
padding: 12,
},
});
export default App;
focus()
Solicita foco para a entrada ou visualização fornecida. O comportamento exato acionado dependerá da plataforma e do tipo de visualização.
blur()
Remove o foco de uma entrada ou visualização. Este é o oposto de focus()
.