Original text: dev. To/tylermcginn… Translator: Front-end technology guy

When you’re about to learn something new, the first thing you should do is ask yourself two questions

  • 1. Why does it exist?
  • 2. What problem does this thing solve?

If you never have a convincing answer to both of these questions, you don’t have a solid enough foundation when you get down to the specifics. On React Hooks, these questions are worth thinking about. When Hooks was released, React was the most popular and popular front-end framework in the JavaScript ecosystem. Despite the high praise React has received, the React team still felt the need to build and release Hooks. Various Medium posts and blog posts have discussed (1) why and for what the React team decided to spend valuable resources building and releasing Hooks and (2) its benefits, despite being highly praised and well received. To better understand the answers to these two questions, we first need to take a deeper look at how we used to write React applications.

createClass

If you’ve been using React long enough, you’ll remember the React. CreateClassAPI. This is how we initially created the React component. All information used to describe the component is passed to the createClass as an object.

const ReposGrid = React.createClass({
  getInitialState () {
    return {
      repos: [],
      loading: true}},componentDidMount () {
    this.updateRepos(this.props.id)
  },
  componentDidUpdate (prevProps) {
    if(prevProps.id ! == this.props.id) { this.updateRepos(this.props.id) } }, updateRepos (id) { this.setState({ loading:true })

    fetchRepos(id)
      .then((repos) => this.setState({
        repos,
        loading: false}})),render() {
    const { loading, repos } = this.state

    if (loading === true) {
      return <Loading />
    }

    return( <ul> {repos.map(({ name, handle, stars, url }) => ( <li key={name}> <ul> <li><a href={url}>{name}</a></li> <li>@{handle}</li> <li>{stars} stars</li> </ul> </li>  ))} </ul> ) } })Copy the code

CreateClass is a simple and effective way to create a React component. React originally used the createClassAPI because JavaScript didn’t have a built-in class system at the time. Of course, that eventually changed. In ES6, JavaScript introduced the class keyword and used it to create classes in JavaScript in a native way. This puts React in a quandary. Either continue to use createClass to combat the evolution of JavaScript, or submit and include classes as the EcmaScript standard dictates. History suggests they chose the latter.

React.Component

We don’t think we’re in the business of designing systems. We just want to create the class in any conventional JavaScript way. -React V0.13.0 releases Reactiv0.13.0 introducing the React.componentAPI, which allows you to create React components from (now) native JavaScript classes. This is a big win because it better aligns with the ECMAScript standard.

class ReposGrid extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
      repos: [],
      loading: true
    }

    this.updateRepos = this.updateRepos.bind(this)
  }
  componentDidMount () {
    this.updateRepos(this.props.id)
  }
  componentDidUpdate (prevProps) {
    if(prevProps.id ! == this.props.id) { this.updateRepos(this.props.id) } } updateRepos (id) { this.setState({ loading:true })

    fetchRepos(id)
      .then((repos) => this.setState({
        repos,
        loading: false}}))render() {
    if (this.state.loading === true) {
      return <Loading />
    }

    return( <ul> {this.state.repos.map(({ name, handle, stars, url }) => ( <li key={name}> <ul> <li><a href={url}>{name}</a></li> <li>@{handle}</li> <li>{stars} stars</li> </ul> </li>  ))} </ul> ) } }Copy the code

Despite a clear step in the right direction, React.Com Ponent is not without its trade-offs

The constructor

Using class components, we can initialize the component’s state to the state property on the instance (this) in the constructor method. However, according to the ECMAScript specification, if you want to extend a subclass (in this case, React.Component), you must call super before you can use this. Specifically, when using React, we also need to remember to pass props to super.

