• React Today and Tomorrow and 90% Cleaner React with Hooks
  • 【React Conf 2018】React Today and Tomorrow
  • React Today And Tomorrow Part II — Chinese And English subtitles
  • React Today and Tomorrow – Part II
  • Speaker: Dan Abramov
  • English subtitles from: YouTube machine Translation
  • English proofread, translation: Ivocin, program yuan _ small hair
  • Layout: Ivocin

Because there are many Demo parts of Dan’s speech, I suggest you watch the video if you have enough time. React Conf 2018 — React Today and Tomorrow Part II Today and Tomorrow in React — Part 1

React Today and Tomorrow — Part two

A: hi. My name is Dan. I work on the React Team, and this is my first React conference. (applause.)

React’s current problems

Sophie talked about these three issues, which I think most developers have encountered in the React process. Of course, we can address these issues one by one. We can try to solve these problems independently. But actually solving one problem may make the others worse.

For example, if we try to solve the “wrapper hell” problem, we can put more logic into the components, but our components will become bigger and harder to refactor. On the other hand, if we try to break components into smaller pieces for reuse, we end up with more nesting of the component tree, and we end up with wrapping hell. Finally, in either case, using class can be confusing.

So we think it’s because these are not three separate issues. We believe that these are three symptoms of the same problem. The problem is that React doesn’t natively provide a simpler, smaller, lighter way to add state or lifecycle than a class component.

And once you’ve used the class component, there’s no way you can break it down any further without causing wrapper hell. In fact, this is not a new problem. If you’ve been using React for a few years, you may remember that when React first came out, it actually included a solution for this problem. Well, that solution is mixins. Mixins allow you to reuse methods between classes and reduce nesting.

So should we add the mixins back in React? (to… No…) By the way, no, no, we’re not adding mixins. What I mean by that is that the old code that used mixins is not unusable. However, we no longer recommend using mixins in React. If you’re wondering why we’re doing this, check out our previous post on React Blog called Mixins Are Bad. In the article, we learned from our experiments that mixins cause more problems than they solve. Therefore, we don’t recommend using mixins.

We have a proposal

Then maybe we won’t be able to solve this problem because it’s inherent to the React component model. Maybe we have to choose to accept it. (Laughter) or maybe there’s another way of writing components that avoids these problems.

That’s what I’m going to share with you today.

But before we start sharing the changes and new features we’ve made with React, I want to talk a little bit about the RFC process we set up a year ago. RFC stands for Request for Comments. It means that whenever we or someone else wants to make a lot of changes to React or add new features, we need to write a proposal that contains details of the motivation and a detailed design of how the proposal will work.

That’s exactly what we’re going to do. We are very excited to announce that we have prepared a proposal to address these three issues.

Importantly, this proposal contains no backwards incompatible changes and does not deprecate any functionality. This proposal is strictly additive, optional, and adds some new apis to help us address these issues. And we’d like to hear your feedback on this proposal, which is why we’re publishing it today.

We’ve thought about a lot of ways to publish this proposal, and maybe we can write the proposal, come up with an RFC and put it there. But since we always have React conferences, we decided to release this proposal at this one.

The Demo link

So, let’s go to the Demo. (applause.)

My screen is already on the monitor. Sorry, there’s a technical problem. Uh, somebody with this projector, help me out. (Laughter) Uh, can I copy my desktop? Please. (I can) Yeah. (Laughter) Okay, but it’s not on the screen. I can’t see anything. (Laughter) That’s my problem now. (Applause.) Okay, disaster over. (Laughter) Okay, well, let me resize the text a little bit. Can you see that? (Yes.) Ok.

A familiar example of a class component

So, let’s see, here’s a normal React component, here’s a Row component, there’s some styles, and then it renders a name.

import React from 'react';
import Row from './Row';

export default function Greeting(props) {
  return (
    <section>
      <Row label="Name">
        {props.name}
      </Row>
    </section>
  );
}
Copy the code

What we want to do is make the name editable. So what do we usually do in React? We need to add an input here, we need to put that in class and return it, add some local state, and have the state drive the input. And that’s what I’m going to do. That’s what people do these days.

I’m going to export the default class Greeting inherited from React.ponent. I’m only going to use the stable JavaScript syntax here. Next we have constructor(props), super (props). Here we initialize the name in state to Mary. Next I’m going to declare a render function. Copy this code and paste it here. I’m sorry. Ok.

