SlideShare a Scribd company logo
1 of 63
From Rendering to Animations
React Native Performance
A story…
=+ Power
Holy grail of mobile development
Holy grail of mobile development
Learn Once, Write Anywhere
Power?
Fear and Loathing in React-Native
Morale => Rethinking React-Native
• Mobile users have high standards in terms of app responsiveness and
animations
• Not thinking about performance is almost certainly would result in
slow app
• Rarely users write about performance directly
• Lots of performance cases can be solved on the React-Native level
Who am I
• Bob Ilya Ivanov
• React-Native 3 years
• React.js 5 years
• Software Development 8 years
Why this talk
Performance
Correctness
Developer
Experience
is hard
Agenda
• Rendering
• Dealing with slow code
• Animations
Agenda
• Rendering
• Dealing with slow code
• Animations
What we are
going to build
• Why should we care
• Where should we focus
• How to achive fewer renders
Rendering
Why
Why
Press – 1.72s
Changes – 2.24s
Response – 520ms
QuickTime Player on MacOS
Shortcut ⌘ + T
Why
Why
Press – 2.34s
Changes – 4.18s
Response – 1840ms
Why
• 100 Artists – 520ms
• 300 Artists – 1840ms
• Golden rule – 200ms
Why
Slow performance stacks
Why
Why – summary
• Performance is already slow for small sample 100 artists (500ms)
• Performance degrades with more data
• Slow responsiveness stack because of JS single threaded
Where
• 1 to * relationships in our hierarchy
• <FlatList or {items.map(item => …} where the count is unknown or varied
• No control over the number of items in our hierarchy
• No control the density of state updates
Where
Page
Artist 1
StatusBar
Box 1 Box 2... Box N
Artist 2 …
StatusBar2
Artist N
StatusBarN
Update Complexity (big O for onPress)
• Current – O(n * m)
• n – number of artists
• m – number of boxes
• Goal – O(1)
Page
Artist 1
StatusBar
Box 1 Box 2... Box M
Artist 2 …
StatusBar2
Artist N
StatusBarN
Where – summary
• Look for FlatList or .map in your codebase
• Look for content of variable length
How
• PureComponent
• React.memo
• shouldComponentUpdate
sholdComponentUpdate
render?
propsstate
shouldComponentUpdate(nextProps, nextState, nextContext) {
return this.props.id !== nextProps.id;
}
conte
xt
PureComponent
Used for classes. Shallow compare === for all properties of props, state
and context
React.memo
Used for function. Shallow compare === for all properties of props and
context
ArtistInfo component
renderArtist(artist) {
const artistStyle = [
styles.artistContainer,
artist.isSelected && styles.selectedArtist
];
return (
<ArtistInfo
style={artistStyle}
onPress={() => this.onPress(artist.id)}
fontStyle={{ fontSize: 23 }}
popularity={artist.popularity} //0..1
/>
);
}
What does it mean to break PureComponent
class MyPureComponent
extends PureComponent {
}
const MyPureComponent =
React.memo(/.../);OR
<MyPureComponent
myParameter={{property: this.state.myValue}}
/>
const a = {} === {}; //false
const b = {property: 1} === {property: 1}; //false
const c = [] === []; //false
const d = (() => undefined) === (() => undefined); //false
const e = '1' === '1'; //true
const f = 1 === 1; //true
This completely ignores shallow compare done by PureComponent because
this.props.myParameter === nextProps.myParameter
will always be false
Fixing onPress
onPress(id) {
this.setState(...)
}
renderArtist(artist) {
return (
<ArtistInfo
onPress={() => this.onPress(artist.id)}
/>
);
}
class ArtistInfo extends PureComponent {
render() {
//...
<TouchableOpacity onPress={this.props.onPress}
Fixing onPress
onPress(id) {
this.setState(...)
}
renderArtist(artist) {
return (
<ArtistInfo
id={artist.id}
onPress={this.onPress}
/>
);
}
class ArtistInfo extends PureComponent {
render() {
//...
<TouchableOpacity
onPress={() => this.props.onPress(this.props.id)}
Fixing onPress
onPress(id) {
this.setState(...)
// ERROR: setState is not a function
}
renderArtist(artist) {
return (
<ArtistInfo
id={artist.id}
onPress={this.onPress}
/>
);
}
class ArtistInfo extends PureComponent {
render() {
//...
<TouchableOpacity
onPress={() => this.props.onPress(this.props.id)}
Fixing onPress
onPress = (id) => {
// works fine...
// Breaks Hot Reloading
}
renderArtist(artist) {
return (
<ArtistInfo
id={artist.id}
onPress={this.onPress}
/>
);
}
Performance
Correctness
Developer
Experience
https://github.com/facebook/react-native/issues/10991
Solutions
• Bind all methods in constructor (not really an option)
• auto-bind
• babel-plugin-functional-hmr
constructor(props){
super(props);
this.onPress = this.onPress.bind(this);
//...
}
import autoBind from 'auto-bind’;
constructor(props){
super(props);
autoBind(this);
}
Fixing styles
renderArtist(artist) {
const artistStyle = [
styles.artistContainer,
artist.isSelected && styles.selectedArtist
];
return (
<ArtistInfo
style={artistStyle}
fontStyle={{ fontSize: 23 }}
/>
);
}
const styles = StyleSheet.create({
artistContainer: {
//commonStyles
},
selectedArtist: {
//specific styles
}
});
Fixing styles const styles = StyleSheet.create({
artistContainer: {
//commonStyles
},
selectedArtist: {
//specific styles
},
artistFont:{
fontSize: 23
}
});
renderArtist(artist) {
const artistStyle = [
styles.artistContainer,
artist.isSelected && styles.selectedArtist
];
return (
<ArtistInfo
style={artistStyle}
fontStyle={styles.artistFont}
/>
);
}
Fixing styles const styles = StyleSheet.create({
artistContainer: {
//commonStyles
},
selectedArtist: {
//specific styles
},
artistFont:{
fontSize: 23
}
});
selectedArtistStyle = [
styles.artistContainer,
styles.selectedArtist,
];
renderArtist(artist) {
const artistStyle = artist.isSelected
? this.selectedArtistStyle
: styles.artistContainer;
return (
<ArtistInfo
style={artistStyle}
fontStyle={styles.artistFont}
/>
);
}
Common render troublemakers
• {}
• []
• () => this.handle(someData)
Results
Results
Careless O(n * m)
100 Artists – 520ms
300 Artists – 1840ms
Optimized O(1)
100 Artists – 130ms
300 Artists – 150ms
Fixing popularity
renderArtist(artist) {
return (
<ArtistInfo
popularity={artist.popularity} //0..1
/>
);
}
• Popularity is based on the most popular artist in the current list
• Artist A 3 000 000 – 1
• Artist B 20 000 – 0.00666666667
• Artist C 1 020 0 00 – 0.34
• Artist D 3 000 001 – 1
popularity = artist.listeners / maxListeners;
3 000 00020 000
0, 0.00000001, 0.00000002, … 1
• Popularity is based on the most popular artist in the current list
• Artist A 3 000 000 – 1 needs update to 0.999999667
• Artist B 20 000 – 0.00666666667 needs update to 0.00666666444
• Artist C. 1 020 0 00 – 0.34 needs update to 0.339999887
• Artist D 3 000 001 – 1
popularity = artist.listeners / maxListeners;
3 000 00020 000
0, 0.00000001, 0.00000002, … 1
• Popularity is based on the most popular artist in the current list
• Artist A 3 000 000 – 100
• Artist B 20 000 – 0
• Artist C 1 020 0 00 – 34
• Artist D 3 000 001 – 100
popularity = Math.round(artist.listeners / maxListeners * 100);
20 000 3 000 000
0, 1, 2, 3, … 100
• Popularity is based on the most popular artist in the current list
• Artist A 3 000 000 – 100 – no need to update
• Artist B 20 000 – 0 – no need to update
• Artist C 1 020 0 00 – 34 – no need to update
• Artist D 3 000 001 – 100
popularity = Math.round(artist.listeners / maxListeners * 100);
20 000 3 000 000
0, 1, 2, 3, … 100
Tools
• ESLint rules
• why-did-you-update
• why-did-you-render
• React profiles
• console.log
• Sandboxed pages (aka Storybook without Storybook)
Rendering summary
• Measure on real devices
• Indentify places where performance degrages
• Make sure you are using pessimistic scenarious (lots of data on slow
device)
• Indetify where you are calling redundant renders within FlatLists or
.map. You can use console.log, why-did-you-update, profilers, etc..
• Refactor the API and usage of your components to make sure no new
instances are being created during FlatList renders
Agenda
• Rendering
• Dealing with slow code
• Animations
Slow Code
• Internal long-running code (do not assume, measure)
• External long-running code
_.sortBy(artists, [a => a.getListeners()]);
for (let i = 0; i < 10000000; i++) {
//...
import {preinitializedOperation} from 'heavy-library';
I need a [min,max] from a set of random numbers
longRunningOperation() {
for (let i = 0; i < 10000000; i++) {
//...
this.setState({
minValue,
maxValue,
isRunning: false
});
render() {
return (
this.state.isRunning ?
<LoadingIndicator />
onPressWithSetStateCallback = () =>
this.setState(
{isRunning: true},
() => this.longRunningOperation(),
);
onPressWithAfterInteractions = () =>
this.setState(
{isRunning: true},
() =>
InteractionManager
.runAfterInteractions(this.longRunningOperation)
);
onPressSetTimeout = () =>
this.setState(
{isRunning: true},
() => setTimeout(this.longRunningOperation),
);
A few words about setTimeout
Performance
Correctness
Developer
Experience
onPressSetTimeout = () =>
this.setState(
{isRunning: true},
() => setTimeout(this.longRunningOperation, 250),
);
A few words about setTimeout
onPressSetTimeout = () =>
this.setState(
{isRunning: true},
() => setTimeout(this.longRunningOperation, 666),
);
External long-running code - inline require
import {preinitializedOperation}
from 'heavy-library’;
onPress(){
preinitializedOperation();
}
onPress(){
const {preinitializedOperation} =
require('heavy-library’);
preinitializedOperation();
}
Agenda
• Rendering
• Dealing with slow code
• Animations
Animations (non-native imperative)
offset = new Animated.Value(0);
onScroll(event) {
const y = event.nativeEvent.contentOffset.y;
if (this.prevOffset === undefined) {
this.prevOffset = y;
} else {
const diff = this.prevOffset - y;
const newOffset = this.offset._value + diff;
this.offset.setValue(Math.min(0, Math.max(newOffset, -HEADER_HEIGHT)));
this.prevOffset = event.nativeEvent.contentOffset.y;
}
}
<FlatList
scrollEventThrottle={16}
onScroll={this.onScroll}
<Header
style={{marginTop: this.offset}}
/>
// 1000 / 60
Animations (non-native imperative)
offset = new Animated.Value(0);
onScroll(event) {
const y = event.nativeEvent.contentOffset.y;
if (this.prevOffset === undefined) {
this.prevOffset = y;
} else {
const diff = this.prevOffset - y;
const newOffset = this.offset._value + diff;
this.offset.setValue(Math.min(0, Math.max(newOffset, -HEADER_HEIGHT)));
this.prevOffset = event.nativeEvent.contentOffset.y;
}
}
<FlatList
scrollEventThrottle={16}
onScroll={this.onScroll}
<Header
style={{marginTop: this.offset}}
/>
Animations (native declarative)
clampedOffset = Animated.diffClamp(this.offset, 0, HEADER_HEIGHT).interpolate(
{
inputRange: [0, 1],
outputRange: [0, -1],
},
);
headerStyle = {transform: [{translateY: this.clampedOffset}]};
<Animated.FlatList
scrollEventThrottle={16}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.offset}}}],
{useNativeDriver: true},
)}
Animations (native declarative)
clampedOffset = Animated.diffClamp(this.offset, 0, HEADER_HEIGHT).interpolate(
{
inputRange: [0, 1],
outputRange: [0, -1],
},
);
headerStyle = {transform: [{translateY: this.clampedOffset}]};
<Animated.FlatList
scrollEventThrottle={16}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.offset}}}],
{useNativeDriver: true},
)}
Summary
• Don’t blame React-Native for it’s performance, at least by default
• A number of performance problems can be solved on the JS level
• Focus on measurement, then optimize having clear numbers in mind
• Focus on 1to* parts within your app
• So much better to have more renders, than fewer ©
Thanks
Email: static.ila@gmail.com
Slides:
Repo: https://github.com/ilyaivanov/ReactNativePerformance
Expo: https://expo.io/@innerself/ReactNativePerformance

More Related Content

Similar to React Native Performance

David Bilík: Anko – modern way to build your layouts?
David Bilík: Anko – modern way to build your layouts?David Bilík: Anko – modern way to build your layouts?
David Bilík: Anko – modern way to build your layouts?mdevtalk
 
OGSA-DAI DQP: A Developer's View
OGSA-DAI DQP: A Developer's ViewOGSA-DAI DQP: A Developer's View
OGSA-DAI DQP: A Developer's ViewBartosz Dobrzelecki
 
OSCON Presentation: Developing High Performance Websites and Modern Apps with...
OSCON Presentation: Developing High Performance Websites and Modern Apps with...OSCON Presentation: Developing High Performance Websites and Modern Apps with...
OSCON Presentation: Developing High Performance Websites and Modern Apps with...Doris Chen
 
Python Raster Function - Esri Developer Conference - 2015
Python Raster Function - Esri Developer Conference - 2015Python Raster Function - Esri Developer Conference - 2015
Python Raster Function - Esri Developer Conference - 2015akferoz07
 
AWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDB
AWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDBAWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDB
AWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDBAmazon Web Services
 
Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012
Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012
Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012Big Data Spain
 
A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny Wendy Chen Dubois
 
Mat lab workshop
Mat lab workshopMat lab workshop
Mat lab workshopVinay Kumar
 
Recommendations with hadoop streaming and python
Recommendations with hadoop streaming and pythonRecommendations with hadoop streaming and python
Recommendations with hadoop streaming and pythonAndrew Look
 
lecture-SQL_Working.ppt
lecture-SQL_Working.pptlecture-SQL_Working.ppt
lecture-SQL_Working.pptLaviKushwaha
 
Processing Large Graphs
Processing Large GraphsProcessing Large Graphs
Processing Large GraphsNishant Gandhi
 
February 2017 HUG: Data Sketches: A required toolkit for Big Data Analytics
February 2017 HUG: Data Sketches: A required toolkit for Big Data AnalyticsFebruary 2017 HUG: Data Sketches: A required toolkit for Big Data Analytics
February 2017 HUG: Data Sketches: A required toolkit for Big Data AnalyticsYahoo Developer Network
 
02 functions, variables, basic input and output of c++
02   functions, variables, basic input and output of c++02   functions, variables, basic input and output of c++
02 functions, variables, basic input and output of c++Manzoor ALam
 
SQL Server Deep Dive, Denis Reznik
SQL Server Deep Dive, Denis ReznikSQL Server Deep Dive, Denis Reznik
SQL Server Deep Dive, Denis ReznikSigma Software
 
Medium TechTalk — iOS
Medium TechTalk — iOSMedium TechTalk — iOS
Medium TechTalk — iOSjimmyatmedium
 

Similar to React Native Performance (20)

David Bilík: Anko – modern way to build your layouts?
David Bilík: Anko – modern way to build your layouts?David Bilík: Anko – modern way to build your layouts?
David Bilík: Anko – modern way to build your layouts?
 
OGSA-DAI DQP: A Developer's View
OGSA-DAI DQP: A Developer's ViewOGSA-DAI DQP: A Developer's View
OGSA-DAI DQP: A Developer's View
 
C
CC
C
 
SQL Functions
SQL FunctionsSQL Functions
SQL Functions
 
Intro to Akka Streams
Intro to Akka StreamsIntro to Akka Streams
Intro to Akka Streams
 
Wider than rails
Wider than railsWider than rails
Wider than rails
 
OSCON Presentation: Developing High Performance Websites and Modern Apps with...
OSCON Presentation: Developing High Performance Websites and Modern Apps with...OSCON Presentation: Developing High Performance Websites and Modern Apps with...
OSCON Presentation: Developing High Performance Websites and Modern Apps with...
 
Python Raster Function - Esri Developer Conference - 2015
Python Raster Function - Esri Developer Conference - 2015Python Raster Function - Esri Developer Conference - 2015
Python Raster Function - Esri Developer Conference - 2015
 
AWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDB
AWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDBAWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDB
AWS December 2015 Webinar Series - Design Patterns using Amazon DynamoDB
 
Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012
Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012
Crunching Data with Google BigQuery. JORDAN TIGANI at Big Data Spain 2012
 
A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny
 
Mat lab workshop
Mat lab workshopMat lab workshop
Mat lab workshop
 
Recommendations with hadoop streaming and python
Recommendations with hadoop streaming and pythonRecommendations with hadoop streaming and python
Recommendations with hadoop streaming and python
 
Deep Dive on Amazon DynamoDB
Deep Dive on Amazon DynamoDBDeep Dive on Amazon DynamoDB
Deep Dive on Amazon DynamoDB
 
lecture-SQL_Working.ppt
lecture-SQL_Working.pptlecture-SQL_Working.ppt
lecture-SQL_Working.ppt
 
Processing Large Graphs
Processing Large GraphsProcessing Large Graphs
Processing Large Graphs
 
February 2017 HUG: Data Sketches: A required toolkit for Big Data Analytics
February 2017 HUG: Data Sketches: A required toolkit for Big Data AnalyticsFebruary 2017 HUG: Data Sketches: A required toolkit for Big Data Analytics
February 2017 HUG: Data Sketches: A required toolkit for Big Data Analytics
 
02 functions, variables, basic input and output of c++
02   functions, variables, basic input and output of c++02   functions, variables, basic input and output of c++
02 functions, variables, basic input and output of c++
 
SQL Server Deep Dive, Denis Reznik
SQL Server Deep Dive, Denis ReznikSQL Server Deep Dive, Denis Reznik
SQL Server Deep Dive, Denis Reznik
 
Medium TechTalk — iOS
Medium TechTalk — iOSMedium TechTalk — iOS
Medium TechTalk — iOS
 

React Native Performance

  • 1. From Rendering to Animations React Native Performance
  • 3. Holy grail of mobile development
  • 4. Holy grail of mobile development Learn Once, Write Anywhere
  • 6. Fear and Loathing in React-Native
  • 7. Morale => Rethinking React-Native • Mobile users have high standards in terms of app responsiveness and animations • Not thinking about performance is almost certainly would result in slow app • Rarely users write about performance directly • Lots of performance cases can be solved on the React-Native level
  • 8. Who am I • Bob Ilya Ivanov • React-Native 3 years • React.js 5 years • Software Development 8 years
  • 10. Agenda • Rendering • Dealing with slow code • Animations
  • 11. Agenda • Rendering • Dealing with slow code • Animations
  • 12. What we are going to build • Why should we care • Where should we focus • How to achive fewer renders Rendering
  • 13. Why
  • 14. Why Press – 1.72s Changes – 2.24s Response – 520ms QuickTime Player on MacOS Shortcut ⌘ + T
  • 15. Why
  • 16. Why Press – 2.34s Changes – 4.18s Response – 1840ms
  • 17. Why • 100 Artists – 520ms • 300 Artists – 1840ms • Golden rule – 200ms
  • 19. Why
  • 20. Why – summary • Performance is already slow for small sample 100 artists (500ms) • Performance degrades with more data • Slow responsiveness stack because of JS single threaded
  • 21. Where • 1 to * relationships in our hierarchy • <FlatList or {items.map(item => …} where the count is unknown or varied • No control over the number of items in our hierarchy • No control the density of state updates
  • 22. Where Page Artist 1 StatusBar Box 1 Box 2... Box N Artist 2 … StatusBar2 Artist N StatusBarN
  • 23. Update Complexity (big O for onPress) • Current – O(n * m) • n – number of artists • m – number of boxes • Goal – O(1) Page Artist 1 StatusBar Box 1 Box 2... Box M Artist 2 … StatusBar2 Artist N StatusBarN
  • 24. Where – summary • Look for FlatList or .map in your codebase • Look for content of variable length
  • 27. PureComponent Used for classes. Shallow compare === for all properties of props, state and context React.memo Used for function. Shallow compare === for all properties of props and context
  • 28. ArtistInfo component renderArtist(artist) { const artistStyle = [ styles.artistContainer, artist.isSelected && styles.selectedArtist ]; return ( <ArtistInfo style={artistStyle} onPress={() => this.onPress(artist.id)} fontStyle={{ fontSize: 23 }} popularity={artist.popularity} //0..1 /> ); }
  • 29. What does it mean to break PureComponent class MyPureComponent extends PureComponent { } const MyPureComponent = React.memo(/.../);OR <MyPureComponent myParameter={{property: this.state.myValue}} /> const a = {} === {}; //false const b = {property: 1} === {property: 1}; //false const c = [] === []; //false const d = (() => undefined) === (() => undefined); //false const e = '1' === '1'; //true const f = 1 === 1; //true This completely ignores shallow compare done by PureComponent because this.props.myParameter === nextProps.myParameter will always be false
  • 30. Fixing onPress onPress(id) { this.setState(...) } renderArtist(artist) { return ( <ArtistInfo onPress={() => this.onPress(artist.id)} /> ); } class ArtistInfo extends PureComponent { render() { //... <TouchableOpacity onPress={this.props.onPress}
  • 31. Fixing onPress onPress(id) { this.setState(...) } renderArtist(artist) { return ( <ArtistInfo id={artist.id} onPress={this.onPress} /> ); } class ArtistInfo extends PureComponent { render() { //... <TouchableOpacity onPress={() => this.props.onPress(this.props.id)}
  • 32. Fixing onPress onPress(id) { this.setState(...) // ERROR: setState is not a function } renderArtist(artist) { return ( <ArtistInfo id={artist.id} onPress={this.onPress} /> ); } class ArtistInfo extends PureComponent { render() { //... <TouchableOpacity onPress={() => this.props.onPress(this.props.id)}
  • 33. Fixing onPress onPress = (id) => { // works fine... // Breaks Hot Reloading } renderArtist(artist) { return ( <ArtistInfo id={artist.id} onPress={this.onPress} /> ); } Performance Correctness Developer Experience https://github.com/facebook/react-native/issues/10991
  • 34. Solutions • Bind all methods in constructor (not really an option) • auto-bind • babel-plugin-functional-hmr constructor(props){ super(props); this.onPress = this.onPress.bind(this); //... } import autoBind from 'auto-bind’; constructor(props){ super(props); autoBind(this); }
  • 35. Fixing styles renderArtist(artist) { const artistStyle = [ styles.artistContainer, artist.isSelected && styles.selectedArtist ]; return ( <ArtistInfo style={artistStyle} fontStyle={{ fontSize: 23 }} /> ); } const styles = StyleSheet.create({ artistContainer: { //commonStyles }, selectedArtist: { //specific styles } });
  • 36. Fixing styles const styles = StyleSheet.create({ artistContainer: { //commonStyles }, selectedArtist: { //specific styles }, artistFont:{ fontSize: 23 } }); renderArtist(artist) { const artistStyle = [ styles.artistContainer, artist.isSelected && styles.selectedArtist ]; return ( <ArtistInfo style={artistStyle} fontStyle={styles.artistFont} /> ); }
  • 37. Fixing styles const styles = StyleSheet.create({ artistContainer: { //commonStyles }, selectedArtist: { //specific styles }, artistFont:{ fontSize: 23 } }); selectedArtistStyle = [ styles.artistContainer, styles.selectedArtist, ]; renderArtist(artist) { const artistStyle = artist.isSelected ? this.selectedArtistStyle : styles.artistContainer; return ( <ArtistInfo style={artistStyle} fontStyle={styles.artistFont} /> ); }
  • 38. Common render troublemakers • {} • [] • () => this.handle(someData)
  • 40. Results Careless O(n * m) 100 Artists – 520ms 300 Artists – 1840ms Optimized O(1) 100 Artists – 130ms 300 Artists – 150ms
  • 41. Fixing popularity renderArtist(artist) { return ( <ArtistInfo popularity={artist.popularity} //0..1 /> ); }
  • 42. • Popularity is based on the most popular artist in the current list • Artist A 3 000 000 – 1 • Artist B 20 000 – 0.00666666667 • Artist C 1 020 0 00 – 0.34 • Artist D 3 000 001 – 1 popularity = artist.listeners / maxListeners; 3 000 00020 000 0, 0.00000001, 0.00000002, … 1
  • 43. • Popularity is based on the most popular artist in the current list • Artist A 3 000 000 – 1 needs update to 0.999999667 • Artist B 20 000 – 0.00666666667 needs update to 0.00666666444 • Artist C. 1 020 0 00 – 0.34 needs update to 0.339999887 • Artist D 3 000 001 – 1 popularity = artist.listeners / maxListeners; 3 000 00020 000 0, 0.00000001, 0.00000002, … 1
  • 44. • Popularity is based on the most popular artist in the current list • Artist A 3 000 000 – 100 • Artist B 20 000 – 0 • Artist C 1 020 0 00 – 34 • Artist D 3 000 001 – 100 popularity = Math.round(artist.listeners / maxListeners * 100); 20 000 3 000 000 0, 1, 2, 3, … 100
  • 45. • Popularity is based on the most popular artist in the current list • Artist A 3 000 000 – 100 – no need to update • Artist B 20 000 – 0 – no need to update • Artist C 1 020 0 00 – 34 – no need to update • Artist D 3 000 001 – 100 popularity = Math.round(artist.listeners / maxListeners * 100); 20 000 3 000 000 0, 1, 2, 3, … 100
  • 46. Tools • ESLint rules • why-did-you-update • why-did-you-render • React profiles • console.log • Sandboxed pages (aka Storybook without Storybook)
  • 47. Rendering summary • Measure on real devices • Indentify places where performance degrages • Make sure you are using pessimistic scenarious (lots of data on slow device) • Indetify where you are calling redundant renders within FlatLists or .map. You can use console.log, why-did-you-update, profilers, etc.. • Refactor the API and usage of your components to make sure no new instances are being created during FlatList renders
  • 48. Agenda • Rendering • Dealing with slow code • Animations
  • 49. Slow Code • Internal long-running code (do not assume, measure) • External long-running code _.sortBy(artists, [a => a.getListeners()]); for (let i = 0; i < 10000000; i++) { //... import {preinitializedOperation} from 'heavy-library';
  • 50. I need a [min,max] from a set of random numbers longRunningOperation() { for (let i = 0; i < 10000000; i++) { //... this.setState({ minValue, maxValue, isRunning: false }); render() { return ( this.state.isRunning ? <LoadingIndicator />
  • 51. onPressWithSetStateCallback = () => this.setState( {isRunning: true}, () => this.longRunningOperation(), );
  • 52. onPressWithAfterInteractions = () => this.setState( {isRunning: true}, () => InteractionManager .runAfterInteractions(this.longRunningOperation) );
  • 53. onPressSetTimeout = () => this.setState( {isRunning: true}, () => setTimeout(this.longRunningOperation), );
  • 54. A few words about setTimeout Performance Correctness Developer Experience onPressSetTimeout = () => this.setState( {isRunning: true}, () => setTimeout(this.longRunningOperation, 250), );
  • 55. A few words about setTimeout onPressSetTimeout = () => this.setState( {isRunning: true}, () => setTimeout(this.longRunningOperation, 666), );
  • 56. External long-running code - inline require import {preinitializedOperation} from 'heavy-library’; onPress(){ preinitializedOperation(); } onPress(){ const {preinitializedOperation} = require('heavy-library’); preinitializedOperation(); }
  • 57. Agenda • Rendering • Dealing with slow code • Animations
  • 58. Animations (non-native imperative) offset = new Animated.Value(0); onScroll(event) { const y = event.nativeEvent.contentOffset.y; if (this.prevOffset === undefined) { this.prevOffset = y; } else { const diff = this.prevOffset - y; const newOffset = this.offset._value + diff; this.offset.setValue(Math.min(0, Math.max(newOffset, -HEADER_HEIGHT))); this.prevOffset = event.nativeEvent.contentOffset.y; } } <FlatList scrollEventThrottle={16} onScroll={this.onScroll} <Header style={{marginTop: this.offset}} /> // 1000 / 60
  • 59. Animations (non-native imperative) offset = new Animated.Value(0); onScroll(event) { const y = event.nativeEvent.contentOffset.y; if (this.prevOffset === undefined) { this.prevOffset = y; } else { const diff = this.prevOffset - y; const newOffset = this.offset._value + diff; this.offset.setValue(Math.min(0, Math.max(newOffset, -HEADER_HEIGHT))); this.prevOffset = event.nativeEvent.contentOffset.y; } } <FlatList scrollEventThrottle={16} onScroll={this.onScroll} <Header style={{marginTop: this.offset}} />
  • 60. Animations (native declarative) clampedOffset = Animated.diffClamp(this.offset, 0, HEADER_HEIGHT).interpolate( { inputRange: [0, 1], outputRange: [0, -1], }, ); headerStyle = {transform: [{translateY: this.clampedOffset}]}; <Animated.FlatList scrollEventThrottle={16} onScroll={Animated.event( [{nativeEvent: {contentOffset: {y: this.offset}}}], {useNativeDriver: true}, )}
  • 61. Animations (native declarative) clampedOffset = Animated.diffClamp(this.offset, 0, HEADER_HEIGHT).interpolate( { inputRange: [0, 1], outputRange: [0, -1], }, ); headerStyle = {transform: [{translateY: this.clampedOffset}]}; <Animated.FlatList scrollEventThrottle={16} onScroll={Animated.event( [{nativeEvent: {contentOffset: {y: this.offset}}}], {useNativeDriver: true}, )}
  • 62. Summary • Don’t blame React-Native for it’s performance, at least by default • A number of performance problems can be solved on the JS level • Focus on measurement, then optimize having clear numbers in mind • Focus on 1to* parts within your app • So much better to have more renders, than fewer ©