Как использовать ссылки в React с Typescript


139

Я использую Typescript с React. У меня возникли проблемы с пониманием того, как использовать ссылки, чтобы получить статическую типизацию и intellisense в отношении узлов реакции, на которые ссылаются ссылки. Мой код выглядит следующим образом.

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

Ответы:


183

Если вы используете React 16.3+, предлагаемый способ создания ссылок использует 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} />;
    }
}

Когда компонент монтируется, свойство refатрибута currentбудет присвоено указанному компоненту / элементу DOM и присвоено обратно nullпри размонтировании. Так, например, вы можете получить к нему доступ, используя this.stepInput.current.

Для получения дополнительной информации RefObjectсм . Ответ @ apieceofbart или добавлен PR createRef() .


Если вы используете более раннюю версию React (<16.3) или вам нужен более детальный контроль над установкой и отключением refs, вы можете использовать «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} />
    }
}

Когда компонент монтируется, React вызывает refобратный вызов с элементом DOM и будет вызывать его, nullкогда он размонтируется. Так, например, вы можете получить к нему доступ, просто используя this.stepInput.

Определив refобратный вызов как связанный метод класса, а не встроенную функцию (как в предыдущей версии этого ответа), вы можете избежать повторного вызова обратного вызова во время обновлений.


Там раньше в API , где refатрибут был строкой (см ответ Akshar Патела ), но из - за некоторых проблем , струнные рефов настоятельно рекомендуется и в конечном итоге будут удалены.


Отредактировано 22 мая 2018 г., чтобы добавить новый способ выполнения ссылок в React 16.3. Спасибо @apieceofbart за указание на новый способ.


Обратите внимание, что это предпочтительный способ. Приведенные ниже примеры с refsатрибутом класса будут устаревшими в следующих версиях React.
Джими Пайала

1
Обратите внимание, что это уже старый способ :) в настоящее время используется React.createRef ()
apieceofbart

@apieceofbart Спасибо за внимание. Обновлен ответ, чтобы включить новый способ.
Джефф Боуэн

2
Я просто не вижу в вашем ответе ничего о машинописном тексте, добавлю еще один ответ
apieceofbart

Упс. В моем исходном ответе был Typescript, но забыл включить его в новый. Добавил его обратно и добавил ссылку на ваш ответ. Спасибо.
Джефф Боуэн

30

Один из способов (который я делал ) - настроить вручную:

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

то вы даже можете обернуть это в более приятную функцию получения (например, здесь ):

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

1
Спасибо @basarat. Я пробовал ваше решение, но получаю сообщение об ошибке «Элемент типа не может быть назначен типу» HTMLInputElement. Свойство accept отсутствует в типе Element ''
Акшар Патель

Может быть проблема с более новой версией определений react-dom. А пока используйте как утверждение
basarat

Очевидно, anyздесь не обязательно. Большинство примеров я вижу в использовании HTMLInputElement. Просто констатирую очевидное, но если ваша ссылка находится в компоненте React (т. PeoplePickerЕ.), Вы можете использовать этот компонент как тип для ввода текста.
Джо Мартелла

24

Начиная с React 16.3, можно добавлять ссылки с помощью React.createRef, как указал Джефф Боуэн в своем ответе. Однако вы можете воспользоваться Typescript, чтобы лучше напечатать свою ссылку.

В вашем примере вы используете ref для элемента ввода. Я бы сделал это следующим образом:

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

    ...

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

Делая это, когда вы хотите использовать эту ссылку, вы получаете доступ ко всем методам ввода:

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

Вы также можете использовать его для пользовательских компонентов:

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

а затем получить, например, доступ к реквизитам:

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

17

РЕДАКТИРОВАТЬ: это больше не правильный способ использовать ссылки с Typescript. Посмотрите на ответ Джеффа Боуэна и проголосуйте за него, чтобы он стал заметнее.

Нашел ответ на проблему. Используйте ссылки, как показано ниже, внутри класса.

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

Спасибо @basarat за то, что указали в правильном направлении.


2
Я все еще получаю Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }'при попытке доступа this.refs.stepInput.
Ник Сумейко

@NikSumeiko, вы получили эту ошибку, потому что у вашего refsобъекта была только [key: string]запись.
Джо Мартелла

9

React.createRef (компоненты класса)

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

Примечание. Пропуск старого API String Refs здесь ...


React.useRef (Крючки / функциональные компоненты)

Ссылки только для чтения для узлов DOM:
const FunctionApp = () => {
  const inputRef = React.useRef<HTMLInputElement>(null) // note the passed in `null` arg
  return <input type="text" ref={inputRef} />
}
Изменяемые ссылки для произвольных сохраненных значений:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Примечание. Не выполняйте инициализацию useRefс помощью nullв этом случае. Сделал бы renderCountRefтип readonly(см. Пример ). Если вам нужно указать nullначальное значение, сделайте следующее:

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

Обратные вызовы (работают для обоих)

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

Образец детской площадки


В чем разница между useRef() as MutableRefObject<HTMLInputElement>и useRef<HTMLInputElement>(null)?
ksav

2
Хороший вопрос - currentсвойство MutableRefObject<HTMLInputElement>может быть изменено, при этом useRef<HTMLInputElement>(null)создается RefObjectтип, currentпомеченный как readonly. Первый можно использовать, если вам нужно самостоятельно изменить текущие узлы DOM в ссылках, например, в сочетании с внешней библиотекой. Она также может быть написана без as: useRef<HTMLInputElement | null>(null). Последний - лучший выбор для DOM-узлов, управляемых React, как и используется в большинстве случаев. React хранит узлы в самих справочниках, и вы не хотите возиться с изменением этих значений.
ford04

1
Спасибо за разъяснения.
ksav

7

Если вы используете React.FC, добавьте HTMLDivElementинтерфейс:

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

И используйте это так:

return <div ref={myRef} />;

1
Спасибо. Еще один совет для всех, кто сталкивается с этим, - проверить элемент. Этот пример относится к использованию элемента DIV. Например, форма будет использовать - const formRef = React.useRef <HTMLFormElement> (null);
Ник Тарас,

1
Спасибо, спасибо, спасибо, спасибо, спасибо, спасибо, спасибо. Спасибо.
Ambrown

2

Чтобы использовать стиль обратного вызова ( https://facebook.github.io/react/docs/refs-and-the-dom.html ), как рекомендовано в документации React, вы можете добавить определение свойства в класс:

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

Не имея полного примера, вот мой небольшой тестовый сценарий для получения пользовательского ввода при работе с React и TypeScript. Частично основано на других комментариях и этой ссылке 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

Для пользователя машинописного текста конструктор не требуется.

...

private divRef: HTMLDivElement | null = null

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

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

...


0

Из определения типа React

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

Таким образом, вы можете получить доступ к вашему элементу refs следующим образом

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

без переопределения индекса refs.

Как упоминал @manakor, вы можете получить ошибку, например

Свойство 'stepInput' не существует для типа '{[key: string]: Component | Элемент; }

если вы переопределяете ссылки (зависит от используемой IDE и версии ts)


0

Просто чтобы добавить другой подход - вы можете просто использовать свой реф, например:

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

0

Я всегда так делаю, в таком случае, чтобы получить реф

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


пусть ввод: HTMLInputElement = ReactDOM.findDOMNode <HTMLInputElement> (this.refs ['input']);
user2662112

0

Если вы не хотите пересылать свой ref, в интерфейсе Props вам нужно использовать RefObject<CmpType>тип изimport React, { RefObject } from 'react';


0

Для тех, кто интересуется, как это сделать, когда у вас есть массив элементов:

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

поместить их в объект


1
Пожалуйста, объясните свой ответ
AesSedai101 01

Этот ответ устанавливает ctrls.input для строго типизированного элемента, что является наиболее типизированным способом. Это лучший вариант "Машинописного текста".
Дуг
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.