I want to not just render the name, I want to render an input. I’m going to replace this with an input, and then the value of the input is going to be this.state.name. Then, when the input changes, call this.handlenamechange, which is the callback to my change event. I’ll declare it here, and when the name changes, call the setState method like we normally do. Then set the name to e.t. value. Isn’t it.

If I edit… (The page reported TypeError) Ok, so I should go bind… (Laughter) Sorry, I need to bind the event event here. Ok, so now we can edit it, and it works.

This class component should be very familiar. You’ll probably encounter a lot of similar code if you develop with React.

import React from 'react';
import Row from './Row';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary'
    }
    this.handleNameChange = this.handleNameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }
  render() {
    return (
      <section>
        <Row label="Name"> <input value={this.state.name} onChange={this.handleNameChange} /> </Row> </section> ); }}Copy the code

Can this function be implemented using the Function component

But let’s step back. If we want to use state, can we not have to use a class component? I’m not sure what to do. But I’m just going to go with what I know, and I need to render an input. I’m going to put an input here. The value of this input value is the current value of name, so I’m just passing in the value of name. I don’t know where to get the name. It doesn’t come from props, well, I’m just going to declare it here, I don’t know what it’s worth, and then I’ll fill this in.

Well, there should also be a change callback, so I’m declaring the onChange function handleNameChange here. I’ll add a function here to handle events. Here I want to tell React to set the name value somewhere, but again, I’m not sure how to do that in the function component. So I’m just going to call a method called setName. Use the current input value. Let me declare it here.

import React from 'react';
import Row from './Row';

export default function Greeting(props) {
  const name = ???
  const setName = ???

  function handleNameChange(e) {
    setName(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
    </section>
  );
}
Copy the code

Well, because these two things go hand in hand, right. One is the current value of the name variable in state, and the other is a function that lets us set the name variable in state. Since these two things are so related, I combined them together as a pair of values.


- const name = ???
- const setName = ???
+ const [name, setName] = ???

Copy the code

We get their values from somewhere together. So the question is where do I get them? The answer is obtained from React local state. How do I get React to the local state in the function component? Well, what if I use useState directly. Pass the initialization to state to the useState function to specify its initial value.

import React, { useState } from 'react';
import Row from './Row';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');

  function handleNameChange(e) {
    setName(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
    </section>
  );
}
Copy the code

Let’s see if it works. Yes, it’s working fine.

(Applause and cheers)

So let’s compare these two approaches. On the left is the familiar class component. Here state must be an object. Well, we bind some event handlers to call. The this.setState method is used in the event handler. When we call the setState method, we don’t actually set the value directly to state. State is incorporated into the state object as an argument. And when I want to get state, we need to call this.state.something.

import React from 'react';
import Row from './Row';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary'
    }
    this.handleNameChange = this.handleNameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }
  render() {
    return (
      <section>
        <Row label="Name"> <input value={this.state.name} onChange={this.handleNameChange} /> </Row> </section> ); }}Copy the code

So let’s look at the example on the right: we don’t need to use this.state.something to get state. Because the name variable in state is already available in the function. It’s just a variable. Similarly, when we need to set state, we don’t need to use this.something. Because the function also lets us set the value of name in its scope. So what exactly is useState? UseState is a Hook. A Hook is a function provided by React that allows you to “Hook” to React features in a function component. And useState is the first hook we talked about today, and there are a few more hooks to follow. We’ll see them later.

import React, { useState } from 'react';
import Row from './Row';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');

  function handleNameChange(e) {
    setName(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
    </section>
  );
}
Copy the code

Use class and hook two ways to achieve the increase of last name editing area

Ok, let’s go back to our familiar class example. We want to add a second region next. For example, add a last name area. So what we usually do is we add a new key to state. Let me copy and paste this line right here. So let’s say we have a first name. Rendered here, here is the surname and handleSurnameChange. I’m just going to copy that event handler again, so I’m going to change it to a firstname. Don’t forget to bind this function. All right, Mary Poppins is on, and we can see that it’s working.

