Jak używać referencji w React with Typescript


139

Używam Typescript z React. Mam problem ze zrozumieniem, jak używać referencji, aby uzyskać statyczne typowanie i inteligencję w odniesieniu do węzłów reakcji, do których odwołują się odwołania. Mój kod jest następujący.

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

Odpowiedzi:


183

Jeśli używasz React 16.3+, sugerowanym sposobem tworzenia referencji jest użycie 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} />;
    }
}

Gdy komponent zostanie zamontowany, właściwość refatrybutu currentzostanie przypisana do przywoływanego komponentu / elementu DOM i przypisana z powrotem do nullmomentu odłączenia. Na przykład możesz uzyskać do niego dostęp za pomocą this.stepInput.current.

Aby uzyskać więcej informacji RefObject, zobacz odpowiedź @ apieceofbart lub PR createRef() został dodany.


Jeśli używasz wcześniejszej wersji Reacta (<16.3) lub potrzebujesz bardziej szczegółowej kontroli nad ustawieniem i wyłączeniem referencji, możesz użyć „callback refs” .

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

Gdy komponent zostanie zamontowany, React wywoła wywołanie refzwrotne z elementem DOM i wywoła je nullprzy odłączeniu. Na przykład możesz uzyskać do niego dostęp po prostu za pomocą this.stepInput.

Definiując refwywołanie zwrotne jako metodę powiązaną w klasie, w przeciwieństwie do funkcji wbudowanej (jak w poprzedniej wersji tej odpowiedzi), można uniknąć dwukrotnego wywołania wywołania zwrotnego podczas aktualizacji.


Tam kiedyś API gdzie refatrybut był ciąg (patrz odpowiedź Akshar Patel ), ale z powodu pewnych problemów , bibl smyczkowe są odradzane i ostatecznie zostaną usunięte.


Edytowano 22 maja 2018 r., Aby dodać nowy sposób robienia referencji w Reakcie 16.3. Dzięki @apieceofbart za wskazanie, że pojawił się nowy sposób.


Zauważ, że jest to preferowany sposób. Poniższe przykłady z refsatrybutem class zostaną wycofane w nadchodzących wersjach Reacta.
Jimi Pajala

1
Zwróć uwagę, że jest to już stary sposób :) Obecnie używa się React.createRef ()
apieceofbart

@apieceofbart Dzięki za ostrzeżenie. Zaktualizowano odpowiedź, aby uwzględnić nowy sposób.
Jeff Bowen

2
Po prostu nie widzę nic na temat maszynopisu w Twojej odpowiedzi, dodam kolejną odpowiedź
apieceofbart

Ups. Miałem maszynopis w mojej oryginalnej odpowiedzi, ale zapomniałem dołączyć go do nowej. Dodałem go z powrotem i połączyłem z Twoją odpowiedzią. Dzięki.
Jeff Bowen

30

Jednym ze sposobów (który robiłem ) jest konfiguracja ręczna:

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

wtedy możesz nawet opakować to w ładniejszą funkcję getter (np. tutaj ):

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

1
Dzięki @basarat. Wypróbowałem Twoje rozwiązanie, ale pojawia się ten błąd: „Nie można przypisać elementu typu do typu„ HTMLInputElement ”. Brak akceptacji właściwości w typie Element ''
Akshar Patel

Może być problem z nowszą wersją definicji React-Dom. W międzyczasie użyj jako potwierdzenia
basarat

Oczywiście anynie jest tutaj obowiązkowe. Większość przykładów, które widzę, używa HTMLInputElement. Po prostu stwierdzając oczywiste, ale jeśli ref jest na komponencie React (tj. PeoplePicker), Możesz użyć tego komponentu jako typu, aby uzyskać typowania.
Joe Martella,

24

Od React 16.3 sposobem dodawania referencji jest użycie React.createRef jak wskazał Jeff Bowen w swojej odpowiedzi. Możesz jednak skorzystać z Typescript, aby lepiej wpisać swój ref.

W swoim przykładzie używasz ref na elemencie wejściowym. Oto sposób, w jaki bym to zrobił:

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

    ...

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

Robiąc to, gdy chcesz skorzystać z tego odniesienia, masz dostęp do wszystkich metod wprowadzania:

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

Możesz go również używać na komponentach niestandardowych:

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

a potem masz np. dostęp do rekwizytów:

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

17

EDYCJA: To już nie jest właściwy sposób używania referencji z Typescriptem. Spójrz na odpowiedź Jeffa Bowena i zagłosuj na nią, aby zwiększyć jej widoczność.

