Writing in the front

React Hooks are a new set of mechanisms introduced by the React team two years ago in version 16.8. The React API is very stable, and this update is a shock to many front-end developers who are afraid of the new wheels. After all, every update is a costly learning process.

The answer works, but for React developers, there’s just one more option. In the past, hooks were based on Class components and hooks were based on function components, which means that the two approaches can coexist and new code can be implemented using hooks on a case by case basis. This article will focus on the advantages of Hooks and several commonly used hook functions.

The advantage of Hooks

1. Deficiencies of class components

  • Large amount of code:

    Using class components requires slightly more code than writing function components, which is the most intuitive feeling.

  • This point:

    Class components always need to consider this pointing, while function components can be ignored.

  • Tends to be complex and difficult to maintain:

    In newer versions of React, some lifecycle functions are updated. These functions are decoupled from each other, resulting in scattered and uncentralized writing, missing key logic, and redundant logic, which makes debugging difficult. In contrast, hooks keep key logic together in a way that is less fragmented and easier to debug.

  • State logic is hard to reuse:

    It is difficult to reuse state logic between components, perhaps using render props or HOC, but both render properties and higher-order components wrap a parent container (usually div elements) around the original component, resulting in hierarchical redundancy.

2. The benefit of Hooks

  • Logic reuse

    Reusing state logic before components often requires complex design patterns such as higher-order components that create redundant component nodes and make debugging difficult. Here is a demo to compare the two implementations.

Class

In the Class component scenario, a higher-order component is defined that listens for window size changes and passes the changed value as props to the next component.

