Why is hooks recommended and what are its benefits?

1. What problem is hook used to solve

In a word, Hook is used to make us better reuse React state logic code. Note that this is not a template code, which can be reused with components; The simple state logic code can not be reused with components

Some students may say that ordinary functions can realize logical code reuse?
The answer is: ordinary functions can reuse logic code, but they can't reuse logic code with state.

What is the state of React?

for instance:

const Comp = () => {
  const [id, setId] = useState(0)
  const [assets, setAssets] = useState()

    useEffect(() => {
      fetch(`https://google.com?id=${id}`).then(async response => {
       const data = await response.json();
        if (response.ok) {
          setAssets(data)
        } else {
          return Promise.reject(data);
        }
        })
      }, [])

  return <div>{assets.map(a => a.name)}</div>
}

The id and assets in this are states. Its feature is that it is defined by a specific API(useState), and when it changes, the component will respond accordingly (such as re render)

const sum = (a, b) => a + b

This ordinary function has no state. No matter how the return value of sum changes, it will not make any component render again

The React team attaches great importance to the reusability of React state code. Since the creation of React, they have been optimizing the solution of code reuse. They have probably experienced: Mixin → HOC → Render Props, and now Hook

Therefore, Hook is not the product of a slap on the forehead. You can't fully understand Hook without understanding this idea

Next, I will send a lot of code screenshots. In order to keep up with the pace, you just need to browse these code screenshots in combination with my topic, and you don't need to pay attention to too many details

1. Mixin

Mixin is the earliest React code reuse scheme

var SubscriptionMixin = {
  getInitialState: function() {
    return {
      comments: DataSource.getComments()
    };
  },

  componentDidMount: function() {
    DataSource.addChangeListener(this.handleChange);
  },

  componentWillUnmount: function() {
    DataSource.removeChangeListener(this.handleChange);
  },

  handleChange: function() {
    this.setState({
      comments: DataSource.getComments()
    });
  }
};

var CommentList = React.createClass({
  mixins: [SubscriptionMixin],

  render: function() {
    // Reading comments from state managed by mixin.
    var comments = this.state.comments;
    return (
      <div>
        {comments.map(function(comment) {
          return <Comment comment={comment} key={comment.id} />
        })}
      </div>
    )
  }
});

Its advantage is simple and crude, in line with intuition, and indeed plays the role of reusing code; However, the disadvantages are also obvious. Implicit dependency, name conflict, and class component are not supported, which is difficult to maintain. In short, it has been completely eliminated

Let's see the official judgment: https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html

2. HOC (higher-order component)

In 2015, after the React team sentenced Mixin to death, it recommended that you use the HOC mode, which adopts the decorator mode in the design mode

function withWindowWidth(BaseComponent) {
  class DerivedClass extends React.Component {
    state = {
      windowWidth: window.innerWidth,
    }

    onResize = () => {
      this.setState({
        windowWidth: window.innerWidth,
      })
    }

    componentDidMount() {
      window.addEventListener('resize', this.onResize)
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.onResize);
    }

    render() {
      return <BaseComponent {...this.props} {...this.state}/>
    }
  }
  return DerivedClass;
}

const MyComponent = (props) => {
  return <div>Window width is: {props.windowWidth}</div>
};

The classic separation of container component and display component starts from here

// components/AddTodo.js

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'

class AddTodo extends React.Component {
  // ...

  handleAddTodo = () => {
    // dispatches actions to add todo
    this.props.addTodo(this.state.input)

    // sets state back to empty string
    this.setState({ input: '' })
  }

  render() {
    return (
      <div>
        <input
          onChange={(e) => this.updateInput(e.target.value)}
          value={this.state.input}
        />
        <button className="add-todo" onClick={this.handleAddTodo}>
          Add Todo
        </button>
      </div>
    )
  }
}

export default connect(null, { addTodo })(AddTodo)

The classic separation of container component and display component starts from here

