React & Redux - Performance tips

Sebastián Bogado

Francisco Puyó

Render pipeline

Render pipeline

Render pipeline

Render pipeline

Render pipeline

Render pipeline

mapping state to props

import Todo from './Todo.jsx';

const mapStateToProps = (state, ownProps) => {
  return {
    todo: state.todos[ownProps.id],
  }
}

export default connect(mapStateToProps, null)(Todo);

mapping state to props

const mapStateToProps = function mapStateToProps(state) {
    return state;
}

Estamos retornando todo el objeto state

class App extends React.Component {
  componentWillMount() {
    this.props.getUser(this.props.locationBeforeTransitions.pathname);
  }

  render() {
    return (
      <DocumentTitle title={Translations.PAGE_TITLE_DEFAULT}>
        <TransitionManager showSpinner={this.props.login.isFetchingUserFromToken}>
          { this.props.children }
        </TransitionManager>
      </DocumentTitle>
    );
  }
}

Y luego tenemos que buscar las propiedades que precisamos.

mapping state to props

- retornar sólo lo que  el componente necesita

 

 

 

const mapStateToProps = function mapStateToProps(state) {
    const pathname = state.route.locationBeforeTransitions.pathname;
    const isFetchingUserFromToken = state.login.isFetchingUserFromToken;
    return { pathname, isFetchingUserFromToken }
}
class App extends React.Component {
  componentWillMount() {
    this.props.getUser(this.props.pathname);
  }

  render() {
    return (
      <DocumentTitle title={Translations.PAGE_TITLE_DEFAULT}>
        <TransitionManager showSpinner={this.props.isFetchingUserFromToken}>
          { this.props.children }
        </TransitionManager>
      </DocumentTitle>
    );
  }
}

mapping state to props

La interfaz del componente queda mucho más clara y definida

class App extends React.Component {

  propTypes = {
    children: React.PropTypes.object,
    getUser: React.PropTypes.func,
    isFetchingUserFromToken: React.PropTypes.bool,
    pathName: React.PropTypes.string,
  }

  /*** Codigo del componente ***/
};

mapping state to props

