Comment utiliser les refs dans React avec Typescript


139

J'utilise Typescript avec React. J'ai du mal à comprendre comment utiliser les refs pour obtenir un typage statique et un intellisense par rapport aux nœuds de réaction référencés par les refs. Mon code est le suivant.

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>
    );
}}

Réponses:


183

Si vous utilisez React 16.3+, la méthode suggérée pour créer des références est d'utiliser 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} />;
    }
}

Lorsque le composant est monté, la propriété de l' refattribut currentsera affectée au composant / élément DOM référencé et nullréattribuée lors de son démontage. Ainsi, par exemple, vous pouvez y accéder en utilisantthis.stepInput.current .

Pour en savoir plus RefObject, consultez la réponse de @ apieceofbart ou le PR a createRef() été ajouté.


Si vous utilisez une version antérieure de React (<16.3) ou avez besoin d'un contrôle plus fin sur le moment où les références sont définies et non définies, vous pouvez utiliser des «références de rappel» .

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} />
    }
}

Lorsque le composant est monté, React appellera le refrappel avec l'élément DOM, et l'appellera avec nulllors du démontage. Ainsi, par exemple, vous pouvez y accéder simplement en utilisant this.stepInput.

En définissant le refrappel comme une méthode liée sur la classe par opposition à une fonction en ligne (comme dans une version précédente de cette réponse), vous pouvez éviter que le rappel soit appelé deux fois lors des mises à jour.


Il y avait une API où l' refattribut était une chaîne (voir la réponse d'Akshar Patel ), mais en raison de certains problèmes , les références de chaîne sont fortement déconseillées et seront éventuellement supprimées.


Édité le 22 mai 2018 pour ajouter la nouvelle façon de faire des refs dans React 16.3. Merci @apieceofbart d'avoir souligné qu'il y avait une nouvelle façon.


Notez que c'est la méthode préférée. Les exemples ci-dessous avec l' refsattribut de classe seront obsolètes dans les prochaines versions de React.
Jimi Pajala

1
Veuillez noter que c'est déjà une ancienne méthode :) le courant est d'utiliser React.createRef ()
apieceofbart

@apieceofbart Merci pour la mise en garde. Mise à jour de la réponse pour inclure la nouvelle méthode.
Jeff Bowen

2
Je ne vois tout simplement rien à propos de la dactylographie dans votre réponse, j'ajouterai une autre réponse
apieceofbart

Oups. J'avais Typescript dans ma réponse originale mais j'ai oublié de l'inclure dans la nouvelle. Ajouté à nouveau et lié à votre réponse également. Merci.
Jeff Bowen

30

