添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Jack Franklin

Getting started with TypeScript and React

I've recently been getting into TypeScript following a lot of positive blogs about it from Tom Dale and others. Today I'll show you how I've set up a TypeScript project from scratch that uses React, and Webpack for managing the build process. I'll also discuss my initial impressions of TypeScript and in particular working with TypeScript and ReactJS.

I won't be going into detail on the specifics of TypeScript's syntax, but you can read either the TypeScript handbook or the free book TypeScript Deep Dive which will also give you a great introduction to the language.

Update: If you'd like to read this post in German, you can do so thanks to the awesome folks at Reactx.de .

Installing TypeScript and configuring it

The first thing to do was install TypeScript locally into my node_modules directory, which I did using Yarn, first using yarn init to create a new project:

yarn init
yarn add typescript

When you install TypeScript you get the tsc command line tool which can compile TypeScript but also create a starting tsconfig.json for you to edit. You can get this by running tsc --init - if you've installed TypeScript locally you'll need to run ./node_modules/.bin/tsc --init .

Note: I have the ./node_modules/.bin directory on my $PATH , which you can find in my dotfiles . This is slightly risky, as I could accidentally run any executable that's in that directory, but I'm willing to take that risk because I know what's installed locally and it saves a lot of typing!

tsc --init generates a tsconfig.json which is where all the config for TypeScript's compiler lives. There's a few changes I've made to the default config, and the one I'm using is below:

{
"compilerOptions": {
"module": "es6", // use ES2015 modules
"target": "es6", // compile to ES2015 (Babel will do the rest)
"allowSyntheticDefaultImports": true, // see below
"baseUrl": "src", // enables you to import relative to this folder
"sourceMap": true, // make TypeScript generate sourcemaps
"outDir": "ts-build", // output directory to build to (irrelevant because we use Webpack most of the time)
"jsx": "preserve", // enable JSX mode, but "preserve" tells TypeScript to not transform it (we'll use Babel)
"strict": true,
},
"exclude": [
"node_modules" // don't run on any code in the node_modules directory
]
}

allowSyntheticDefaultImports

This rule allows you to use ES2015 style default imports even when the code you're importing doesn't have an ES2015 default export.

This happens when you import, for example, React, whose code is not written in ES2015 (the source code is, but React ships a built version). This means that it technically doesn't have an ES2015 default export, so TypeScript will tell you so when you import it. However, build tools like Webpack are able to import the right thing, so I turn this option on because I prefer import React from 'react' over import * as React from 'react' .

strict : true

TypeScript version 2.3 introduced a new config option, strict . When turned on this configures TypeScript's compiler to be as strict as possible - this might not be what you want if you're porting some JS to TS, but for new projects it makes sense to be as strict as possible out of the box. This turns on a few different settings, the most notable of which are noImplicitAny and strictNullChecks :

noImplicitAny

Often when you want to add TypeScript to an existing project TypeScript makes it easy by not throwing an error when you don't declare the types of variables. However, when I'm creating a new TypeScript project from scratch I'd like the compiler to be as strict as possible.

One of the things TypeScript does by default is implicitly add the any type to variables. any is effectively an escape hatch in TypeScript to say "don't type-check this, it can be any value". That's useful when you're porting JavaScript, but it's better to be strict when you can. With this setting set to true , you can't miss any declarations. For example, this code will error when noImplicitAny is set to true :

