Inoltrare Refs
L’inoltro delle ref è una tecnica per passare automaticamente una ref attraverso un componente ad uno dei suoi figli. Tipicamente questo non è necessario per la maggior parte dei componenti dell’applicazione. Può comunque essere molto utile per alcuni tipi di componenti, specialmente per i componenti riusabili appartenenti alle librerie. Lo scenario più comune è descritto di seguito.
Inoltro delle refs ai componenti del DOM
Considera di avere un componente FancyButton
che renderizza l’elemento nativo del DOM button
:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
I componenti React nascondono i dettagli della loro implementazione, incluso il loro output renderizzato. Altri componenti che usano FancyButton
solitamente non hanno bisogno di ottenere una ref dell’elemento interno del DOM button
. Ciò è ottimo perché previene ai componenti di affidarsi troppo alla struttura DOM l’uno dell’altro.
Sebbene questa incapsulazione sia desiderabile per componenti a livello applicativo come FeedStory
o Comment
, può essere conveniente per componenti “foglia” altamente riutilizzabili come FancyButton
o MyTextInput
. Questi componenti tendono ad essere utilizzati attraverso l’applicazione in modo simile agli elementi del DOM button
e input
e l’accesso ai loro nodi DOM può essere inevitabile per la gestione di messa a fuoco, selezione, o animazioni.
L’inoltro delle refs è una feature opt-in che permette ad alcuni componenti di prendere le ref
che ricevono e passarle in basso (in altre parole avanti) ai suoi figli.
Nell’esempio seguente, FancyButton
utilizza React.forwardRef
per ottenere la ref
passata, dopodiché la passa all’elemento del DOM button
che la renderizza:
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
In questo modo i componenti che usano FancyButton
possono ottenere una ref al nodo del DOM button
e accedervi, se necessario, proprio come se utilizzassero l’elemento del DOM button
direttamente.
Qui puoi trovare una spiegazione passo passo di quello che succede nel precedente esempio:
- Creiamo una ref React chiamando
React.createRef
e assegnandola ad una variabileref
. - Passiamo la nostra
ref
a<FancyButton ref={ref}>
specificandola come un attributo JSX. - React passa la
ref
alla funzione(props, ref) => ...
all’interno diforwardRef
come secondo argomento. - Passiamo questo argomento
ref
a<button ref={ref}>
specificandolo come attributo JSX. - Quando la ref è attaccata,
ref.current
punterà all’elemento del DOMbutton
.
Nota
Il secondo argomento
ref
esiste solamente quando viene definito un componente con la chiamataReact.forwardRef
. I componenti di tipo classe e di tipo funzione non ricevono l’argomentoref
e laref
non è disponibile nemmeno nelle props.L’inoltro delle Ref non è limitato ai componenti del DOM. Possiamo passare le refs anche ad istanze di componenti classe.
Nota per i mantenitori di librerie di componenti
Quando inizi ad usare forwardRef
nelle librerie di componenti, dovresti trattarlo come una breaking change e rilasciare una nuova versione della libreria. Questo perché la tua libreria probabilmente ha un comportamento diverso (ad esempio nel modo in cui le ref sono assegnate e i tipi che sono esportati), e questo può rompere applicazioni e altre librerie che dipendono da questo vecchio comportamento.
Applicare in modo condizionale le React.forwardRef
quando esistono non è raccomandato per alcune ragioni: cambiano il comportamento della tua libreria e possono rompere le applicazioni degli utenti quando questi aggiornano React.
Inoltro delle refs in componenti di tipo higher-order
Questa tecnica può anche essere particolarmente utile con componenti di tipo higher-order (conosciuti anche come HOC). Iniziamo con un esempio che logga le props di un componente in console:
function logProps(WrappedComponent) { class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />; }
}
return LogProps;
}
L’HOC “logProps” passa tutte le props
attraverso il componente che lo contiene, in questo modo l’output renderizzato sarà il solito. Ad esempio possiamo usare l’HOC per stampare tutte le props passate al componente FancyButton
:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
C’è un avvertimento all’esempio precedente: le refs non saranno passate attraverso di esso. Questo perché una ref
non è una prop. Come la key
, è maneggiata in modo diverso da React. Se vuoi aggiungere una ref ad un HOC, la ref dovrà riferirsi ad un componente container più esterno, non al componente che lo contiene.
Questo significa che le refs per il nostro componente FancyButton
saranno attaccate al componente LogProps
:
import FancyButton from './FancyButton';
const ref = React.createRef();
// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}/>;
Fortunatamente possiamo passare le refs esplicitamente al componente interno FancyButton
utilizzando le API React.forwardRef
. React.forwardRef
accetta una funzione di renderizzazione che riceve le props
e le ref
come parametri e le ritorna ad un nodo React. Ad esempio:
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />; }
}
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; });}
Mostrare un nome custom negli strumenti dello sviluppatore
React.forwardRef
accetta una funzione di renderizzazione. Gli strumenti per gli sviluppatori di React utilizzano questa funzione per determinare cosa mostrare per l’inoltro al componente.
Ad esempio, il seguente componente apparirà come ”ForwardRef” negli strumenti per sviluppatori:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
Se assegni un nome alla funzione di renderizzazione, gli strumenti per sviluppatori includeranno anche il suo nome (ad esempio ”ForwardRef(myFunction)“)
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
Puoi anche settare la proprietà displayName
della funzione per includere il componente che stai avvolgendo:
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
// Give this component a more helpful display name in DevTools.
// e.g. "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}