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

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.

Jeff Bowen
fonte
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);
basarat
fonte
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
apieceofbart
fonte
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.

Akshar Patel
fonte
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

ford04
fonte
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} />;
JulienRioux
fonte
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; }}/>)
}
lucavgobbi
fonte
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 });
        }
    });
}

}

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

...

Morlo Mbakop
fonte
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)

Dzmitry Valadziankou
fonte
0

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

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

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

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

user2662112
fonte
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';

MiF
fonte
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)}
    />
}
Jöcker
fonte
-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

Varman
fonte
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