Inhaltsverzeichnis


Nichts verpassen?

react | Jack Franklin ÔÇó | 17 Minuten

Ich bin neulich auf den Geschmack von TypeScript gekommen und habe dar├╝ber in vielen Blogs von Tom Dale und anderen gelesen. Heute zeige ich dir wie ich ein TypeScript Projekt von Grund auf mit React und Webpack, um den Build-Process zu verwalten, neu eingerichtet habe. Ich werde dir auch meine ersten Eindr├╝cke von TypeScript geben und insbesondere wie du React mit TypeScript nutzen kannst.

Ich werde nicht zu sehr ins Detail der Besonderheiten der TypeScript Syntax eingehen, aber du kannst entweder das TypeScript Handbook oder das kostenlose Buch TypeScript Deep Dive, welches dir auch eine gro├čartige Einf├╝hrung in die Sprache geben wird, lesen.

TypeScript installieren und konfigurieren

Das erste was zu tun war, ist TypeScript lokal in mein node_modules zu installieren, was ich mit yarn gemacht habe. Zuerst habe ich also mit yarn init ein neues Projekt erstellt:

yarn init
yarn add typescript

Wenn du TypeScript installierst, bekommst du das tsc command line tool, welches TypeScript kompilieren kann und auch einen start tsconfig.json, den du bearbeiten kannst. Das kannst du, indem du tsc --init ausf├╝hrstÔÇô wenn du TypeScript lokal installiert hast, dann musst du ./node_modules/.bin/tsc --init ausf├╝hren.

Anmerkung: Ich habe das Verzeichnis ./node_modules/.bin auf meinem $PATH, welches du in meinen dotfiles finden kannst. Das ist etwas riskant, da ich versehentlich irgendeine ausf├╝hrbare Datei aus diesem Verzeichnis ausf├╝hren k├Ânnte, aber ich gehe das Risiko ein, weil ich wei├č was ich lokal installiert habe und das spart mir sehr viel getippe!

tsc --init generiert ein tsconfig.json, dort sind alle configs der Kompilierer von TypeScript. Es gibt ein paar Änderungen, die ich an der default config vorgenommen habe und diejenige die ich verwende, siehst du unten:

{
  "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

Mit dieser Regel kannst du ES2015 style default imports benutzen, sogar dann, wenn der Code, den du importierst, keinen ES2015 default export hat.

Das geschieht beim importieren, zum Beispiel wenn du React importierst, dessen Code nicht in ES2015 geschrieben ist (Der source code ist es, aber React liefert eine eingebaute Version). Das bedeutet, technisch gesehen hat er keinen ES2015 default export, also wird TypeScript dir sagen, wenn du ihn importierst. Allerdings sind Build-Tools wie Webpack in oder Lage, das richtige zu importieren, also schalte ich die Option an, weil ich import React from 'react' gegen├╝ber import * as React from'react'bevorzuge.

strict: true

TypeScript version 2.3 hat eine neue config Option eingef├╝hrt, strict. Wenn eingeschaltet, dann konfiguriert der TypeScript-Compiler so streng wie m├Âglich ÔÇô das ist vielleicht nicht was du willst, wenn du einen JS an TS portierst, aber f├╝r neue Projekte ist es sinnvoll so ÔÇ×strengÔÇť wie m├Âglich zu sein. Dies schaltet ein paar verschiedene Einstellungen ein, die erw├Ąhnenswertesten sind noImplicitAny und strictNullChecks:

noImplicitAny

Oftmals, wenn du TypeScript zu einem vorhandenen Projekt hinzuf├╝gen m├Âchtest, macht es dir TypeScript einfach, indem es keine Fehlermeldung auswirft, wenn du die Variablentypen nicht deklarierst. Allerdings m├Âchte ich, dass der Compiler so streng wie m├Âglich ist, wenn ich ein neues TypeScript Projekt aufbaue.

Eines der Dinge, die TypeScript standartm├Ą├čig ausf├╝hrt ist den any Typ zu Variablen anf├╝gt. any ist eine effektive Methode in TypeScript die sagt ÔÇ×type-checke das nicht, es kann jeder Wert seinÔÇť. Das ist n├╝tzlich, wenn du JavaScript portierst, aber es ist besser strikt zu sein, wenn du kannst. Mit dieser Einstellung die auf true gesetzt ist, kannst du keine Deklarationen verpassen. Zum Beispiel wird dir dieser Code einen Error geben, wenn noImplicitAny auf true gesetzt ist:

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

Du kannst dar├╝ber mehr in TypeScript Deep Dive lesen.

strictNullChecks

Das ist eine weitere Option, um den TypeScript-Compiler strikter zu gestalten. Das TypeScript Deep Dive Buch beinhaltet eine gro├če Sektion ├╝ber diese Option.

Mit dieser Option wird TypeScript mehr F├Ąlle erkennen, bei denen du auf einen Wert verweist, der undefiniert sein k├Ânnte. Es wird dir, zum Beispiel, ein Fehler zur├╝ckgeben:

person.age.increment()

Mit strictNullChecks aktiv, wirft Typescript eine Fehlermeldung aus und stellt sicher, dass du dich darum k├╝mmerst, wenn TypeScript denkt, dass person oder person.age undefiniert ist. Das verhindert runtime Fehler, deshalb scheint es eine ziemlich gute Option zu sein.

Webpack, Babel und TypeScript einstellen

Ich bin ein gro├čer Fan von Webpack; ich genie├če das ├ľkosystem der verf├╝gbaren Plugins, ich mag den Workflow der Developer und es ist gut bei der Verwaltung komplexer Anwendungen. Deshalb w├╝rde ich Webpack hinzuf├╝gen, auch wenn wir einfach den TypeScript-Compiler verwenden k├Ânnten. Wir brauchen auch Babel, weil der TypeScript Compiler ES2015 + React f├╝r uns ausgibt, also lassen wir Babel die Arbeit f├╝r uns machen. Lass uns Webpack, Babel und die n├Âtigen Presets installieren, zusammen mit dem ts-loader und dem Webpack Plugin f├╝r TypeScript.

Es gibt auch einen awesome-typeScript-loader, aber ich habe ts-loader zuerst gefunden und bisher funktioniert der super. Ich w├╝rde gerne von irgendjemandem der den awesome-typescript-loader benutzt h├Âren, wie die Vergleiche sind.

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

An dieser Stelle m├Âchte ich Tom Duncalf danken, Blog-Post zu TypeScript 1.9 + React war ein genialer Startpunkt f├╝r mich, den ich w├Ąrmstens empfehlen kann.

Es gibt nichts ├ťberraschendes in der Webpack-config, ich habe dem Code einige Kommentare beigef├╝gt:

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') }
    ]
  },
}

