Original text: medium.com/swlh/, by Nir Yosef

Wechat search [front-end full stack developer] pay attention to this hair loss, stall, selling goods, continuous learning of the programmer, the first time to read the latest article, will give priority to two days to publish new articles. Attention can be a gift package, you can save a lot of money!

In this post I will share my views on React Hooks, as the title of this post implies, I am not a big fan.

Let’s examine the motivations for abandoning classes in favor of hooks as described in the React documentation.

Motivation 1: Class is confusing

We found that class can be a big barrier to learning React, and you have to understand how this works in JavaScript, which is very different from how it works in most languages. You have to remember that binding event handlers, the code can be very verbose, the differences between functions and class components in React, and when to use each component can cause disagreement even among experienced React developers.

Ok, I can agree that this can be a little confusing when you’re just starting out with Javascript, but the arrow function clears up the confusion and calls a phase 3 feature already supported by Typescript out of the box “unstable syntax suggestions,” which is purely inflammatory. The React team refers to the class field syntax, which is already widely used and may soon be officially supported:

class Foo extends React.Component {
  onPress = () = > {
    console.log(this.props.someProp);
  }

  render() {
    return <Button onPress={this.onPress} />}}Copy the code

As you can see, by using the class field arrow function, you don’t have to bind anything in the constructor, and it always points to the correct context.

If Class is confusing, what can we say about the new hook function? The hook function is not a regular function because it has state, the odd-looking this (aka useRef), and can have multiple instances. But this is definitely not a class, it’s something in between, and from now on I’m going to call it Funclass. So will those funclasses be easier for humans and machines? I’m not sure about machines, but I really don’t think funclasses are conceptually easier to understand than classes.

Classes are a well-known conceptual concept, and every developer is familiar with the concept of this, even in javascript. Funclass, on the other hand, is a new concept, a very strange concept. They feel more magical, and they rely too much on convention rather than strict grammar. You have to follow some strict and strange rules, you need to be careful where you put your code, and there are a lot of pitfalls. Also prepare for some scary names like useRef (fancy name for this), useEffect, useMemo, useImperativeHandle(what?). And so on.

The syntax for classes was invented specifically to deal with the concept of multiple instances and the concept of instance scope (the exact purpose of this). Funclass is just a strange way of achieving the same goal. Many people confuse Funclass with functional programming, but Funclass is really just a class in disguise. A class is a concept, not a grammar.

In React, the distinction between functions and class components, and when to use each, can cause disagreement even among experienced React developers.

So far, the distinction is clear — if state or lifecycle methods are needed, use classes, otherwise, using functions or classes doesn’t really matter. Personally, I love the idea that when I stumble upon a functional component, I can immediately know that it is a “dumb component” with no state. Unfortunately, with the introduction of Funclasses, this is no longer the case.

Motivation 2: It is difficult to reuse stateful logic between components

Is it ironic? The biggest problem with React, at least in my opinion, is that it doesn’t provide an out-of-the-box state management solution, leaving us debating how to fill that void and opening the door to some very bad design patterns like Redux. So after years of frustration, the React team finally came to the conclusion that it was hard to share stateful logic between components…… Who would have thought?

In any case, will the hook make things better? The answer is not really. Hooks don’t work with classes, so if your code base is already written in classes, you’ll need another way to share stateful logic. Also, hooks only solve the problem of logic sharing per instance, but if you want to share state across multiple instances, you still need to use stores and third-party state management solutions, and as I said, if you already use them, you don’t really need hooks.

So, instead of just treating the symptoms, maybe it’s time React went ahead and implemented a proper state management tool that manages both global stores and local states (per instance) to kill the bug once and for all.

Motivation 3: Complex components become difficult to understand

This statement makes little sense if you’re already using stores, and let’s see why.

class Foo extends React.Component {
  componentDidMount(){ doA(); doB(); doC(); }}Copy the code

In this example, you can see that we may have mixed irrelevant logic in componentDidMount, but does that make our components bloated? Not exactly. The whole implementation is outside the class, and the state is inside the Store, and without the store all the state logic has to be implemented inside the class, and the class does get bloated. But React seems to solve another problem that exists mostly in a world without state management tools. In fact, most large applications already use state management tools, and the problem has been mitigated. Alternatively, in most cases, we might be able to break the class into smaller components and place each doSomething() in the child component’s componentDidMount.

Using Funclass, we can write the following code:

function Foo() {
  useA(); 
  useB(); 
  useC();
}
Copy the code

