De acordo com a documentação:
componentDidUpdate()
é chamado imediatamente após a atualização ocorrer. Este método não é chamado para a renderização inicial.
Podemos usar o novo useEffect()
gancho para simular componentDidUpdate()
, mas parece que useEffect()
está sendo executado após cada renderização, mesmo na primeira vez. Como faço para que ele não execute na renderização inicial?
Como você pode ver no exemplo abaixo, componentDidUpdateFunction
é impresso durante a renderização inicial, mas componentDidUpdateClass
não foi impresso durante a renderização 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
fonte
fonte
count
?Respostas:
Podemos usar o
useRef
gancho para armazenar qualquer valor mutável que quisermos , então podemos usá-lo para rastrear se é a primeira vez que auseEffect
função está sendo executada.Se quisermos que o efeito seja executado na mesma fase que o
componentDidUpdate
faz, podemos usaruseLayoutEffect
.Exemplo
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>
fonte
useRef
comuseState
, mas usar o setter acionou uma re-renderização, o que não acontece ao atribuir a,firstUpdate.current
então acho que essa é a única maneira legal :)componentDidUpdate
com ganchos, por isso usei.Você pode transformá-lo em ganchos personalizados , assim:
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;
Exemplo 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> ); } // ...
fonte
Fiz um
useFirstRender
gancho simples para lidar com casos como focalizar uma entrada de formulário:import { useRef, useEffect } from 'react'; export function useFirstRender() { const firstRender = useRef(true); useEffect(() => { firstRender.current = false; }, []); return firstRender.current; }
Ele começa como e
true
, em seguida, muda parafalse
nouseEffect
, que é executado apenas uma vez e nunca mais.Em seu componente, use-o:
const firstRender = useFirstRender(); const phoneNumberRef = useRef(null); useEffect(() => { if (firstRender || errors.phoneNumber) { phoneNumberRef.current.focus(); } }, [firstRender, errors.phoneNumber]);
Para o seu caso, você apenas usaria
if (!firstRender) { ...
.fonte
@ravi, o seu não chama a função de desmontagem passada. Esta é uma versão um pouco mais 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; }, []); };
fonte
useEffect(() => {...});
dependencies
parâmetro ao chamá-lo.@MehdiDehghani, sua solução funciona perfeitamente bem, uma adição que você precisa fazer é desmontar e redefinir o
didMount.current
valor parafalse
. Ao tentar usar este gancho personalizado em outro lugar, você não obtém o valor do cache.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;
fonte
false
.