Wir konfigurieren die Loader so dass jede .ts(x) Datei zuerst durch den ts-loader geht - Dieser kompiliert es mit TypeScript, indem es die Einstellungen in unserer tsconfig.json benutzt ÔÇô und ES2015 zu emmitieren. Dann benutzen wir Babel um es runter zu ES5 zu konvertieren. Um das zu schaffen, erstelle ich eine .babelrc die die Voreinstellungen hat die wir ben├Âtigen:

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

Und damit sind wir jetzt in der Lage unsere TypeScript Anwendung zu schreiben.

Eine TypeScript React Komponente schreiben

Jetzt sind wir bereit um src/index.tsx zu erstellen, das wird der Einstiegspunkt unserer Anwendung. Jetzt k├Ânnen wir zun├Ąchst eine Dummy-Komponente erstellen und rendern, um zu checken, ob alles funktioniert.

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

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

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

Wenn du jetzt Webpack laufen l├Ąsst, siehst du einige Fehler:

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'.

Das passiert, weil TypeScript versucht den Typ von React herauszufinden. Das gleiche versucht er also f├╝r React DOM zu machen. React ist nicht in TypeScript verfasst, deshalb enth├Ąlt es diese Information nicht. Aber zum Gl├╝ck hat die Community f├╝r diesen Fall DefinitelyTyped erstellt, ein gro├čes Repertoir an Typen und Modulen.

Der Installations-Mechanismus hat sich k├╝rzlich ge├Ąndert; alle Typen werden unter dem Bereich @types gespeichert. Um die Typen f├╝r React und ReactDOM zu bekommen, f├╝hren wir folgendes aus:

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

Und damit verschwinden die Fehler. Immer wenn du eine Abh├Ąngigkeit installierst, kannst du immer versuchen das @types Paket auch zu installieren, oder wenn du sehen willst ob die Typen verf├╝gbar sind, kannst du die TypeSearch Webseite benutzen.

Die Anwendung lokal ausf├╝hren

Um die Anwendung lokal auszur├╝hren m├╝ssen wir einfach nur den webpack-dev-server Befehl ausf├╝hren. Ich habe daf├╝r ein Shortcut start erstellt:

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

Der Dev-Server wird die webpack.config.json Datei finden und sie benutzen, um unsere Anwendung zu bauen.

Wenn du yarn start ausf├╝hrst wirst du den Output vom Server sehen, eingeschlossen den Output vom ts-loader, der best├Ątigt, dass alles funktioniert.

$ 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 typescript@2.3.0 and /Users/jackfranklin/git/interactive-react-introduction/tsconfig.json
Version: webpack 2.4.1
Time: 6077ms
 Asset     Size  Chunks                    Chunk Names
app.js  1.14 MB       0  [emitted]  [big]  main
webpack: Compiled successfully.

Um es lokal zu sehen, kann ich eine index.html Datei erstellen, die unseren kompilierten Code l├Ądt:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>My Typescript App</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="dist/app.js"></script>
  </body>
</html>

Du solltest Hello world! auf Port 3000 sehen, dann l├Ąuft TypeScript!

Ein Modul eingeben

F├╝r ein Projekt an dem ich gearbeitet habe, wollte ich das React Ace Modul verwenden, um einen Code Editor in mein Projekt einzuf├╝gen. Allerdings hat das Modul keine Typen daf├╝r zur Verf├╝gung gestellt und es gibt auch kein @types/react-ace. In diesem Fall m├╝ssen wir Typen zu unserer Anwendung hinzuf├╝gen, damit TypeScript wei├č, wie man es eingibt. Das mag vielleicht ├Ąrgerlich klingen, aber die Vorteile, dass TypeScript zumindest ein bisschen ├╝ber alle Drittanbieter-Abh├Ąngigkeiten wei├č, wird dir viel Debugging-Zeit ersparen.

