REACT NATIVE
NATIVE DEVELOPMENT SUCKS
SLOW DEVELOPMENT CYCLE
SLOW DEPLOYMENT CYCLE
DIFFERENT APIS TO CODE SAME THINGS
SEPARATE PLATFORM TEAMS
X-PLATFORM TOOLS SUCK
POOR PERFORMANCE
NON-NATIVE FEEL
LIMITED FEATURE SUPPORT
HARD TO DEBUG / POOR TOOLING
COMMON TO BOTH
STATEFUL, MUTABLE UI
HARD TO MANAGE COMPLEXITY
HARD TO VERIFY CORRECTNESS
HARD TO MAINTAIN/RE-USE
ENTER: REACT NATIVE
SLOW DEVELOPMENT CYCLE
INSTANT RELOAD
SLOW DEPLOYMENT CYCLE
DOWNLOAD UPDATES OTA WITHOUT
RESUBMISSION*
*APPLE SAYS IT'S OK
DIFFERENT APIS TO CODE SAME THINGS
CONSISTENT TOOLING & COMMON APIS FOR
EVERY PLATFORM
SEPARATE PLATFORM TEAMS
SHARED SKILLSET*
*UP TO 90% CODE RE-USE BETWEEN IOS AND ANDROID
POOR PERFORMANCE
VIRTUAL DOM
NON-NATIVE FEEL
BACKED BY STANDARD NATIVE VIEWS
LIMITED FEATURE SUPPORT
EXTENSIBLE PLUGIN-BASED ARCHITECTURE
HARD TO DEBUG / POOR TOOLING
BREAKPOINTS AND SINGLE-STEP DEBUGGING
WITH BROWSER-BASED DEV TOOLS
STATEFUL, MUTABLE UI
IMMUTABLE VIEWS, PURE RENDER
FUNCTIONS, ONE-WAY DATA FLOW
REACT NATIVE IS
FUNCTIONAL UI
VIRTUAL DOM
FLEXBOX LAYOUT
JAVASCRIPT FRONT-END
NATIVE BACK-END
FUNCTIONAL UI
DEFINING STATES, NOT TRANSITIONS
IMPERATIVE UI: DEFINE TRANSITIONS
FUNCTIONAL UI: DEFINE STATES
IMPERATIVE UI: DEFINE TRANSITIONS
IMPERATIVE UI: DEFINE TRANSITIONS
IMPERATIVE UI: DEFINE TRANSITIONS
IMPERATIVE UI: DEFINE TRANSITIONS
IMPERATIVE UI: DEFINE TRANSITIONS
3 STATES
9 TRANSITIONS
O(N2
)
if (count > 99) { // branch 1
if (!hasFire()) { // branch 2
addFire();
}
} else {
if (hasFire()) { // branch 3
removeFire();
}
}
if (count === 0) { // branch 4
if (hasBadge()) { // branch 5
removeBadge();
}
return;
}
if (!hasBadge()) { // branch 6
addBadge();
}
var countText = count > 99 ? '99+' : count.toString(); // branch 7
getBadge().setText(countText);
FUNCTIONAL UI: DEFINE STATES
if (count === 0) { // state 1
return <Bell/>;
}
FUNCTIONAL UI: DEFINE STATES
if (count === 0) { // state 1
return <Bell/>;
} else if(count <= 99) { // state 2
return (
<Bell>
<Badge count={count} />
</Bell>
);
}
FUNCTIONAL UI: DEFINE STATES
if (count === 0) { // state 1
return <Bell/>;
} else if(count <= 99) { // state 2
return (
<Bell>
<Badge count={count} />
</Bell>
);
} else { // state 3
return (
<Bell style={styles.onFire}>
<Badge count=“99+” />
</Bell>
);
}
VIEW IS RECREATED EACH TIME
BUT ISN'T THAT SLOW?
VIRTUAL DOM*
*DOCUMENT OBJECT MODEL
(AKA VIEW HIERARCHY)
REACT CALCULATES CHANGESET ON THE JS SIDE
AUTORESIZING VS AUTOLAYOUT
AUTORESIZING VS AUTOLAYOUT
FLEXBOX LAYOUT
FLEXBOX API
EXPLICT AND INFERRED POSITIONING
CONTAINERS SPECIFY LAYOUT FOR CHILDREN
DIVIDES CONTENT INTO ROWS & COLUMNS
BASED ON CSS STANDARDS
FLEX DIRECTION
flexDirection: 'row' | 'column' (default)
This defines the main axis along which subviews are stacked.
JUSTIFY CONTENT
justifyContent: 'flex-start' (default) | 'center' | 'flex-end' |
'space-between' | 'space-around'
This defines how views align along the main axis.
ALIGN ITEMS
alignItems: 'flex-start' (default) | 'center' | 'flex-end'
This defines how views align along the cross axis.
FLEX
flex: (number)
A flex value > 0 indicates that a view should scale to fill its container.
Flex values > 1 control the relative size of views inside their parent.
FLEX WRAP
flexWrap: 'nowrap' (default) | 'wrap'
With the flexWrap style, subviews that overflow the bounds of their
container can be set to automatically wrap onto the next line.
JAVASCRIPT FRONT-END
JAVASCRIPT!?
WHY JAVASCRIPT?
ALREADY SUPPORTED X-PLATFORM
REACT ALREADY USES IT
ONLY OPTION FOR OTA UPDATES
3.3.2 An Application may not download or install executable code.
Interpreted code may only be used in an Application if all scripts,
code and interpreters are packaged in the Application and not
downloaded. The only exception to the foregoing is scripts and
code downloaded and run by Apple's built-in WebKit framework,
provided that such scripts and code do not change the primary
purpose of the Application by providing features or functionality
that are inconsistent with the intended and advertised purpose of
the Application as submitted to the App Store.
BUT... JAVASCRIPT SUCKS
WEAKLY TYPED
GARBAGE COLLECTED
INTERPRETED
IF ONLY JAVASCRIPT
function foo(fn, x) {
return fn(x);
}
IF ONLY JAVASCRIPT
function foo(fn, x) {
return fn(x);
}
WAS MORE LIKE SWIFT
function foo<X, Y>(fn: F<X, Y>, x: X): Y {
return fn(x);
}
FLOW
STATIC ANALYSIS, STRONG TYPING AND
TYPE INFERENCE FOR JAVASCRIPT
TYPE ALIASES TRUE CLASSES MAYBE TYPES
UNIONS ENUMS GENERICS TUPLES MIXINS
ARROW FUNCTIONS DESTRUCTURING
GARBAGE COLLECTION
INTERPRETED CODE
GARBAGE COLLECTION
INTERPRETED CODE
ASYNC EXECUTION**JS RUNS ON A DEDICATED THREAD - DOESN'T BLOCK UI
EXAMPLE
var React = require('react-native');
var { AppRegistry, StyleSheet, Text, View } = React;
class HelloWorld extends React.Component {
render() {
return (
React.createElement(View, {style: styles.container},
React.createElement(Text, null, "Hello World!")
)
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1, justifyContent: 'center', alignItems: 'center',
}
});
AppRegistry.registerComponent('HelloWorld', () => HelloWorld);
JSX (JAVASCRIPT + XML)
var React = require('react-native');
var { AppRegistry, StyleSheet, Text, View } = React;
class HelloWorld extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Hello World</Text>
</View>
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1, justifyContent: 'center', alignItems: 'center',
}
});
AppRegistry.registerComponent('HelloWorld', () => HelloWorld);
NATIVE BACK-END
INTEGRATION
NSURL *src = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
NSString *moduleName = @"MyApp";
// Option 1
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:src
moduleName:moduleName
launchOptions:nil];
// Option 2
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:src
moduleProvider:nil
launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName];
PLUGIN ARCHITECTURE
BRIDGE MODULES
VIEW MANAGERS
(MORE IN FUTURE)
BRIDGE MODULES
// CalendarManager.m
@interface CalendarManager : NSObject <RCTBridgeModule>
@end
@implementation CalendarManager
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
@end
// CalendarManager.js
var CalendarManager = require('NativeModules').CalendarManager;
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
TYPE CONVERSION
// CalendarManager.m
@implementation CalendarManager
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(addEvent:(NSString *)name date:(NSDate *)date)
{
RCTLogInfo(@"Pretending to create an event %@ on %@", name, date);
}
@end
// CalendarManager.js
CalendarManager.addEvent(
'Birthday Party',
date.toTime() // returns time in seconds since unix epoch
);
CALLBACKS
// CalendarManager.m
@implementation CalendarManager
...
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
NSArray *events = ...
callback(@[[NSNull null], events]);
}
@end
// CalendarManager.js
CalendarManager.findEvents((error, events) => {
if (error) {
console.error(error);
} else {
// do something with events
}
})
VIEW MANAGERS
// MapManager.m
@interface MapManager : RCTViewManager <MKMapViewDelegate>
@end
@implementation MapManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[MKMapView alloc] init];
}
@end
// MapView.js
var React = require('react-native');
var { requireNativeComponent } = React;
module.exports = requireNativeComponent('Map', null);
PROPERTY BINDING
// MapManager.m
@implementation MapManager
...
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
@end
// MapView.js
var React = require('react-native');
var { requireNativeComponent } = React;
class MapView extends React.Component {
render() {
return <Map {...this.props} />;
}
}
MapView.propTypes = {
pitchEnabled: React.PropTypes.bool,
};
var Map = requireNativeComponent('Map', MapView);
module.exports = MapView;
EVENTS
// MapManager.m
@implementation MapManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
MKMapView *map = [[MKMapView alloc] init];
map.delegate = self;
return map;
}
#pragma mark MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
MKCoordinateRegion region = mapView.region;
NSDictionary *event = @{ @"lat": @(region.center.latitude), @"long": @(region.center.longitude) };
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
@end
EVENTS
// MapView.js
class MapView extends React.Component {
constructor() {
this._onChange = this._onChange.bind(this);
}
_onChange(event: Event) {
if (!this.props.onRegionChange) {
return;
}
this.props.onRegionChange(event.nativeEvent.region);
}
render() {
return <Map {...this.props} onChange={this._onChange} />;
}
}
MapView.propTypes = {
pitchEnabled: React.PropTypes.bool,
onRegionChange: React.PropTypes.func,
};
WHAT ABOUT SWIFT?
// CalendarManagerBridge.m
#import "RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(CalendarManager, NSObject)
RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date)
@end
// CalendarManager.swift
@objc(CalendarManager)
class CalendarManager: NSObject {
@objc func addEvent(name: String, location: String, date: NSDate) -> Void {
// Date is ready to use!
}
}
LINKS
React Native on Github
https://facebook.github.io/react-native/
Colin Eberhardt's RayWenderlich.com Tutorial
http://tinyurl.com/rw-react-tutorial
Christopher Chedeau’s ReactJS Conf Keynote
https://www.youtube.com/watch?v=7rDsRXj9-cU
Q&A

Pieter De Baets - An introduction to React Native