React
March 19, 2022

Используем forwardRef в React для оптимизации

В этой статье мы разберем концепцию использования ссылок в React и поймем как это может нам помочь при взаимодействии с DOM деревом.

Для того, чтобы получить больше практического опыта мы рассмотрим как можно создавать ссылки, прикреплять их к элементам DOM дерева и работать с методом forwardRef.

Что такое forwardRef в React?

React forwardRef - это метод, который позволяет родительским компонентам прокидывать ссылки своим дочерним компонентам. Использование forwardRef в React дает дочернему компоненту ссылку на элемент DOM, созданный его родительским компонентом. Затем это позволяет дочернему элементу читать и изменять этот элемент в любом месте, где он используется.

Как именно работает forwardRef?

Давайте разберем что такое ссылки и как они работают на примерах для того чтобы понять как работает перенаправление ссылок. Обычно в React родительские элементы передают какие-то данные дочерним через props.

Обычно, для того чтобы изменить поведение дочернего элемента вам нужно просто передать другой набор props для вызова перерендера. Но сейчас нашей целью будет изменение поведение дочернего компонента не меняя его набор props и не вызывая перерисовку, для этого мы воспользуемся ссылками.

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

Давайте разберем небольшой пример и поставим фокус на поле ввода по нажатии на кнопку используя ссылку:

import * as React from "react";
import ReactDOM from "react-dom";
 
export const App = () => {
 const ref = React.useRef();
 
 const focus = () => ref.current.focus();
 
 return (
   <div className="App">
     <input ref={ref} placeholder="my input" />
     <button onClick={focus}>Поставить фокус</button>
   </div>
 );
}

Для того чтобы получить схожий эффект, мы могли бы использовать JavaScript:

document.getElementById('myInput').focus();

Но так делать не рекомендуется и даже помечено как 'bad practice' при использовании React.

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

Когда использовать ссылки в React?

В официальной документации React, приведены ситуации, когда мы можем использовать ссылки для различных задач:

Управление фокусом, выделением текста, воспроизведением видео:

Давайте представим, что в вашем приложении есть видео плеер, с которым должны уметь взаимодействовать разные кнопки (ставить на паузу / запускать). Имеет смысл изменять только первично отрисованный компонент плеера, а не менять его состояние через props и не вызывать перерендер.

Запуск анимации:

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

Счетчики значений:

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

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

Интересно

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

Внимание

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

Ссылки в классовых компонентах

Создание ссылок

Чтобы создать ссылку, можно воспользоваться функцией React.createRef().

После создания ссылки могут быть прикреплены к элементам React с помощью атрибута ref:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    // Теперь мы можем использовать newRef в компоненте
    this.newRef = React.createRef();
  }
 ...
  render() {
    return <div ref={this.myRef} />;
  }
}

Сначала, мы в конструкторе создаем ссылку, используя React.createRef(), после чего прикрепляем ее к элементу через ref.

Прикрепление ссылок

Ссылки создаются при отрисовке компонента и могут быть проинициализированы либо в componentDidMount(), либо в конструкторе.

Ссылки могут быть прикреплены либо к элементам DOM дерева, либо к компонентам класса, но к не к функциональным компонентам, поскольку у них нет экземпляров.

Каждая ссылка, которую вы определяете, будет указывать на элемент DOM древа. Следовательно, когда вы хотите сослаться на этот элемент в функции render(), React предоставляет атрибут current, который ссылается на указанный сам узел.

// Ссылается на узел из DOM дерева
const DOMNode = this.newRef.current;

Значение ссылки зависит от типа узла, на который она указывает.

Для того чтобы лучше понять ссылки и типы узлов, на которые они могут указывать, давайте обратимся к документации:

  • Когда атрибут ref используется с HTML элементом, ссылка, созданная в конструкторе с помощью React.createRef(), получает базовый элемент DOM в качестве своего свойства current.
  • Когда атрибут ref используется с классовым компонентом, созданная ссылка получает в качестве current экземпляр компонента с пропсами, состоянием и методами.

В качестве примера я сделала небольшую песочницу:

Тут я использовала ref для того чтобы воспроизводить и ставить на паузу видео при нажатии на кнопки под плеером. При нажатии на кнопки будут вызываться методы видео плеера, а вот перерисовка не произойдет.

Ссылки в функциональных компонентах

В функциональных компонентах мы не можем просто использовать функцию React.createRef(), поскольку она будет создавать каждый раз новую ссылку.

Мы могли бы использовать useEffect и useState для обработки ссылок, но в React есть гораздо более простой способ - хук useRef. useRef заботится о том, чтобы каждый раз возвращать ту же ссылку, что и при первоначальной отрисовке.

Перенаправление ссылок с использованием forwardRef

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

Перенаправление ссылок - это метод для автоматической передачи ссылки через компонент одному из его дочерних элементов.

forwardRef - это функция, используемая для передачи ссылки дочернему компоненту.

Давайте рассмотрим пример компонента inputText:

const InputText = (props) => <input {...props} />);

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

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

Но, если мы обернем наш компонент InputText в React.forwardRef , то появится возможность получить ссылку на внутренний input:

const InputText = React.forwardRef((props, ref) => (
  <input ref={ref} {...props} />
));

Давайте используем наш компонент в контексте приложения для создания кнопки, которая автоматически фокусирует ввод при нажатии:

import * as React from "react";
import ReactDOM from "react-dom";
 
const InputText = React.forwardRef((props, ref) => (
 <input ref={ref} {...props} />
));
 
export const App = () => {
 const inputRef = React.useRef();
 
 function focus() {
   inputRef.current.focus();
 }
 
 return (
   <div className="App">
     <InputText ref={inputRef} placeholder="my input" />
     <button onClick={focus}>Focus</button>
   </div>
 );
}
 
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Подводим итог

Ссылки в React - это мощный инструмент, который обеспечивает прямой доступ к узлам DOM и, таким образом, открывает целый новый спектр методов и опций для создания более производительных, многофункциональных и чистых компонентов.

Однако прямой доступ к DOM часто рассматривается в React как 'bad practice', и по какой-то причине при неправильном использовании он может превратить все свои преимущества в реальные проблемы и сложные баги. Как правило, его следует избегать и использовать только при очень специфических обстоятельствах и после тщательного изучения.

В этом руководстве мы познакомились с темой ссылок и пересылки ссылок, рассмотрели несколько вариантов использования и создали код с использованием функциональных и классовых компонентов. Чтобы узнать больше о ссылках, ознакомьтесь с документацией .

Интересное: