Como usar refs em React with Typescript


139

Estou usando o Typecript com React. Estou tendo problemas para entender como usar refs para obter tipagem estática e intellisense com relação aos nós de reação referenciados pelos refs. Meu código é o seguinte.

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

Respostas:


183

Se você estiver usando o React 16.3+, a maneira sugerida de criar referências é usar 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 o componente é montado, a propriedade refdo atributo currentserá atribuída ao componente / elemento DOM referenciado e atribuída de volta nullquando ele desmontar. Por exemplo, você pode acessá-lo usandothis.stepInput.current .

Para RefObjectsaber mais , consulte a resposta de @ apieceofbart ou o PR createRef() foi adicionado.


Se você estiver usando uma versão anterior do React (<16.3) ou precisar de um controle mais refinado sobre quando as refs estão definidas e desabilitadas, você pode usar "refs de retorno de chamada" .

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 o componente é montado, o React chama o refretorno de chamada com o elemento DOM e o chama nullquando desmonta. Por exemplo, você pode acessá-lo simplesmente usandothis.stepInput .

Ao definir o refretorno de chamada como um método vinculado na classe, em oposição a uma função embutida (como em uma versão anterior desta resposta), você pode evitar que o retorno de chamada seja chamado duas vezes durante as atualizações.


Não costumava ser uma API onde o refatributo foi uma string (ver resposta de Akshar Patel ), mas devido a algumas questões , refs cordas são fortemente desencorajados e eventualmente será removido.


Editado em 22 de maio de 2018 para adicionar a nova maneira de fazer refs no React 16.3. Obrigado @apieceofbart por apontar que havia uma nova maneira.


Observe que essa é a maneira preferida. Os exemplos abaixo com o refsatributo class serão descontinuados nas próximas versões do React.
Jimi Pajala

1
Por favor note que este é já uma velha maneira :) atual é usar React.createRef ()
apieceofbart

@apieceofbart Obrigado pela atenção. Atualizada a resposta para incluir a nova maneira.
Jeff Bowen

2
Eu simplesmente não consigo ver nada sobre datilografado em sua resposta, vou adicionar outra resposta
apieceofbart

Ops. Tinha datilografado na minha resposta original, mas esqueci de incluí-la na nova. Adicionado novamente e vinculado à sua resposta também. Obrigado.
Jeff Bowen

30

Uma maneira (que eu tenho feito ) é configurar manualmente:

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

então você pode até encerrar isso em uma função getter mais agradável (por exemplo, aqui ):

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

1
Obrigado @basarat. Eu tentei sua solução, mas estou recebendo este erro 'Tipo de elemento não pode ser atribuído ao tipo' HTMLInputElement. Falta propriedade aceitar no tipo Elemento ''
Akshar Patel

Pode ser um problema com a versão mais recente das definições de reação. Use como asserção entretanto
basarat 19/11/2015

Obviamente anynão é obrigatório aqui. A maioria dos exemplos que vejo são úteis HTMLInputElement. Apenas afirme o óbvio, mas se sua referência estiver em um componente React (ou seja PeoplePicker), você poderá usar esse componente como o tipo para obter digitações.
Joe Martella

24

Desde o React 16.3, a maneira de adicionar refs é usar React.createRef como Jeff Bowen apontou em sua resposta. No entanto, você pode aproveitar o Typescript para digitar melhor sua referência.

No seu exemplo, você está usando ref no elemento de entrada. Então, da maneira que eu faria, é:

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

    ...

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

Ao fazer isso quando você quiser fazer uso dessa referência, você terá acesso a todos os métodos de entrada:

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

Você também pode usá-lo em componentes personalizados:

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

e, em seguida, tenha, por exemplo, acesso a adereços:

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

17

EDIT: Esta não é mais a maneira correta de usar refs com o Typecript. Veja a resposta de Jeff Bowen e vote-a para aumentar sua visibilidade.

Encontrou a resposta para o problema. Use refs como abaixo dentro da classe.

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

Obrigado @basarat por apontar na direção certa.


