React Native

Native mobile apps with React.js








Brent Vatne
github.com/brentvatne
@notbrent


 Credit: @wesbos

On the web, server-side

views are easy


1. Fetch data to be used in view

2. Put that data into a template, result: HTML

Updating views on the client side


Option 1: Fetch HTML partials from server

- Send request to server to render some partial.

- Replace an entire portion of the page with that partial.

Option 2: Client-side templating

- Same as Option 1, but do rendering on client side. Replace entire portion of page with the result.

Option 3: Direct DOM manipulation

- Imperatively add / remove / modify DOM elements.

$('.todo').addClass('completed');
$('.list').append("<li class='todo'>"+todoName+</li>");

Imperative code is hard to write,
error prone, and sucks to maintain


Replacing subtrees of the DOM discards state (eg: focus, scroll position) and is slow

Imperative UI


 


Thanks @steveluscher for letting me use this example!

What if we could have the ease of rendering the entire page each time but the benefits of direct manipulation?


 Functional UI

 

But isn't it really slow to re-render the entire page each time any data changes?

Virtual DOM


Problem: DOM is statefulcomplex, and expensive to manipulate



Ideal: Re-render the entire app every time data changes.



Solution: Mirror the DOM with light-weight Virtual DOM tree, re-generate the entire* tree on each data change, diff the new tree with the old tree, generate imperative instructions that update the rendered Browser DOM to match the new Virtual DOM

Object.keys(document.createElement('div'))
// Array[122]

Object.keys(React.DOM.div({}))
// ["type", "key", "ref", "_owner", "_context", "props"]

 

Modified from source: http://www.ibm.com/developerworks/library/wa-react-intro/index.html

 
    
  var CounterApp = React.createClass({
    getInitialState() {
      return {count: 0};
    },

    incrementCount() {
      this.setState({count: this.state.count + 1});
    },

    render() {
      return (
        <div>
          <h2>My first React app</h2>
          <span onClick={this.incrementCount}>
            {this.state.count} clicks
          </span>
        </div>
      )
    }
});

React.render(<CounterApp />, document.querySelector('body'));     
          
        
        
        
        
    
 JSX

HTML in your Javascript

 

Composite vs Native DOM components


var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});

Summary so far


- Your React app is a tree of components

- Each component has a render function that returns a subtree of virtual DOM nodes

- The render functions are called any time the data that the component depends on is changed, producing an updated virtual DOM tree

- This new tree is diffed against the old tree and optimized imperative instructions to update the actual screen are generated and executed
 

Tools to build mobile apps



Native

Webviews & basic JS bridge (& hybrid)
Compile to Native binary
Native components via JS bridge
React Native
Different APIs on each platform, even with compile to native 




 Expensive and/or closed source tooling



Slow development cycle 


 


Slow deployment cycle

 Unintuitive layout systems


Complex native UI must be re-implemented 




 Poor performance & non-native feel


 Limited access to native APIs

 

     

Imperative UI

React Native






- Free, open source tooling with liberal license
- Live-reload: instant feedback
- Functional UI: a better abstraction
- Apple sanctioned OTA updates                                  
- Intuitive Flexbox layout, CSS styles                                  
- Same skill-set, similar APIs on each platform: React
- Share common code via JavaScript
- Native UI components
- Simple and powerful Native plugin API


 Functional UI


 

var CounterApp = React.createClass({
  getInitialState() {
    return {count: 0};
  },

  incrementCount() {
    this.setState({count: this.state.count + 1});
  },

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          My first React Native app
        </Text>
        <Text onPress={this.incrementCount} style={styles.counter}>
          {this.state.count} clicks
        </Text>
      </View>
    );
  }
});

RCTUIManager.createView([3,"RCTView",{"flex":1,"justifyContent":"center","alignItems":"center"}])

RCTUIManager.createView([4,"RCTText",{"accessible":true,"isHighlighted":false,"fontSize":26, "textAlign":"center","margin":10}])RCTUIManager.createView([5,"RCTRawText",{"text":"My first React Native app"}])

RCTUIManager.createView([6,"RCTText",{"accessible":true,"isHighlighted":false,"fontSize":24}])
RCTUIManager.createView([7,"RCTRawText",{"text":"0"}])
RCTUIManager.createView([8,"RCTRawText",{"text":" clicks"}])

RCTUIManager.updateView([7,"RCTRawText",{"text":"1"}])
RCTUIManager.updateView([7,"RCTRawText",{"text":"2"}])


 OTA Updates



 

"CSS" via inline styles
See http://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html 


 

 
Native UI components

        
View, Text, Image, ScrollView, DatePickerIOS, PickerIOS, ListView,
Navigator, SwitchIOS, MapView, AlertIOS, WebView, Touchable, TabBarIOS, SegmentedControlIOS, ActivityIndicatorIOS, CameraRoll, NetInfo, PanResponder, StatusBarIOS, TextInput, SliderIOS, PushNotificationIOS, LinkingIOS, ActionSheetIOS, AsyncStorage, Geolocation, VibrationIOS, Video, Camera, AudioRecorder, etc..
        
    


 
Plugins: Bridge Modules

