Skip to content

Latest commit

 

History

History
146 lines (106 loc) · 5.32 KB

JSX-Spread.md

File metadata and controls

146 lines (106 loc) · 5.32 KB

You Can Spread Props In JSX, But Should You?

When I first learned about spreading props in JSX, I was thrilled! It is just so convenient to pass props with <MyComponent {...this.props} />, and override props defined after the spread, like <MyComponent {...this.props} text='override text prop' />. I knew it made for a great developer experience, but I always wondered if it came with a cost. How does React handle it? Does it affect performance?

Well, it took me far too long, but I've finally answered my questions. You're welcome to play around with this code using Babel's online repl.

Transpiling Explicit Props

Let's start with our control.

const Comp = (props) => (
  <div hi='bye' yes='no' />
)

// transpiles to:

var Comp = function Comp(props) {
  return React.createElement('div', { hi: 'bye', yes: 'no' });
};

Cool, this shows how JSX gets converted to React.createElement() and made into valid javascript. Also note that props are converted and passed as an inline object literal.

Transpiling Spread Props

const someProps = {
  one: 1,
  two: 2,
}

const OnlySpread = (props) => (
  <div {...someProps} />
)

Given the above, how do you expect OnlySpread to be transpiled?

var someProps = {
  one: 1,
  two: 2
};

var OnlySpread = function OnlySpread(props) {
  return React.createElement('div', someProps);
};

Oooh, nice! It just uses the already created object as its props. No cloning, just passing by reference.

Transpiling Spread AND Explicit Props

const someProps = {
  one: 1,
  two: 2,
}

const SpreadAndExplicit = (props) => (
  <div {...someProps} hi='bye' />
)

Given the above, how do you expect SpreadAndExplicit to be transpiled?

var someProps = {
  one: 1,
  two: 2
};

var SpreadAndExplicit = function SpreadAndExplicit(props) {
  return React.createElement('div', Object.assign({}, someProps, { hi: 'bye' }));
};

Bummer! No magic here. It just converts the explicit props to an object literal, then uses Object.assign() to merge the two.

Object.assign() is a micro-perf hit compared to creating an object literal, as this esbench shows. So, it is better to explicitly pass all props and not use a spread at all.

const AllExplicit = (props) => (
  <div one={1} two={2} hi='bye' />
)

// transpiles to:

var AllExplicit = function AllExplicit(props) {
  return React.createElement('div', { one: 1, two: 2, hi: 'bye' });
};

We're back to a single object literal. Micro-perf win!

Transpiling Multiple Spread Props

To be pedantic, lets see what happens when we spread two objects.

const someProps = {
  one: 1,
  two: 2,
}

const TwoSpread = (props) => (
  <div {...someProps} {...props} />
)

// transpiles to:

var TwoSpread = function TwoSpread(props) {
  return React.createElement('div', Object.assign({}, someProps, props));
};

Again, no magic. Not surprised that it still uses Object.assign().

More to Consider

From our exploration, we could conclude that JSX spreads are good if and only if they are not accompanied with other props.

// GOOD
<div {...this.props} />

// BAD
<div {...this.props} two={2} />

// BAD
<div {...this.props} {...otherProps} />

But there is more to consider.

You see, spread is a deoptimization for two babel transforms used on production bundles: transform-react-inline-elements and transform-react-constant-elements. I want to say this can be fixed by ordering Babel's plugins properly, but this thread explains that an inline object literal (not an object reference) is required for the optimization. Even if transform-react-inline-elements runs after <div {...someProps} /> gets converted to React.createElement('div', someProps), it will not inline it. Why? Because someProps can contain a ref, which this transform can not optimize for. Even if the referenced object does not currently contain a ref, there is no way to guarantee that it won't have one in the future.

So, Should You Spread Props In JSX?

If you prioritize developer experience over performance, then go for it. Otherwise, avoid it where you can.

// BAD :(
<div {...this.props} />

Maybe Prepack will enable performant spreads in the future?

Note: I publish these to learn from your responses! Please let me know if you have any thoughts on the subject.