4
头图

write in front

React Hooks is a brand new set of mechanisms introduced by the React team in version 16.8 two years ago. As the most mainstream front-end framework, React's API is very stable. The release of this update has shocked many front-end bigwigs who are afraid of new wheels. After all, every update is a high-cost study. What?

The answer is easy to use, for React developers, it's just one more choice. In the past, the development method was based on Class components, while hooks were based on function components, which means that the two development methods of can coexist and , and the new code can be implemented in the way of Hooks according to the specific situation. This article mainly introduces the advantages of Hooks and several commonly used hook functions .

Advantages of Hooks

1. Inadequacy of class components

  • has more codes than :

    Compared with the way of writing function components, the code amount of using class components is slightly more, which is the most intuitive feeling.

  • this points to :

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

  • tends to be complex and difficult to maintain :

    In the high version of React, some life cycle functions have been updated. Because these functions are decoupled from each other, it is easy to cause scattered writing, missing key logic and redundant logic, which makes it difficult to debug later. On the contrary, hooks can put key logic together, which is not so separate, and it is easier to understand when debugging.

  • state logic is difficult to reuse :

    It is difficult to reuse state logic between components. It may be necessary to use render props (rendering properties) or HOC (higher-order components), but both render props and higher-order components will be outside the original component. Wrap a layer of parent container (usually a div element), resulting in hierarchical redundancy.

2. Benefits of Hooks

  • logic multiplexing

    Reusing state logic before components often requires the help of complex design patterns such as high-level components. These high-level components will generate redundant component nodes, making debugging difficult. Let's use a demo to compare the two implementations.

Class

In the class component scenario, a higher-order component is defined, which is responsible for monitoring the window size change and passing the changed value to the next component as props.

const useWindowSize = 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;
};

Next, a function such as useWindowSize can be called in a custom component to generate a new component with a size property, for example:

class MyComponent extends React.Component{
  render() {
    const { size } = this.props;
    if (size === "small") return <SmallComponent />;
    else return <LargeComponent />;
  }
}
// 使用 useWindowSize 产生高阶组件,用于产生 size 属性传递给真正的业务组件
export default useWindowSize(MyComponent); 

Let's see 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;
};

use:

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

From the above example, the window size is encapsulated by Hooks, thereby turning it into a bindable data source. In this way, when the window size changes, the components using this Hook will be re-rendered. And the code is more concise and intuitive, no additional component nodes are generated, and it is not so redundant.

  • Business code is more aggregated

The following is an example of the most common timer.