import React from 'react';
import Row from './Row';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary',
      surname: 'Poppins',
    }
    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleSurnameChange = this.handleSurnameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }

  handleSurnameChange(e) {
    this.setState({
      surname: e.target.value
    })
  }

  render() {
    return (
      <section>
        <Row label="Name">
          <input
            value={this.state.name}
            onChange={this.handleNameChange}
          />
        </Row>
        <Row label="Surname"> <input value={this.state.surname} onChange={this.handleSurnameChange} /> </Row> </section> ); }}Copy the code

So how can we use hooks to achieve the same functionality? One thing we need to do is change our state to an object. As you can see, the state that uses a hook does not force its type to be an object. It can be any native JavaScript type. We can turn it into an object if we want, but we don’t have to.

Conceptually speaking, surname has little to do with name. So what we need to do is call useState hook again to declare the second state variable. So here I’m going to declare surname, and of course I can give it any name, because it’s just a variable in my program. Let’s set setSurname. Call useState, passing in the state initial variable ‘Poppins’. I’ll copy and paste the Row fragment again. Change the value to surname and onchange event to handleSurnameChange. When a user edits a given name, not a Sir Name, we want to be able to change the value of the surname.

import React, { useState } from 'react';
import Row from './Row';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');

  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
    </section>
  );
}
Copy the code

Let’s see if it works. Yeah, it’s working fine. (applause.)

So we can see that we can use hooks multiple times in a component. Let’s compare these two approaches in more detail. The state in the familiar class component on the left is always an object with multiple fields, and you need to call setState to incorporate some of those values into the state object. When we need to get it, we need to call this.state.something. In the example with hook on the right, we use hook twice and declare two variables: name and surname. And every time we call setName or setSurname, React will be notified that we need to rerender that component, just like we do with setState. So the next time the React rendering component passes the current name and surname to the component. And we can use these state variables directly, without calling this.state.something.

Use the React Context in both class and hook ways

Ok. Let’s go back to our class component example. Are there any other React features that we know of? So another thing you might want to do inside the component is read the context. In case you’re not familiar with context, it’s like a global variable for a subtree. Context is useful when you need to get the current topic or the language that the current user is using. Especially when all components need to read some of the same variables, using context is a great way to avoid always passing values through props.

Let’s import ThemeContext and LocaleContext, which I’ve already defined in another file. Probably the API that you’re most familiar with for consuming contexts, especially multiple contexts, is the Render Prop API. I’ll write it like this. Let me scroll down here. We use ThemeContext Consumer to get the topic. In my case, the theme is a simple style. I copied this code and put it all inside the Render Prop. Assign the className value to theme. Ok, very old style. (laughter.)

I also want to show the current language, so I’ll use LocaleContext Consumer. I’m going to render another line, and I’m going to copy and paste it in here, language. Language. Render here. Okay, so we can see the context running.

import React from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'Mary',
      surname: 'Poppins',
    }
    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleSurnameChange = this.handleSurnameChange.bind(this);
  }

  handleNameChange(e) {
    this.setState({
      name: e.target.value
    })
  }

  handleSurnameChange(e) {
    this.setState({
      surname: e.target.value
    })
  }

  render() {
    return (
      <ThemeContext.Consumer>
        {theme => (
          <section className={theme}>
            <Row label="Name">
              <input
                value={this.state.name}
                onChange={this.handleNameChange}
              />
            </Row>
            <Row label="Surname">
              <input
                value={this.state.surname}
                onChange={this.handleSurnameChange}
              />
            </Row>
            <LocaleContext.Consumer>
              {locale => (
                <Row label="Language"> {locale} </Row> )} </LocaleContext.Consumer> </section> )} </ThemeContext.Consumer> ); }}Copy the code

This is probably the most common consumption context case. In fact, we added a more convenient API to get it with React 16.6. Well, but this is your usual multi-context situation. So let’s see how we can use hooks to achieve the same functionality.

As we said, state is the base feature of React, so we can use useState to get state. So if we want to use context, first we need to import my context. Import ThemeContext and LocaleContext here. Now if I want to useContext in my component, I can use useContext. You can use ThemeContext to get the current topic and LocaleContext to get the current language. In this case, useContext doesn’t just read the context, it subscrises to the component, which updates when the context changes. But now useContext gives the current value of ThemeContext, theme, so I can assign it to className. So what we’re going to do is we’re going to add a sibling node, change the label to Language, put the locale here. (applause.)