Um eine Datei zu definieren, die gerade Typen hat, h├Ąngst du ihr .d.ts (das ÔÇ×dÔÇť steht f├╝r ÔÇ×declarationÔÇť) an. Du kannst dar├╝ber mehr auf den TypeScript docs lesen. TypeScript wird diese Dateien in deinem Projekt automatisch finden. Du musst sie nicht explizit importieren.

Ich habe die Datei react-ace.d.ts erstellt, der das Module erstellt und ihren default expor als eine React Komponente definiert:

declare module 'react-ace' {
    interface ReactAceProps {
      mode: string
      theme: string
      name: string
      editorProps?: {}
      showPrintMargin?: boolean
      minLines?: number
      maxLines?: number
      wrapEnabled?: boolean
      value: string
      highlightActiveLine?: boolean
      width?: string
      fontSize?: number
    }

    const ReactAce: React.ComponentClass<ReactAceProps>
    export = ReactAce
}

Zuerst erstelle ich ein TypeScript Interface f├╝r die Eigenschaften, welche die Komponente ben├Âtigt, dann musst ich nur noch export = ReactAce deklarieren. Durch die Eingabe der Eigenschaften wird TypeScript mir sagen, wenn ich eine Eigenschaft falsch geschrieben habe oder vergessen habe, die wichtig ist.

Testen

Zum Schluss wollte ich auch ein gutes Testing Setup mit TypeScript haben. Ich bin ein gro├čer Fan von Facebooks Jest und habe ein bisschen gegooglet, um herauszufinden, ob ich es mit TypeScript starten kann. Es hat sich herausgestellt, dass es sehr gut m├Âglich ist, und da gibt es sogar das ts-jest Paket, dass all die schweren Arbeiten erledigt. Dar├╝ber hinaus gibt es da noch das @types/jest Paket, damit kannst du auch all deine Tests type-checken lassen kannst.

Gro├čes Dankesch├Ân an RJ Zaworski, dessen Post ├╝ber TypeScript und Jest der mich in dieses Thema eingef├╝hrt hat. Sobald du ts-jest installiert hast, musst du Jest nur noch konfigurieren, das machst du in package.json mit diesen Einstellungen:

"jest": {
  "moduleFileExtensions": [
    "ts",
    "tsx",
    "js"
  ],
  "transform": {
    "\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
  },
  "testRegex": "/*.spec.(ts|tsx|js)$"
},

Die erste Einstellung macht, dass Jest nach .ts und .tsx Dateien schaut. Das transform sagt, dass Jest jegliche TypeScript Dateien durch den ts-jest Pr├Ąprozessor zum laufen bringt, der sie ├╝ber den TypeScript-Compiler ausf├╝hrt und JavaScript erzeugt, das Jest erkennt. Dann schlie├člich habe ich die testRegex Einstellung geupdatet, um nach *.spec.ts(x) Dateien zu schauen (meine bevorzugte Namensgebungkonvention f├╝r Tests).

Damit kann ich einfach jest ausf├╝hren und alles l├Ąuft wie erwartet.

Linting mit TSLint

Obwohl TypeScript deinen Code auf viele Arten checkt, wollte ich trotzdem noch einen Linter, um einige weitere Code-Style- und Qualit├Ątschecks zu erm├Âglichen. ├ähnlich wie ESLint in JavaScript ist TSLint die beste Option, um TypeScript Dateien zu ├╝berpr├╝fen. Es funktioniert auf die gleiche Art wie ESLint ÔÇô mit einer Reihe von Regeln die du aktivierst oder deaktivierst. Und es gibt auch ein TSLint-React Paket, mit dem du spezifische Regeln hinzuf├╝gen kannst.

Du kannst TSLint ├╝ber eine tslint.json Datei konfigurieren. Meine ist unten. Ich benutze beide, die tslint:latest und die tslint-react Presets, welche eine Reihe von Regeln erm├Âglichen. Ich bin jedoch mit einigen der Defaults nicht einverstanden, also ├╝berschreibe ich sie ÔÇô du kannst das aber auch anders regeln ÔÇô das liegt bei dir!

{
    "defaultSeverity": "error",
    "extends": ["tslint:latest", "tslint-react"],
    "jsRules": {},
    "rules": {
      // use single quotes, but enforce double quotes in JSX
      "quotemark": [true, "single", "jsx-double"],
      // I prefer no semi colons :)
      "semicolon": [true, "never"],
      // This rule makes each Interface be prefixed with 'I' which I don't like
      "interface-name": [true, "never-prefix"],
      // This rule enforces objects to always have keys in alphabetical order
      "object-literal-sort-keys": false
    },
    "rulesDirectory": []
}

Dann kann ich das tslint --project tsconfig.json laufen lassen, um mein Projekt zu linten.

Credits

Original gepostet von Jack Franklin.