“This is the sixth day of my participation in the August More Text Challenge. For details, see: August More Text Challenge.”

1. Before React Hooks were born

Hooks are a new feature in React 16.8 that allows us to use state and other React features (such as lifecycle) without having to write classes. React Hooks came into being as a reflection on and focus on the two forms of components: class components and function components. Let’s take a look at the pros and cons of function components versus class components.

(1) Class components

The React component is based on ES6 Class writing and inherited from React.ponent. Here is a class component:

import React from 'react';

class ClassComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      text: ""}}componentDidMount() {
    / /...
  }
  changeText = (newText) = > {
    this.setState({
      text: newText
    });
  };

  render() {
    return (
      <div>
        <p>{this.state.text}</p>
        <button onClick={this.changeText}>Modify the</button>
      </div>); }}export default ClassComponent
Copy the code

For class components, the advantages are summarized as follows:

  • Component state: Class components can define their own state, which is used to store the internal state of the component. Function components are not. Each time the function is called, a new temporary variable is created.
  • Life cycle: A class component has a life cycle in which the business logic can be completed, such as sending a network request in componentDidMount, and the life cycle function is executed only once; When a network request is sent in a function component, the network request is resent each time it is rerendered;
  • Render optimization: Class components can only re-execute render function and lifecycle functions such as componentDidUpdate when the state changes; When the function component is rerendered, the entire function is executed.

For class components, the disadvantages are summarized as follows:

  • Hard to split: With the increase of business, class components become more and more complex, a lot of logic is often mixed together, forced to split will lead to overdesign, increasing the complexity of the code;
  • Hard to understand: Class components have this and lifecycle pain points. For life cycles, not only is it expensive to learn, but the business logic needs to be planned in the proper life cycle. The logic in each life cycle seems to be unrelated, and the logic seems to be “scattered” into the life cycle. In addition, there is a reference to this in the class component, and we have to figure out who this refers to, which can easily cause problems. To solve the problem where this does not match expectations, you can use the bind, arrow function to solve the problem. But in essence, they are solving problems at the design level with constraints at the practice level.
  • Hard to reuse component state: Reusing state logic relies heavily on component design patterns such as HOC and Render Props. React doesn’t provide a way to do that at the native level. These design patterns are not omnipotent, and while they enable logic reuse, they also break the structure of components, one of the most common problems is the phenomenon of “nesting hell.”

(2) Function components

A function component is a React component that exists as a function. A function component cannot define and maintain state internally, so it has another name, “stateless component.” Here is a function component:

import React from 'react';

function FunctionComponent(props) {
  const { text } = props
  return (
    <div>
      <p>${text} '}</p>
    </div>
  );
}

export default FunctionComponent
Copy the code

Compared with class components, functional components naturally include lightweight, flexible, easy to organize and maintain, lower learning costs, and so on. In fact, between a class component and a function component, isobject-orientedandFunctional programmingThe difference between the two design ideas. The function component is more in line with the React framework design philosophy:

The React component itself is positioned as functions: functions that input data and output the UI. The React framework’s main job is to translate declarative code into imperative DOM operations in a timely manner, mapping data-level descriptions to user-visible UI changes. In principle, React data should always be tightly bound to the rendering, and class components cannot do this. The function component really binds the data and rendering together. A functional component is a form of component representation that better matches its design philosophy and is more advantageous to logical decomposition and reuse.

In order for developers to better write function components. React Hooks were born.

2. What are React Hooks?

(1) Concept

To make a function component more useful, the goal is to add state to the function component. We know that functions, unlike classes, do not have an instance of an object that can hold the state between multiple executions, so there needs to be a space outside the function to hold the state and detect changes in the state to trigger rerendering of the component. So, we need a mechanism to bind data to the execution of a function. Functions can be automatically re-executed when data changes. This way, any external data that affects UI presentation can be bound to React function components through this mechanism. And that mechanism is called React Hooks.

