React Native + Typescript

React-Native is a big deal, you should try it.

By default, it comes with Flow support, but in this case we will setup a project using Typescript. It’s awesome how Typescript increases in popularity thanks to projects like Angular2 and how all the community maintains the typings.

See the complete project and more complex code on: github.com/mrpatiwi/ReactNativeTS

Requirements

Follow the official requirements on facebook.github.io/react-native/docs/getting-started.html#requirements.

Setup

Install Homebrew:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Install nvm (Node Version Manager):

brew update && brew install nvm
mkdir ~/.nvm

Add this to your ~/.bash_profile or ~/.zshconfig:

export NVM_DIR=~/.nvm
source $(brew --prefix nvm)/nvm.sh

Install the lastest version of Node:

nvm install node && nvm alias default node

Install Facebook’s watchman:

brew install watchman

Install react-native-cli, Typescript and DefinitelyTyped/tsd:

npm install -g react-native-cli typescript tsd

Init Project

Let’s create a project named ReactNativeTS:

react-native init ReactNativeTS

We will remove .flowconfig:

rm .flowconfig

Init Typescript settings

This will create:

tsd init && tsc --init

We will modify tsconfig.json and set it as:

{
    "compilerOptions": {
        "target": "es6",
        "jsx": "react",
        "noImplicitAny": true,
        "experimentalDecorators": true,
        "preserveConstEnums": true,
        "outDir": "built",
        "rootDir": "src",
        "sourceMap": true
    },
    "filesGlob": [
        "typings/**/*.d.ts",
        "src/**/*.ts",
        "src/**/*.tsx"
    ],
    "exclude": [
        "node_modules"
    ],
    "compileOnSave": false
}
  • We will compile to ES6 (ES2015).
  • Use React support
  • The input code have to be placed at src.
  • The output code will be placed at built
  • compileOnSave is for your IDE, in my case Atom.

Make sure to add built to your .gitignore:

# .gitignore

# Typescript Output
built

Dependencies

Let’s install some dependencies:

npm install --save-dev typescript gulp gulp-typescript concurrently

Gulpfile

Now, we have to setup the gulpfile.js to declare our build pipeline:

var gulp = require('gulp');
var ts = require('gulp-typescript');

// Grab settings from tsconfig.json
var tsProject = ts.createProject('tsconfig.json');

gulp.task('build', function() {
    var tsResult = tsProject.src().pipe(ts(tsProject));
    return tsResult.js.pipe(gulp.dest('built'));
});

gulp.task('watch', ['build'], function() {
  gulp.watch('src/**/*.ts', ['build']);
  gulp.watch('src/**/*.tsx', ['build']);
});

gulp.task('default', ['build']);

Now we have the following available on the command line:

Typings

Let’s install the typings we need:

tsd install --save react-native

This will update the tsd.json like this:


{
  "version": "v4",
  "repo": "borisyankov/DefinitelyTyped",
  "ref": "master",
  "path": "typings",
  "bundle": "typings/tsd.d.ts",
  "installed": {
    "react-native/react-native.d.ts": {
      "commit": "dc9dabe74a5be62613b17a3605309783a12ff28a"
    },
    "react/react.d.ts": {
      "commit": "dc9dabe74a5be62613b17a3605309783a12ff28a"
    }
  }
}

And the typings directory as:

ReactNativeTS
└── typings
    ├── react-native
    ├── react
    └── tsd.d.ts

Hello world

Let’s modify the entry points of the app:

// index.ios.js

'use strict'

import { AppRegistry } from 'react-native'
import App from './built'

AppRegistry.registerComponent('ReactNativeTS', () => App)
// index.android.js

'use strict'

import { AppRegistry } from 'react-native'
import App from './built'

AppRegistry.registerComponent('ReactNativeTS', () => App)

Yes, they are identical. React-Native automatically picks the .android or .ios file from a local dependency, in this case the index.js of build.

Typescript files

Let’s convert the default hello world page to .tsx, we will start with the iOS part:

Import dependencies:

// src/index.ios.tsx

/// <reference path="../typings/tsd.d.ts"/>

import React from "react-native";
const { StyleSheet, Text, View } = React;

The style remains the same:

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#F5FCFF",
    },
    welcome: {
        fontSize: 20,
        textAlign: "center",
        margin: 10,
    },
    instructions: {
        textAlign: "center",
        color: "#333333",
        marginBottom: 5,
    },
});

And the App class will be like this:

// We are exporting it
export default class App extends React.Component<any, any> {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                    Welcome to React Native
                </Text>
                <Text style={styles.instructions}>
                    To get started, edit index.ios.js
                </Text>
                <Text style={styles.instructions}>
                    Press Cmd+R to reload, {"\n"}
                    Cmd+D or shake for dev menu
                </Text>
            </View>
        );
    }
}

The src/index.android.tsx is very similar:

// src/index.android.tsx

/// <reference path="../typings/tsd.d.ts"/>

import React from "react-native";
const { StyleSheet, Text, View } = React;


const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#F5FCFF",
    },
    welcome: {
        fontSize: 20,
        textAlign: "center",
        margin: 10,
    },
    instructions: {
        textAlign: "center",
        color: "#333333",
        marginBottom: 5,
    },
});


export default class App extends React.Component<any, any> {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                    Welcome to React Native!
                </Text>
                <Text style={styles.instructions}>
                    To get started, edit index.android.js
                </Text>
                <Text style={styles.instructions}>
                    Shake or press menu button for dev menu
                </Text>
            </View>
        );
    }
}

Run the app

I recommend adding this scripts to package.json:

"scripts": {
    "build": "gulp build",
    "watch": "gulp watch",
    "start": "concurrent \"npm run watch\" \"node node_modules/react-native/local-cli/cli.js start\" ",
    "android": "adb reverse tcp:8081 tcp:8081 && react-native run-android"
}

Run it!:

npm start

See the result

iOS

Open ios/ReactNativeTS.xcodeproj with XCode and press play.

Android

See facebook.github.io/react-native/docs/android-setup.html.

Conclusions

We can see that it’s 100% possible to write React-Native apps with Typescript.

The problem comes with maintainability, because the React-Native team is moving fast and the typings will not have official support.

Maybe it’s better to stick with Flow.

comments powered by Disqus