2
Ainda estou recebendo Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }', ao tentar acessar this.refs.stepInput.
Nik Sumeiko

@ Nikikumeume, você estava recebendo esse erro porque seu refsobjeto tinha apenas a [key: string]entrada.
Joe Martella

9

React.createRef (componentes de classe)

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

Nota: Omitindo a antiga API herdada de referências de string aqui ...


React.useRef (Ganchos / componentes de função)

Refs somente leitura para nós DOM:
const FunctionApp = () => {
  const inputRef = React.useRef<HTMLInputElement>(null) // note the passed in `null` arg
  return <input type="text" ref={inputRef} />
}
Referências mutáveis para valores armazenados arbitrários:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Nota: Não inicialize useRefcom nullneste caso. Seria o renderCountReftipo readonly(veja o exemplo ). Se você precisar fornecer nullcomo valor inicial, faça o seguinte:

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

Refs de retorno de chamada (trabalho para ambos)

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

Amostra para parque infantil


Qual é a diferença entre useRef() as MutableRefObject<HTMLInputElement>e useRef<HTMLInputElement>(null)?
ksav

2
Boa pergunta - a currentpropriedade de MutableRefObject<HTMLInputElement>pode ser modificada, ao passo que useRef<HTMLInputElement>(null)cria um RefObjecttipo currentmarcado com como readonly. O primeiro pode ser usado, se você precisar alterar os nós DOM atuais em refs, por exemplo, em combinação com uma biblioteca externa. Ele também pode ser escrita sem as: useRef<HTMLInputElement | null>(null). O último é uma escolha melhor para nós DOM gerenciados pelo React, conforme usado na maioria dos casos. O React armazena os nós nas próprias referências e você não deseja mexer na alteração desses valores.
ford04

1
Obrigado pelo esclarecimento.
ksav

7

Se você estiver usando React.FC, adicione a HTMLDivElementinterface:

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

E use-o da seguinte maneira:

return <div ref={myRef} />;

1
Obrigado. Outra dica para quem se deparar com isso é verificar o elemento. Este exemplo refere-se ao uso de um elemento DIV. Um formulário, por exemplo, usaria - const formRef = React.useRef <HTMLFormElement> (null);
Nick Taras

1
Obrigado obrigado obrigado obrigado obrigado obrigado obrigado obrigado. Obrigado.
Ambrown 8/07

2

Para usar o estilo de retorno de chamada ( https://facebook.github.io/react/docs/refs-and-the-dom.html ) conforme recomendado na documentação do React, você pode adicionar uma definição para uma propriedade na 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

Na falta de um exemplo completo, aqui está meu pequeno script de teste para obter informações do usuário ao trabalhar com o React e o TypeScript. Baseado parcialmente nos outros comentários e neste 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

Para usuário datilografado, nenhum construtor é necessário.

...

private divRef: HTMLDivElement | null = null

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

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

...


0

Na definição do tipo React

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

Para que você possa acessar o elemento refs como segue

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

sem redefinição do índice refs.

Como o @manakor mencionou, você pode obter erros como

A propriedade 'stepInput' não existe no tipo '{[key: string]: Componente | Elemento; }

se você redefinir refs (depende do IDE e da versão ts usada)


0

Apenas para adicionar uma abordagem diferente - você pode simplesmente converter sua referência, algo como:

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

0

Eu sempre faço isso, nesse caso, para pegar um juiz

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


deixe input: HTMLInputElement = ReactDOM.findDOMNode <HTMLInputElement> (this.refs ['input']);
precisa saber é o seguinte

0

Se você não encaminhar sua ref, na interface Props você precisará usar o RefObject<CmpType>tipo deimport React, { RefObject } from 'react';


0

Para aqueles que procuram como fazê-lo quando você tem uma matriz 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)}
    />
}

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

colocá-los em um objeto


1
Por favor, explique sua resposta
AesSedai101 1/16

Esta resposta está configurando ctrls.input para um elemento fortemente digitado, que é o caminho fortemente digitado. Esta é uma melhor escolha "Datilografada".
Doug
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.