Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Perf Tuning w/ notes

React Perf Tuning w/ notes

Boston ReactJS Meetup 5/25/2016

gusvargas

May 26, 2016
Tweet

More Decks by gusvargas

Other Decks in Programming

Transcript

  1. GUS

  2. - Performance can be a pretty vague subject - I

    want to align on what this talk is about
  3. IMMUTABLE.JS MOBX REDUX RESELECT REACT INTERNALS - When you think

    of perf these are probably things that come to mind - This talk isn’t about any of those things
  4. IMMUTABLE.JS MOBX REDUX RESELECT REACT INTERNALS - Not about anything

    inside or outside of react - This talk is about leveraging what most of us already have at our disposal, assuming you’re using React.
  5. ISN’T REACT ALREADY FAST? - In most small to medium

    cases you’re probably not going to run into any perf issues - More components means more bigger virtual DOMs, more nodes to reconcile in between changes
  6. CONFIRM YOU HAVE A REAL PROBLEM - Before jumping in,

    confirm that you have a real problem
  7. CONFIRM YOU HAVE A REAL PROBLEM - Emphasis on real

    - If you’re currently working on a React app, there are prob optimizations you can make right now. There are probably inefficiencies there. - Doesn’t mean you should go after them - This stuff can be a time sink - Focus on building things for your users
  8. ! WHAT’S A REAL PROBLEM? - You observed some jankiness

    in your UI or maybe you already did some profiling and your updates are taking way over 16ms and you’re shooting for 60fps. - Users already reported that your app feels slow - But also when figuring out whether or not this is a problem worth pursuing..
  9. - The development build of React includes the overhead in

    checking propTypes - If you have the Elements Pane or React dev tools open - There is overhead involved in repainting those things - These things make a huge difference in perceived performance - Don’t fall victim to following red herrings
  10. TRIAGING WITH CPU PROFILING REACT PERF TOOLS - Now that

    you’ve confirmed you have a real problem - To gain a further understanding..
  11. - This is a tab in the Chrome dev tools

    - Click start and perform one of the action, then stop
  12. - You can then choose how you want to visualize

    the profile - We’re going to choose the chart view
  13. - This is called a flame chart - Stacked blocks

    are function calls - Width represents time on the CPU
  14. - Crap I don’t understand, crap I don’t understand -

    Now I see my code, I can see a change bubbling up through some of our style guide components - Hover over - some timezone stuff
  15. - You don’t have to know what all this stuff

    is but the flame chart gives you a good feel for where the problem may be - Obviously in this case the 5ms are not the bottle neck - Dig into why React is doing all that work on the right
  16. - You can switch the view to Heavy Bottom Up

    view. List all functions - Right now sorted by total - total is the time is took for that particular function and downstream function calls - If you sort by self that’ll tell you how long it took for just that function to run, not including any downstream function calls. - As you can see batchedUpdates is taking the longest which tells me React is spending a lot of time updating
  17. TRIAGING WITH CPU PROFILING REACT PERF TOOLS - Now that

    we know the problem is related to React and there is no single function we wrote that is the bottle neck. - We can dig into React specific perf issues using React Perf Tools - These come with the React addons
  18. Perf.start Perf.stop Perf.printWasted - This is the API you’re going

    to be working with - There is more to it but this is what’s going to give you the most value
  19. Perf.start Perf.stop Perf.printWasted - Prints a table showing all the

    wasted renders - A wasted render is just a render that didn’t end up having any net change on the DOM - Pure overhead
  20. - Here’s an example - There were 1000 instances of

    this wasted render and it took about 103ms - So now we know the problem - We’re over rendering when we don’t need to
  21. ! CONFIRM - Confirming that it’s a real problem -

    It’s not caused by a red herring
  22. ! CONFIRM TRIAGING - You can then gain further understanding

    - With the CPU Profiler and React perf tools
  23. WHAT DOES
 IT MEAN? - Who knows what memoization is?

    - It’s just an academic term for caching. - A memorized function is just a function that has a way of remembering something it’s already computed
  24. fib(100); fib(100); - Canonical example - here’s an example -

    We can say that this is a memoized fib function
  25. WHAT DOES THIS HAVE TO DO WITH REACT? - Memoization

    is a powerful technique you can use to make your own computations faster - You can also use it to make React render faster -
  26. ui(props, state); - We often say that React lets us

    treat our UI as a function of props and state
  27. ui(props, state); ui(props, state); - If this is true, I

    should be able to memoize it just like I did fib - In case you’re new to React or aren’t super familiar with how it works at a high level, let’s quickly go over how changes in your DOM occur
  28. … … vDOM - React maintains an in-memory rep of

    the real DOM, called virtual DOM
  29. … … … … … vDOM vDOM vDOM’ - When

    a change comes in, React wants to figure out the minimal DOM operations that need to take place to update the DOM - It computes an updated virtual DOM, performs a diffs while figuring out what changes need to occur
  30. … … … … … … … vDOM vDOM vDOM’

    vDOM - flushes those changes on the real DOM. Now the vDOM and real DOM match - Computing this diff and figuring out which imperative changes need to occur is expensive - React gives us a way to memoize our components so that it doesn’t spend time diffing nodes that haven’t changed. - There is a hook that lets us tell React, “you don’t need to spend time rendering me, I’m not going to change”
  31. shouldComponentUpdate( nextProps, nextState ) - That hook is called -

    This is a method you implement on your component - Execute logics that determines whether or not - Default implementation returns true - This means that by default React will always re-render your components
  32. … - To further illustrate this point - Lets say

    this is your UI - setState gets called on the top node
  33. … … - It will essentially prune that subtree. -

    And lets React focus on the part of the UI should update - Think of it as if you’re providing insider information to React so that it can more efficiently do its job
  34. shallowCompare/ PureRenderMixin - You don’t usually want to write custom

    shouldComponentUpdate methods. Subtle bugs - Theoretically, you shouldn’t be passing props to your components that it doesn’t need to render. - So you should be able to simply check for equality of props and state within shouldComponentUpdate - React add ons ships with these two utilities - shallowCompare is just a function that does exactly what it says - PureRenderMixin implements a shouldComponentUpdate that uses shallowCompare
  35. WHY ISN’T THAT THE DEFAULT? - If there is a

    general solutions, why isn’t this the default? - The nature of JavaScript references types like Objects, arrays - With ref types, you can never really be certain that anything is the same or different. - Example: Array push, mutate obj in memory, the reference to it will never change - React doesn’t assume you’re using an immutable methodology so it can’t auto opt you into shallow comparisons
  36. MEMOIZING EFFECTIVELY - Implementing shouldComponentUpdate should help - But you

    can still shoot yourself in the foot by accidentally breaking your shallow equal comparisons - There are a few things you can do to memoize effectively
  37. MAINTAIN IDENTITY // Bad <Item onClick={() => foo()} /> !

    ! // Good <Item onClick={this.handeClick} /> - This is an example of what you don’t want to do. - Creating a new function on every render - If Item implements shouldComponentUpdate, then this.props.onClick will never be equal to nextProps.onClick
  38. MAINTAIN IDENTITY // Bad <Item onClick={() => foo()} /> !

    ! // Good <Item onClick={this.handeClick} /> - Instead, reference the same function each time
  39. MAINTAIN IDENTITY // Bad <Item onClick={this.handleClick.bind( null, item.id )} />

    // Good <Item id={item.id} onClick={this.handleClick} /> ! ! - This is pretty much the same problem. - When you use bind inline like this, you’re still creating a new function on each render
  40. MAINTAIN IDENTITY // Bad <Item onClick={this.handleClick.bind( null, item.id )} />

    // Good <Item id={item.id} onClick={this.handleClick} /> ! ! - One thing you can do - allow the component to pass the id to you via the callback - At HubSpot we use a mixin or decorator function to give us a memoized partial function.
  41. MAINTAIN IDENTITY // Bad <Item onClick={this.handleClick.bind( null, item.id )} />

    // Good <Item onClick={this.partial( this.handleClick, item.id )} /> ! ! - Here the partial function is memoized with a cache backed by an immutable Map - So the first time it renders it’ll create my partial for me but on subsequent renders it’ll just return the same partial
  42. MAINTAIN IDENTITY // Bad <User friends={user.friends || []} /> !

    // Good (defaultProps) <User friends={user.friends} /> - Another thing you don’t want to do is implement default props - If users.friends is not defined then we’re creating a new array on every render.
  43. MAINTAIN IDENTITY // Bad <User friends={user.friends || []} /> !

    // Good (getDefaultProps) <User friends={user.friends} /> - Instead use defaultProps API so that you’re guaranteed the same reference as a default
  44. IMMUTABILITY - Immutability is something you might want to consider

    using - It gives you the ability to use reference equality as a heuristic within `shouldComponentUpdate` - which is best case scenario
  45. IMMUTABILITY // Bad handleAddItem(item) { this.state.items.push(item); this.setState({ items: this.state.items });

    } ! // Good handleAddItem(item) { this.setState({ items: this.state.items.concat(item) }); } - This is a bit of a contrived example. - In the first example, we’re mutating an existing reference to an array so this.state.items is always equal to - In the second example, we’re using concat to create a new array - Yes coding in this style is slower but it’s a trade off - You’re trading that overhead of creating a new array for the ability pruning chunks of your UI at render time.
  46. ABSTRACTION BOUNDARIES - Another way to memoize effectively - is

    to have good abstraction boundaries - This is just good practice in general
  47. ABSTRACTION BOUNDARIES (a, b) => { … } - This

    is just good practice in general - Lets say you have a function does performs two different computations
  48. ABSTRACTION BOUNDARIES (a, b) => { … } (a) =>

    { … } (b) => { … } - Break the function out into smaller memoized functions. - Even though the return value associated with combination of those arguments isn’t cached - You can still save cycles from having the individual computations cached.
  49. ABSTRACTION BOUNDARIES render() { return ( <div> <input type="text" onChange={this.handleChange}

    value={this.props.todoInputValue} /> <ul> {this.props.todos.map(todo => ( <Todo todo={todo} /> ))} </ul> </div> ); } - Same applies with components - This example doesn’t have great abstraction boundaries - Every time `todoInputValue` changes, React will have to re-render all of the todos.
  50. ABSTRACTION BOUNDARIES render() { return ( <div> <TodoInput value={this.props.todoInputValue} />

    <TodoList todos={this.props.todos} /> </div> ); } - Each component can be individually implement `shouldComponentUpdate` -
  51. UNDERSTANDING MEMOIZATION ALT APPROACHES - So that’s memoization - To

    recap - What it is, - How we can do it with React using `shouldComponentUpdate` - And how to do it effectively
  52. UNDERSTANDING MEMOIZATION ALT APPROACHES - Now we’ll move on to

    alt approaches - What other tricks can we pull out of our sleeve to get perf gains?
  53. COMPILE TIME OPTIMIZATIONS - There are a couple of plugins

    that ship with Babel that you can use - React >= 0.14
  54. react-constant-elements function render() { return <div className='foo' />; } -

    The plugin will statically analyze your components to find any subtrees that are considered constants - and hoist them
  55. react-constant-elements var foo = <div className="foo" />; function render() {

    return foo; } - React will detect that it’s the same exact ReactElement and bail out of any reconciling.
  56. react-constant-elements var foo = <div className="foo" />; function render() {

    return foo; } https://github.com/facebook/react/issues/3226 - If you’re interested about the details around how the plugin figures out what’s constant, there’s some good discussion here
  57. react-inline-elements <div className="foo"> {bar} <Baz key="baz" /> </div> - As

    you may know, JSX usually compiles to `React.createElement` calls. - Those calls just spit out objects which are descriptions of your React elements - This plugin will turn this
  58. react-inline-elements { type: 'div', props: { className: 'foo', children: [

    bar, { type: Baz, props: { }, key: 'baz', ref: null } ] }, key: null, ref: null } - into this at build time - Saves you the calls to `React.createElement` at runtime
  59. RENDER LESS STUFF - This might seem obvious but maybe

    you should - Easier said than done - There are a few ways to do this
  60. PAGINATION WINDOWING - Also knowing as virtualizing - This is

    essentially only rendering the components that are visible and only render others as the user scrolls - FixedDataTable, react-virtualized
  61. DECOUPLE LISTS VIEWS FROM ITEM MODELS - Let’s assume you’re

    implementing some flavor of Flux where you have some components hooked up to a store. - You have some concept of a store - a way to hook up components to the store
  62. [{ id: 1, text: 'foo', ...}, ...] … Connect(ListView) ListView

    ItemView - You might implement something like this - The problem with this is anyone of items changes in anyway, the shouldComponentUpdate implemented by ListView is going to break - It’ll have to iterate through all the items
  63. [1, 80, 43, 201, 30, ...] … … { id:

    1, text: ‘foo', … } Connect(ListView) ListView Connect(ItemView) - So an alternative that works especially nicely if your data is normalized on the client is to - Just pass ids to the ListView - Let item containers hold subscriptions to the store
  64. UNDERSTANDING MEMOIZATION - What it is and how it applies

    to React - This is probably what’s going to give you the most bang for your buck
  65. UNDERSTANDING MEMOIZATION ALT APPROACHES - Other useful techniques you can

    use to squeeze every last bit of perf you can get