useImperativeHandle

useImperativeHandle - це хук React, який дозволяє вам налаштовувати дескриптор, виставлений як реф.

useImperativeHandle(ref, createHandle, dependencies?)

Посилання

useImperativeHandle(ref, createHandle, dependencies?)

Викличте useImperativeHandle на верхньому рівні вашого компонента, щоб налаштувати дескриптор посилання, який він розкриває:

import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
    };
  }, []);
  // ...

Дивіться більше прикладів нижче.

Параметри

  • ref: ref ви отримали як другий аргумент функції forwardRef render.

  • createHandle: Функція, яка не отримує аргументів і повертає дескриптор посилання, який ви хочете показати. Цей дескриптор може мати будь-який тип. Зазвичай, ви повернете об'єкт з методами, які ви хочете показати.

  • додаткові залежності: список усіх реактивних значень, на які посилаються у коді createHandle. Реактивні значення включають пропси, стан і всі змінні та функції, оголошені безпосередньо у тілі вашого компонента. Якщо ваш лінтер налаштований на React, він буде перевіряти, щоб кожне реактивне значення було правильно вказане як залежність. Список залежностей повинен мати постійну кількість елементів і бути записаний в рядок як [dep1, dep2, dep3]. React порівняє кожну залежність з її попереднім значенням за допомогою порівняння Object.is. Якщо повторний рендер призвів до зміни якоїсь залежності, або якщо ви пропустили цей аргумент, ваша функція createHandle буде виконана повторно, а новостворений дескриптор буде присвоєно до ref.

Повернення

useImperativeHandle повертає undefined.


Використання

Відкриття дескриптора користувацького посилання батьківському компоненту

За замовчуванням компоненти не показують свої DOM-вузли батьківським компонентам. Наприклад, якщо ви хочете, щоб батьківський компонент MyInput до мав доступ до DOM-вузла <input>, вам слід вказати forwardRef:

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  return <input {...props} ref={ref} />;
});

За допомогою вищенаведеного коду посилання на MyInput отримає вузол DOM <input>.Втім, ви можете виставити власне значення. Щоб налаштувати відкритий дескриптор, викличте useImperativeHandle на верхньому рівні вашого компонента:

import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
    };
  }, []);

  return <input {...props} />;
});

Зверніть увагу, що у вищенаведеному коді посилання ref більше не пересилається на <input>.

Наприклад, припустимо, що ви не хочете показувати весь DOM-вузол <input>, але хочете показати два його методи: focus та scrollIntoView. Для цього збережіть реальний DOM браузера в окремому рефі. Потім використовуйте useImperativeHandle, щоб виставити дескриптор лише з тими методами, які ви хочете, щоб викликав батьківський компонент:

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

Тепер, якщо батьківський компонент отримає посилання на MyInput, він зможе викликати методи focus і scrollIntoView. Однак він не матиме повного доступу до базового вузла DOM <input>.

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // This won't work because the DOM node isn't exposed:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}
import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

export default MyInput;
input {
  margin: 5px;
}

Розкриття власних імперативних методів

Методи, які ви оголошуєте через імперативний дескриптор, не обов'язково повинні точно відповідати методам DOM. Наприклад, цей компонент Post показує метод scrollAndFocusAddComment через імперативний дескриптор. Це дозволяє батьківському Page прокручувати список коментарів і фокусувати поле введення при натисканні кнопки:

import { useRef } from 'react';
import Post from './Post.js';

export default function Page() {
  const postRef = useRef(null);

  function handleClick() {
    postRef.current.scrollAndFocusAddComment();
  }

  return (
    <>
      <button onClick={handleClick}>
        Write a comment
      </button>
      <Post ref={postRef} />
    </>
  );
}
import { forwardRef, useRef, useImperativeHandle } from 'react';
import CommentList from './CommentList.js';
import AddComment from './AddComment.js';

const Post = forwardRef((props, ref) => {
  const commentsRef = useRef(null);
  const addCommentRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      scrollAndFocusAddComment() {
        commentsRef.current.scrollToBottom();
        addCommentRef.current.focus();
      }
    };
  }, []);

  return (
    <>
      <article>
        <p>Welcome to my blog!</p>
      </article>
      <CommentList ref={commentsRef} />
      <AddComment ref={addCommentRef} />
    </>
  );
});

export default Post;
import { forwardRef, useRef, useImperativeHandle } from 'react';

const CommentList = forwardRef(function CommentList(props, ref) {
  const divRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      scrollToBottom() {
        const node = divRef.current;
        node.scrollTop = node.scrollHeight;
      }
    };
  }, []);

  let comments = [];
  for (let i = 0; i < 50; i++) {
    comments.push(<p key={i}>Comment #{i}</p>);
  }

  return (
    <div className="CommentList" ref={divRef}>
      {comments}
    </div>
  );
});

export default CommentList;
import { forwardRef, useRef, useImperativeHandle } from 'react';

const AddComment = forwardRef(function AddComment(props, ref) {
  return <input placeholder="Add comment..." ref={ref} />;
});

export default AddComment;
.CommentList {
  height: 100px;
  overflow: scroll;
  border: 1px solid black;
  margin-top: 20px;
  margin-bottom: 20px;
}

Не зловживайте рефами. Ви повинні використовувати рефи лише для імперативної поведінки, яку ви не можете виразити реквізитами: наприклад, прокрутка до вузла, фокусування вузла, запуск анімації, виділення тексту і так далі.

Якщо ви можете виразити щось як проп, не варто використовувати реф. Наприклад, замість того, щоб виставляти імперативний дескриптор типу { open, close } з компонента Modal, краще взяти isOpen як проп типу <Modal isOpen={isOpen} />. Ефекти можуть допомогти вам розкрити імперативну поведінку за допомогою пропсів.