Как использовать ссылки в 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);
basarat
источник
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
apieceofbart
источник
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} />
}

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

ford04
источник
В чем разница между 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} />;
JulienRioux
источник
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; }}/>)
}
lucavgobbi
источник
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 });
        }
    });
}

}

Tikall
источник
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);

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

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

MiF
источник
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)}
    />
}
Jocker
источник
-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 для строго типизированного элемента, что является наиболее типизированным способом. Это лучший вариант "Машинописного текста".
Дуг