A compelling reason for using React Native instead of WebView-based
tools is to achieve 60 FPS and a native look & feel to your apps. Where
possible, we would like for React Native to do the right thing and help
you to focus on your app instead of performance optimization, but there
are areas where we're not quite there yet, and others where React Native
(similar to writing native code directly) cannot possibly determine the
best way to optimize for you and so manual intervention will be
necessary.
This guide is intended to teach you some basics to help you
to troubleshoot performance issues, as well as discuss common sources of
problems and their suggested solutions.
### What you need to know about frames
Your grandparents' generation called movies ["moving
pictures"](https://www.youtube.com/watch?v=F1i40rnpOsA) for a reason:
realistic motion in video is an illusion created by quickly changing
static images at a consistent speed. We refer to each of these images as
frames. The number of frames that is displayed each second has a direct
impact on how smooth and ultimately life-like a video (or user
interface) seems to be. iOS devices display 60 frames per second, which
gives you and the UI system about 16.67ms to do all of the work needed to
generate the static image (frame) that the user will see on the screen
for that interval. If you are unable to do the work necessary to
generate that frame within the allotted 16.67ms, then you will "drop a
frame" and the UI will appear unresponsive.
Now to confuse the matter a little bit, open up the developer menu in
your app and toggle `Show FPS Monitor`. You will notice that there are
two different frame rates.
#### JavaScript frame rate
For most React Native applications, your business logic will run on the
JavaScript thread. This is where your React application lives, API calls
are made, touch events are processed, etc... Updates to native-backed
views are batched and sent over to the native side at the end of each iteration of the event loop, before the frame deadline (if
all goes well). If the JavaScript thread is unresponsive for a frame, it
will be considered a dropped frame. For example, if you were to call
`this.setState` on the root component of a complex application and it
resulted in re-rendering computationally expensive component subtrees,
it's conceivable that this might take 200ms and result in 12 frames
being dropped. Any animations controlled by JavaScript would appear to freeze during that time. If anything takes longer than 100ms, the user will feel it.
This often happens during Navigator transitions: when you push a new
route, the JavaScript thread needs to render all of the components
necessary for the scene in order to send over the proper commands to the
native side to create the backing views. It's common for the work being
done here to take a few frames and cause jank because the transition is
controlled by the JavaScript thread. Sometimes components will do
additional work on `componentDidMount`, which might result in a second
stutter in the transition.
Another example is responding to touches: if you are doing work across
multiple frames on the JavaScript thread, you might notice a delay in
responding to TouchableOpacity, for example. This is because the JavaScript thread is busy and cannot process the raw touch events sent over from the main thread. As a result, TouchableOpacity cannot react to the touch events and command the native view to adjust its opacity.
#### Main thread (aka UI thread) frame rate
Many people have noticed that performance of `NavigatorIOS` is better
out of the box than `Navigator`. The reason for this is that the
animations for the transitions are done entirely on the main thread, and
so they are not interrupted by frame drops on the JavaScript thread.
([Read about why you should probably use Navigator
When running a bundled app, these statements can cause a big bottleneck in the JavaScript thread. This includes calls from debugging libraries such as [redux-logger](https://github.com/evgenyrodionov/redux-logger), so make sure to remove them before bundling.