Const useWindowSize = Component => {// Generate a high-order Component HOC, Class HOC extends react. PureComponent {constructor(props) {super(props); this.state = { size: this.getSize() }; } componentDidMount() { window.addEventListener("resize", this.handleResize); } componentWillUnmount() { 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 HOC; };Copy the code

You can then call a function like useWindowSize in a custom component to generate a new component with the size property, for example:

class MyComponent extends React.Component{ render() { const { size } = this.props; if (size === "small") return <SmallComponent />; else return <LargeComponent />; Export default useWindowSize(MyComponent);Copy the code

Let’s look at how Hooks are implemented

Hooks

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; };Copy the code

Use:

const Demo = () => {
  const size = useWindowSize();
  if (size === "small") return <SmallComponent />;
  else return <LargeComponent />;
};
Copy the code

From the above example, the window size is wrapped in Hooks to make it a bindable data source. This way, when the window size changes, components that use this Hook will all be re-rendered. The code is cleaner and more intuitive, with no additional component nodes and less redundancy.

  • Business code is more aggregated

Here is an example of one of the most common timers.

class

let timer = null componentDidMount() { timer = setInterval(() => { // ... }, 1000)} //... componentWillUnmount() { if (timer) clearInterval(timer) }Copy the code

Hooks

useEffect(() => { let timer = setInterval(() => { // ... }, 1000) return () => { if (timer) clearInterval(timer) } }, [//...] )Copy the code

Hooks are implemented in a way that makes code more focused and logic clearer.

  • Writing concise

Using a function component can save a lot of code

Several built-in Hooks function as well as use thinking

UseState: Enables function components to maintain state

const[count, setCount]=useState(0);
Copy the code

Advantages:

Give function components the ability to maintain state that is shared between multiple renderings of a function component. Easy to maintain status.

Disadvantages:

Once a component has its own state, it means that if the component is recreated, it needs to have a process to restore the state, which usually makes the component more complex.

Usage:

  1. The initialState parameter of useState(initialState) is the initial value to create the state.

It can be of any type, such as number, object, array, and so on.

  1. The return value of useState() is a two-element array. The first element reads the value of state, and the second element sets the value of state.

Note here that the state variable (count in the example) is read-only, so we must set its value with the second array element, setCount.

  1. If we were to create multiple states, we would need to call useState multiple times.

What values should be stored in state?

In general, one rule to follow is: Do not store values in state that can be computed.

  • The value passed from props. Sometimes the values passed by props cannot be used directly. Instead, they need to be computed and displayed on the UI, such as sorting. So what we’re going to do is we’re going to reorder it every time we use it, or we’re going to use some cache mechanism, instead of putting the result directly into state.
  • The value read from the URL. For example, you sometimes need to read parameters in the URL as part of the component’s state. So we can read it from the URL every time we need it, instead of just reading it and putting it in state.
  • The value read from cookie, localStorage. In general, it is also read directly every time it is used, rather than read and put into state.

UseEffect: Indicates execution side effects

useEffect(fn, deps);

UseEffect, as the name implies, is used to perform a side effect.

What are side effects?

Typically, a side effect is a piece of code that has nothing to do with the result of the current execution. For example, to modify a variable outside of a function, to make a request, and so on.

That is, during the execution of a function component, the code in useEffect does not affect the rendered UI.

For a Class component, useEffect covers the ComponentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. However, if you are used to using Class components, don’t use effects to one or more lifecycling. Just remember that useEffect is a dependency determination and execution every time the component is rendered.

UseEffect also has two special uses: no dependencies, and dependencies as an empty array. Let’s look at it in detail.

  1. Without dependencies, the execution is reexecuted after each render. Such as:
UseEffect (() = > {/ / finish each render must perform the console log (' rendering... '); });Copy the code
  1. An empty array, as a dependency, only fires on the first execution, corresponding to the Class component componentDidMount. Such as:
UseEffect (() => {// executed when the component is first rendered, equivalent to componentDidMount console.log('did mount........ 'in the class component '); } []);Copy the code

Summary usage:

To summarize, useEffect allows us to execute a callback with side effects in four ways:

  1. Execute after each render: no second dependency argument is provided.

For example useEffect(() => {}).

  1. Execute only after the first render: provide an empty array as a dependency.

For example useEffect(() => {}, []).

  1. First time and after a dependency change: Provides an array of dependencies.

For example useEffect(() => {}, [deps]).

  1. Component executed after unmount: Returns a callback function.

UseEffect () => {return () => {}}, []).

UseCallback: Cache callback function

useCallback(fn, deps)

Why use useCallback?

In the React function component, each UI change is performed by re-executing the entire function, which is very different from traditional Class components: there is no direct way to maintain a state between multiple renders.

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

Think about the process. Each time the component state changes, the function component actually executes again. On each execution, a new event handler, handleIncrement, is actually created.

This also means that a new function is created each time the function component is re-rendered because of other state changes (the function component is re-executed), even if the count does not change. Creating a new event handler does not affect the correctness of the result, but it is not necessary. Not only does this add overhead to the system, but more importantly, each time a new function is created in such a way that the component that receives the event handler needs to be re-rendered.

The Button component in this example, for example, receives handleIncrement as a property. If it is a new one each time, the React will assume that the props of the component have changed and must be rerendered. Therefore, all we need to do is respecify a callback function only when count changes. And that’s what the useCallback Hook does.

import React, { useState, useCallback } from 'react'; function Counter() { const [count, setCount] = useState(0); Const handleIncrement = useCallback(() => setCount(count + 1), [count], const handleIncrement = useCallback(() => setCount(count + 1), return <button onClick={handleIncrement}>+</button> }Copy the code

UseMemo: cache calculation results

useMemo(fn, deps);
Copy the code

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

Here fn is a computational function that produces the required data. Typically, FN uses some variables declared in DEPS to generate a result that is used to render the final UI.

This scenario should be easy to understand: if one piece of data is computed from other data, then recalculation should only be required if the data used, the dependent data, changes.

Avoid double counting

Through the Hook of useMemo, you can avoid the double calculation when the data used does not change. Although the example shows a simple scenario, if it is a complex calculation, it can go a long way towards improving performance.

Here’s an example:

Const calc = (a, b) => {const calc = (a, b) => { } const MyComponent = (props) => { const {a, b} = props; const c = calc(a, b); return <div>c: {c}</div>; }Copy the code

If the CALC calculation takes 1000ms, how can you optimize if you have to wait that long for each render?

If a and B are constant, c is going to be the same.

So we can use useMemo to cache the values and avoid double-counting the same results.

Const calc = (a, b) => {const calc = (a, b) => { } const MyComponent = (props) => { const {a, b} = props; / / cache the const c = React. UseMemo (() = > calc (a, b), (a, b)); return <div>c: {c}</div>; }Copy the code

The functions of useCallback can be implemented with useMemo:

Const myEventHandler = useMemo(() => {return () => {// handle events here}}, [dep1, dep2]);Copy the code

To summarize:

A hook is a relationship that binds a result to a dependent data. The result needs to be retrieved only if the dependency changes.

UseRef: Share data between multiple renders

const myRefContainer =useRef(initialValue);
Copy the code

We can think of useRef as a container space created outside of the function component. On this container, we can set a value with a unique current genus, thus sharing the value between multiple renders of the function component.

Important features of useRef

1. Store data across renders

The data stored with useRef is generally independent of the RENDERING of the UI, so a change in the ref value does not trigger a re-rendering of the component, which is what differentiates useRef from useState.

For example:

const [time, setTime] = useState(0); // Define a container for holding a variable between component renderings const timer = useRef(null); Const handleStart = useCallback(() => {// Use the current attribute to set the ref value timer.current = window.setinterval (() => { setTime((time) => time + 1); }, 100); } []);Copy the code

2. Save a reference to a DOM node

In some scenarios, we need to get a reference to a real DOM node, so by combining the React ref attribute with the useRef Hook, we can get a real DOM node and operate on it.

React example:

function TextInputWithFocusButton() { const inputEl = useRef(null); Const onButtonClick = () => {// The current attribute points to the actual INPUT DOM node, so you can call the focus method inputel.current.focus (); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }Copy the code

To understand:

As you can see, the ref attribute provides the ability to get the DOM node and save the application of the node using useRef. This way, once the input node is rendered to the interface, we can access the actual DOM node instance via inputel.current

UseContext: defines the global status

Why use useContext?

There is only one way to pass state between React components, and that is through props. Disadvantages: This transitive relationship can only occur between parent and child components.

The question then arises: how do you share data across layers, or between components at the same layer? This leads to a new proposition: global state management.

React provides the following solution: Context mechanism.

Specific principles:

React provides a Context mechanism that allows all components to create a Context in the tree where a component starts. So all the components in the component tree can access and modify the Context.

So inside the function component, we can use a Hook like useContext to manage the Context.

Use :(official examples are used here)

const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "# 222222"}}; // Create a ThemeContext const ThemeContext = react.createcontext (themes.light); Function App() {// Use themecontext.provider as the root component return (// use themes.dark as the current Context < themecontext.provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } // Use a Button 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 the code

Advantages:

Context provides a convenient mechanism for sharing data between multiple components.

Disadvantages:

Context provides a mechanism for defining global variables in the React world. Global variables mean two things:

1. It makes debugging difficult because it’s hard to track exactly how changes to a Context are generated.

2. It makes it difficult to reuse components because a component that uses a Context must ensure that the Context’s Provider is in the path of its parent.

Actual Application Scenarios

In React development, we rarely use Context to share much data, except for obvious variables such as Theme and Language that need to be set globally. Again, Context provides more of a powerful mechanism for React applications to define globally responsive data.

In addition, many state management frameworks, such as Redux, use the Context mechanism to provide a more controlled state management mechanism between components. Therefore, understanding the mechanics of Context also gives us a better understanding of how frameworks like Redux work.

The last

It feels like there’s nothing more and nothing less. Now that you have learned useState and useEffect Hooks, you can complete the development of most React functions.

UseCallback, useMemo, useRef and useContext. These hooks are designed to solve specific problems encountered in functional components.

There are a few more edge hooks will not be written here, interested in the big guy can move to the official document to have a look.

Code word is not easy, but also hard to guide the communication ~

team

TNTWeb – Tencent news front end team, TNTWeb is committed to the exploration of cutting-edge technology in the industry and the improvement of team members’ personal ability. For front-end developers to organize the applets and the latest quality content in the field of web front-end technology, weekly update ✨, welcome star, github address: github.com/tnfe/TNT-We…