1: Introduction
Hook is a new feature of React 16.8. It allows you to use state and other React features without writing class.
hook introduction time (react version update event)
- 2013 May 29 v0.3.0 Facebook Open source React ..... - 2014 October 28 v0.12.0 use BSD agreement+Additional patent license agreement- 2016 March 30 v0.14.0 Split into React and ReactDOM - 2016 April 9 v15.0.0 Change of mounting mode of components SVG Compatibility, etc - 2017 September 25 v15.6.2 Open source agreement changed to MIT - 2017 September 26 v16.0.0 introduce Fiber - 2019 February 6 v16.8.0 Hook introduce - 2020 October 20 v17.0.0 Concurrent Mode,Technical transformation of the bottom layer and solving some historical burdens
Before Hook
Function component
function Welcome(props) { return <h1>hello, {props.name}</h1> }
Class component
With Hook
Function components can perform the same functions as class components, and have their own state, life cycle and state control capabilities
let timer = null export default function Clock() { const [date, setDate] = useState(new Date()) useEffect(() => { timer = setInterval(() => { setDate(new Date()) }, 1000) return () => { clearTimeout(timer) }; }, []) return <div> <h1>Hello,world!</h1> <h2>It is {date.toLocaleTimeString()}.</h2> </div> }
2: List of built-in hook APIs provided by React
useState useEffect useContext useRe useCallback useMemo useReducer useLayoutEffect useImperativeHandle useDebugValue useTransition useDeferredValue
3: Hook API details
- useState
Source code definition
export function useState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); }
Use example
const [a, setA] = useState(0)
const [data, setData] = useState(function () { return JSON.parse(bigData) })
If you pass in a function, the function is only executed when the first component is initialized, and the calculation will not be repeated when the later component is update d. It is suitable for the optimization of scenarios where a large number of calculations are required for the initial value
- useEffect
Source code definition
export function useEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { const dispatcher = resolveDispatcher(); return dispatcher.useEffect(create, deps); }
Function:
- Treatment side effects
- Implementation lifecycle
- Controllable particle size
Use example
useEffect, as a side effect processing function, can simulate the life cycle of componentDidMount and componentDidUpdate,
- useLayoutEffect
Source code definition
export function useLayoutEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { const dispatcher = resolveDispatcher(); return dispatcher.useLayoutEffect(create, deps); }
Function:
- Treatment side effects
- Implementation lifecycle
- Controllable particle size
The use method is consistent with useEffect
Difference: it will block browser rendering and execute before useEffect
Usage scenario:
Do not want users to see the dom change process. There are dom update operations in the side effect callback function, which is suitable for use
Sample code
Compare the following two groups of codes, one is the useEffect used, and the other is the uselayouteeffect used
import { React } from '../adaptation' // import { useEffect } from './react/packages/react' const { useLayoutEffect, useEffect, useState } = React export default function TestUseLayoutEffect(props) { const [count, setCount] = useState(0) const handleClick = () => { setCount(0) } useLayoutEffect(() => { if (count === 0) { // Time consuming operation start const arr = [] for (let i = 0; i < 100000000; i++) { arr.push(i) } // Time consuming operation end setCount(Math.random()) } }, [count]) return <div > {count} <button onClick={handleClick}>change</button> </div> }
import { React } from '../adaptation' // import { useEffect } from './react/packages/react' const { useEffect, useState } = React export default function TestUseEffect(props) { const [count, setCount] = useState(0) const handleClick = () => { setCount(0) } useEffect(() => { if (count === 0) { // Time consuming operation start const arr = [] for (let i = 0; i < 100000000; i++) { arr.push(i) } // Time consuming operation end setCount(Math.random()) } }, [count]) useEffect(() => { console.log('xxx123') }) return <div > {count} <button onClick={handleClick}>change</button> </div> }
- useRef
Source code definition
export function useRef<T>(initialValue: T): {| current: T |} { const dispatcher = resolveDispatcher(); return dispatcher.useRef(initialValue); }
Example
const { useRef } = React export default function TestRef() { const myRef = useRef(null) const handleClick = () => { console.log(myRef.current) } return <div> <button ref={myRef} onClick={handleClick}>click</button> </div> }
useRef vs createRef()
Similar function
useRef remains referenced throughout its lifecycle. Modifying the value of useRef current does not trigger re rendering
The createRef value changes each time you re render
- useMemo and useCallback
Source code definition
export function useCallback<T>( callback: T, deps: Array<mixed> | void | null, ): T { const dispatcher = resolveDispatcher(); return dispatcher.useCallback(callback, deps); } export function useMemo<T>( create: () => T, deps: Array<mixed> | void | null, ): T { const dispatcher = resolveDispatcher(); return dispatcher.useMemo(create, deps); }
useMemo avoid double counting values when updating
useCallback avoids redefining functions
Example:
In the following example, the genWelcomeStr method will not be executed no matter how many times the name is updated
import { React } from '../adaptation' const { useMemo } = React export default function TestMemo(props) { const { name, count } = props const genWelcomeStr = () => { console.log('genWelcomeStr called') return 'Hello,' + name } const welcomeStr = useMemo(genWelcomeStr, [name]) // const welcomeStr = genWelcomeStr() return <div> {count} {welcomeStr} </div> }
In the following two sets of codes, the parent-child components useCallback and useMemo cooperate. As long as the name remains unchanged, the function direction of the useCallback package will not change. The child components listen for the incoming method through useMemo. As long as the method direction remains unchanged, the calculation will not be repeated
import { React } from '../adaptation'; // import TestMemo from './TestComponents/TestMemo' import TestUseCallback from './TestUseCallback'; const { useState, useCallback } = React function TestUseCallbackInApp() { const [count, setCount] = useState(0) const [name, setName] = useState('bob') const handleClick = (count) => { setCount(++count) } const handleGenStr = useCallback(() => { return 'name: ' + name }, [name]) return <div className="App"> <TestUseCallback handleGenStr={handleGenStr} /> {count} <button onClick={() => handleClick(count)}>add</button> <button onClick={() => setName('jack' + Date.now())}>changeName</button> </div> } export default TestUseCallbackInApp;
import { React } from '../adaptation' const { useMemo } = React export default function TestUseCallback(props) { const { handleGenStr } = props const renderStr = useMemo(() => { console.log('handleGenStr called') return handleGenStr() }, [handleGenStr]) console.log('child comp render') return <div > {renderStr} </div> }
useMemo and useCallback are suitable for optimization in extreme situations such as large data volume. Otherwise, the optimization effect is not great under ordinary data volume. Even due to a lot of monitoring logic, the code volume and performance become worse, and even cause bug s. The speed of the underlying engine can offset this performance difference.
- useReducer
Source code definition
export function useReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>] { const dispatcher = resolveDispatcher(); return dispatcher.useReducer(reducer, initialArg, init); }
Example
- useContext
Source code definition
export function useContext<T>( Context: ReactContext<T>, unstable_observedBits: number | boolean | void, ) { const dispatcher = resolveDispatcher(); if (__DEV__) { if (unstable_observedBits !== undefined) { console.error( 'useContext() second argument is reserved for future ' + 'use in React. Passing it is not supported. ' + 'You passed: %s.%s', unstable_observedBits, typeof unstable_observedBits === 'number' && Array.isArray(arguments[2]) ? '\n\nDid you call array.map(useContext)? ' + 'Calling Hooks inside a loop is not supported. ' + 'Learn more at https://fb.me/rules-of-hooks' : '', ); } // TODO: add a more generic warning for invalid values. if ((Context: any)._context !== undefined) { const realContext = (Context: any)._context; // Don't deduplicate because this legitimately causes bugs // and nobody should be using this in existing code. if (realContext.Consumer === Context) { console.error( 'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' + 'removed in a future major release. Did you mean to call useContext(Context) instead?', ); } else if (realContext.Provider === Context) { console.error( 'Calling useContext(Context.Provider) is not supported. ' + 'Did you mean to call useContext(Context) instead?', ); } } } return dispatcher.useContext(Context, unstable_observedBits); }
Example
Solve the problem of function component state sharing
Layer by layer transmission is avoided
- other
- useDebugValue add a debug specific name to the hook
function useMyCount(num) { const [ count, setCount ] = useState(0); useDebugValue('myHook'); const myCount = () => { setCount(count + 2); } return [ count, myCount ]; }
- useImperativeHandle restricts the exposed contents of the dom instance corresponding to ref
import { React } from '../adaptation' const { useRef,useImperativeHandle,forwardRef } = React const TestImperative = forwardRef((props, ref) => { const myRef = useRef(null) useImperativeHandle(ref, () => ({ focus: () => { myRef.current.focus() } })) return <div> <input ref={myRef}/> </div> }) export default TestImperative
import { React } from '../adaptation'; // import TestMemo from './TestComponents/TestMemo' import TestImperative from './TestUseImperative'; const { useEffect, useRef } = React function TestUseImperativeInApp() { const myRef = useRef(null) useEffect(() => { console.log(myRef) myRef.current.focus() }, []) return <div className="App"> <TestImperative ref={myRef}/> </div> } export default TestUseImperativeInApp;
Operation results
- useTransition component delay transition hook
- useDeferredValue listens for status, delaying over update
4: Frequently asked questions
- Why can't you define hook s in conditional, nested, or circular statements
This is the basic structure of hook linked list:
type Hooks = { memoizedState: any, baseState: any, baseUpdate: Update<any> | null queue: UpdateQueue<any> | null next: Hook | null, // link to the next hooks and concatenate each hook through next }
Pseudo code describes the correspondence between code and hook data structure:
hook sets the next line by line according to the linked list during initialization. If a conditional statement is inserted halfway, the order of the linked list generated during the first initialization may be inconsistent with the order defined by the actual useState at the time of update, resulting in a bug.
-
Can the function component with Hook completely replace the Class component?
"It is officially recommended to use hook to write components" -
Can hook simulate all the life cycles of a class component? Refer to the following figure:
class component | Hooks component |
---|---|
constructor | When useState |
getDerivedStateFromProps | useEffect configures the dependent variables and controls the update according to the dependent old and new comparison |
shouldComponentUpdate | React.memo caches the value of render and configures dependency |
render | The content returned by the function |
componentDidMount | useEffect dependency is configured as an empty array |
componentDidUpdate | useEffect controls the execution only at the time of update by using rect to cache a variable that records the first update |
componentWillUnmount | The first parameter of useEffect returns a function that will be executed when unmount |