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.
→ 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.
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.
</div> ); } 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.
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.
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.
/> </StaticContainer> </div> ); } We wrap the <ExpensiveChild /> with <StaticContainer> with a prop shouldUpdate equal to true when the component is not animating.
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.
<div style={{left: this.state.left}}> {this._child} </div> ); } 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.
<div style={{left: this.state.left}}> {this._child} </div> ); } 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.
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.
); } 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.
); } 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.
); } ????? 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.
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 :(((
); } 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,
); } 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.
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?
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.
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.
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.
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.
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.
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.
); } So, let’s see how we can implement Data Binding in React. The first thing we need to do is to change <div> to <Animated.div>. The default <div> doesn’t know about bindings so we need to write one that does.
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.
{...this.props} />; } } 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.
{ React.findDOMNode(this).style.left = value + 'px'; }); } render() { return <div {...this.props} />; } } 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.
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 :)
} render() { return <div {...this._props} />; } } We need to do one last change: <div> 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 <div> element.
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.