Embrace react hooks

Ugly talk ahead
It is strongly recommended to brush at least once Official documents , repeated study <Hooks FAQ>
Here I mainly focus on the aggregation, convenient to understand for practice

I. what should react hooks solve?

The following are the shortcomings of the previous generation of standard writing class components, which is exactly what hook needs to solve

  • Large components are difficult to split, refactor, and test.
  • Business logic is scattered among various methods of components, resulting in duplicate logic or association logic.
  • Component classes introduce complex programming patterns, such as Render props and higher-order components

Design purpose

  • The enhanced function component can write a full-featured component without using "class" at all
  • Components should be written as pure functions as possible. If external functions and side effects are needed, hook the external code in

2. How to use react hooks well?

Clear concepts

  • All hook s, by default, have no dependency arrays, which are updated every time they render
  • hooks such as Props, State, event handling, Effect follow the characteristics of Capture Value every time you Render
  • When rendering, various variables will be registered, including hooks. N times of Render, there will be n mutually isolated state scopes
  • If your useEffect depends on the array as [], it is initialized once, and the state, props, etc. used are always the values saved by the Render at the time of initialization
  • React ensures that the identifications of setstate, dispatch and context functions are stable and can be safely omitted from hooks' dependency list

Every Render in Function Component will form a snapshot and keep it, which ensures the state can be controlled. hook will update every time by default, which will lead to a series of problems such as repeated requests. If you give [], it will remain unchanged. Therefore, the most important thing to use hooks well is to learn to control its changes

III. one sentence summary of Hook API

  • useState asynchronously set update state
  • useEffect handles side effects (requests, event listening, DOM manipulation, etc.)
  • useContext receives a context object and returns the current value of the context
  • useReducer synchronizes complex state s, reducing the dependency on deep delivery callbacks
  • useCallback returns a remembered callback function to avoid unnecessary rendering
  • useMemo returns a memorized value, which makes it easier to control when specific child nodes are updated, reduces the need for pure components, and can replace shouldComponentUpdate
  • useRef returns a ref object that remains unchanged throughout the life cycle of the component. Its. current property is variable and can bypass the Capture Value property
  • Uselayouteeffect has the same function signature as useEffect, but it calls effect synchronously after all DOM changes
  • Useimperatoryhandle should be used with forwardRef to expose ref customization to the instance value of the parent component
  • useDebugValue can be used to display the label of a custom hook in the React developer tool

IV. focus on similarities and differences

useState and this.setState

  • Same point: it is asynchronous. For example, in onClick event, when setState is called twice, the data changes only once.
  • Difference: setState in class is merge, while setState in useState is replace.

useState and useReducer

  • The same thing: all operation state
  • Difference: the setState method obtained by useState is asynchronous when updating data, while the dispatch method obtained by useReducer is synchronous when updating data.
  • Recommendation: use useReducer when state value structure is complex

Uselayouteeffect and useEffect

  • Same thing: side effects after browser finishes layout and drawing
  • Difference: useEffect will delay the call, uselayouteeffect will synchronously call to block visual update, which can be used to read DOM layout and trigger re rendering synchronously
  • Recommendation: first use useEffect, and then try uselayouteeffect only when it has problems

useCallback and useMemo

  • The same point: all return remembered, usecallback (FN, DEPs) is equivalent to usememo (() = > FN, DEPs)
  • Difference: useMemo returns the cached variable, useCallback returns the cached function
  • Recommendation: do not optimize the performance too early, it is better to match the taste (see performance optimization below for details)

V. performance optimization

In most cases, we just follow the default behavior of React, because React only updates the changed DOM nodes, but it still takes some time to re render, unless it is too slow to notice

The optimization points of performance in react are as follows:

  • 1. Calling setState will trigger the re rendering of components, no matter whether the state before and after is different or not
  • 2. When the parent component is updated, the child component will also be automatically updated

Previous solutions

Based on the above two points, our usual solution is:

  • Use immutable for comparison, and call setState when it is not equal;
  • Judge props and state before and after in shouldComponentUpdate. If there is no change, return false to prevent update.
  • Use React.PureComponent

Solutions after using hooks function

Traditionally, it is believed that the performance impact of using inline functions in React is related to how passing new callbacks in each rendering will destroy the shouldComponentUpdate optimization of subcomponents. It is very useful when using useCallback to cache function references and then passing them to optimized subcomponents and using reference equality to avoid unnecessary rendering

  • 1. Using React.memo is equivalent to PureComponent, but it only compares props, and the return value is the opposite, true will skip the update
const Button = React.memo((props) => {
  // Your components
}, fn);// You can also customize the comparison function
  • 2. Use useMemo to optimize each specific sub node (see practice 3 for details)
  • 3. useCallback Hook allows you to keep the same callback reference between re renderings so that shouldComponentUpdate can continue to work (see practice 3 for details)
  • 4. useReducer Hook reduces the dependence on deep transfer callback (see practice 2 for details)

How to create expensive objects lazily?

  • When creating an initial state is expensive, we can pass a function to useState to avoid recreating the ignored initial state
function Table(props) {
  // ⚠⚠ createRows() is called every time it is rendered
  const [rows, setRows] = useState(createRows(props.count));
  // ...
  // ✅ createRows() will only be called once
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}
  • Avoid recreating the initial value of useRef(), and ensure that some imperative class instances are created only once:
function Image(props) {
  // ⚠ IntersectionObserver will be created in every rendering
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}
function Image(props) {
  const ref = useRef(null);
  // ✅ IntersectionObserver will only be created once by laziness
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }
  // Call getObserver() when you need it
  // ...
}