function log(thing) {
  console.log('thing', thing)

You can read more about this in the TypeScript Deep Dive .

strictNullChecks

This is another option that makes TypeScript's compiler stricter. The TypeScript Deep Dive book has a great section on this option . With this option on, TypeScript will spot more occasions where you're referencing a value that might be undefined, it will error at you. For example:

person.age.increment();

With strictNullChecks , if TypeScript thinks that person or person.age might be undefined , it will error and make sure you deal with it. This prevents runtime errors so it seems like a pretty good option to enable from the get go.

Setting up Webpack, Babel and TypeScript

I'm a big Webpack fan; I enjoy the ecosystem of plugins available, I like the developer workflow and it's good at managing complex applications and their build pipeline. Therefore, even though we could just use TypeScript's compiler, I'd still like to add Webpack in. We'll also need Babel because the TypeScript compiler is going to output ES2015 + React for us, so we'll get Babel to do the work. Let's install Webpack, Babel and the relevant presets, along with ts-loader , the Webpack plugin for TypeScript.

There is also awesome-typescript-loader , but I found ts-loader first and so far it's been great. I would love to hear from anyone who uses the awesome-typescript-loader , and how it compares.

yarn add webpack babel-core babel-loader babel-preset-es2015 babel-preset-react ts-loader webpack-dev-server

At this point I have to thank Tom Duncalf, whose blog post on TypeScript 1.9 + React was a brilliant starting point for me and I highly recommend it.

There's nothing too surprising in the Webpack config, but I've left some comments in the code to explain it:

const webpack = require('webpack');
const path = require('path');

module.exports = {
// put sourcemaps inline
devtool: 'eval',

// entry point of our application, within the `src` directory (which we add to resolve.modules below):
entry: ['index.tsx'],

// configure the output directory and publicPath for the devServer
output: {
filename: 'app.js',
publicPath: 'dist',
path: path.resolve('dist'),
},

// configure the dev server to run
devServer: {
port: 3000,
historyApiFallback: true,
inline: true,
},

// tell Webpack to load TypeScript files
resolve: {
// Look for modules in .ts(x) files first, then .js
extensions: ['.ts', '.tsx', '.js'],

// add 'src' to the modules, so that when you import files you can do so with 'src' as the relative route
modules: ['src', 'node_modules'],
},

module: {
loaders: [
// .ts(x) files should first pass through the Typescript loader, and then through babel
{
test: /\.tsx?$/,
loaders: ['babel-loader', 'ts-loader'],
include: path.resolve('src'),
},
],
},
};

We configure the loaders so that any .ts(x) file is first passed through ts-loader . This compiles it with TypeScript using the settings in our tsconfig.json - and emits ES2015 . We then use Babel to convert that down to ES5. To do this I create a .babelrc that contains the presets that we need:

{
"presets": ["es2015", "react"]
}

And with that, we're now ready to write our TypeScript application.

Writing a TypeScript React component

Now we are ready to create src/index.tsx , which will be our application's entry point. For now we can create a dummy component and render it to check it's all working.

import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
return (
<div>
<p>Hello world!</p>
</div>
);
};

ReactDOM.render(<App />, document.getElementById('app'));

If you run Webpack now against this code you'll see some errors:

ERROR in ./src/index.tsx
(1,19): error TS2307: Cannot find module 'react'.
ERROR in ./src/index.tsx
(2,22): error TS2307: Cannot find module 'react-dom'.

This happens because TypeScript is trying to figure out the type of React, and what it exports, and is trying to do the same for React DOM. React isn't authored in TypeScript so it doesn't contain that information, but thankfully for this situation the community has created DefinitelyTyped , a large repository of types for modules.

The installation mechanism changed recently; all the types are published under the npm @types scope, so to get the types for React and ReactDOM we run:

yarn add @types/react
yarn add @types/react-dom

And with that the errors go away. Whenever you install a dependency you can always try installing the @types package too, or if you want to see if it has types available, you can use the TypeSearch website to do so.

Running the app locally

To run the app locally we just run the webpack-dev-server command. I set up a script, start , that will do just that:

"scripts": {
"start": "webpack-dev-server"
}

The dev server will find the webpack.config.json file and use it to build our application.

If you run yarn start you will see the output from the server, including the ts-loader output that confirms it's all working.

$ webpack-dev-server
Project is running at http://localhost:3000/
webpack output is served from /dist
404s will fallback to /index.html
ts-loader: Using [email protected] and /Users/jackfranklin/git/interactive-react-introduction/tsconfig.json