Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Hey everyone, my name is Christopher Chedeau also know as vjeux on the internet, I’m working on React Native. The goal of this project is to let you write high quality mobile applications that are indistinguishable from native apps. One of the big challenge when using React on mobile was animations. Unlike on desktop, animations are everywhere and you have a hard 16ms time constraint on a slower device. In this talk, I’m going to explain all the approaches we’ve tried to get high performance animations in React Native.

Slide 3

Slide 3 text

Element Caching ↑ ↑ ↓ ↓ ← → ← → B A Raw DOM Mutations shouldComponentUpdate I’m going to introduce you five techniques that you can add to your toolbox to optimize your React application. I’ll introduce the “secret” one that we use in the React Native Animated library.

Slide 4

Slide 4 text

getInitialState() { return {left: 0}; } We’re going to focus on one simple animation for the purpose of this talk: sliding an element in the screen. If you follow the React tutorial, you’re going to keep the animation progress in a state variable.

Slide 5

Slide 5 text

getInitialState() { return {left: 0}; }
 render() {
 return (
);
 } Then, we use inline style in order to render the element.

Slide 6

Slide 6 text


 render() {
 return (
);
 } 
 onChange(value) {
 this.setState({left: value});
 } And finally, on requestAnimationFrame, we call onChange with the new value and use setState to re-render the component.

Slide 7

Slide 7 text

setState Let see the performance characteristics of this approach. We call set state on the component.

Slide 8

Slide 8 text

React is going to call render on this component.

Slide 9

Slide 9 text

And also calls render on all the components underneath.

Slide 10

Slide 10 text

This may sound crazy to have the default being to re-render the entire subtree. But it’s actually a very good one. In order to improve the performance of a system, you need to add more constraints and therefore put a burden on the developer. It turns out that most of your application is usually not performance sensitive. In cases where you run into a performance issue, then it’s time to make those trade-offs.

Slide 11

Slide 11 text

shouldComponentUpdate The recommended way to optimize this example is to implement shouldComponentUpdate on the children. This is a way to tell React that we know that nothing changed.

Slide 12

Slide 12 text

You Person A Person B While it solves most performance issues, it is not straightforward to implement in practice. The reason is social: the children are often not owned by the same person. Implementing shouldComponentUpdate requires to refactor the component to ensure that it uses immutable data structures and avoid bound functions. In itself this is not very hard, but now you need to coordinate with 2 other people to animate that component.

Slide 13

Slide 13 text

You Person A Person B This breaks one of the key property of React: isolation. If you want to animate a component, you shouldn’t have to touch other components.

Slide 14

Slide 14 text

You Person A Person B What we really want is to move that decision of when to re-render to the parent: do not re-render anything while animating. This way we can preserve isolation.

Slide 15

Slide 15 text

Turns out that it’s possible to implement this using a component that we call StaticContainer.

Slide 16

Slide 16 text

render() {
 return (
);
 } We wrap the with with a prop shouldUpdate equal to true when the component is not animating.

Slide 17

Slide 17 text

class StaticContainer extends React.Component { render() { return this.props.children; } shouldComponentUpdate(nextProps) { return nextProps.shouldUpdate; } } The implementation is simple: we create a component

Slide 18

Slide 18 text

class StaticContainer extends React.Component { render() { return this.props.children; } shouldComponentUpdate(nextProps) { return nextProps.shouldUpdate; } } that just forwards all the children in render

Slide 19

Slide 19 text

class StaticContainer extends React.Component { render() { return this.props.children; } shouldComponentUpdate(nextProps) { return nextProps.shouldUpdate; } } and uses shouldUpdate prop to implement shouldComponentUpdate

Slide 20

Slide 20 text

Element Caching It turns out that there is another way in React to achieve the same result with a technique called Element Caching

Slide 21

Slide 21 text

;
 
 Element To understand the technique, we first need to understand what a React Element is. It’s the thing you get when you use a JSX tag. You can also get one using React.createElement() if you are not using JSX.