const mapStateToProps = function mapStateToProps(state) {
    const pathname = state.route.locationBeforeTransitions.pathname;
    const isFetchingUserFromToken = state.login.isFetchingUserFromToken;
    return { pathname, isFetchingUserFromToken };
}
const mapStateToProps = function mapStateToProps(state) {
    return state
}
[
  {
    "_id": "5909e202bfc1e6d2ce07e931",
    "index": 0,
    "guid": "c7c6eb56-176b-4c95-8ada-f6dff11cea95",
    "isActive": false,
    "balance": "$1,426.78",
    "picture": "http://placehold.it/32x32",
    "age": 21,
    "eyeColor": "green",
    "name": "Rivers Ellison",
    "gender": "male",
    "company": "ISOLOGIX",
    "email": "riversellison@isologix.com",
    "phone": "+1 (907) 534-3406",
    "address": "677 Grace Court, Canby, Connecticut, 600",
    "about": "Sunt dolore occaecat veniam deserunt dolor officia. Ea aliqua sunt cupidatat ut laborum sunt nostrud adipisicing fugiat pariatur. Id mollit ipsum sint enim et. Laboris veniam dolore pariatur exercitation laborum anim dolore. Irure officia ex aliqua labore ea proident aute sunt nulla. Anim ex deserunt anim deserunt laborum est laborum labore cillum occaecat voluptate in aliquip.\r\n",
    "registered": "2015-02-16T04:38:44 +03:00",
    "latitude": -23.818232,
    "longitude": 101.703476,
    "tags": [
      "eu",
      "anim",
      "deserunt",
      "duis",
      "amet",
      "sit",
      "pariatur"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Parrish Villarreal"
      },
      {
        "id": 1,
        "name": "Betsy Mueller"
      },
      {
        "id": 2,
        "name": "Peck Salazar"
      }
    ],
    "greeting": "Hello, Rivers Ellison! You have 5 unread messages.",
    "favoriteFruit": "strawberry"
  },
  {
    "_id": "5909e2020ee0c00c5bd23942",
    "index": 1,
    "guid": "bcc55d66-66cf-4993-82b1-f7df8de8bc0d",
    "isActive": true,
    "balance": "$1,083.59",
    "picture": "http://placehold.it/32x32",
    "age": 32,
    "eyeColor": "brown",
    "name": "Hyde Snow",
    "gender": "male",
    "company": "STUCCO",
    "email": "hydesnow@stucco.com",
    "phone": "+1 (898) 517-2958",
    "address": "982 Neptune Court, Kylertown, New York, 8396",
    "about": "Et ipsum non cillum enim reprehenderit nisi minim pariatur esse. Minim ullamco tempor excepteur ad sint commodo esse et id non eu eu. Ex et mollit veniam ad. Irure do magna excepteur consectetur eu cillum adipisicing reprehenderit do. Ex ea voluptate enim est ad magna reprehenderit consequat mollit esse tempor.\r\n",
    "registered": "2014-08-11T05:08:14 +03:00",
    "latitude": -45.554253,
    "longitude": 24.409109,
    "tags": [
      "laborum",
      "fugiat",
      "enim",
      "aliqua",
      "fugiat",
      "irure",
      "tempor"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Jacobs Shannon"
      },
      {
        "id": 1,
        "name": "Jan Pena"
      },
      {
        "id": 2,
        "name": "Sallie Hamilton"
      }
    ],
    "greeting": "Hello, Hyde Snow! You have 10 unread messages.",
    "favoriteFruit": "strawberry"
  },
  {
    "_id": "5909e202abba6f2e7b1d6fdd",
    "index": 2,
    "guid": "54f40bef-54a1-4ab5-82e6-adce37226e68",
    "isActive": false,
    "balance": "$3,417.90",
    "picture": "http://placehold.it/32x32",
    "age": 28,
    "eyeColor": "blue",
    "name": "Merritt Bray",
    "gender": "male",
    "company": "UPLINX",
    "email": "merrittbray@uplinx.com",
    "phone": "+1 (840) 540-2695",
    "address": "529 Tapscott Street, Movico, Idaho, 954",
    "about": "In ad nisi reprehenderit esse commodo. Culpa ullamco ipsum enim voluptate labore consectetur amet esse et aliqua et. Nisi mollit sunt incididunt incididunt aliqua ipsum elit. Velit cupidatat velit est amet aliquip anim commodo amet minim dolore enim. Cupidatat sit adipisicing ullamco consequat tempor id in dolor eiusmod aliqua cillum est ullamco. Pariatur enim qui ad dolor officia laboris adipisicing in ea ut esse.\r\n",
    "registered": "2014-07-03T11:19:57 +03:00",
    "latitude": 24.491741,
    "longitude": -145.470934,
    "tags": [
      "deserunt",
      "sint",
      "eiusmod",
      "amet",
      "irure",
      "nulla",
      "Lorem"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Eleanor Delacruz"
      },
      {
        "id": 1,
        "name": "Turner Gross"
      },
      {
        "id": 2,
        "name": "Finch Mcneil"
      }
    ],
    "greeting": "Hello, Merritt Bray! You have 5 unread messages.",
    "favoriteFruit": "banana"
  },
  {
    "_id": "5909e20225e7ebac44e2d200",
    "index": 3,
    "guid": "1cdde354-6781-4ec0-bc49-33e9778e3313",
    "isActive": false,
    "balance": "$3,631.47",
    "picture": "http://placehold.it/32x32",
    "age": 38,
    "eyeColor": "green",
    "name": "Mcfarland Melton",
    "gender": "male",
    "company": "ASSISTIX",
    "email": "mcfarlandmelton@assistix.com",
    "phone": "+1 (841) 404-2770",
    "address": "525 Royce Street, Lewis, Tennessee, 7008",
    "about": "Laborum tempor officia sit consectetur voluptate deserunt et dolore sunt sit consectetur officia deserunt. Veniam amet sit eiusmod officia. Do sint ad fugiat sint deserunt reprehenderit aute irure. Dolore culpa non cupidatat in aute mollit. Et irure ut pariatur nisi sunt.\r\n",
    "registered": "2014-01-06T06:24:48 +03:00",
    "latitude": 85.011245,
    "longitude": 168.285699,
    "tags": [
      "sint",
      "aliqua",
      "sint",
      "officia",
      "laborum",
      "occaecat",
      "officia"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Grimes Holder"
      },
      {
        "id": 1,
        "name": "Marlene Hardy"
      },
      {
        "id": 2,
        "name": "Juliette Hines"
      }
    ],
    "greeting": "Hello, Mcfarland Melton! You have 10 unread messages.",
    "favoriteFruit": "strawberry"
  },
  {
    "_id": "5909e20212221cccf0809b8e",
    "index": 4,
    "guid": "fa43fc9e-cd11-42f9-98f3-cd6252b2cb2e",
    "isActive": false,
    "balance": "$2,045.81",
    "picture": "http://placehold.it/32x32",
    "age": 34,
    "eyeColor": "blue",
    "name": "Raquel Drake",
    "gender": "female",
    "company": "FURNAFIX",
    "email": "raqueldrake@furnafix.com",
    "phone": "+1 (954) 543-3334",
    "address": "588 Kenilworth Place, Smock, Hawaii, 6260",
    "about": "Sit irure minim consectetur eu. Non quis Lorem duis pariatur. Reprehenderit magna in voluptate anim.\r\n",
    "registered": "2017-04-04T07:44:44 +03:00",
    "latitude": 87.95867,
    "longitude": -64.982767,
    "tags": [
      "proident",
      "ad",
      "sit",
      "culpa",
      "id",
      "dolore",
      "nisi"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Constance Mcgowan"
      },
      {
        "id": 1,
        "name": "Marsha Becker"
      },
      {
        "id": 2,
        "name": "Tracey Kinney"
      }
    ],
    "greeting": "Hello, Raquel Drake! You have 4 unread messages.",
    "favoriteFruit": "strawberry"
  },
  {
    "_id": "5909e202f9e951a59069b6f5",
    "index": 5,
    "guid": "290617f8-c0f4-47ac-a162-c2f0d25f6c3f",
    "isActive": true,
    "balance": "$1,845.49",
    "picture": "http://placehold.it/32x32",
    "age": 27,
    "eyeColor": "green",
    "name": "Petty Mccall",
    "gender": "male",
    "company": "ZILLACOM",
    "email": "pettymccall@zillacom.com",
    "phone": "+1 (990) 553-2406",
    "address": "210 Colin Place, Biddle, Alabama, 697",
    "about": "Consectetur amet irure sit nisi quis magna eu nostrud aliquip. Do minim duis tempor id irure amet occaecat. Commodo elit exercitation ea ad anim deserunt voluptate sit qui pariatur exercitation velit occaecat. Pariatur cillum id occaecat esse. Ipsum sit non culpa dolor.\r\n",
    "registered": "2015-02-01T04:51:30 +03:00",
    "latitude": 39.788631,
    "longitude": -119.798066,
    "tags": [
      "incididunt",
      "pariatur",
      "commodo",
      "proident",
      "mollit",
      "culpa",
      "fugiat"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Mitzi Reyes"
      },
      {
        "id": 1,
        "name": "Ava Best"
      },
      {
        "id": 2,
        "name": "Celeste Weber"
      }
    ],
    "greeting": "Hello, Petty Mccall! You have 5 unread messages.",
    "favoriteFruit": "apple"
  },
  {
    "_id": "5909e20245a110dbfba5553c",
    "index": 6,
    "guid": "2ece983f-af14-4cf5-a8d0-b1cebe470f19",
    "isActive": false,
    "balance": "$2,377.02",
    "picture": "http://placehold.it/32x32",
    "age": 25,
    "eyeColor": "green",
    "name": "Laurie Valdez",
    "gender": "female",
    "company": "AQUAFIRE",
    "email": "laurievaldez@aquafire.com",
    "phone": "+1 (949) 498-2402",
    "address": "569 Hampton Avenue, Fairfield, Maryland, 684",
    "about": "Minim magna dolore officia velit exercitation magna aliquip labore. Cupidatat Lorem aliqua cillum quis voluptate exercitation sint. In dolor velit sunt sit commodo officia anim nisi qui excepteur pariatur.\r\n",
    "registered": "2015-04-06T11:12:55 +03:00",
    "latitude": 5.557238,
    "longitude": 102.973884,
    "tags": [
      "est",
      "cillum",
      "ea",
      "pariatur",
      "amet",
      "sunt",
      "nisi"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Vega Talley"
      },
      {
        "id": 1,
        "name": "Susie Holden"
      },
      {
        "id": 2,
        "name": "Krista Nolan"
      }
    ],
    "greeting": "Hello, Laurie Valdez! You have 2 unread messages.",
    "favoriteFruit": "banana"
  }
]
{
    "pathname": "/sandbox",
    "isFetchingUserFromToken": true,
}

