Як використовувати refs в React with Typescript


139

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

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) або вам потрібен більш тонкий контроль над встановленням та зняттям даних, ви можете скористатися "зворотними дзвінками" .

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.
Прийняття

Можливо, це буде проблемою з новою версією визначень реагуючих домінів. Тим часом використовуйте як твердження
базарат

Очевидно, anyце не обов'язково. Більшість прикладів я бачу в застосуванні HTMLInputElement. Просто констатуючи очевидне, але якщо ваш посилання на компонент React (тобто PeoplePicker), ви можете використовувати цей компонент як тип для отримання типізації.
Джо Мартелла

24

Оскільки React 16.3 спосіб додавати refs - це використовувати 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

EDIT: Це більше не є правильним способом використання refs з 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)?
ксав

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

1
Дякуємо за роз’яснення.
ксав

7

Якщо ви використовуєте React.FC, додайте HTMLDivElementінтерфейс:

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

І використовуйте його так:

return <div ref={myRef} />;

1
Дякую. Ще одна порада для тих, хто стикається з цим, - перевірити Елемент. Цей приклад стосується використання елемента DIV. Наприклад, використовувала б форму - const formRef = React.useRef <HTMLFormElement> (null);
Нік Тарас

1
Дякую, дякую, дякую, дякую, дякую, дякую, дякую. Дякую.
Амвроун

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']);

без переглядання індексу реф.

Як згадував @manakor, ви можете отримати помилку

Властивість 'stepInput' не існує для типу '{[key: string]: Компонент | Елемент; }

якщо ви переглянете уточнення (залежить від 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']);
користувач2662112

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

Ця відповідь встановлює ctrls.input для сильно набраного елемента, який є сильно набраним способом. Це кращий вибір "Typescript".
Дуг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.