import React, { useState, useContext } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);


  function handleNameChange(e) {
    setName(e.target.value);
  }

  function handleSurameChange(e) {
    setSurname(e.target.value);
  }

  return (
    <section className={theme}>
      <Row label="Name">
        <input
          value={name}
          onChange={handleNameChange}
        />
      </Row>
      <Row label="Surname">
        <input
          value={surname}
          onChange={handleSurnameChange}
        />
      </Row>
      <Row label="Language">
        {locale}
      </Row>
    </section>
  );
}
Copy the code

So, let’s compare these two methods. The example on the left is a traditional use of the Render Prop API. It shows very clearly what it’s doing. But it also contains a little bit of nesting, and the nesting problem will occur not just with context, but with any type of Render Prop API.

We can do the same with hooks. But the code will be flatter. So let’s see, we’re using two usecontexts, from which we get theme and locale. Then we can use them. So you might be wondering how does React know, for example, I’m calling two usestates here, how does React know which state corresponds to which useState I’m calling? The answer is that React depends on the order of these calls, which may be a little unusual.

In order for a hook to work correctly, we need to follow a rule when using a hook: it must not be called inside a condition, it must be at the top level of your component. For example, I do something like if props, and then I call useState hook inside the condition. We developed a Linter plugin that says ‘This is not the correct way to use hooks’.

Although this is an unusual limitation, it is very important for the hook to work properly and to make things more clear. I think you will like it and I will show you it in a moment.

How to handle side effects using classes and hooks

So, let’s go back to our class. Another thing you might want to do with class is lifecycle functions. The most common example of using lifecycle functions is to handle side effects, such as sending requests or calling certain browser apis to monitor DOM changes. But you can’t do anything like this during the render phase, because the DOM may not be rendered yet. Therefore, the way to handle side effects in React is to declare lifecycle methods such as componentDidMount.

So for example, well, let me show you this. So, you see at the top of the screen, the TAB shows the title React App. There’s actually a browser API that lets us update this title. Now we want the TAB title to be the person’s name, and to be able to change with the value I enter.

Now I’m going to initialize it. Well, there’s a browser API that can do this, and that’s document.title, which is equal to this.state.name plus a space plus this.state.surname. Now we can see that Mary Poppins is shown here. But if I edit the name, the title on the TAB is not automatically updated because I haven’t implemented the componentDitUpdate method. To make this side effect consistent with my rendering, I declare componentDitUpdate here, then copy the code and paste it in here. Now the title shows Mary Poppins, and if I start editing the input field, the TAB title is updated. This is an example of how we can handle side effects in a class.


+ componentDidMount() {
+ document.title = this.state.name + ' ' + this.state.surname;
+}

+ componentDidUpdate() {
+ document.title = this.state.name + ' ' + this.state.surname;
+}

Copy the code

So how can we implement the same function with hooks? The ability to handle side effects is another core feature of the React component. So if we want to use side effects, we need to import a useEffect from React. And then we’re going to tell React what to do with the DOM after React clears the component. Document.title = name + ‘+’ + surname = ‘given name’ + ‘+’ given name ‘+’ + ‘given name’ + ‘+’

- import React, { useState, useContext } from 'react';
+ import React, { useState, useContext, useEffect } from 'react';

+ useEffect(() => {
+ document.title = name + ' ' + surname;
+})

Copy the code

As you can see, the title of the page is Mary Poppins. If I start editing it, the page title will be updated.

So, by default, userEffect is performed after the initial render and after each update. So by default, the title of the page is the same as what’s being rendered here. You can opt out of this default behavior for performance reasons or if you have special logic. Ryan’s talk after me is going to talk a little bit about this.

So let’s compare these two methods. In the class on the left, we separate the logic into lifecycle methods with different names. That’s why we have componentDidMount and componentDitUpdate, which fire at different times. We sometimes repeat logic between them. You can put this logic in one function, but you still have to call it in two places, and remember to be consistent.

With effect hook, the default is consistent and you can choose not to use the default behavior. Note that in class we need access to this.state, so we need a special API to implement it. But in the effect example, you don’t really need a special API to access the state variable. Because it is already in the scope of this function, declared above. This is why effects are declared inside components. And that way we can also access the state variable and the context and assign values to them.

Two implementations of subscriptions

So, let’s go back to the familiar class example. Well, the other thing you might want to implement with the lifecycle method in your class is the subscription function. You might want to subscribe to some browser API, which will give you some values, such as the size of the window. You need the component to update as this state value changes. So the way we do this in class is, let’s say we want to, well, we want to monitor the width of the window.

