Summary of Knowledge Points for React-hooks Interview and Inspection

Introduction to Hook s

Problems with React before Hook s

  1. Reusing state logic between components is hard

React doesn't provide a way to "attach" reusable behavior to components (eg, connect components to a store). There are some solutions to this problem, such as render props and higher order components. But such scenarios require reorganizing your component structure, which can be cumbersome and make your code difficult to understand.

  1. Complex components become difficult to understand

Components often fetch data in componentDidMount and componentDidUpdate. However, the same componentDidMount may also contain a lot of other logic, such as setting event listeners, which need to be cleared later in componentWillUnmount. Code that is related to each other and needs to be modified is split, while code that is completely unrelated is combined in the same method. This is prone to bug s and leads to inconsistencies in logic.

  1. incomprehensible class

Classes are a big barrier to learning React. You have to understand how this works in JavaScript, which is very different from other languages. And don't forget to bind event handlers. Without a stable syntax proposal, the code is very redundant. You have a good understanding of props, state and top-down data flow, but nothing about class es.

Solutions brought by Hook

  1. You can use Hook s to extract state logic from components so that this logic can be independently tested and reused. Hook s allow you to reuse state logic without modifying the component structure.
  2. Hook s split the interrelated parts of a component into smaller functions (such as setting up subscriptions or requesting data), rather than enforcing lifecycle division. You can also use reducer s to manage the internal state of components, making them more predictable.
  3. Hook s allow you to use more React features without class es. Conceptually, React components have always been more like functions. Hook s, on the other hand, embrace functions without sacrificing the spiritual principles of React. Hook s provide solutions to problems without having to learn complex functional or reactive programming techniques.

Hook API

useState

useState is a hook function that comes with react, and its role is to declare state variables. The parameter the useState function receives is the initial state of our state, and it returns an array. The item [0] of this array is the current state value, and the item [1] is the method that can change the state value. function.

initialization

//Returns a state, and a function setState that updates the state (receives a new state value and enqueues a re-render of the component)
const [state, setState] = useState(initialState);
copy

functional update

//If the new state needs to be calculated using the previous state, then a function can be passed to setState. This function will take the previous state and return an updated value.
function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}
copy

lazy initial state

//If the initial state needs to be obtained through complex calculations, you can pass in a function, calculate and return the initial state in the function, this function is only called during the initial rendering
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});
copy

skip state update

When calling the State Hook's update function and passing in the current state, React will skip rendering of child components and execution of effect s. (React uses the Object.is comparison algorithm to compare state.)

useEffect

The stateful components we write usually have a lot of side effect s, such as initiating ajax requests to get data, adding some monitoring registration and deregistration, manually modifying the dom, and so on. We have written these side effects functions in the life cycle function hooks before, such as componentDidMount, componentDidUpdate and componentWillUnmount. And now useEffect is quite a collection of these declarative periodic function hooks. It's one to three.

Simple example

import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the title of the document
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>        Click me      </button>
    </div>
  );
}
copy

clear effect

Typically, when a component unloads, it needs to clean up resources such as subscription or timer ID s created by the effect. To do this, the useEffect function returns a cleanup function. Here is an example of creating a subscription:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // clear subscription
    subscription.unsubscribe();
  };
});
copy

To prevent memory leaks, the cleanup function is executed before the component is unloaded. Also, if the component is rendered multiple times (as is usually the case), the previous effect is cleared before the next effect is executed.

When the effect is executed

Unlike componentDidMount and componentDidUpdate, the function passed to useEffect will be called lazily after the browser completes the layout and rendering. This makes it suitable for many common side-effect scenarios, such as setting up subscriptions and event handling, and so should not be performed in functions that block the browser from updating the screen.

Conditional execution of effect s

By default, effect s are executed after each round of component rendering. This way, whenever an effect's dependencies change, it will be recreated. In some cases, we don't need to create a new subscription every time the component is updated, but only when the source prop changes. To achieve this, useEffect can be passed a second parameter, which is an array of values ​​on which the effect depends.

//At this point, the subscription will only be recreated if props.source has changed. (To implement the componentDidMount function, you only need to set the second parameter to [])
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
copy

useContext

You can pass values ​​from deep components, from parent components to descendant components. Takes a context object (the return value of React.createContext ) and returns the current value of that context. The current context value is determined by the value prop of the <MyContext.Provider> closest to the current component in the upper-level component.

When the most recent <MyContext.Provider> above the component is updated, this Hook will trigger a re-render with the latest context value passed to the MyContext provider. Re-renders when component itself uses useContext even if ancestor uses React.memo or shouldComponentUpdate

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!    </button>
  );
}
copy

useReducer

