1. The introduction
Instrumental articles should be skipped, while literary classics should be read repeatedly. If React 0.14 introduces lifecycle parallels to instrumental articles, then 16.7 introduces Hooks that need to be reread like literary classics.
Hooks apis are much better than previous lifecycle apis, both in terms of simplicity and depth of use, so they must be understood and practiced repeatedly, or they will only stay on the surface.
The most difficult tool to understand is the useEffect tool, as compared to useState or custom Hooks. I want to learn more about useEffect from the a-complet-guide-to-useEffect article.
The original text is very long, so the summary is condensed by the author. Dan Abramov is a core developer at React.
Summary of 2.
UnLearning is learning to forget. Your previous learning experiences may prevent you from further learning.
Understanding useEffect requires a deep understanding of the Function Component rendering mechanism, Function VS Class Component: Function VS Class Component: Function VS Class Component: Function VS Class Component: Function VS Class Component
Function Component is a much more thorough state-driven abstraction. There is even no concept of a Class Component lifecycle, only one state, and React is responsible for synchronizing to the DOM. This is the key to understanding Function Component and useEffect and will be covered in more detail later.
Because the original text is very, very long, so the author cut down the content and rearrange again. Another reason why the original text is very long is that it adopts heuristic thinking and progressive writing, and the author keeps this thinking frame to the greatest extent.
Start with a few questions
Assume that the reader has extensive experience with the front-end & React development and has written several Hooks. You might think that Function Component is a great tool to use, but there’s always a question in your mind. For example:
- ๐ค how to use
useEffect
Instead ofcomponentDidMount
? - ๐ค how to use
useEffect
Collect the number? parameter[]
What does it stand for? - ๐ค
useEffect
Can the dependency of be a function? What are the functions? - ๐ค Why does fetching numbers sometimes trigger an infinite loop?
- ๐ค why sometimes
useEffect
Is the state or props you got in the
You’ve probably asked yourself and answered the first question countless times, but you’ll forget it the next time you’re writing code. The same goes for me, and I’ve covered it in three different intensive readings:
- React Hooks
- How to Make Wheels With React Hooks
- Intensive reading of Function VS Class Components
But I forgot about it the next day, because it’s awkward to implement the life cycle with Hooks. Honestly, if you want to solve this problem completely, you should forget React and forget the life cycle and reunderstand the way Function Component thinks.
The answers to the above 5 questions will not be repeated, readers can go to the original TLDR if they have doubts.
To clarify useEffect, it is best to begin with the concept of Render.
Every Render has its Props and State
Each time a snapshot of the contents of Render is created and retained, so when the State changes to Rerender, there are N Render states, each with its own fixed Props and states.
Look at the following count:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()= > setCount(count + 1)}>Click me</button>
</div>
);
}
Copy the code
Count is just a constant that does not change every time you click, and there is no two-way binding with Proxy, just a constant that exists every time you Render.
The initial value of count is 0, but as the button is clicked, the value of count is fixed to 1, 2, 3 each time the Render process is performed:
// During first render
function Counter() {
const count = 0; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
// After a click, our function is called again
function Counter() {
const count = 1; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
// After another click, our function is called again
function Counter() {
const count = 2; // Returned by useState()
// ...
<p>You clicked {count} times</p>;
// ...
}
Copy the code
In fact, not only the object, the function is also independent of each rendering. This is the Capture Value feature, which is described as “Capture Value feature here”.
Each Render has its own event handling
This explains why the following code prints 5 instead of 3:
const App = (a)= > {
const [temp, setTemp] = React.useState(5);
const log = (a)= > {
setTimeout((a)= > {
console.log(3 seconds ago temp = 5, now temp =", temp);
}, 3000);
};
return (
<div
onClick={()= >{ log(); setTemp(3); Temp = 5; temp = 5; temp = 5</div>
);
};
Copy the code
In the Render procedure that log executes, temp is treated as a constant value of 5. SetTemp (3) is given to a new Render, so log is not executed. The content executed after 3 seconds is emitted by the Render with temp 5, so the result is automatically 5.
The reason is that both temp and log have the Capture Value feature.
Each Render has its own Effects
UseEffect also has the Capture Value feature.
UseEffect is executed after the actual DOM is rendered. The Value obtained by useEffect also follows the characteristics of Capture Value:
function Counter() {
const [count, setCount] = useState(0);
useEffect((a)= > {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()= > setCount(count + 1)}>Click me</button>
</div>
);
}
Copy the code
The useEffect of the above is a constant fixed in the count of each Render process.
How do I bypass Capture Value
Using useRef, you can bypass the Capture Value feature. It can be thought that ref maintains a unique reference across all Render processes, so that all assignments or values to ref give only one final state, and there is no isolation between each Render.
function Example() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect((a)= > {
// Set the mutable latest value
latestCount.current = count;
setTimeout((a)= > {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
});
// ...
}
Copy the code
We can also tersely say that ref is Mutable and state is Immutable.
Recycling mechanism
When the component is destroyed, the listener registered with useEffect needs to be destroyed. This can be done with the return value of useEffect:
useEffect((a)= > {
ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
return (a)= > {
ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
};
});
Copy the code
When the component is destroyed, the callback within the return value function is performed. Also, due to the Capture Value feature, each “register” and “reclaim” will receive a fixed pair of values.
Replace “life cycle” with synchronization
Function Component does not have a life cycle, so don’t try to fit the Class Component concept into it. The Function Component just describes the UI state. React synchronizes it to the DOM. That’s all.
Since state synchronization occurs, the state is solidified every time it is rendered. This includes state props useEffect and all functions written in the Function Component.
However, there are some performance issues with leaving out lifecycle synchronization, so we need to tell React how to compare Effect.
Tell React how to compare Effects
Although React diff the content during DOM rendering and only changes the changed part, rather than replacing the whole, it fails to recognize the incremental changes to Effect. UseEffect’s second argument to tell React which external variables are used:
useEffect((a)= > {
document.title = "Hello, " + name;
}, [name]); // Our deps
Copy the code
Rerender, useEffect will not be executed again until the name is changed.
However, manual maintenance is cumbersome and can be missed, so you can use the ESLint plugin to automatically prompt + FIX:
Don’t lie to Dependencies
If you use a variable and don’t declare it in a dependency, you’re lying to React and the useEffect won’t be executed again if the dependent variable changes:
useEffect((a)= > {
document.title = "Hello, "+ name; } []);// Wrong: name is missing in dep
Copy the code
This may seem silly, but what about another example?
function Counter() {
const [count, setCount] = useState(0);
useEffect((a)= > {
const id = setInterval((a)= > {
setCount(count + 1);
}, 1000);
return (a)= >clearInterval(id); } []);return <h1>{count}</h1>;
}
Copy the code
We only want to execute setInterval once, so we thought we were smart enough to lie to React and write the dependency as [].
“The component performs setInterval once upon initialization and clearInterval once upon destruction, as expected.” You might think so.
But you are wrong. Since useEffect is consistent with Capture Value, the count Value you get is always initialized as 0. This means that the setInterval is always executed in a Scope with a count of 0, and your subsequent setCount operations have no effect.
The price of honesty
I changed the title a little, because honesty comes at a price:
useEffect((a)= > {
const id = setInterval((a)= > {
setCount(count + 1);
}, 1000);
return (a)= > clearInterval(id);
}, [count]);
Copy the code
If you tell React “Hey, wait until the count changes”, you’ll get one good news and two bad news.
The good news is that the code is working, and I’ve got the latest count.
The bad news:
- The timer is off, because every time
count
Changes are destroyed and re-timed. - The frequent generation/destruction of timers imposes a performance burden.
How can you be honest and efficient?
The example above uses count, but such code is awkward because you rely on an external variable in an Effect that you only want to execute once.
To be honest, you have to find a way not to rely on external variables:
useEffect((a)= > {
const id = setInterval((a)= > {
setCount(c= > c + 1);
}, 1000);
return (a)= >clearInterval(id); } []);Copy the code
SetCount also has a callback mode. You don’t need to care what the current value is, just change the “old value.” This way, although the code is always running in the first Render, it always has access to the latest state.
Decouple updates from actions
As you may have noticed, the above opportunistic approach does not completely solve the problem in all scenarios, such as relying on two states at once:
useEffect((a)= > {
const id = setInterval((a)= > {
setCount(c= > c + step);
}, 1000);
return (a)= > clearInterval(id);
}, [step]);
Copy the code
You’ll find that you have to rely on the step variable, which brings us back to the “Cost of honesty” chapter. Of course, Dan is going to solve it for us.
Use useEffect’s sibling useReducer function to decouple the update from the action:
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect((a)= > {
const id = setInterval((a)= > {
dispatch({ type: "tick" }); // Instead of setCount(c => c + step);
}, 1000);
return (a)= > clearInterval(id);
}, [dispatch]);
Copy the code
This is a local “Redux”, since the update becomes dispatch({type: “tick”}), so no matter how many variables need to be relied on for the update, no variables need to be relied on in the action that calls the update. The specific update operation can be written in the Reducer function. The online Demo.
Dan also compares useReducer to Hooks’ goldfinger mode, because it bypasses Diff, but it does solve the pain points!
Move Function to Effect
The “Tell React how to compare Diff” chapter introduces the importance of dependency and being honest with React. If the function definition is not inside the useEffect function, not only will dependencies be missed, but the ESLint plugin won’t help you collect them automatically.
Your intuition tells you that this is going to cause more trouble, like how do you reuse functions? Yes, functions that do not depend on variables in the Function Component can safely be extracted:
// โ
affected by the data flow
function getFetchUrl(query) {
return "https://hn.algolia.com/api/v1/search?query=" + query;
}
Copy the code
But what about functions that depend on variables?
What if I had to write Function outside of Effect?
If you must, use useCallback!
function Parent() {
const [query, setQuery] = useState("react");
// โ
Clearer Identity until query changes
const fetchData = useCallback((a)= > {
const url = "https://hn.algolia.com/api/v1/search?query=" + query;
// ... Fetch data and return it ...
}, [query]); // โ
Callback deps are OK
return <Child fetchData={fetchData} />; } function Child({ fetchData }) { let [data, setData] = useState(null); useEffect(() => { fetchData().then(setData); }, [fetchData]); // โ
Effect deps are OK //... }Copy the code
Since functions also have the Capture Value feature, usecallback-wrapped functions can be treated as normal variables as useEffect dependencies. What useCallback does is return a new function reference when its dependency changes, triggering the useEffect dependency change and activating its re-execution.
The benefits of useCallback
In Class Component code, you can’t directly compare the Diff function to the Diff function if you want the parameter to change:
componentDidUpdate(prevProps) {
// ๐ด This condition will never be true
if (this.props.fetchData ! == prevProps.fetchData) {this.props.fetchData(); }}Copy the code
On the contrary, the comparison is whether the parameter of the number changes:
componentDidUpdate(prevProps) {
if (this.props.query ! == prevProps.query) {this.props.fetchData(); }}Copy the code
However, this kind of code is not cohesive, and once the parameter of take number changes, it will cause maintenance crisis in many parts of the code.
UseEffect is concerned with the change of the Function Function, and the change of the Function parameter is concerned with the change of the useCallback. When combined with esLint plug-in scans, dependencies are not lost, logic is cohesive, and thus easy to maintain.
More, more cohesive
In addition to the function dependent logical cohesion, let’s look at the whole process of taking numbers:
For a normal fetch of a Class Component, consider these points:
- in
didMount
Initialize the send request. - in
didUpdate
Judge the number parameter change, change to call the number function to take the number again. - in
unmount
Add a flag to the life cycle indidMount
didUpdate
Do compatibility between the two, and cancel the count when the component is destroyed.
You get the feeling that the code is bouncing around, not only caring about the fetch function and the fetch parameter at the same time, but also maintaining multiple sets of logic in different lifetimes. What about the thinking of Function Component?
The author used useCallback to transform the original Demo.
function Article({ id }) {
const [article, setArticle] = useState(null);
// The side effect is that it depends on the pick function
useEffect((a)= > {
// didCancel assignment is more cohesive with changing position
let didCancel = false;
async function fetchData() {
const article = await API.fetchArticle(id);
if(! didCancel) { setArticle(article); } } fetchData();return (a)= > {
didCancel = true;
};
}, [fetchArticle]);
// ...
}
Copy the code
UseEffect is more expensive to learn up front, but once you use it correctly, you can handle edge cases much better than Class Component.
UseEffect is just a low-level API. In the future, businesses will be exposed to more encapsulated upper-layer apis, such as useFetch or useTheme, which will be more useful.
3. The intensive reading
At 9000+ words, the original text is very long. If you want to use Function Components or Hooks well, this article is almost a must read, because no one can guess what Capture Value is, but can’t understand the concept. Function Component doesn’t work well either.
To recap the idea of the article:
- This section describes the Capture Value feature of Render.
- Extends to Function Component to Capture everything except Ref.
- This section describes the useEffect API from the perspective of Capture Value.
- Introduced the fact that the Function Component only cares about the render state.
- It leads to the thinking of how to improve the useEffect performance.
- Introduces the basic rule of not lying to Dependencies.
- The special case of having to lie shows how to solve these problems using the Function Component mind.
- As you learn to think in terms of the Function Component concept, you begin to see some of its advantages.
- In the end, I highlighted the two features of logical cohesion and high-level encapsulation, giving you an insight into the power and elegance of Hooks.
It can be seen that a higher level than writing frameworks is to discover the beauty of code. For example, Hooks were originally created to enhance the capability of Function Component, but in the process of throwing out problems and solving problems, they can constantly see the rule restrictions, break them from another perspective, and finally realize the beauty of overall logic.
You can also read about how to enhance your learning ability. The author tells us that learning to forget can lead to better understanding. Let’s not attach a life-cycle mindset to the Hooks, because that prevents us from understanding them.
Add some other bits and pieces.
What other advantages does useEffect have
UseEffect is executed at the end of the render, so it does not block the browser rendering process, so projects written using the Function Component will generally have better performance.
It naturally conforms to the idea of React Fiber, because Fiber will suspend or jump the queue to execute the Render of different components according to the situation. If the code follows the characteristics of Capture Value, the secure access of values will be guaranteed in the environment of Fiber. Weakening the life cycle also solves the problem of interrupted execution.
UseEffect is not executed during server-side rendering.
Since it is executed after the DOM has completed, you are guaranteed to get the DOM properties after the state has taken effect.
4. To summarize
Finally, two most important points to make to see if you have understood this article:
- Capture Value feature.
- Consistency. Focus on the dependency (the second argument to useEffect []), not on when it fires.
What deeper do you mean by “consistency”? Welcome to leave a message and reply.
The discussion address is: intensive reading of the complete guide to useEffect ยท Issue #138 ยท dt-fe/weekly
If you’d like to participate in the discussion, pleaseClick here to, each week has a new theme, released on the weekend or Monday. Front end intensive reading – to help you filter the right content.
Pay attention to the front end of the intensive reading wechat public account
special Sponsors
- DevOps full process platform
Copyright Notice: Free Reprint – Non-Commercial – Non-Derivative – Keep Your Name (Creative Sharing 3.0 License)