A classic use case of HOC is the connect method in react redux. The addtodo component is like an innocent white rabbit. Its addtodo method is injected into it by the connect method

It can work in any component, including Class Component

The separation principle of container components and presentation components advocated by it achieves: separation of concerns

Disadvantages:

  • It is not intuitive and difficult to read
  • Name conflict
  • Component nesting

3. Render Props

In 2017, render props became popular

class WindowWidth extends React.Component {
  propTypes = {
    children: PropTypes.func.isRequired
  }

  state = {
    windowWidth: window.innerWidth,
  }

  onResize = () => {
    this.setState({
      windowWidth: window.innerWidth,
    })
  }

  componentDidMount() {
    window.addEventListener('resize', this.onResize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onResize);
  }

  render() {
    return this.props.children(this.state.windowWidth);
  }
}

const MyComponent = () => {
  return (
    <WindowWidth>
      {width => <div>Window width is: {width}</div>}
    </WindowWidth>
  )
}

In 2017, render props became popular. Its disadvantage is that it is difficult to read and understand. The following is a use case

4. Hook

You can see the above two methods. What is their ultimate purpose? It is to inject the state of windowWidth into components. For this purpose, they use complex and non intuitive methods. Is there any way to be intuitive? That's our Hook,

It's the same requirement as above. I'll implement it again with Hook

import { useState, useEffect } from "react";

const useWindowsWidth = () => {
  const [isScreenSmall, setIsScreenSmall] = useState(false);

  let checkScreenSize = () => {
    setIsScreenSmall(window.innerWidth < 600);
  };
  useEffect(() => {
    checkScreenSize();
    window.addEventListener("resize", checkScreenSize);

    return () => window.removeEventListener("resize", checkScreenSize);
  }, []);

  return isScreenSmall;
};

export default useWindowsWidth;
import React from 'react'
import useWindowWidth from './useWindowWidth.js'

const MyComponent = () => {
  const onSmallScreen = useWindowWidth();

  return (
    // Return some elements
  )
}

Advantages of Hook over other schemes:

  • Extracting logic is very easy
  • Very easy to combine
  • Very readable
  • No name conflicts

There are two kinds of hooks: React's own Hook and custom Hook. Custom hooks are a combination of their own hooks, so let's talk about their own hooks first

Detailed explanation of React's own Hook

useState

useState is the most basic Hook. Why do you say so, because it is a state producer. What is the difference between the state it produces and ordinary variables?

const [count, setCount] = useState(initialCount);
const count = 1
const setCount = (value) => count = value

What's the difference between the two? The difference is that the first useState generates a state. When the state changes, the component will render again. It is responsive; The second is a common variable, which changes nothing. Does it sound a little pathetic

useEffect

With the state generated by useState, we can write some simple components, such as

const Count = () => {
  const [count, setCount] = useState(0)
  const add = setCount(count + 1)
  return <button onClick={add}>add</button>
}

Such a simple counting component

However, this is self entertainment after all. The code we write should be connected with the outside world of this component, and our state should be synchronized with the outside world in order to produce industrial value. We call what happens outside as side effects

For example, if you want to synchronize the count with the server code, and if you want to synchronize the count with the vibration of the mobile phone, you need to use useEffect. To abandon the previous concept of life cycle, the only function of useEffect is to synchronize side effects.

useContext

The Componentization of React allows us to separate different business codes, but it also brings a problem that it is very inconvenient for components to share state. For example, you have a state that many components will use, the app topic state. How can a component get this state at any time? You may have heard of state improvement, which alleviates but does not solve the problem. Context is to solve this problem. You can understand it as Redux of React. In fact, Redux is implemented with context

useReducer

As we all know, useState is the main state producer. This useReducer is another less commonly used state producer. It is suitable when the state logic is very complex, or the next state value depends on the previous state value, such as

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>
    </>
  );
}

In this case, it is OK to use useState, but using useReducer makes the code clean and beautiful

