1 on the hooks

1.1 Why hook

In the React class component, there are setState and lifecycle controls for state management, but these are not in the function component, so hooks (version: >=16.8) are introduced to enable developers to use more of the React feature in non-class situations.

The following is a comparison of two ways to implement an input box, class component and function component:

/** * @name */ import React from 'React '; export default class Home extends React.Component { constructor(props) { super(props); this.state = { name: 'world' }; } componentDidMount() {console.log(' componentWillUnmount ') {console.log(' componentDidMount ')} componentWillUnmount() {console.log(' componentDidMount ')} componentDidUpdate(prevProps, prevState) { if (prevState.name ! }} render() {return (<div> <p>hello {this.state.name}</p> <input type="text" placeholder="input new name" onChange={(e) => this.setState({ name: e.target.value })}> </input> </div> ); }}Copy the code
/** * @name */ import React, {useState, useEffect} from 'React '; export default function Home() { const [name, setName] = useState('world'); return ( <div> <p>hello {name}</p> <DemoState /> </div> ) } function DemoState() { const [n1, setN1] = useState(1) const [n2, setN2] = useState(2) const [n3, setN3] = useState(3) useEffect(() => { setN1(10) setN1(100) }, []) const handleClick = () => { setN2(20) setN3(30) } console.log('demo-state', n1, n2, n3) return <button onClick={handleClick}>click</button> }Copy the code

In the above example, useState is equivalent to constructor, doing the initialization of the data;

UseEffect = componentDidMount and componentDidUpdate; return () => {}; useEffect = componentDidMount and componentDidUpdate; Effect is used to monitor and unbind as follows:

UseEffect (() = > {window. AddEventListener (‘ scroll ‘, throttleFunc) return () = > {window. The removeEventListener (‘ scroll ‘, throttleFunc) } }, [])

Bind and unbind in the same Effect hook for easier state management and cleaner code.

ShouldComponentUpdate cycle useMemo = shouldComponentUpdate cycle shouldComponentUpdate cycle useMemo = shouldComponentUpdate cycle shouldComponentUpdate cycle shouldComponentUpdate cycle

The class components hooks
shouldComponentUpdate useMemo
render The function itself
getDerivedStateFromProps The update of useState
getDerivedStateFromError There is no
constructor useState
componentWillUnmount Return function in useEffect
componentDidUpdate useEffect
componentDidMount useEffect
componentDidCatch There is no
Conclusion: Using hooks function components simplifies a lot of code without having to maintain a complex life cycle or worry about this pointing.

1.2 What is hook

MemoizedState mounted on the Fiber node. Filber structure is as follows:

// Fiber structure memoziedState, // Components update based on type, // native or React key, tag,... }Copy the code

MemoziedState this field is important and is the only basis for component updates. In a class component, this is the this.state structure. When this. SetState is called, it changes its data.

That is, even a Class component does not actively call any lifecycle functions, but instead reexecutes the component after memoziedState changes, passing through these cycles during execution.

So, this explains why functional components can change their state via hooks, which essentially change the memoziedState of the corresponding Fiber node.

Hooks have the following features:

1. Reuse state logic without modifying component structure;

2. Complex components will be easier to understand by breaking the interrelated parts of components into smaller functions;

Functions within each component (including event handlers, effects, timers, or API calls, etc.) capture props and state defined in a render.

4. Memo cache component,useMemo cache value, useCallback cache function;

Each render has its own props, state, and effects. Functions within each component, including event handlers, effects, timers, or API calls, capture props and state defined in a render.

React re-renders the component when updating the state (e.g. SetCount (count + 1)). Each render gets a separate count state, which is a constant in the function.

7. Without explicit lifecycle, all post-rendering execution methods are managed in useEffect;

8. Functional programming without defining constructor, render, or class;

9. It is entirely up to the developer whether a component method needs to be rendered or re-executed, which is easy to manage.

1.3 common hook

UseState, useEffect, useMemo, useCallback, useRef, useContext, useReducer… .

All hooks introduce external functions to functions. The React convention uses the use prefix to name hooks.

Two commonly used hook

2.1 useState

Example:

const [stateA, setStateA] = useState(0)

The parameter is the initial state (it is best to give the initial value for later maintenance, 0/false/ “/[]/{}).

Return values: one is the current state and one is the function that updates the state.

The implementation of useState is simple, with only two lines

export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}


