Hook profile
Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class. Take a look at two examples of Hook usage provided in the official documentation.
-
State Hook
import React, { useState } from 'react'; function Example() { // useState is a hook that declares a state variable called "count" with an initial value of 0. A function that returns the current value and changed value. 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
-
Effect Hook
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: Document. title = 'You clicked ${count} times'; useEffect() => {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
Hook to solve the problem
- It is difficult to reuse state logic between components.
- The state and side effect code dispersion of logic within complex components is difficult to understand.
- Class components are difficult to understand and not conducive to tool optimization.
Introduce Hook influence
- Optional: Use incrementally without rewriting existing code, breaking existing structures, or replacing existing React concepts.
- Compatibility: 100% backward compatibility, no destructive changes.
- Available since V16.8.0.
Use Hook limitation and detection tools
Limitations:
- Use hooks only at the top level; do not call hooks in loops, conditions, or nested functions
- Only call the React function component and custom hooks, not normal JavaScript functions
React provides an elint plugin, eslint-plugin-react-hooks. Use this plugin to check that hooks are used correctly.
react-hooks/rules-of-hooks
: Check the Hook rulesreact-hooks/exhaustive-deps
: Check for effect dependencies
Use of built-in hooks
Built-in 10 hooks are as follows:
useState
: used to declare and update component state.userEffect
: used to pass in a function to perform side effects such as Dom changes, which is executed after each rendering of the component.userContext
: Used to receive the upper layerContext
Object and return the current value.useReducer
: a simple redux for handling complex state of components.useCallback
Memoized: Used to return a memoized callback function, with the function reference returned unchanged if the dependency is not changed. Often used for event binding or function props passing.userMemo
Returns an memoized value that is directly used by the last calculated value without re-executing the function if the dependency is not changed.userRef
: Returns a ref object, which can be thought of as an instance variable of the component. Rerendering is not triggered when the value of ref.current changes.userImperativeHandle
: in conjunction with ref, exposes instance methods and values to the parent component.userLayoutEffect
Basically equivalent touserEffect
, but it is executed before drawing.userDebugValue
For custom Hook debugging, the React developer tool displays the tag for custom hooks.
The detailed usage of each Hook is described below.
0. Related types
The following are the type definitions for the built-in Hook function definition dependencies.
type Dispatch<A> = A => void;
type BasicStateAction<S> = (S => S) | S;
Copy the code
1. useState
define
useState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>]
Copy the code
describe
Declare a state and set the initial value, return a state value and a function to update the state.
The sample
import React, { useState } from "react";
export default function App({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(count + 1)}>+</button>
</>
);
}
Copy the code
points
- UseState returns an array of current state and update state functions, named by destruct.
- UseState takes a function that lazily evaluates the initial value and skips it when not rendered for the first time. Eg:
useState(computedFunction)}
. - SetCount updates behave by overwriting the current value rather than merging to the current value.
- SetCount can take a function that takes the previous state and returns the updated value. Eg:
setCount(prevCount => prevCount + 1)}
.
2. userEffect
define
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void
Copy the code
describe
The side effects are done using the userEffect pass-in function, which is executed after each rendering of the component.
The sample
import React, { useState, useEffect } from 'react';
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Copy the code
note
- Support for returning a function to remove side effects, side effects and side effect removal functions are executed after each render. To do this before rendering, see useLayoutEffect.
- Support for passing in an array of side effect function dependencies with the second argument, bypassside processing if the dependencies remain unchanged.
- If you want to dynamically decide whether to execute based on a condition, you need to put the condition in the side effect function.
3. userContext
define
useContext<T>(context: ReactContext<T>): T
Copy the code
use
Used to accept the upper Context object and return the current value.
The sample
import React, { useContext } from "react";
const Context = React.createContext();
function Counter() {
const count = useContext(Context);
return <div>Current: {count}</div>;
}
export default function App() {
return (
<Context.Provider value={1}>
<Counter />
</Context.Provider>
);
};
Copy the code
note
- The Context must be used together with context. Provider in upper-layer components.
4. useReducer
define
useReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init? : I => S, ): [S, Dispatch<A>]Copy the code
use
A simple redux for handling complex state of components.
The sample
import React, { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}
Copy the code
note
- Accept the second parameter
initialArg
Is the initial value. If the initial value requires a lot of calculation, you can pass in the third parameterinit
Function to initialize.
5. useCallback
define
useCallback<T>(callback: T, deps: Array<any> | void | null): T
Copy the code
use
Used to return a memoized callback function, with the function reference returned unchanged if the dependency is not changed. Often used for event binding or function props passing.
The sample
import React, { useState, useCallback } from "react"; class Button extends React.PureComponent { render() { console.log("rendered ", this.props.text); return <button onClick={this.props.onClick}>{this.props.text}</button>; } } export default function App() { const [count1, setCount1] = useState(0); const memoizedOnClick = useCallback(function onClick() { setCount1((count1) => count1 + 1); } []); const [count2, setCount2] = useState(0); const onClick = function onClick() { setCount2((count2) => count2 + 1); }; Return (<> {count1} <Button text="button1" onClick={memoizedOnClick} /> {count2}) <Button text="button2" onClick={onClick} /> </> ); }Copy the code
note
useCallback(fn, deps)
The equivalent ofuseMemo(() => fn, deps)
.
6. userMemo
define
useMemo<T>(create: () => T, deps: Array<any> | void | null): T
Copy the code
use
Returns a memoized value that does not re-execute the function directly using the last calculated value if the dependency is not changed
The sample
import React, { useState, useMemo } from "react"; export default function App() { const [count1, setCount1] = useState(2); Const result = useMemo(function () {console.log("computed pow"); return Math.pow(count1, 2); }, [count1] ); const [count2, setCount2] = useState(2); return ( <> pow: {result} <br /> count2: {count2} <br /> <button onClick={() => setCount1(count1 + 1)}>button1</button> <button onClick={() => setCount2(count2 + 1)}>button2</button> </> ); }Copy the code
7. userRef
define
useRef<T>(initialValue: T): {current: T}
Copy the code
use
Returns a ref object, which can be thought of as an instance variable of the component, and does not trigger rerendering when the value of ref.current changes
The sample
Import React, {useRef} from "React "; export default function TextInputWithFocusButton() { const inputEl = useRef(null); Const onButtonClick = () => {// Get the DOM node inputel.current.focus (); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }Copy the code
Import React, {useEffect, useRef, useState} from "React "; function Counter() { const [count, setCount] = useState(0); // intervalId for component instance variables let intervalId = useRef(null); useEffect(() => { intervalId.current = setInterval(() => { setCount((c) => c + 1); }, 1000); console.log("create ", intervalId.current); return () => { console.log("clear ", intervalId.current); clearInterval(intervalId.current); }; } []); Return (<> count: {count}; Intervali.current: {intervali.current} <button onClick={() => {// example 3: ClearInterval (Intervali.current) will not be re-rendered if you change the value of intervalId after clicking Stop. intervalId.current = Math.random(); }} > stop </button> </> ); } export default function App() { const [visible, setVisible] = useState(true); return ( <> {visible && <Counter />} <button onClick={() => setVisible((v) => ! v)}>Toggle</button> </> ); }Copy the code
note
- Can be used to get dom nodes
- Can be used with component instance variables
- The ref.current update does not cause component re-rendering
8. userImperativeHandle
define
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<any> | void | null,
): void
Copy the code
use
Expose instance methods and values to the parent component in conjunction with ref
The sample
import React, { forwardRef, useRef, useState, useImperativeHandle } from "react"; const Counter = forwardRef(function Counter(props, ref) { const [count, setCount] = useState(0); UseImperativeHandle (ref, () => (increment: () => {increment: ()) setCount((c) => c + 1); }})); return <>count: {count}</>; }); export default function App() { const counterRef = useRef(); return ( <> <Counter ref={counterRef} /> <button onClick={() => counterRef.current.increment()}>increment</button> </> ); }Copy the code
note
- Avoid using
- Should be used with the forwardRef
9. userLayoutEffect
define
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<any> | void | null,
): void
Copy the code
use
Basically the same as userEffect, except that it is executed after a DOM change and before drawing
The sample
// styles.css
.container {
background: green;
height: 200px;
position: relative;
margin-bottom: 10px;
}
.inner {
display: block;
width: 130px;
height: 50px;
background: red;
position: absolute;
left: 0;
top: 0;
transition: all 3s;
}
.active {
left: 100px;
}
Copy the code
// App.js import React, { useLayoutEffect, useRef, useEffect } from "react"; import "./styles.css"; export default function App() { const ref1 = useRef(); const ref2 = useRef(); UseLayoutEffect (() => {// 1. Ref1.current. ClassName = "inner active"; ref1.current. } []); UseEffect (() => {// 1. 3. Execute useEffect to add class active // 4. Ref2.current. ClassName = "inner active"; } []); return ( <div> <div className="container"> <div ref={ref1} className="inner"> useLayoutEffect </div> </div> <div className="container"> <div ref={ref2} className="inner"> useEffect </div> </div> </div> ); }Copy the code
10. userDebugValue
define
useDebugValue<T>(value: T, formatterFn: ? (value: T) => any): voidCopy the code
use
For custom Hook debugging, the React developer tool displays the tag for custom hooks.
The sample
import React, { useDebugValue, useState } from "react"; function useFriendStatus() { const [isOnline] = useState("1"); "FriendStatus: Online" useDebugValue(isOnline? "Online" : "Offline"); return isOnline; } export default function App() { const isOnline = useFriendStatus(); return <>isOnline: {isOnline}</>; }Copy the code
note
- Accepts a formatting function as an optional second argument. This function is called only when the Hook is checked. It takes a debug value as an argument and returns a formatted display value. eg:
useDebugValue(date, date => date.toDateString());
Customize the Hook
- A custom Hook is a normal function that calls a Hook. It is highly recommended that the name begin with “use”. This is a non-mandatory convention, which does not affect function operation, but affects reading experience and tool recognition and detection Hook rules.
- Other built-in or custom hooks can be called from within a function, again observing the limitations of hooks such as only being called at the top level.
- By custom Hook, component logic can be extracted into a separate function for reuse. If the purpose is not to reuse component logic and no Hook is called, do not name the function with the beginning of “use”.
Here is an example of a custom Hook that reuses formatting and updating logic over time for chat and comment message delivery times.
import React, { useRef, useEffect, useState } from "react"; function formatTime(time) { const now = +new Date(); const offsetSeconds = Math.round((now - time) / 1000); const Minute = 60; const Hour = Minute * 60; const Day = Hour * 24; If (offsetSeconds < Minute) {return '${offsetSeconds} before'; } else if (offsetSeconds < Hour) {return '${math. round(offsetSeconds/Minute)}'; } else if (offsetSeconds < Day) {return '${math.round (offsetSeconds/Hour)} Hour ago'; } else {return '${math.round (offsetSeconds/Day)} days ago'; } } function useFormatMessageTime(arrivedTime) { const [formattedTime, setFormattedTime] = useState(() => formatTime(arrivedTime) ); const ref = useRef(); useEffect(() => { console.log("setInterval"); ref.current = setInterval(() => { setFormattedTime(formatTime(arrivedTime)); }); return () => { console.log("clearInterval"); clearInterval(ref.current); ref.current = null; }; }, [arrivedTime]); return formattedTime; } export default function App() { const [time, setTime] = useState(new Date()); const formattedTime = useFormatMessageTime(time); return ( <> message arrived at: {formattedTime} <button onClick={() => setTime(new Date())}>set now</button> </> ); }Copy the code
Realize the principle of
Recommended reading:
- Use an array of closures to save hook state
- Simple understanding and implementation of Fiber architecture: rendering task refinement using requestIdleCallback and saving the current task, hook hanging on Fiber in the form of linked list, simple DOM diff
IO /s/react-fib…
Contrast the Vue
This article compares React Hooks with the Vue Composition API in more detail. List briefly the similarities and differences in the presentation.
Similarities:
- Solve the problem of state reuse and logical code point dispersion
- The API has similarities
Difference:
- The React list-based implementation has restrictions on the order of calls, while the Vue proxy-based implementation does not.
- React requires manually defining hook dependencies but provides an optimized means of skipping execution. Vue collects dependencies automatically.
- React hooks need to be executed every time they are rendered, and vue is executed once in setup.
- React provides useEffect to handle side effects similar to component execution equivalent to multiple life cycles. Vue provides different apis for different option life cycles.
Conclusion figure
The resources
- The react hook documents
- Eslint – plugin – react – hooks document
- The birth of React hooks
- React hooks Advances and principles
- The React principle of Hooks
- React Hook basic implementation principles
- React hooks: Not magic, just arrays
- Simple understanding and implementation of the Fiber architecture
- Why does React Fiber use linked lists to design component trees
- Rounding requestIdleCallback
- The react hook source
- Vue composite API documentation
- Contrast React Hooks with Vue Composition API