This article is available on Github: github.com/beichensky/… Welcome Star!
Q&A
-
🐤 What are the obvious differences between useState and setState?
-
🐤 useState and useReducer initial value If it is a return value of an execution function, will the execution function be executed more than once?
-
🐤 restore the initial value of useReducer, why not restore back?
-
🐤 useEffect How to model componentDidMount, componentUpdate, componentWillUnmount life cycle?
-
🐤 How to set event listeners for DOM correctly in useEffect?
-
🐤 useEffect, useCallback, and props are old values.
-
🐤 useEffect why is there an infinite execution problem?
-
🐤 useEffect race
-
🐤 How do I save some properties in a function component and follow the component through creation and destruction?
-
🐤 How can I optimize when useCallback triggers frequently?
-
🐤 What is the difference between useCallback and useMemo?
-
🐤 Should useCallback and useMemo be used frequently?
-
🐤 How do I call a child’s state or method from a parent?
I believe that after reading this article, you can get the answers you need.
First, function component rendering process
Let’s take a look at how function components work:
Counter.js
function Counter() {
const [count, setCount] = useState(0);
return <p onClick={()= > setCount(count + 1)}>count: {count}</p>;
}
Copy the code
Each time you click on the p tag, count + 1, setCount triggers the rendering of the function component. A re-rendering of a function component is actually a re-execution of the current function. In each rendering of a function component, the internal state, the function, and the props passed in are independent.
Such as:
// First render
function Counter() {
// First render, count = 0
const [count, setCount] = useState(0);
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
// Click the p TAB to trigger a second rendering
function Counter() {
// Second render, count = 1
const [count, setCount] = useState(0);
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
// Click the p TAB to trigger a third render
function Counter() {
// Render the third time, count = 2
const [count, setCount] = useState(0);
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
// ...
Copy the code
The methods declared in function components are similar. Therefore, each frame rendered by the function component corresponds to its own independent state, function, and props.
UseState/useReducer
useState
VS setState
-
UseState applies only to function components and setState applies only to class components
-
UseState can declare multiple values in a function component, and state values in a class component must be declared in the state object of this
-
In general, when state changes:
-
When useState changes state, the values of the same useState declaration are overwritten, and multiple useState declarations trigger multiple renders
-
SetState When state is changed, objects with multiple setStates are merged
-
-
When useState changes state and sets the same value, function components do not re-render, whereas class components that inherit Component trigger rendering even with the same value of setState
useState
VS useReducer
The initial value
useState
If the initial value is a value, you can directly set it. If it is a function return value, you are advised to set it using a callback function
const initCount = c= > {
console.log('initCount execution');
return c * 2;
};
function Counter() {
const [count, setCount] = useState(initCount(0));
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
You’ll notice that even though the Counter component doesn’t reinitialize count when it rerenders, the initCount function repeats itself
Change to a callback function:
const initCount = c= > {
console.log('initCount execution');
return c * 2;
};
function Counter() {
const [count, setCount] = useState(() = > initCount(0));
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
At this point, initCount is only executed when the Counter component is initialized, and then no matter how the component is rendered, initCount is never executed again. Okay
useReducer
When setting an initial value, it must be a value, not a callback function- If an executor returns the value, the executor will still execute when the component is re-rendered
Modify the state of
useState
The value is the same when changing the statususeState
The declared state is overwritten
function Counter() {
const [count, setCount] = useState(0);
return (
<p
onClick={()= > {
setCount(count + 1);
setCount(count + 2);
}}
>
clicked {count} times
</p>
);
}
Copy the code
The step of count in the current interface is 2
useReducer
Change the status several timesdispatch
The components are rendered in sequence
function Counter() {
const [count, dispatch] = useReducer((x, payload) = > x + payload, 0);
return (
<p
onClick={()= > {
dispatch(1);
dispatch(2);
}}
>
clicked {count} times
</p>
);
}
Copy the code
The step of count in the current interface is 3
reductionuseReducer
The initial value of, why can not restore
Take this example:
const initPerson = { name: 'Ming' };
const reducer = function (state, action) {
switch (action.type) {
case 'CHANGE':
state.name = action.payload;
return { ...state };
case 'RESET':
return initPerson;
default:
returnstate; }};function Counter() {
const [person, dispatch] = useReducer(reducer, initPerson);
const [value, setValue] = useState('little red');
const handleChange = useCallback(e= > setValue(e.target.value), []);
const handleChangeClick = useCallback(() = > dispatch({ type: 'CHANGE'.payload: value }), [value]);
const handleResetClick = useCallback(() = > dispatch({ type: 'RESET' }), []);
return (
<>
<p>name: {person.name}</p>
<input type="text" value={value} onChange={handleChange} />
<br />
<br />
<button onClick={handleChangeClick}>Modify the</button>| {'}<button onClick={handleResetClick}>reset</button>
</>
);
}
Copy the code
Click the Modify button to change the name of the object to little red, click the reset button to restore the original object. But let’s look at the effect:
You can see that after name changes the red, no matter how you click the reset button, it cannot be restored.
This is because we changed the state property of initPerson, causing the initial value of initPerson to change. Therefore, after RESET, even if initPerson ‘ ‘is returned, the name value is still red.
Therefore, when we modify the data, we should pay attention to not perform attribute operation on the original data, but create a new object for operation. For example, make the following changes:
// ...
const reducer = function (state, action) {
switch (action.type) {
case 'CHANGE':
/ /! The modified code
constnewState = { ... state,name: action.payload }
return newState;
case 'RESET':
return initPerson;
default:
returnstate; }};// ...
Copy the code
Check the effect after modification, can be reset normally:
Third, useEffect
useEffect
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
console.log('count: ', count);
});
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
Every time you click on the P TAB, the Counter component is rerendered and you can see the log printed on the console.
useuseEffect
simulationcomponentDidMount
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
console.log('count: ', count);
// Set the dependency to an empty array} []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
Set the useEffect dependency to an empty array, and you can see that the console prints out only when the component is first rendered. No matter how the count is updated, it will not print again.
useuseEffect
simulationcomponentDidUpdate
- Use conditions to determine if a dependency is an initial value, or follow update logic if it is not
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
if(count ! = =0) {
console.log('count: ', count);
}
}, [count]);
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
The downside of this approach, however, is that multiple comparisons are required when there are multiple dependencies, so you can choose to use the following approach.
- use
useRef
Set an initial value for comparison
function Counter() {
const [count, setCount] = useState(0);
const firstRender = useRef(true);
useEffect(() = > {
if (firstRender.current) {
firstRender.current = false;
} else {
console.log('count: ', count);
}
}, [count]);
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
useuseEffect
simulationcomponentWillUnmount
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
console.log('count: ', count);
return () = > {
console.log('component will unmount')}}, []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
UseEffect returns a function from the wrap function that triggers execution when the function component is re-rendered, cleaning up the previous frame of data. So this function can do some cleaning. If useEffect is given a dependency that is an empty array, then when the return function is executed, the component is actually unloaded.
Set useEffect to an empty array of dependencies and return a function that is equivalent to componentWillUnmount
Note that you must set the dependency to an empty array. If it is not an empty array, this function is not fired when the component is unloaded, but when the component is re-rendered, cleaning up the data from the previous frame.
inuseEffect
The right toDOM
Setting up Event Listeners
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
const handleClick = function() {
console.log('count: ', count);
}
window.addEventListener('click', handleClick, false)
return () = > {
window.removeEventListener('click', handleClick, false)}; }, [count]);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
In useEffect, set the event listener. In return, clean up the side effects and cancel the event listener
inUseEffect, useCallback, useMemo
Obtained fromThe state, props,
Why old value
As we said earlier, each frame of a function component has its own independent state, function, and props. UseEffect, useCallback, and useMemo have the caching function.
Therefore, we take the variables under the current scope of the corresponding function. If the dependencies are not set correctly, useEffect, useCallback, and useMemo are not re-executed, using the same values as before.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
const handleClick = function() {
console.log('count: ', count);
}
window.addEventListener('click', handleClick, false)
return () = > {
window.removeEventListener('click', handleClick, false)}; } []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
Again, if you set useEffect to an empty array as a dependency, no matter how many times the count changes, if you click on window, the printed count will still be 0
useEffect
Why does infinite execution occur in
- Not for
useEffect
Set the dependency and in theuseEffect
In the updatestate
, will cause the interface to repeatedly render
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
setCount(count + 1);
});
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
This would cause the interface to repeat rendering indefinitely because there are no dependencies set. If we wanted to set count to a new value when the interface first renders, we would just set the dependency to an empty array.
Changed: Only the count value is set during initialization
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
setCount(count + 1); } []);return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
The above example is a problem when a dependency is missing, and a problem when a dependency is properly set.
- There is one requirement at this point: every time
count
When adding, we need to turn the page (page
+ 1), see how to write:
Because we’re relying on count, we need to include count in the dependency, and we need to rely on page to modify the page, we need to include page in the dependency as well
function Counter() {
const [count, setCount] = useState(0);
const [page, setPage] = useState(0);
useEffect(() = > {
setPage(page + 1);
}, [count, page]);
return (
<>
<p onClick={()= > setCount(count + 1)}>clicked {count} times</p>
<p>page: {page}</p>
</>
);
}
Copy the code
This will also cause the interface to repeatedly render the situation, then change the page to function mode, and remove the page from the dependency
Modified: can achieve the effect, and avoid repeated rendering
function Counter() {
const [count, setCount] = useState(0);
const [page, setPage] = useState(0);
useEffect(() = > {
setPage(p= > p + 1);
}, [count]);
return (
<>
<p onClick={()= > setCount(count + 1)}>clicked {count} times</p>
<p>page: {page}</p>
</>
);
}
Copy the code
Fourth, race
Executing earlier but returning later incorrectly overwrites the status value
In useEffect, there may be a scenario of network request. We will initiate a network request according to the id passed in by the parent component. When the id changes, the request will be renewed.
function App() {
const [id, setId] = useState(0);
useEffect(() = > {
setId(10); } []);// Pass the ID attribute
return <Counter id={id} />;
}
// Simulate a network request
const fetchData = id= >
new Promise(resolve= > {
setTimeout(() = > {
const result = ` id for${id}Request result ';
resolve(result);
}, Math.random() * 1000 + 1000);
});
function Counter({ id }) {
const [data, setData] = useState('In the request... ');
useEffect(() = > {
// Send a network request to modify the interface display information
const getData = async() = > {const result = await fetchData(id);
setData(result);
};
getData();
}, [id]);
return <p>result: {data}</p>;
}
Copy the code
Show results:
In the example above, refreshing the page several times, you can see that the end result sometimes displays the result of a request with ID 0, and sometimes it displays the result with ID 10. The correct result should be ‘request result with ID 10’. That’s the problem with races.
Solutions:
- Canceling asynchronous operations
// Map requested by the storage network
const fetchMap = new Map(a);// Simulate a network request
const fetchData = id= >
new Promise(resolve= > {
const timer = setTimeout(() = > {
const result = ` id for${id}Request result ';
// End of request Remove the corresponding ID
fetchMap.delete(id);
resolve(result);
}, Math.random() * 1000 + 1000);
// Set the ID to fetchMap
fetchMap.set(id, timer);
});
// Cancel the network request corresponding to id
const removeFetch = (id) = > {
clearTimeout(fetchMap.get(id));
}
function Counter({ id }) {
const [data, setData] = useState('In the request... ');
useEffect(() = > {
const getData = async() = > {const result = await fetchData(id);
setData(result);
};
getData();
return () = > {
// Cancel the network request
removeFetch(id)
}
}, [id]);
return <p>result: {data}</p>;
}
Copy the code
Show results:
No matter how the page is refreshed, only the result of the request with ID 10 is displayed.
- Set the Boolean variable to trace
// Simulate a network request
const fetchData = id= >
new Promise(resolve= > {
setTimeout(() = > {
const result = ` id for${id}Request result ';
resolve(result);
}, Math.random() * 1000 + 1000);
});
function Counter({ id }) {
const [data, setData] = useState('In the request... ');
useEffect(() = > {
let didCancel = false;
const getData = async() = > {const result = await fetchData(id);
if (!didCancel) {
setData(result);
}
};
getData();
return () = > {
didCancel = true;
};
}, [id]);
return <p>result: {data}</p>;
}
Copy the code
You can see that no matter how the page is refreshed, only the result of the request with ID 10 is displayed.
5. How to save non in function componentsstate
,props
The value of the
Function components do not point to this, so in order to save component instance properties, useRef can be used for operations
The refs of function components have the ability to penetrate closures. By converting a value of a common type to an object reference with a current property, the property value is kept up to date each time it is accessed.
Is guaranteed to be accessed in every frame of the function componentstate
The values are the same
- Let’s see if we don’t use it
useRef
In the case of each framestate
How are values printed
function Counter() {
const [count, setCount] = useState(0);
useEffect(() = > {
const handleClick = function() {
console.log('count: ', count);
}
window.addEventListener('click', handleClick, false)});return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
Click the p label 5 times and then click the window object to see the print result:
- use
useRef
And then, in every frameref
How are values printed
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() = > {
// Set the latest state to countref.current
countRef.current = count;
const handleClick = function () {
console.log('count: ', countRef.current);
};
window.addEventListener('click', handleClick, false);
});
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
The same operation as before, click the P label 5 times, then click the Window interface, you can see the print result
Using useRef ensures that the state value accessed in each frame of the function component is the same.
How do I save properties of a function component instance
The function component has no instance, so properties cannot be mounted on this. What if we want to create a non-state, props variable that can be created and destroyed along with the function component?
Again, you can use useRef, which not only works on the DOM, but also converts ordinary variables into objects with the current attribute
For example, we want to set up an instance of Model that will generate the Model instance when the component is created, and will automatically generate a new Model instance when the component is destroyed and recreated
class Model {
constructor() {
console.log('create Model');
this.data = []; }}function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(new Model());
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
In this way, when the function component is created, an instance of Model is generated and mounted to the current property of countRef. CountRef is not reassigned when it is rerendered.
This means that the same Model instance is used until the component is uninstalled, after which the current Model instance is destroyed.
A close look at the console output shows that while countRef is not reassigned, the Model constructor is still executed multiple times while the component is being rerendered
So at this point we can borrow the useState feature and rewrite it.
class Model {
constructor() {
console.log('create Model');
this.data = []; }}function Counter() {
const [count, setCount] = useState(0);
const [model] = useState(() = > new Model());
const countRef = useRef(model);
return <p onClick={()= > setCount(count + 1)}>clicked {count} times</p>;
}
Copy the code
Using this way, you can use properties in the Model instance without changing state, flag, data source, or even Mobx store.
Six, useCallback
How do you avoid frequent useCallback execution when dependencies change frequently?
function Counter() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() = > {
setCount(count + 1);
}, [count]);
return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code
Here, we extract the Click event and wrap it with a useCallback, but that doesn’t work very well.
Because the Counter component rerenders currently only rely on count changes, the useCallback is used and not used here.
useuseReducer
alternativeuseState
You can use useReducer instead.
function Counter() {
const [count, dispatch] = useReducer(x= > x + 1.0);
const handleClick = useCallback(() = >{ dispatch(); } []);return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code
The dispatch function returned by useReducer comes with Memoize and does not change over multiple renders. Therefore dispatch is not required as a dependency in useCallback.
tosetState
Intermediate transfer function
function Counter() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() = > {
setCount(c= > c + 1); } []);return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code
When you use a function as an argument in setCount, the value you receive is the most recent state value, so you can perform an operation with that value.
throughuseRef
Do closure penetration
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() = > {
countRef.current = count;
}, [count]);
const handleClick = useCallback(() = > {
setCount(countRef.current + 1); } []);return <p onClick={handleClick}>clicked {count} times</p>;
}
Copy the code
The same effect can be achieved this way. Not recommended, not only does it require more code, but it can cause unexpected problems.
Seven, useMemo
Some of the problems and solutions with useCallback are described above. Let’s take a look at useMemo.
UseMemo and React.memo are different:
useMemo
Some data inside the component is optimized and cached, lazy processing.React.memo
It’s wrapped around the function component, inside the componentstate
、props
Perform a shallow comparison to determine if rendering is required.
Difference between useMemo and useCallback
useMemo
Is a value that can be a property or a function (including a component).useCallback
The return value of can only be functions
Therefore, useMemo can replace useCallback to some extent. Equivalent conditions: useCallback(fn, DEps) => useMemo(() => FN, DEps)
Therefore, some of the optimizations mentioned above about useCallback also apply to useMemo.
Should useCallback and useMemo be used frequently
Here is my humble opinion: it is not recommended to use frequently
Hey, guys, before you say anything, let me just say something
The reason:
- UseCallback and useMemo are actually called as functions in the function component, so the first argument is the callback we pass. This callback will be created with or without useCallback and useMemo, so it will not reduce the cost of creating the function
- Not only does it fail to reduce the cost of creation, but with useCallback and useMemo, the second parameter dependency also needs to perform a shallow comparison at each render, which virtually increases the cost of data comparison
- Therefore, using useCallback and useMemo will not only reduce the workload, but also increase the cost of comparison, so it is not recommended to use it frequently
UseCallback and useMemo do not make sense, of course not. React is not useful at all.
We still need to use it, but we need to make a judgment based on the situation and when to use it.
The following describes some scenarios where useCallback and useMemo apply
Usage scenarios of useCallback
-
Scenario 1: Sub-components need to be optimized for performance
In this example, the App passes a function property onClick to the child component Foo
Code before optimization using useCallback
App.js
import React, { useState } from 'react'; import Foo from './Foo'; function App() { const [count, setCount] = useState(0); const fooClick = () = > { console.log('Clicked the button on the Foo component'); }; return ( <div style={{ padding: 50}} > <Foo onClick={fooClick} /> <p>{count}</p> <button onClick={()= > setCount(count + 1)}>count increment</button> </div> ); } export default App; Copy the code
Foo.js
import React from 'react'; const Foo = ({ onClick }) = > { console.log('Foo component: render'); return <button onClick={onClick}>Button in the Foo component</button>; }; export default Foo; Copy the code
If you click the Count Increment button in the App, you can see that the child component Foo is rerendered every time, but when the count changes, the parent component is rerendered, while the child component does not need to be rerendered.
However, if the Foo component is a very complex and large component, then it is necessary to optimize the Foo component and useCallback can come in handy.
Optimized code using useCallback
App.js wraps function properties passed to child components in useCallback
import React, { useCallback, useState } from 'react'; import Foo from './Foo'; function App() { const [count, setCount] = useState(0); const fooClick = useCallback(() = > { console.log('Clicked the button on the Foo component'); } []);return ( <div style={{ padding: 50}} > <Foo onClick={fooClick} /> <p>{count}</p> <button onClick={()= > setCount(count + 1)}>count increment</button> </div> ); } export default App; Copy the code
In foo.js, use react. memo to wrap components (same effect as inheriting PureComponent from class components).
import React from 'react'; const Foo = ({ onClick }) = > { console.log('Foo component: render'); return <button onClick={onClick}>Button in the Foo component</button>; }; export default React.memo(Foo); Copy the code
Click the Count Increment button again to see that the parent component is updated, but the child component is not rerendered
-
Scenario 2: Needed as a dependency for other hooks, demonstrated here using useEffect only
When the page changes, we want to trigger useEffect to call the network request. In useEffect, getDetail is called. In order to use the latest page, So useEffect relies on the getDetail function to call the latest getDetail
use
useCallback
Code before processingApp.js
import React, { useEffect, useState } from 'react'; const request = (p) = > new Promise(resolve= > setTimeout(() = > resolve({ content: The first `${p}Page data ` }), 300)); function App() { const [page, setPage] = useState(1); const [detail, setDetail] = useState(' '); const getDetail = () = > { request(page).then(res= > setDetail(res)); }; useEffect(() = > { getDetail(); }, [getDetail]); console.log('App component: render'); return ( <div style={{ padding: 50}} > <p>Detail: {detail.content}</p> <p>Current page: {page}</p> <button onClick={()= > setPage(page + 1)}>page increment</button> </div> ); } export default App; Copy the code
However, following the above writing method will cause the App component to render in an infinite loop, in which case, useCallback is needed for processing
use
useCallback
Processed codeApp.js
import React, { useEffect, useState, useCallback } from 'react'; const request = (p) = > new Promise(resolve= > setTimeout(() = > resolve({ content: The first `${p}Page data ` }), 300)); function App() { const [page, setPage] = useState(1); const [detail, setDetail] = useState(' '); const getDetail = useCallback(() = > { request(page).then(res= > setDetail(res)); }, [page]); useEffect(() = > { getDetail(); }, [getDetail]); console.log('App component: render'); return ( <div style={{ padding: 50}} > <p>Detail: {detail.content}</p> <p>Current page: {page}</p> <button onClick={()= > setPage(page + 1)}>page increment</button> </div> ); } export default App; Copy the code
Now you can see that the App component can render normally. This is demonstrated using only useEffect, which needs to be optimized as a dependency for other hooks
-
Summary of useCallback Usage scenarios:
-
When function properties are passed to a child component and the child component needs to be optimized, the function properties need to be wrapped useCallback
-
Functions need to be useCallback wrapped when they are dependent on other hooks
-
Usage scenarios of useMemo
-
This is similar to useCallback scenario 1, where performance tuning of subcomponents is required
-
This is also similar to useCallback Scenario 2: when you need to rely on other hooks
-
When large or complex operations are required, useMemo can be used for data caching to improve performance
Again, useMemo’s data caching capabilities are used, and functions wrapped in useMemo are not re-executed until the dependency changes
Consider the following example of two states in an App component: If you click increment, count will be incremented. If you click fresh, you will retrieve the dataSource again, but you do not need to display the dataSource. Instead, we need to display the sum of all the elements in our dataSource, so we need a new variable, sum, to display on the page.
So let’s look at the code
use
useMemo
Code before optimizationApp.js
import React, { useState } from 'react'; const request = () = > new Promise(resolve= > setTimeout( () = > resolve(Array.from({ length: 100 }, () = > Math.floor(100 * Math.random()))), 300));function App() { const [count, setCount] = useState(1); const [dataSource, setDataSource] = useState([]); const reduceDataSource = () = > { console.log('reduce'); return dataSource.reduce((reducer, item) = > { return reducer + item; }, 0); }; const sum = reduceDataSource(); const refreshClick = () = > { request().then(res= > setDataSource(res)); }; return ( <div style={{ padding: 50}} > <p>DataSource {sum}</p> <button onClick={refreshClick}>Refresh</button> <p>Current count: {count}</p> <button onClick={()= > setCount(count + 1)}>increment</button> </div> ); } export default App; Copy the code
Open the console, and you can see that the reduceDataSource function will be executed once, regardless of whether the INCREMENT or Refresh button is clicked. However, the dataSource has 100 elements. So we definitely want to recalculate the sum when the dataSource changes, which is where useMemo comes in.
use
useMemo
Optimized codeApp.js
import React, { useMemo, useState } from 'react'; const request = () = > new Promise(resolve= > setTimeout( () = > resolve(Array.from({ length: 100 }, () = > Math.floor(100 * Math.random()))), 300));function App() { const [count, setCount] = useState(1); const [dataSource, setDataSource] = useState([]); const sum = useMemo(() = > { console.log('reduce'); return dataSource.reduce((reducer, item) = > { return reducer + item; }, 0); }, [dataSource]); const refreshClick = () = > { request().then(res= > setDataSource(res)); }; return ( <div style={{ padding: 50}} > <p>DataSource {sum}</p> <button onClick={refreshClick}>Refresh</button> <p>Current count: {count}</p> <button onClick={()= > setCount(count + 1)}>increment</button> </div> ); } export default App; Copy the code
You can see that the functions in the useMemo will only be re-executed when the Refresh button is clicked. When the increment button is clicked, the sum is the same as the cached sum and will not be recalculated.
-
Summary of useMemo Usage scenarios:
-
When a reference type property is passed to a child component and the child component needs to be optimized, useMemo wraps the property
-
Reference type values, which need to be wrapped in useMemo when using as dependencies for other hooks that return property values
-
To improve performance, you can use the useMemo to cache data and save computing costs
-
Therefore, in the use of useCallback and useMemo, it is not necessary to use them if it is not necessary. Frequent use may increase the cost of dependency comparison and reduce performance.
How do I call a child’s state or method from a parent component
In a function component, there is no component instance, so you cannot invoke a state or method in a child component by binding its instance, as in a class component.
So in a function component, how does a parent component call a child component’s state or method? The answer is to use the useImperativeHandle
grammar
useImperativeHandle(ref, createHandle, [deps])
-
The first parameter is the ref value, which can be passed in as an attribute or used with the forwardRef
-
The second argument is a function that returns an object whose properties are mounted to the current property of the first argument, ref
-
The third parameter is the set of dependent elements, like useEffect, useCallback, and useMemo. When the dependency changes, the second parameter is reexecuted and remounted to the current property of the first parameter
usage
Note:
- The third parameter, dependency, must be filled in as required, too little will result in object property exceptions returned, too much will result in
createHandle
repeat - A component or
hook
For the sameref
, can only be used onceuseImperativeHandle
, several times, the following implementationuseImperativeHandle
的createHandle
The return value replaces the previous oneuseImperativeHandle
的createHandle
The return value
Foo.js
import React, { useState, useImperativeHandle, useCallback } from 'react';
const Foo = ({ actionRef }) = > {
const [value, setValue] = useState(' ');
/** * randomly changes the value of the function */
const randomValue = useCallback(() = > {
setValue(Math.round(Math.random() * 100) + ' '); } []);/** * submit function */
const submit = useCallback(() = > {
if (value) {
alert('Submitted successfully, user name:${value}`);
} else {
alert('Please enter a user name! ');
}
}, [value]);
useImperativeHandle(
actionRef,
() = > {
return {
randomValue,
submit,
};
},
[randomValue, submit]
);
/ *!!!!! To return multiple properties like this, UseImperativeHandle useImperativeHandle(actionRef, () => {return {submit,}} [submit]) useImperativeHandle(actionRef, () => { return { randomValue } }, [randomValue]) */
return (
<div className="box">
<h2>Function component</h2>
<section>
<label>User name:</label>
<input
value={value}
placeholder="Please enter user name"
onChange={e= > setValue(e.target.value)}
/>
</section>
<br />
</div>
);
};
export default Foo;
Copy the code
App.js
import React, { useRef } from 'react';
import Foo from './Foo'
const App = () = > {
const childRef = useRef();
return (
<div>
<Foo actionRef={childRef} />
<button onClick={()= >Childref.current.submit ()}> calls the submission function of the child component</button>
<br />
<br />
<button onClick={()= >ChildRef. Current. RandomValue ()} > random modify child components of the input value</button>
</div>
);
};
Copy the code
X. Reference documents
- UseEffect complete guide
- React Hooks first issue: useCallback
Write in the back
If there is something wrong or not precise, you are welcome to put forward valuable suggestions, thank you very much.
Welcome Star if you like it or if you are helpful, it is also a kind of encouragement and support for the author.