It looks a little clean, but is it? We also need to write three different useEffect hooks somewhere, so we’ll end up writing some more code to see what we’re doing here — with class components, you can see at a glance what components are doing on mount. In the Funclass example, you need to follow the hook and try to search for useEffect with an empty dependency array to see what the component is doing on the mount. The declarative nature of the lifecycle approach is essentially a good thing, and I find it much more difficult to study the flow of Funclasss. I’ve seen a lot of examples of Funclasses making it easier for developers to write bad code, and we’ll see an example of that later.

But first, I must admit that useEffect has some benefits, as shown in the following example:

useEffect(() = > {
  subscribeToA();
  return () = >{ unsubscribeFromA(); }; } []);Copy the code

The useEffect hook lets us pair the subscribe and unsubscribe logic together. This is actually a pretty neat pattern, as is pairing componentDidMount with componentDidUpdate. In my experience, these situations are uncommon, but they are still valid use cases, and useEffect is really useful here. The question is, why do we have to use Funclass to get useEffect? Why can’t our Class have something like that? The answer is that we can:

class Foo extends React.Component {
   someEffect = effect((value1, value2) = > {
     subscribeToA(value1, value2);
     return () = > {
        unsubscribeFromA();
     };
   })
   render(){ 
    this.someEffect(this.props.value1, this.state.value2);
    return <Text>Hello world</Text>}}Copy the code

The effect function remembers the given function and only calls it again if one of its arguments has changed. By triggering the effect from within our render function, we can ensure that it is called every render/update, but the given function will only run again if one of its arguments is changed, So we’ve implemented a useEffect-like effect in combining componentDidMount and componentDidUpdate, but unfortunately we still need to do the final cleanup manually in componentWillUnmount. Also, calling effect functions from within Render is a bit ugly. To get exactly the same effect as useEffect, React needs to add support for it.

The most important thing is that useEffect should not be considered a valid motive for entering funclass, it is a valid motive in itself and can be implemented for classes.

Motivation 4: Performance

The React team says classes are hard to optimize and minimize and funclass should be improved in some way, I have only one thing to say about this — show me the numbers.

I can’t find any papers yet, nor do I have a benchmark demo application that I can clone and run to compare the performance of Funclasses VS classes. In fact, it’s not surprising that we haven’t seen such a demo — Funclasses need to do this somehow (or Ref if you like), so I expect the same issues that make it hard to optimize classes will affect Funclasses as well.

Anyway, all the performance arguments are worthless without showing the data, so we really can’t use it as an argument.

Motivation 5: Funclass is not too verbose

You can find many examples of reducing code by converting a Class to Funclass, but most or all of them use the useEffect hook to combine componentDidMount and componentWillUnmount, So as to achieve great results.

But as I said earlier, useEffect should not be considered an advantage of Funclass, and if you ignore the reduction in code it implements, it leaves a very small footprint. Also, if you try to optimize Funclass using useMemo, useCallback, etc., you might even end up with more verbose code than the equivalent classes.

Funclasses definitely win when comparing small and trivial components, because classes have some built-in templates that you need to pay for no matter how small your class is. But with larger components, you can barely tell the difference, and sometimes, as I said, classes can be even cleaner.

Finally, I have to say a few words about useContext: useContext is actually a big improvement over the context API of our current class. But again, why can’t we have such a nice and neat API for classes? Why can’t we do something like this.

//inside "./someContext" :
export const someContext = React.Context({helloText: 'bla'});

//inside "Foo":
import {someContext} from './someContext';

class Foo extends React.component {
   render() {
      <View>
        <Text>{someContext.helloText}</Text>
      </View>}}Copy the code

When the helloText in the context changes, the component should be rerendered to reflect those changes. That’s it, no need for ugly high-level HOC.

So why did the React team choose to improve only the useContext API rather than the regular Content API? I don’t know, but that doesn’t mean Funclass is intrinsically cleaner. This means React should do better by implementing the same API improvements for classes.

So, having asked about motivation, let’s look at something else about Funclass that I don’t like.

Hidden Side effects

One of the things that bothered me most about the useEffect implementation of Funclasses was not knowing what the side effects of a component were. For classes, if you want to know what a component does when it’s mounted, you can easily check the code in componentDidMount or check the constructor. If you see a repeated call, you should probably check componentDidUpdate. With the new useEffec T hook, side effects can be deeply nested in the code.

Suppose we detect some unnecessary server calls, we look at the code for the suspect component and see the following:

const renderContacts = (props) = > {
  const [contacts, loadMoreContacts] = useContacts(props.contactsIds);
  return (
    <SmartContactList contacts={contacts}/>)}Copy the code

