Как использовать ссылки в React с Typescript
Я использую 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>
);
}}
16 ответов
Если вы используете 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 или пиар createRef()
был добавлен в.
Если вы используете более раннюю версию React (<16.3) или вам нужно более детальное управление, когда ссылки установлены и не установлены, вы можете использовать "обратные ссылки".
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
Атрибут был строкой (см . ответ Акшара Пателя), но из-за некоторых проблем строковые ссылки настоятельно не рекомендуется и в конечном итоге будут удалены.
Отредактировано 22 мая 2018 года, чтобы добавить новый способ делать ссылки в Реакте 16.3. Спасибо @apieceofbart за указание на то, что появился новый способ.
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} />
}
Если вы используете React.FC
, добавить HTMLDivElement
интерфейс:
const myRef = React.useRef<HTMLDivElement>(null);
И используйте его так:
return <div ref={myRef} />;
Начиная с React 16.3, способ добавления ссылок заключается в использовании React.createRef, как указал Джефф Боуэн в своем ответе. Однако вы можете воспользоваться Typescript, чтобы лучше набрать свой реф.
В вашем примере вы используете ссылку на элемент ввода. Таким образом, я бы так и сделал:
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
РЕДАКТИРОВАТЬ: Это больше не правильный способ использовать ссылки с Typescript. Посмотрите на ответ Джеффа Боуэна и увеличьте его, чтобы улучшить видимость.
Нашел ответ на проблему. Используйте ссылки как ниже внутри класса.
refs: {
[key: string]: (Element);
stepInput: (HTMLInputElement);
}
Спасибо @basarat за указание в правильном направлении.
FIRST ADD AN IMPORT
import React, { useRef } from "react";
ТОГДА ЭТО
const studentCapacityRef = useRef<HTMLInputElement>(null);
ИЛИ ЭТО
const studentCapacityRef = useRef<HTMLAreaElement>(null);
ИЛИ ЭТО
const studentCapacityRef = useRef<HTMLDivElement>(null);
и т. д...
Для тех, кто интересуется, как это сделать, когда у вас есть массив элементов:
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)}
/>
}
Чтобы использовать стиль обратного вызова ( 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; }}/>)
}
Для пользователя машинописного текста конструктор не требуется.
...
private divRef: HTMLDivElement | null = null
getDivRef = (ref: HTMLDivElement | null): void => {
this.divRef = ref
}
render() {
return <div ref={this.getDivRef} />
}
...
Если вы не хотите, чтобы переслать ref
в интерфейсе Props нужно использовать RefObject<CmpType>
введите от import React, { RefObject } from 'react';
Без полного примера, вот мой маленький тестовый скрипт для получения пользовательского ввода при работе с React и TypeScript. Основано частично на других комментариях и этой ссылке https://medium.com/@basarat/strongly-typed-refs-for-react-typescript-9a07419f807
/// <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 });
}
});
}
}
Из определения типа React
type ReactInstance = Component<any, any> | Element;
....
refs: {
[key: string]: ReactInstance
};
Таким образом, вы можете получить доступ к элементу refs следующим образом
stepInput = () => ReactDOM.findDOMNode(this.refs['stepInput']);
без переопределения индекса ссылок.
Как упомянул @manakor, вы можете получить ошибку как
Свойство 'stepInput' не существует для типа '{ [key: string]: Component | Элемент; }
если вы переопределите ссылки (зависит от IDE и используемой вами версии)
Просто чтобы добавить другой подход - вы можете просто привести свой реф, что-то вроде:
let myInputElement: Element = this.refs["myInput"] as Element
Я всегда делаю это, в этом случае, чтобы получить ссылку
let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);
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();
}
}
положить их в объект