class
let timer = null
componentDidMount() {
    timer = setInterval(() => {
        // ...
    }, 1000)
}
// ...
componentWillUnmount() {
    if (timer) clearInterval(timer)
}
Hooks
useEffect(() => {
    let timer = setInterval(() => {
        // ...
    }, 1000)
    return () => {
        if (timer) clearInterval(timer)
    }
}, [//...])

The way Hooks are implemented can make the code more centralized and the logic clearer.

  • written concisely

This is not an example. It can be understood literally. Using function components can indeed reduce a lot of code. I understand everything, hee hee~

The role of several built-in Hooks and the use of thinking

useState : Let functional components have the ability to maintain state

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

Advantages:

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

Cons:

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

Usage:

  1. The parameter initialState of useState(initialState) is the initial value of the created state.
It can be of any type, such as numbers, objects, arrays, etc.
  1. The return value of useState() is an array with two elements. The first array element is used to read the value of the state, and the second is used to set the value of this state.
The thing to note here is that the state variable (count in the example) is read-only, so we have to set its value through the second array element setCount.
  1. If we want to create multiple state , then we need to call useState multiple times.

What value should be stored in state?

Generally speaking, a principle we want to follow is: state Do not store the value that can be calculated.

  • The value passed from props. Sometimes the value passed by props cannot be used directly, but must be displayed on the UI after a certain calculation, such as sorting. Then what we have to do is to reorder it every time we use it, or use some cache mechanism instead of putting the result directly into the state.
  • The value read from the URL. For example, sometimes it is necessary to read the parameters in the URL as part of the state of the component. Then we can read it from the URL every time we need it, instead of reading it directly into the state.
  • Values read from cookies, localStorage. Generally speaking, it is also read directly every time it is used, rather than read it and put it in the state.

useEffect: execute side effects

useEffect(fn, deps);

useEffect , as the name suggests, is used to perform a side effect.

What are side effects?

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

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

Corresponding to the Class component, then useEffect covers the three life cycle methods of ComponentDidMount, componentDidUpdate and componentWillUnmount. But if you are accustomed to using Class components, don't follow the method of mapping useEffect to one or several life cycles. You just need to remember that useEffect is to judge the dependencies and execute them every time the component is rendered.

useEffect also has two special uses: no dependencies, and dependencies as an empty array. Let's analyze it in detail.

  1. Without dependencies, it will be re-executed after every render. For example:
useEffect(() => {
  // 每次 render 完一定执行
  console.log('渲染...........');
});
  1. An empty array as a dependency is only triggered when it is executed for the first time, and the corresponding Class component is componentDidMount. For example:
useEffect(() => {
  // 组件首次渲染时执行,等价于 class 组件中的 componentDidMount
  console.log('did mount........');
}, []);

Summary usage:

To sum up, useEffect allows us to execute a callback function to produce side effects at the following four occasions:

  1. Execute after each render: No second dependency parameter is provided.
For example useEffect(() => {}).
  1. Executed only after the first render: Provide an empty array as a dependency.
For example useEffect(() => {}, []).
  1. Executed the first time and after a dependency change: Provides an array of dependencies.
For example useEffect(() => {}, [deps]).
  1. Executed after the component is unmount: returns a callback function.
For example useEffect() => { return () => {} }, []).

useCallback: cache callback function

useCallback(fn, deps)

Why use useCallback?

In the React function component, every change of UI is done by re-executing the entire function, which is very different from a traditional Class component: A state is maintained between renders.

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

Think about this process. Each time the state of the component changes, the function component will actually re-execute . On each execution, a new event handler function handleIncrement is actually created.

This also means that even if the count does not change, when the function component is re-rendered due to other state changes (the function component is re-executed), this writing method will create a new function each time. Creating a new event handler function, although it does not affect the correctness of the result, is actually unnecessary. Because doing so not only increases the overhead of the system, but more importantly: Every time a new function is created, the component that receives the event handler needs to be re- .

For example, the button component in this example receives handleIncrement as a property. If it's a new one each time, then React will think the component's props have changed and must re-render. Therefore, what we need to do is: Only when count changes, we need to reset a callback function . And this is the role of the useCallback Hook.

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

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = useCallback(
    () => setCount(count + 1),
    [count], // 只有当 count 发生变化时,才会重新创建回调函数
  );
  return <button onClick={handleIncrement}>+</button>
}

useMemo: cache the result of the calculation

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

Here fn is a calculation function that produces the required data. Generally speaking, the Fn use deps variables declared to produce a result, used to render a final the UI .

This scenario should be easy to understand: if a certain data is calculated from other data, then only when the data used, that is, the dependent data, should be recalculated.

avoid double counting

Through the Hook of useMemo , you can avoid repeated calculations when the data used has not changed. Although the example shows a very simple scene, if it is a complex calculation, it will be very helpful for to improve the performance of .

for example:

const calc = (a, b) => {
    // 假设这里做了复杂的计算,暂时用次幂模拟
    return a ** b;
}
const MyComponent = (props) => {
    const {a, b} = props;
    const c = calc(a, b);
    return <div>c: {c}</div>;
}

If the calc calculation takes 1000ms, then each rendering has to wait so long, how to optimize it?

When the values of a and b are unchanged, the obtained c is definitely the same.

So we can use useMemo to cache the value to avoid recomputing the same result.

const calc = (a, b) => {
    // 假设这里做了复杂的计算,暂时用次幂模拟
    return a ** b;
}
const MyComponent = (props) => {
    const {a, b} = props;
    // 缓存
    const c = React.useMemo(() => calc(a, b), [a, b]);
    return <div>c: {c}</div>;
}

