中安拓也のブログ

プログラミングについて書くブログ

TypeScriptでcanvas要素を扱っていたら発生したエラー: 型に呼び出しシグネチャがない式を呼び出すことはできません

f:id:l08084:20180211170429p:plain

はじめに

Angular + TypeScriptでcanvasの処理を書いていたら、下記のエラーが発生しました(3つとも同様のエラー)。

ERROR in src/app/app.component.ts(159,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'string | boolean' has no compatible call signatures.
src/app/app.component.ts(178,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'string | boolean' has no compatible call signatures.
src/app/app.component.ts(183,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'string | boolean' has no compatible call signatures.

開発環境

  • Angular@5.2.0

  • TypeScript@2.5.3

  • VisualStudioCode@1.20.0

発生したエラーについて

上記エラーは日本語だと下記の通り表示されます。

[ts] 型に呼び出しシグネチャがない式を呼び出すことはできません。型 'string | boolean' には互換性のある呼び出しシグネチャがありません。

エラーが発生した箇所はいずれもHTML5のcanvasから取得したcontextからメソッドを呼び出す箇所で発生しています。

  • エラー発生箇所-1
render() {
    const ctx = this.context;
    if (ctx) {
      ctx.clearRect(0, 0, this.canpasWidth, this.canpasHeight); // <- エラー発生
      ctx.strokeStyle = 'black';
  • エラー発生箇所-2, 3
  drawBlock(x: number, y: number) {
    this.context.fillRect(this.blockWidth * x,
                          this.blockHeight * y,
                          this.blockWidth - 1,
                          this.blockHeight - 1); // <- エラー発生

    this.context.strokeRect(this.blockWidth * x,
                            this.blockHeight * y,
                            this.blockWidth - 1,
                            this.blockHeight - 1); // <- エラー発生
  }

エラー原因

canvasから取得したcontextの型を誤まってCanvas2DContextAttributesにしたのが原因でした。

  • 修正前のコード
export class AppComponent implements AfterViewInit {
context: Canvas2DContextAttributes; // <- エラー原因

  ngAfterViewInit() {
    const canvas = this.block.nativeElement;
    this.context = canvas.getContext('2d');
    setInterval(() => this.render(), 30);
  }
}

contextの型をCanvasRenderingContext2Dに修正したところエラーが全部消えて正しく稼働しました。

  • 修正後のコード
export class AppComponent implements AfterViewInit {
context: CanvasRenderingContext2D; // <- CanvasRenderingContext2Dに修正するとエラーが消える

  ngAfterViewInit() {
    const canvas = this.block.nativeElement;
    this.context = canvas.getContext('2d');
    setInterval(() => this.render(), 30);
  }
}

参考にしたサイト

qiita.com

VSCodeのDecoratorに関するエラー:[ts] Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option to remove this warning.

f:id:l08084:20180209151947p:plain

はじめに

Visual Studio Codeを使ってAngular + Typescriptのコードを書いてたら下記のエラー(警告?)が発生したので、対処法を調べました。

[ts] Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option to remove this warning.

エラー文には、「今後のリリースでデコレータの仕様は変わる可能性があります。このエラーを消したければ、'experimentalDecorators' オプションを設定してください。」と書いてある気がします。

開発環境

  • Angular@5.2.0

  • TypeScript@2.5.3

  • VisualStudioCode@1.20.0

エラー対応方法

tsconfig.jsonにエラー内容で指示された'experimentalDecorators''allowJs'オプションを設定します。

{
  "compileOnSave": false,
  "include": [
    "typings/index.d.ts"
  ],
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true, // <- this add
    "allowJs": true, // <- this add
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}

tsconfig.jsonに上記設定を追加した後、VSCodeを再起動すると表題のエラーが表示されなくなるはずです。

参考にしたサイト

ihatetomatoes.net

VSCodeでJavaScriptのドキュメンテーションコメント(JSDoc)を自動生成する

f:id:l08084:20180208152959p:plain

はじめに

Visual Studio Codeを使用してJSDocコメントを自動生成する方法について書きます。

JSDoc自動生成方法

Document Thisのインストール

VSCodeの拡張機能ボタンをクリックした後、Document Thisをインストールする。 なお、Document ThisはJavaScriptだけでなく、TypeScriptにも対応している。 f:id:l08084:20180208151738p:plain

JSDocコメント生成

インストールが完了したら、JSDocコメントを作成したい関数にカーソルを合わせて、Ctrl+Alt+Dを2回実行するとコメントを生成してくれる(Mac book proとかだとAltキーはないのでOptionキーになる)。 f:id:l08084:20180208152222p:plain

Angular + Redux 環境の構築手順

はじめに

ReduxをAngularで使用するときの環境構築手順を記述する

出典

github.com

環境構築手順

まず、Angular CLIを使用してAngularプロジェクトを作成する

# Install Angular CLI
npm install -g @angular/cli

# Use it to spin up a new app.
ng new angular-redux-quickstart
cd angular-redux-quickstart

angular-reduxライブラリをインストールして完了

npm install redux --save @angular-redux/store

angular-reduxの記述ルールについては、上記リンクを参考にする。

コードレビュー依頼時のGitコマンド

はじめに

以前所属していたプロジェクトにおけるコードレビューの流れ(Gitコマンドの手順)をメモ。

コードレビュー依頼時のGitコマンド

$ git checkout feature/v0.9.1/bug-fix-no-250
$ git rebase develop
$ git push -f
  1. コードレビューを依頼したいfeatureブランチにチェックアウト
  2. featureブランチ名は、基本的にfeature/[developブランチのバージョン番号]/bug-fix-no-[Redmineのチケット番号]のルールで命名される(※) ※ featureブランチ名の例: feature/v0.9.1/bug-fix-no-250
  3. リベースを実施git rebase develop
  4. featureブランチをGit rebaseした結果を強制的にリモートブランチに書き込むgit push -f
  5. Gitホスティングサービス(Gogsを使用)からdevelopブランチに向けてプルリクエストを作成
  6. レビューの結果、問題がなければプルリクエストがレビュー担当者によりマージされる
  7. プルリクエストをマージした後、レビュー担当者によりバージョン番号がインクリメントされ、developブランチにタグとして発行される

リベース

$ git rebase develop

# コンフリクトした時(add コマンドの後に)
$ git rebase --continue
# リベースを中断したい時
$ git rebase --abort
  • developブランチからfeatureブランチを作成した後、developブランチ側で実施された変更をfeatureブランチ側に取り込む時にGit rebase を実施している

  • 以前はGit rebaseでなく、featureブランチに対して、developブランチをマージすることによってfeatureブランチにdevelop側の変更を取り込んでいた

  • 作業ツリーが見づらくなるという理由で、現在はマージによる変更取り込みでなく、リベースを実施している

  • 通常のpushでリベース結果をリモートに書き込むとマージによるコミットが自動で実施され、コミットログが読みづらくなるため、git push -fを実施する

  • git rebase develop後にコンフリクトが発生した場合は、コンフリクト解消後にgit addgit rebase --continueコマンドを実施する(リベースを中止する場合はgit rebase --abort)

エラー(error TS2503: Cannot find namespace'NodeJS'.)が発生した場合の対応メモ

はじめに

Angular5 + Typescript2でコードを書いてたら、表題のエラーに遭遇した。遭遇するの2度目なので解決方法をメモる。

f:id:l08084:20180204150857p:plain

開発環境

  • Angular@5.2.0

  • TypeScript@2.5.3

  • Node@8.1.4

エラー発生時のコード

下記コードのinterval: NodeJS.Timer;の部分でTS2503: Cannot find namespace'NodeJS'.エラーが発生した

export class AppComponent {

  interval: NodeJS.Timer; // <- ここで表題のエラーが発生

  sampleOne() {
    clearInterval(this.interval); // インターバルをリセット
    this.interval = setInterval(() => this.sampleTwo(), 250); // 250ミリ秒間隔でメソッドを呼び出す
  }
}

対応手順

# typingsをインストールしていない場合
$ npm install -g typings

$ typings install dt~node --global --save-dev
  1. プロジェクトディレクトリ上でターミナルを開き、typings install dt~node --global --save-devコマンドを実施(※) ※typingsがインストールされていない場合は、npm install -g typingsで事前にインストールが必要

  2. 上記コマンドの結果、typingsディレクトリが作成される

  3. tsconfig.jsonに"include": ["typings/index.d.ts"]を追加

{
  "compileOnSave": false,
  // ここから
  "include": [
    "typings/index.d.ts"
  ],
  // ここまでを追加
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}

参考にしたサイト

stackoverflow.com

forEachループの途中で要素を削除した場合のメモ

はじめに

forEachでループを回している配列の要素をループの途中で削除した時に、(あくまで自分からみるとの話です)よくわからない挙動をしたので備忘録目的でまとめました。

developer.mozilla.org

ループの途中で要素を削除した場合についての、👆MDNのサイトで記載されている部分の引用です。

forEach によって処理される配列要素の範囲は、callback が最初に呼び出される前に設定されます。forEach の呼び出しが開始された後に追加された配列要素に対しては、callback は実行されません。既存の配列要素が変更または削除された場合、callback に渡される値は forEach がそれらを参照した時点での値になります。削除された配列要素を参照することはありません。

👆上記引用文ですが、渡された時点の配列要素の範囲でループを回すので、ループの途中で配列を追加したり、削除してもループ回数とループで渡す要素には影響ないよ、という意味だと自分は解釈しています。

検証プログラム

let eventA = {
    title: '遠征',
    start: '4/10'
}
let eventB = {
    title: '練習試合',
    start: '4/10'
}
let eventC = {
    title: '留学',
    start: '4/11'
}
let eventD = {
    title: '旅行',
    start: '4/10'
}
let eventList = [eventA, eventB, eventC, eventD];
let convertedList = [];

eventList.forEach((event, index, array) => {
    // startが同じeventの配列を作成する
    let filteredArray = eventList.filter((element) => {
        return (element.start === event.start);
    });

    // startが同じeventの件数をtitleに代入する
    event.title = filteredArray.length;

    // 件数をカウントしたeventをeventListから削除する
    eventList = eventList.filter((element) => {
        return (event.start !== element.start);
    });
    convertedList.push(event);
});

console.log(convertedList);
/*  実行結果
    Array(4) [Object, Object, Object, Object]
    length:4
    __proto__:Array(0) [, …]
    0:Object {title: 3, start: "4/10"}
    1:Object {title: 0, start: "4/10"}
    2:Object {title: 1, start: "4/11"}
    3:Object {title: 0, start: "4/10"}
*/

上記のソースコードですが、開始(start)が同じ日付のeventオブジェクトが複数件あった場合、一つにまとめて、まとめたeventオブジェクトの件名(title)に開始が同じ日付のeventの件数を設定するということをしています。 上記プログラムを動かす前は、開始(start)が4/10のイベントが3件、開始(start)が4/11のイベントが1件あるので、下記の通り表示される認識でしたが、

    Array(2) [Object, Object]
    length:4
    __proto__:Array(0) [, …]
    0:Object {title: 3, start: "4/10"}
    2:Object {title: 1, start: "4/11"}

実際には、次の通り表示されました。

    Array(4) [Object, Object, Object, Object]
    length:4
    __proto__:Array(0) [, …]
    0:Object {title: 3, start: "4/10"}
    1:Object {title: 0, start: "4/10"}
    2:Object {title: 1, start: "4/11"}
    3:Object {title: 0, start: "4/10"}

一度、開始(start)が同じだと判断してまとめたeventについては、次の部分で削除しているので、

// 件数をカウントしたeventをeventListから削除する
eventList = eventList.filter((element) => {
    return (event.start !== element.start);
});

titleが0のeventは出力されない認識でしたが、下記2つの要因が原因で上記の通り出力されたようです。

  • MDNのサイトに記載されている通り、ループの途中で要素を削除しても、ループの範囲とループで渡される要素には、影響がない

  • forEachで回しているeventList配列と、内部から参照しているeventList配列が別々の参照になっているため、forEachで回しているeventList配列では、要素が削除されていないが、内部から参照しているeventList配列では、要素が削除されいる扱いになっている(4回目のループでは、forEachで回しているeventList配列の要素数は4件だが、内部から参照しているeventList配列の要素数は2件になっていると思われる)

スコープについての理解が曖昧なせいか(?)原理がよくわからない...そもそもループの途中で要素を削除するようなプログラムを書くこと自体が間違っているのか?それともforEach以外を使うべきだったのか...という感じです。