@implementation FacebookLoginManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(newSession:(RCTResponseSenderBlock)callback) {

    FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
    [login logInWithReadPermissions:@[@"public_profile", @"email"]
                            handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
        if (error) {
            callback(@[@"Error", [NSNull null]]);
        } else if (result.isCancelled) {
            callback(@[@"Canceled", [NSNull null]]);
        } else {
            FBSDKAccessToken *token = result.token;
            NSString *tokenString = token.tokenString;
            NSString *userId = token.userID;
            NSDictionary *credentials = @{ @"token" : tokenString, @"userId" : userId };
            callback(@[[NSNull null], credentials]);
        }
    }];
};

@end
 
var FacebookLoginManager = require('NativeModules').FacebookLoginManager;
var FacebookLogin = React.createClass({
  login() {
    FacebookLoginManager.newSession((error, info) => {
      if (!error) { console.log(info) }
    });
  },

  render() {
    return (
      <TouchableHighlight onPress={this.login}>
        <Text style={styles.loginButton}>Facebook Login</Text>
      </TouchableHighlight>
    );
  }
});
Plugins: View Managers
@implementation RCTSliderManager

RCT_EXPORT_MODULE()

- (UIView *)view
{
  UISlider *slider = [[UISlider alloc] init];
  [slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
  return slider;
}

- (void)sliderValueChanged:(UISlider *)sender
{
  NSDictionary *event = @{
    @"target": sender.reactTag,
    @"value": @(sender.value),
    @"continuous": @YES,
  };

  [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}

RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);

@end
var SliderIOS = React.createClass({
  mixins: [NativeMethodsMixin],

  propTypes: {
    style: View.propTypes.style,
    minimumValue: PropTypes.number,
    maximumValue: PropTypes.number,
    onChange: PropTypes.func,
  },

  render: function() {
    return (
      <RCTSlider
        style={[styles.slider, this.props.style]}
        value={this.props.value}
        maximumValue={this.props.maximumValue}
        minimumValue={this.props.minimumValue}
        onChange={this.props.onChange} />
    );
  }
});

var styles = StyleSheet.create({
  slider: {
    height: 40,
  },
});

var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
module.exports = SliderIOS;
var MyApp = React.createClass({
  onChange(e) {
    console.log(e);
  },

  render() {
    return (
      <View>
        <SliderIOS minimumValue={1} maximumValue={42} onChange={this.doSomething} />
      </View>
    )
  }
});
 

A bit about the internals

 Cordova's JavaScript Bridge


 

React Native's JavaScript Bridge

- Uses iOS run loop rather than poke, 60 fps
- MessageQueue on JS/ObjC sides
- Serialized as JSON using custom RPC protocol - stores callbacks on JS side for later invocation with result.
- Handles conversion of JS data types automatically - support for other conversions (eg: UIColor) too
- Completely asynchronous.

Exercise 1: Add a breakpoint in RCTUIManager.m updateView, explore stack.

Exercise 2: Open up MessageQueue.js  and set DEBUG_SPY_MODE to true, run UIExplorer

Shadow thread




- Similar to AsyncDisplayKit, React Native performs layout calculations off of the main thread.
- Layout: calculate top, left, width and height using facebook/css-layout and Flexbox layout properties: padding, margin, border, flex, width, height, position, top, left, bottom, right, etc..
- Properties diffed in React, layout calculated in shadow thread *
- Expensive for text: each glyph has certain width, letter spacing, weight, etc. Some languages even more expensive, eg: Thai.
Must-watch talk on AsyncDisplayKit:
https://www.youtube.com/watch?v=h4QDbgB7RLo
 



Composite component
vs
Native [DOM|iOS|Android] component
 


var SomeComponent = React.createClass({
  render() {
    return <Text>5</Text>
  }
})

var TestCompositeComponents = React.createClass({
  render() {
    return (
      <View style={styles.container}>
        <SomeComponent />
        <SomeComponent />
      </View>
    );
  }
});

function renderApplication(rootComponent, initialProps, rootTag) {
  var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
  React.render(
    <View style={styles.appContainer}>
      <RootComponent
        {...initialProps}
      />
      {warningBox}
    </View>,
    rootTag
  );
}


#import "RCTBridgeModule.h"

@interface AppReloader : NSObject <RCTBridgeModule>
@end

@implementation AppReloader

RCT_EXPORT_MODULE()

-(dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

/**
 *  var AppReloader = require('NativeModules').AppReloader;
 *  AppReloader.reloadAppWithURLString('https://example.com/index.ios.bundle', 'App')
 */
RCT_EXPORT_METHOD(reloadAppWithURLString:(NSString *)URLString moduleNamed:(NSString *)moduleName) {
  AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

  NSURL *JSBundleURL = [NSURL URLWithString:URLString];
  [delegate.viewController reloadWithJSBundleURL:JSBundleURL moduleNamed:moduleName];
}

@end

 


Go to http://exp.host on your phone to install

Follow up

- Read React docs
- Watch videos on egghead.io
- #reactnative on Freenode (or via #react-native w/  Reactiflux on Slack)
- Immutable.js - Immutable Data Structures in JavaScript
- Flow - Static typing checking in your JavaScript
- Read my blog if you want, follow me on Twitter @notbrent and on Github @brentvatne
- Learn about AsyncDisplayKit because it's very interesting
- Read the React Native source - it's very approachable, mostly
- @nicklockwood's presentation slides
- Make your own apps, components, share them open source and contribute to the community and the project!

react-native

By Brent Vatne