Copy the code

Focus on the dispatcher, the dispatcher through resolveDispatcher () to obtain, this function will only ReactCurrentDispatcher. Current value is assigned to the dispatcher

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}

Copy the code

UseState hung on the dispatcher, resolveDispatcher () returns the ReactCurrentDispatcher. Current, So useState (XXX) is equivalent to ReactCurrentDispatcher. Current. UseState (XXX).

UseState (hooks) executes as follows:

  • UpdateContainer -… – > beginWork
  • The beginWork will determine what to do based on the tag of the fiber currently being updated. In the functional component, updateFunctionComponent is executed.

Will render time, for the first time the React Fiber from packages/React – the reconciler/SRC/ReactFiberBeginWork beginWork of js () starts. In the beginWork function, the component can be loaded or updated according to the tag value on the workInProgress (which is a Fiber node), as follows:

function beginWork(current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) : Fiber | null {/ * * * * / / / according to the different methods of different component types go switch (workInProgress. Tag) {/ / uncertainty component case IndeterminateComponent: { const elementType = workInProgress.elementType; / / load the initial component return mountIndeterminateComponent (current, workInProgress, elementType renderExpirationTime,); } // case FunctionComponent: {const Component = workinprogress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps: resolveDefaultProps(Component, unresolvedProps); // Return updateFunctionComponent(Current, workInProgress, Component, resolvedProps, renderExpirationTime,); Case ClassComponent: {/****/}}Copy the code
  • In updateFunctionComponent, hooks are handled as follows
nextChildren = renderWithHooks(
  current,
  workInProgress,
  Component,
  nextProps,
  context,
  renderExpirationTime,
);

Copy the code

So the render core for React Hooks is renderWithHooks, and in the renderWithHooks function, the Dispatcher is initialized.

export function renderWithHooks < Props, SecondArg > (current: Fiber | null, workInProgress: Fiber, Component: (p: Props, arg: SecondArg) = >any, props: Props, secondArg: SecondArg, nextRenderLanes: Lanes, ) : If any {/ / Fiber is empty, is considered to be first loaded ReactCurrentDispatcher. Current = current = = = null | | current. MemoizedState = = = null? HooksDispatcherOnMount : HooksDispatcherOnUpdate; // const HooksDispatcherOnMount: Dispatcher = {readContext, //... useCallback: mountCallback, useContext: readContext, useEffect: mountEffect, useMemo: mountMemo, useState: mountState, // ... }; Dispatcher const HooksDispatcherOnUpdate: Dispatcher = {readContext, //... useCallback: updateCallback, useContext: readContext, useEffect: updateEffect, useMemo: updateMemo, useRef: updateRef, useState: updateState, // .... }; }Copy the code
  • In renderWithHooks, fiber is initialized based on whether its memoizedState is null. Because memoizedState holds hooks in a functional component. If yes, mount, otherwise update (check whether execute, no, mount, update)
  • In the mount (mount), functional components, ReactCurrentDispatcher. Current HooksDispatcherOnMount, is called, will initialize hooks list, initialState, dispatch function, and returns. This completes the initialization of useState, and the subsequent functional components proceed to complete the render return. (First rendering process)
  • In the update (update), functional components, ReactCurrentDispatcher. Current HooksDispatcherOnUpdate, is called, updateWorkInProgressHook Hook is used to retrieve the current work. Then determine whether to process the re-render state based on whether the numberOfReRenders are greater than 0: if yes, execute renderPhaseUpdates to get the first update, then loop to get the new state until the next update is null; If no, obtain the first update in the update list and run a loop to check whether the priority of the update needs to be updated. (Update process)
  • The result returns the current state and the method to modify the state

Take mounting as an example, generate a hook object (mountState) and initialize the hook object (mountWork in Progress shook) as follows:

function mountState < S > (initialState: (() = >S) | S, ) : [S, Dispatch < BasicStateAction < S >>] {// Create a new hook object Const hook = mountWorkInProgressHook(); if (typeof initialState === 'function') { initialState = initialState(); } hook.memoizedState = hook.baseState = initialState; Queue = hook. Queue = {// Create a new queue // Save the update object pending: Dispatchaction.bind () = dispatchaction.bind () = dispatchaction.bind () Reducer // (state, action) => {return typeof action === 'function'? Action (state) : action} lastRenderedReducer: Reducer // Last state lastRenderedState before a new dispatch is triggered (initialState: Any),} // Bind current fiber to queue. Const dispatch: Dispatch < BasicStateAction < S > , >=(queue.dispatch = (dispatchAction.bind(null, currentlyRenderingFiber, queue, ) : any)); // Return [hook.memoizedState, dispatch]; }Copy the code
MemoizedState: null, memoizedState: null, memoizedState: null Null, // Store state baseQueue before update, // queue: null, // store multiple update behavior next: null // point to the next useState hook object}; // If the workInProgressHook list is null, the new hook object will be assigned to it. If the workInProgressHook list is not null, the new hook object will be added to the list. if (workInProgressHook === null) { currentlyRenderingFiber.memoizedState = workInProgressHook = hook; } else { workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; } how does setFn update the stateA value after initialization? This is actually updated via the dispatchAction method as follows: CurrentlyRenderingFiber $1 is a global variable, Var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);Copy the code

There is also an update to the hook object (dispatchAction) as follows:

function dispatchAction(fiber, queue, action) { // ... // This object contains the scheduler priority /state/reducer and the action const update that was passed when dispatch/setState was called: Update < S, A > ={ lane, action, eagerReducer: null, eagerState: null, next: (null: any), }; // 2. Update to queue.pending, and the last update. Next points to the first update object, forming a closed loop. const pending = queue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; }Copy the code

Simple understanding:

  • On the first rendering, insert state, DEPS, etc. into the memoizedState array in the order of useState, useEffect, sharing the same memoizedState, sharing the same order.
  • When updating, take the last recorded values out of memoizedState in that order.
let memoizedState = []; // hooks store this array let cursor = 0; / / the current memoizedState subscript function useState (the initialValue) {memoizedState [cursor] = memoizedState (cursor) | | the initialValue. const currentCursor = cursor; function setState(newState) { memoizedState[currentCursor] = newState; render(); } return [memoizedState[cursor++], setState]; } function useEffect(callback, depArray) {const hasNoDeps =! depArray; const deps = memoizedState[cursor]; const hasChangedDeps = deps ? ! depArray.every((el, i) => el === deps[i]) : true; if (hasNoDeps || hasChangedDeps) { callback(); memoizedState[cursor] = depArray; } cursor++; }Copy the code
  • Look at the figure below

1, initialization: array empty, subscript 0

2. First render: Add the dependency of the encountered hook into the array, corresponding to the subscript one by one

3. Event trigger: The content of trigger hook is modified, and the modified data replaces the original data in the array

4. Re-render: When ReRender is ReRender, the function component is rerexecuted, but no action is done on the previously executed function component

Here’s an example:

const DemoState = memo(() => { const [n1, setN1] = useState(1) const [n2, setN2] = useState(2) const [n3, setN3] = useState(3) useEffect(() => { setN1(10) setN1(100) }, []) const handleClick = () => { setN2(20) setN3(30) } console.log('demo-state', n1, n2, n3) return <div> <div className={`${classPrefix}-title`}>---useState---</div> <button OnClick ={handleClick}> change n2, n3</button> </div>}) // demo-state 1 2 3 => demo-state 100 2 3 => demo-state 100 20 30Copy the code

When rendering, Effect assigns n1 twice, but actually refreshes only once; The click event assigns values to N1 and n2, respectively, and actually refreshes only once.

Conclusion: setState returns a function execution that causes re-render; Multiple function operations are merged internally to keep useState up to date and avoid repeated rendering.

If the initial state needs to be computed by complex calculations, you can pass in a function that computes and returns the initial state. This function is called only during the initial render, as follows:

const [count, setCount] = useState(() => {
  const initialCount = someExpensiveComputation(props)
  return initialState
})
Copy the code

So, what’s the difference between state and function? In the following two pieces of code, what happens if each click event fires 5 times within 1s?

class HooksDemoRule extends React.PureComponent { state = { count: 0 } increment = () => { console.log('---setState---') setTimeout(() => { this.setState({ count: this.state.count + 1 }) }, 3000) } render() { return <div> <div className={`${classPrefix}-title`}>---useState && setState---</div> <div ClassName ={' ${classPrefix}-text '}>setState {this.state.count}</div> <button onClick={this.increment}>+1</button> <IKWebviewRouterLink ikTo='home' ClassName = ${classPrefix} {` - to - home `} > < p > back < / p > < / IKWebviewRouterLink > < / div >}}Copy the code
const SecondDemo = memo(() => { const [count, setCount] = useState(0) const increment = () => { console.log('---useState---') setTimeout(() => { setCount(count + 1) }, 3000) } return <div> <div className={`${classPrefix}-title`}>---useState && setState---</div> <div ClassName ={' ${classPrefix}-text '}>useState {count}</div> <button onClick={increment}>+1</button> <IKWebviewRouterLink ikTo='rule' ClassName = ${classPrefix} {` - to - home `} > < p > back < / p > < / IKWebviewRouterLink > < / div >})Copy the code

End result: In the class component, the number on the page increases from 0 to 5; In the function component, the number on the page only increases from 0 to 1.

The reason for this is that in the class component, count is referred to by this.state, and each setTimeout is referred to by the last count, so the last count is incrementing to 5. In a function component, however, each update is a re-execution of the current function, and the count read in setTimeout within 1s is actually the initial value of 0, so it only increments to 1. How to do this if you want to increase the function component to 5 as well is explained below by useRef.

In simple terms, the state of a class component depends on the last state, and the state of a function component re-executes the current function.

2.2 useEffect

The useEffect implementation is simple, with only two lines:

export function useEffect (create: () = >(() = >void) | void, deps: Array < mixed > |void | null, ) : void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}
Copy the code

The hooks generated by useEffect will be placed on Fibre. memoizedState, which will generate an Effect object, which will be stored in memoizedState of the corresponding hook, and connected to other effects in a circular linked list. A single effect object contains the following properties: create: The first argument passed to useEffect, the callback function; Destory: the return function in the callback function, which is executed when the effect is destroyed, by default, after the first rendering. It can also be executed when the value in the dependency array changes, clearing side effects (listener, subscription, timer, etc.) by returning; Deps: dependency, the second argument passed in to control whether the Effect wrapped function is executed. If the dependency is an empty array [], this Effect is executed only once each time the component is mounted, which is equivalent to the fusion of componentDidMount and componentDidupdate life cycles in the class component. If there is no second argument, effect is called continuously. Next: points to the next effect; Tag: specifies the type of effect. UseEffect and useLayoputEffect are distinguished. The hook will be attached to Fiber. memoizedState. MemoizedState stores the Effect object of useEffect (Effect1), next points to the effect object of useLayoutEffect (Effect2), Effect2’s next points back to Effect1, forming a closed loop. The structure is as follows:

const DemoEffect = memo(() => { useEffect(() => { console.log('useEffect1'); const timeId = setTimeout(() => { console.log('useEffect1-setTimeout-2000'); }, 2000); return () => { clearTimeout(timeId); }; } []); useEffect(() => { console.log('useEffect2'); const timeId = setInterval(() => { console.log('useEffect2-setInterval-1000'); }, 1000); return () => { clearInterval(timeId); }; } []); return ( <div> <div className={`${classPrefix}-title`}>---useEffect---</div> {(() => { console.log('render'); return null; })()} </div> ); }) // render => useEffect1 => useEffect2 => useEffect2-setInterval-1000 => useEffect1-setTimeout-2000 => index.js:67 useEffect2-setInterval-1000 * nCopy the code

Conclusion: Effects are executed sequentially after the page is rendered and asynchronously internally

UseEffect and useLayoutEffect: UseLayoutEffect is also a hook method, similar to useEffect, but the difference is that the rendering timing is different. UseEffect takes place after the browser finishes rendering, while useLayoutEffect takes place after dom update.

UseEffect and useLayoutEffect are both effect hooks. Here is an example of moving a block. Add a right shift to effect to understand the difference: Both occur after Render, and useLayoutEffect occurs before useEffect

const moveTo = (dom, delay, options) => { dom.style.transform = `translate(${options.x}px)` dom.style.transition = `left ${delay}ms` } const Animate  = memo(() => { const squRef = useRef() const squRef1 = useRef() const squRef2 = useRef() useLayoutEffect(() => { // Log (' uselayouteffect-1 ') moveTo(squref1.current, 500, {x: 600})}, []) useEffect(() => {console.log('useEffect') moveTo(squref.current, 500, {x: 600 }) }, []) useLayoutEffect(() => { console.log('useLayoutEffect-2') moveTo(squRef2.current, 500, { x: 600})}, []) console.log('render') return ( <> <div className={`${classPrefix}-title`}>---useEffect && useLayoutEffect---</div> <div className={`${classPrefix}-square`} ref={squRef}></div> <div className={`${classPrefix}-square1`} ref={squRef1}></div> <div className={`${classPrefix}-square1`} ref={squRef2}></div> </> ) }) // render -> useLayoutEffect-1 -> useLayoutEffect-2 -> useEffectCopy the code

UseLayoutEffect is similar to useEffect, except that useEffect is executed asynchronously while useLayoutEffect is executed synchronously.

When a function component is refreshed (rendered),

The whole running process of the component containing useEffect is as follows: 1. Trigger the component to re-render (re-render the component by changing the state of the component or the parent component of the component, causing the child node to render). 2

UseLayoutEffect components run as follows: 1. Trigger component rerendering (by changing component state or component parent rerendering, UseLayoutEffect hook execution, React wait for useLayoutEffect function to complete execution

The advantage of useEffect asynchronous execution is that the React rendering component does not have to wait for useEffect to complete, causing a block.

99 percent of the time, useEffect is fine, and the only time we need to use useLayoutEffect is when our screen flickers (the component is rendered twice in a very short amount of time).

2.3 useReducer

Parameters: The first is the reducer pure function, the second is the initial state, and the third is the modification of the initial state. The return value used to reset is an array. The first element of the array is the current value of state, and the second element is the dispatch function that sends the action

What happens when you manage state objects with multiple child values? The following takes obtaining an interface as an example:

Const fetchReducer = (state, action) => {switch (action.type) {case 'FETCH_INIT': // Interface initialization return {... State, status: 'initialization ', loading: true, error: false}; Case 'FETCH_SUCCESS': return {... State, status: 'successful ', loading: false, error: false, data: action.data}; Case 'FETCH_FAIL': return {... State, status: 'failed ', loading: false, error: true}; default: return null } }; const DemoReducer = memo(() => { const [state, dispatch] = useReducer(fetchReducer, { loading: false, error: false, status: '', data: {} }); const getData = async () => { const { data } = await Apis.GET_USER_INFO().catch(() => { dispatch({ type: 'FETCH_FAIL', data: null }) return false }) if (! data) return dispatch({ type: 'FETCH_SUCCESS', data }) } useEffect(() => { dispatch({ type: 'FETCH_INIT' }) getData() }, []) console.log('state---', state) return <div> <div className={`${classPrefix}-title`}>---useReducer---</div> <div The className = ${classPrefix} {` - text `} > request status: {state. The status} < / div > < / div >})Copy the code

Conclusion: useReducer can handle multiple logic (load status, error messages, request data) implemented with useState.

Question to consider: The emergence of useState allows us to use multiple state variables to hold state, such as

const [width, setWidth] = useState(100)
const [height, setHeight] = useState(100)
const [left, setPageX] = useState(0)
const [top, setPageY] = useState(0)
Copy the code

It is also possible to put all states in one OBJ, as in the this.state of the class component, as in

const [state, setState] = useState({ width: 100, height: 100, left: 0, top: 0 });
Copy the code

You can also use useReducer processing, for example

const stateReducer = (state, action) => { switch (action.type) { case 'WIDTH': return { ... state, width: action.width }; case 'HEIGHT': return { ... state, height: action.height }; . } const [state, dispatch] = useReducer(stateReducer, { width: 100, height: 100, left: 0, top: 0 })Copy the code

Which of these three methods is better?

2.4 useMemo

The argument is to create an array of functions and dependencies.

The return value is a memoized value that occurs before render and is recalculated only when the dependency changes.

const DemoMemo = memo(() => { const [num1, setNum1] = useState(1) const [num2, SetNum2] = useState(2) const = useMemo(() => {console.log(' arithmetic ') let sum = 0 for (let I = 0; i < num1 * 100; i++) { sum += i } return sum }, [num1]) const handleClick1 = () => { console.log('num1++') setNum1(num1 + 1) } const handleClick2 = () => { console.log('num2++') setNum2(num2 + 1) } return <div> <div className={`${classPrefix}-title`}>---useMemo---</div> <div ClassName ={' ${classPrefix}-text '}> Current num1: {num1}</div> <div className={' ${classPrefix}-text '}> Current num2: {num2}</div> <div className={' ${classPrefix}-text '}> Current expensive(num1 only) : {expensive}</div> <div> {(() => { console.log('render'); return null; })()} <button onClick={handleClick1}>num1++</button> <button onClick={handleClick2}>num2++</button> </div> </div> }) // Num1 ++: num1++ => num2++: num1++ => renderCopy the code

Conclusion: useMemo occurs before render, returns a cache of data, and only changes after dependency changes.

Use useMemo to avoid excess computing overhead.

2.5 useCallback

The arguments are inline callback functions and an array of dependencies,

The return value is an Memoized version of the callback function, which is updated only when a dependency changes.

const set = new Set(); const DemoCallback = memo(() => { const [num1, setNum1] = useState(1) const [num2, SetNum2] = useState(2) const callback = useCallback(() => { }, [num1]) set.add(callback) const handleClick1 = () => { setNum1(num1 + 1) } const handleClick2 = () => { setNum2(num2 + 1) } console.log('demo-callback', set.size) return <div> <div className={`${classPrefix}-title`}>---useCallback---</div> <div ClassName ={' ${classPrefix}-text '}> current num1: {num1}</div> <Child callback={callback}/> <div> <button onClick={handleClick1}>num1++</button> <button onClick={handleClick2}>num2++</button> </div> </div> }) const Child = memo(({ callback }) => { console.log('---child Render ') return <div> <div className={' ${classPrefix}-text '}>child refresh (num1 only) : {set. The size} < / div > < / div >}) / / demo - 1 = > callback - child render / / click num1 + + : Demo-callback 2 => child render // click num2++: demo-callback 2Copy the code

Conclusion: Returning a cached function, adding an array of dependencies can avoid the function’s meaningless evaluation and reduce the rendering overhead of the child components.

2.6 useRef

The return value is a mutable ref object, and the value of this object changes without causing the page to render.

const DemoRef = memo(() => {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus()
    inputEl.current.value = '自定义'
  };
  return (
    <>
      <div className={`${classPrefix}-title`}>---useRef---</div>
      {(() => {
        console.log('render');
        return null;
      })()}
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>click</button>
    </>
  );
})
// render
// 点击click:  
Copy the code

Conclusion: useRef can store data that does not require page rendering; The only way to change the useRef value is to change.current without causing rerendering.

You can solve the problems described in 2.1 by the following methods

const SecondDemoNew = memo(() => {
  const [ count, setCount ] = useState(0)
  const ref = useRef(0)
  const increment = () => {
    console.log('---useState---')
    setTimeout(() => {
      setCount((ref.current += 1))
      // setCount(count => count + 1)
    }, 3000)
  }
  return <div>
    <div className={`${classPrefix}-title`}>---useState && setState---</div>
    <div className={`${classPrefix}-text`}>当前值:{count}</div>
    <button onClick={increment}>+1</button>
  </div>
})
Copy the code

In addition, useRef can also be used to implement anchor jump, as follows

Const scrollRef = useRef(null) const scrollToRef = (ref) = >{// jump if (! ref) return ref.current.scrollIntoView({ behavior: 'smooth' }) } ...... < div onClick ={() = >scrollToRef(scrollRef)} </ div> <span ref={scrollRef}></span >Copy the code

2.7 useContext

A hook function that shares data across components and receives a context object and returns the current value of that object.

The current context value is determined by the value of the nearest < myContext.provider > in the upper component, and if the context of the parent component changes, the child component is rerendered.

Const MyContext = react.createcontext () // createContext, Const DemoContext = memo(() => {const [value, setValue] = useState('initValue') return ( <div> <div className={`${classPrefix}-title`}>---useContext---</div> {(() => { console.log('render'); return null; })()} <button onClick={() => {setValue('newValue')}}> Change value </button> < myContext.provider value={value}> <Child1 /> <Child2 /> </MyContext.Provider> </div> ); }) const Child1 = memo(() => { const value = useContext(MyContext) console.log('Child1-value', value) return <div className={`${classPrefix}-text`}>Child1-value: {value}</div> }) const Child2 = () => { console.log('Child2'); return <div className={`${classPrefix}-text`}>Child2</div>; } // render => child1-value initValue => Child2 // click BTN: render => child1-value newValue => Child2Copy the code

Conclusion: useContext is re-rendered when the context value changes. When the value of < myContext.provider > changes, the wrapped component is re-rendered regardless of whether it subscribed to the value. You can use the Memo to optimize the child components that do not use the value.

2.8 Custom Hooks

Sometimes we need to reuse some state logic, how to deal with it can realize reuse without adding components, custom hook can achieve this purpose.

Through the custom hook, the logic used repeatedly by multiple components is extracted and added to a custom hook called useSomething. The purpose of logic reuse is achieved by calling this hook, and logic sharing is realized without adding components.

A custom hook is a function whose name starts with “use” that can call other hooks internally. Take the request processing process as an example, the custom hook is as follows:

Three optimization

Hook mainly optimizes functional components from the following three aspects:

UseCallback (fn, DEps) === useMemo(() => fn, DEps) useReducer is used to handle multiple states

4 summary

Hooks execution process:

In React, the JSX elements returned by components are converted to the virtual DOM, which is the vNode below. Each VNode has a _component property mounted to it, which points to the component instance. A _hooks attribute is mounted on the component instance. This _hooks attribute holds information about all the Hook methods that execute a component.

First, there is a global currentIndex variable that, when the component is first rendered or updated, is reset to 0 each time a function component is entered, incremented each time a hook method is encountered, and the hook method is added to the _list (cache). CurrentIndex is reset to 0 again the next time you come in or enter the next component; When the component is updated, the corresponding item is fetched from _list based on currentIndex. Specific description of adding links is as follows:

Component render => currentIndex reset 0 => encountered Hooks method, Add _list array => index currentIndex++ => end render component update => currentIndex reset 0 => encountered Hooks method, Get _list[currentIndex]=> currentIndex++ => repeat the previous steps => update ends

Eslint-plugin-react-hooks control the following two rules:

  • A Hook can only be called on the outermost layer of a function. Do not call in loops, conditional judgments, or subfunctions (hook2 gets state from the last HOOk1 execution, not from the last HOOk2 execution. If you put hook1 in an if statement, this will obviously cause an error when the hook is not executed.
  • You can only call hooks in the React function component (because only updates to the function component trigger the renderWithHooks function, which handles hooks’ logic).

Optimizations themselves cause a lot of computation, and meaningless optimizations add extra overhead. Therefore, the optimization of 3 needs to be cautious.

Blog.csdn.net/qq_30997503… Github.com/brickspert/…