In fact, React Hooks are a set of “Hooks” that make function components more powerful and flexible. In React, Hooks hook a target result to a data source or event source that may change, so that when the hooked data or event changes, the code that generated the target result is reexecuted to produce the updated result. We know that function components are better than class components to express React component execution, because they are more consistent with State => View logic, but the lack of State, life cycle and other mechanisms has limited its functionality. To help function components fill in these missing capabilities.

Here’s a look at how this is implemented using class components and React Hooks, using a counter.

Using a class component:

import React from 'react'

class CounterClass extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0}}render() {
    return (
      <div>
        <h2>Current count: {this.state.counter}</h2>
        <button onClick={e= > this.increment()}>+1</button>
        <button onClick={e= > this.decrement()}>-1</button>
      </div>)}increment() {
    this.setState({counter: this.state.counter + 1})}decrement() {
    this.setState({counter: this.state.counter - 1}}})export default CounterClass
Copy the code

Use React Hooks to implement:

import React, { useState } from 'react';

function CounterHook() {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <h2>Current count: {counter}</h2>
      <button onClick={e= > setState(counter + 1)}>+1</button>
      <button onClick={e= > setState(counter - 1)}>-1</button>
    </div>)}export default CounterHook
Copy the code

As you can see from these two pieces of code, the code using React Hooks is much cleaner and more logical.

(2) Characteristics

React has two main features: simplifying logic reuse and facilitating separation of concerns.

1) Simplify logic reuse

Before the introduction of Hooks, reusing component logic was difficult. We had to rely on component design patterns such as HOC and Render Props to implement React Hooks.

Here’s an example: We have multiple components that need to resize the page layout when the user resizes the browser window. In React, we render different components based on Size. The code is as follows:

function render() {
  return size === small ? <SmallComponent /> : <LargeComponent />;
}
Copy the code

This code looks simple. However, if we use class components to implement, we need to use higher-order components to solve the problem, so let’s use higher-order components to implement.

We need to define a high-level component that will listen for changes in the window size and pass the changed value as props to the next component:

const withWindowSize = Component= > {
 	class WrappedComponent extends React.PureComponent {
 		constructor(props) {
 			super(props);
 			this.state = {
 					size: this.getSize()
 			};
 		}
 		componentDidMount() {
      // Listen to the browser window size
   		window.addEventListener("resize".this.handleResize);
   	}
 		componentWillUnmount() {
      // Remove the listener
 			window.removeEventListener("resize".this.handleResize);
 		}
    getSize() {
 			return window.innerWidth > 1000 ? "large""small";
    }
 		handleResize = () = > {
 			const currentSize = this.getSize();
 			this.setState({
 				size: this.getSize()
 			});
 		}
		render() {
      return <Component size={this.state.size} />; }}return WrappedComponent;
};
Copy the code

This can be done by calling the withWindowSize method to generate a new component with its own size property, such as:

class MyComponent extends React.Component{
 	render() {
 	const { size } = this.props;
 		return size === small ? <SmallComponent /> : <LargeComponent />; }}export default withWindowSize(MyComponent); 
Copy the code

As you can see, in order to pass the external state (size), we have to put another layer around the component, just to encapsulate a piece of reusable logic. The downside is obvious:

  • The code is not intuitive, difficult to understand, and poses great maintenance challenges;
  • By adding many additional component nodes, each higher-order component will have an extra layer of wrapping, making debugging difficult.

With the advent of React Hooks, it’s easy to do this:

const getSize = () = > {
  return window.innerWidth > 1000 ? "large" : "small";
}
const useWindowSize = () = > {
  const [size, setSize] = useState(getSize());
  useEffect(() = > {
    const handler = () = > {
      setSize(getSize())
    };
    window.addEventListener('resize', handler);
    return () = > {
      window.removeEventListener('resize', handler); }; } []);return size;
};

/ / use
const Demo = () = > {
  const size = useWindowSize();
  return size === small ? <SmallComponent /> : <LargeComponent />;
};
Copy the code

As you can see, the window size is an external data state that is encapsulated by Hooks to make it a binding data source. This way, when the window size changes, the component that uses this Hook will be rerendered. The code is also cleaner and more intuitive, with no additional component nodes.