Une façon (ce que j'ai fait ) est de configurer manuellement:

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

alors vous pouvez même envelopper cela dans une fonction getter plus agréable (par exemple ici ):

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

1
Merci @basarat. J'ai essayé votre solution mais j'obtiens cette erreur «L'élément de type n'est pas attribuable au type» HTMLInputElement. La propriété acceptée est manquante dans le type Élément ''
Akshar Patel

Cela pourrait poser un problème avec la nouvelle version des définitions de react-dom. Utiliser comme assertion entre
basarat

Ce anyn'est évidemment pas obligatoire ici. La plupart des exemples que je vois utiliser HTMLInputElement. Indiquant simplement l'évidence, mais si votre référence est sur un composant React (c'est-à-dire PeoplePicker), vous pouvez utiliser ce composant comme type pour obtenir des typages.
Joe Martella

24

Depuis React 16.3, la façon d'ajouter des refs est d'utiliser React.createRef comme Jeff Bowen l'a indiqué dans sa réponse. Cependant, vous pouvez profiter de Typescript pour mieux saisir votre référence.

Dans votre exemple, vous utilisez ref sur l'élément d'entrée. Donc, la façon dont je le ferais est:

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

    ...

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

En faisant cela, lorsque vous souhaitez utiliser cette référence, vous avez accès à toutes les méthodes d'entrée:

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

Vous pouvez également l'utiliser sur des composants personnalisés:

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

et ensuite avoir, par exemple, accès aux accessoires:

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

17

EDIT: Ce n'est plus la bonne façon d'utiliser les refs avec Typescript. Regardez la réponse de Jeff Bowen et votez pour augmenter sa visibilité.

J'ai trouvé la réponse au problème. Utilisez les références comme ci-dessous à l'intérieur de la classe.

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

Merci @basarat pour avoir pointé dans la bonne direction.


2
J'obtiens toujours Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }', en essayant d'accéder this.refs.stepInput.
Nik Sumeiko

@NikSumeiko, vous obteniez cette erreur car votre refsobjet n'avait que l' [key: string]entrée.
Joe Martella

9

React.createRef (composants de classe)

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

Remarque: Omettre l'ancienne API héritée de String Refs ici ...


React.useRef (Crochets / composants de fonction)

Références en lecture seule pour les nœuds DOM:
const FunctionApp = () => {
  const inputRef = React.useRef<HTMLInputElement>(null) // note the passed in `null` arg
  return <input type="text" ref={inputRef} />
}
Refs mutables pour les valeurs stockées arbitraires:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Remarque: ne pas initialiser useRefavec nulldans ce cas. Cela ferait le renderCountReftype readonly(voir exemple ). Si vous devez fournir nullune valeur initiale, procédez comme suit:

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

Références de rappel (fonctionne pour les deux)

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

Échantillon de terrain de jeu


Quelle est la différence entre useRef() as MutableRefObject<HTMLInputElement>et useRef<HTMLInputElement>(null)?
ksav

2
Bonne question - la currentpropriété de MutableRefObject<HTMLInputElement>peut être modifiée, alors que useRef<HTMLInputElement>(null)crée un RefObjecttype currentmarqué comme readonly. Le premier peut être utilisé si vous avez besoin de modifier vous-même les nœuds DOM actuels dans les refs, par exemple en combinaison avec une bibliothèque externe. Il peut également être écrit sans as: useRef<HTMLInputElement | null>(null). Ce dernier est un meilleur choix pour les nœuds DOM gérés par React, comme utilisé dans la plupart des cas. React stocke les nœuds dans les refs eux-mêmes et vous ne voulez pas chercher à changer ces valeurs.
ford04

1
Merci pour la clarification.
ksav

7

Si vous utilisez React.FC, ajoutez l' HTMLDivElementinterface:

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

Et utilisez-le comme suit:

return <div ref={myRef} />;

1
Merci. Une autre astuce pour quiconque rencontre cela est de vérifier l'élément. Cet exemple fait référence à l'utilisation d'un élément DIV. Un formulaire par exemple utiliserait - const formRef = React.useRef <HTMLFormElement> (null);
Nick Taras le

1
Merci merci merci merci merci merci merci merci merci. Je vous remercie.
Ambrown

2

Pour utiliser le style de rappel ( https://facebook.github.io/react/docs/refs-and-the-dom.html ) comme recommandé dans la documentation de React, vous pouvez ajouter une définition pour une propriété sur la 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

Faute d'un exemple complet, voici mon petit script de test pour obtenir les entrées de l'utilisateur lorsque vous travaillez avec React et TypeScript. Basé en partie sur les autres commentaires et ce lien 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

Pour l'utilisateur dactylographié, aucun constructeur n'est requis.

...

private divRef: HTMLDivElement | null = null

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

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

...


0

À partir de la définition de type React

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

Ainsi, vous pouvez accéder à votre élément refs comme suit

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

sans redéfinition de l'indice de référence.

Comme @manakor l'a mentionné, vous pouvez obtenir une erreur comme

La propriété 'stepInput' n'existe pas sur le type '{[key: string]: Component | Élément; }

si vous redéfinissez les références (dépend de la version IDE et ts que vous utilisez)


0

Juste pour ajouter une approche différente - vous pouvez simplement lancer votre ref, quelque chose comme:

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

0

Je fais toujours ça, dans ce cas pour attraper un ref

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


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

0

Si vous ne souhaitez pas transférer votre ref, dans l'interface des accessoires, vous devez utiliser le RefObject<CmpType>type deimport React, { RefObject } from 'react';


0

Pour ceux qui cherchent comment le faire lorsque vous avez un tableau d'éléments:

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();
    }
}

les mettre dans un objet


1
Veuillez expliquer votre réponse
AesSedai101

Cette réponse définit ctrls.input sur un élément fortement typé, ce qui est la voie à suivre fortement typée. C'est un meilleur choix "Typescript".
Doug
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.