(props) {super(props) // 🤮... }Copy the code

Automatic binding

When using createClass, React will automatically bind all methods to the instance of the component, namely this. With React.component, things are different. Quickly, React developers everywhere realized they didn’t know how to use the “this” keyword. We must remember the.bind method in the constructor class, rather than call it using a method that is just available. If you do not, you will get the common “cannot read undefined setState property” error.

constructor (props) { ... This. UpdateRepos = this.updaterepos.bind (this) // 😭}Copy the code

Now I guess you might be thinking. First, these questions are rather superficial. Sure, calling super(props) with the bind method in mind is cumbersome, but there’s nothing fundamentally wrong here. Second, these React problems aren’t as serious as the way JavaScript classes are designed. Of course, both of these points are indisputable. However, we are developers. Even the most obvious problems can become annoying when you’re dealing with them more than 20 times a day. Fortunately, the class field proposal appeared shortly after switching from createClass to React.component.

Class fields

Class fields allow us to add instance properties directly to class properties without using Constructor. What this means for us is that in class fields, the two “minor” problems we discussed earlier will be solved. We no longer need to use Constructor to set the initial state of the component, nor.bind from constructor, since we can use the arrow function instead.

class ReposGrid extends React.Component {
  state = {
    repos: [],
    loading: true
  }
  componentDidMount () {
    this.updateRepos(this.props.id)
  }
  componentDidUpdate (prevProps) {
    if(prevProps.id ! == this.props.id) { this.updateRepos(this.props.id) } } updateRepos = (id) => { this.setState({ loading:true })

    fetchRepos(id)
      .then((repos) => this.setState({
        repos,
        loading: false}}))render() {
    const { loading, repos } = this.state

    if (loading === true) {
      return <Loading />
    }

    return( <ul> {repos.map(({ name, handle, stars, url }) => ( <li key={name}> <ul> <li><a href={url}>{name}</a></li> <li>@{handle}</li> <li>{stars} stars</li> </ul> </li>  ))} </ul> ) } }Copy the code

So now we don’t have a problem, do we? But it doesn’t. There were some trade-offs during the migration from createClass to React.ponent, but as we’ve seen, class fields solved some of the problems. Unfortunately, we still have some more profound (but less mentioned) problems with all the previous versions we’ve seen. The whole idea of React is that you can better manage the complexity of your applications by breaking them down into individual components and then combining them together. This component model is what makes React so subtle and so unique. However, the problem is not the component model, but how to install it.

Repeat logic

In the past, the way we built the React component was coupled to the component lifecycle. This gap naturally forces the entire component to scatter logic. We can see this clearly in our ReposGrid example. We need three separate methods (componentDidMount, componentDidUpdate, and updateRepos) to accomplish the same task — to synchronize repos with any props. Id.

componentDidMount () {
    this.updateRepos(this.props.id)
 }
 componentDidUpdate (prevProps) {
    if(prevProps.id ! == this.props.id) { this.updateRepos(this.props.id) } } updateRepos = (id) => { this.setState({ loading:true })
    fetchRepos(id)
      .then((repos) => this.setState({
        repos,
        loading: false}}))Copy the code

To solve this problem, we need a new paradigm to deal with the side effects of the React component.

Share non-visual logic

When you think about composition in React, you’re most likely thinking about UI composition. This is natural, because it’s what React does well.

view = fn(state)
Copy the code

In fact, there is more to building an application than just building the UI layer. The need to compose and reuse non-visual logic is not uncommon. However, because React couples the UI to components, this is more difficult. So far React has not provided a good solution. Moving on to our example, suppose we need to create another component that also requires repos state. Now you have that state and the logic to process it in the ReposGrid component. How do we do that? One of the easiest ways to do this is to copy all the logic used to get and process repos and paste it into a new component. That sounds great, but, uh, no. A more subtle approach is to create a higher-order component that encapsulates all the shared logic and passes loading and repos as properties to any component that needs them.

function withRepos (Component) {
  return class WithRepos extends React.Component {
    state = {
      repos: [],
      loading: true
    }
    componentDidMount () {
      this.updateRepos(this.props.id)
    }
    componentDidUpdate (prevProps) {
      if(prevProps.id ! == this.props.id) { this.updateRepos(this.props.id) } } updateRepos = (id) => { this.setState({ loading:true })

      fetchRepos(id)
        .then((repos) => this.setState({
          repos,
          loading: false}}))render () {
      return( <Component {... this.props} {... this.state} /> ) } } }Copy the code

Now, whenever any component in your application needs repos(or loading), you can encapsulate it in the withRepos advanced component.

// ReposGrid.js
function ReposGrid ({ loading, repos }) {
  ...
}

export default withRepos(ReposGrid)
Copy the code
// Profile.js
function Profile ({ loading, repos }) {
  ...
}

export default withRepos(Profile)
Copy the code

This works, and it, along with the Render Props of the past, has been a recommended solution for sharing non-visual logic. However, both models have some disadvantages. First, if you’re not familiar with them (and even if you are), you’ll be a little confused. When we use the withRepos advanced component, we have a function that takes the final rendered component as the first argument, but returns a new class component, where the logic lies. What a complicated process it is. Next, what if we consume multiple advanced components? As you can imagine, it quickly got out of hand.

export default withHover(
  withTheme(
    withAuth(
      withRepos(Profile)
    )
  )
)
Copy the code

Worse than ^ is what you end up with. These high-level components (and similar patterns) force us to restructure and wrap components. This can eventually lead to “packaging hell,” which again makes it harder to follow.

<WithHover>
  <WithTheme hovering={false}>
    <WithAuth hovering={false} theme='dark'>
      <WithRepos hovering={false} theme='dark' authed={true}>
        <Profile 
          id='JavaScript'
          loading={true} 
          repos={[]}
          authed={true}
          theme='dark'
          hovering={false}
        />
      </WithRepos>
    </WithAuth>
  <WithTheme>
</WithHover>
Copy the code

The current

That’s where we are right now.

  • React is popular.
  • We used classes for the React component because that made the most sense at the time.
  • Calling super(props) is annoying.
  • Nobody knows what “this” is about.
  • All right, calm down. I know you know how this works, but for some people, it’s an unnecessary barrier.
  • Organizing components in a lifecycle approach forces us to scatter related logic in the components.
  • React does not have a good primitive for sharing non-visual logic.

Now we need a new component API that solves all of these problems while remaining simple, composable, flexible, and extensible. The task was daunting, but the React team pulled it off.

React Hooks

Since Active0.14.0, we have two ways to create components – classes or functions. The difference is that classes must be used if the component has state or needs to use lifecycle methods. Otherwise, if it just accepts items and renders some UI, we can use a function. What if it’s not. What if instead of using classes, we always use functions?

Sometimes all you need for a flawless installation is a function. No method. There is no class. No frame. You only need one function. — John Carmack, cto of OculusVR.

Of course, we need to find a way to add the ability for functional components to have state and lifecycle methods, but suppose we did, what benefit would we get? We no longer need to call super(props), we no longer need to consider the bind method or the this keyword, and we no longer need to use class fields. All the “little” problems we talked about before will disappear.

Blue Intersect,_ "intersect) Blue's intersect 🗑functionヾ (Ő ‿ Ő ✿)Copy the code

Now comes the trickier question.

  • state
  • Lifecycle approach
  • Sharing non-visual logic

state

Since we no longer use classes or this, we need a new way to add and manage state inside components. React V16.8.0 gives us this new approach via the useState method. UseState is the first of many “Hooks” that we will see in this course. Let the rest of this article serve as a brief introduction. After that, we’ll take a closer look at useState and other Hooks. UseState accepts only one parameter, the initial value of the state. It returns an array where the first item is a state block and the second item is a function to update that state.

const loadingTuple = React.useState(true)
const loading = loadingTuple[0]
const setLoading = loadingTuple[1]

...

loading // true
setLoading(false)
loading // false
Copy the code

As you can see, getting each item in the array individually is not the best developer experience. This is just to demonstrate how useState returns an array. We usually use array destructors to get values in a row.

// const loadingTuple = React.useState(true)
// const loading = loadingTuple[0]
// const setLoading = loadingTuple[1]

const [ loading, setLoading ] = React.useState(true) / / 👌Copy the code

Now, let’s update the ReposGrid component using our newly discovered knowledge about the hooks of useState.

function ReposGrid ({ id }) {
  const [ repos, setRepos ] = React.useState([])
  const [ loading, setLoading ] = React.useState(true)

  if (loading === true) {
    return <Loading />
  }

  return( <ul> {repos.map(({ name, handle, stars, url }) => ( <li key={name}> <ul> <li><a href={url}>{name}</a></li> <li>@{handle}</li> <li>{stars} stars</li> </ul> </li>  ))} </ul> ) }Copy the code
  • State ✅
  • Lifecycle approach
  • Sharing non-visual logic

Lifecycle approach

Something that might make you sad (or happy?) . When using ReactHooks, we need to forget everything we know about the popular React lifecycle approach and this way of thinking. We’ve seen the problem with thinking about the life cycle of a component – “it logically forces the logic to be scattered throughout the component.” Instead, consider synchronization. Think of a time when we used life cycle events. Whether it’s setting the initial state of a component, fetching data, updating the DOM, etc., the end goal is always synchronization. In general, things outside React Land (API requests, DOM, etc.) are synchronized with things inside Reactland (component state) and vice versa. When we think about synchronization rather than life cycle events, it allows us to group related logical blocks together. To this end, Reaction gives us another Hook called useEffect. It is safe to say that useEffect enables us to perform side effects in the Function component. It takes two arguments, a function and an optional array. The function defines the side effects to run, and the (optional) array defines when to “resynchronize” (or re-run)effect.

React.useEffect(() => {
  document.title = `Hello, ${username}`
}, [username])
Copy the code

In the above code, the function passed to useEffect runs when the user name changes. Therefore, synchronize the title of the document with the content resolved by Hello, ${username}. Now, how do we use the useEffect Hook in our code to synchronize repos and fetchRepos API requests?

function ReposGrid ({ id }) {
  const [ repos, setRepos ] = React.useState([])
  const [ loading, setLoading ] = React.useState(true)

  React.useEffect(() => {
    setLoading(true)

    fetchRepos(id)
      .then((repos) => {
        setRepos(repos)
        setLoading(false)
      })
  }, [id])

  if (loading === true) {
    return <Loading />
  }

  return( <ul> {repos.map(({ name, handle, stars, url }) => ( <li key={name}> <ul> <li><a href={url}>{name}</a></li> <li>@{handle}</li> <li>{stars} stars</li> </ul> </li>  ))} </ul> ) }Copy the code

Pretty neat, right? We have successfully gotten rid of react.component, constructor, super, this, and more importantly, we no longer scatter (and copy) effect logic across the entire component.

  • State ✅
  • Lifecycle approach ✅
  • Sharing non-visual logic

Sharing non-visual logic

As mentioned earlier, React doesn’t have a great solution for sharing non-visual logic because “React couples the UI to components.” This leads to overly complex patterns like advanced components or render items. As you might have guessed by now, Hooks has an answer to this, too. However, it’s not what you might think. There are no built-in hooks for sharing non-visual logic, but instead we can create customizations decouple from any UI. We can see this by creating our own custom useRepos Hook. This will take the ID of the Repos we want and (with a similar API) return an array with the first item in the Loading state and the second item in the Repos state.

function useRepos (id) {
  const [ repos, setRepos ] = React.useState([])
  const [ loading, setLoading ] = React.useState(true)

  React.useEffect(() => {
    setLoading(true)

    fetchRepos(id)
      .then((repos) => {
        setRepos(repos)
        setLoading(false)
      })
  }, [id])

  return [ loading, repos ]
}
Copy the code

The good news is that any logic associated with retrieving repos can be abstracted in this custom Hook. Now, regardless of which component we are in, even if it is non-visual logic, we can use useRepos custom hooks whenever we need data about repos.

function ReposGrid ({ id }) {
  const [ loading, repos ] = useRepos(id)
  ...
}
Copy the code
function Profile ({ user }) {
  const [ loading, repos ] = useRepos(user.id)
  ...
}
Copy the code
  • State ✅
  • Lifecycle approach ✅
  • Shared non-visual logic ✅

Hooks are promoted on the idea that we can use state in functional components. In fact, Hooks are much more than that. It’s more about improved code reuse, composition, and better defaults. We still have a lot to learn about Hooks, but now that you know why they exist, we have a solid foundation.

❤ ️ look after

  • Like, so that more people can see this content (collection does not like, is a rogue -_-)
  • Pay attention to the public number “new front-end community”, number to enjoy the first article experience! Focus on conquering one front-end technical difficulty every week.