Как использовать React.forwardRef в компоненте на основе классов?

113

Я пытаюсь использовать React.forwardRef, но не понимаю, как заставить его работать в компоненте на основе классов (а не в HOC).

В примерах документации используются элементы и функциональные компоненты, даже классы-оболочки в функциях для компонентов более высокого порядка.

Итак, начнем с чего-то вроде этого в их ref.jsфайле:

const TextInput = React.forwardRef(
    (props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />)
);

и вместо этого определив его примерно так:

class TextInput extends React.Component {
  render() {
    let { props, ref } = React.forwardRef((props, ref) => ({ props, ref }));
    return <input type="text" placeholder="Hello World" ref={ref} />;
  }
}

или

class TextInput extends React.Component {
  render() { 
    return (
      React.forwardRef((props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />))
    );
  }
}

только работает: /

Кроме того, я знаю, что знаю, что ref - это не способ реакции. Я пытаюсь использовать стороннюю библиотеку холста и хочу добавить некоторые из их инструментов в отдельные компоненты, поэтому мне нужны прослушиватели событий, поэтому мне нужны методы жизненного цикла. Позже он может пойти другим путем, но я хочу попробовать это.

Документы говорят, что это возможно!

Пересылка ссылок не ограничивается компонентами DOM. Вы также можете пересылать ссылки на экземпляры компонентов класса.

из примечания в этом разделе.

Но затем они продолжают использовать HOC вместо классов.

Хан Лазарь
источник

Ответы:

108

Идея всегда использовать одну и ту же опору для refкласса может быть реализована путем проксирования экспорта класса с помощью помощника.

class ElemComponent extends Component {
  render() {
    return (
      <div ref={this.props.innerRef}>
        Div has ref
      </div>
    )
  }
}

export default React.forwardRef((props, ref) => <ElemComponent 
  innerRef={ref} {...props}
/>);

В общем, да, мы вынуждены использовать другую опору для пересылки ссылки, но это можно сделать под хабом. Важно, чтобы публика использовала его как обычный справочник.

Г-н Бр
источник
4
Что вы будете делать, если ваш компонент завернут в HOC, такой как React-Redux connectили marterial-ui withStyles?
J. Hesters
В чем проблема с connectили withStyles? Вы должны обернуть все HOC forwardRefи внутренне использовать "safe" prop для передачи ссылки на самый нижний компонент в цепочке.
Mr Br
Любое представление о том, как проверить это в Enzyme, когда вы используете setState?
zero_cool
Извините, мне нужно немного больше деталей. Не уверен, в чем именно проблема.
Mr Br
2
Я получаю: Неизменяемое нарушение: объекты недопустимы в качестве дочерних элементов React (найдено: объект с ключами {$$ typeof, render}). Если вы хотели отобразить коллекцию дочерних элементов, используйте вместо этого массив.
Брайан
9
class BeautifulInput extends React.Component {
  const { innerRef, ...props } = this.props;
  render() (
    return (
      <div style={{backgroundColor: "blue"}}>
        <input ref={innerRef} {...props} />
      </div>
    )
  )
}

const BeautifulInputForwardingRef = React.forwardRef((props, ref) => (
  <BeautifulInput {...props} innerRef={ref}/>
));

const App = () => (
  <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
)

Вам нужно использовать другое имя опоры для пересылки ссылки в класс. innerRefобычно используется во многих библиотеках.

Себастьян Лорбер
источник
в вашем коде beautifulInputElementэто скорее функция, чем элемент React, он должен быть такимconst beautifulInputElement = <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
Оливье Буассе
8

По сути, это просто функция HOC. Если вы хотите использовать его с классом, вы можете сделать это самостоятельно и использовать обычные реквизиты.

class TextInput extends React.Component {
    render() {
        <input ref={this.props.forwardRef} />
    }
}

const ref = React.createRef();
<TextInput forwardRef={ref} />

Этот шаблон используется, например, styled-componentsи называется innerRefтам.

Лукаш Войцеховски
источник
3
Использование опоры с другим именем, например innerRef, полностью упускает суть. Цель - полная прозрачность: я должен иметь возможность рассматривать a, <FancyButton>как если бы это был обычный элемент DOM, но, используя подход стилизованных компонентов, мне нужно помнить, что этот компонент использует свойство с произвольным именем для ссылок, а не просто ref.
user128216
2
В любом случае, styled-components намеревается отказаться от поддержкиinnerRef в пользу передачи ссылки дочернему с помощью React.forwardRef, что и пытается выполнить OP.
user128216
5

Это можно сделать с помощью компонента более высокого порядка, если хотите:

import React, { forwardRef } from 'react'

const withForwardedRef = Comp => {
  const handle = (props, ref) =>
    <Comp {...props} forwardedRef={ref} />

  const name = Comp.displayName || Comp.name
  handle.displayName = `withForwardedRef(${name})`

  return forwardRef(handle)
}

export default withForwardedRef

А затем в вашем файле компонента:

class Boop extends React.Component {
  render() {
    const { forwardedRef } = this.props

    return (
      <div ref={forwardedRef} />
    )
  }
}

export default withForwardedRef(Boop)

Я выполнил предварительную работу с тестами и опубликовал для этого пакет react-with-forwarded-ref: https://www.npmjs.com/package/react-with-forwarded-ref

rpearce
источник
3

Если вам нужно повторно использовать это во многих различных компонентах, вы можете экспортировать эту способность во что-то вроде withForwardingRef

const withForwardingRef = <Props extends {[_: string]: any}>(BaseComponent: React.ReactType<Props>) =>
    React.forwardRef((props, ref) => <BaseComponent {...props} forwardedRef={ref} />);

export default withForwardingRef;

использование:

const Comp = ({forwardedRef}) => (
 <input ref={forwardedRef} />
)
const EnhanceComponent = withForwardingRef<Props>(Comp);  // Now Comp has a prop called forwardedRef
орепор
источник