reactjs - Cuándo usar useImperativeHandle, useLayoutEffect y useDebugValue
(2)
No puedo entender por qué se necesitan los siguientes ganchos UseImperativeHandle, useLayoutEffect y useDebugValue, ¿puede dar ejemplos de cuándo se pueden usar, pero no ejemplos de la documentación, por favor?
useImperativeHandle
Imagina que creas una
Table
componentes.
Es posible que desee especificar cómo las
refs
en la
Table
interactúan con ese componente;
puede ser que, por ejemplo, desee proporcionar una celda en particular, en lugar de poder acceder al elemento raíz.
[index.tsx]
import React, { useRef, useEffect } from "react";
import { render } from "react-dom";
import Table from "./Table";
import "./styles.css";
function App() {
const tableRef = useRef(null);
useEffect(() => {
tableRef.current.style.color = "green";
}, []);
return (
<div>
<Table ref={tableRef} />
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
[Table.tsx]
import React, { useRef, useImperativeHandle, forwardRef } from "react";
function Table(props, ref) {
const cellRef = useRef(null);
useImperativeHandle(ref, () => cellRef.current);
return (
<table>
<tr>
<td>First</td>
<td ref={cellRef}>Second</td>
</tr>
</table>
);
}
export default forwardRef(Table);
useLayoutEffect
Esto es lo mismo que
useEffect
, pero solo se dispara una vez que se completan todas las mutaciones de DOM.
Este artículo de
Kent C. Dodds explica la diferencia tan bien como cualquiera, respecto a estos dos, dice:
El 99% del tiempo [
useEffect
] es lo que desea 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 debe usar
useLayoutEffect
cuando
useEffect
tiene problemas.
useDebugValue
Siento que la documentación es un buen ejemplo de cómo explicar esto. Si tiene un gancho personalizado y desea etiquetarlo dentro de React DevTools, entonces esto es lo que usa.
Si tiene algún problema específico con esto, entonces probablemente sería mejor hacer un comentario o hacer otra pregunta, porque creo que cualquier cosa que pongan aquí simplemente reiterará los documentos, al menos hasta que lleguemos a un problema más específico.
Permítanme prefaciar esta respuesta afirmando que todos estos ganchos se usan muy raramente. El 99% de las veces, no las necesitarás. Sólo están destinados a cubrir algunos escenarios de esquinas poco comunes.
useImperativeHandle
Por lo general, cuando usa
useRef
le da el valor de instancia del componente al que se adjunta la
ref
.
Esto le permite interactuar con el elemento DOM directamente.
useImperativeHandle
es muy similar, pero te permite hacer dos cosas:
- Te da control sobre el valor que se devuelve. En lugar de devolver el elemento de instancia, declara explícitamente cuál será el valor de retorno (vea el fragmento a continuación).
-
Le permite reemplazar funciones nativas (como
blur
,focus
, etc.) con funciones propias, lo que permite efectos secundarios del comportamiento normal o un comportamiento completamente diferente. Sin embargo, puedes llamar a la función como quieras.
Puede haber muchas razones por las que quiera hacer alguna de las dos opciones anteriores;
es posible que no desee exponer las 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 instancia que se expone a los componentes primarios cuando se usaref
Ejemplo
En este ejemplo, el valor que obtendremos de la
ref
solo contendrá la función de
blur
que
useImperativeHandle
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 se espera.
Aquí, establece
document.title
y difumina la entrada cuando se invoca
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.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
Aunque es similar en cierta medida a
useEffect()
, difiere en que se ejecutará después de que React haya confirmado las actualizaciones del DOM.
Se utiliza en casos excepcionales cuando necesita calcular la distancia entre elementos después de una actualización o realizar otros cálculos / efectos secundarios posteriores a la actualización.
La firma es idéntica a
useEffect
, pero se dispara de forma síncrona después de todas las mutaciones de DOM. Use esto para leer el diseño del DOM y volver a renderizar de forma sincrónica. Las actualizaciones programadas dentro deuseLayoutEffect
seuseLayoutEffect
sincrónicamente, antes de que el navegador tenga la oportunidad de pintar .
Ejemplo
Supongamos que tiene un elemento absolutamente posicionado cuya altura puede variar y desea colocar otro
div
debajo de él.
Puede usar
getBoundingCLientRect()
para calcular la altura y las propiedades principales del padre y luego aplicarlas a la propiedad superior del hijo.
Aquí le gustaría usar
useLayoutEffect
lugar de
useEffect
.
Vea por qué en los ejemplos a continuación:
Con
useEffect
: (note 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 podrían afectar el rendimiento.
useDebugValue
solo se llama cuando React DevTools está abierto y el enlace relacionado se inspecciona, lo que evita cualquier impacto en el rendimiento.
useDebugValue
puede usarse para mostrar una etiqueta para ganchos personalizados en React DevTools.