I’m going to put width in state. Use the window.innerWidth browser API to initialize. And then I want to render it. Well, let’s copy and paste this code. I’ll change this to width. I’m going to render it here. This is changed to this.state.width. That’s the width of the window, not the width of Mary Poppins. (Laughter) I’m going to add a, well, I’m going to add an event listener, so we need to actually listen for this width change. So set window.addeventListener. I’ll listen for the resize event, handleResize. And then I need to declare this event. Here we update the width state to window.innerWidth. And then we need to bind it.

And then, well, then I need to unsubscribe, too. So I don’t want to cause a memory leak by keeping these subscriptions. I’d like to unsubscribe from this event. The way we handle this in a class is to create another lifecycle method called componentWillUnmount. I then copy and paste the logic here and change it to removeEventListener. We set up an event listener, and we removed the event listener. We can verify this by dragging the window. You can see that the width is changing. The operation is normal.

import React from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default class Greeting extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
        name: 'Mary',
        surname: 'Poppins',
+ width: window.innerWidth,
      }
      this.handleNameChange = this.handleNameChange.bind(this);
      this.handleSurnameChange = this.handleSurnameChange.bind(this);
+ this.handleResize = this.handleResize.bind(this);
  }

    componentDidMount() {
        document.title = this.state.name + ' ' + this.state.surname;
+ window.addEventListener('resize', handleResize);
    }

    componentDidUpdate() {
      document.title = this.state.name + ' ' + this.state.surname;
    }

+ componentWillUnmount() {
+ window.removeEventListener('resize', handleResize);
+}

+ handleResize() {
+ this.setState({
+ width: window.innerWidth
+});
+}

    handleNameChange(e) {
      this.setState({
        name: e.target.value
      })
    }

    handleSurnameChange(e) {
      this.setState({
        surname: e.target.value
      })
    }

  render() {
    return (
      <ThemeContext.Consumer>
        {theme => (
          <section className={theme}>
            <Row label="Name">
              <input
                value={this.state.name}
                onChange={this.handleNameChange}
              />
            </Row>
            <Row label="Surname">
              <input
                value={this.state.surname}
                onChange={this.handleSurnameChange}
              />
            </Row>
            <LocaleContext.Consumer>
              {locale => (
                <Row label="Language">
                  {locale}
                </Row>
              )}
            </LocaleContext.Consumer>
+ 
      
+ {this.state.width}
+ </section> )} </ThemeContext.Consumer> ); }}Copy the code

So let’s see how we can, how we can implement this functionality with hooks. Conceptually, the listening window width is independent of setting the document title. That’s why we didn’t put it in this useEffect. They are conceptually completely separate side effects, just as we can use useState multiple times to declare multiple state variables, and we can use useEffect multiple times to handle different side effects.

Here I want to subscribe to window.addeventListener, resize, handleResize. Then I need to save the current width state. So, I’ll declare another set of state variables. So let’s say width and setWidth. We use useState to set their initial value to window.innerWidth. Now I’m going to declare handleResize here. Because it’s not called anywhere else. Then use setWidth to set the current width. Well, I need to render it. So LET me copy and paste this Row. I’ll change this to width.

Finally I need to clear it after this effect. So I need to specify how to clear it. Conceptually, cleanup is also part of this effect. So this effect has a clear place. The order in which you can specify how to clear the subscription is that effect can choose to return a function. If it returns a function, React will call the function after effect to clear it. So this is where we unsubscribe. Ok, let’s verify that it works. Yeah! (applause.)

import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);

  useEffect(() => {
    document.title = name + ' ' + surname;
  })

+ const [width, setWidth] = useState(window.innerWidth);
+ useEffect(() => {
+ const handleResize = () => setWidth(window.innerWidth);
+ window.addEventListener('resize', handleResize);
+ return () => {
+ window.removeEventListener('resize', handleResize);
+};
+})function handleNameChange(e) { setName(e.target.value); } function handleSurameChange(e) { setSurname(e.target.value); } return ( <section className={theme}> <Row label="Name"> <input value={name} onChange={handleNameChange} /> </Row> <Row  label="Surname"> <input value={surname} onChange={handleSurnameChange} /> </Row> <Row label="Language"> {locale} </Row>+ 
      
+ {width}
+ 
    </section>
  );
}
Copy the code