mapping state to props

const getPersonStats = (person) => {
    const stats = expensiveFunction(person);
    const children = person.children ? 
        person.children.map(getPersonStats) : [];
    return {
        stats, 
        children,
    };
}

const getFamilyStats = (state) => state.familyPersons.map(getPersonStats);

const mapStateToProps = (state) => ({
    getFamilyTree: getFamilyStats(state),
});

connect(mapStateToProps)(FamilyTreeRecursiveComponent)

Reselect

import { createSelector } from 'reselect';

const getPersonStats = (person) => {
    const stats = expensiveFunction(person);
    const children = person.children ? 
        person.children.map(getPersonStats) : [];
    return {
        stats, 
        children,
    };
}

const getFamilyPersons = (state, familyId) => state.familyPersons[familyId];

const getFamilyStats = createSelector(
    getFamilyPersons,
    (persons) => persons.map(getPersonStats)
);

const mapStateToProps = (state, props) => ({
    getFamilyTree: getFamilyStats(state, props.familyId),
});

connect(mapStateToProps)(FamilyTreeRecursiveComponent)
import { getFamilyStats } from './FamilyTree.selector.js'

const mapStateToProps = (state, props) => ({
    getFamilyTree: getFamilyStats(state, props.familyId), 
});

connect(mapStateToProps)(FamilyTreeRecursiveComponent)

