Cómo usar referencias en React with Typecript

139

Estoy usando Typecript con React. Tengo problemas para entender cómo usar las referencias para obtener un tipeo estático e inteligencia con respecto a los nodos de reacción a los que hacen referencia las referencias. Mi código es el siguiente.

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>
    );
}}
Akshar Patel
fuente

Respuestas:

183

Si está usando React 16.3+, la forma sugerida de crear referencias es usando 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} />;
    }
}

Cuando el componente se monta, la propiedad refdel atributo currentse asignará al componente de referencia / elemento DOM y se asignará nuevamente nullcuando se desmonte. Entonces, por ejemplo, puede acceder usando this.stepInput.current.

Para obtener más información RefObject, consulte la respuesta de @ apieceofbart o se agregó el PR createRef() .


Si está utilizando una versión anterior de React (<16.3) o necesita un control más detallado sobre cuándo las referencias están configuradas y no, puede usar "referencias de devolución de llamada" .

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

Cuando se monta el componente, React llamará a la refdevolución de llamada con el elemento DOM y lo llamará nullcuando se desmonte. Entonces, por ejemplo, puede acceder simplemente usando this.stepInput.

Al definir la refdevolución de llamada como un método enlazado en la clase en lugar de una función en línea (como en una versión anterior de esta respuesta), puede evitar que la devolución de llamada se llame dos veces durante las actualizaciones.


No solía ser una API donde el refatributo era una cadena (véase la respuesta de Akshar Patel ), pero debido a algunos problemas , árbitros de cuerdas son totalmente desaconsejada y eventualmente serán eliminados.


Editado el 22 de mayo de 2018 para agregar la nueva forma de hacer referencias en React 16.3. Gracias @apieceofbart por señalar que había una nueva forma.

Jeff Bowen
fuente
Tenga en cuenta que esta es la forma preferida. Los ejemplos a continuación con el refsatributo de clase quedarán obsoletos en las próximas versiones de React.
Jimi Pajala
1
Tenga en cuenta que esto ya es una forma antigua :) actual es usar React.createRef ()
apieceofbart
@apieceofbart Gracias por el aviso. Se actualizó la respuesta para incluir la nueva forma.
Jeff Bowen
2
Simplemente no veo nada sobre el mecanografiado en su respuesta,
agregaré
Whoops Tenía Typecript en mi respuesta original pero olvidé incluirlo en la nueva. Lo agregó nuevamente y lo vinculó a su respuesta también. Gracias.
Jeff Bowen
30

Una forma (que he estado haciendo ) es configurarlo manualmente:

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

entonces incluso puede envolver esto en una función getter más agradable (por ejemplo, aquí ):

stepInput = (): HTMLInputElement => ReactDOM.findDOMNode(this.refs.stepInput);
basarat
fuente
1
Gracias @basarat. Intenté su solución pero recibo este error 'Tipo de elemento no es asignable para escribir' HTMLInputElement. Falta la aceptación de propiedad en el tipo Elemento ''
Akshar Patel
Podría ser un problema con la versión más reciente de las definiciones de react-dom. Usar como afirmación mientras tanto
basarat
Obviamente anyno es obligatorio aquí. La mayoría de los ejemplos que veo uso HTMLInputElement. Solo indicando lo obvio, pero si su referencia está en un componente Reaccionar (es decir PeoplePicker), puede usar ese componente como el tipo para obtener tipings.
Joe Martella
24

Desde React 16.3, la forma de agregar referencias es usar React.createRef como Jeff Bowen señaló en su respuesta. Sin embargo, puede aprovechar Typecript para escribir mejor su referencia.

En su ejemplo, está utilizando ref en el elemento de entrada. Así que lo harían es:

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

    ...

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

Al hacer esto cuando desea hacer uso de esa referencia, tiene acceso a todos los métodos de entrada:

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

También puede usarlo en componentes personalizados:

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

y luego tener, por ejemplo, acceso a accesorios:

this.componentRef.current.props; // 'props' satisfy IProps interface
apieceofbart
fuente
17

EDITAR: Esta ya no es la forma correcta de usar referencias con Typecript. Mira la respuesta de Jeff Bowen y vota para aumentar su visibilidad.

Encontré la respuesta al problema. Use referencias como se muestra a continuación dentro de la clase.

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

Gracias @basarat por apuntar en la dirección correcta.