2) It helps to separate attention

The other benefit of the Hooks is that it helps to separate concerns. In a class component, we need the same business logic to be distributed in different life cycles. For example, in the example above, we are listening to the window in componentDidMount. Unbind listener events in componentWillUnmount. In function components, we can write all the logic together. Clear separation of business logic via Hooks makes code easier to understand and maintain.

Of course, React Hooks are not perfect, but they have a few flaws:

  • Hooks cannot fully complement class component capabilities for function components, such as getSnapshotBeforeUpdate and componentDidCatch, which are currently strongly dependent on class components.
  • Sometimes there are many instances of some methods in class components, and if you use functional components to solve the same problem, the splitting and organization of the business logic can be a big challenge. The boundary between coupling and cohesion is sometimes difficult to grasp, and functional components give us a degree of freedom, but they also require a higher level of development.
  • There are strict rules governing the use of Hooks, and it’s easy for React developers to encounter unexpected problems if they don’t remember and practice the principles of use, and if they don’t have a solid grasp of the key principles of Hooks.

(3) Application scenarios

React Hooks are used in the following scenarios:

  • Hook basically replaces all the previous use of class components;
  • If it’s an older project, you don’t need to refactor all the code to Hooks because it’s completely backward compatible and can be used incrementally.
  • Hooks can only be used in function components, not outside of class components or function components.

Note: Hooks refer to functions like useState, useEffect, etc. Hooks are an umbrella term for these functions.

(4) Usage specifications

Hooks specification is as follows:

  • Always use Hooks at the top of the React function. Following this rule ensures that Hooks are called in the same order every time a component is rendereduseStateuseEffectWhy the state of the Hook is correctly preserved between calls;
  • Hooks are only used in React functions.

The Eslint Plugin provides eslint-plugin-React-hooks that allow us to comply with both of these specifications. Its use method is as follows:

  1. Install eslint-plugin-react-hooks:
npm install eslint-plugin-react-hooks --save-dev
Copy the code
  1. Configure an Hooks rule in esLint’s config:
{
  "plugins": [
    // ...
    "react-hooks"]."rules": {
    // ...
    "react-hooks/rules-of-hooks": "error".// Check the hooks rule
    "react-hooks/exhaustive-deps": "warn"  // Check the dependencies of effect}}Copy the code

3. UseState: Maintenance status

(1) Basic use

UseState is a Hook that allows us to add state to the React function component.

import React, { useState } from 'react';

function Example() {
  const [state, setState] = useState(0);
  const [age, setAge] = useState(18);
}

export default Example
Copy the code

When the useState method is called here, we define a state variable with an initial value of 0, which provides exactly the same functionality as this.state in class.

For the useState method:

(1) Parameter: initialization value, which can be of any type, such as number, object, array, etc. If not set to undefined;

(2) Return value: an array containing two elements (usually obtained through an array destruct assignment);

  • Element 1: the value of the current state (initialized on the first call), which is read-only and can only be modified by the methods of the second element;
  • Element 2: A function to set the state value;

In fact, hooks are JavaScript functions that help you Hook into features such as React State and lifecycle. UseState is similar to setState in class components. The difference is that there can only be one state in a class component. It is common to treat an object as a state, and then use different properties of the object to represent different states. Using useState in a function component makes it easy to create multiple states, making it more semantic.

(2) Complex variables

The state variables defined above (value type data) are relatively simple, so how do you update a complex state variable (reference type data)? Here’s an example:

import React, { useState } from 'react'

export default function ComplexHookState() {

  const [friends, setFrineds] = useState(["zhangsan"."lisi"]);
  
  function addFriend() {
    friends.push("wangwu");
    setFrineds(friends);
  }

  return (
    <div>
      <h2>Friends list:</h2>
      <ul>
        {
          friends.map((item, index) => {
            return <li key={index}>{item}</li>})}</ul>// The right way to do it<button onClick={e= >SetFrineds ([...friends, "wangwu"])}> Add friends</button>// The wrong way<button onClick={addFriend}>Add friends</button>
    </div>)}Copy the code

The state defined here is an array. If you want to modify the array, you need to redefine an array to make the modification. Changes to the original array do not cause the component to be rerendered. The React component’s update mechanism makes only a shallow comparison of state, i.e. updating a complex type of data does not rerender the component as long as its reference address does not change. Therefore, when adding data directly to the original array, there is no rerendering of the component.

In this case, a common practice is to use the extension operator (…). To reassign an array element to a new array, or to make a deep copy of the original data.

(3) Independence

When a component requires multiple states, we can use useState multiple times in the component:

const [age, setAge] = useState(17)
const [fruit, setFruit] = useState('apple')
const [todos, setTodos] = useState({text: 'learn Hooks'})
Copy the code

Here, each Hook is independent of each other. So how does React keep it independent when multiple states are present? UseState is called three times, each time it passes a value. How does React know which state this value corresponds to?

When useState is initialized, it creates two arrays of state and setters, and sets a cursor = 0. When useState is initialized, it increments the value of cursor at each state and increments the value of cursor at each state. Next, put the set function in the setters, associate the set function with the state function by cursor, and return the saved state and set functions as an array. For example, when you run setCount(15), you simply run the set function, which has a corresponding cursor value, and then change state.

(4) Disadvantages

State, while easy to maintain state, has drawbacks. Once the component has its own state, when the component is recreated, there is a process of restoring the state, which makes the component more complex.

For example, if a component wants to fetch a list of users from the server and display it, if you put the data it reads in the local state, it will need to fetch it again every time the component is used. If the state of all components is managed through some state management framework, such as Redux, then the components themselves can be stateless. Stateless components can be a more pure presentation layer, with less business logic, and thus easier to use, test, and maintain.

4. UseEffect: Perform side effects

(1) Basic use

Functional components have the ability to manipulate state through useState. Changing state needs to be done in the appropriate scenario: class components need to update state during the component lifecycle, and functional components need to simulate the lifecycle with useEffect. Currently useEffect is equivalent to the synthesis of componentDidMount, componentDidUpdate, and componentWillUnmount life cycles of class components. That is, the useEffect declared callback function is executed when the component is mounted, updated, or unmounted. In fact, the useEffect function is to execute side effects, which are the code mentioned above that is independent of the current execution result. Manually manipulating the DOM, subscribing to events, and network requests are all side effects of updating the DOM with React.

UseEffect can be used as follows:

useEffect(callBack, [])
Copy the code

UseEffect takes two arguments, a callback function and an array of dependencies. To avoid performing all useEffect callbacks for each rendering, useEffect provides a second argument, which is an array. The useEffect callback is executed only if the values in the array change during rendering.

(2) Example

Here’s an example:

import React, { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  
  useEffect(() = > {
    console.log(count + 'The value has changed.')
  }, [count])
  
  function changeTheCount () {
    setCount(count + 1)}return (
    <div>
      <div onClick={()= > changeTheCount()}>
        <p>{count}</p>
      </div>
    </div>)}export default App
Copy the code

After the above code executes, the number is clicked three times, the count value changes to 3, and the output is printed four times in the console. The first time is when the DOM is rendered for the first time, and the next three times are when the count value changes after each click, triggering the DOM to be rerendered. As you can see, effect is executed every time an element in the dependent array changes.

UseEffect has two other special uses: no dependencies and an empty array of dependencies.

1) No dependencies

For the following code, if there are no dependencies, it will execute after each render:

useEffect(() = > {
    console.log(count + 'The value has changed.')})Copy the code

2) The dependencies are empty arrays

For the following code, if the dependencies are an empty array, it will fire on the first execution, corresponding to the lifecycle of the class component, which is componentDidMount.

useEffect(() = > {
    console.log(count + 'The value has changed.')}, [])Copy the code

In addition, useEffect allows you to return a method that does some cleaning when a component is destroyed to prevent memory leaks. For example, remove listening for events. This mechanism is equivalent to componentWillUnmount in the class component lifecycle. For example, the clear timer:

const [data, setData] = useState(new Date());
useEffect(() = > {
 	const timer = setInterval(() = > {
  	 setDate(new Date());
  }, 1000);
  return () = > clearInterval(timer); } []);Copy the code

With this mechanism, side effects can be better managed to ensure consistency between components and side effects.

(3) Summary

As you can see from the above example, useEffect can be executed in four main ways:

  • Execute after each render: no second dependency parameter is provided. Such as:useEffect(() => {});
  • Component Mount executes: Provides an empty array as a dependency. Such as:useEffect(() => {}, []);
  • Execute for the first time and after a dependency change: Provide an array of dependencies. Such as:useEffect(() => {}, [deps]);
  • Component unmount: returns a callback function. Such as:useEffect() => { return () => {} }, []).

When using useEffect, note the following:

  • Dependencies in the dependency array must be used in the callback function, otherwise they are meaningless;
  • Dependencies are typically a constant array because they should be identified when the callback function is created;
  • React uses shallow comparisons every time it is executed, so be careful about the dependencies of object and array types.

5. UseCallback: Cache callback function

In the class component shouldComponentUpdate can be determined by the props and state changes before and after, to determine whether to prevent update rendering. However, using the function component form loses this feature, which means that every call to the function component will execute all of its internal logic, with a significant performance cost. The useMemo and useCallback are designed to solve this performance problem.

(1) Application scenario

In the React function component, the function is reexecuted every time the UI changes, which is quite different from a class component: a function component cannot maintain a state between each render.

Take the following counter example:

function Counter() {
 const [count, setCount] = useState(0);
 const increment = () = > setCount(count + 1);
 return <button onClick={increment}>+</button>
}
Copy the code

Since the method increment is inside the component, this causes the component to be rerendered each time you change the count, so increment cannot be reused. Each time you create a new method increment.

Not only that, but even if count does not change, the component will be re-rendered when other states within the component change, and the increment method here will be re-created accordingly. While none of this affects the normal use of the page, it adds overhead to the system and creates a new function in a way that causes the component receiving the event handler to re-render each time.

In this case, for the example above, what we want is that the corresponding increment method is recreated only if the count changes. This is useCallback.

(2) Basic use

UseCallback returns the memory value of a function, which is the same multiple times when the dependency is fixed. It can be used as follows:

useCallback(callBack, [])
Copy the code

It is used in a similar way to useEffect, where the first argument is the defined callback function and the second argument is an array of dependent variables. The defined callback function is redeclared only when one of the dependent variables changes.

Since useCallback returns a function when the dependency changes, it is not possible to determine whether the returned function has changed. Here, we use the data type Set in ES6 to determine whether the returned function has changed:

import React, { useState, useCallback } from "react";

const set = new Set(a);export default function Callback() {
  const [count, setCount] = useState(1);
  const [value, setValue] = useState(1);

  const callback = useCallback(() = > {
    console.log(count);
  }, [count]);
  set.add(callback);

  return (
    <div>
      <h1>Count: {count}</h1>
      <h1>Set.size: {set.size}</h1>
      <h1>Value: {value}</h1>
      <div>
        <button onClick={()= > setCount(count + 1)}>Count + 1</button>
        <button onClick={()= > setValue(value + 2)}>Value + 2</button>
      </div>
    </div>
  );
}

Copy the code

The running effect is shown in the figure below:As you can see, when we click the Count + 1 button, Count and set.size both increase by 1, indicating that a new callback function is generated. When you click Value + 2, only Value changes and set-.size does not change, indicating that no new callback is generated and that the old version of the cached function is returned.

Now that we know that the useCallback has these characteristics, what are the circumstances under which it can be used?

Usage scenario: A child component of a parent component, in which case, when the parent component is updated, its children will also be updated. In most cases, it is not necessary for the child component to update as the parent component updates. You can use useCallback to return the function, and then pass the function as props to the child component. In this way, the child component can avoid unnecessary updates.