An alternative to useState that can be used for complex state handling. It takes a reducer of the form (state, action) => newState and returns the current state and its dispatch method. (If you're familiar with Redux, you already know how it works.) Reference Front-end react interview questions answered in detail

specify initial state

There are two different ways to initialize the useReducer state, you can choose one of them according to the usage scenario. Passing the initial state as the second parameter to useReducer is the easiest way:

//nst [state, dispatch] = useReducer(reducer, initialArg, init);
 const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
 );
copy

In some scenarios, useReducer is more suitable than useState, for example, the state logic is more complex and contains multiple sub-values, or the next state depends on the previous state, etc. Also, using useReducer can also optimize the performance of components that trigger deep updates, because you can pass dispatch to child components instead of callback functions.

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

lazy initialization

You can choose to create the initial state lazily. To do this, pass the init function as the third argument to useReducer, so the initial state will be set to init(initialArg).

Doing so extracts the logic for calculating the state out of the reducer, which also makes it easier to handle action s that reset the state in the future:

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>        Reset      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
copy

skip dispatch

If the return value of the Reducer Hook is the same as the current state, React will skip the rendering of child components and the execution of side effects. (React uses the Object.is comparison algorithm to compare state.)

useMemo

Pass a "create" function and an array of dependencies as arguments to useMemo, which will only recalculate the memoized value when a dependency changes. This optimization helps avoid expensive computations on every render. If no dependencies array is provided, useMemo will compute new values ​​on every render. memo is a shallow comparison, which means that the object only compares the memory address. As long as your memory address does not change, no matter the ever-changing value in your object, the render will not be triggered.

You can use useMemo as a performance optimization, but don't use it as a semantic guarantee. In the future, React may choose to "forget" some of the previous memoized values ​​and recompute them on the next render, like freeing memory for offscreen components. Write code that can be executed without useMemo first - then add useMemo to your code to optimize performance.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 
copy

useCallback

Pass an inline callback function and an array of dependencies as arguments to useCallback and it will return a memoized version of the callback function that will only be updated when a dependency changes. This is useful when you pass callbacks to child components that are optimized to use reference equality to avoid unnecessary rendering (such as shouldComponentUpdate).

useMemo is similar to useCallback, both have the function of caching, useMemo is for caching values, and useCallback is for caching functions.

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
copy

useRef

useRef returns a mutable ref object whose .current property is initialized to the passed parameter (initialValue). The returned ref object persists throughout the lifetime of the component.

The value of state in useEffect is fixed. There is a way to solve this. UseRef is used, which can be understood as a function of useRef: it is equivalent to the global scope, where one is modified and the other is updated.

Essentially, useRef is like a "box" that can hold a mutable value in its .current property. You should be familiar with ref s, the main way to access the DOM. If you pass a ref object into the component as <div ref={myRef} /> , React will set the ref object's .current property to the corresponding DOM node no matter how the node changes. However, useRef() is more useful than the ref attribute. It is convenient to hold any mutable value, similar to how instance fields are used in a class.

Remember that useRef doesn't notify you when the contents of the ref object change. Changing the .current property will not cause the component to re-render. If you want to run some code when React binds or unbinds a DOM node's ref, you need to use a callback ref to do so.

const Hook =()=>{
    const [count, setCount] = useState(0)
    const btnRef = useRef(null)

    useEffect(() => {
        console.log('use effect...')
        const onClick = ()=>{
            setCount(count+1)
        }
        btnRef.current.addEventListener('click',onClick, false)
        return ()=> btnRef.current.removeEventListener('click',onClick, false)
    },[count])

    return(
        <div>
            <div>
                {count}            </div>
            <button ref={btnRef}>click me </button>
        </div>
    )
}
copy

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])
copy

useImperativeHandle allows you to customize the instance value exposed to the parent component when using ref. In most cases, imperative code like ref should be avoided. useImperativeHandle should be used with forwardRef:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
copy

In this example, the parent component that renders <FancyInput ref={inputRef} /> can call inputRef.current.focus().

Custom Hook

A custom Hook is a function whose name starts with "use" and can call other Hooks inside the function.

For example, useFriendStatus below is our first custom Hook:

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
copy

Customize a hook that listens to the width and height of the window when resize

import {useEffect, useState} from "react";

export const useWindowSize = () => {
    const [width, setWidth] = useState()
    const [height, setHeight] = useState()

    useEffect(() => {
        const {clientWidth, clientHeight} = document.documentElement
        setWidth(clientWidth)
        setHeight(clientHeight)
    }, [])

    useEffect(() => {
        const handleWindowSize = () =>{
            const {clientWidth, clientHeight} = document.documentElement
            setWidth(clientWidth)
            setHeight(clientHeight)
        };

        window.addEventListener('resize', handleWindowSize, false)

        return () => {
            window.removeEventListener('resize',handleWindowSize, false)
        }
    })

    return [width, height]
}
copy

use:

const [width, height] = useWindowSize()
const isOnline = useFriendStatus(id);
copy

Tags: React mapreduce

Posted by derezzz on Thu, 06 Oct 2022 10:17:46 +1030