Nothing special here, should we look at The SmartContactList, or should we dig into useContacts? Let’s delve into useContacts:

export const useContacts = (contactsIds) = > {
  const {loadedContacts, loadingStatus}  = useContactsLoader();
  const {isRefreshing, handleSwipe} = useSwipeToReresh(loadingStatus);
  // ... many other useX() functions
  useEffect(() = > {
    //** A lot of code related to some animation loading contacts. * / /
  
  }, [loadingStatus]);
  
  / /.. rest of code
}
Copy the code

Okay, this is getting tricky. What are the hidden side effects? If we drill down into useSwipeToRefresh, we see:

export const useSwipeToRefresh = (loadingStatus) = > {
  / /.. lot's of code
  // ...
  
  useEffect(() = > {
    if(loadingStatus === 'refresing') {
       refreshContacts(); // bingo! Our hidden side effects}});//<== we forgot the dependency array!
}
Copy the code

We found our hidden effect that refreshContacts would accidentally call Fetch Contacts when each component was rendered. Nested USEeffects can cause problems in large code bases and in some poorly structured components.

I’m not saying you can’t write bad code with classes, but Funclasses are more error-prone, and without a rigorously defined lifecycle method structure, it’s easier to do bad things.

The expansion of the API

By adding the hook API along with the class, the React API actually doubles. Now everyone has to learn two completely different approaches, and I must say that the new API is much more obscure than the old one. Simple things, like getting props and state before, now make good interview material. Can you write a hook to get props before without using Google?

A large library like React has to be very careful about adding such huge changes to its API that the incentive to do so is not even reasonable.

Lack of clarity

In my opinion, funclasses are messier than classes. For example, it can be difficult to find a pointcut for a component — just search the render function with classes, but the main return statement is harder to find with Funclasses. In addition, it is difficult to understand the flow of a component in terms of different useEffect statements, whereas the regular lifecycle approach will give you a good indication of where your code needs to look. If I’m looking for some kind of initialization logic I’ll jump (CMD + shift + o in VSCode) to componentDidMount, if I’m looking for some kind of update mechanism I might jump to componentDidUpdate etc. With Funclass, I find it difficult to locate within large components.

A contract-driven API

One of the main (and probably most important) rules for hooks is the use of prefix conventions.

It just doesn’t feel right

Do you know what feels wrong? That’s how I feel about hooks. Sometimes I can pinpoint a problem, but other times it’s just a general feeling that we’re going in the wrong direction. When you find a good concept, you can see how things fit together well, but when you agonize over the wrong concept, you find that you need to add more concrete things and rules to make things work.

With hooks, there are more and more weird things popping up, more “useful” hooks to help you do trivial things, and more things to learn. If we need so many utils in our daily work just to hide some strange complexity, it’s a huge sign that we’re on the wrong track.

When I switched to React from Angular 1.5 a few years ago, I was amazed at how simple the React API was and how thin the documentation was. Angular used to have huge documentation, and it would take days to cover everything — digestion, different build phases, Transclude, bindings, templates, and so on. That alone was a huge revelation to me, whereas React is clean and you can read the entire document in a couple of hours. In my first, second, and all subsequent attempts to use hooks, I found myself obligated to use the document again and again.

conclusion

I hate to be a party pooper, but I really think that Hooks are probably the 2nd worst thing that ever happened to the React community (# 1 still held by Redux). It adds yet another useless debate to an already fragile ecosystem, and it’s unclear whether hooks are the recommended way to use them or just another matter of functionality and personal taste.

I hope the React community wakes up and demands a balance between Funclass and class functionality. We could have better Context apis in our classes, and we could provide things like useEffect for our classes. React should give us the option to continue using the class if we need to, rather than leaving it behind by killing it by force simply by adding more functionality to the Funclass.

Also, back in late 2017, I published a piece titled “The Ugly Side of Redux,” and now even Redux creator Dan Abramov has admitted that Redux was a huge mistake.

Is history repeating itself? Time will tell.

However, my teammates and I decided to stick with classes for the time being and use a Mobx-based solution as a state management tool. I think there is a big difference in the prevalence of Hooks between independent developers and team workers — the bad nature of Hooks is more obvious in a large code base where you need to work with other people’s code. I personally really wish React had all the CTRL + Z hooks together.

I’m going to start working on an RFC to come up with a simple, clean, built-in state management solution for React that will solve the problem of shared state logic once and for all, hopefully in a less clunky way than Funclasses.