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.