The react – hook profile
introduce
- Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.
- React -hook can use functions to declare a stateful component.
What can React-Hook do for us?
- Functional programming. Use functions to animate components, never use them again
class
Keyword to declare the component. Make code writing concise, reduce a lot of redundant code at compile time. - Better state logic reuse scheme. Previously, HOC(high order components) and renderProps were used to solve the difficult problem of state logic reuse
- The hierarchy is too deeply nested, and both patterns nest components layer by layer through composition
- Too many life cycles, logical code logic is difficult to read, bug debugging is difficult
- Custom hooks can take arguments and return values of any type. Every time the hook is called, the state and logic are isolated, so that the business logic can be pulled out of the UI at any time, and can be repeated, reduce the amount of code, improve the development efficiency.
React-hook API
useState
import React, { useState } from 'react'; Const [count, setCount] = useState(0); const [count, setCount] = useState(0); return( <div>p <p>{count}</p> <button onClick={()=>setCount(count+1)}>+1</button> </div> ) }Copy the code
conclusion
- UseState () takes a parameter as the initial value for the current declaration of state. Note: useState is executed only when the component is initialized, so the initial value of useState only takes effect on the first rendering. So you can do the first rendering in the useState function, like in the class component
constructor
andcomponentWillMount
.const Parent = () => { const [count, setCount] = useState(0); Return (<div>p <p>{count}</p> <button onClick={()=>setCount(count+1)}>+1</button> </div>)} const Child = ({count}) => { Const [chldCount, setChldcount] = useState(count); return( <div>p <p>{count}</p> <button onClick={()=>setCount(count+1)}>+1</button> </div> ) }Copy the code
- UseState returns an array of the current state and the update state function. For identification purposes, array destructuring is used to declare the value as
xxx
andsetxxx
Two values. - Using multiple update state functions triggers multiple render functions, not a single render like the setState combination in a class component. So the associated state suggests using a useState
Const Demo = () => {// Declare multiple state variables const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); Const [todos, setTodos] = useState([{text: 'learn Hook'}]); ChangeState = () =>{// Render is triggered three times, setAge(1) is not recommended if the component render is consuming too much performance; setFruit('apple'); SetTodos ([{text:' learn how to do this '])}Copy the code
- You can write a custom hook to handle the situation where multiple state updates result in multiple render. You are advised to use useReducer when you need to modify multiple states with complex logic.
Const useLegacyState = (states = {}) => {const [value, setValue] = useState(states); const setState = (param) => { if(typeof param === 'object'){ const newStates = Object.assign({}, value, param); SetValue (newStates)}else if(typeof param === 'function'){setValue(param)}else{throw new Error(" parameter Error "); } } return [value, SetState]} const Demo () = = > {/ / declare multiple state variables const [state, setStete] = useLegacyState ({age: 42, fruit: banana, todos: 'learning Hook'}) changeState = () =>{// Render setStete({age:1, fruit:'apple', todos:' learn how todo '})}Copy the code
- Since useState replaces the previous state with each update, passing the same object into the update function will not trigger the update when useState is used.
- Closure issues with useState
const demo = ()=>{ const [count,setCount] = useState(0); const asyncChangeCount = ()=>{ setTimeout(()=>{ setCount(count+1) },3000) } return( <div> <a Count <a onclick={()=>setCount(count+1)}> Sync +1</a> When count is 3, AsyncChangeCount method const asyncChangeCount = ()=>{setTimeout(()=>{// Change here setCount((count)=>count+1) },3000) }Copy the code
About closures in hooks:
UseEffect, useMemo, and useCallback are all built-in closures. That is, every time a component is rendered, it captures the state (props) in the context of the current component function. Therefore, each execution of the three hooks reflects the state at that time, and the latest state cannot be obtained. In this case, dependencies should be used precisely to refresh obsolete closures.
useEffect
- Effect accepts a callback function with an array type dependency that allows you to perform side effects in function components and return a function that clears side effects
useEffect(() => { document.title = `You clicked ${count} times`; window.addEventListener('load', loadHandle); / / loadHandle function definitions to omit the return () = > {window. The removeEventListener (' load 'loadHandle); // Perform cleanup: callback is called before the next execution}; }, [count]); UseEffect is reexecuted every time count changesCopy the code
conclusion
- Execution time:Render once after each render, or only after the first render if the dependency is set to an empty array, equivalent to the class component’s
componentDidMount
Equivalent to the class component if no dependency is passed incomponentDidUpdate
, returns a function that clears dependencies equivalent to the class component’scomponentWillUnmount
.
prompt
Unlike componentDidMount or componentDidUpdate, Effect > scheduled with useEffect does not block the browser update screen, making your application seem more responsive. In most cases, effect > does not need to be executed synchronously. In individual cases (such as measuring layout), there is a separate useLayoutEffect Hook for you to use, with the same API as useEffect.
- The comparison of dependencies is shallow, passing in objects and functions (referencing type parameters) is meaningless, and many bug loops are caused by incorrect passing of dependencies. From a performance optimization and bug reduction perspective, it is recommended that dependency parameters be passed every time.
CountArray is an array const Demo = ({countArray}) =>{// Every time the Demo component is updated, UseEffect (()=>{// do something},[countArray])} const Demo = ()=>{ const [countArray,setCountArray] = useState([]); UseEffect (()=>{setCountArray([])},[countArray])}Copy the code
- Use functions declared inside useEffect whenever possible. It is difficult to remember which props and states are used by functions outside effect. This is why you usually want to declare the required function inside effect.
const App = ({ someProp })=> { const doSomething =()=> { console.log(someProp); } useEffect(() => { doSomething(); } []); // This is not safe (it calls' doSomething 'which uses' someProp'). When someProp is updated, } const App =({someProp})=> {useEffect(() => {const doSomething =()=> { console.log(someProp); } doSomething(); }, [someProp]); // useEffect relies on someProp so that doSomething prints the correct value every time someProp is updated. }Copy the code
- You can use multiple USEeffects in a component for logical separation.
Const Demo () = = > {useEffect (() = > {a} / / logic) useEffect (() = > {/ logic / 2})}Copy the code
- UseEffect gets the render state and props after each render. UseRef saves any variable value, and the.current property always takes the latest value. So if you can’t get the latest values, you can use useRef to save state and props.
function Counter() { const [count, setCount] = useState(0); // useEffect will print after 3 seconds. // You clicked 1 times. // You clicked 2 times useEffect(() => { setTimeout(() => { console.log(`You clicked ${count} times`); }, 3000); }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } // use useRef... const [count, setCount] = useState(0); const latestCount = useRef(count); UseEffect is clicked after 3 seconds. // You clicked 3 times. // You clicked 3 times useEffect(() => { latestCount.current = count; setTimeout(() => { console.log(`You clicked ${latestCount.current} times`); }, 3000); }); .Copy the code
useReducer
- UseReducer is an alternative to useState. If there are too many Usestates maintained in a component (more than 5 are recommended) or the state logic is complex, you should consider using useReducer. It can also optimize the performance of components that trigger deep updates. (useState is built using useReducer, so any place that can use useState must use useReducer.)
- The useReducer receives a Reducer (state,action)=>newState and returns the current state and its accompanying dispatch function.
// React official demo 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(); } } function Counter() { 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
conclusion
- React ensures that the function’s identity is stable and does not change when the component is re-rendered. That is, when a child component accepts the dispatch function, it does not re-render the component because Dispatch is a function variable. If the state change function returned by useState is passed to the child component, the child component will re-render.
- Using the dispatch function twice at the same time will only trigger render once.
- Multiple Usereducers can be maintained in a component, and usereducers can also use the same set of Reduce to improve code reuse.
useContext
- Receives a context object (the return value of React. CreateContext) and returns the current value of the context. The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component.
// Create a React context // defaultValue if the parent component does not use Provider, when the child component uses useContext, Const DemoContext = react.createconText (defaultVadelue); Function App() {return (< democontext. Provider value={{a:1}}> <Children /> </DemoContext.Provider> ); } const Children = () =>{ const context = useContext(DemoContext); // context={a:1} ... }Copy the code
conclusion
- UseContext solves the problem of transparent transmission of parent component values to child components and avoids the problem of transparent transmission of multiple props
- Note: Components that call useContext will always be rerendered when the context value changes. If the context changes frequently, react.Memo () is a good way to manage performance issues with subcomponents.
- Build a small Redux using useReducer and useContext
// Content.js import React, { useReducer, createContext } from 'react' const defaultState = { value: 0 } function reducer(state, action) { switch(action.type) { case 'ADD_NUM': return { ... state, value: state.value + 1 }; case 'REDUCE_NUM': return { ... state, value: state.value - 1 }; default: throw new Error(); } } export const Context = createContext(null) export function Content() { const [state, dispatch] = useReducer(reducer, DefaultState) return (// pass dispatch and state to all Children's components. <Children/> </ context.provider >)} // We can use useContext() to retrieve state and dispatch functions.Copy the code
useCallback
- UseCallback takes an inline callback function with an array of dependencies as arguments and returns the Memoized version of the callback function.
Const App = ()=> {// Using useCallback returns an Memoized version of ()=>console.log('222') and is only generated when the component is first rendered, Const fn = useCallback(()=>{console.log('222')},[]) return(<div> <button onClick={fn}> click </button> </div>)}Copy the code
conclusion
- UseCallback returns a memoizedCallback, which always refers to the same function when the dependency is unchanged, that is, useCallback returns a cached version of the inline function.
// let fn = null; const App = () => { const [count, setCount] = useState(0); const consoleCount = useCallback(() => { console.log('6666',count); // Always output 0}, []) // Return false for first render and true for every count that becomes a re-render console.log('666', object.is (fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, consoleCount)) fn = consoleCount; Return (<div> <h1>{count}</h1> <button onClick={() => setCount(count +1)}> click +1</button> <button OnClick ={consoleCount}> Output count</button> </div>)}Copy the code
- UseCallback does not prevent the creation of functions, even if the dependency array of useCallback is
[]
, every time a component renders()=>console.log('222')
The function will still be recreated. - The most used scenario for useCallback is cachingmemoizedCallback, so that if you want tomemoizedCallbackPass to a child component, which can be used in a child component
React.memo
To reduce unnecessary rendering.
let fun = null; const Parent = ()=>{ const [count,setCount] = useState(0); Const memoSetCount = useCallback((count)=>setCount(count),[]) Return true console.log('77', object. is(fun,memoSetCount)) fun=memoSetCount; Return (<div> <h1>{count}</h1> <Child setCount={memoSetCount} /> </div>)} // Child component will not re-render const Child = after count changes The React. Memo (({setCount}) = > {return (< button onClick = {() = > setCount ((count) = > count + 1} > click + 1 < / button >)})Copy the code
- React.memo and react. useCallback should be used together. If either of them is missing, the performance will drop rather than increase.
useMemo
- Unlike useCallback, useMemo returns a “cache value “, passing the” create “function and an array of dependencies as arguments to useMemo, which recalculates memoized values only when a dependency changes
const Parent = () => { const [count, setCount] = useState(0); const [number, setNumber] = useState(0); const handleSetCount = () => { setCount(count + 1); } const handleCalNumber = () => { setNumber(number + 1); <button onClick={handleSetCount}>count is: <button onClick={handleCalNumber}>numberis: {number} </button> <hr /> <Child count={count} /> </div> ); } // Child caches the component value every time count changes. If count does not change, Child returns only the cached value. Const Child = ({count}) => {return useMemo(() => {console.log('111')); return ( <div> <h1>{count}</h1> </div> ) }, [count]) }Copy the code
conclusion
- UseCallback (fn, deps) is equivalent to useMemo(() => FN, deps).
- UseCallback and uesMemo do not prevent the execution of inline functions, but impose additional operations on caching and contrast dependencies (performance costs). If using useMemo makes the code difficult to read (cost of understanding) or if the optimization effect is not obvious, it is not recommended to use it in large numbers.
- The greatest use of useCallback and uesMemo is the ability to keep the returned values and function references unchanged without changing the dependency. For some functions or values as useEffect dependencies and components of the Memo optimization convenience is still very useful, but it is still necessary to evaluate the effect of optimization is not great. (Number of renders, number of renders, amount of calculations)
useRef
- UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component.
Function TextInputWithFocusButton() {const inputEl = useRef(null); Const onButtonClick = () => {// 'current' points to the text input element inputel.current.focus () mounted to the DOM; }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }Copy the code
- UseRef () is more useful than the ref attribute. Similar to this in class, it is easy to hold any variable value.
conclusion
- What is the difference between createRef and useRef? CreateRef returns a new reference every time it renders, while useRef returns the same reference every time.
Const App = () => {const [count, setCount] = useState(0) // createRef = createRef(); // useRef returns the same ref object every time it renders. const ref = useRef(); useEffect(() => { creRef.current = count; ref.current = count; }) console.log('creRef', creref.current) // returns null null null console.log('ref',ref.current) // returns 0,1,2,3 return ( <div> <h1>{count}</h1> <button onClick={setcount.bind (null, count +1)}> click +1</button> </div>)}Copy the code
- Using useRef will solve the problem with most of the hook, because useEffect, useCallback, useMemo produces closures, if there is no correct specify dependent or no reasonable use closures will not want to get the value of the.
- Because every time the.current property of useRef() takes the latest value. The global scope is modified in one place and updated everywhere else. So we can always get the latest value with.current.
- In hook development, if you want to retrieve the value of the last update, you can also use useRef to do so
const usePreValue = value = >{ const valueRef = useRef(); Const preValue = valueref.current; // Update current valueref. current = value; // return preValue; } // Use const App = () => {const [count, setCount] = useState(0) const preValue = usePreValue(count); Return (<div > <h1>current: {count}</h1> <h1>preValue: {preValue}</h1> <button onClick={setcount.bind (null, count +1)}> </button> </div>)Copy the code
useImperativeHanle
- Definition:
useImperativeHandle(ref, createHandle, [deps])
- Ref receives the REF instance passed by the parent and forwards the child’s REF instance properties to the parent’s REF
- Expose those properties in the REF instance in the child component
- Dependencies. UseImperativeHandle reprints the instance properties of the child component to the parent component only when the listening dependency changes
- Function: The parent component’s REF instance can be exposed to its parent component’s REF instance
const FancyInput(props, ref) { const inputRef = useRef(); UseImperativeHandle (ref, () => ({inputref.current. Focus: () => {inputref.current. }})); return <input ref={inputRef} />; } const FancyRefInput = forwardRef(FancyInput); const Parent = ()=>{ const parentRef = useRef(); Return (<div> <FancyRefInput ref={parentRef}/> <button onClick={()=> parentref.current.focus ()}> Focus </button> </div> ) }Copy the code
conclusion
- UseImperativeHandle allows you to customize the ref instance properties that child components expose to parent components, so you can also customize the Focus method to do more in it.
- UseImperativeHandle should be used together with the forwardRef.
- You can use this hook if you don’t want to expose all of the ref’s attributes to the parent component, but it’s rarely used in practice.
useLayoutEffect
const App = () => { const [count, setCount] = useState(0); const [number, setNumber] = useState(0); useEffect(() => { if (count === 0) { setCount(10 + Math.random() * 200); } }, [count]); useLayoutEffect(() => { if (number === 0) { setNumber(10 + Math.random() * 200); } }, [number]); <h1 onClick={() => setCount(0)}>{count}</h1> <h1 onClick={() => setNumber(0)}>{number}</h1> </div>); };Copy the code
conclusion
- UseLayoutEffect is triggered earlier than useEffect.
- UseEffect should be used unless you are considering using useLayoutEffect to modify the DOM and not let the user see the DOM modification process.
- UseLayoutEffect and useEffect are used the same way, taking a function and dependency and returning a callback to clear up the side effects.
- UseLayoutEffect executes at the same time as the normally written ‘componentDidMount’ and ‘componentDidUpdate’ for ClassComponent.
useDebugValue
- UseDebugValue can be used to display the tag of custom hooks in the React developer tool. It is mainly used to encapsulate custom hooks.
Const useHook = () => {const [count, setCount] = useState(0); UseDebugValue (count > 5? 'count>5' : 'count<=5'); const mySetCount = () => { setCount(count + 2); } return [count, mySetCount]; } const App = () => { const [count, setCount] = useHook(); return ( <div> <h1>{count}</h1> <button onClick={setCount}>count+2</button> </div> ) }Copy the code
conclusion
- React does not recommend adding debug values to every custom Hook. It is most valuable when it is part of a shared library.
- UseDebugValue 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.
useDebugValue(date, date => date.toDateString());
React-hook rendering logic
- When a functional component is updated, the logic surrounding the component (such as defined variables, calculations) is re-executed. The outer logic in a class component is only re-executed on new. Don’t worry about the hooks being slowed down by creating functions at render time; in modern browsers, the raw performance of closures and classes is only significantly different in extreme scenarios.
- Each use of the update status function in useState causes the component to re-render once, or twice if two update status functions are used within an operation.
- With useReducer, using two dispatch functions in the same operation to change the state will only cause the component to re-render once.
- When the useState update function updates the same value, the component is not re-rendered.
- When the parent component is updated, the child components are rerendered. Use the react.memo to shallow compare the props to prevent the child component from updating.
conclusion
- The more carefully the components are broken down, the better the hooks will be.
- When the status update logic is complex, you are advised to use useReducer.