import React, { useState, useCallback, useEffect } from "react";
export default function Parent() {
  const [count, setCount] = useState(1);
  const [value, setValue] = useState(1);

  const callback = useCallback(() = > {
    return count;
  }, [count]);

  return (
    <div>
      <h1>Parent: {count}</h1>
      <h1>Value: {value}</h1>
      <Child callback={callback} />
      <div>
        <button onClick={()= > setCount(count + 1)}>Count + 1</button>
        <button onClick={()= > setValue(value + 2)}>Value + 2</button>
      </div>
    </div>
  );
}

function Child({ callback }) {
  const [count, setCount] = useState(() = > callback());
  useEffect(() = > {
    setCount(callback());
  }, [callback]);

  return <h2>Child: {count}</h2>;
}
Copy the code

For this code, the result is as follows:

As you can see, when we hit Counte + 1, both Parent and Child add one; When the Value + 1 button is clicked, only the Value increases, and the data in the Child component does not change, so there is no rerendering. This avoids the extraneous actions that would cause the child component to be rerendered along with the parent.

In addition to the examples above, useCallback is used in all instances where you need to use caching functions to create functions that rely on local state or props. UseCallback is usually used to prevent subcomponents from being rendered multiple times, not to cache functions.

6. UseMemo: Indicates the cache calculation result

The real purpose of useMemo is also for performance optimization.

(1) Application scenario

Let’s look at some code:

import React, { useState } from "react";

export default function WithoutMemo() {
  const [count, setCount] = useState(1);
  const [value, setValue] = useState(1);

  function expensive() {
    console.log("compute");
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
      sum += i;
    }
    return sum;
  }

  return (
    <div>
      <h1>Count: {count}</h1>
      <h1>Value: {value}</h1>
      <h1>Expensive: {expensive()}</h1>
      <div>
        <button onClick={()= > setCount(count + 1)}>Count + 1</button>
        <button onClick={()= > setValue(value + 2)}>Value + 2</button>
      </div>
    </div>
  );
}
Copy the code

This code is simple. The expensive method is used to calculate the sum of 0 to 100 times count, which is expensive. When we click on the two buttons on the page, the expensive method is executed (which can be seen in the console), and the results are shown below:

We know that the expensive method only relies on count and only needs to be recalculated if the count changes. In this case, we can use ememo to perform the calculation of expensive only when the value of count is changed.

(2) Example

UseMemo also returns a memorized value, which is the same multiple times when the dependency is fixed. It can be used as follows:

useCallback(callBack, [])
Copy the code

It is used in a similar way to useCallback above. The first argument is a calculation function that generates the required data. Typically, it uses the array dependencies in the second argument to generate a result that will be used to render the final UI.

Let’s use useMemo to optimize the above code:

import React, { useState, useMemo } from "react";