So let’s compare these two methods. On the left, we use a familiar class component, and, well, there are no surprises here. We have some side effects, some related logic is separate: we can see that the title of the document is set here, but it is also set here. And we subscribe to Effect here, sorry, we subscribe to this event here, but we unsubscribe here. So these things need to be in sync with each other. And this method contains two unrelated methods on two unrelated lines. Therefore, it’s a little difficult for me to test them individually in the future. But it looks very familiar, which is good.

Then the code might not look familiar. But let’s look at what’s happening here. Well, in the hook, we separate the code not by the name of the lifecycle function, but by what the code is going to do. So we can see that this has an effect that we use to update the title of the document and that’s one thing that this component can do. There is another effect, which subscribes to the window’s resize event and updates the state whenever the window’s size changes. Then, well, this effect has a clear phase, and its purpose is to remove the effect so that React cancles the event listener to avoid memory leaks. If you’ve been watching closely, you may have noticed that since Effect runs after each render, we re-subscribe. There is a way to optimize this problem. The default is consistent, which is very important. If you, for example, use some prop here, I need to go and re-subscribe to a different ID, which is from props or something like that. But there is a way to optimize it, and you can opt out of this behavior. Ryan will talk about how to do that in his next talk.

Custom Hook

Okay, there’s one other thing I want to show you here. Now that the components are very large, this is not too much of a problem. We consider that you might be able to do more things in the Function component, the component will be bigger, but that’s perfectly fine. Yeah, but you might want to reuse some logic from other components, or you might want to extract common logic, or you might want to test separately. Interestingly, hook calls are actually function calls. And components are functions. So how do we normally share logic between two functions. We’re going to extract the common logic into another function. And that’s what I’m going to do. So let me copy and paste this code over here. I’m going to create a new function called useWindowWidth. And then I’ll paste it in here. We need the width inside the component so that we can render it. Because I need to return the current width in this function. Then we go back to the code above and change it to const Width = useWindowWidth. (Applause and cheers)

import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);
+ const width = useWindowWidth();

  useEffect(() => {
    document.title = name + ' ' + surname;
  })

- const [width, setWidth] = useState(window.innerWidth);
- useEffect(() => {
- const handleResize = () => setWidth(window.innerWidth);
- window.addEventListener('resize', handleResize);
- return () => {
- window.removeEventListener('resize', handleResize);
-};
-})function handleNameChange(e) { setName(e.target.value); } function handleSurameChange(e) { setSurname(e.target.value); } return ( <section className={theme}> <Row label="Name"> <input value={name} onChange={handleNameChange} /> </Row> <Row  label="Surname"> <input value={surname} onChange={handleSurnameChange} /> </Row> <Row label="Language"> {locale} </Row>  <Row label="Width"> {width} </Row> </section> ); }+function useWindowWidth() {
+ const [width, setWidth] = useState(window.innerWidth);
+ useEffect(() => {
+ const handleResize = () => setWidth(window.innerWidth);
+ window.addEventListener('resize', handleResize);
+ return () => {
+ window.removeEventListener('resize', handleResize);
+};
+})
+ return width;
+}
Copy the code

So what is this function? We’re not doing anything special, we’re just extracting the logic into a function. Uh, but there’s a convention. We call this function custom hook. By convention, the name of custom hook should start with “use”. There are two main reasons for this.

We’ll read your function name or change it. However, this is an important convention, because first naming custom hook with “use” allows usto automatically detect whether the first rule I mentioned before is violated: hook should not be used in the condition judgment. So if we don’t know which functions are hooks, then we can’t do automatic detection.

Another reason is that if you look at the component code, you might want to know if a function contains state. So this convention is important. Ok, so a function that starts with “use” means that the function is stateful.

Here the width variable gives us the current width and subscribes to its updates. We can go further if we want to. It may not be necessary in this example, but I want to give you an idea. Well, we might have a more complex function for setting the title of a document, and you want to be able to extract its logic and test it separately. So let me copy and paste this code over here. I can write a new Custom hook. I’m going to call this hook useDocumentTitle. Since name and surname have no meaning in context scope. I want to call the title, which is just a parameter, and since Custom Hooks are JavaScript functions, they can pass arguments, return values or not. I’m going to set the title parameter here. Then, in the component, use useDocumentTitle with name plus surname as the parameter.


