Solución:
Permítame comenzar esta respuesta indicando que todos estos ganchos se usan muy raramente. El 99% del tiempo, no los necesitará. Solo están destinados a cubrir algunos escenarios raros de casos de esquina.
useImperativeHandle
Por lo general, cuando usa useRef
se le da el valor de instancia del componente ref
se adjunta a. Esto le permite interactuar con el elemento DOM directamente.
useImperativeHandle
es muy similar, pero te permite hacer dos cosas:
- Le da control sobre el valor que se devuelve. En lugar de devolver el elemento de la instancia, indica explícitamente cuál será el valor de retorno (consulte el fragmento a continuación).
- Le permite reemplazar funciones nativas (como
blur
,focus
, etc.) con funciones propias, permitiendo así efectos secundarios al comportamiento normal, o un comportamiento completamente diferente. Sin embargo, puedes llamar a la función como quieras.
Puede haber muchas razones por las que desee hacer cualquiera de las anteriores; es posible que no desee exponer propiedades nativas al padre o tal vez desee cambiar el comportamiento de una función nativa. Podría haber muchas razones. Sin embargo, useImperativeHandle
rara vez se utiliza.
useImperativeHandle
personaliza el valor de la instancia que se expone a los componentes principales cuando se utilizaref
Ejemplo
En este ejemplo, el valor que obtendremos del ref
solo contendrá la función blur
que declaramos en nuestro useImperativeHandle
. No contendrá ninguna otra propiedad (Estoy registrando el valor para demostrar esto). La función en sí también está “personalizada” para comportarse de manera diferente a lo que normalmente esperarías. Aquí, se pone document.title
y difumina la entrada cuando blur
se invoca.
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.log(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
Si bien es similar en cierta medida a useEffect()
, difiere en que se ejecutará después de que React haya comprometido actualizaciones al DOM. Se usa en casos excepcionales cuando necesita calcular la distancia entre elementos después de una actualización o hacer otros cálculos / efectos secundarios posteriores a la actualización.
La firma es idéntica a
useEffect
, pero se dispara sincrónicamente después de todas las mutaciones DOM. Use esto para leer el diseño del DOM y volver a renderizar sincrónicamente. Actualizaciones programadas en el interioruseLayoutEffect
se lavará sincrónicamente, antes de que el navegador tenga la oportunidad de pintar.
Ejemplo
Suponga que tiene un elemento absolutamente posicionado cuya altura puede variar y desea colocar otro div
debajo de ella. Podrías usar getBoundingCLientRect()
para calcular la altura del padre y las propiedades superiores y luego aplicarlas a la propiedad superior del hijo.
Aquí querrías usar useLayoutEffect
en vez de useEffect
. Vea por qué en los ejemplos siguientes:
Con useEffect
: (observe el comportamiento nervioso)
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>
Con 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
A veces, es posible que desee depurar ciertos valores o propiedades, pero hacerlo puede requerir operaciones costosas que pueden afectar el rendimiento.
useDebugValue
solo se llama cuando las React DevTools están abiertas y se inspecciona el enlace relacionado, lo que evita cualquier impacto en el rendimiento.
useDebugValue
se puede usar para mostrar una etiqueta para ganchos personalizados en React DevTools.
Sin embargo, personalmente nunca he usado este gancho. Tal vez alguien en los comentarios pueda dar una idea con un buen ejemplo.
useImperativeHandle
useImperativeHandle
le permite determinar qué propiedades se expondrán en una ref. En el siguiente ejemplo, tenemos un componente de botón y nos gustaría exponer el someExposedProperty
propiedad en esa ref:
[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.log(Object.keys(buttonRef.current)); // ['someExposedProperty']
console.log("click in index.tsx");
buttonRef.current.someExposedProperty();
};
return (
<div>
<Button onClick={handleClick} ref={buttonRef} />
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
[Button.tsx]
import React, { useRef, useImperativeHandle, forwardRef } from "react";
function Button(props, ref) {
const buttonRef = useRef();
useImperativeHandle(ref, () => ({
someExposedProperty: () => {
console.log(`we're inside the exposed property function!`);
}
}));
return (
<button ref={buttonRef} {...props}>
Button
</button>
);
}
export default forwardRef(Button);
Disponible aquí.
useLayoutEffect
Esto es lo mismo que useEffect
, pero solo se activa una vez que se completan todas las mutaciones DOM. Este artículo de Kent C. Dodds explica la diferencia tan bien como cualquiera, con respecto a estos dos, dice:
99% del tiempo [
useEffect
] es lo que quieres usar.
No he visto ningún ejemplo que ilustre esto particularmente bien, y tampoco estoy seguro de que pueda crear nada. Probablemente sea mejor decir que solo debes usar useLayoutEffect
cuando useEffect
tiene problemas.
useDebugValue
Siento que los documentos son un buen ejemplo de cómo explicar este. Si tiene un gancho personalizado y le gustaría etiquetarlo dentro de React DevTools, entonces esto es lo que usa.
Si tiene algún problema específico con esto, probablemente sea mejor comentar o hacer otra pregunta, porque siento que cualquier cosa que la gente ponga aquí será simplemente reiterando los documentos, al menos hasta que lleguemos a un problema más específico.