The function of useCallback can actually be implemented with useMemo:

 const myEventHandler = useMemo(() => {
   // 返回一个函数作为缓存结果
   return () => {
     // 在这里进行事件处理
   }
 }, [dep1, dep2]);

To summarize:

felt this feeling. In fact, a hook is to establish a relationship that binds a result to the dependent data. This result needs to be retrieved only when the dependency changes.

useRef: share data between multiple renders

const myRefContainer =useRef(initialValue);

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

Important features of useRef

1. stores cross-rendered data

Use useRef saved data and rendering the UI is generally irrelevant, so when ref value is changed, it will not trigger re-rendering component, which is useRef different from useState place.

Example:

 const [time, setTime] = useState(0);
 // 定义 timer 这样一个容器用于在跨组件渲染之间保存一个变量 
 const timer = useRef(null);

  const handleStart = useCallback(() => {
    // 使用 current 属性设置 ref 的值
    timer.current = window.setInterval(() => { setTime((time) => time + 1); }, 100);
  }, []);

2. holds a reference a DOM node

In some scenarios, we must obtain the reference of the real DOM node, so by combining React's ref attribute and useRef Hook, we can obtain the real DOM node.

React official example:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // current 属性指向了真实的 input 这个 DOM 节点,从而可以调用 focus 方法
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

understand:

You can see that the ref attribute provides the ability to obtain a DOM node, and use useRef to save the application of this node. In this way, once the input node is rendered on the interface, we can access the real DOM node instance through inputEl.current

useContext: defines global state

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 be done between parent and child components.

Then the question arises: how to share data across layers, or between components at the same layer? This involves a new proposition: Global State Management .

Solution provided by react: Context mechanism.

Specific principle:

React provides a mechanism such as Context. enables all components to create a Context on the component tree starting from a component. In this way, all components on this component tree can access and modify this Context .

Then in the function component, we can use a Hook such as useContext to manage the Context.

Use: (official example is used here)

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};
// 创建一个 Theme 的 Context

const ThemeContext = React.createContext(themes.light);
function App() {
  // 整个应用使用 ThemeContext.Provider 作为根组件
  return (
    // 使用 themes.dark 作为当前 Context 
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 在 Toolbar 组件中使用一个会使用 Theme 的 Button
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

// 在 Theme Button 中使用 useContext 来获取当前的主题
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{
      background: theme.background,
      color: theme.foreground
    }}>
      I am styled by theme context!
    </button>
  );
}

advantage:

Context provides a mechanism to facilitate sharing data among multiple components.

shortcoming:

Context is equivalent to providing a mechanism to define global variables in the React world, and global variables mean two things:

1. makes debugging difficult because it is difficult to trace how a change in a Context .

2. make it difficult to reuse components, a component because if you use a Context , it must ensure that the place must have been used in this Context of Provider the parent component on the path.

Practical application scenarios

Due to the above shortcomings, in the development of React, in addition to the variables that need to be set globally at a glance, such as Theme and Language, we rarely use Context to share too much data. It needs to be emphasized again and again that Context provides a powerful mechanism that enables React applications to define global reactive data.

In addition, many state management frameworks, such as Redux, use the Context mechanism to provide a more controllable state management mechanism between components. Therefore, understanding the mechanism of Context can also allow us to better understand the principle of the implementation of frameworks such as Redux.

At last

I feel like this time there is not much content. In fact, knowing the two core Hooks, useState and useEffect, can basically 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 also a few more marginal hooks that will not be written here, and those who are interested can move to the official documentation to have a look.

It is not easy to code words, and it is also hard for the big guys to guide and communicate~

team

TNTWeb - Tencent News front-end team, TNTWeb is committed to the exploration of cutting-edge technologies in the industry and the improvement of team members' personal abilities. Sorted out the latest high-quality content in the field of small programs and web front-end technology for front-end developers, updated weekly ✨, welcome star, github address: https://github.com/tnfe/TNT-Weekly

image.png


TNTWEB
3.8k 声望8.5k 粉丝

腾讯新闻前端团队