import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);
  const width = useWindowWidth();
+ useDocumentTitle(name + ' ' + surname);

- useEffect(() => {
- document.title = name + ' ' + surname;
-})function handleNameChange(e) { setName(e.target.value); } function handleSurameChange(e) { setSurname(e.target.value); } return ( <section className={theme}> <Row label="Name"> <input value={name} onChange={handleNameChange} /> </Row> <Row  label="Surname"> <input value={surname} onChange={handleSurnameChange} /> </Row> <Row label="Language"> {locale} </Row>  <Row label="Width"> {width} </Row> </section> ); }+function useDocumentTitle(title) {
+ useEffect(() => {
+ document.title = title;
+})
+}

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  })
  return width;
}

Copy the code

In fact, I can go one step further. Not necessarily in this case, but by the same reason, maybe our input field is more complex, maybe we need to track whether the input field is in focus or out of focus, or whether the input field has been checked, committed, etc. Maybe there’s more logic we want to pull out of the component. Yeah, and they want to reduce duplication of code. There is already duplicate code, and the two event handlers are almost the same.

So if we, uh, I delete one of them, and then I extract the other. I’m going to create another new hook, and I’m going to call it useFormInput. This hook is my change handler. Now LET me copy and paste this statement here. This defines the state of the input box. Instead of name and setName. I’m going to change this to the more general value and setValue. I’m taking the initial value as an argument. HandleChange here, setValue here. So how do we use input fields in our components? We need to get the current value and change handlers. This is what we need to assign to the input field. So we return them in the hook. Well, return value and the onChange handleChange function. So if we go back to the component, we’re going to change name to useFormInput, the argument Mary. Here the name becomes an object, including value and onChange. So here’s what it says: first_name useFormInput, Poppins. Here it is changed to name.value and surname.value. Because those are the strings that we need. So I’m going to delete that, and I’m going to change that to the Spread attribute. Someone is laughing. [Laughter] Ok. Let’s verify that, yes, it works.

import React, { useState, useContext, useEffect } from 'react';
import Row from './Row';
import { ThemeContext, LocaleContext } from './context';

export default function Greeting(props) {
- const [name, setName] = useState('Mary');
- const [surname, setSurname] = useState('Poppins');
+ const name = useFormInput('Mary');
+ const surname = useFormInput('Poppins');
  const theme = useContext(ThemeContext);
  const locale = useContext(LocaleContext);
  const width = useWindowWidth();
- useDocumentTitle(name+ ' ' + surname);
+ useDocumentTitle(name.value + ' ' + surname.value);

- function handleNameChange(e) {
- setName(e.target.value);
-}

- function handleSurameChange(e) {
- setSurname(e.target.value);
-}

  return (
    <section className={theme}>
      <Row label="Name">
- 
- value={name}
- onChange={handleNameChange}
- />
+ 
      </Row>
      <Row label="Surname">
- 
- value={surname}
- onChange={handleSurnameChange}
- />
+ 
      </Row>
      <Row label="Language">
        {locale}
      </Row>
      <Row label="Width">
        {width}
      </Row>
    </section>
  );
}

+function useFormInput(initialValue) {
+ const [value, setValue] = useState(initialValue);
+ function handleChange(e) {
+ setValue(e.target.value);
+}
+ return {
+ value,
+ onChange: handleChange
+};
+}

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  })
}

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  })
  return width;
}
Copy the code

Each time we call the hook, its state is completely independent. This is because we rely on the order in which hooks are called, not by name or otherwise. So you can call the same hook multiple times. Each call gets its own local state.

Let’s compare these two approaches one last time. Well, in our familiar class component example on the left, we have some states in an object, some methods bound to it, and some logic spread over different declaration cycle methods, which is a series of event handlers. Well, we’re using the content from the context to render the content. Well, we’re pretty familiar with this situation.

Inside the right pane, it is different from the React component that we normally see. But it makes sense. Even if you don’t know how these functions are implemented. As you can see, this function is used to organize the input boxes, this function uses the context to get the topic and the native language, this function uses the window width and the document title, and then renders a sequence of contents. If we want to learn more, we can scroll down here, and you can see that this is how the input field works, this is how to set the title of the document, and this is how to set and subscribe to the width of the window. Maybe this is an NPM package, and you don’t really need to know how it’s implemented. We can call them inside components, or copy and paste them between components. Custom hooks are provided to give users the flexibility to create their own abstract functions. Custom hooks do not make your React tree too large and avoid “wrapping hell”. (applause.)

