• React, Inline Functions, and Performance
  • Originally written by Ryan Florence
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: wznonstop
  • Proofreader: MechanicianW, Sunhaokk

React inline functions and performance

My wife and I recently completed a spectacular renovation. We can’t wait to show people what’s new. We had my mother-in-law visit, and she walked into the beautifully decorated bedroom, looked up at the cleverly constructed window and said, “There are no blinds?” 😐

Our new bedroom; Oh, my God, it looks like a magazine photo. Also, there are no blinds.

I find the same emotion when I talk about React. I’ll be showing off some cool new features in the first session of the workshop. People always say, “Inline functions? I hear they’re slow.”

Not always, but in recent months this idea has come up every day. As a lecturer and codebase author, it’s exhausting. Unfortunately, I may have been a bit of a fool to just rant on Twitter before instead of writing something that might be insightful to others. So, I will try a better choice 😂.

What is an inline function

In React context, inline functions are defined when React is “rendered”. People often get confused about the two meanings of “render” in React. One is to fetch the React element from a component during an update (call the component’s render method). The other is to render to update the actual DOM structure. The “rendering” mentioned in this article refers to the first type.

The following are the chestnuts of some inline functions 🌰 :

class App extends Component {
  // ...
  render() {
    return<button onClick={() => {this.setState({clicked: clicked).true}) }} > Click! <Sidebar onToggle={(isOpen) => {this.setState({sidebarIsOpen:); isOpen }) }}/> {/* 3. A render prop callback */} <Route path="/topic/:id"
          render={({ match }) => (
            <div>
              <h1>{match.params.id}</h1>}
            </div>
          )
        />
      </div>
    )
  }
}
Copy the code

Premature optimization is the root of all evil

Before we go to the next step, we need to discuss how to optimize the program. Ask any performance expert and they will tell you not to optimize your program too soon. Yes, anyone with extensive performance tuning experience will tell you not to tune your code too early.

If you don’t measure it, you won’t even know if your optimizations are making the program better or worse.

I remember my friend Ralph Holzmann giving a talk about how Gzip works that solidified my view on this. He talked about an experiment he did with LABjs, an ancient script-loading library. You can watch 30:02 to 32:35 of this video to learn about it, or read on.

The LABjs source code was doing some embarrassing things in terms of performance at the time. Instead of using normal object notation (obj.foo), it stores the key in a string and uses square bracket notation to access the object (obj[stringForFoo]). The idea is that after miniaturization and gzip compression, unnaturally written code will be smaller than naturally written code. You can see it here.

Ralph fork the source code, not thinking about how to optimize for miniaturization and Gzip, but writing the code naturally to remove the optimized parts.

It turns out that by removing the “optimization section”, the file size was reduced by 5.3%! If you don’t measure it, you won’t even know if your optimizations are making the application better or worse!

Premature optimization can not only take up development time and damage clean code, but can even have the perverse effect of causing performance problems, as in the case of LABjs. If authors are constantly measuring, rather than just imagining performance issues, they can save development time and make the code simpler and perform better.

Don’t optimize too soon. Okay, back to React.

Why do people say inline functions are slow?

Two reasons: memory/garbage collection issues and shouldComponentUpdate.

Memory and garbage collection

First, people (and esLint Configs) are concerned about the memory and garbage collection costs associated with creating inline functions. Before arrow functions became common, a lot of code called bind inline, which historically didn’t work well. Such as:

