Come utilizzare React.forwardRef in un componente basato su classi?


113

Sto cercando di usare React.forwardRef, ma inciampo su come farlo funzionare in un componente basato su classi (non HOC).

Gli esempi della documentazione utilizzano elementi e componenti funzionali, anche avvolgendo classi in funzioni per componenti di ordine superiore.

Così, a partire da qualcosa di simile a questo nel loro ref.jsdocumento:

const TextInput = React.forwardRef(
    (props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />)
);

e invece definendolo come qualcosa del genere:

class TextInput extends React.Component {
  render() {
    let { props, ref } = React.forwardRef((props, ref) => ({ props, ref }));
    return <input type="text" placeholder="Hello World" ref={ref} />;
  }
}

o

class TextInput extends React.Component {
  render() { 
    return (
      React.forwardRef((props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />))
    );
  }
}

solo funzionante: /

Inoltre, so di sapere, gli arbitri non sono il modo di reagire. Sto cercando di utilizzare una libreria canvas di terze parti e vorrei aggiungere alcuni dei loro strumenti in componenti separati, quindi ho bisogno di listener di eventi, quindi ho bisogno di metodi del ciclo di vita. Potrebbe seguire un percorso diverso in seguito, ma voglio provare questo.

I documenti dicono che è possibile!

L'inoltro di riferimento non è limitato ai componenti DOM. Puoi anche inoltrare i riferimenti alle istanze dei componenti di classe.

dalla nota in questa sezione.

Ma poi continuano a usare gli HOC invece delle sole classi.

Risposte:


108

L'idea di utilizzare sempre lo stesso oggetto di scena per il refpuò essere ottenuta tramite proxy dell'esportazione della classe con un helper.

class ElemComponent extends Component {
  render() {
    return (
      <div ref={this.props.innerRef}>
        Div has ref
      </div>
    )
  }
}

export default React.forwardRef((props, ref) => <ElemComponent 
  innerRef={ref} {...props}
/>);

Quindi, fondamentalmente, sì, siamo costretti ad avere un'elica diversa per il riferimento in avanti, ma può essere fatto sotto il mozzo. È importante che il pubblico lo utilizzi come un normale ref.


4
Cosa fai se il tuo componente è avvolto in un HOC come React-Redux connecto marterial-ui's withStyles?
J. Hesters

Qual è il problema con connecto withStyles? Dovresti avvolgere tutti gli HOC forwardRefe utilizzare internamente l'elica "sicura" per passare un riferimento al componente più basso della catena.
Mr Br

Qualche idea su come testare questo in Enzyme quando usi setState?
zero_cool

Scusa, ho bisogno di qualche dettaglio in più. Non sono sicuro di quale sia esattamente il problema.
Mr Br

2
Ricevo: Violazione invariante: gli oggetti non sono validi come figlio React (trovato: oggetto con chiavi {$$ typeof, render}). Se intendevi eseguire il rendering di una raccolta di bambini, usa invece un array.
Brian Loughnane

9
class BeautifulInput extends React.Component {
  const { innerRef, ...props } = this.props;
  render() (
    return (
      <div style={{backgroundColor: "blue"}}>
        <input ref={innerRef} {...props} />
      </div>
    )
  )
}

const BeautifulInputForwardingRef = React.forwardRef((props, ref) => (
  <BeautifulInput {...props} innerRef={ref}/>
));

const App = () => (
  <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
)

È necessario utilizzare un nome prop diverso per inoltrare il riferimento a una classe. innerRefè comunemente usato in molte biblioteche.


nel tuo codice beautifulInputElementè più una funzione che un elemento React, dovrebbe essere cosìconst beautifulInputElement = <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
Olivier Boissé

8

Fondamentalmente, questa è solo una funzione HOC. Se vuoi usarlo con la classe, puoi farlo da solo e usare oggetti di scena regolari.

class TextInput extends React.Component {
    render() {
        <input ref={this.props.forwardRef} />
    }
}

const ref = React.createRef();
<TextInput forwardRef={ref} />

Questo modello viene utilizzato ad esempio in styled-componentse viene chiamato innerReflì.


3
Usare un oggetto di scena dal nome diverso come innerRefmanca completamente il punto. L'obiettivo è la trasparenza completa: dovrei essere in grado di trattare un <FancyButton>elemento come se fosse un normale elemento DOM, ma usando l'approccio dei componenti in stile, ho bisogno di ricordare che questo componente utilizza un puntello dal nome arbitrario per refs invece che solo ref.
user128216

2
In ogni caso, i componenti in stile intendono abbandonare il supporto perinnerRef a favore di passare il riferimento al bambino che usa React.forwardRef, che è ciò che l'OP sta cercando di ottenere.
user128216

5

Questo può essere ottenuto con un componente di ordine superiore, se preferisci:

import React, { forwardRef } from 'react'

const withForwardedRef = Comp => {
  const handle = (props, ref) =>
    <Comp {...props} forwardedRef={ref} />

  const name = Comp.displayName || Comp.name
  handle.displayName = `withForwardedRef(${name})`

  return forwardRef(handle)
}

export default withForwardedRef

E poi nel file del componente:

class Boop extends React.Component {
  render() {
    const { forwardedRef } = this.props

    return (
      <div ref={forwardedRef} />
    )
  }
}

export default withForwardedRef(Boop)

Ho svolto il lavoro in anticipo con i test e ho pubblicato un pacchetto per questo react-with-forwarded-ref: https://www.npmjs.com/package/react-with-forwarded-ref


3

Nel caso in cui sia necessario riutilizzarlo in molti componenti diversi, è possibile esportare questa capacità in qualcosa di simile withForwardingRef

const withForwardingRef = <Props extends {[_: string]: any}>(BaseComponent: React.ReactType<Props>) =>
    React.forwardRef((props, ref) => <BaseComponent {...props} forwardedRef={ref} />);

export default withForwardingRef;

utilizzo:

const Comp = ({forwardedRef}) => (
 <input ref={forwardedRef} />
)
const EnhanceComponent = withForwardingRef<Props>(Comp);  // Now Comp has a prop called forwardedRef
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.