With React Hooks, mom no longer has to worry about function components remembering state

In the past, functions in React were called stateless functional components. This is because functions had no way to have their own state and could only render the UI according to Props. The property is similar to that of the Render function in a class component. Although the structure is straightforward, its usefulness is limited.

But since React Hooks came along, function components now have the ability to save state, and increasingly overwrite the use of class components, React Hooks are the way to go.

What problem do React Hooks fix

Complex components are difficult to disassemble

We know that the idea of componentization is to abstract and break up a complex page/large component into purer components at different levels, which can reduce code coupling on the one hand and reuse code better on the other hand. However, when using React class components, it is often difficult to further split complex components. This is because the logic is stateful. If forced to split, the code complexity will increase dramatically. With design patterns like HOC and Render Props, this creates “nested hell” that makes our code obscure.

The complexity of state logic is a barrier to unit testing

This is a continuation of the previous point: writing unit tests for a component that has a lot of state logic can be frustrating because you need to write a lot of test cases to cover the code execution path.

The component life cycle is complex

For class components, we need the components provide processing state in the lifecycle of the hook of initialization, data acquisition, data update operation, such as logic to handle itself is more complicated, and various “side effect” together also make people dizzy, also is likely to forget to change/components in component state is destroyed to eliminate side effects.

React Hooks are here to fix these problems

  • React-hooks: React-hooks are not the solution for reusing state logic. Functions have simple logic and are easy to reuse.
  • React Hooks block the concept of the lifecycle. Everything is state-driven, or data-driven, so it is much easier to understand and deal with.

Encapsulate logic with associated state using custom Hooks bundles

I think the best thing about React Hooks is not the official React apis, they are just basic capabilities; Again, the highlight is custom Hooks, a design pattern that encapsulates and reuses.

For example, a page often has many states with their own processing logic. If you use class components, these states and logic are mixed together, which is not intuitive:

class Com extends React.Component {
    state = {
        a: 1.b: 2.c: 3,
    }
    
    componentDidMount() {
        handleA()
        handleB()
        handleC()
    }
}
Copy the code

With React Hooks, we can associate state with logic and split the code into multiple custom Hooks for a clearer structure:

function useA() {
    const [a, setA] = useState(1)
    useEffect((a)= > {
        handleA()
    }, [])
    
    return a
}

function useB() {
    const [b, setB] = useState(2)
    useEffect((a)= > {
        handleB()
    }, [])
    
    return b
}

function useC() {
    const [c, setC] = useState(3)
    useEffect((a)= > {
        handleC()
    }, [])
    
    return c
}

function Com() {
    const a = useA()
    const b = useB()
    const c = useC()
}
Copy the code

In addition to using custom Hooks to split business logic, we can also split generic logic for higher reuse value, such as the currently popular Hooks library: react-use; In addition, many of the original libraries in the React ecosystem are starting to provide Hooks apis, such as react-Router.

Forget the component life cycle

React provides a large number of component lifecycle hooks, which are not often used in business development, but only componentDidUpdate and componentWillUnmount. Forgetting to handle the props update and component destruction scenarios where side effects need to be handled can leave you with visible bugs as well as memory leaks.

The MVVM-like framework is data-driven, while the design pattern of life cycle is obviously more inclined to the traditional event-driven model. When we introduced React Hooks, the data-driven features were much purer.

Processing props Update

Here’s an example of a very typical list page:

class List extends Component {
  state = {
    data: []
  }
  fetchData = (id, authorId) = > {
    // Request the interface
  }
  componentDidMount() {
    this.fetchData(this.props.id, this.props.authorId)
    / /... Other unrelated initialization logic
  }
  componentDidUpdate(prevProps) {
    if (
      this.props.id ! == prevProps.id ||this.props.authorId ! == prevProps.authorId// Don't leave it out!
    ) {
      this.fetchData(this.props.id, this.props.authorId)
    }
    
    / /... Other unrelated update logic
  }
  render() {
    // ...}}Copy the code

There are three problems with this code:

  • Almost the same logic needs to be executed in both life cycles simultaneously.
  • When determining whether data needs to be updated, it is easy to miss dependent conditions.
  • In each lifecycle hook, a large number of unrelated logical code is scattered, which violates the principle of high cohesion and affects the consistency of reading code.

If you implement it using React Hooks, the problem is largely resolved:

function List({ id, authorId }) {
    const [data, SetData] = useState([])
    const fetchData = (id, authorId) = > {}
    useEffect((a)= > {
        fetchData(id, authorId)
    }, [id, authorId])
}
Copy the code

React Hooks:

  • We don’t need to worry about the life cycle, we just need to put all the logical dependency states into the dependency list. React will help us determine when to execute.
  • React provides a plugin for ESLint to check that the dependency list is complete.
  • You can use multiple Useeffects, or multiple custom Hooks, to separate separate pieces of logic from each other and ensure high cohesion.

Dealing with side effects

The most common side effect is binding DOM events:

class List extends React.Component {
    handleFunc = (a)= > {}
    componentDidMount() {
        window.addEventListener('scroll'.this.handleFunc)
    }
    componentWillUnmount() {
        window.removeEventListener('scroll'.this.handleFunc)
    }
}
Copy the code

This will also have the same high cohesion problems described above.

function List() {
    useEffect((a)= > {
        window.addEventListener('scroll'.this.handleFunc)
    }, () => {
        window.removeEventListener('scroll'.this.handleFunc)
    })
}
Copy the code

And, remarkably, in addition to being triggered when a component is destroyed, the side effect of clearing the previous round is also performed when a dependency changes.

Local performance optimization using useMemo

/ / Use useMemo to rerender. / / Use useMemo to rerender. / / Use useMemo to rerender.

function List(props) = >{
  useEffect((a)= > {
    fetchData(props.id)
  }, [props.id])

  return useMemo((a)= > (
    // ...
  ), [props.id])
}
Copy the code

In this code, we can see that the final render is dependent on the props. Id, so as long as the props. Id does not change, the component will not be re-rendered, even if the other props do.

Rely on useRef to get rid of closures

When we first started using React Hooks, we often encountered scenarios where an event callback needed to determine what to do next based on the current state value; However, we find that the event callback always gets the old state value instead of the latest state value. Why is this?

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

  const log = (a)= > {
    setCount(count + 1);
    setTimeout((a)= > {
      console.log(count);
    }, 3000);
  };

  return (
    <button onClick={log}>Count off</button>
  );
}

/* If we click three times in a row within three seconds, the count value will eventually be 3, and the output will be? 0 1 2 * /

Copy the code

“It’s a feature, not a bug.” Each time we pass a callback to setTimeout, the callback references the current function scope (count has not been updated yet), so the old state value will be printed at execution time.

How is a class component implemented?

Why is the class component always fetching the latest status value? This is because the state values we take from the class component are taken from this.state, which acts as an execution context for the class component and is always up to date.

Use useRef to share changes

An object created by useRef has a single copy of its value, which is shared among all rerenders.

UseRef = this.state; useRef = this.state; useRef = this.state; useRef = this.state;

function Counter() {
  const count = useRef(0);

  const log = (a)= > {
    count.current++;
    setTimeout((a)= > {
      console.log(count.current);
    }, 3000);
  };

  return (
    <button onClick={log}>Count off</button>
  );
}

/* 3, 3, 3 */
Copy the code