Según los documentos:
componentDidUpdate()
se invoca inmediatamente después de que se realiza la actualización. Este método no se llama para el renderizado inicial.
Podemos usar el nuevo useEffect()
gancho para simular componentDidUpdate()
, pero parece que useEffect()
se ejecuta después de cada renderizado, incluso la primera vez. ¿Cómo hago para que no se ejecute en el renderizado inicial?
Como puede ver en el ejemplo siguiente, componentDidUpdateFunction
se imprime durante el renderizado inicial pero componentDidUpdateClass
no se imprimió durante el renderizado inicial.
function ComponentDidUpdateFunction() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
class ComponentDidUpdateClass extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidUpdate() {
console.log("componentDidUpdateClass");
}
render() {
return (
<div>
<p>componentDidUpdateClass: {this.state.count} times</p>
<button
onClick={() => {
this.setState({ count: this.state.count + 1 });
}}
>
Click Me
</button>
</div>
);
}
}
ReactDOM.render(
<div>
<ComponentDidUpdateFunction />
<ComponentDidUpdateClass />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
javascript
reactjs
react-hooks
Yangshun Tay
fuente
fuente
count
?Respuestas:
Podemos usar el
useRef
gancho para almacenar cualquier valor mutable que nos guste , por lo que podríamos usarlo para realizar un seguimiento de si es la primera vezuseEffect
que se ejecuta la función.Si queremos que el efecto se ejecute en la misma fase que lo
componentDidUpdate
hace, podemos usaruseLayoutEffect
en su lugar.Ejemplo
const { useState, useRef, useLayoutEffect } = React; function ComponentDidUpdateFunction() { const [count, setCount] = useState(0); const firstUpdate = useRef(true); useLayoutEffect(() => { if (firstUpdate.current) { firstUpdate.current = false; return; } console.log("componentDidUpdateFunction"); }); return ( <div> <p>componentDidUpdateFunction: {count} times</p> <button onClick={() => { setCount(count + 1); }} > Click Me </button> </div> ); } ReactDOM.render( <ComponentDidUpdateFunction />, document.getElementById("app") );
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
fuente
useRef
conuseState
, pero utilizando la incubadora provocó una re-render, que no está sucediendo cuando se asigna afirstUpdate.current
así que supongo que esta es la única manera agradable :)componentDidUpdate
con ganchos, por eso lo usé.Puede convertirlo en ganchos personalizados , así:
import React, { useEffect, useRef } from 'react'; const useDidMountEffect = (func, deps) => { const didMount = useRef(false); useEffect(() => { if (didMount.current) func(); else didMount.current = true; }, deps); } export default useDidMountEffect;
Ejemplo de uso:
import React, { useState, useEffect } from 'react'; import useDidMountEffect from '../path/to/useDidMountEffect'; const MyComponent = (props) => { const [state, setState] = useState({ key: false }); useEffect(() => { // you know what is this, don't you? }, []); useDidMountEffect(() => { // react please run me if 'key' changes, but not on initial render }, [state.key]); return ( <div> ... </div> ); } // ...
fuente
Hice un
useFirstRender
gancho simple para manejar casos como enfocar una entrada de formulario:import { useRef, useEffect } from 'react'; export function useFirstRender() { const firstRender = useRef(true); useEffect(() => { firstRender.current = false; }, []); return firstRender.current; }
Comienza como
true
, luego cambia afalse
enuseEffect
, que solo se ejecuta una vez y nunca más.En su componente, utilícelo:
const firstRender = useFirstRender(); const phoneNumberRef = useRef(null); useEffect(() => { if (firstRender || errors.phoneNumber) { phoneNumberRef.current.focus(); } }, [firstRender, errors.phoneNumber]);
Para su caso, solo usaría
if (!firstRender) { ...
.fuente
@ravi, el tuyo no llama a la función de desmontaje pasada. Aquí tienes una versión un poco más completa:
/** * Identical to React.useEffect, except that it never runs on mount. This is * the equivalent of the componentDidUpdate lifecycle function. * * @param {function:function} effect - A useEffect effect. * @param {array} [dependencies] - useEffect dependency list. */ export const useEffectExceptOnMount = (effect, dependencies) => { const mounted = React.useRef(false); React.useEffect(() => { if (mounted.current) { const unmount = effect(); return () => unmount && unmount(); } else { mounted.current = true; } }, dependencies); // Reset on unmount for the next mount. React.useEffect(() => { return () => mounted.current = false; }, []); };
fuente
useEffect(() => {...});
dependencies
parámetro cuando lo llama.@MehdiDehghani, su solución funciona perfectamente bien, una adición que debe hacer es desmontar, restablecer el
didMount.current
valor afalse
. Cuando intente usar este gancho personalizado en otro lugar, no obtendrá el valor de la caché.import React, { useEffect, useRef } from 'react'; const useDidMountEffect = (func, deps) => { const didMount = useRef(false); useEffect(() => { let unmount; if (didMount.current) unmount = func(); else didMount.current = true; return () => { didMount.current = false; unmount && unmount(); } }, deps); } export default useDidMountEffect;
fuente
false
.