Vi. precautions

Hook rules

  • Use Hook at the top
  • Call Hook only in the React function, instead of calling it in the ordinary JavaScript function.
  • Put condition judgment in hook
  • All Hooks must start with use, which is a convention. It is convenient to use the ESLint plug-in to enforce the Hook specification to avoid bugs;
useEffect(function persistForm() {
  if (name !== '') {
    localStorage.setItem('formData', name);
  }
});

Tell React which external variables are used and how to compare dependencies

useEffect(() => {
  document.title = "Hello, " + name;
}, [name]); // Take useEffect as an example for all hook s

The use effect will not be executed again until the Rerender when the name changes, ensuring the performance and controllable state

Don't set dependent variables inside hook, otherwise your code can't stop like a rotating Chrysanthemum

useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(id);
}, [count]);// Take useEffect as an example for all hook s

Do not perform rendering independent operations inside useMemo

  • useMemo returns a memorized value. The "create" function and dependency array are passed into useMemo as parameters. It recalculates the memorized value only when a dependency changes, avoiding high overhead calculation every time it renders.
  • The function passed in useMemo will be executed during rendering. Please do not perform rendering independent operations inside this function.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

VII. Examples of practice scenarios

The actual application scenario is often not a hook that can handle. It is not necessarily clear in a long speech, but directly on the example (from the official website excerpt, network collection, self summary)

1. The Effect that you want to execute only once needs to depend on external variables

[decouple update and action] - [useEffect, useReducer, useState]

  • 1-1. Use the functional update of setState to solve the problem of relying on a variable

This function receives the previous state and returns an updated value

useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  return () => clearInterval(id);
}, []);
  • 1-2. Use useReducer to solve dependency on multiple variables
import React, { useReducer, useEffect } from "react";

const initialState = {
  count: 0,
  step: 1,
};

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === 'tick') {
    return { count: count + step, step };
  } else if (action.type === 'step') {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}
export default function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;
  console.log(count);
  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: 'tick' });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return (
    <>
      <h1>{count}</h1>
      <input value={step} onChange={e => {
        dispatch({
          type: 'step',
          step: Number(e.target.value)
        });
      }} />
    </>
  );
}

2. Deep transfer callback in large component tree

[pass down a dispatch function through context] - [createContext, useReducer, useContext]

/**index.js**/
import React, { useReducer } from "react";
import Count from './Count'
export const StoreDispatch = React.createContext(null);
const initialState = {
  count: 0,
  step: 1,
};

function reducer(state, action) {
  const { count, step } = state;
  switch (action.type) {
    case 'tick':
      return { count: count + step, step };
    case 'step':
      return { count, step: action.step };
    default:
      throw new Error();
  }
}
export default function Counter() {
  // Tip: 'dispatch' does not change between renderings
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StoreDispatch.Provider value={dispatch}>
      <Count state={state} />
    </StoreDispatch.Provider>
  );
}

/**Count.js**/
import React, { useEffect,useContext }  from 'react';
import {StoreDispatch} from '../index'
import styles from './index.css';

export default function(props) {
  const { count, step } = props.state;
  const dispatch = useContext(StoreDispatch);
  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: 'tick' });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);
  return (
    <div className={styles.normal}>
      <h1>{count}</h1>
      <input value={step} onChange={e => {
        dispatch({
          type: 'step',
          step: Number(e.target.value)
        });
      }} />
    </div>
  );
}

3. Code cohesion, update controllable

[level by level dependency, management by level] - [useEffect, useCallback, useContext]

function App() {
  const [count, setCount] = useState(1);
  const countRef = useRef();// Maintain a unique instance in the component life cycle and pass values through closures

  useEffect(() => {
    countRef.current = count; // Write count to ref
  });
  // Only when countRef changes will the function be recreated
  const callback = useCallback(() => {
    const currentCount = countRef.current //Keep up to date
    console.log(currentCount);
  }, [countRef]);
  return (
    <Parent callback={callback} count={count}/>
  )
}
function Parent({ count, callback }) {
  // count changes before re rendering
  const child1 = useMemo(() => <Child1 count={count} />, [count]);
  // If the callback changes, it will be re rendered. If the count changes, it will not be re rendered
  const child2 = useMemo(() => <Child2 callback={callback} />, [callback]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

VIII. Custom HOOK

Get props or state of last round

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

Run effect only at update time

function useUpdate(fn) {
  const mounting = useRef(true);
  useEffect(() => {
    if (mounting.current) {
      mounting.current = false;
    } else {
      fn();
    }
  });
}

Component destroyed or not

function useIsMounted(fn) {
  const [isMount, setIsMount] = useState(false);
  useEffect(() => {
    if (!isMount) {
      setIsMount(true);
    }
    return () => setIsMount(false);
  }, []);
  return isMount;
}

Lazy initialization useRef

function useInertRef(obj) { // Pass in an instance new IntersectionObserver(onIntersect)
  const ref = useRef(null);
  if (ref.current === null) {
  // ✅ IntersectionObserver will only be created once by laziness
    ref.current = obj;
  }
  return ref.current;
}

Reference articles

  1. Read "useEffect Complete Guide"
  2. Intensive reading of Function VS Class components
  3. How to make wheels with React Hooks
  4. Use of React Hooks
  5. useMemo and useCallback User Guide

Tags: Javascript React Programming snapshot

Posted on Fri, 08 Nov 2019 02:09:36 -0800 by PhilippeDJ