问题
I'd like to clarify my understanding of what's happening here. Any detail to improve my current understanding'd be appreciated.
function Timer() {
let [time, setTime] = useState(5);
useEffect(() => {
let timer = setInterval(() => {
setTime(time - 1);
}, 1000)
return () => clearInterval(timer);
}, );
return <div>{time}</div>
}
export default Timer
https://codesandbox.io/s/cranky-chaplygin-g1r0p
time
is being initialised to5
.useEffect
is read. Its callback must be made ready to fire later.- The
div
is rendered. useEffect
's callback is executed.setInterval
's callback gets ready to fire. SurelyuseEffect
'sreturn
statement doesn't fire here, because if it did it would cancel the timer (and the timer does work).- After, roughly, 1 second,
setInterval
's callback fires changing the state oftime
(to 4). - Now that a piece of state has changed, the function is re-executed.
time
, a new variable, is initialised to the new time state. - A new
useEffect
is read, it's callback made ready to fire later. (This happens because there is no 2nd argument ofuseEffect()
). - The component function's
return
statement is executed. This effectively re-renders thediv
. - At some point, the previous
useEffect
'sreturn
statement executes (which disables thetimer
in that previoususeEffect
). I'm not sure when this occurs. - The 'new'
useEffect
's callback is executed.
回答1:
Your understanding of the sequence of events is correct. The only thing missing is the precise timing of the effect callbacks and cleanup.
When the component re-renders, any useEffect
s will have their dependency arrays analyzed for changes. If there has been a change, then that effect callback will run. These callbacks are guaranteed to run in the order that they're declared in the component. For example, below, a
will always be logged just before b
.
const App = () => {
const [num, setNum] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setNum(num => num + 1);
}, 1000);
}, []);
React.useEffect(() => {
console.log('a', num);
}, [num]);
React.useEffect(() => {
console.log('b', num);
}, [num]);
return num;
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
These effect callbacks will run shortly after the browser re-paints.
Now add the effect cleanup callback into the mix. These will always run synchronously just before the effect callback for a render runs. For example, let's say the component starts at Render A, and in Render A, an effect hook has returned a cleanup callback. Then, some state changes, and a transition to Render B occurs, and there exists a useEffect
with a dependency array that includes the state change. What will happen is:
- The functional component will be called with the new props/state, for Render B
- The component returns the new markup at the end of the function
- The browser repaints the screen if necessary
- The cleanup function from render A will run
- The effect callback from render B will run
You can see the source code for those last two actions here:
commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork);
commitHookEffectListMount(Passive$1 | HasEffect, finishedWork);
That first call invokes all cleanup callbacks from a prior render. That second call invokes all effect callbacks for the current render. Current render effect callbacks run synchronously after the execution of prior render cleanup callbacks.
回答2:
Does a functional component 'dismount' after a change in its state and does this result in previous useEffect callback return statements executing?
No, component dismounts only once at end of its life time, React allows to execute a callback with useEffect
hook by providing an empty dep array with a return
statement:
useEffect(() => {
return () => {
console.log("unmounts");
};
}, []);
When component dismounts?
When its parent stops rendering it. See conditional rendering.
Could you help me understand, in general or in the example in my question, when the return statement of useEffect executes?
Depends on the dep array:
- if it's empty
[]
, on unmount. - if it has dependencies
[value1,value2]
, on dependencies change (shallow comparison). - if it has no dependencies (no 2nd argument for
useEffect
) it runs on every render.
See follow up question useEffect in depth / use of useEffect?
回答3:
You are almost on point, let me try to provide some more clarity on this
Before we dive into this we need to understand that if useEffect
has nothing as the second argument ( as it is in the question ) the function passed to useEffect
will be executed in every render.
- The argument passed to
useState()
is used as the initial value. Hence time is initialised to 5 useEffect
is executed =>setTimeout()
will now execute after 1 sec => return value ofuseEffect
lets call it func1 is stored to be executed later- The value of time, which is 5 right now, is rendered
- After 1 second
setTimeout()
executes and changes the value of time to 4 and sets it - hence a re-render occurs and
useEffect
is executed again. At this pointuseEffect
executes func1 to clean up the previous effect and then function passed touseEffect
will execute so a newsetTimeout()
is initialised and return statement, lets call this func2 is stored to be executed later - The value of time, which is 4 right now, is rendered
- After 1 second
setTimeout()
executes and changes the value of time to 3 and sets it - This now goes back to point 4 and the process keeps happening infinitely
来源:https://stackoverflow.com/questions/65225094/when-does-the-useeffects-callbacks-return-statement-execute