Come usare i ref in React con Typescript


139

Sto usando Typescript con React. Ho difficoltà a capire come usare i ref in modo da ottenere la tipizzazione statica e l'intellisense rispetto ai nodi di reazione a cui fanno riferimento gli ref. Il mio codice è il seguente.

import * as React from 'react';

interface AppState {
    count: number;
}

interface AppProps {
    steps: number;
}

interface AppRefs {
    stepInput: HTMLInputElement;
}

export default class TestApp extends React.Component<AppProps, AppState> {

constructor(props: AppProps) {
    super(props);
    this.state = {
        count: 0
    };
}

incrementCounter() {
    this.setState({count: this.state.count + 1});
}

render() {
    return (
        <div>
            <h1>Hello World</h1>
            <input type="text" ref="stepInput" />
            <button onClick={() => this.incrementCounter()}>Increment</button>
            Count : {this.state.count}
        </div>
    );
}}

Risposte:


183

Se stai usando React 16.3+, sta usando il modo suggerito per creare riferimenti React.createRef().

class TestApp extends React.Component<AppProps, AppState> {
    private stepInput: React.RefObject<HTMLInputElement>;
    constructor(props) {
        super(props);
        this.stepInput = React.createRef();
    }
    render() {
        return <input type="text" ref={this.stepInput} />;
    }
}

Quando il componente viene montato, la proprietà refdell'attributo currentverrà assegnata al componente di riferimento / elemento DOM e assegnata a nullquando verrà smontata. Quindi, ad esempio, puoi accedervi usandothis.stepInput.current .

Per ulteriori informazioni RefObject, vedere la risposta di @ apieceofbart o il PR è createRef() stato aggiunto.


Se stai usando una versione precedente di React (<16.3) o hai bisogno di un controllo più approfondito quando i ref sono impostati e non impostati, puoi usare i "ref call refs" .

class TestApp extends React.Component<AppProps, AppState> {
    private stepInput: HTMLInputElement;
    constructor(props) {
        super(props);
        this.stepInput = null;
        this.setStepInputRef = element => {
            this.stepInput = element;
        };
    }
    render() {
        return <input type="text" ref={this.setStepInputRef} />
    }
}

Quando il componente viene montato, React chiamerà il refcallback con l'elemento DOM e lo chiamerà nullquando verrà smontato. Quindi, ad esempio, puoi accedervi semplicemente usando this.stepInput.

Definendo il refcallback come metodo associato sulla classe anziché una funzione inline (come in una versione precedente di questa risposta), è possibile evitare che il callback venga chiamato due volte durante gli aggiornamenti.


Ci deve essere utilizzato un'API in cui l' refattributo è una stringa (vedi risposta di Akshar Patel ), ma a causa di alcuni problemi , arbitri stringa sono fortemente scoraggiati e verranno rimossi.


Modificato il 22 maggio 2018 per aggiungere il nuovo modo di fare riferimento in React 16.3. Grazie @apieceofbart per aver sottolineato che c'era un nuovo modo.


Si noti che questo è il modo preferito. Gli esempi seguenti con l' refsattributo class saranno deprecati nelle prossime versioni di React.
Jimi Pajala

1
Si noti che questo è già un vecchio modo :) corrente è usare React.createRef ()
apieceofbart

@apieceofbart Grazie per il testa a testa. Aggiornata la risposta per includere il nuovo modo.
Jeff Bowen,

2
Semplicemente non vedo nulla del dattiloscritto nella tua risposta, aggiungerò un'altra risposta
apieceofbart

Ops. Ho scritto Typescript nella mia risposta originale ma ho dimenticato di includerlo in quello nuovo. Aggiunto di nuovo e collegato anche alla tua risposta. Grazie.
Jeff Bowen,

30

Un modo (che ho fatto ) è di configurare manualmente:

refs: {
    [string: string]: any;
    stepInput:any;
}

quindi puoi anche concludere con una funzione getter più bella (ad esempio qui ):

stepInput = (): HTMLInputElement => ReactDOM.findDOMNode(this.refs.stepInput);

1
Grazie @basarat. Ho provato la tua soluzione ma visualizzo questo errore "Tipo elemento non assegnabile al tipo" HTMLInputElement. Proprietà accetta mancante nel tipo Element ''
Akshar Patel

Potrebbe essere un problema con la versione più recente delle definizioni di reattanza. Usa come affermazione nel frattempo
basarat

Ovviamente anynon è obbligatorio qui. La maggior parte degli esempi che vedo usare HTMLInputElement. Dichiarando semplicemente l'ovvio, ma se il tuo riferimento è su un componente React (cioè PeoplePicker), puoi usare quel componente come tipo per ottenere le digitazioni.
Joe Martella,