And importantly, these two examples are not separate applications. In fact, these two examples are in the same application. The reason I left this window open is to show that classes can work side by side with hooks. The hook represents our future expectations for React, well, but we don’t want to make changes that aren’t backwards compatible. We also need to make sure that the class works.

Hook proposal

Let’s go back to the slide. Ok, so this is a slide where you can tweet. (laughter.)

Today we show you the Hook proposal. Hooks allow us to use many features of React without using classes. And we’re not deprecating classes, but we’re giving you a new option not to write classes. We intend to complete all use cases using hooks instead of classes as soon as possible. There’s still a piece missing, but we’re working on it. Moreover, hooks allow you to reuse stateful logic, extract it from components, test it separately, and reuse it between different components, and avoid the “wrapping hell”.

Importantly, the hook is not a destructive change, it is fully backward compatible and strictly additive. You can find our documents about hooks from this URL. Well, we’d love to hear your feedback, the React community would love to hear what you think of Hook, um, whether you like it or not. And we find it hard to get counter-comments if we don’t let people actually use hooks. So we released the hook build to React 16.7 alpha. This is not a major release, this is a minor release. But in this alpha release, you can try using hooks. And we’ve been testing in the Facebook production environment for a month, so we don’t think there will be any major bugs. But the API of the hook can be adjusted based on your feedback. And I don’t recommend rewriting the entire application with hooks. Because first of all, the hook is still in the proposal stage. For the second reason, I personally think that the way of thinking using hook needs a change in thinking. Maybe you will be confused when you try to convert class components into hook writing at the beginning. But I recommend you try using hooks in your new code and let us know what you think. So, thank you. (applause.)

In our opinion, Hook represents the future of React. But I think it’s also indicative of the way we’re moving React forward. And that is we don’t do big rewrites. Well, we hope that the new models that we prefer can coexist with the old models so that we can make a gradual migration and embrace these new models, just as you gradually embraced React itself.

The Hook was there all along

That’s pretty much the end of my talk. But finally, I want to make a few personal points. I learned React four years ago. The first question I encountered was why I was using JSX.

Well, my second question is what exactly does the React Logo mean? The React project isn’t called Atom; it’s not a physics engine. Well, one explanation is that React is based on reactions — atoms are involved in chemical reactions — so the React Logo uses atoms.

But React has not officially acknowledged this claim. Well, I’ve found an explanation that makes more sense to me. The way I think about it is, we know that matter is made up of atoms. We’ve learned that the appearance and behavior of matter is determined by the original and its internal properties. React seems to me to be similar. You can use React to build the user interface by breaking it up into individual units called components. The appearance and behavior of the user interface is determined by these components and their internal properties.

Ironically, the word “Atom” literally means indivisible. When scientists first discovered atoms, they thought they were the smallest things we’d ever found. But then they discovered electrons, which are smaller particles inside atoms. It turns out that electrons actually describe how atoms work better.

I have a similar feeling about Hook. I feel hook is not a new feature. I feel that hooks provide the ability to use the React features we know, such as state, context, and lifecycle. And I feel hook is like a more intuitive representation of React. Hooks really explain how the component works inside the component. I feel like Hook has been hiding in our sight for four years. In fact, if you look at the React Logo, you can see the orbital of the electrons, and the hook seems to have been there all along. thank you (applause.)


portal

  • The most important React official documents: Introducing Hooks
  • React Today and Tomorrow (Picture version) – Part one
  • 【React Conf 2018】React Today and Tomorrow
  • React Today And Tomorrow Part I — Sophie’s Keynote
  • React Today And Tomorrow Part II — Dan’s Keynote
  • Dan: Making Sense of React Hooks
  • [原 文] Understand React Hooks
  • React Hooks in 30 minutes

If you find any errors in the translation or subtitle or other areas that need improvement, please go to the GitHub repository of this project to modify the English subtitle or translation and PR it. Thank you. Of course, this video also includes the third part of Ryan’s speech titled 90% Cleaner React with Hooks. Welcome interested partners to proofread and translate English subtitles.