There’s been quite a bit of buzz recently about the benefits cross-platform mobile development gains from libraries such as React Native, as well as what TypeScript can bring to improved JS developer experience with static typing. React Native comes out of the box with Flow support, and that looks to be a solid choice too for client-side type guarantees.
If you’re looking into using TypeScript however as part of your wider application codebase, you can use TypeScript with React Native projects to target web and mobile for iOS and Android (and soon-to-be Windows too), with lots of shared code, knowledge and tooling.
Guides to modern client-side development often feature very heavy build tooling setups that can feel brittle and a right pain to get up and running. I got this particular stack up and running relatively easily, and what it brings to the dev experience is simply worth it. For larger projects, the benefits for maintainable and code safety with static typing and the productivity boosts with one shared base framework can’t be overstated either.
In this Raygun post, I’ll be looking at the tooling needed to set up a new React Native project, convert the code to TypeScript, get the build process working, and finish with some notes about the workflow and the plusses it brings. React Native and TypeScript together you say? Yes, so let’s get on with it!
Creating a new React Native project
Starting from the absolute basics, we’ll install Node (for the JS bundle server), Watchman (a fast native file watcher tool from Facebook), and the React Native CLI to create the new project’s skeleton.
If you already have the above, you can skip to the next section where we’ll convert the project to TypeScript.
Install dependencies for React Native for your OS and preferred target platform:
As an example, for developing on MacOS and targeting iOS, you can run these commands from the directory where you want to create your project beneath:
brew install node
brew install watchman
npm install -g react-native-cli
react-native init myproject
Your device simulator will then appear and run the Hello World sample.
Converting the project to TypeScript
Here we’ll install the TypeScript tooling from NPM, and alter the project structure for a clean development/bundle workflow. Run the following:
npm install -g typescript
npm install typescript
npm install --save-dev @types/react-native
The third command will create a new
tsconfig.json file for our TypeScript build config. The fourth one will install install the Typescript definitions for the React Native package. Next, from the project root move the React Native JS source files to a directory beneath it, such as
src/, and rename them to their TypeScript extensions. For JSX files, rename them to
.tsx, and for non-JSX
.js modules, rename them to
.ts. Your index.*.js files need a
.tsx extension if they’re React Components.
Now we’ll configure TypeScript to build our React JSX syntax files. Add this to
noImplicitAny flag above – if you have a greenfield project you can set this to
true and gain the full power of static typing for the client out of the box. For larger existing projects this will require some refactoring out of the box, so it’s OK to let the compiler be lenient while you’re getting the build process set up.
Now, with your
tsconfig.json in place and your
.ts[x] files beneath
src, you can run the following:
artifacts/. You can then update the native code to reference the new entry points, such as in
AppDelegate.m, so that the jsCodeLocation looks like
jsBundleURLForBundleRoot:<a href="http://twitter.com/"artifacts/index.ios">@"artifacts/index.ios</a>" (and the same for Android). Running
npm start will have the RN CLI pick up the changes and bundle them up, ready for your simulator or device to load and run them.
Exploring TypeScript with the initial refactor
After running the compile command, you will also see a varying bunch of type warnings – this is a good thing! Your client-side code is now statically typed and the refactoring process can proceed safely
The most impressive thing about the TypeScript compiler is that while refactoring my JS to TS, that mechanical process of fixing the compiler warnings resulted in me picking up many logic, type and code path bugs that were hiding in the JS.
Guaranteeing the sanity of inputs and outputs results in more reliable code that doesn’t fall over at runtime, and therefore doesn’t need a human to diagnose and mop it up in production.
As a general guide, the initial process mostly involves updating code like
var foo = 'bar' to
var foo: string = 'bar', for both variables, function parameters, module exports etc. There’s also a bunch of nice static type definitions for the React/React Native libraries that the DefinitelyTyped definitions give you, and these are very human-readable and guide you as to what the API expects. Because those preconditions are now listed and discoverable, if you get stuck with the syntax warnings and types are now quickly googleable, speeding up the mechanical refactoring process further.
My favourite language feature in TS is generics – being able to write these in client-side code is a breath of fresh air and leads to much more natural and maintainable abstractions/DRY code. This alone is worth the cost of entry for me.
The documentation for TypeScript is thorough and clear, and for React TSX classes such docs at https://www.typescriptlang.org/docs/handbook/jsx.html provide a good overview of what it can do.
Fully modern development workflow with React Native and Typescript
Once you’ve got your project compiling and running to an acceptable degree, you can easily add a couple of nice tools that make the dev process really smooth and modern. My first recommendation is either Visual Studio Code or Atom, for lightweight IDE goodness.
The former has really tight TypeScript integration (VSCode itself is built with it), including build-on-save, inline Intellisense code hints/squigglies, compiler warnings in the build tool etc. Atom also has an atom-typescript extension, and a solid Vim mode (which I’m a fan of – VSCode’s Vim mode extensions are still under heavy development currently). As of time of writing VSCode feels very natural and jank-free, it Just Works and is very configurable.
Both, however, will use the new type definitions to show the real types of your function parameters and variables when you hover over them with the mouse. No more hunting through massive projects with grep/find-in-files just to find out what types common function expects or returns. TypeScript will give you the correct type the majority of the time with its structural (duck) typing.
After you’ve got the ability to type text into a file and compile it sorted, the next step is fancy modern hot reloading and live reloading support. If you’re not using your editor to call tsc with
compileOnSave, naturally running
tsc is manual and needs to be automated. A couple of options here are to use the
gulp-typescript plugins to run on the src directory after detecting file changes, with [grunt|gulp]-watch.
After that step spits out the JS files into
artifacts, the bundler can then pick them up and load them onto the device/simulator. By pressing Command-D (iOS) or the R key twice (Android), you can bring up the developer menu and enable Live Reload. In this mode, when the bundle has been re-generated the simulator will automatically reload, enabling a really fast development cycle.
This can be made even faster with the Hot Reload option, which dynamically replaces the changed class/module without causing the app to reload all its JS. This is amazing for designing with the React Native Flexbox implementation as the combination of type hints on
FlexStyle and sub-second UI updates make it fast to build a UI. And the best thing with this stack – if you architect your app properly, it’s one code base that is cross-platform and statically typed, leading to far lower maintenance costs and gnarly on-device debugging.
Naturally, static typing is not a panacea for all runtime bugs, but having a sane, designed language certainly helps you a whole bunch up front. For the logic/state/environment/etc bugs that do make it through, Raygun fortunately fully supports both sending runtime errors from the generated JS, as well as native exceptions that occur in the underlying Objective C/Java code.
This combination gives me more confidence in deploying updates and refactoring code, while ensuring end users get their hands on releases in a timely manner, as bug-free and feature-rich as possible.