<div>
  {stuff.map(function(thing) {
    <div>{thing.whatever}</div>
  }.bind(this)}
</div>
Copy the code

Function.prototype.bind’s performance problems are solved here, and the arrow functions are either native or converted from Babel to plain functions; In both cases, we can assume that it is not slow.

Remember, you don’t want to sit there and think, “I bet this code is slow.” You should code naturally and then measure it. If there are performance issues, fix them. We do not need to prove that an inline arrow function is fast, nor do we need someone else to prove that it is slow. Otherwise, this is premature optimization.

As far as I know, no one has analyzed their application to show that inline arrow functions are slow. It’s not even worth talking about until we get into the analysis – but anyway, I’ll offer a new idea 😝

If the cost of creating an inline function is so high that you need to use the ESLint rule to circumvent it, why would we want to shift that overhead to the initialized hot path?

class Dashboard extends Component {
  state = { handlingThings: false }
  
  constructor(props) {
    super(props)
    
    this.handleThings = () =>
      this.setState({ handlingThings: true}) this.handleStuff = () => { /* ... * / / /}bindThe overhead of the more expensive enclosing handleMoreStuff = this. HandleMoreStuff. Bind (this)}handleMoreStuff() {/ *... * /}render() {
    return (
      <div>
        {this.state.handlingThings ? (
          <div>
            <button onClick={this.handleStuff}/>
            <button onClick={this.handleMoreStuff}/>
          </div>
        ) : (
          <button onClick={this.handleThings}/>
        )}
      </div>
    )
  }
}
Copy the code

Because of premature optimization, we have slowed the initialization of components by a factor of 3! If all handlers are inline, then only one function needs to be created during initialization. Instead, we create three. We’re not measuring anything, so there’s no reason to think it’s a problem.

If you want to ignore this completely, then go ahead and make an ESLint rule that requires inline functions everywhere to speed up initial rendering 🤦🏾♀.

PureComponent and shouldComponentUpdate

This is the real crux of the problem. You can see a real performance boost by understanding two things: shouldComponentUpdate and JavaScript strictly equal comparison. If they are not well understood, they can inadvertently make React code harder to deal with in the name of performance optimization.

When you call setState, React compares the old React element to a new set of React elements (this is called r_econciliation_, which you can read about here), and then updates the actual DOM element with that information. Sometimes this process can be slow if you have a lot of elements to check (such as a large SVG). React provides an escape hatch for situations like this, called shouldComponentUpdate.

class Avatar extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return stuffChanged(this, nextProps, nextState))
  }
  
  render() {
    return //...
  }
}
Copy the code

If your component defines shouldComponentUpdate, it will ask shouldComponentUpdate if it has changed before React compares new elements with new ones. If false is returned, React will skip the element diff check directly, saving some time. If your components are large enough, this can have a considerable impact on performance.

The most common way to optimize components is to extend “react. PureComponent” instead of” react.component.ponent “. A PureComponent should compare props and state in shouldComponentUpdate so you don’t have to do it manually.

class Avatar extends React.PureComponent { ... }
Copy the code

When required to update, the Avatar uses a strict equality comparison between its props and state, hoping to speed things up.

Strict equality comparison

There are six basic types in JavaScript: String, number, Boolean, NULL, undefined, and symbol. When you do a “strict equality comparison” between two primitive types with the same value, you get a true value. For example 🌰:

const one = 1
const uno = 1
one === uno // true
Copy the code

When PureComponent compares props, it uses strict equality comparison. This works well for inlining raw values:

.

Prop comparisons only cause problems when there are non-primitive types — oh, sorry, types not types. There’s only one other type, and that’s Object. You ask functions and arrays? In fact, they are both objects.

Functions are regular objects with additional callable functionality.

  • Developer.mozilla.org/en-US/docs/…

Hahaha, it’s JavaScript. In any case, using strict equality checking on objects, even values that appear equal on the surface, are judged false (not equal) :

const one = { n: 1 }
const uno = { n: 1 }
one === uno // false
one === one // true
Copy the code

So, if you use an object inline in JSX, it invalidates the prop diff check for PureComponent and uses the more expensive diff check for React elements instead. The diff of the element becomes empty, wasting the time needed to compare the differences twice.

// first render <Avatar user={{id:'ryan'}}/> // render <Avatar user={{id:'ryan'}}/> // Prop Diff thinks something has changed because {}! == {} // A Diff examination of the elements (Reconciler) found no changesCopy the code

Because functions are objects, and the PureComponent checks props for strict equality, an inline function will always fail the DIff check of prop and turn to the diff check of elements in the Reconciler.

As you can see, it’s not just about inline functions. Function is simply the lead singer of object, function and array trilogy deduction promotion.

To keep shouldComponentUpdate happy, you must keep the function reference identifier. For experienced JavaScript developers, this isn’t bad. But Michael and I led a workshop for over 3,500 people with varying development experiences, and it wasn’t easy for many of them. The ES classes also provide no help to guide us through the various JavaScript paths:

Class Dashboard extends Component {constructor(props) {super(props) // Usebind? Slowing down initialization doesn't look good when you have 20bindI've seen your code, HandleStuff = this.handlestuff.bind (this) // _this is not elegant var _this = this this.handlestuff =function() {_this.setstate ({})} // If you know ES classes, you'll probably use the arrow // function (via Babel, or using modern browsers). It's not too hard but // putting all your handlers in constructors // not so good this.handleStuff = () => {this.setState({})}} // This is nice, but it's not JavaScript, At least not yet, so for now // we'll talk about how TC39 works and evaluate our draft // phase risk tolerance handleStuff = () => {}}Copy the code

Learning how to keep a reference to a function will lead to a surprisingly long discussion.

There is usually no reason to force people to do this unless there is an ESLint configuration that yells at them. I want to show that you can have both inline functions and improved performance. But first, I’d like to tell a performance-related story from my own experience.

My experience with PureComponent

When I first learned about PureRenderMixin (called PureComponent in early versions of React), I did a lot of testing to test the performance of my application. I then add PureRenderMixin to each component. When I adopted an optimized set of measurements, I wanted a cool story to tell about how fast things had changed.

To my surprise, my app slowed down 🤔.

Why is that? Think about it, if you have a Component, how many diff checks are there? If you have a PureComponent, how many diff checks are there? The answers were “only once” and “at least once, sometimes twice.” If a component changes frequently during updates, PureComponent will perform two diff checks instead of one (strict equality comparison of props and state in shouldComponentUpdate, and regular element diff checks). That means it’s usually slower and occasionally faster. Obviously, most of my components are changing most of the time, so overall, my application is slower. Oh oh 😯.

There is no silver bullet in performance. You have to measure.

Three scenarios

At the beginning of this article, I showed three types of inline functions. Now that we have some background, let’s discuss them one by one. But remember, put the PureComponent on the shelf until you have a metric to judge.

DOM component event handlers

< button onClick = {() = > enclosing setState (...). } >click</button>Copy the code

In general, buttons, Inputs, and other DOM components do nothing except setState in their event handlers. This makes the inline function the cleanest method in general. Instead of jumping through files looking for event handlers, they put content in the same place. The React community generally welcomes this approach.

The Button component (and all the other DOM components) is not even a PureComponent, so there is no shouldComponentUpdate reference flag here.

So, the only reason to think this process is slow is if you think that simply defining a function creates enough overhead to worry about. As we have discussed, this has not been proven anywhere. This is just an armchair performance assumption. Until proven, that’s fine.

A “custom event” or “action”

<Sidebar onToggle={(isOpen) => {
  this.setState({ sidebarIsOpen: isOpen })
}}/>
Copy the code

If Sidebar were PureComponent, we would break prop’s diff check. Again, because of the simplicity of the handler, it’s best to put them all in the same place.

Why would Sidebar need to diff check an event like onToggle? There are only two cases where you need to include a prop in the diff check of shouldComponentUpdate:

  1. You use prop to render
  2. You use prop to get incomponentWillReceiveProps.componentDidUpdateOr,componentWillUpdateIn some other way

Most on

prop don’t meet these requirements. As a result, most PureComponent uses result in multiple diff checks, forcing developers to unnecessarily maintain handler reference identifiers.

We should only perform diff checks on prop that will have an impact. This way, people can put their handlers in the same place and still get the performance boost they’re looking for (and since we care about performance, we want fewer diff checks!).

For most of the components, I recommend creating a PureComponentMinusHandlers class and inheritance, rather than inherited from PureComponent. It skips all checks on functions. Have your cake and eat it too.

Well, something like that.

If you receive a function and pass it directly to another component, it will not be updated. Take a look at this:

Form will pass down a function to button that is close to the prop it got From the App // 3. The App will try the mounting down after mountingset4. Form passes a new function to Button, This function and close to / / / / new prop phase 5. The Button will ignore the new function, and unable to / / update the click handler, thus submitted stale data class App extends ponent {state = {val: React.Com"one" }

  componentDidMount() {
    this.setState({ val: "two"})}render() {
    return <Form value={this.state.val} />
  }
}

const Form = props => (
  <Button
    onClick={() => {
      submit(props.value)
    }}
  />
)

class Button extends React.Component {
  shouldComponentUpdate() {// Let's pretend to compare everything except functionsreturn false
  }

  handleClick = () => this.props.onClick()

  render() {
    return<div> < buttonclick ={this.props. OnClick}> </ buttonclick ={() => </button> </button onClick={this.handleclick}> </button> </div>)}}Copy the code

This is a sandbox to run the application

So, if you like the idea of inherited from PureRenderWithoutHandlers, please make sure that you will never want to be in the diff handlers directly to ignore during the inspection passed to other components – you need to packing them in some way.

Now we must either maintain or avoid reference tokens! Welcome to performance tuning. At least in this approach, you must deal with optimizing the component, not the code that uses it.

I’ll be honest, this sample application was an edit I made after releasing Andrew Clark, and it caught my eye. At this point, you think I’m smart enough to know when to manage reference flags and when not to! 😂

A render prop

<Route
  path="/topic/:id"
  render={({ match }) => (
    <div>
      <h1>{match.params.id}</h1>}
    </div>
  )
/>
Copy the code

A prop for rendering is a pattern for creating a component for composing and managing shared state. (You can learn more here). Its contents are unknown to the component, for example 🌰 :

const App = (props) => (
  <div>
    <h1>Welcome, {props.name}</h1>
    <Route path="/"Render ={() => (<div> {/* prop.name is passed in from outside the route, it is not passed in as a prop, so the route cannot reliably become a PureComponent, It doesn't know what will be rendered inside the component */} <h1>Hey, {props. Name},let's get started!   )}/>  )Copy the code

This means that an inline prop function for rendering does not cause shouldComponentUpdate problems: it never has enough information to be a PureComponent.

So, the only objection goes back to the belief that simply defining a function is slow. To repeat the first example: there is no evidence to support this idea. This is just an armchair performance assumption.

conclusion

  1. Code naturally, design code
  2. Measure your interactions and find out where the slowness is. Here’s how.
  3. Use only when neededPureComponentshouldComponentUpdateAvoid using prop functions (unless they are used in a lifecycle hook function for some purpose).

If you really believe that premature optimization is a bad idea, then you don’t need to prove that inline functions are fast, you need to prove that they are slow.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.