24

A partire da React 16.3 il modo per aggiungere refs è usare React.createRef come Jeff Bowen ha indicato nella sua risposta. Tuttavia, puoi utilizzare Typescript per digitare meglio il tuo riferimento.

Nel tuo esempio stai usando ref sull'elemento input. Quindi il modo in cui lo farei è:

class SomeComponent extends React.Component<IProps, IState> {
    private inputRef: React.RefObject<HTMLInputElement>;
    constructor() {
        ...
        this.inputRef = React.createRef();
    }

    ...

    render() {
        <input type="text" ref={this.inputRef} />;
    }
}

In questo modo quando si desidera utilizzare tale riferimento si ha accesso a tutti i metodi di input:

someMethod() {
    this.inputRef.current.focus(); // 'current' is input node, autocompletion, yay!
}

Puoi usarlo anche su componenti personalizzati:

private componentRef: React.RefObject<React.Component<IProps>>;

e quindi, ad esempio, avere accesso agli oggetti di scena:

this.componentRef.current.props; // 'props' satisfy IProps interface

17

EDIT: Questo non è più il modo giusto di usare refs con Typescript. Guarda la risposta di Jeff Bowen e votala per aumentarne la visibilità.

Ho trovato la risposta al problema. Usa i riferimenti come sotto all'interno della classe.

refs: {
    [key: string]: (Element);
    stepInput: (HTMLInputElement);
}

Grazie @basarat per aver puntato nella giusta direzione.


2
Sto ancora ricevendo Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }', quando provo ad accedere this.refs.stepInput.
Nik Sumeiko,

@NikSumeiko, hai riscontrato quell'errore perché il tuo refsoggetto aveva solo la [key: string]voce.
Joe Martella,

9

React.createRef (componenti di classe)

class ClassApp extends React.Component {
  inputRef = React.createRef<HTMLInputElement>();
  
  render() {
    return <input type="text" ref={this.inputRef} />
  }
}

Nota: omettendo qui la vecchia API legacy di String Refs ...


React.useRef (Ganci / componenti funzionali)

Riferimenti di sola lettura per i nodi DOM:
const FunctionApp = () => {
  const inputRef = React.useRef<HTMLInputElement>(null) // note the passed in `null` arg
  return <input type="text" ref={inputRef} />
}
Riferimenti mutabili per valori archiviati arbitrari:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Nota: non inizializzare useRefcon nullin questo caso. Ne farebbe il renderCountReftipo readonly(vedi esempio ). Se è necessario fornire nullcome valore iniziale, procedere come segue:

const renderCountRef = useRef<number | null>(null)

Riferimenti callback (lavoro per entrambi)

// Function component example 
const FunctionApp = () => {
  const handleDomNodeChange = (domNode: HTMLInputElement | null) => {
    // ... do something with changed dom node.
  }
  return <input type="text" ref={handleDomNodeChange} />
}

Esempio di parco giochi


Qual è la differenza tra useRef() as MutableRefObject<HTMLInputElement>e useRef<HTMLInputElement>(null)?
ksav,

2
Buona domanda: la currentproprietà di MutableRefObject<HTMLInputElement>può essere modificata, mentre useRef<HTMLInputElement>(null)crea un RefObjecttipo con currentcontrassegnato come readonly. Il primo può essere utilizzato, se è necessario modificare manualmente i nodi DOM correnti in ref, ad es. In combinazione con una libreria esterna. Può anche essere scritto senza as: useRef<HTMLInputElement | null>(null). Quest'ultima è una scelta migliore per i nodi DOM gestiti di React, come usato nella maggior parte dei casi. React memorizza i nodi negli stessi ref e non si vuole armeggiare cambiando questi valori.
ford04,

1
Grazie per il chiarimento.
ksav,

7

Se stai usando React.FC, aggiungi l' HTMLDivElementinterfaccia:

const myRef = React.useRef<HTMLDivElement>(null);

E usalo come segue:

return <div ref={myRef} />;

1
Grazie. Un altro consiglio per chiunque si imbatta in questo è controllare l'Elemento. Questo esempio si riferisce all'uso di un elemento DIV. Un modulo per esempio userebbe - const formRef = React.useRef <HTMLFormElement> (null);
Nick Taras,

1
Grazie grazie grazie grazie grazie grazie grazie grazie grazie. Grazie.
Ambrown

2