Znalazłem odpowiedź na problem. Użyj referencji jak poniżej wewnątrz klasy.

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

Dzięki @basarat za wskazanie właściwego kierunku.


2
Nadal otrzymuję Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }', próbując uzyskać dostęp this.refs.stepInput.
Nik Sumeiko,

@NikSumeiko, otrzymujesz ten błąd, ponieważ Twój refsobiekt miał tylko [key: string]wpis.
Joe Martella,

9

React.createRef (komponenty klasy)

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

Uwaga: pomijanie starego interfejsu API String Refs tutaj ...


React.useRef (Haczyki / komponenty funkcyjne)

Odniesienia tylko do odczytu dla węzłów DOM:
const FunctionApp = () => {
  const inputRef = React.useRef<HTMLInputElement>(null) // note the passed in `null` arg
  return <input type="text" ref={inputRef} />
}
Zmienne odniesienia dla dowolnych przechowywanych wartości:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Uwaga: w tym przypadku nie inicjuj useRefz null. Zrobiłoby to renderCountReftyp readonly(patrz przykład ). Jeśli trzeba dostarczyć nulljako wartości początkowej, to zrobić:

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

Oddzwonienia (działają dla obu)

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

Próbka placu zabaw


Jaka jest różnica między useRef() as MutableRefObject<HTMLInputElement>i useRef<HTMLInputElement>(null)?
ksav

2
Dobre pytanie - currentwłaściwość MutableRefObject<HTMLInputElement>można modyfikować, natomiast useRef<HTMLInputElement>(null)tworzy RefObjecttyp z currentoznaczeniem jako readonly. Pierwszego można użyć, jeśli chcesz samodzielnie zmienić bieżące węzły DOM w refs, np. W połączeniu z zewnętrzną biblioteką. Może też być napisane bez as: useRef<HTMLInputElement | null>(null). To drugie jest lepszym wyborem dla węzłów DOM zarządzanych przez React, jak jest używane w większości przypadków. React przechowuje węzły w samych referencjach i nie chcesz grzebać w zmianie tych wartości.
ford 04

1
Dziękuję za wyjaśnienie.
ksav

7

Jeśli używasz React.FC, dodaj HTMLDivElementinterfejs:

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

I używaj go w następujący sposób:

return <div ref={myRef} />;

1
Dzięki. Kolejną wskazówką dla każdego, kto się z tym spotka, jest sprawdzenie elementu. Ten przykład odnosi się do użycia elementu DIV. Formularz na przykład użyłby - const formRef = React.useRef <HTMLFormElement> (null);
Nick Taras,

1
Dziekuje dziekuje dziekuje dziekuje dziekuje dziekuje dziekuje dziekuje dziekuje. Dziękuję Ci.
Ambrown

2

Aby użyć stylu wywołania zwrotnego ( https://facebook.github.io/react/docs/refs-and-the-dom.html ) zgodnie z zaleceniami w dokumentacji Reacta, możesz dodać definicję właściwości w klasie:

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

Nie mając pełnego przykładu, oto mój mały skrypt testowy do uzyskiwania danych wejściowych użytkownika podczas pracy z React i TypeScript. Oparty częściowo na innych komentarzach i tym linku 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

Dla użytkownika maszynopisu nie jest wymagany konstruktor.

...

private divRef: HTMLDivElement | null = null

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

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

...


0

Z definicji typu React

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

Możesz więc uzyskać dostęp do elementu refs w następujący sposób

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

bez redefinicji indeksu referencyjnego.

Jak wspomniał @manakor, możesz otrzymać błąd, taki jak

Właściwość „stepInput” nie istnieje w typie „{[key: string]: Component | Element; }

jeśli przedefiniujesz refs (zależy od wersji IDE i ts, których używasz)


0

Aby dodać inne podejście - możesz po prostu rzucić swój ref, na przykład:

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

0

Zawsze to robię, w takim przypadku, żeby złapać ref

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


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

0

Jeśli nie chcesz przekazywać dalej swojego ref, w interfejsie Rekwizytów musisz użyć RefObject<CmpType>typu zimport React, { RefObject } from 'react';


0

Dla tych, którzy szukają sposobu, aby to zrobić, gdy masz tablicę elementów:

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

umieść je w obiekcie


1
Proszę wyjaśnić swoją odpowiedź
AesSedai101

Ta odpowiedź polega na ustawieniu ctrls.input na element o jednoznacznie określonym typie, co jest metodą silnie wpisaną. To jest lepszy wybór typu „Typescript”.
Doug
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.