React Hook
preface
Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.
In the early days of React, components created using classes were found to be bulky and poorly reusable. It becomes more and more difficult to change later as side effects and states increase. Therefore, learning hooks allows us to build leaner and more reusable code.
A, useState
1. Basic method of use
- Using useState returns two things, the state you created and the setstate function that updates the state.
- [count, setCount] below is array deconstruction, but you can use other names
setState
There are two methods of update in. One is direct update, independent of the previous state; The other is a functional update that depends on the previous state.
import { useState } from "react";
const Test = () = > {
const [count, setCount] = useState(0);
return (
<>
<div>{count}</div>
<! -- Mode 1, direct update -->
<button
onClick={()= >{ setCount(count + 1); > +}}</button>
<! -- Method 2, functional update -->
<button
onClick={()= >{ setCount((prevCount) => prevCount - 1); > -}}</button>
</>
);
};
export default Test;
Copy the code
2, there are multiple states use multiple useState
- Multiple states do not share state
import { useState } from "react";
const Test = () = > {
const [count, setCount] = useState(0);
const [counter, setCounter] = useState(0);
return (
<>
<div>{count}</div>
<button onClick={()= >{setCount(count + 1); > +}}</button>
<div>{counter}</div>
<button onClick={()= >{setCounter(counter + 10); > +}}</button>
</>
);
};
export default Test;
Copy the code
3. When the state is a reference type
Note that setState in useState does not perform a shallow merge, but a direct replacement
import { useState } from "react";
const Test = () = > {
const [customer, setCustomer] = useState({ id: 0.name: "hello".age: 18 });
return (
<>
<div>
{customer.id} - {customer.name} - {customer.age}
</div>
<! -- error in age -->
<button
onClick={()= > {
setCustomer({ age: customer.age + 1 });
}}
>
error-change-age
</button>
<! The correct modification method is to expand the previous state and overwrite the attributes that need to be modified directly.
<button
onClick={()= >{ setCustomer({ ... customer, age: customer.age + 1 }); }} > true-change-age</button>
</>
);
};
export default Test;
Copy the code
Lazy initialization of state
The initialState parameter is only used in the initial rendering of the component and will be ignored in subsequent renderings. If the initial state needs to be computed through complex calculations, we can pass in a function that computes and returns the initial state, which is only called during the initial render:
const [state, setState] = useState(() = > {
const initialState = someExpensiveComputation(props);
return initialState;
});
Copy the code
Second, the useEffect
UseEffect works like componentDidMount, componentDidUpdate, and componentWillUnmount. Its main function is to process the side effect function, clear the side effect function and some state to listen.
1. Basic method of use
The basic form is as follows
- Effect is the side effect function. The action is performed after the mount is successful and the dependent parameter input changes
- Cleanup is a return function that typically fills in the action performed before the component is unloaded and the accompanying effect cleanup function
- Input is a dependent argument and can be passed an empty array
useEffect(() = > {
effect
return () = > {
cleanup
}
}, [input])
Copy the code
Order of execution
- The mount phase reads the various Useeffects from top to bottom and pushes the effects into a queue in order of precedence (first-in, first-out). After the component is mounted, effect in the queue is executed, the return function is taken, and pushed to another queue.
- The update phase reads the various USEeffects from top to bottom and pushes the effects into a queue in sequence (first-in, first-out). After the component finishes updating, the return function cleanup in the queue is executed, followed by effect in the queue. Gets a new return function queue at execution time.
- In the unload phase, only the return function queue is executed
Now let’s verify that
import { useState, useEffect } from "react";
const App = () = > {
const [show, setShow] = useState(true);
return (
<>
<! Control Counter unmount and mount -->
<button
onClick={()= >{ setShow(! show); }} > toggle</button>
{show && <Counter />}
</>
);
};
const Counter = () = > {
const [count, setCount] = useState(0);
// Execute after mount and update is complete
useEffect(() = > {
console.log("useEffect - 1 ");
return () = > {
console.log("UseEffect - 1 - return");
};
});
useEffect(() = > {
console.log("useEffect - 2 ");
return () = > {
console.log("UseEffect - 2 - return");
};
});
useEffect(() = > {
console.log("useEffect - 3 ");
return () = > {
console.log("UseEffect - 3 - return");
};
});
// This sentence is executed before mounting
console.log("Mount");
return (
<>
<div>{count}</div>
<button
onClick={()= >{ setCount(count + 1); > +}}</button>
</>
);
};
export default App;
Copy the code
About dependency parameters
- When a component is updated, the side effect function and cleanup function will check whether the corresponding useEffect dependency parameter has changed before execution. If no, they will not be executed. Note, however, that effect is executed by default after the component has been mounted, with or without dependent parameters.
- If there are no dependent parameters, effect is executed after the mount is complete and after any updates
- If the dependency argument is an empty array [], the effect is executed only once
Ok, now that we know these points, we can emulate the lifecycle functions in the class component.
The mock component declares periodic functions and listeners
1), componentDidMount
First, to be clear, componentDidMount executes after the component has been mounted, and only once
useEffect(() = > {
console.log("Imitate componentDidMount"); } []);Copy the code
2), componentWillUnmount
Execute when the component is about to uninstall
useEffect(() = > {
return () = > {
console.log("Imitate componentWillUnmount"); }; } []);Copy the code
3), componentDidUpdate
Here I will use a hooks called useRef, which we will use to record whether it is mount phase, to make useEffect not run after mount, only run after any update.
const Counter = () = > {
const [count, setCount] = useState(0);
const [numb, setNumb] = useState(10); // Add a new state
const fisrtRend = useRef(true); // Use it to record whether it is a mount phase
useEffect(() = > {
if (fisrtRend.current) {
fisrtRend.current = false;
return;
}
console.log("Analog componentDidUpdate");
});
return (
<>
<div>count - {count}</div>
<button
onClick={()= >{ setCount(count + 1); > +}}</button>
<div>numb - {numb}</div>
<button
onClick={()= >{ setNumb(numb + 10); > +}}</button>
</>
);
};
Copy the code
4) Listener
WatchEffect, similar to Vue3, only listens for specific data and does something when the data changes
const Counter = () = > {
const [count, setCount] = useState(0);
const fisrtRend = useRef(true);
useEffect(() = > {
if (fisrtRend.current) {
fisrtRend.current = false;
return;
}
console.log("Now the count is" + count);
}, [count]);
return (
<>
<div>count - {count}</div>
<button
onClick={()= >{ setCount(count + 1); > +}}</button>
</>
);
};
Copy the code
3. Summary
- Can put the
useEffect Hook
As acomponentDidMount
.componentDidUpdate
andcomponentWillUnmount
The combination of these three functions - Unlike componentDidMount or componentDidUpdate, use
useEffect
Scheduling the effect ofDoesn’t block the browser update screen, making your app seem more responsive. In most cases, effects do not need to be executed synchronously.
Third, useRef
- In class components, we use ref all the time
React.createRef
To create. - However, in hooks, we pass
useRef
To create it, to use itAssociated instanceorRecord the state of the component before it is updated. - UseRef returns a mutable ref object whose
.current
Property is initialized as the passed parameter (initialValue
). The returnedThe REF object remains constant throughout the life of the component. - UseRef does not notify you when the ref object’s contents change. change
.current
attributeComponent rerendering is not caused.
1. Obtain the DOM node
You should be familiar with ref, the main way to access the DOM. If you pass the ref object to the component as
, React sets the.current property of the ref object to the corresponding DOM node, regardless of how the node changes.
import { useState, useRef } from "react";
const Test = () = > {
const [count, setcount] = useState(0);
const refEl = useRef();
return (
<>
<p ref={refEl}>{count}</p>
<button
onClick={()= >{ console.log(refEl.current); }} > Get the p tag</button>
<br />
<button
onClick={()= >{ setcount(count + 1); > +}}</button>
</>
);
};
export default Test;
Copy the code
2. Record some status before update
Why can it be done? Because it creates a normal Javascript object, useRef() and creates a {current:… The only difference with the} object is that useRef returns the same ref object every time it renders.
verify
import { useState, useEffect, useRef } from "react";
const Test = () = > {
const [count, setcount] = useState(0);
const prevCount = useRef(count);
Prevcount.current does not change, even if count changes
useEffect(() = > {
console.log(`count: ${count}, prevCount: ${prevCount.current}`);
}, [count]);
return (
<>
<p>{count}</p>
<br />
<button
onClick={()= >{ setcount(count + 1); > +}}</button>
</>
);
};
export default Test;
Copy the code
The only way to change this is to manually change the prevCount.current value
useEffect(() = > {
console.log(`count: ${count}, prevCount: ${prevCount.current}`);
// Manually change the prevCount.current value to the current value of count after each change
prevCount.current = count;
}, [count]);
Copy the code
Four, useMemo
- UseMemo is somewhat similar to computed properties in VUE, in that it returns certain properties based on a dependency parameter and recalculates memoized values only when the dependency parameter changes
- If the dependency array is not provided, useMemo evaluates the new value each time it renders
- Functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function, such as side effects, which are used by useEffect
1. Why use it
We know that an update of a parent component causes an update of a child component. In class components, we have PureComponent and shouldComponentUpdate to optimize, but in function components, we can’t use these apis. In its place, useMemo.
No useMemo is used
import { useState } from "react";
const Test = () = > {
const [count, setcount] = useState([0]);
return (
<>
<button
onClick={()= > {
setcount([...count, count.length]);
}}
>
add
</button>
{count.map((item) => (
<Child data={item} key={item} />
))}
</>
);
};
const Child = ({ data }) = > {
function updater() {
console.log(`${data}- rendering `);
return "Child" + data;
}
return (
<>
<div>{updater()}</div>
</>
);
};
export default Test;
Copy the code
2. How to use it
The API is identical to useEffect, but be careful not to confuse it with useEffect
import { useState, useMemo } from "react";
const Test = () = > {
const [count, setcount] = useState([0]);
return (
<>
<button
onClick={()= > {
setcount([...count, count.length]);
}}
>
add
</button>
{count.map((item) => (
<Child data={item} key={item} />
))}
</>
);
};
const Child = ({ data }) = > {
const updater = useMemo(() = > {
console.log(`${data}- rendering `);
return "Child" + data;
}, [data]);
return (
<>
<! -- Notice that this is not updater() because it will execute automatically -->
<div>{updater}</div>
</>
);
};
export default Test;
Copy the code
3. Is the PureComponent effect finally implemented?
Let’s print at mount time to see if the child components have been rerendered
import { useState, useEffect, useMemo } from "react";
const Test = () = > {
const [count, setcount] = useState([0]);
return (
<>
<button
onClick={()= > {
setcount([...count, count.length]);
}}
>
add
</button>
{count.map((item) => (
<Child data={item} key={item} />
))}
</>
);
};
const Child = ({ data }) = > {
const updater = useMemo(() = > {
console.log(`${data} ------ useMemo `);
return "Child" + data;
}, [data]);
console.log(` -${data}- mount `);
return (
<>
<div>{updater}</div>
</>
);
};
export default Test;
Copy the code
4, the conclusion
As you can see, the child component is still updated. So what exactly does useMemo do here? The answer is that while the child component is still reloaded, a new virtual DOM is created, and the old and new virtual DOM are compared and found to be the same, so the DOM is not updated.
Five, the Memo
- React.memo is a higher-order component
- If your component is rendering the same props, you can improve the performance of the component by wrapping it in a react.Memo call to remember the component’s rendering results. This means that in this case React will skip the render component and simply reuse the results of the last render
- By default, only shallow comparisons are performed on complex objects. If you want to control the comparison process, pass in your custom comparison function as a second argument
- The argument to the comparison function is (prevProps, nextProps)
1. Optimize the above example using Memo
import { useState, useMemo, memo } from "react";
const Test = () = > {
const [count, setcount] = useState([0]);
return (
<>
<button
onClick={()= > {
setcount([...count, count.length]);
}}
>
add
</button>
{count.map((item) => (
<MemoChild data={item} key={item} />
))}
</>
);
};
const Child = ({ data }) = > {
const updater = useMemo(() = > {
console.log(`${data} ------ useMemo `);
return "Child" + data;
}, [data]);
console.log(` -${data}- mount `);
return (
<>
<div>{updater}</div>
</>
);
};
// Note that higher-order components are named after PASCAL
const MemoChild = memo(Child);
Copy the code
Now you can see that the memo optimization meets our expectations.
2. On the second parameter
This is a bit different from shouldComponentUpdate, which does not perform the update if it returns true and does it if it returns false
import { useState, memo } from "react";
const Test = () = > {
const [count, setcount] = useState(0);
return (
<>
<button
onClick={()= > {
setcount(count + 1);
}}
>
add
</button>
<MemoChild1 data={count} />
<MemoChild2 data={count} />
</>
);
};
const Child = ({ data }) = > {
return (
<>
<div>{data}</div>
</>
);
};
const MemoChild1 = memo(Child, (prevProps, props) = > {
// Do not perform the update
console.log("memo1", prevProps, props);
return true;
});
const MemoChild2 = memo(Child, (prevProps, props) = > {
// Perform the update
console.log("memo2", prevProps, props);
return false;
});
export default Test;
Copy the code
Six, useContext
- To receive a
The context object
(React.createContext return value) andReturns the current value of the context. The current context value is contained in the upper-layer componentThe value of < myContext.provider > that is closest to the current componentdecision - When the most recent < myContext. Provider> update is made to the upper layer of the component,This Hook triggers rerenderingAnd use the latest context value passed to MyContext Provider. Even though the ancestors used
React.memo
orshouldComponentUpdate
Is also rerendered when the component itself uses useContext
Use useContext to implement counter
import { useState, createContext, useContext } from "react";
const MyCustomContext = createContext();
const Test = () = > {
const [count, setCount] = useState(0);
return (
<>
<MyCustomContext.Provider value={{ count}} >
<Child />
</MyCustomContext.Provider>
<button
onClick={()= >{ setCount(count + 1); > +}}</button>
</>
);
};
const Child = () = > {
return (
<>
<GrandChild />
</>
);
};
const GrandChild = () = > {
{count: 0}}
const { count } = useContext(MyCustomContext);
return (
<>
<p>GrandChild - {count}</p>
</>
);
};
export default Test;
Copy the code
Seven, useReducer
- An alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method
- UseReducer can be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state.
- Also, using useReducer can optimize performance for components that trigger deep updates because you can pass dispatches to child components instead of callbacks
1. Basic use
import { useReducer} from "react";
function reducer(state, action) {
switch(action.type) {
case "INCREMENT":
return {count: state.count + 1}
case "DECREMENT": {
return {count: state.count - 1}}default: {return state
}
}
}
const initialState = {count: 0};
const Test = () = > {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
<div>Count: {state.count}</div>
<button onClick={()= >{dispatch({type: "INCREMENT"})}}>+</button>
<button onClick={()= >{dispatch({type: "DECREMENT"})}}>-</button>
</>
);
};
export default Test;
Copy the code
Lazy initialization
- You can choose to lazily create the initial state. To do this, you need to pass in the init function as the third argument to the useReducer, so that the initial state will be set to init(initialArg)
- Doing so extracts the logic used to calculate the state outside the Reducer, which also facilitates future actions to reset the state
import { useReducer } from "react";
// initialState
const count = 0;
// the third argument init
function init(initialState) {
return { count: initialState };
}
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT": {
return { count: state.count - 1 };
}
case "RESET": {
return { count };
}
default: {
returnstate; }}}const Test = () = > {
const [state, dispatch] = useReducer(reducer, count, init);
return (
<>
<div>Count: {state.count}</div>
<button
onClick={()= >{ dispatch({ type: "INCREMENT" }); > +}}</button>
<button
onClick={()= >{ dispatch({ type: "DECREMENT" }); > -}}</button>
<button
onClick={()= > {
dispatch({ type: "RESET" });
}}
>
RESET
</button>
</>
);
};
export default Test;
Copy the code
8. Customize hooks
- A custom Hook is a function whose name starts with “use” that can call other hooks from within
- Unlike the React component, custom hooks do not need to have a special identity. We are free to decide what its arguments are and what it should return (if necessary)
- Will using the same Hook in two components share state? Don’t.
- How do custom hooks get independent state? Each time a Hook is called, it gets a separate state.
Let’s implement a simple hook of our own, in the form of useState, that sets a div tag to display the scrollY of the current screen and to jump to the top when clicked.
import { useEffect, useState } from "react";
const useCustom = () = > {
const [y, setY] = useState(window.scrollY);
useEffect(() = > {
window.onscroll = () = > {
setY(window.scrollY);
};
return () = > {
window.onscroll = null; }; } []);return [
y.toFixed(3),
(newY) = > {
window.scrollTo(0, newY); setY(newY); },]; };const Test = () = > {
const arr = new Array(20).fill(0);
const style = {
width: "100px".height: "100px".background: "red".margin: "5px"};const style2 = {
position: "fixed".left: 0.top: "500px".width: "100px".background: "black".color: "white"};const [y, setY] = useCustom();
return (
<>
<div
style={style2}
onClick={()= > {
setY();
}}
>
{y}
</div>
{arr.map((item, index) => (
<div key={index} style={style}></div>
))}
</>
);
};
export default Test;
Copy the code