RESELECT WITH MULTIPLE INSTANCES

const makeGetFamilyStats = () => createSelector(
   getFamilyPersons,
   (persons) => persons.map(getPersonStats)
);

const makeMapStateToProps = () => {
    const getFamilyStats = makeGetFamilyStats();
    const mapStateToProps = (state, props) => ({
        getFamilyTree: getFamilyStats(state, props.familyId), 
    });
    return mapStateToProps;
};

connect(makeMapStateToProps)(FamilyTreeRecursiveComponent)

Mapping state to props

  • Pasar sólo las propiedades necesarias al componente

 

  • Usar funciones puras
  • Usar Reselect

ShouldComponentUpdate

SCU - pure components

  • implementa un SCU que realiza un chequeo shallow en las props y el state

 

  • en vez de extender de React.Component, extendemos de React.PureComponent

 

SCU - pure components


class ChildComponent extends React.Component {
  render() {
    return <span>{this.props.text}</span>;
  }
}

class ChildPureComponent extends React.PureComponent {
  render() {
    return <span>{this.props.text}</span>;
  }
}

class ParentComponent extends React.Component {
  render() {
    this.state.text = 'hello';
    setInterval(() => this.setState({text: 'hello'}), 1000)
  }

  render() {
    return (
      <ChildComponent text={this.state.text} />
      <ChildPureComponent text={this.state.text} />
    );
  }
}

SCU - custom

class GridToolbar extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <OptionsPanel>
        { this.props.toolbar }
        { this.props.displayOptions }
      </OptionsPanel>
    );
  }
}

GridToolbar.propTypes = {
  toolbar: React.PropTypes.node,
  displayOptions: React.PropTypes.node.isRequired,
};

GridToolbar.defaultProps = {
  toolbar: <Toolbar />,
  displayOptions: <DisplayOptions />,
};

Render function

render function - cortocircuito

render() {
  if (this.state.someCondition) return null;

  return (
    <div>
      <MyComplexChild />
      <MySecondComplexChild />
    </div>
  );
}

render function - No crear objetos ni funciones

render() {
  return (
    <div>
      <MyChild 
        items={this.props.items || []}
        handleChange={(val) => this.someValue = val;}
        handleSomething={handleSomething.bind(this)}
      />
    </div>
  );
}

render function - No crear objetos ni funciones

class MyComponent extends React.Component {
  handleChange = (val) => this.someValue = val
  handleSomething = ({ text }) => this.props.someCallback(text)
  
  render() {
    return (
      <div>
        <MyChild 
          items={this.props.items}
          handleChange={handleChange}
          handleSomething={handleSomething}
        />
      </div>
    );
  }
}


MyComponent.defaultProps = {
  items: [],
};

render function - usar keys cuando hay listas

<ul>
  <li>yasten</li>
  <li>danee</li>
</ul>
<ul>
  <li>yasten</li>
  <li>danee</li>
  <li>matse</li>
</ul>
<ul>
  <li>yasten</li>
  <li>danee</li>
</ul>
<ul>
  <li>matse</li>
  <li>yasten</li>
  <li>danee</li>
</ul>

render function - usar keys cuando hay listas

<ul>
  <li key="puyo">yasten</li>
  <li key="mata">danee</li>
</ul>
<ul>
  <li key="cesp">matse</li>
  <li key="puyo">yasten</li>
  <li key="mata">danee</li>
</ul>

React performance tools


 npm install --save-dev react-perf-addons

React performance tools

Perf.printInclusive();
Perf.printExclusive();

React performance tools

Perf.printWasted();

React Devtools - trace updates

React Devtools - trace updates

React Devtools - trace updates

highlights

  • mapStateToProps pura, precisa e inteligente. Si es necesario, HOF

  • PureComponents
  • Medir -> probar mejora -> medir
  • render's que no creen nuevos objetos

Preguntas?

Gracias!

React + Redux - Performance

By Francisco Puyó

React + Redux - Performance

  • 1,061