Slide 22

Slide 22 text

render() { this._child = this._child || ;
 return (
{this._child}
);
 } Element During the diff algorithm, when React compares two elements (before vs after), if those two elements are === the same, then React is going to bail and not re-render it. Knowing this, we can cache the element inside of an instance variable and always return the same one.

Slide 23

Slide 23 text

render() { this._child = this._child || ;
 return (
{this._child}
);
 } Element Race Condition With those two techniques, we’ve been able to solve the performance issues of our application, unfortunately they are not a silver bullet (otherwise we would have made them the default!). In this case, we are now susceptible to race conditions. If an update came in during the animation, we’re going to skip it. If we do not re- render with the new value at the end of the animation, then we’re going to show stale data. And worse, if sometimes in the future the components gets re- rendered, then the new value is going to flash in for no obvious reason.

Slide 24

Slide 24 text

Raw DOM Mutations If we go back to the initial problem, we just want to update a single attribute on a single node. We went to great length to work around the React diffing algorithm but really, we don’t want React to re- render anything or diff anything. We just want to set the attribute. Turns out that React gives you a way to implement that.

Slide 25

Slide 25 text

findDOMNode( ).style.left = '10px'; You can call findDOMNode in order to get the real DOM node and then do raw DOM manipulation.

Slide 26

Slide 26 text

render() { return (
);
 } onUpdate(value) { React.findDOMNode(this).style.left = value + 'px'; } This is the code that makes it work, nothing difficult. It gives us the most performant way to solve the problem at hand: we just set the value when it updates.

Slide 27

Slide 27 text

render() { return (
);
 } onUpdate(value) { React.findDOMNode(this).style.left = value + 'px'; } Unfortunately, this is no free lunch either. You’re now facing the $1 billion mistake: null references. If the component is unmounted while the animation is still running, then you’re going to try to set the value on a DOM node that doesn’t exist anymore.

Slide 28

Slide 28 text

render() { return (
);
 } ????? And, you’re not exempt from the race conditions either. In this case, React normal rendering process is going to have one value for the attribute and when you use raw DOM manipulation you’re going to have another one. If the two values are different, then whoever gets in last wins.

Slide 29

Slide 29 text

