Overall: Capable of achieving the same ends as any app developed using the platform’s preferred tooling by fundamentally the same mechanisms.

I claim React Native meets that bar.

Same Mechanisms Differently Marshaled

I’ve spent most of my years as a professional programmer working on Mac & iOS apps. From my Apple-native point of view, React Native is a very elaborate way to marshal UIViews and other UIKit mechanisms towards the usual UIKit ends:

View creation and configuration: Many iOS apps rely on XIB files for their view creation and configuration. If you haven’t looked at a XIB file on disk before, have a look: It’s your UI, rendered in XML. React Native uses hand-written JSX to rig up its UIViews, but that’s a difference more in markup flavor than in kind. (It codegens something nearer manual view creation and configuration code, but that’s also something in vogue amongst some iOS devs.)

Layout: It’s not enough to just stuff some views inside some others. You sometimes want them to have a certain shape. iOS devs can use raw AutoLayout constraints, or Ye Olde Visual Format Language, or the newer anchor-based API that leverages the type system, or Snap, or Masonry, or, or… Whatever it is you use, it ultimately boils down to setting the view’s frame. Well, React Native likes to use a flexbox-alike to describe its layout. It, too, boils down to frame updates by way of Yoga.

Language & Execution Context

Well, about that one more language. Let’s talk about animation jank and asynchrony.

What is “jank”? It’s jargon for what happens when it’s time for something to show up on screen, but your app can’t render the needed pixels fast enough to show that something. As Shawn Maust put it back in 2015 in “What the Jank?”:

“Jank” is any stuttering or choppiness that a user experiences when there is
motion on the screen—like during scrolling, transitions, or animations.

The difference in language drives to something that may seem less than native at first glance. You see, there’s a context switch between UIKit-land and React Native JavaScript-action-handler-land, and at a high enough call rate – like, say, animation handlers that are supposed to run at the frame rate – the time taken in data marshaling and context switching can become noticeable.

Native apps aren’t immune from animation jank. It feels like there’s a WWDC session or three every year on how not to stutter when you scroll. But the overhead inherent in the technical mechanism eats some of your time budget, which means you get to sweep less inefficiency in your app code under Moore’s rug.

Native apps also aren’t immune from blocking rendering entirely. Do a bulk-import into Core Data on the main thread, parse a sufficiently large (or malicious) XML or JSON document on the main thread, or run a whole network request on the main thread, and the system watchdog will kill your app while leaving behind a death note of “8badf00d”. React Native’s context switch automatically enforces the best practice of doing work off the main thread: React Native developers naturally fall into the “pit of success” when it comes to aggressively pushing work off the main thread.

Asynchrony

How do you deal with the time taken by a function call? You do less work, or you do work on the other side of the bridge.

Or you surface that gap, that asynchrony, in your programming model with:

Callbacks

Delegates

Operations

Reactors (Run-Loop Observers)

Promises Futures Results Observables Streams Channels

Apple’s frameworks are rife with these mechanisms. Your standard IBAction-to-URLSession-to-spinner-to-view-update flow has a slow as a dog HTTP call in the middle. React Native’s IBAction-to-JSCore-to-view-update flow has a tiny little RPC bridge in the middle that often runs fast enough that you can pretend it’s synchronous. By the end of 2018, you may not even have to pretend - React Native will directly support synchronous cross-language calls where that’s advantageous.

React Native apps with their action handlers in JavaScript are no less native than an iOS app with their action handlers on a server on the other side of an HTTP API.

If you’ve worked on the common “all the brains are in our serverside API” flavor of iOS app, this should sound familiar. It should sound doubly familiar if that serverside API happens to be implemented in Node.js.

And, indeed, running the same language both serverside and clientside makes it a lot easier to change up which side of the pipe an operation happens on. (Such are the joys of isomorphic code, and it’s a small reason some are excited about Swift on the Server.)

Native Is As Native Does

React Native uses the same underlying mechanisms and benefits as much from Apple’s work on UIKit as does any other iOS app. React Native apps are native - perhaps even more native than many “iOS app as Web API frontend” apps!