Avoid Meaningless Optimization in React
# Perface
This is a summary of team weekly seminar and the topic is as title. Mainly four advanced features were introduced, including useCallback
, useMemo
, useEffect
, React.memo
. This article itself is already a summary so it won't be long for each sections.
# useCallback
So here is a case of when useCallback needed. Let's say we have functional components A, B. While A manage the states, B only gets data from props. Therefore we have:
Example 1.
A -- count
|
|
props.onClick = { () => doSomeThings }
|
|
--> B
2
3
4
5
6
7
8
We knew that everytime props change, the components is renderred again. But it's not everytime we need B to be re-renderred. When B.onClick gets an anonymous function from A, it is technically a different entity for B, and thus B is re-renderred. In this case, useCallback can be use like useCallback(()=>{do something}, [])
by monitoring nothing to avoid the re-renderring.
However, the drawback is the use of useCallback
also blocking the new renderring if you unconciously set wrong "monitorees" and thus the component won't show any feedback. (I think there is also some cases of implict dependencies such as states in subcomponents that no one will immediately remember to declare in the useCallback, but by now I can't get any case). 😦
The basic mechanism of useCallback
is that it will set a global scope variable which tries to remember the states of dependencies in succession. So it comes with the cost.
# useMemo
Here I forgot what is said in the seminar. So I take a look to some other reference. Credit & reference.
We can intuitively say that useMemo
is an advanced useState
hook with a brain to remember something. It maps the combinations of key props to the function responses.
The source code is below and just focus the tail parts of useCallback
and useMemo
implmentation.
function updateCallback(callback, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
function updateMemo(nextCreate, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
const nextValue = nextCreate(); // 🤩
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
So what I understood is, it is basically use extra space tradeoff the cut down of redundant calcuation.
Some notes that I am not quite sure if it is correct:
for these hooks, the method of comparation can be customize
the comparation of object is just to compare the shallow layers of attributes of each objects.
the use of useMemo to reduce RTF component results make a n^3 scale complexity comparation like the comparation between DOM trees. (just a guess by me)
# useEffect
useEffect is not quite suitable to call
Dispatch
actions as it make a binding between the dependencies and the dispatche. --- QiangGe
useEffect is called at the "nexttick" of renderring.
# React.memo
Totally forgot. Try research later.
# Best practise
Sometimes the incoming props may be undefined or null for the renderring components. Currently a reliable practise could be to force the component is renderred only after it is ready (just set that as the variable to be the flag 😃), which is better than making many conditions in many places.
When the project gets larger, you will have many components at a deep layer. Some just conceptally extract a part of code from the render secction as a new component and the extracted component is merely reused later. So, it is suggested to create pure components instead of the components with self-managed states. And arrange the data-processing and condition logic to single component/function/hook as possible as you can to make it easier to review and modify in the future.
useContext is not suitable to use everywhere, specially when it is at a deep layer, then the efficiency is low.
😕 : Efficiency is low, in terms of what? speed? How?