Когда использовать useImperativeHandle, useLayoutEffect и useDebugValue

Я не могу понять, зачем нужны следующие хуки useImperativeHandle, useLayoutEffect и useDebugValue, можете привести примеры, когда их можно использовать, но не примеры из документации, пожалуйста.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
111
0
72 347
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

useImperativeHandle

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

[index.tsx]

import React, { useRef } from "react";
import { render } from "react-dom";
import Button from "./Button";

import "./styles.css";

function App() {
  const buttonRef = useRef(null);

  const handleClick = () => {
    console.info(Object.keys(buttonRef.current)); // ['someExposedProperty']
    console.info("click in index.tsx");
    buttonRef.current.someExposedProperty();
  };

  return (
    <div>
      <Button onClick = {handleClick} ref = {buttonRef} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

[Кнопка.tsx]

import React, { useRef, useImperativeHandle, forwardRef } from "react";

function Button(props, ref) {
  const buttonRef = useRef();
  useImperativeHandle(ref, () => ({
    someExposedProperty: () => {
      console.info(`we're inside the exposed property function!`);
    }
  }));
  return (
    <button ref = {buttonRef} {...props}>
      Button
    </button>
  );
}

export default forwardRef(Button);

Доступно здесь.

useLayoutEffect

Это то же самое, что и useEffect, но срабатывает только после завершения всех мутаций DOM. эта статья От Кента С. Доддса, как никто другой, объясняет разницу в отношении этих двух, он говорит:

99% of the time [useEffect] is what you want to use.

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

useDebugValue

Я чувствую, что документы делает довольно хороший пример объяснения этого. Если у вас есть пользовательский хук, и вы хотите пометить его в React DevTools, то используйте его.

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

Ваш пример useImperativeHandle не работает.

Kevin Ghadyani 23.07.2019 03:05

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

OliverRadini 23.07.2019 07:50
useLayoutEffect когда вам нужно мутировать (изменить) DOM, сгенерированный React, полезно для интеграции библиотек, которые мутируют DOM с React.
Victor Castro 10.11.2019 17:21

В примере useImperativeHandle просто отсутствует правильный код в Table.jsx, я просто скопировал предоставленный вами фрагмент, и он работает. Спасибо!

lewislbr 23.03.2020 08:39

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

AuthorProxy 11.06.2020 09:54

@AuthorProxy Я обновил ответ на версию кода, которая теперь работает. Надеюсь, вы сможете немного пересмотреть свой комментарий — другие сочли предыдущий пример полезным, и я считаю полезным попытаться оставить комментарии строго конструктивными.

OliverRadini 11.06.2020 11:49
Ответ принят как подходящий

Позвольте мне предварить этот ответ, заявив, что все эти хуки используются очень редко. В 99% случаев они вам не понадобятся. Они предназначены только для покрытия некоторых редких сценариев крайних случаев.


useImperativeHandle

Обычно, когда вы используете useRef, вам дается значение экземпляра компонента, к которому прикреплен ref. Это позволяет вам напрямую взаимодействовать с элементом DOM.

useImperativeHandle очень похож, но позволяет делать две вещи:

  1. Это дает вам контроль над возвращаемым значением. Вместо того, чтобы возвращать элемент экземпляра, вы явно указываете, каким будет возвращаемое значение (см. фрагмент ниже).
  2. Это позволяет вам заменять собственные функции (такие как blur, focus и т. д.) вашими собственными функциями, тем самым допуская побочные эффекты нормального поведения или вообще другое поведение. Хотя вы можете называть функцию как угодно.

Может быть много причин, по которым вы хотите сделать что-либо из вышеперечисленного; вы можете не захотеть предоставлять собственные свойства родителю или, возможно, вы хотите изменить поведение собственной функции. Причин может быть много. Однако useImperativeHandle используется редко.

useImperativeHandle customizes the instance value that is exposed to parent components when using ref

Пример

В этом примере значение, которое мы получим из ref, будет содержать только функцию blur, которую мы объявили в нашем useImperativeHandle. Он не будет содержать никаких других свойств (Я регистрирую значение, чтобы продемонстрировать это). Сама функция также «настроена» так, чтобы вести себя не так, как вы обычно ожидаете. Здесь он устанавливает document.title и размывает ввод при вызове blur.

const MyInput = React.forwardRef((props, ref) => {
  const [val, setVal] = React.useState('');
  const inputRef = React.useRef();

  React.useImperativeHandle(ref, () => ({
    blur: () => {
      document.title = val;
      inputRef.current.blur();
    }
  }));

  return (
    <input
      ref = {inputRef}
      val = {val}
      onChange = {e => setVal(e.target.value)}
      {...props}
    />
  );
});

const App = () => {
  const ref = React.useRef(null);
  const onBlur = () => {
    console.info(ref.current); // Only contains one property!
    ref.current.blur();
  };

  return <MyInput ref = {ref} onBlur = {onBlur} />;
};

ReactDOM.render(<App />, document.getElementById("app"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id = "app"></div>

useLayoutEffect

Хотя в некоторой степени он похож на useEffect(), он отличается тем, что запускается после того, как React зафиксирует обновления в DOM. Используется в редких случаях, когда нужно рассчитать расстояние между элементами после обновления или сделать другие пост-апдейтные расчеты/побочные эффекты.

The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.

Пример

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

Здесь вы хотели бы использовать useLayoutEffect, а не useEffect. Посмотрите, почему в примерах ниже:

С useEffect: (обратите внимание на нервное поведение)

const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref = {msgRef} className = "msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref = {boxRef} className = "box" onClick = {() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef = {boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id = "app"></div>

С useLayoutEffect:

const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useLayoutEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref = {msgRef} className = "msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref = {boxRef} className = "box" onClick = {() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef = {boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id = "app"></div>

useDebugValue

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

useDebugValue вызывается только тогда, когда React DevTools открыт и соответствующий хук проверяется, что предотвращает какое-либо влияние на производительность.

useDebugValue can be used to display a label for custom hooks in React DevTools.

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

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

user192344 07.05.2020 15:55

@user192344 user192344 недостаточно контекста, чтобы ответить. Не совсем понятно, что вам нужно. Я предлагаю вам опубликовать новый вопрос и объяснить вашу проблему :)

Chris 07.05.2020 19:43

@user192344 user192344 Я думаю, что то, что вы описываете, больше похоже на работу типа Redux, где вы должны передать функцию отправки, возвращаемую из useReducer(). Если вы хотите предоставить набор функций для манипулирования состоянием (думайте об этом как об API состояния), хотя useImperativeHandle() может работать, вероятно, не рекомендуется использовать его таким образом.

Brain2000 27.05.2020 07:48

useImperativeHandle

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

const Sidebar=forwardRef((props,ref)=>{

       const [visibility,setVisibility]=useState(null)
       
       const opensideBar=()=>{
         setVisibility(!visibility)
       }
        useImperativeHandle(ref,()=>({
            
            opensideBar:()=>{
               set()
            }
        }))
        
       return(
        <Fragment>
         <button onClick = {opensideBar}>SHOW_SIDEBAR</button>
         {visibility==true?(
           <aside className = "sidebar">
              <ul className = "list-group ">
                <li className = " list-group-item">HOME</li>
                <li className = " list-group-item">ABOUT</li>
                <li className = " list-group-item">SERVICES</li>
                <li className = " list-group-item">CONTACT</li>
                <li className = " list-group-item">GALLERY</li>
             </ul>
          </aside>
         ):null}
        </Fragment>
       )
     }
    
    
    //using sidebar component 

 
class Main extends Component{
    
     myRef=createRef();
    
     render(){
      return(
       <Fragment>
         <button onClick = {()=>{
                     ///hear we calling sidebar component
                     this.myRef?.current?.opensideBar()
                    }}>
          Show Sidebar
        </button>
        <Sidebar ref = {this.myRef}/>
       </Fragment>
      )
     }
 }

Крюк useImperativeHandle очень помог мне в моем случае использования.

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

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

Этого легко добиться, добавив методы внутри хука useImperativeHandle, и тогда они будут доступны его родителю.

Мой компонент сетки выглядит примерно так:

import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import ThirdPartyGrid from 'some-library';

export default forwardRef((props, forwardedRef) => {
    const gridRef = useRef(null);
    useImperativeHandle(forwardedRef,
        () => ({

            storeExpandedRecords () {
                // Some code
            },

            restoreExpandedRecords () {
                // Some code
            },

        }));
    
    return (
        <div ref = {forwardedRef}>
            <ThirdPartyGrid 
                ref = {gridRef}
                {...props.config}
            />
        </div>
    );
});

И затем в моем родителе я могу выполнить эти методы:

import React, { useRef, useEffect } from 'react';

export default function Parent () {
    const gridRef = useRef(null);

    const storeRecords = () => {
        gridRef.current.storeExpandedRecords();
    };

    useEffect(() => {
        storeRecords();
    }, []);

    return <GridWrapper ref = {gridRef} config = {{ something: true }} />
};

Я вижу, что на этот вопрос уже есть ответы. Итак, я просто хочу поделиться своими статьями о useImperativeHandle, useEffect и useLayoutEffect.

Я написал хук для статьи aboutuseImperativeHandle на основе примера, вы можете посмотреть его здесь: useImperativeHandle по примерам

Я также написал статью о эффекты в React, в которой подробно объясняются различия хуков использованиеЭффект и использоватьLayoutEffect: Руководство для начинающих по эффектам в React

Другие вопросы по теме