Per usare lo stile di callback ( https://facebook.github.io/react/docs/refs-and-the-dom.html ) come raccomandato nella documentazione di React è possibile aggiungere una definizione per una proprietà nella classe:

export class Foo extends React.Component<{}, {}> {
// You don't need to use 'references' as the name
references: {
    // If you are using other components be more specific than HTMLInputElement
    myRef: HTMLInputElement;
} = {
    myRef: null
}
...
 myFunction() {
    // Use like this
    this.references.myRef.focus();
}
...
render() {
    return(<input ref={(i: any) => { this.references.myRef = i; }}/>)
}

1

In mancanza di un esempio completo, ecco il mio piccolo script di test per ottenere l'input dell'utente quando si lavora con React e TypeScript. Basato parzialmente sugli altri commenti e su questo link https://medium.com/@basarat/strongly-typed-refs-for-react-typescript-9a07419f807#.cdrghertm

/// <reference path="typings/react/react-global.d.ts" />

// Init our code using jquery on document ready
$(function () {
    ReactDOM.render(<ServerTime />, document.getElementById("reactTest"));
});

interface IServerTimeProps {
}

interface IServerTimeState {
    time: string;
}

interface IServerTimeInputs {
    userFormat?: HTMLInputElement;
}

class ServerTime extends React.Component<IServerTimeProps, IServerTimeState> {
    inputs: IServerTimeInputs = {};

    constructor() {
        super();
        this.state = { time: "unknown" }
    }

    render() {
        return (
            <div>
                <div>Server time: { this.state.time }</div>
                <input type="text" ref={ a => this.inputs.userFormat = a } defaultValue="s" ></input>
                <button onClick={ this._buttonClick.bind(this) }>GetTime</button>
            </div>
        );
    }

    // Update state with value from server
    _buttonClick(): void {
    alert(`Format:${this.inputs.userFormat.value}`);

        // This part requires a listening web server to work, but alert shows the user input
    jQuery.ajax({
        method: "POST",
        data: { format: this.inputs.userFormat.value },
        url: "/Home/ServerTime",
        success: (result) => {
            this.setState({ time : result });
        }
    });
}

}


1

Per l'utente dattiloscritto nessun costruttore richiesto.

...

private divRef: HTMLDivElement | null = null

getDivRef = (ref: HTMLDivElement | null): void => {
    this.divRef = ref
}

render() {
    return <div ref={this.getDivRef} />
}

...


0

Dalla definizione del tipo di React

    type ReactInstance = Component<any, any> | Element;
....
    refs: {
            [key: string]: ReactInstance
    };

Quindi puoi accedere al tuo elemento refs come segue

stepInput = () => ReactDOM.findDOMNode(this.refs['stepInput']);

senza ridefinizione dell'indice di riferimento.

Come menzionato @manakor, è possibile che si verifichi un errore simile

La proprietà 'stepInput' non esiste sul tipo '{[key: string]: Component | Elemento; }

se si ridefiniscono i riferimenti (dipende dall'IDE e dalla versione ts utilizzata)


0

Solo per aggiungere un approccio diverso: puoi semplicemente lanciare il tuo riferimento, qualcosa del tipo:

let myInputElement: Element = this.refs["myInput"] as Element

0

Lo faccio sempre, in quel caso per afferrare un riferimento

let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);


let input: HTMLInputElement = ReactDOM.findDOMNode <HTMLInputElement> (this.refs ['input']);
user2662112

0

Se non vuoi inoltrare la tua ref, nell'interfaccia Props devi usare il RefObject<CmpType>tipo daimport React, { RefObject } from 'react';


0

Per coloro che cercano come farlo quando si dispone di una matrice di elementi:

const textInputRefs = useRef<(HTMLDivElement | null)[]>([])

...

const onClickFocus = (event: React.BaseSyntheticEvent, index: number) => {
    textInputRefs.current[index]?.focus()
};

...

{items.map((item, index) => (
    <textInput
        inputRef={(ref) => textInputs.current[index] = ref}
    />
    <Button
        onClick={event => onClickFocus(event, index)}
    />
}

-1
class SelfFocusingInput extends React.Component<{ value: string, onChange: (value: string) => any }, {}>{
    ctrls: {
        input?: HTMLInputElement;
    } = {};
    render() {
        return (
            <input
                ref={(input) => this.ctrls.input = input}
                value={this.props.value}
                onChange={(e) => { this.props.onChange(this.ctrls.input.value) } }
                />
        );
    }
    componentDidMount() {
        this.ctrls.input.focus();
    }
}

metterli in un oggetto


1
Spiega la tua risposta
AesSedai101

Questa risposta sta impostando ctrls.input su un elemento fortemente tipizzato, che è la strada fortemente tipizzata da percorrere. Questa è una scelta "dattiloscritta" migliore.
Doug,
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.