TypeScriptでJavaScriptを作成してみた
はじめに
TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースのプログラミング言語である。 TypeScriptはJavaScriptに対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセットとなっている。
Wikipediaより
公式サイト TypeScript - JavaScript that scales.
インストール
node.jsをインストール
npm でインストール
$ node -v
v10.15.0
$ npm install -g typescript
TypeScriptファイルを作成してJavascriptにコンパイル
tsファイルでファイルを作成
class User {
}
console.log('hello world');
tscコマンドで対象ファイルをコンパイルすると同じ階層にjsファイルが作成される
tsc main.ts
var User = (function () {
function User() {
}
return User;
}());
console.log('hello world');
TypeScriptとJavaScritpの型の違い
- TypeScript = 静的型付け言語
- JavaScritp = 動的型付け言語
例
var x: number = 10;
x = 'hello';
console.log(x);
$ tsc main.ts
main.ts:2:1 - error TS2322: Type '"hello"' is not assignable to type 'number'.
// TypeScriptは、xの型がnumberと数字で決められたため、"hello"を代入することができない
var x = 10;
x = 'hello';
console.log(x);
$ node main.js
hello
// JavaScriptは、xの型がnumberと数字と代入したあとでも、"hello"という文字へ型を変更することが可能
基本の型
let 変数名: 型 = 値;
公式から
http://www.typescriptlang.org/docs/handbook/basic-types.html
Boolean 真偽値
let variable = true; // false
Number 数値
0-9の10進法のほかにも0-Fの16進法や小数点が可能
let number = 10;
let number = 0xf;
let number = 10.1;
String 文字
文字列
Array 配列
let list: number[] = [1, 2, 3];
Tuple
型が指定されているものに対して、配列を表現できる
let x: [string, number]; // 0番目が文字、1番目が数値
x = ['hello', 10]; // [文字,数値]なのでOK
x = [10, 'hello']; // [数値, 文字]なのでerror
Enum
列挙型
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
let colorName: string = Color[2];
Any
JavaScriptの動的型付け
var x: number = 10;
x = 'hello';
console.log(x);
$ tsc number.ts //error
var x: number = 10;
x = 'hello';
console.log(x);
$ tsc any.ts // OK
Void
anyと似ている。関数のreturnの値を決めるときに使用する
function hello(): void {
console.log("Hello World !");
}
hello();
$ tsc hello.ts
$ node hello.js
Hello World !
Never
決して起こらない型らしい。never occur.
これは使用するときがいまいちわからなかった。
関数の型のオーバーロード
型が複数あった場合
let add = function (a: any, b: any) {
if (typeof a === 'string' && typeof b === 'string') {
return a + ' ' + b;
} else {
return a + b;
}
}
console.log(add(4, 6));
console.log(add('Hello', 'World!'));
$ tsc main.ts
$ node main.js
10
Hello World!
クラス
ECMAScript 2015からのclassにも対応している
class User {
name: string;
constructor(name: string) {
this.name = name;
}
hello(): void {
console.log('Hello I\'m ' + this.name);
}
}
let tom = new User('Tom');
console.log(tom.name);
tom.hello();
$ tsc main.ts
$ node main.js
Tom
Hello I'm Tom
アクセス修飾子(public,protected,private)
class User {
name: string;
constructor(name: string) {
this.name = name;
}
hello(): void {
console.log('Hello I\'m ' + this.name);
}
}
let tom = new User('Tom');
console.log(tom.name);
tom.hello();
これは厳密にpublicが省略されている
class User {
public name: string;
constructor(name: string) {
this.name = name;
}
public hello(): void {
console.log('Hello I\'m ' + this.name);
}
}
let tom = new User('Tom');
console.log(tom.name);
tom.hello();
privateにするとインスタンスから読みだせなくなる
class User {
private name: string;
constructor(name: string) {
this.name = name;
}
private hello(): void {
console.log('Hello I\'m ' + this.name);
}
}
let tom = new User('Tom');
console.log(tom.name);
tom.hello();
$ npx tsc main.ts
main.ts:12:17 - error TS2341: Property 'name' is private and only accessible within class 'User'.
12 console.log(tom.name);
~~~~
main.ts:13:5 - error TS2341: Property 'hello' is private and only accessible within class 'User'.
13 tom.hello();
~~~~~
Found 2 errors.
// こうなってアクセスできませんとエラーが表示される
privateから値を取得、セットするときはgetとsetを使用する
class User {
private _name: string;
constructor(_name: string) {
this._name = _name;
}
public hello(): void {
console.log('Hello I\'m ' + this._name);
}
get name() {
return this._name;
}
set name(newValue: string) {
this._name = newValue;
}
}
let tom = new User('Tom');
console.log(tom.name);
tom.hello();
$ npx tsc main.ts -t es5 // オプションでes5を指定する
$ node main.js
Tom
Hello I'm Tom
Interface(オブジェクトへ型付け)
オブジェクトの型付け
function getTotal(result: { a: number, b: number }) {
return result.a + result.b;
}
let result = {
a: 32,
b: 58
}
console.log(getTotal(result));
$ npx tsc main.ts -t es6
$ node main.js
90
このgetTotalのオブジェクトプロパティを分離する
// 分離にinterfaceを使用する
interface Result {
a: number,
b: number
}
function getTotal(result: Result) {
return result.a + result.b;
}
let result = {
a: 32,
b: 58
}
console.log(getTotal(result));
$ npx tsc main.ts -t es6
$ node main.js
90
継承
// 分離にinterfaceを使用する
interface Result {
a: number,
b: number
}
// interfaceはextendsを使い継承することが可能
interface ResultA {
a: number
}
interface ResultB extends ResultA {
b: number
}
function getTotal(result: ResultB) {
return result.a + result.b;
}
classの組み合わせ
interface Test {
result: number;
showResult(): void;
}
// implements interface名とすることでclassには必ずinterfaceの値を含んでいないいけないとすることができる
class User implements Test {
name: string;
constructor(name: string) {
this.name = name;
}
hello(): void {
console.log('Hello I\m ' + this.name);
}
}
$ npx tsc main.ts -t es6
main.ts:6:7 - error TS2420: Class 'User' incorrectly implements interface 'Test'.
Type 'User' is missing the following properties from type 'Test': result, showResult
6 class User implements Test {
~~~~
Found 1 error.
内部モジュール
module UserModule {
export let name = 'Tom';// 出力する際はexportをつける
export module ResultModule {
export let result = '80';
}
}
console.log('I\'m ' + UserModule.name)
console.log('Result ' + UserModule.ResultModule.result)
$ npx tsc main.ts -t es6
$ node main.js
I'm Tom
Result 80
moduleを別ファイルに分ける
module UserModule {
export let name = 'Tom';
export module ResultModule {
export let result = '80';
}
}
/// <reference path='./user.ts' /> // /が3つと<reference />でmoduleファイルを指定する
console.log('I\'m ' + UserModule.name)
console.log('Result ' + UserModule.ResultModule.result)
tsc main.ts
node main.js
// これだとmain.tsのみがコンパイルされmoduleのファイルがコンパイルされない
tsc main.ts --out all.js // outオプションを付与して別ファイルにmain.tsとuser.tsを合わせたファイルをコンパイルする
node all.js
I'm Tom
Result 80
外部モジュール
export let name = 'Tom';
export let result = '80';
import User = require('./user');
import Result = require('./result');
console.log('I\'m ' + User.name)
console.log('Result ' + Result.result)
これでmain.tsをコンパイルすればよいが方法はいくつかある
今回はNode - CommonJS
$ npx tsc main.ts -m commonjs
$ node main.js
I'm Tom
Result 80
JSはこんな感じで出力される
"use strict";
exports.__esModule = true;
var User = require("./user");
var Result = require("./result");
console.log('I\'m ' + User.name);
console.log('Result ' + Result.result);
"use strict";
exports.__esModule = true;
exports.name = 'Tom';
"use strict";
exports.__esModule = true;
exports.result = '80';
感じたこと
型がきっちりきめないといけなく、それが戸惑うところ
正直jQueryでJavaScriptを数百行単位で開発していたころに比べ、さらにJavaScriptの開発を大掛かりにしてくるととても重要になってくると感じた
JavaScriptを大規模開発で使用を前提として考えているのも納得
(sassが出始めてときもこんな感じだったのかと思う)