Como usar forwardRef com FunctionComponent no React Native usando TypeScript

Oct 22 2020

Eu li muitos dos documentos e exemplos, mas ainda não consigo entender como usar forwardRefcom um componente funcional com TypeScript no React Native. Abaixo está um exemplo onde eu crio um MyCustomComponentcom uma função personalizada que tento chamar do pai criando um ref. No entanto, como o ref está definido incorretamente e null, obviamente, recebo uma mensagem de erro informando que a função não existe. Por favor, me ajude a entender como usar corretamente o forwardRefReact Native Desde já, obrigado!

interface MyCustomComponentProps {
    title: string
}

const MyCustomComponent: React.FunctionComponent<MyCustomComponentProps> = React.forwardRef((props, ref) => {
    const coolAlert = () => {
        Alert.alert('Hey!', 'This was called from MyCustomComponent')
    }
    return (
        <View>
            <Text>{props.title}</Text>
        </View>
    )
})

export default function App () {
    const MyCustomComponentRef = useRef()
    return (
        <SafeAreaView>
            <MyCustomComponent ref={MyCustomComponentRef} title='Hello World' />
            <TouchableOpacity
                onPress={() => {
                    MyCustomComponentRef.coolAlert()
                }}>
                <Text>Click Me</Text>
            </TouchableOpacity>
        </SafeAreaView>
    )
}

Respostas

3 LindaPaiste Oct 22 2020 at 23:27

Encaminhando a Ref

Refs podem ser realmente confusos porque existem várias maneiras de manipulá-los e porque as pessoas não estão cientes da diferença entre o objeto ref ( React.MutableRefObjectou React.RefObject) e o valor ref, que é armazenado na .currentpropriedade do objeto ref. Você cometeu esse erro aqui, junto com alguns tipos de texto datilografados ausentes ou incorretos.

useRef<T>é um gancho genérico onde o valor Tinforma que tipo de valor será armazenado. Precisamos dizer Appque pretendemos armazenar algo com um coolAlertmétodo. Na verdade, veremos mais tarde que precisamos que nosso ref seja imutável, então usaremos em seu createRef<T>lugar.

interface MyRef {
  coolAlert(): void;
}

const MyCustomComponentRef = createRef<MyRef>();

Quando chamamos onPress, precisamos acessar o valor atual do objeto ref. Ao adicionar o genérico a createRef, o typescript já sabe que esse valor é MyRefou undefined. Podemos ligar coolAlertcom o ?.operador de encadeamento opcional .

onPress={() => MyCustomComponentRef.current?.coolAlert()}

Agora precisamos trabalhar um pouco MyCustomComponent. Você errou ao atribuir a ele o tipo React.FunctionComponent<MyCustomComponentProps>porque um componente de função não tem o conhecimento sobre o encaminhamento de referência de que precisamos.

function forwardRef<T, P = {}>(Component: RefForwardingComponent<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;

O tipo de MyCustomComponentdeve ser aquele tipo de retorno complicado de forwardRef. Mas não precisamos atribuir esse tipo nós mesmos, só precisamos passar os genéricos Te Pa forwardRefchamada de função. Té o tipo do ref e Pé o tipo dos adereços.

const MyCustomComponent = React.forwardRef<MyRef, MyCustomComponentProps>(...

Ok, então nos livramos de todos os erros de digitação ! Yay! Exceto ... espere. Na verdade, não faz nada . Tudo isso e ainda não funciona. Eu odeio os árbitros. Refs são ruins.

Usando o Ref

Encaminhamos a ref para MyCustomComponent, que agora tem acesso à ref encaminhada e pode anexá-la a um componente DOM. Mas não o queremos anexado ao elemento DOM, queremos anexá-lo MyCustomComponent. Mas não podemos realmente fazer isso.

Por padrão, você não pode usar o atributo ref em componentes de função porque eles não têm instâncias [docs]

Temos que fazer uso de um gancho chamado useImperativeHandleque parece uma solução de hack e até mesmo os documentos dizem "não faça isso". Sim, eu odeio árbitros.

useImperativeHandle personaliza o valor da instância que é exposto aos componentes pai ao usar ref. Como sempre, o código obrigatório usando refs deve ser evitado na maioria dos casos. useImperativeHandle deve ser usado com forwardRef. [docs]

Temos que expor nosso coolAlertmétodo completamente useImperativeHandle.

useImperativeHandle(ref , () => ({coolAlert}));

E agora realmente funciona , finalmente!