Akshar Patel
fuente
2
Todavía estoy obteniendo Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }', cuando intento acceder this.refs.stepInput.
Nik Sumeiko
@NikSumeiko, recibiste ese error porque tu refsobjeto solo tenía la [key: string]entrada.
Joe Martella
9

React.createRef (componentes de clase)

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

Nota: Omitiendo la antigua API heredada de String Refs aquí ...


React.useRef (Ganchos / componentes de función)

Referencia de solo lectura para nodos DOM:
const FunctionApp = () => {
  const inputRef = React.useRef<HTMLInputElement>(null) // note the passed in `null` arg
  return <input type="text" ref={inputRef} />
}
Referencias mutables para valores arbitrarios almacenados:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Nota: No inicialice useRefcon nullen este caso. Haría el renderCountReftipo readonly(ver ejemplo ). Si necesita proporcionar nullcomo valor inicial, haga esto:

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

Referencia de devolución de llamada (trabajo para ambos)

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

Muestra del patio

ford04
fuente
¿Cuál es la diferencia entre useRef() as MutableRefObject<HTMLInputElement>y useRef<HTMLInputElement>(null)?
ksav
2
Buena pregunta: la currentpropiedad de MutableRefObject<HTMLInputElement>se puede modificar, mientras que useRef<HTMLInputElement>(null)crea un RefObjecttipo currentmarcado con readonly. Se puede usar el primero, si necesita cambiar los nodos DOM actuales en las referencias, por ejemplo, en combinación con una biblioteca externa. También se puede escribir sin as: useRef<HTMLInputElement | null>(null). Esta última es una mejor opción para los nodos DOM gestionados por React, como se usa en la mayoría de los casos. React almacena los nodos en las referencias mismas y no querrás buscar cambios en estos valores.
Ford04
1
Gracias por la aclaración.
ksav
7

Si está utilizando React.FC, agregue la HTMLDivElementinterfaz:

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

Y úsalo de la siguiente manera:

return <div ref={myRef} />;
JulienRioux
fuente
1
Gracias. Otro consejo para cualquiera que se encuentre con esto es verificar el Elemento. Este ejemplo se refiere al uso de un elemento DIV. Un formulario, por ejemplo, usaría - const formRef = React.useRef <HTMLFormElement> (nulo);
Nick Taras
1
Gracias gracias gracias gracias gracias gracias gracias gracias. Gracias.
Emboscado el
2

Para usar el estilo de devolución de llamada ( https://facebook.github.io/react/docs/refs-and-the-dom.html ) como se recomienda en la documentación de React, puede agregar una definición para una propiedad en la clase:

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; }}/>)
}
lucavgobbi
fuente
1

A falta de un ejemplo completo, aquí está mi pequeño script de prueba para obtener la entrada del usuario cuando trabaja con React y TypeScript. Basado parcialmente en los otros comentarios y este enlace 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 });
        }
    });
}

}

Tikall
fuente
1

Para el usuario mecanografiado no se requiere constructor.

...

private divRef: HTMLDivElement | null = null

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

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

...

Morlo Mbakop
fuente
0

De la definición de tipo React

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

Para que pueda acceder a su elemento de referencia de la siguiente manera

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

sin redefinir el índice de referencia.

Como @manakor mencionó, puede obtener un error como

La propiedad 'stepInput' no existe en el tipo '{[clave: cadena]: Componente | Elemento; }

si redefine las referencias (depende del IDE y la versión de TS que use)

Dzmitry Valadziankou
fuente
0

Solo para agregar un enfoque diferente: simplemente puede emitir su referencia, algo como:

let myInputElement: Element = this.refs["myInput"] as Element
Dimitar Dimitrov
fuente
0

Siempre hago esto, en ese caso para tomar una referencia

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

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

Si no quiere reenviar su ref, en la interfaz de Props necesita usar el RefObject<CmpType>tipo deimport React, { RefObject } from 'react';

MiF
fuente
0

Para aquellos que buscan cómo hacerlo cuando tienes una variedad de elementos:

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)}
    />
}
Jöcker
fuente
-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();
    }
}

ponerlos en un objeto

varman
fuente
1
Por favor explique su respuesta
AesSedai101
Esta respuesta está estableciendo ctrls.input en un elemento fuertemente tipado, que es la forma más segura de hacerlo. Esta es una mejor opción "mecanografiada".
Doug