After Hook comes out, I believe many friends are eager to try their own. For those who like to use React and Hook, this article will play Hook with you.
This article is a bit long, but please bear with it
Hook function: a callback function that is triggered at a certain stage. Example: Vue lifecycle functions are hook functions
To do a good job, he must sharpen his tools
Let’s take a closer look at the hooks built into React
Here we simply label a few hooks
- UseState [Maintenance status]
- useEffect
- UseContext [using shared state]
- UseReducer 【 akin to redux】
- UseCallback [cache function]
- UseMemo cache value
- UseRef [DOM]
- UseImperativeHandle using values/methods exposed by child components
- UseLayoutEffect blocks browser drawing after completing side effects
Let’s take a closer look at each of these hooks
useState
Normal update/functional update state
const Index = () => { const [count, setCount] = useState(0); const [obj, setObj] = useState({ id: 1 }); Return (<> {/* normal update */} <div>count: {count}< div> <button onClick={() => setCount(count + 1)}>add</button> {/* function update */} <div>obj: {JSON.stringify(obj)}</div> <button onClick={() => setObj((prevObj) => ({ ... prevObj, ... {id: 2, name: "* *"}}})) > merge < / button > < / a >). };Copy the code
useEffect
UseEffet replaces componentDidMount, componentDidUpdate, componentWillUnmount. UseEffet replaces componentDidMount, componentDidUpdate, componentWillUnmount. UseEffet replaces componentDidMount, componentDidUpdate, componentWillUnmount.
This hook is important, so let’s spend a little time mastering it.
- A code structure with three life cycles
UseEffect (() => {// here code block equivalent to componentDidMount // do something... // componentWillUnmount return () => {// do something... }; }, // componentDidUpdate [XXX, obj.xxx]); // componentDidUpdate [XXX, obj.xxx]);Copy the code
Note: Dependency lists are flexible and can be written in three ways
- When the array is empty
[]
, indicating that the callback method will not be executed for the state change of the page.componentDidMount
】, - When this parameter is not passed, it means that the callback method will be executed if any state of the page changes
- When the array is not empty, the callback method is executed whenever the value in the array changes
- We will also encounter scenarios such as:
- Scenario 1: I rely on some value, but I don’t want to execute the callback method on initialization, I want the dependency to change and then execute the callback method
Here we have the useRef hook:
const firstLoad = useRef(true);
useEffect(() => {
if (firstLoad.current) {
firstLoad.current = false;
return;
}
// do something...
}, [ xxx ]);
Copy the code
- Scenario 2: I have a getData asynchronous request method that I want to call at initialization and also call by clicking a button
So let’s write it this way
/ /... const getData = async () => { const data = await xxx({ id: 1 }); setDetail(data); }; useEffect(() => { getData(); } []); const handleClick = () => { getData(); }; / /...Copy the code
But a warning was reported:
Line 77:6: React Hook useEffect has a missing dependency: 'getData'.
Either include it or remove the dependency array
react-hooks/exhaustive-deps
Copy the code
I need to useEffect and add the getData dependency
This is the rule of Hook, so we change it like this:
// ...
const getData = async () => {
const data = await xxx({ id: 1 });
setDetail(data);
};
useEffect(() => {
getData();
}, [getData]);
const handleClick = () => {
getData();
};
// ...
Copy the code
But a warning was reported:
Line 39:9: The 'getData' function makes the dependencies of useEffect Hook (at line 76) change on every render.
Move it inside the useEffect callback.
Alternatively, wrap the 'getData' definition into its own useCallback() Hook react-hooks/exhaustive-deps
Copy the code
GetData will be redefined as soon as render is triggered by an update to the component, which will cause useEffect to run.
This is the behavior that affects performance, and we use the useCallback hook to cache it to improve performance:
/ /... const getData = useCallback(async () => { const data = await xxx({ id: 1 }); setDetail(data); } []); useEffect(() => { getData(); }, [getData]); const handleClick = () => { getData(); }; / /...Copy the code
This is just one example, mainly to illustrate the thinking that arises from the error prompts. You can also turn off ESLint with a comment, or turn off esLint rules directly, depending on how you choose.
Use // eslint-disable-next-line react-hooks/ Exhaustive deps, for example:
// ...
const [count, setCount] = useState(1);
const xxx = () => {};
useEffect(() => {
// use count do something...
console.log(count);
xxx();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ...
Copy the code
useContext
Context provides a way to share such values between components without explicitly passing props layer by layer through the component tree
Here’s an example
const obj = { value: 1 }; const obj2 = { value: 2 }; const ObjContext = React.createContext(obj); const Obj2Context = React.createContext(obj2); const App = () => { return ( <ObjContext.Provider value={obj}> <Obj2Context.Provider value={obj2}> <ChildComp /> </Obj2Context.Provider> </ObjContext.Provider> ); }; // Const ChildComp = () => {return < ChildComp />; }; // Const ChildChildComp = () => {const obj = useContext(ObjContext); const obj2 = useContext(Obj2Context); return ( <> <div>{obj.value}</div> <div>{obj2.value}</div> </> ); };Copy the code
useReducer
In some scenarios, useReducer is more useful than useState when the state logic is more complex. We can replace useState with this hook, which works like Redux. Here’s an example:
Const initialState = [{id: 1, name: "zhang"}, {id: 2, name: "Li"}]; const reducer = (state: any, { type, payload }: any) => { switch (type) { case "add": return [...state, payload]; case "remove": return state.filter((item: any) => item.id ! == payload.id); case "update": return state.map((item: any) => item.id === payload.id ? {... item, ... payload } : item ); case "clear": return []; default: throw new Error(); }}; const List = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <> List: {JSON.stringify(state)} <button onClick={() => dispatch({ type: "add", payload: { id: 3, name: } > add </button> <button onClick={() => dispatch({type: "remove", payload: {id: 1 } })}> remove </button> <button onClick={() => dispatch({ type: "update", payload: { id: 2, name: "Li si - update"}})} > update < / button > < button onClick = {() = > dispatch ({type: "clear"})} > clear < / button > < / a >). };Copy the code
The exposed type can make us better understand what we are doing at the moment.
useCallback
// Unless 'a' or 'b' changes, there is no change to const memoizedCallback = useCallback(() => {doSomething(a, b); }, [a, b], );Copy the code
Let’s go ahead and slide it up. There’s an example already mentioned, useCallback, which is kind of a scenario that we all know can be used to cache a function.
Now let’s talk about another scenario.
React provides several methods to avoid the performance overhead of rerendering, which by default triggers the child group’s render whenever the parent component render: React.PureComponent, react. memo, shouldComponentUpdate()
Let’s take a patient look at an example where our child component accepts a property as a method, as in:
const Index = () => { const [count, setCount] = useState(0); Const getList = (n) => {return array.apply (Array, Array(n)).map((item, I) => ({id: I, name: "zhang3" + I})); }; return ( <> <Child getList={getList} /> <button onClick={() => setCount(count + 1)}>count+1</button> </> ); }; const Child = ({ getList }) => { console.log("child-render"); Return (< > {getList (10). The map ((item) = > (< div key = {item. Id} > id:} {item. The id, name: {item. The name} < / div >))} < / a >). };Copy the code
Let’s try to decipher what happens when the “count+1” button is clicked:
Parent render > child render > child render" child-render"Copy the code
We use react. memo to avoid unnecessary rendering of subcomponents, as in:
/ /... const Child = React.memo(({ getList }) => { console.log("child-render"); Return (< > {getList (10). The map ((item) = > (< div key = {item. Id} > id:} {item. The id, name: {item. The name} < / div >))} < / a >). }); / /...Copy the code
We assume that when we click “count+1”, the child component will no longer be re-rendered. But the reality is that it still renders. Why is that? A: The memo will only make a shallow comparison to the props method, so the parent component will pass in the method getList with different references after the parent component rerenders, so the child component will still render.
This is where useCallback comes in. It can cache a function and keep returning the same reference until the dependency changes. Such as:
/ /... Const getList = useCallback((n) => {return array.apply (Array, Array(n)).map((item, I) => ({id: I, name: "三 三" + I})); } []); / /...Copy the code
Summary: If a child component accepts a method as a property, we need to use useCallback when using react. memo to avoid unnecessary rendering of the child component, otherwise the react. memo will be meaningless.
useMemo
Computed, similar to vue, is used to avoid doing expensive computations every time you render, for a simple example.
No matter how many times the page is rendered, the timestamp will not change because it is already cached, unless the dependency changes.
/ /... const getNumUseMemo = useMemo(() => { return `${+new Date()}`; } []); / /...Copy the code
useRef
We use it to access the DOM to manipulate it, such as clicking a button to focus a text box:
const Index = () => {
const inputEl = useRef(null);
const handleFocus = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={handleFocus}>Focus</button>
</>
);
};
Copy the code
Note: The ref object returned remains constant throughout the life of the component. It is similar to an instance property of a class, and we take advantage of it. Go up there and look at the example with useRef.
What if we want to access a component and manipulate the specific DOM in that component? ForwardRef (forwardRef); react. forwardRef (forwardRef);
const Index = () => {
const inputEl = useRef(null);
const handleFocus = () => {
inputEl.current.focus();
};
return (
<>
<Child ref={inputEl} />
<button onClick={handleFocus}>Focus</button>
</>
);
};
const Child = forwardRef((props, ref) => {
return <input ref={ref} />;
});
Copy the code
useImperativeHandle
UseImperativeHandle allows us to invoke properties/methods exposed by child components from the parent component. Such as:
const Index = () => { const inputEl = useRef(); useEffect(() => { console.log(inputEl.current.someValue); // test }, []); Return (< > Child ref = {inputEl} < / a > < button onClick = {() = > inputEl. Current. The setValues ((val) = > val + 1)} > accumulative value of subcomponents </button> </> ); }; const Child = forwardRef((props, ref) => { const inputRef = useRef(); const [value, setValue] = useState(0); useImperativeHandle(ref, () => ({ setValue, someValue: "test" })); return ( <> <div>child-value:{value}</div> <input ref={inputRef} /> </> ); });Copy the code
In a similar way that Vue uses the ref flag on the component and this.$refs. XXX to manipulate the DOM or call subcomponent values/methods, React calls it “with two hooks”.
useLayoutEffect
Effect is called synchronously after all DOM changes. You can use it to read DOM layouts and trigger rerenders synchronously. The update schedule inside useLayoutEffect is flushed in the same step before the browser executes the drawing, which means it blocks the browser drawing. UseEffect whenever possible to avoid blocking visual updates.
Click on me, see an example, and see the light bulb
Summary: The next front-end era will be the era of Hook. For those of you who like React, let’s first use these hooks to win every fight.