↑ ↑ ↓ ↓ ← → ← → B A So far, we’ve seen three techniques that let us get the performance we desired, but the trade-off is that we lost some of the strong guarantees that React give us. I love React because it eliminated a huge amount of bugs, but those surfaced again in React Native on code that implemented animations :(((

Slide 30

Slide 30 text

render() {
 return (
);
 } I wanted to figure out if there was a way to get the performance of raw DOM manipulation but with the safety guarantees of React. If we go back to the initial example,

Slide 31

Slide 31 text

render() {
 return (
);
 } then as a developer we explicitly write that [div style left] has the value [this.state.left].

Slide 32

Slide 32 text

render() {
 return (
);
 } onChange(value) {
 this.setState({left: value});
 } React.findDOMNode(this).style.left = value + 'px'; So, there’s no reason why React cannot be smart enough to trigger a raw DOM mutation whenever we change the left value.

Slide 33

Slide 33 text

Before we go to the next slide, I want you to take a moment and forget everything you know about React Keep an open mind

Slide 34

Slide 34 text

Data Binding This technique actually has a name: Data Binding. Now you probably wonder why Pete Hunt went on stage and told everyone that we chose *not* to use Data Binding for React. Was he wrong? Am I delusional?

Slide 35

Slide 35 text

Memory Initialization cost O(1) updates per binding ( Data Binding To answer those questions, let’s see what are the characteristics of Data Binding. We have to pay some memory and initialization time for every single binding. But as a result we get ultra-fast O(1) updates as we know for each value all the places where it is being used.

Slide 36

Slide 36 text

Model >> View User Interface Before React, Data Binding was used to drive the entire UI. It turns out that most of the time, your model is much bigger than what you render. You probably have data in cache, implement some sort of pagination… So having to pay the cost of all the data you have in cache when you only display a handful is wasteful.

Slide 37

Slide 37 text

Most values do not change When they change, in big blocks User Interface The second realization is that the most frequent style of updates is to tear down an entire subview and replace it by another one. You have in places just small updates that do happen, but with React, having this coarse update mechanism is usually fast enough.

Slide 38

Slide 38 text

Startup time matters a lot User Interface The trade-off that Data Binding makes is to have more work done at startup in order to make updates faster. But, for a website like Facebook, startup time is absolutely critical. So React model is better suited.

Slide 39

Slide 39 text

Only a few attributes Animations Now, if we take the same constraints and apply them to Animations, then we get a very different story. Most of the time, you are only animating a few elements at a time and just changing a few attributes such as left or opacity. Creating a dozen of bindings is not very expensive.

Slide 40

Slide 40 text

Frame performance >>>>> Startup time Animations In the case of animations, we have a —very hard— deadline, we need to squeeze every bit of performance we can and Data Binding gives us some very nice properties in that regard.

Slide 41

Slide 41 text

render() { return ( );
 } So, let’s see how we can implement Data Binding in React. The first thing we need to do is to change
to . The default
doesn’t know about bindings so we need to write one that does.

Slide 42

Slide 42 text

);
 } getInitialState() { return {left: new Animated.Value(0)}; } onUpdate(value) { this.state.left.setValue(value); } In the same vein, we cannot use just a regular number, we need to wrap it into an object that we can listen for changes. In React Native, we call it new Animated.Value and you can call setValue on it. I have hopes that we can add an optimization in Babel that would allow us to do that as an optimization pass for regular React code, but it’s very much a theory at this point.

Slide 43

Slide 43 text

Animated.div = class extends React.Component { render() {
 return
;
 }
 } Okay, so now let’s implement the special div that knows about binding. The first thing we need to do is to create a wrapper component that just forwards all the props. This can be used as if it was a normal div.

Slide 44

Slide 44 text

Animated.div = class extends React.Component { componentWillReceiveProps(nextProps) { nextProps.style.left.onChange(value => { React.findDOMNode(this).style.left = value + 'px'; });
 } render() {
 return
;
 }
 } Now, every time we receive some props, we want to add our binding: whenever the value changes, we want to trigger the raw DOM mutation.

Slide 45

Slide 45 text

Animated.div = class extends React.Component { componentWillUnmount() {
 this.props.style.left.removeAllListeners();
 } componentWillReceiveProps(nextProps) {
 this.props.style.left.removeAllListeners(); nextProps.left.onChange(value => { React.findDOMNode(this).style.left = value + 'px'; }); } But, if we were to implement it as is, it would have crazy memory leaks: we never clean up bindings, so every time we render we allocate new bindings! Fortunately, React lifecycle methods have us covered. We can clean up in componentWillUnmount and before receiving new props :)

Slide 46

Slide 46 text


 this._props = React.addons.update( nextProps, {style: {left: {$set: nextProps.left.getValue()}}}, ); }
 render() {
 return
;
 } } We need to do one last change:
doesn’t know about bindings. Instead, we want to replace the binding with the real value (just a number here) before sending it to the
element.

Slide 47

Slide 47 text

Element Caching Data Binding Raw DOM Mutations shouldComponentUpdate If you have some performance issues with your React app, I highly encourage to evaluate those techniques and see if the trade-offs make sense.

Slide 48

Slide 48 text

React by defaults it provides a reasonably good way to update the DOM after some change. But what is mind blowing to me is the fact that it provides the hooks needed to implement different strategies that are better suited for your use case. Not only that, but you can encapsulate those within a component and use it like a regular component. No one has to know that your component uses data binding in order to drive animations instead of the diff algorithm.

Slide 49

Slide 49 text

Thanks! Christopher “vjeux” Chedeau