useCallback/useMemo

The document on React's official website here is very unclear.

Give us a judgment question: when the parent component is refreshed, all child components will be refreshed. Is this right?

This sentence is true. When the parent component is refreshed, all child components will be refreshed. This sounds very performance consuming, but for most components, the performance is no problem. It should be React, which is really fast.

However, for performance consuming components, this is a big problem. Performance consuming components do not want to be refreshed frequently, so we can use react Memo wraps them so that they refresh only when their props change.

There is another problem, such as:

const TestComp = () => {
  const value = {name: 'Jack'}
  return <MemoExpensiveList value={value}/>
}

Let's see that MemoExpensiveList is affected by react After the memo is processed, its props will be refreshed. But in the above case, the MemoExpensiveList will be refreshed as soon as TestComp refreshes. Why? The reason is that onClick will generate a new instance every time TestComp is refreshed, {name: 'Jack'} ≠ = {name: 'Jack'}

This is when useMemo comes in handy. We can wrap it with useMemo:

const value = useMemo(() => {}, [])

In this way, it will only generate one instance and will not disturb the MemoExpensiveList

useCallback is a special version of useMemo, which is specially used to handle the function

useRef

The above describes the concept of state in detail. Sometimes we want to create a type of value. It is not a state, but it can exist in the form of the same instance between different render s. It is a bit similar to this in the class component xxx

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

Customize Hook

Custom Hook is the best way to reuse React logic at present. It is very similar to ordinary functions. The particularity of custom Hook is that it is stateful and returns state. So when should we use a custom Hook? That is, when we want to abstract the logic of processing state

Let me give you an example

const Comp = () => {
  const [arr, setArr] = useState([1, 2])
  return <button onClick={() => setArr([...arr, value])}>add</button>
}

If you find that there are several such array processing in your app, you can

export const useArray = <T>(initialArray: T[]) => {
  const [value, setValue] = useState(initialArray);
  return {
    value,
    setValue,
    add: (item: T) => setValue([...value, item]),
    clear: () => setValue([]),
    removeIndex: (index: number) => {
      const copy = [...value];
      copy.splice(index, 1);
      setValue(copy);
    },
  };
};

With such a custom Hook, not only the state is returned, but also the method to process the state is returned

This example also shows that a custom Hook can take the state as the core and encapsulate it and its related things. This is also in line with the seperation of concern in our programming, that is, the principle of separation of concerns. Separation of concerns is something we must pay attention to when writing code, that is, irrelevant code should not be put together, otherwise the concerns will be mixed together, which will greatly increase the difficulty of maintenance.

If you look at your code tomorrow, you may find that some noodle codes can be abstracted with hook. Give you another example

const Comp = () => {
  const [id, setId] = useState(0)
  const [assets, setAssets] = useState()

    useEffect(() => {
      fetch(`https://google.com?id=${id}`).then(async response => {
       const data = await response.json();
        if (response.ok) {
          setAssets(data)
        } else {
          return Promise.reject(data);
        }
        })
      }, [])

  return <div>{assets.map(a => a.name)}</div>
}

Does the content of the fetch here have much to do with this component? Not really, because this component doesn't really care about the details of the fetch. It only cares about getting the result Data, then we can use hook to abstract

// util.ts
const useAssets = (id) => {
    const [assets, setAssets] = useState()

    useEffect(() => {
      fetch(`https://google.com?id=${id}`).then(async response => {
       const data = await response.json();
        if (response.ok) {
          setAssets(data)
        } else {
          return Promise.reject(data);
        }
        })
      }, [])
    return assets
}

// comp.tsx
const Comp = () => {
  const [id, setId] = useState(0)
  const assets = useAssets(id)

  return <div>{assets.map(a => a.name)}</div>
}

You see, this realizes the separation of logic

Keywords: React hooks hook

Added by 7724 on Sat, 18 Dec 2021 18:07:39 +0200