export default function WithoutMemo() {
  const [count, setCount] = useState(1);
  const [value, setValue] = useState(1);

  const expensive = useMemo(() = > {
    console.log("Expensive");
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

  return (
    <div>
      <h1>Count: {count}</h1>
      <h1>Value: {value}</h1>
      <h1>Expensive: {expensive}</h1>
      <div>
        <button onClick={()= > setCount(count + 1)}>Count + 1</button>
        <button onClick={()= > setValue(value + 2)}>Value + 2</button>
      </div>
    </div>
  );
}

Copy the code

The running result of the code is as follows:

As you can see, the expensive method only executes when you click on the Count + 1 button; The expensive method is not executed when the Value + 1 button is clicked. Here we use useMemo to perform the expensive calculation, then return the calculated value and pass in the count as the dependent value. This only triggers the execution of expensive when the count changes, and returns the last cached value when the value changes.

Therefore, when some data is calculated from other data, it should be recalculated only if the data used, that is, the dependent data, changes. UseMemo avoids repeated calculations if the data used has not changed.

In addition, useMemo has another important use: avoiding repeated renderings of subcomponents, which is similar to useCallback above, but will not be illustrated here.

It can be seen that useMemo and useCallback are very similar, and they can be converted into each other: useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

7. UseRef: share data

While a function component may seem intuitive, it has so far lacked an important capability relative to a class component: the ability to share data across multiple renderings. In class functions, we can store data state through object properties. But in function components, there is no such space to hold data. Therefore, useRef provides such functionality.

UseRef can be used as follows:

const myRefContainer = useRef(initialValue);
Copy the code

UseRef returns a mutable ref object whose.current property is initialized to the passed parameter. The ref object returned remains the same throughout the lifetime of the component, which means that the same REF object is returned each time the function component is rerendered.

So in practice, what’s useRef for? There are two main application scenarios:

(1) DOM binding

Here’s a simple scenario: When you initialize a page, make one of the input boxes in the page automatically focus. This can be done using a class component:

class InputFocus extends React.Component {
  refInput = React.createRef();
  componentDidMount() {
    this.refInput.current && this.refInput.current.focus();
  }
  render() {
    return <input ref={this.refInput} />; }}Copy the code

If you want to do that in the function component, you can use useRef to do it:

function InputFocus() {
  const refInput = React.useRef(null);
  React.useEffect(() = >{ refInput.current && refInput.current.focus(); } []);return <input ref={refInput} />;
}
Copy the code

Here, we bind refInput to the input field, and when we refresh the page, the mouse is still focused on the input field.

(2) Save data

In a scenario where we have a timer component that can start and pause, we can use setInterval to time, and in order to pause, we need to get a reference to the timer and clear the timer when we pause. The timer reference can then be stored in useRef because it can store data across renderings, as follows:

import React, { useState, useCallback, useRef } from "react";

export default function Timer() {
  const [time, setTime] = useState(0);
  const timer = useRef(null);

  const handleStart = useCallback(() = > {
    timer.current = window.setInterval(() = > {
      setTime((time) = > time + 1);
    }, 100); } []);const handlePause = useCallback(() = > {
    window.clearInterval(timer.current);
    timer.current = null; } []);return (
    <div>
      <p>{time / 10} seconds</p>
      <button onClick={handleStart}>start</button>
      <button onClick={handlePause}>suspended</button>
    </div>
  );
}
Copy the code

As you can see, useRef is used to create a reference to save the setInterval so that the timer can be cleared when the pause is clicked for the purpose of pausing. At the same time, the data stored with useRef is generally independent of the RENDERING of the UI. When the ref value changes, it does not trigger a rerendering of the component, which is the difference between useRef and useState.

8. UseContext: global status management

We know that React provides Context to manage global state. When we create a Context on a component, all components in the component tree can access and modify that Context. This property applies to class components. A similar attribute is provided in React Hooks, which are called useContext.

In simple terms, useContext creates a context object and exposes providers and consumers, and all sub-components within the context, to access the data within the context.

So what context does is it creates a context object, and it exposes the provider and the consumer, and all the child components within that context can access the data within that context without using props. Simply put, a context is a technique for providing globally shared data to the tree of components it contains.

First, create a context that provides two different page theme styles:

const themes = {
  light: {
    foreground: "# 000000".background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff".background: "# 222222"}};const ThemeContext = React.createContext(themes.light)
Copy the code

Next, create a Toolbar component that contains a ThemedButton component. Forget the logic of the ThemedButton component here:

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

In this case, the data needs to be provided by the provider, which is usually located at a higher level, directly within the App. Themecontext. Provider is the Provider here, and the value it receives is the context object it wants to provide:

function App() {
  return (
    <ThemeContext.Provider value={themes.light}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}
Copy the code

The consumer then retrives the data, which is used in the ThemedButton component:

function ThemedButton(props) {
  const theme = useContext(ThemeContext);
  const [themes, setthemes] = useState(theme.dark);

  return (
    <div>
      <div
        style={{
          width: "100px",
          height: "100px",
          background: themes.background.color: themes.foreground
        }}
      ></div>
      <button onClick={()= > setthemes(theme.light)}>Light</button>
      <button onClick={()= > setthemes(theme.dark)}>Dark</button>
    </div>
  );
}
Copy the code

At this point, the entire example ends. Here is the overall code:

import React, { useContext, useState } from "react";

const themes = {
  light: {
    foreground: "# 000000".background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff".background: "# 222222"}};const ThemeContext = React.createContext(themes.light);

function ThemedButton(props) {
  const theme = useContext(ThemeContext);
  const [themes, setthemes] = useState(theme.dark);

  return (
    <div>
      <div
        style={{
          width: "100px",
          height: "100px",
          background: themes.background.color: themes.foreground
        }}
      ></div>
      <button onClick={()= > setthemes(theme.light)}>Light</button>&nbsp;&nbsp;
      <button onClick={()= > setthemes(theme.dark)}>Dark</button>
    </div>
  );
}

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

export default function App() {
  return (
    <ThemeContext.Provider value={themes}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}
Copy the code

Using useContext to obtain the themes data in the top-level context, the running effect is as follows:In this case, useContext looks like global data, so why design such a complex mechanism instead of using a global variable to hold the data? It’s all about being able to bind data. When useContext data changes, components that use that data are automatically refreshed. But if you don’t have useContext, and you use a simple global variable, it’s hard to switch data.

In effect, a Context provides a mechanism for a variable, and a global variable means:

  • Can make debugging difficult because it is difficult to track exactly how changes to a Context are made.
  • Make reuse of components difficult, because if a component uses a Context, it must make sure that the Context Provider is in the parent component’s path where it is used.

Therefore, useContext is a double-edged sword, or should be used according to the actual business scenario.

UseReducer: An alternative to useState

An API useReducer is provided in Hooks, which is an alternative to useState.

First, let’s look at the syntax of useReducer:

const [state, dispatch] = useReducer((state, action) = > {
    // Return a newState based on the type of action assigned
}, initialArg, init)
Copy the code

UseReducer receives the Reducer function as an argument. The Reducer receives two parameters, one is state and the other is action, and then returns a state and dispatch. State is the value of the returned state. Dispatch is a function that can publish an event to update state.

Since it is an alternative to useState, let’s see how it differs from useState:

import React, { useState } from 'react'
function App() {
  const [count, setCount] = useState(0) 
    
  return (
    <div>
      <h1>you click {count} times</h1>
      <input type="button" onClick={()= > setCount(count + 1)} value="click me" />
    </div>)}export default App
Copy the code

2) Use useReducer to implement:

import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    default:
      throw new Error();
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h1>you click {state.count} times</h1>
      <input
        type="button"
        onClick={()= > dispatch({ type: "increment" })}
        value="click me"
      />
    </div>
  );
}
export default App;

Copy the code

Compared to useState, the rewritten code becomes longer. The execution process is as follows:

  • Click on theclick meButton is triggeredclickEvents;
  • clickIn the case ofdispatchThe function,dispatchPublish event notificationreducerI performedincrementActions;
  • reducerWill go to findincrementTo return a newstateValue.

The following isuseReducerThe entire implementation process of:

In fact, useReducer is executed in three steps:

  • Step 1: The event occurs;
  • Step 2: Dispatch (action);
  • Step 3: Reducer returns a new state based on action.type.

Although the code gets longer when using useReducer, it seems to be easier to understand, which is one of the advantages of useReducer. UseReducer has the following advantages:

  • Better readability;
  • reducerSo let’s separate what we do from how we do it. Did we click in the demo aboveclick meButton, what we need to do is to initiate the operation of adding 1, and how to implement the operation of adding 1 is maintained in the Reducer. Component only needs to consider how to make our code as clear as user behavior;
  • stateAll the processing is concentrated inreducerforstateChange is more controlled and easier to reusestateLogical change code, especially forstateChanging very complex scenes.

UseReducer can be used preferentially when encountering the following scenarios:

  • stateThe changes are complex, and often a single operation requires many changesstate;
  • To modify some state in deep subcomponents;
  • The application is large and the UI and business need to be maintained separately.

Finally: So far, this article has only covered the simple use of React Hooks. I am deeply studying Hooks recently, looking forward to the next article!