20201206のJavaScriptに関する記事は30件です。

Babylon.js で WebGPU を試してみるテスト

この記事はWeb グラフィックス Advent Calendar 2020の6日目の記事です。

はじめに

この記事では Babylon.js の次バージョンとして開発中の v5.0.0 alpha を用いて、WebGPU を試してみようと思います。

Babylon.js とは

Babylon.js はマイクロソフト社が開発している非常にパワフルな WebGL ベースの 3D エンジンです。
2020年11月に安定版である Babylon.js v4.2 が発表され、現在は次バージョンとして v5.0.0 の開発がスタートしています。

WebGPU とは

WebGPUWebGLWebGL2 の後継とされているグラフィックス API です。
WebGLKhronos グループが中心となって仕様策定が行われていましたが WebGPUW3C コミュニティグループにより行われています。

ブラウザのAPI 対応するバックエンドのAPI 利用可能なシェーダ 仕様策定
WebGL 1.0 OpenGL ES 2.0 GLSL ES 1.0 Khronos
WebGL 2.0 OpenGL ES 3.0 GLSL ES 3.0 Khronos
WebGPU Vulkan/DirectX/Metal WGSL W3C

WGSL とは

WGSLWebGPU Shading Language の略で、その名の通り WebGPU 用のシェーダです。
残念ながら WGSL の対応はまだ初期の段階である為、Chrome や Firefox の WebGPU 実装では、暫定的に SPIR-V (GLSL) が用いられています。

WebGPU を試すには?

Chrome Canary 及び Firefox Nightly でフラグを有効化することで、WebGPU を試すことが出来ます。
ただし、現状、Firefox の WebGPU 実装は若干不安定な為、Chrome Canary で試すのが良いかと思います。

ブラウザ WebGPU 有効化方法
Chrome Canary chrome://flags/#enable-unsafe-web
Firefox Nightly dom.webgpu.enable

Babylon.js の WebGL と WebGPU モードの比較

Babylon.jsWebGPU 対応としては、グラフィック API の違いをほとんど吸収してくれている為、僅かな変更で対応できるように設計されています。

以下のサンプルでの対応箇所は以下の通りです。エンジンの初期化を非同期で行うことが主な違いです。
image.png

実際動作するサンプルへのリンクは下記になります。

WebGL モード :https://cx20.github.io/webgl-test/examples/babylonjs/complex/index.html
WebGPU モード:https://cx20.github.io/webgpu-test/examples/babylonjs/complex/index.html

以下は実行結果のイメージです。

image.png

おわりに

WebGPU は、より低レベルな制御が行えるようになる半面、コードの記述量が増えることが予想されます。
各種 Web 3D エンジンやライブラリでも WebGPU 対応を視野に入れているようですので、恐らく Babylon.js の対応同様、ユーザーへの影響を最小限に抑えるような対応をしてくるのではないかと思います。
その為、以前より、エンジンやライブラリの重要度が増すと考えられます。

ちなみに、WebGPU の登場を首を長くして待っている方も多いかもしれませんが、残念ながら、もう少し待つ必要があるようです。
今のところ仕様策定の完成目標としては 2022年のQ1を予定しているそうです。

WebGPU には Compute Shader も含まれるようですので、より高速な処理やエンジンの開発を目指す方はドラフト仕様を調査しておくのも悪くないと思います。

参考情報

■ WebGPU Support | Babylon.js Documentation
https://doc.babylonjs.com/advanced_topics/webGPU
■ WebGPU Implementation Status
https://github.com/gpuweb/gpuweb/wiki/Implementation-Status
■ WebGPU Table of Contents
https://github.com/gpuweb/gpuweb/wiki/Table-of-Contents
■ WebGPU Samples
https://austineng.github.io/webgpu-samples/
■ Test of WebGL Library
https://github.com/cx20/webgl-test
■ Test of WebGPU Library
https://github.com/cx20/webgpu-test
■[忙しい人向け] 100行から始めるWebGPU
https://qiita.com/cx20/items/67f4e70b36e06ef77900

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】超簡単なお絵かきアプリを作りました

完成物
demo

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" href="./style.css">
  </head>
  <body>
    <div id="black"></div>
    <div id="red"></div>
    <div id="green"></div>
    <script src="./main.js"></script>
  </body>
</html>
style.css
.dot {
  position: absolute;
  width: 10px;
  height: 10px;
}

#black {
  width: 100px;
  height: 100px;
  background-color: black;
}

#red {
  width: 100px;
  height: 100px;
  background-color: red;
}

#green {
  width: 100px;
  height: 100px;
  background-color: green;
}
main.js
let clicking;
let color = 'black';
const black = document.getElementById('black');
const red = document.getElementById('red');
const green = document.getElementById('green');

black.addEventListener('click', () => {
  color = 'black';
});
red.addEventListener('click', () => {
  color = 'red';
});
green.addEventListener('click', () => {
  color = 'green';
});

document.addEventListener('mousedown', () => {
  clicking = true;
});

document.addEventListener('mouseup', () => {
  clicking = false;
});

document.addEventListener('mousemove', (e) => {
  if (!clicking) {
    return;
  }
  const dot = document.createElement('div');
  dot.className = 'dot';
  dot.style.left = `${e.clientX}px`;
  dot.style.top = `${e.clientY}px`;
  dot.style.backgroundColor = color;
  document.body.append(dot);
});

スクリーンショット 2020-12-06 23.33.23.png

楽しい・・・
時間かけたらもっと本格的なものが作れそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでブラウザから音声を録音してFirebaseにアップロードしてみた(iOS未対応)

経緯

最近は音声を投稿するSNSが増えてきているなぁと感じてきており、私が知っている限りでも10個は直ぐに思い出せます。
そこで、いつか音声サービスを作ってみたくなるかもしれないので、興味本位で調べてみました。

動作確認

WindowsのChrome

開発情報

  • react
  • firebase
  • react-audio-player

デモ動画

YouTubeで確認できます。
https://youtu.be/zHmhcu2CANs

コード

App.js
import React, { useEffect, useState, useRef } from "react";
import firebase from "firebase";
import ReactAudioPlayer from "react-audio-player";

// configは、自分のfirebaseの設定を指定してください。
const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  storageBucket: "",
};

const App = () => {
  const [file, setFile] = useState([]);
  const [audioState, setAudioState] = useState(true);
  const audioRef = useRef();

  useEffect(() => {
    if (firebase.apps.length === 0) {
      firebase.initializeApp(config);
    }
    // マイクへのアクセス権を取得
    navigator.getUserMedia =
      navigator.getUserMedia || navigator.webkitGetUserMedia;
    //audioのみtrue
    navigator.getUserMedia(
      {
        audio: true,
        video: false,
      },
      handleSuccess,
      hancleError
    );
  }, []);

  const handleSuccess = (stream) => {
    // レコーディングのインスタンスを作成
    audioRef.current = new MediaRecorder(stream, {
      mimeType: "video/webm;codecs=vp9",
    });
    // 音声データを貯める場所
    var chunks = [];
    // 録音が終わった後のデータをまとめる
    audioRef.current.addEventListener("dataavailable", (ele) => {
      if (ele.data.size > 0) {
        chunks.push(ele.data);
      }
      // 音声データをセット
      setFile(chunks);
    });
    // 録音を開始したら状態を変える
    audioRef.current.addEventListener("start", () => setAudioState(false));
    // 録音がストップしたらchunkを空にして、録音状態を更新
    audioRef.current.addEventListener("stop", () => {
      setAudioState(true);
      chunks = [];
    });
  };
  // 録音開始
  const handleStart = () => {
    audioRef.current.start();
  };

  // 録音停止
  const handleStop = () => {
    audioRef.current.stop();
  };
  // firebaseに音声ファイルを送信
  const handleSubmit = () => {
    // firebaseのrefを作成
    var storageRef = firebase.storage().ref();
    var metadata = {
      contentType: "audio/mp3",
    };
    // ファイル名を付けてBlobからファイルを作成して送信
    var mountainsRef = storageRef.child(new Date() + "test.mp3");
    mountainsRef.put(new Blob(file), metadata).then(function () {
      console.log("アップロード完了!");
    });
  };
  const handleRemove = () => {
    setAudioState(true);
    setFile([]);
  };

  const hancleError = () => {
    alert("エラーです。");
  };

  return (
    <div>
      <button onClick={handleStart}>録音</button>
      <button onClick={handleStop} disabled={audioState}>
        ストップ
      </button>
      <button onClick={handleSubmit} disabled={file.length === 0}>
        送信
      </button>
      <button onClick={handleRemove}>削除</button>
      <ReactAudioPlayer src={URL.createObjectURL(new Blob(file))} controls />
    </div>
  );
};

export default App;

所感

ほぼ参考サイトのコピペですが、それをReactで実装してみました。
Webで音声を録音することも案外できるものだなぁと感じました。
また、音声の再生には「react-audio-player」というライブラリが直感的に使えて助かりました。
音声を録音と変換が簡単にできるreactのライブラリがあればよかったのですが...

また、iOSのブラウザではWeb Audio APIを使用する必要があるみたいで、この実装だと動きません。
実装し終えてから気づきました。。。

次はiOSに対応するバージョンと、ReactNativeでの音声の録音と保存にも挑戦してみようと思います。

最後に

※発言は個人の見解で所属組織とは無関係です

参考にした記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FixedMidashiで縦スクロールした時にIEの時だけヘッダが上下にがたつく挙動の対処法

 業務で複数の行および列が固定されたテーブルを作成するにあたり、FixedMidashi.jsを使用しました。大変使いやすいライブラリですが、IEで縦スクロールを行ったときに上部ヘッダが一瞬上下にがたつくため、その対処法を記載します。

発生条件

  1. ブラウザ ⇒ IE(私はIE11を使用しています。下のバージョンで現象が発生するかは未確認です)。
  2. 縦スクロールが一番上にある状態でマウスホイールでスクロール

FixedMidashiを用いたテーブルの用意

まずFixedMidashi公式ページの使用方法に従ってテーブルを作成しました。
※業務ではdivモードを使用したので、divモードで記載します。

 ・tableタグをdivタグで囲み、そのdivタグにclassを付けます。
  (本記事ではclass="scroll_div"を付与)
 ・<table>_fixedhead="rows:x; cols:y"を付けます。
  (x,yにはそれぞれ固定したい行数、列数を記載)

table.html
<body>
    <div class="scroll_div">
        <table _fixedhead="rows:2; cols:2">

      ~省略~

        </table>
    </div>
</body>

onloadイベントでFixedMidashi.create()を呼び出します。

table.js
window.onload = function () {
            FixedMidashi.create();
}

・bodyタグを囲っている<div class="scroll_div">overflow: autoを付けます。

table.css
table tr,
table th,
table td {
    border: 1px solid black;
}
table {
    border-collapse: collapse;
    white-space: nowrap;
    box-sizing: content-box;
}
.scroll_div {
    overflow: auto;
    width: 312px; /* 任意の数字*/
    height: 159px; /* 任意の数字*/
}

table.PNG

対応方法

FixedMidashiの公式ページに以下の記載があります。

FixedMidashiは固定する見出し部分を別 table にコピーし、それを元 table の上に重ねて表示する、 という方法で実現しています

  FixedMidashi公式ページの概要より引用(http://hp.vector.co.jp/authors/VA056612/fixed_midashi/manual/index.html#summary)

 対応方法としては元テーブルの上に重ねて表示された固定見出しに手を加えることになります。

行固定見出しへの対応

縦スクロール前
 行固定見出しがvisibility: hiddenとなっています。
scroll.PNG

縦スクロール後
 visibility: visibleとなります。
scrolled.PNG

一番上からスクロールした時に行固定見出しのvisibilityの値がhiddenからvisibleへ変わり、またスクロールを一番上へ戻すとvisibleからhiddenへ変わります。
これががたつきの要因となっています。
ここで、visibilityが常にvisibleとなるクラスを作成します。

table.css
// スクロールがたつき対応
.copyheader_visible {
   visibility: visible !important;
}

スクロールバーが一番上に来た時に行固定見出しにclass="copyheader_visible"を追加します。

table.js
 // IEの時のみ対応
 if (navigator.userAgent.match(/MSIE 10/i) || navigator.userAgent.match(/Trident\/7\./)) {
     // 行固定見出しに、スクロールがたつき対応クラスを追加
     $(".scroll_div").eq(2).addClass("copyheader_visible");

     $(".scroll_div").scroll(function () {
        // 縦スクロール量を取得
        var scrollTop = $('.scroll_div').scrollTop();

       if (scrollTop == 0) {
          // 縦スクロールの位置が一番上に来た時
          var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
          if (!topHeaderVisible) {
            // 行固定見出しをvisibleとする
            $('.scroll_div').eq(2).addClass('copyheader_visible');
          }
       } else {
          var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
          if (topHeaderVisible) {
             $('.scroll_div').eq(2).removeClass('copyheader_visible');
          }
       }
  })

行列固定見出しへの対応

このままだと横スクロールをした時に行固定見出しが左へ突き出てしまいます。
scrollLeft.PNG

そのため、縦スクロールをしていない状態で横スクロールを行った際に行列固定見出しvisibilityが常にvisibleとなるようにします。

table.js
  /* ~該当箇所のみ抜粋~ */

 // 縦スクロール量を取得
 var scrollTop = $('.scroll_div').scrollTop();
// 横スクロール量を取得
 var scrollLeft = $('.scroll_div').scrollLeft();

 if (scrollTop == 0 && scrollLeft == 0) {
    // 縦スクロールの位置が一番上かつ、横スクロールの位置が一番左の時
    var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
    if (!topHeaderVisible) {
        // 行固定見出しをvisibleとする
        $('.scroll_div').eq(2).addClass('copyheader_visible');
    }
 }else if(scrollTop == 0 && scrollLeft != 0){
       // 縦スクロールの位置が一番上のときかつ、横スクロールされている時
       var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
       var leftTopHeaderVisible = $('.scroll_div').eq(3).hasClass("copyheader_visible");
       if (!topHeaderVisible) {
          // 行固定見出しをvisibleとする
          $('.scroll_div').eq(2).addClass('copyheader_visible');
       }
       if (!leftTopHeaderVisible) {
          // 行列固定見出しをvisibleとする
          $('.scroll_div').eq(3).addClass('copyheader_visible');
       }
 } else {
        var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
        var leftTopHeaderVisible = $('.scroll_div').eq(3).hasClass("copyheader_visible");
          // 初期状態に戻す
          if (topHeaderVisible) {
               $('.scroll_div').eq(2).removeClass('copyheader_visible');
          }
          if(leftTopHeaderVisible){
               $('.scroll_div').eq(3).removeClass('copyheader_visible');
          }

}

まとめると

以上をまとめると以下のようなコードになります。
※一枚のhtmlにまとめました。

table.html
<!DOCTYPE html>
<html>
<head>
    <title>FixedMidashi</title>
    <meta charset="utf-8" />
    <style type="text/css">
        .scroll_div {
            overflow: auto;
            width: 312px;
            height: 159px;
        }
        table tr,
        table th,
        table td {
            border: 1px solid black;
        }
        table {
            border-collapse: collapse;
            white-space: nowrap;
            box-sizing: content-box;
        }
        /* がたつき対応 */
        .copyheader_visible {
            visibility: visible !important;
        }
    </style>
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script src="./fixed_midashi_1.11/fixed_midashi.js"></script>
    <script>
        window.onload = function () {
            FixedMidashi.create();
            // IEの時のみ対応
            if (navigator.userAgent.match(/MSIE 10/i) || navigator.userAgent.match(/Trident\/7\./)) {
                行固定見出しにスクロールがたつき対応クラスを追加
                $(".scroll_div").eq(2).addClass("copyheader_visible");

                $(".scroll_div").scroll(function () {
                    // 縦スクロール量を取得
                    var scrollTop = $('.scroll_div').scrollTop();
                    // 横スクロール量を取得
                    var scrollLeft = $('.scroll_div').scrollLeft();

                    if (scrollTop == 0 && scrollLeft == 0) {
                        // 縦スクロールの位置が一番上かつ、横スクロールの位置が一番左の時
                        var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
                        if (!topHeaderVisible) {
                            // 行固定見出しをvisibleとする
                            $('.scroll_div').eq(2).addClass('copyheader_visible');
                        }
                    }else if(scrollTop == 0 && scrollLeft != 0){
                        // 縦スクロールの位置が一番上のときかつ、横スクロールされている時
                        var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
                        var leftTopHeaderVisible = $('.scroll_div').eq(3).hasClass("copyheader_visible");
                        if (!topHeaderVisible) {
                            // 行固定見出しをvisibleとする
                            $('.scroll_div').eq(2).addClass('copyheader_visible');
                        }
                        if (!leftTopHeaderVisible) {
                            // 行列固定見出しをvisibleとする
                            $('.scroll_div').eq(3).addClass('copyheader_visible');
                        }
                    } else {
                        var topHeaderVisible = $('.scroll_div').eq(2).hasClass("copyheader_visible");
                        var leftTopHeaderVisible = $('.scroll_div').eq(3).hasClass("copyheader_visible");
                        // 初期状態に戻す
                        if (topHeaderVisible) {
                            $('.scroll_div').eq(2).removeClass('copyheader_visible');
                        }
                        if(leftTopHeaderVisible){
                            $('.scroll_div').eq(3).removeClass('copyheader_visible');
                        }

                    }
                })
            }
        };

    </script>
</head>
<body>
    <div class="scroll_div">
        <table _fixedhead="rows:2; cols:2">
            ~省略~
        </table>
    </div>
</body>
</html>

これでIEで縦スクロールした際にヘッダのがたつきが無くなります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sequelizeでビット演算を使った検索を行う

以下のようなテーブルがあるとします。
statusの値は10進数ですが、アプリケーションではビット演算で管理されてることがわかると思います。

id:1, status:1 // 1つめのフラグが立っている
id:2, status:4 // 3つめのフラグが立っている
id:3, status:1023 // 全てのフラグが立っている

SQLでの書き方

3つめのフラグが立っているかどうかのクエリーは以下になります。

xxx.sql
SELECT * FROM xxx WHERE (status & 4) = 4;

idが2,3が出力されます。

Sequelize.whereを使った書き方

xxx.ts
Model.findAll({
    where: sequelize.where(sequelize.literal('status & 1023'), 1023)
})

参考

https://github.com/sequelize/sequelize/issues/5207
https://lambdalisue.hatenablog.com/entry/2013/12/22/041347
https://sequelize.org/master/manual/model-querying-basics.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GMMをJavaScriptで実装した

はじめに

色々な機械学習処理をブラウザ上で試せるサイトを作った」中で実装したモデルの解説の九回目です。

今回はGMMの実装について解説します。

デモはこちらから。(TaskをClustering/Anomaly Detection/Density Estimationにして、ModelのGaussian mixture modelを選択)
実際のコードはgmm.jsにあります。

なお、可視化部分については一切触れません。

概説

GMM(Gaussian Mixture Model)はクラスタリング、密度推定、異常検知に使われるモデルです。

複数の正規分布をいい感じに変形させて、データの密度を推定することがGMMの目的です。

理論およびアルゴリズムはこちらのスライドに詳しく記述されています。
実装は、このP.39にまとめられているアルゴリズムをコード起こしただけです。

以下、数式やノーテーションは上記サイトを参考にしています。

クラスタリング

クラスタリングで使用する場合は、各データがどの確率分布に属する可能性が最も高いかによってクラスタリングを行います。

\newcommand{\argmax}{\mathop{\rm arg~max}\limits}

\argmax_{k} \mathcal N \left(\bf x | \mu_{\it k}, \Sigma_{\it k} \right)

密度推定

密度推定は、確率分布の重み付き和によって次の通り計算ができます。

p(\bf x) = \sum_{{\it k} = 1}^{\it K} \pi_{\it k} \mathcal N \left(\bf x | \mu_{\it k}, \Sigma_{\it k} \right)

異常検知

クラス内にコードは存在しませんが、正常である確率は以下の通り計算しています。

\begin{eqnarray}
p'(\bf x) &=& 1 - \prod_{{\it k} = 1}^{\it K} \exp\left(-\pi_{\it k} \mathcal N \left(\bf x | \mu_{\it k}, \Sigma_{\it k} \right) \right) \\
&=& 1 - \exp(-p(\bf x))
\end{eqnarray}

この確率が一定以下の場合に異常と判定することで、異常検知ができます。

分散の計算

分散は、分布の平均に近いデータ点が一つとなると、正則ではなくなるようです。
すると確率が計算できなくなり、以降の処理に問題が発生します。
なので更新時、分散の対角成分に微少量を足すようにしています。

コード

GMM

class GMM {
    // see https://www.slideshare.net/TakayukiYagi1/em-66114496
    // Anomaly detection https://towardsdatascience.com/understanding-anomaly-detection-in-python-using-gaussian-mixture-model-e26e5d06094b
    //                   A Survey of Outlier Detection Methodologies. (2004)
    constructor(d) {
        this._k = 0;
        this._d = d;
        this._p = [];
        this._m = [];
        this._s = [];
    }

    add() {
        this._k++;
        this._p.push(Math.random());
        this._m.push(Matrix.random(this._d, 1));
        const s = Matrix.randn(this._d, this._d);
        this._s.push(s.tDot(s));
    }

    clear() {
        this._k = 0;
        this._p = [];
        this._m = [];
        this._s = [];
    }

    probability(data) {
        return data.map(v => {
            const x = new Matrix(this._d, 1, v);
            const prob = [];
            for (let i = 0; i < this._k; i++) {
                const v = this._gaussian(x, this._m[i], this._s[i]) * this._p[i];
                prob.push(v);
            }
            return prob;
        })
    }

    predict(data) {
        return data.map(v => {
            const x = new Matrix(this._d, 1, v);
            let max_p = 0;
            let max_c = -1;
            for (let i = 0; i < this._k; i++) {
                let v = this._gaussian(x, this._m[i], this._s[i]);
                if (v > max_p) {
                    max_p = v;
                    max_c = i;
                }
            }
            return max_c;
        });
    }

    _gaussian(x, m, s) {
        const xs = x.copySub(m);
        return Math.exp(-0.5 * xs.tDot(s.inv()).dot(xs).value[0]) / (Math.sqrt(2 * Math.PI) ** this._d * Math.sqrt(s.det()));
    }

    fit(datas) {
        const n = datas.length;
        const g = [];
        const N = Array(this._k).fill(0);
        const x = [];
        datas.forEach((data, i) => {
            const ns = [];
            let s = 0;
            const xi = new Matrix(this._d, 1, data);
            for (let j = 0; j < this._k; j++) {
                const v = this._gaussian(xi, this._m[j], this._s[j]) * this._p[j];
                ns.push(v || 0);
                s += v || 0;
            }
            const gi = ns.map(v => v / (s || 1.0));
            g.push(gi);
            x.push(xi);
            gi.forEach((v, j) => N[j] += v);
        });

        for(let i = 0; i < this._k; i++) {
            const new_mi = new Matrix(this._d, 1);
            for (let j = 0; j < n; j++) {
                new_mi.add(x[j].copyMult(g[j][i]));
            }
            new_mi.div(N[i]);
            this._m[i] = new_mi;

            const new_si = new Matrix(this._d, this._d);
            for (let j = 0; j < n; j++) {
                let tt = x[j].copySub(new_mi);
                tt = tt.dot(tt.t);
                tt.mult(g[j][i]);
                new_si.add(tt);
            }
            new_si.div(N[i]);
            new_si.add(Matrix.eye(this._d, this._d, 1.0e-8))
            this._s[i] = new_si;

            this._p[i] = N[i] / n;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptエラー問題集

事前準備

ターミナル上で下記コマンドを実行してcloneする。

git clone https://github.com/Shu-Hos/ajax_app_error.git
cd chat-app_error
bundle install
yarn install
rails db:create
rails db:migrate

問題1

新規投稿をしてもイベントが発火しません。
まずはこちらを解決しましょう!

問題2

新規投稿をしようとすると添付画像のエラーが表示されます。
次はこちらの問題を解決してください。

スクリーンショット 2020-12-06 19.42.49.png

ヒント

エラーの英文のしっかりと確認しましょう。
formText.value = "";に対してCannot set property value of nullと出ています。こちら翻訳するとnullのプロパティ値を設定できないという意味になります。こちらをヒントにプロパティ周りを確認してみると良さそうです。

問題3

問題2のエラーが解決した後に再度新規投稿しても非同期ではUndefinedが出てしまい、一度リロードしないと投稿内容が反映されません。
こちらを修正して非同期で投稿ができるようにしましょう

スクリーンショット 2020-12-06 19.50.04.png

ヒント

こちらエラーが出ずにUndefinedが出るということは、ajaxでレスポンスを受け取った後、新しい要素を作り出して表示するまでのプロセスのどこかに問題が潜んでいる可能性が高いです。

問題4

残りはcheck機能のエラー問題です。
投稿をクリックしても色が変化せずに添付画像のエラーが出てしまいます。
こちら原因が2箇所あります。

スクリーンショット 2020-12-06 21.40.50.png

ヒント

問題5

スクリーンショット 2020-12-06 22.02.43.png

エラーはでなくなりましたが投稿を何回クリックしても色が白に戻りません。
こちらがクリックする度に色がグレーと白が交互に変わるように修正してください。

ヒント

お疲れさまです!!
何かご不明点とかあれば、今後ぜひ教えてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptで括弧の中身の文字列を取得するメモ【2020】

Javascriptで括弧の中身の文字列を取得するメモのアップデート版。

()の中身を取り出したい時。

let str = "aaa(bbb)ccc";
console.log(str.match(/\((.+)\)/)[1]);

// output: bbb

{}の中身を取り出したい時。
([1]をなくせば様々な情報がセットで手に入ります。)

let str = "aaa{bbb}ccc";
console.log(str.match(/\{(.+)\}/)[1]);

// output: bbb

{}で囲まれた複数の要素を取り出したい時。
(/gがあれば複数取得できます。)

let str = "aaa{bbb}ccc{ddd}";
console.log(str.match(/\{.*?\}/g));

// output: (2) ["{bbb}", "{ddd}"]

情報源:[https://teratail.com/questions/156083]

{}で囲まれた複数の要素を取り出すんでなく、大きく取り出したい時。
(?があればできるだけ短いものを探索してくれるので上のような状態になります。)

let str = "aaa{bbb}ccc{ddd}";
console.log(str.match(/\{.*\}/g));

// output: (2) ["{bbb}ccc{ddd}"]

もっと詳しく

Javascript 正規表現入門

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モダン JavaScriptとは、初心者がまとめてみた

はじめに

脱初心者にむけてアウトプットをしていこうと思って、
記事を書いております。
私は、まだ現場に出たことのない完全にど素人です。
間違ったことがありましたら、ぜひコメントいただけると幸いです。

対象読者

JavaScriptの基本的な文法を学んだ後に、VueやReact,
Angularを学ぼうと決めている方、
モダンな(流行しているもの) JavaScriptの開発について知りたい方。

モダン JavaScriptとは?

私が知っている範囲内で説明すると
1. React、Vue、Angular、Riot等の仮想DOMを用いるライブラリ/フレームワーク。
2. パッケージマネージャ(npm,yarnなど)を使っている。
3. 主にES2015以降の記法を使っている
4. モジュールバンドラー(Webpackなど)を使用している
5. トランスパイラ(Babelなど)を使用している。

以上の5つがそれぞれ含まれている場合、モダンなJavaScriptの開発と言えるでしょう。
一つ一つ簡易的に紹介していこうと思います。

仮想DOMとは

React、Vue、Angulerなど、人気のライブラリやフレームワークには共通して、仮想DOMという手法を使っている。
まず、DOMとはHTMLを構築する木構造のこと。
このDOMをJavaScriptのオブジェクトに再現して、リクエストがあった時にブラウザに反映させることができるのが仮想DOMである。

パッケージマネージャとは(npm/yarn)

Node.jsが開発される前までは、一つのファイルに全ての処理を書いていたらしい、、そしてNode.jsが開発されてからは様々なフレームワークが開発され、それによって、プログラムの依存関係が問題になった。
この問題を解決したのがパッケージマネージャーである。
それぞれの依存関係を解決し、さらに、世界中の公開されているパッケージをコマンド一つで使えるようになった。
つまり、パッケージマネージャーとは、依存関係を勝手に解決してくれるもの。

ES2015以降の記法とは

ESとはJavaScriptの標準規格です。(コードを書く時のルール)
その2015以降から様々な便利な機能が追加されました。
三つだけ紹介します、
詳しくは→https://qiita.com/soarflat/items/b251caf9cb59b72beb9b

constとlet

letは再宣言が不可能。
constは再宣言と、再代入が不可能。
今までは変数宣言をする時に、varしかなかったため、間違えて変数を二重に定義してしまったりした。

テンプレート文字列

``バッククォートで文字列を囲む。
${}を使えば、変数などを展開できる。

アロー関数

functionを省略できる、
条件はあるが、return文も省略できる。

モジュールバンドラとは(Webpackなど)

一言でいうなら、複数のファイルを一つにまとめてくれるもの。
そうすることで、読み込み速度が上がる。

トランスパイラとは(Babel)

新しいJavaScriptの記法を古い記法に変換してくれるもの
JavaScriptはブラウザの言語なので、ブラウザによって表示できる記法が異なるため、必要らしい。

終わり

筆者の解釈で書いているために、言葉足らずなところや、説明不足なところもあると思います。
なので、気兼ねなくコメントしていただければと思います。
また、これ以外にも、モダンJavaScriptの要素がありましたら、教えていただけると幸いです。

最後まで、読んでくださり、ありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptを知らない人のためのTypeScript入門

今どきはよく「JavaScriptじゃなくてTypeScriptを使うべき」みたいな話を聞くのですが、ネット上のどのTypeScript入門も、JavaScriptを知っている前提で話が進みます。以前、友達に「JavaScript知らないんだけど、それをすっ飛ばしてTypeScriptを学ぶ方法はないの?」と訊いたところ、「素直にJavaScriptをしてから、TypeScriptしようね。」という回答が帰ってきました。にゃーん。

しかし、これは正しい回答で、TypeScriptの公式サイトLearning JavaScript and TypeScriptに以下のことが書かれています。

We frequently see the question “Should I learn JavaScript or TypeScript?“.
The answer is that you can’t learn TypeScript without learning JavaScript! TypeScript shares syntax and runtime behavior with JavaScript, so anything you learn about JavaScript is helping you learn TypeScript at the same time.

翻訳すると以下の通り。

『JavaScriptとTypeScriptのどちらを学ぶべきか』という質問をよく目にします。
答えは、JavaScriptを学ばなければTypeScriptを学ぶことはできないということです。 TypeScriptはJavaScriptと構文と実行時の動作を共有するため、JavaScriptについて学んだことはすべて、TypeScriptを同時に学ぶのに役立ちます。

なるほど、JavaScriptなくして、TypeScriptは語れないということですね。ただ、JavaScriptを学んで、TypeScriptを学ぶというのは、道のりが長い気がします。というわけで、JavaScriptを学びながら、TypeScriptを学べる「JavaScriptを知らないためのTypeScript入門」を書いてみました。

体系的に学びたい方へ

JavaScriptもTypeScriptもだいたい分かるにょん、という方や、この記事を読み終えた方、もしくは


このような方は、TypeScriptの公式サイトThe TypeScript Handbook(英語)を読むことをおすすめします。

事前に知っておくべきこと

この記事はMDN web docsのJavaScriptガイドに沿って話を進めていきます。このガイドは事前に知っておくべきことに書かれている予備知識を持っていることを前提としています。

用語説明

まず用語の説明をします。記事に出てくるキーワードや、よく見かけるキーワードについて簡単な説明を書きました。記事で分からないキーワードが出てきたら、ここに戻って見ましょう。

HTML・CSS・JavaScriptとは何か

JavaScriptを知らない方はそもそもHTMLとCSSとJavaScriptとは何なのかを知らない方かもしれません。
この記事では詳しくは説明しませんが、@shuntaro_tamuraさんの超絶初心者のためのフロント入門(HTML、CSS、JavaScript)を読めば簡単に理解できると思います。

ナンセンスな例えですが、家に例えると、
骨組みや柱がなどの「構造」がHTML
壁紙や窓のデザインなどの「装飾」がCSS
電気やガスのスイッチなどの「動的な処理」がJavaScript
ですね。

HTMLについてもっと詳しく知りたい方はMDN web docsのHTML: HyperText Markup Languageを読むことをおすすめします1。CSSについてもっと詳しく知りたい方はMDN web docsのCSS: カスケーディングスタイルシートを読むことをおすすめします。JavaScriptについてもっと詳しく知りたい方はMDN web docsのJavaScript とはを読むことをおすすめします。

TypeScriptとは何か

一言で言うと、JavaScriptを拡張して作られたプログラミング言語です2
JavaScriptとTypeScriptの大きな違いは「型定義」ができるかできないかです。JavaScriptには変数に型がありませんが、TypeScriptには変数に型があります。変数に型を付けることができるので、ちょっとしたミスが起きにくくなります。
ちなみに、JavaScriptを拡張して作られたプログラミング言語は他にも色々あって、それらを総称してAltJS(Alternative JavaScript)と呼びます。

さきほどの例えの続きですが、今まで電気やガスのスイッチなどの「動的な処理」をJavaScriptでやっていたけれど、最近では代わりにそこをTypeScriptで処理することがあるという感じです。

TypeScriptの長所にもっと詳しく知りたい方は@SoraKumoさんのTypeScriptをお勧めしたい7つの理由を読むことをおすすめします。

その他の説明

DOM(Document Object Model)は一言でいうと、HTMLやXMLで書かれた文書などを木構造のオブジェクトで表現することで、文書をプログラムから操作・利用することを可能にする仕組みです。詳しくは説明しませんが、@takiyu713さんのDOMと仮想DOMについてを読めば簡単に理解できると思います。もっと詳しく知りたい方はMDN web docsのDOM の紹介を読むことをおすすめします。

Node.jsは一言でいうと、ブラウザ上という制限された環境でしか動けなかったJavaScriptを、PythonやRubyのようにパソコン上で動かせるようにしてくれるものです。詳しくは説明しませんが、@non_calさんのNode.jsとはなにか?なぜみんな使っているのか?を読めば簡単に理解できると思います。

npmは一言でいうと、Node.jsのモジュールを管理するツールです。詳しくは説明しませんが、@righteousさんの【初心者向け】NPMとpackage.jsonを概念的に理解するを読めば簡単に理解できると思います。

0章: 環境構築

JavaScriptはそのままブラウザ上で動きますが、TypeScriptはJavaScriptに変換してからでないと動きません。
そこでTypeScriptコンパイラを使って、TypeScriptをJavaScriptに変換します。

今回はTypeScriptを中心に学ぶので、環境開発は最低限で行います。

  1. Node.jsをインストール
    1. Node.jsの公式サイトからLTSをダウンロードし、インストールします。
    2. npmはNode.jsと一緒にインストールされます。
  2. TypeScriptコンパイラをインストール
    1. コマンドプロンプトやターミナルなどを実行し、下記のコマンドを実行してください。
    2. npm install -g typescript(もしくはsudo npm install -g typescript)でTypeScriptコンパイラをインストールします。
  3. インストールの確認
    1. tsc -vを実行し、バージョンが表示されればインストールは完了です。

以下は、実行例です3

$ node -v
v12.19.0
$ npm -v
6.14.8
$ npm install -g typescript
...
+ typescript@4.1.2
added 1 package from 1 contributor in 23.883s
$ tsc -v
Version 4.1.2

TypeScriptの環境構築についてもっと詳しく知りたい方は@ochiochiさんのTypeScriptチュートリアル① -環境構築編-を読むことをおすすめします。

1章: Hello, world!

実際にTypeScrptのプログラムを書いて、コンパイルして、実行してみましょう。
さて、Hello, world!を表示させるという一億番煎じ4のプログラムですが、次のソースコードをお好きなエディタ5で書いて、hello.tsという名前で保存してください。

01/hello.ts
console.log('Hello, world!');

そしてコマンドプロンプトもしくはターミナルで、下記のコマンドを実行してください3

$ tsc ./hello.ts

このtscコマンドがTypeScriptコンパイラです。うまくコンパイルできると、結果としてhello.jsというファイルができます。ここでは簡単にNode.jsで実行してみましょう。次のコマンドを入力してください3

$ node hello.js

実行すると、コマンドプロンプトもしくはターミナルでHello, world!が表示されます。

$ tsc ./hello.ts
$ node hello.js
Hello, world!

2章: 文法とデータ型

この章からは、はじめにMDN web docsのJavaScriptガイドを読み、次にTypeScriptではJavaScriptとどう違うかを説明していく形式で進めていきます。「JavaScriptではこのように書きます。では、TypeScriptではどうしょう。」といった感じですね。

さて、この章では最初に文法とデータ型を読んで、JavaScriptの知識を付けましょう。

読み終えましたか?この章ではTypeScriptは以下の点でJavaScriptと違いが見られます。

  1. TypeScriptにはグローバル変数はありません。
  2. TypeScriptはJavaScriptと違って、変数、定数、関数、引数などの後ろに: 型名を指定することで型を宣言することができます。これを型アノテーション(Type Annotation)と言います。型に一致しない代入や参照が行われるとコンパイルエラーが発生します。

型アノテーション

TypeScriptでは型アノテーションでプリミティブ型を指定することができます。

02/primitive.ts
let v_bool: boolean = true;
let v_null: null = null;
let v_undef: undefined = undefined;
let v_num: number = 123;
let v_bigint: bigint = 11451481093189319194545072136436489464n;
let v_str: string = "Hello, world!";

ただし、ターゲットがES2020未満の場合、bigintリテラルは使用できないので気をつけてください。

なお、配列やオブジェクトの型は以下のように指定できます。

02/array-and-object.ts
let coffees: string[] = ['French Roast', 'Colombian', 'Kona'];
var car: object = { myCar: 'Saturn', getCar: 'Honda', special: 'Toyota' };

実際に型に一致しない代入や参照が行われるとコンパイルエラーが発生するか確かめてみましょう。

02/type-error.ts
var v_num: number = 123;
var v_str: string = 123; // ここでコンパイルエラー!

型推論

JavaScriptでは以下のように書くことができます。詳しくは説明しませんが、MDN web docsのデータ型の変換を読めば簡単に理解できると思います。

02/answer.js
var answer = 42;
answer = 'Thanks for all the fish...';

ただ、これをTypeScriptとして書くと次のようなコンパイルエラーが出ます。

$ tsc test.ts
test.ts:2:1 - error TS2322: Type 'string' is not assignable to type 'number'.

2 answer = 'Thanks for all the fish...';
  ^^^^^^

Found 1 error.

TypeScriptは、いくつかの簡単なルールに基づいて変数の型を推論(およびチェック)します。これを型推論といいます。変数の型は、定義によって推論されます。つまり、var answer = 42;var answer: number = 42;と同じで、answerの型はnumberなのです。型推論についてもっと詳しく知りたい方は@uhyoさんのTypeScriptの型推論詳説を読むことをおすすめします。

このエラーを回避する方法として、以下の2つの方法があります。

1つ目に、|を使用することで、複数の型を指定する方法があります。
string または number の型を示す場合は number | string とします。
詳しくは12章の共同型で説明します。

02/answer-pipe.ts
var answer: number | string = 42;
answer = 'Thanks for all the fish...';

もう一つの方法は、anyを使用することで、プリミティブを含む任意の型を許容することです。

02/answer-any.ts
var answer: any = 42;
answer = 'Thanks for all the fish...';

数値と '+' 演算子

数値と文字列を+演算子で結合する式では、TypeScriptは数値を文字列に変換します6。以下の式を見てみましょう。

02/string-plus-number.ts
var x = '答えは ' + 42; // "答えは 42"
var y = 42 + ' が答え'; // "42 が答え"

それ以外の演算子がある式では、型が違うため、TypeScriptはコンパイルエラーになります。

02/string-minus-number.ts
'37' - 7 // ここでコンパイルエラー!(JavaScriptだと30になります。)
'37' + 7 // "377"

3章: 制御フローとエラー処理

if文などの制御フローやtry...catch構文はJavaScriptと同じです。MDN web docsの制御フローとエラー処理を読めば簡単に理解できると思います。

4章: ループと反復処理

ループ文はJavaScriptと同じです。MDN web docsのループと反復処理を読めば簡単に理解できると思います。

5章: 関数

この章では最初に関数を読んで、JavaScriptの知識を付けましょう。

読み終えましたか?この章ではTypeScriptは以下の点でJavaScriptと違いが見られます。

  1. 関数の返り値の型を型アノテーションで指定できます。

関数の返り値の型指定

例えば、次のコードはsquareという名前の簡単な関数を定義します:

05/square.ts
function square(x: number): number {
  return x * x;
}

例えば、関数式では次のように定義できます:

05/anonymous.ts
const square = function(x: number): number { return x * x; };
var x = square(4); // x の値は 16 となる

もちろん関数式に名前をつけることもできます:

05/factorial.ts
const factorial = function fac(n: number): number { return n < 2 ? 1 : n * fac(n - 1); };
console.log(factorial(3));

(引数名:型, 引数名:型 ...) => 戻り値と書くことで関数を型指定することができます。第1引数のfはnumberの型の引数を一つ取り、numberの型を返す関数なので、: (x: number) => numberで型指定することができます。次の例ではmap関数を定義し、第1引数に関数を取り、第2引数に配列を取ります:

function map(f: (x: number) => number, a: number[]) {
  let result = []; // 新しい配列を作成
  let i: number; // 変数の宣言
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}

下記のコードでは、この関数は関数式で定義された関数を受け取って、2つ目の引数で受け取った配列の各要素に対して実行しています。

function map(f: (x: number) => number, a: number[]) {
  let result = []; // 新しい配列を作成
  let i: number; // 変数の宣言
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}
var f = function (x: number): number {
  return x * x * x;
};
let numbers = [0, 1, 2, 5, 10];
let cube = map(f, numbers);
console.log(cube);

これは[0, 1, 8, 125, 1000]を返します。

6章: 式と演算子

式と演算子はJavaScriptと同じです。MDN web docsの式と演算子を読めば簡単に理解できると思います。

7章: テキスト処理

テキスト処理はJavaScriptと同じです。MDN web docsのテキスト処理を読めば簡単に理解できると思います。

8章: 正規表現

正規表現はJavaScriptと同じです。MDN web docsの正規表現を読めば簡単に理解できると思います。

正規表現が意図したとおりに動くか試したいときは、regex101.comというとても便利なサイトがあるので、ぜひ試してみてください。

9章: インデックス付きコレクション

この章では最初にインデックス付きコレクションを読んで、JavaScriptの知識を付けましょう。

配列の型アノテーション

配列の型アノテーションは以下のように、変数を宣言するときに配列の要素の型の後に[]を付けます。

09/emp.ts
let emp: string[] = []
emp[0] = 'Casey Jones'
emp[1] = 'Phil Lesh'
emp[2] = 'August West'

また、Array<型>と指定して書くことも可能です。

09/emp2.ts
let emp: Array<string> = []
emp[0] = 'Casey Jones'
emp[1] = 'Phil Lesh'
emp[2] = 'August West'

2次元配列の型アノテーション

2次元の配列の場合は[][]と、[]を二つ書けばいいです。

09/99.ts
var table: number[][] = new Array();
for (let i = 0; i < 9; i++) {
    table[i] = new Array();
    for (let j = 0; j < 9; j++) {
        table[i][j] = (i + 1) * (j + 1);
    }
}

10章: キー付きコレクション

この章では最初にキー付きコレクションを読んで、JavaScriptの知識を付けましょう。

Mapの型アノテーション

Mapオブジェクトの型アノテーションは以下のように、Map<キーの型, 値の型>と書きます。

10/sayings.ts
let sayings: Map<string, string> = new Map();
sayings.set('dog', 'woof');
sayings.set('cat', 'meow');
sayings.set('elephant', 'toot');

このファイルをコンパイルする際はtsc ./sayings.ts --lib es6としてください。

Hashの型アノテーション

Hashオブジェクトの型アノテーションは以下のように、Map<要素の型>と書きます。

10/sayings.ts
let mySet: Set<number | string> = new Set();
mySet.add(1);
mySet.add('some text');
mySet.add('foo');

11章: オブジェクトでの作業

この章では最初にオブジェクトでの作業を読んで、JavaScriptの知識を付けましょう。

オブジェクトの型アノテーションは以下のように、objectで指定できます。

11/myCar.ts
var myCar: object = {
  make: 'Ford',
  model: 'Mustang',
  year: 1969
};

TypeScriptにはObjectというものもあり、var myCar: Object = ...と書いてもコンパイルが通ります。6 TypeScriptのObject型とobject型は同じ……と思うじゃん?ところがどっこい、そうではありません。詳しくは説明しませんが、@suinさんの書いたこの記事を読めば簡単に理解できると思います。

12章: 型の定義

この章では、引き続きMDN web docsのオブジェクトでの作業に沿って話を進めつつ、TypeScriptの公式サイトTypeScript for JavaScript Programmersに書かれているような説明を加えていきます。

interface

TypeScriptでは、以下のようにinterfaceキーワードを使って、インターフェイスの宣言を使用して、オブジェクトの形状を明示的に記述することができます。

12/Car.ts
interface Car {
  make: string;
  model: string;
  year: number;
}

次に、変数宣言の後に以下のように構文を使用して、JavaScriptオブジェクトがCarインターフェイスの形状に準拠していることを宣言できます。

12/myCar2.ts
interface Car {
  make: string;
  model: string;
  year: number;
}

var myCar: Car = {
  make: 'Ford',
  model: 'Mustang',
  year: 1969
};

共同型

共同型を使えば、複数の型をひとまとめにすることができます。たとえば、ブール型をtrueまたはfalseのいずれかとして記述できます。

12/myBool.ts
type MyBool = true | false;

ところで、このMyBoolの型はbooleanです。これについてはここで説明しません。

とはいえ、MyBoolなんて型を実際に使うことはないでしょう。共同型のもっと一般的な使用例は、値が許可されている文字列または数値リテラルのセットを記述することです。

12/type.ts
type OthelloColor = 'black' | 'white';
type OneDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Make = 'Ford' | 'Eagle' | 'Nissan' | 'Mazda';

13章: オブジェクトモデルの詳細

PromiseはJavaScriptと同じです。MDN web docsのオブジェクトモデルの詳細を読めば簡単に理解できると思います。

14章: Promiseを使う

PromiseはJavaScriptと同じです。MDN web docsのPromiseを使うを読めば簡単に理解できると思います。

15章: イテレーターとジェネレーター

イテレーターとジェネレーターはJavaScriptと同じです。MDN web docsのイテレーターとジェネレーターを読めば簡単に理解できると思います。

16章: メタプログラミング

メタプログラミングはJavaScriptと同じです。MDN web docsのメタプログラミングを読めば簡単に理解できると思います。

17章: インターフェイス

これはJavaScriptにはない機能です。他のプログラミング言語と同じで、中身の実装を持たずに、メンバーや型の定義だけ持つ機能のことです。

この章に関しては、The TypeScript HandbookInterfaces(英語)を読めば簡単に理解できると思うので、説明しません。英語が読めない方は@murankさんのTypeScript Handbook を読む (3. Interfaces)を読むといいかもしれません。

また、@nogsonさんのTypescriptのinterfaceの使い方や、@t_t238さんの【TypeScript】インターフェースの使い方を読んでも簡単に理解できると思います。

18章: ジェネリクス

これもJavaScriptにはない機能です。ジェネリクスは実は前にやった<>のことです。ジェネリクスは、実際に使われるまで型が決まらないときに、抽象的な型(ジェネリック型)を使うことで、いろいろな型の値を与える関数などを作るときに使います。ジェネリクスは総称型と呼ばれることもあります。

この章に関しては、The TypeScript HandbookGenerics(英語)を読めば簡単に理解できると思うので、説明しません。英語が読めない方は@murankさんのTypeScript Handbook を読む (6. Generics)を読むといいかもしれません。

19章: 列挙型

これもJavaScriptにはない機能です。この記事では詳しくは説明しませんが、The TypeScript HandbookEnums(英語)を読めば簡単に理解できると思います。英語が読めない方は@murankさんのTypeScript Handbook を読む (7. Enums)を読むといいかもしれません。

実はTypeScriptでは列挙型はあまり使われません。代わりに共同型を使います。このことについてもっと詳しく知りたい方はTypeScriptのenumを使わないほうがいい理由を、Tree-shakingの観点で紹介しますや、[TypeScript] 列挙型は Union か enum か、または、さようなら、TypeScript enumを読むことをおすすめします。

20章: TypeScriptを実践する

TypeScript自体の説明は前の章で終わりです。この章では、TypeScriptを実際に使う際に参照するべき記事を紹介します。TypeScriptを学んでいる皆さんはおそらく何か目的を持っているはずですから、ここではその目的への橋渡しをしたいと思っています。

ここに書かれていない参考になる記事があったらコメント欄に書いてください。コメント歓迎します。

React + TypeScript

おそらく、ReactでTypeScriptする方が多いでしょうか。私もそのうちの一人です。

vue.js + TypeScript

布教用

JavaScriptを使っている人に向けてTypeScriptを勧めるときの記事です。

あとがき

お疲れ様でした!
ひとまず、これでこの記事は終わりです。この記事はごちゃごちゃとJavaScriptとTypeScriptを行ったり来たりしているので、整理したい方や、やった内容を忘れてしまった方もいらっしゃると思います。そんなときは上に方にある「体系的に学びたい方へ」で紹介した公式ドキュメントをお読みいただけるとスッキリすると思います。また、飛ばした章や読んでいないリンク先の記事(特に詳しく知りたい方用の記事1)は後でまた読んで見ると新たな知見を手に入れることができると思います。

この記事書いている人はJavaScriptとTypeScriptの言語の根っこから理解しているわけではないので、大きな誤解をしているかもしれません。(私自身がJavaScriptをすっ飛ばして、TypeScriptを勉強した身ですので…)くだらない間違いはコメントください。(みんなコメント書こうね。)あんまりにも間違いが多くて呆れた方はどうか代わりに正しい記事を書いてください。そしたら、この記事を消します。


  1. この記事では「…にもっと詳しく知りたい方は…を読むことをおすすめします」という構文で紹介している記事はとても詳しく書いてあることがあるため、初心者向けではない可能性があることに留意してください。 

  2. 関西型言語が得意な方が次のようなことを言っていましたが、これは冗談です。

    JavaとJavaScriptは名前が似ていますが、全くの別物です。よく言われる例えとして、インドとインドネシアくらい(話される言語や宗教が)違うことが挙げられます。
  3. コマンドの$(ドル記号)は打たないでください。$(ドル記号)が頭に付く行は、それに続くコマンドを実行することを意味しています。このドル記号のことをコマンドプロンプト(コマンド入力が可能な状態)と呼びます。これは、使用するシェルやターミナルソフトウェア設定により変化しますが この記事では、$で統一します。 

  4. 「一億番煎じ」はこの前Qiitaで見かけて思わず笑ってしまった単語です。どうやらオタク特有の過剰表現らしいですが、Hello, world!を表示させるプログラムに関しては、わりと冗談抜きで一億番煎じですね。 

  5. 2077年、SDGsを達成してから44年経ち、ようやく世界に平和が訪れると思われていた。しかし、世界中が高度に発達し、プログラミングが盛んに行われるようになってから、人々の中で「どのテキストエディタ(またはIDE)が一番良いか」というテーマの論争が過激化していった。最初はお互いを批判するだけの、小さな小競り合いのようなものだったが、次第に、腱鞘炎で病院へ行くと「君はEmacsじゃなくてVimを使っているのに不思議だねぇ」と医者に言われるようになったり、ニュースで国内の指定暴力団Vimmerが暴れまわるようになったり、終いにはvi原理主義者がテロを起こして、アメリカが核を飛ばす始末。国外では戦争が、国内でも内戦が発生した最悪な状態で、Emacs派の主人公は友達のcenti(nanoの後継の後継の後継)派と共に他の仲間を探して、荒れ果てた日本を縦断する――。Qiita記事なのでこれ以上書くと怒られるので書きませんが、誰かこういう感じのフィクション作品を書いてくれませんか?「JetBrainを使っているやつは頭もJetBrainだな」「お前は今、二つ間違えた。一つ目はそれが褒め言葉であること、二つ目はJetBrainじゃなくてJetBrainsだ、(銃声)」というやり取りとか、VSCoderに対して悪魔のスキンHot Dog Standで目を潰すとかそういうシーンが見たいです。もちろんお好きなエディタで書いていいですよ。 

  6. これはJavaScriptでも同じです。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

firebase authentication(JavaScript SDK)サインイン時のエラーハンドリング

Firebase Authentication(JavaScript SDK)でメールアドレス、パスワードを利用してサインインする場合に、throwされるエラーをまとめました。

ドキュメントに記述されてますが、以下のエラーがthrowされるので、エラーに応じて適切に処理することになります。

  • メールアドレスの形式がおかしい
    • errorCode: auth/invalid-email
    • message: The email address is badly formatted.
  • ユーザが無効になっている
    • errorCode: auth/user-disabled
    • message: The user account has been disabled by an administrator.
  • ユーザが存在しない
    • errorCode: auth/user-not-found
    • message: There is no user record corresponding to this identifier. The user may have been deleted.
  • パスワードが間違っている
    • errorCode: auth/wrong-password
    • message: The password is invalid or the user does not have a password.

また、パスワードを5回間違えると、以下のエラーが返ってきます。

try {
  const data = await firebaseAuth.signInWithEmailAndPassword(
    "email-adress@example.com",
    "password"
  )
} catch (error) {
  if (error.code === 'auth/invalid-email') {
    // メールアドレスの形式がおかしい
  } else if(error.code === 'auth/user-disabled') {
    // ユーザが無効になっている
  } else if(error.code === 'auth/user-not-found') {
    // ユーザが存在しない
  } else if(error.code === 'auth/wrong-password') {
    // パスワードが間違っている
  } else if (error.code === 'auth/too-many-requests') {
    // 何度もパスワードを間違えた
  } else {
    // その他
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】Redux Toolkit のチュートリアルのサンプルコードを読んでみる

はじめに

React を習得するまでの軌跡をメモっていく備忘録的な記事です。

Redux Toolkit を使ってみる

2019年にリリースされた React の状態管理のライブラリで従来の Redux をより使いやすくしたものらしい。

Redux Toolkit の公式サイト

公式サイトの Intermediate Tutorial を参考にしました。
サンプルコードのGitHubのリポジトリはこちら

サンプルコードの中身は todo アプリで
仕様としては

  • 一個一個の todo は text(すべきこと) と completed(完了したかどうか) を持つ
  • フッターのボタンで all(全てのTODOを表示), active(完了してないTODOを表示) , completed(完了したTODOを表示) を切り替えることができる

UIは以下のような感じです。

スクリーンショット 2020-12-06 19.55.17.png

実際にソースコードを読む

ファイル構成はこんな感じです。

スクリーンショット 2020-12-06 23.59.53.png

Redux Toolkitの要になりそうな部分だけ取り上げています。

ソースコード中にコメントを入れる形で書いていきます。

index.js
import React from 'react'
import { render } from 'react-dom'
import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'


const store = configureStore({
  // store を定義する
  // rootReducer は createSliceで作った Reducer 達を combineReducerにより合体させたもの
  // (↑ reducers/index.js で定義している)
  reducer: rootReducer
})

render(
  // 子コンポーネントが store に接続できるように
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

TODOを追加したり、完了状態を変更する部分の実装

features/todos/todoSlice.js
import { createSlice } from '@reduxjs/toolkit' // createSlice を使えるようにする

// slice を作成する Redux Toolkitのメイン部分
// State, Reducer, Action を一気に生成する
const todosSlice = createSlice({
  name: 'todos', // slice の名前を設定
  initialState: [], // state の初期値を設定
  reducers: { // reducer を設定 複数の reducer を設定できる
     // prepare のコールバックを用いた書き方
    addTodo: {
      reducer(state, action) {
        const { id, text } = action.payload // action.payload には reducer に渡したい正味の値が入る
        state.push({ id, text, completed: false }) // state に新規の値を追加している
      },
      //prepare のコールバックを使うとstateを更新する前に色々と事前に編集できて便利
      prepare(text) { 
        return { payload: { text, id: nextTodoId++ } }
      }
    },
    // こっちが通常の書き方
    toggleTodo(state, action) {
      // state の中から id に一致した todo を見つける
      const todo = state.find(todo => todo.id === action.payload)
      if (todo) {
        todo.completed = !todo.completed // 見つけた todo の complete を更新する
      }
    }
  }
})

// addTodo と toggleTodo の Action を生成できる関数を export
export const { addTodo, toggleTodo } = todosSlice.actions

// Reducer を export
export default todosSlice.reducer

connect, mapDispatch, mapStateToProps についてはこちらの記事が大変参考になりました。
mapStateToPropsとmapDispatchToPropsの理解の仕方

features/todos/AddTodo.js
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { addTodo } from './todosSlice'

// AddTodo component に props として addTodo の Action を渡すため
const mapDispatch = { addTodo }

const AddTodo = ({ addTodo }) => {
  const [todoText, setTodoText] = useState('')

  const onChange = e => setTodoText(e.target.value)

  return (
    <div>
      <form
      {
        // 送信ボタンが押された時、form の値がなければそのまま return
        // 値があれば addTodoの Action が Reducer にフォームの値を送り、 state が更新される、 
        // その後、フォームは空になる
      }
        onSubmit={e => {
          e.preventDefault()
          if (!todoText.trim()) {
            return
          }
          addTodo(todoText)
          setTodoText('')
        }}
      >
        <input value={todoText} onChange={onChange} />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}

// connectを使って Action を AddTodo に Propsとして送る
export default connect(
  null,
  mapDispatch
)(AddTodo)

表示するTODOの切り替え部分の実装

features/filters/filterSlice.js
import { createSlice } from '@reduxjs/toolkit' // createSlice を使えるようにする

// フィルタリングの状態(全て、完了、未完了)を定義した定数
export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}


const filtersSlice = createSlice({
  name: 'visibilityFilters', // slice の名前を設定
  initialState: VisibilityFilters.SHOW_ALL, // slice の初期値を設定
  reducers: { // コールバックは使わず通常の書き方
    setVisibilityFilter(state, action) {
      return action.payload // 受け取った action の値でそのまま state を更新
    }
  }
})

// Action を export
export const { setVisibilityFilter } = filtersSlice.actions

// Reducer を export
export default filtersSlice.reducer
features/filters/Link.js
import React from 'react'
import PropTypes from 'prop-types'

// filter には VisibilityFilters のいずれかが入る 
const Link = ({ active, children, setVisibilityFilter, filter }) => (
  <button
    // setVisibilityFilter Action で Reducer に filter の値を送信
    onClick={() => setVisibilityFilter(filter)}
    disabled={active} // 現状クリックされているなら disabled に
    style={{
      marginLeft: '4px'
    }}
  >
    {children}
  </button>
)

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  setVisibilityFilter: PropTypes.func.isRequired,
  filter: PropTypes.string.isRequired
}

export default Link

Storeから State を受け取り表示する部分の実装

src/features/todos/VisibleTodoList.js
import { connect } from 'react-redux'
import { createSelector } from '@reduxjs/toolkit'
import { toggleTodo } from 'features/todos/todosSlice'
import TodoList from './TodoList'
import { VisibilityFilters } from 'features/filters/filtersSlice'

const selectTodos = state => state.todos // Store から todos の state を select
const selectFilter = state => state.visibilityFilter //  Store から visibilityFilter の state を select

const selectVisibleTodos = createSelector( // selector を作成
  [selectTodos, selectFilter],
  (todos, filter) => {
    switch (filter) {
      case VisibilityFilters.SHOW_ALL:
        return todos
      case VisibilityFilters.SHOW_COMPLETED:
        return todos.filter(t => t.completed)
      case VisibilityFilters.SHOW_ACTIVE:
        return todos.filter(t => !t.completed)
      default:
        throw new Error('Unknown filter: ' + filter)
    }
  }
)

const mapStateToProps = state => ({
  todos: selectVisibleTodos(state)
})

const mapDispatchToProps = { toggleTodo }

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

まとめ

  • createSlice を使うと State, Action, Reducer を一気に作れるので便利。
  • Componentprops として StateAction を渡したい場合は、 connect, mapStateToProps, mapDispatchToPropsを使うと便利。
  • ReducercombineReducer で結合させて rootReducer を作る
  • configureStorestore を定義する。
  • 作った storeProvider 経由で 子コンポーネントに渡す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザーに優しいローディング画面「スケルトンスクリーン」

はじめまして。アドベントカレンダー初参戦のフロントエンド1年生です。(1月で2年生?)
以前ふと耳にした言葉「スケルトンスクリーン」がずっと気になっていたので、これを機に皆さまにも共有できたらと思います。

スケルトンスクリーンとは

スケルトンスクリーンはご存知ですか?
名前を聞いたことがない方も、きっとどこかで見たことがあると思います。
↓この人です。
skeleton.png

ページやコンテンツの読み込み中、真っ白なページが長く続くとユーザーは不安になります。
その不安を解消するため、プログレスバーやぐるぐる回るスピナーを表示して「今、読み込んでますよ」とユーザーに伝える手法が定番でした。
そしてここ数年で新たに見かけるようになったのがスケルトンスクリーンです。

スケルトンスクリーンは、画像やCSS、JavaScriptを読み込んでいる間にワイヤーフレームのようなボックスを表示し、UXを向上させるために使われる。ユーザにとってはプログレスバーやスピナーと違いどんなページが表示されるか予想できるため、ロード時間が長くても心理的に短く感じられる。
(引用:https://kuroeveryday.blogspot.com/2017/05/load-with-skeleton-screens-and-shimmer-effect.html)

確かに、どこにどんな形で表示されるのかわかるので、
ただ真ん中でスピナーが1つグルグル表示されているよりは、ストレスも軽減されますね!!
YouTubeやSlackやQiitaなど、さまざまなサイトで見かけるようになりましたが、
なんだか実装難しそう・・・
ということで、調べながらですが挑戦してみました。

実装

手順は3つ!
1. コンテンツを用意
2. loadingクラスでスケルトンスクリーン作成
3. 画像の読み込みが終わったタイミングで、loadingクラスを非表示にする

今回の題材は、お正月も近いので干支にしました。
contents

コンテンツが用意できたらHTMLとCSSでこの子のスケルトンスクリーンを作成します。

index.html
<ul class="list">
  <li class="item">
    <div class="item__img">
      <!-- 画像部分のスケルトンスクリーン -->
      <div class="loading" data-js="loading">
        <span class="loading__img"></span>
      </div>
      <img class="img"
      src="/images/nezumi.png"
      />
    </div>
    <div class="item__text">
      <!-- テキスト部分のスケルトンスクリーン -->
      <div class="loading" data-js="loading">
        <span class="loading__line"></span>
        <span class="loading__line"></span>
        <span class="loading__line"></span>
      </div>
      <div class="text">
        <dl class="text__title">ねずみ(子)</dl>
        <dd class="text__description">すぐに子ねずみが増え成長することから、子孫繁栄の意味があります。</dd>
      </div>
    </div>
  </li>
</ul>
style.css
.loading {
  position: absolute;
  display: block;
  width: 100%;
  height: 100%;
  background: #fff;
  overflow: hidden;
  z-index: 50;
}

/* キラキラエフェクトのアニメーション */
@keyframes skeleton-animation {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

.loading::before {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  content: "";
  display: block;
  height: 100%;
  width: 100%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.2),
    transparent
  );
  animation: skeleton-animation 1.2s linear infinite;
}

/* 画像部分のスケルトンスクリーン */
.loading__img {
  display: block;
  width: 100%;
  height: 100%;
  background-color: #e2e2e2;
  border-radius: 50%;
}

/* テキスト部分のスケルトンスクリーン */
.loading__line {
  display: block;
  margin-top: 10px;
  height: 20px;
  background-color: #e2e2e2;
}

/* スケルトンスクリーンを非表示にするクラス */
.loading--hidden {
  display: none;
}

skeleton.png
こんな感じに、コンテンツの画像・テキストをシルバーのスクリーンで覆うイメージです。

そして最後にJSです。
画像の読み込みが終わったらdata-js='loading'の要素のクラスにloading--hiddenを追加して非表示にしてあげるだけです。

main.js
// ローディングの要素取得
const loadingItem = document.querySelectorAll("[data-js=loading]");

// loading非表示クラスを追加
const hideLoading = (list, className) => {
  list.forEach((element) => {
    element.classList.add(className);
  });
};

window.onload = () => {
  hideLoading(loadingItem, "loading--hidden");
};

skeleton.gif
このように、ローディング中の数秒間だけスケルトンスクリーンが表示されるようになりました!

完成

ねずみだけだと寂しいので、仲間を増やして完成です。
とてもとてもいい感じな気がします。
※コードはこちら:GitHub

skeleton2.gif

おわりに

スケルトンスクリーンの実装は思ったよりも難しくはなく、案外シンプルに実装できました。
しかし、

  • 読み込みが早い場合はコンテンツ表示時に一瞬ちらつくな…
  • コンテンツの文字数が増えたらはみ出しそうだな…
  • スクロールに合わせて画像を読み込み開始にしたらさらに良いかも…

など、いろいろ改善の余地はありそうです。
また、従来のスピナーやプログレスバーとの効果的な使い分けについても、もう少し探っていけたらと思います。

ストレスフリーなローディング画面を目指して!!!!

参考

)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでも綺麗なコードでWebAPIを作る(routing-controllers)

はじめに

Node.jsでWebAPIを作ると、その自由度の高さからコードが綺麗に書けないことが多いと思います。
そんなときにはrouting-controllersを使うのがおすすめです。
今回はrouting-controllersを使ったモダンなWebAPIの書き方を紹介します。

routing-controllersとは

https://github.com/typestack/routing-controllers

Allows to create controller classes with methods as actions that handle requests. You can use routing-controllers with express.js or koa.js.

いわゆるMVCのコントローラーをTypescriptのクラスベースで書くことができるライブラリで、express.jsやkoa.jsなどのフレームワークに適応しています。
クラスベースであることにより、構造的かつ綺麗なコードを書くことができます。
例として、以下のようにクラスのメソッドをコントローラーのハンドラーとして書くことができます。

sampleController.ts
import { Controller, Param, Body, Get, Post, Put, Delete } from "routing-controllers";

@Controller()
export class UserController {

    @Get("/users")
    getAll() {
       return "This action returns all users";
    }

    @Get("/users/:id")
    getOne(@Param("id") id: number) {
       return "This action returns user #" + id;
    }

    @Post("/users")
    post(@Body() user: any) {
       return "Saving user...";
    }

    @Put("/users/:id")
    put(@Param("id") id: number, @Body() user: any) {
       return "Updating a user...";
    }

    @Delete("/users/:id")
    remove(@Param("id") id: number) {
       return "Removing user...";
    }
}

Typescriptに馴染みのない人には見慣れない構文があるかと思います。
@Get("/users")などはTypescriptのデコレーターという機能になります。
https://www.typescriptlang.org/docs/handbook/decorators.html
デコレータとはクラスの宣言などに(ここではメソッドに対して)アタッチできる特別な宣言です。

さっそく作ってみる

こちらに詳細なコードが載っています。
https://github.com/tonio0720/modernApiInTypescript

パッケージインストール

npm init # 初期化
npm i -S express reflect-metadata routing-controllers class-transformer class-validator
npm i -D @types/express ts-node

TSCONFIGの設定

tsc --init
# tsconfig.jsonができればOK

以下の3つの設定を変更

tsconfig.json
{
    "compilerOptions": {
        ~
        "strictPropertyInitialization": false,
        /* Experimental Options */
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
        ~
    }
}

コントローラーを書いてみる

ポケモンのデータを返す処理を書いてみました。

controllers/PokemonController.ts
import {
    JsonController,
    Get,
    QueryParams,
    Param,
} from 'routing-controllers';
import { IsInt, IsOptional } from 'class-validator';

interface Pokemon {
    id: number;
    name: string;
    type1: string;
    type2: string;
}

const pokemons: Pokemon[] = [
    {
        id: 1,
        name: 'フシギダネ',
        type1: 'くさ',
        type2: 'どく'
    },
    {
        id: 2,
        name: 'フシギソウ',
        type1: 'くさ',
        type2: 'どく'
    },
    {
        id: 3,
        name: 'フシギバナ',
        type1: 'くさ',
        type2: 'どく'
    }
]

class GetPokemonQuery {
    @IsInt()
    @IsOptional()
    limit?: number;

    @IsInt()
    @IsOptional()
    offset?: number;
}

@JsonController()
export class PokemonController {
    @Get('/pokemons')
    async pokemons(
        @QueryParams() query: GetPokemonQuery
    ): Promise<Pokemon[]> {
        const { offset = 0, limit = 100 } = query;
        return pokemons.slice(offset, offset + limit);
    }

    @Get('/pokemon/:id')
    async pokemon(
        @Param('id') id: number
    ): Promise<Pokemon> {
        const pokemon = pokemons.find((pokemon) => pokemon.id === id);
        if (pokemon) {
            return pokemon;
        }
        throw new Error('no pokemon');
    }
}

解説

  • クラスに対して@JsonControllerデコレーターを付けることでレスポンスをJSONとして扱うことを意味します。
  • リクエストのメソッドがGETのときは@Get、POSTのときは@Postという風にデコレーターを付与します。
  • クエリパラメータを受け取るときは、@QueryParamsを使います。
    • クエリパラメータもクラスベースで書くことができます。
    • @IsIntを付けることによって、バリデートやサニタイズを自動でしてくれます。
    • 他にも@IsBoolean@IsPositiveなどが使えます。
    • 詳しくはclass-validatorのドキュメントをご参照ください。
  • URL内のパラメータを受け取るときは@Paramを使います。

app.ts

app.tsがメインファイルになります。
Expressサーバーを起動し、ポート3000番でリッスンしています。
先ほど書いたコントローラーをインポートします。
比較的シンプルに書くことができます。

app.ts
import 'reflect-metadata';
import express from 'express';
import bodyParser from 'body-parser';
import {
    useExpressServer
} from 'routing-controllers';
import { PokemonController } from './controllers';

const PORT = 3000;

async function bootstrap() {
    const app = express();

    app.use(bodyParser.json());

    useExpressServer(app, {
        controllers: [
            PokemonController
        ]
    });

    app.listen(PORT, () => {
        console.log(`Express server listening on port ${PORT}`);
    });
}

bootstrap();

実行してみる

ts-node app.ts
# 「Express server listening on port 3000」となれば成功

ブラウザなどから、
http://localhost:3000/pokemons?limit=1
にアクセスしてレスポンスが返れば成功です。

Express単体との比較

非同期処理

Expressでハンドラーを書く際、非同期処理を即時間数でラップするなど面倒な書き方になってしまいます。

expressの場合
const express = require('express');
const router = express.Router();

router.get('/users', (req, res, next) => {
    (async () => {
        const users = await getUsers();
        res.status(200).json(users);
    })().catch(next);
});

一方でrouting-controllersでは、Promise型をそのまま返すだけでOKです。

rcの場合
import {
    JsonController,
    Get,
} from 'routing-controllers';

@JsonController()
export class UserController {
    @Get('/users')
    async users(): Promise<User[]> {
        return getUsers();
    }
}

バリデーション

Expressでバリデーションをするときは、express-validatorを使います。

express-validatorの場合
const { body, validationResult } = require('express-validator');

app.post('/user', [
    body('username').isEmail(),
    body('password').isLength({ min: 6 })
], (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }

    // ...
});

routing-controllersでは、デコレーターで書くことができます。

rcの場合
export class User {
    @IsEmail()
    email: string;

    @MinLength(6)
    password: string;
}

@JsonController()
export class UserController {
    @Post('/login')
    async login(
        @Body() user: User
    ) {
        // ...
    }
}

おわりに

いかがでしたでしょうか?
routing-controllersを使うことで、バリデーションやサニタイズもしつつ綺麗にコードを書くことができました。
routing-controllersはexpress.js以外のフレームワークにも適用できるので是非お試しください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これ書くだけ!JavaScriptで配列をcsv書き出し

用意した配列を「test」という名前だとします。
だとしたらこれを入れるだけでいいです。

var test = ['あああ','いいい','ううう'];

let bom  = new Uint8Array([0xEF, 0xBB, 0xBF]);
let blob = new Blob([bom, test],{type:"text/csv"});
let link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = '作ったファイル.txt';
link.click();

ここのコードを書き換えました。JavaScriptで文字列をファイル出力する方法を現役エンジニアが解説【初心者向け】

補足

途中のtext/csvを以下の物にも書き換えれるようです。(audio/mpeg、image/jpegとか)
でもこのためにnew Blobあたりのコードを書き換える必要があると思います。
mozillaに他の書き出し形式もいっぱい書かれているのでぜひ。

https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

p5.jsでトゥーンシェーダーを書く

この記事はProcessing Advent Calendar 2020 6日目の記事です。

0. はじめに

0. 1 シェーダーって何?

シェーダー(shader)とは、動詞"shade"「陰をつける,明暗(濃淡)をつける」に"-er"「~するもの」がついた名前の通り、「3DCGで陰影処理を行うプログラム」のことです。
p5.jsのWEBGLモードにおいては、loadshader()で外部ファイルとして読み込む、またはcreateshader()でString型として記述したものを読み込むことによって、自分で書いたシェーダーを使うことができます。
自作のシェーダーを使わない場合には、p5.jsライブラリ標準のシェーダーが使われます。シェーダーを自分で書く際には、はじめのうちは標準シェーダーを書き換えるような形で行うことがおすすめです。

0. 2 トゥーンシェーダーって何?

「トゥーンシェーダー」とは、通常ではグラデーションのようになる陰をベタ塗りにすることでカートゥーン調(セルアニメ調)に見せる「トゥーンシェーディング」を行うためのシェーダーのことです。
トゥーンシェーダーによって、↓のような陰をつけることができます。

 

1. トゥーンシェーダーを書く

ここでは、loadshader()を使った方法で、トゥーンシェーダーの書き方を説明していきます。

1. 1 メインのコード

メインとなるコードは下の通りです。

sketch.js
let toonShader;

function preload() {
  toonShader = loadShader("toon.vert", "toon.frag"); // シェーダーファイルの読み込み
}

function setup() {
  createCanvas(800, 600, WEBGL); // WEBGLモードにする
  shader(toonShader); // シェーダーの設定
}

function draw() {
  // 背景・光の設定
  background(250, 200, 200);
  directionalLight(255, 255, 255, 0.5, 0.5, -1);

  // 回転するトーラスの描画
  rotateX(millis() / 1000);
  rotateY(millis() / 3000);  
  noStroke();
  fill(240, 220, 120);
  torus(120, 60, 40, 40);
}

まず、preload()内でloadShader()を使い、変数toonShaderに後述する外部ファイル"toon.vert""toon.frag"を読み込ませます。
次に、setup()内でshader()を使い、レンダラーのシェーダーを、自作シェーダーtoonShaderに設定します。
今回作るトゥーンシェーダーは、平行光源に対応したものなので、directionalLight()を設定します。

必要な設定は以上で、あとは好きなようにtorus()sphere()などの立体を描画するコードを書きます。

1.2 バーテックスシェーダー

バーテックスシェーダー(vertex shader)とは、立体の頂点ごとに位置や法線ベクトルを計算するシェーダーです。
今回は、外部ファイル"toon.vert"にバーテックスシェーダーを記述します。

標準シェーダーのうちの一つ、"normal.vert"に加筆する形で記述していきます。

toon.vert
// ☆: 加筆箇所
attribute vec3 aPosition; // 頂点の位置ベクトル
attribute vec3 aNormal; // 頂点の法線ベクトル
attribute vec2 aTexCoord; // 頂点のUV座標(テクスチャーを貼る座標)

uniform mat4 uModelViewMatrix; // 位置ベクトルを座標変換する行列その1
uniform mat4 uProjectionMatrix; // 位置ベクトルを座標変換する行列その2
uniform mat3 uNormalMatrix; // 法線ベクトルを座標変換する行列

uniform vec3 uLightingDirection[5]; // ☆平行光源の方向ベクトル

varying vec3 vVertexNormal; // 座標変換後の法線ベクトル
varying highp vec2 vVertTexCoord; // UV座標(テクスチャーを貼る座標)
varying vec3 vLightDirection; // ☆平行光源の方向ベクトルの逆

void main(void) {
  vec4 positionVec4 = vec4(aPosition, 1.0);
  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
  vVertexNormal = normalize(vec3( uNormalMatrix * aNormal ));
  vVertTexCoord = aTexCoord;
  vLightDirection = -uLightingDirection[0]; // ☆平行光源の方向ベクトルをフラグメントシェーダーに渡す
}

void main(void){}の前に並んでいるのが、このシェーダーが他のプログラムとやり取りをする変数です。

ここで、シェーダーの変数について簡単に解説します。
例えば、下記のような変数について、

attribute vec3 aPosition;

1番最初のattributeを修飾子、2番目のvec2をデータ型、3番目のaPositionを変数名と呼びます。
修飾子には3種類あり、プログラム間でどのようなやり取りをするかによって変わります。

修飾子名 役割
attribute javascriptのプログラムからバーテックスシェーダーに送られる各頂点が持つ変数
uniform javascriptのプログラムからいずれかのシェーダーに送られる全頂点で一律の変数
varying バーテックスシェーダーからフラグメントシェーダーに送られる変数

attribute修飾子付き変数は、p5.jsライブラリで定義されたもののみが使え、uniform修飾子付き変数は、ライブラリで定義されたもののほか、setUniform()を使い、jsコード内で自分で定義することも可能です(p5.jsで使えるuniform変数名は、ライブラリ内の_setFillUniform()から読み解くことができます)。また、varying修飾子付き変数は、シェーダー間でのみ使われるため、全て自分で定義する必要があります。
データ型は、ベクトルを表すvec*、行列を表すmat*の他、浮動小数点数を表すfloatなどが使えます。

今回のバーテックスシェーダーの加筆箇所では、平行光源の方向ベクトルが格納されたuniform修飾子付き変数uLightingDirection[5]のうちの1番目の要素をフラグメントシェーダーに渡すために、varying修飾子付き変数vLightDirectionに代入しています。
マイナスをつける意味は、この後のフラグメントシェーダーの項で説明します。

1.3 フラグメントシェーダー

フラグメントシェーダー(fragment shader)とは、画面のピクセルごとの色を計算するシェーダーです。
今回は、外部ファイル"toon.frag"にフラグメントシェーダーを記述します。

今回のトゥーンシェーダーのメインとなる計算をするのが、このフラグメントシェーダーです。

toon.frag
precision highp float;

uniform vec4 uMaterialColor; // fill()で指定された色(RGBA)
uniform sampler2D uSampler; // texture()で指定された画像
uniform bool isTexture; // textureを使うか否か

varying vec3 vVertexNormal; // 座標変換後の法線ベクトル
varying highp vec2 vVertTexCoord; // UV座標(テクスチャーを貼る座標)
varying vec3 vLightDirection; // ☆平行光源の方向ベクトルの逆

void main(void) {
  vec3 direction = normalize(vLightDirection);
  vec3 normal = normalize(vVertexNormal);
  float intensity = max(0.0, dot(direction, normal)); // 平行光源と法線が成す角をθとしたときのcosθ

  vec4 tintColor; // 影の色
  if (intensity > 0.95) {
      tintColor = vec4(1.0, 1.0, 1.0, 1.0);
  } else if (intensity > 0.5) {
      tintColor = vec4(0.9, 0.8, 0.8, 1.0);
  } else if (intensity > 0.25) {
      tintColor = vec4(0.7, 0.5, 0.5, 1.0);
  } else {
      tintColor = vec4(0.5, 0.2, 0.2, 1.0);
  }

  if(!isTexture) { // テクスチャーを使わないとき
    gl_FragColor = uMaterialColor * tintColor;
  }
  else { // テクスチャーを使うとき
    gl_FragColor = texture2D(uSampler, vVertTexCoord) * tintColor;
  }
}

void main(void){}内の計算を、上から順に説明していきます。

  vec3 direction = normalize(vLightDirection);
  vec3 normal = normalize(vVertexNormal);
  float intensity = max(0.0, dot(direction, normal)); // 平行光源と法線が成す角をθとしたときのcosθ

GLSLにおいて、normalize(v)はベクトルの正規化(向きは同じのまま、大きさを1にする)、dot(v1, v2)はベクトルの内積(出力はfloat型)、max(a, b)は2つの値の大きい方を表します。
つまり、上記のコードの1, 2行目は平行光源の方向ベクトルの逆と、法線ベクトルを正規化し大きさを1にする計算だとわかります。
3行目のdot(direction, normal)では、1, 2行目で正規化した2つのベクトルの内積を計算していますが、ベクトルの内積は、$\vec{a} \cdot \vec{b} = |\vec{a}| |\vec{b}| \cos θ$ となることから、dot(direction, normal)が表すのは、2つのベクトルの成す角をθとしたときのcosθだとわかります。これにmax(0.0, dot(direction, normal))がついているため、cosθが0以下のの時はintensityは0、0以上の時はintensityはcosθになります。よって、intensityは法線ベクトルが平行光源の方向ベクトルの逆と同じ向きになるときに最大値1をとり、この2つのベクトルが成す角が90度を超えると0になる値だとわかります。
この計算を行うため、バーテックスシェーダーでは平行光源の方向ベクトルにマイナスをつけました。

次のif文が使われるかたまりが、影の色を計算する部分です。

  vec4 tintColor; // 影の色
  if (intensity > 0.95) {
      tintColor = vec4(1.0, 1.0, 1.0, 1.0);
  } else if (intensity > 0.5) {
      tintColor = vec4(0.9, 0.8, 0.8, 1.0);
  } else if (intensity > 0.25) {
      tintColor = vec4(0.7, 0.5, 0.5, 1.0);
  } else {
      tintColor = vec4(0.5, 0.2, 0.2, 1.0);
  }

tintColorは後ほど立体の色に掛け合わせる4次元のベクトルで、4つの要素は前から順にRGBAを表します。
通常の陰影の計算においては、intensityのような連続した値を影の色として掛け合わせることでグラデーションのような陰影をつけますが、トゥーンシェーディングにおいては、intensityの値に応じて、tintColorを不連続的に変化させ、ベタ塗りの影を表現します。
if文内に使われている0.95や0.5のような数字は、出力の見た目がよくなるように自分で調整する値です。
tintColorの色は、intensityが小さくなるにつれて暗くなっていくように設定します。今回は、赤みを帯びた影になるよう、Rの値をGとBに比べて大きく設定してみました。

最後に、立体の色にtintColorを掛け合わせ、色の出力gl_FragColorに代入します。

  if(!isTexture) { // テクスチャーを使わないとき
    gl_FragColor = uMaterialColor * tintColor;
  }
  else { // テクスチャーを使うとき
    gl_FragColor = texture2D(uSampler, vVertTexCoord) * tintColor;
  }

gl_FragColorはRGBAの要素を持つ4次元ベクトルで、この値が出力される色の値となります。
テクスチャーを使わない場合においては、fill()で指定された色にそのまま掛ければよいのですが、テクスチャーを使う場合、texture2D()という関数が登場します。この関数では、第1引数にテクスチャー画像、第2引数にUV座標を指定することで、そのUV座標における画像のRGBAを取り出すことができます。テクスチャーを使う場合においては、こうして画像から取り出した色に、tintColorを掛けます。

1. 4 出力結果

donut_1.png

2. アウトライン(輪郭線)をつける

トゥーンシェーディングでは、しばしばアウトライン(輪郭線)をつけます。
シェーダーのコードは前述のものと同様のため、割愛します。

2. 1 アウトラインをつける仕組み

アウトラインをつけるためには、WebGLの「カリング」という機能を使います。
カリングとは、「面の表裏によって描画するか否かを変える機能」です。
このカリングを使ってどのようにアウトラインをつけるかについては、下記のサイトの図がとても参考になります。
wgld.org | WebGL: トゥーンレンダリング |
大雑把に説明すると、カリングで表面を描画する状態にしたうえで立体を描画し、そこにカリングで裏面を描画する状態にしたうえでちょっとだけ膨らませた黒の立体を描画させると、あたかも枠線がついたように見える、というわけです。

2. 2 メインのコード

メインとなるコードは、下の通りです。

sketch.js
let toonShader;
let gl;

function preload() {
  toonShader = loadShader("toon.vert", "toon.frag") // シェーダーファイルの読み込み
}

function setup() {
  createCanvas(800, 600, WEBGL); // WEBGLモードにする
  shader(toonShader); // シェーダーの設定

  gl = this._renderer.GL; // WebGLレンダリングコンテキストを取得
  gl.enable(gl.CULL_FACE); // カリングを有効にする
}


function draw() {
  // 背景・光の設定
  background(250, 200, 200);
  directionalLight(255, 255, 255, 0.5, 0.5, -1);

  // 回転するトーラスの描画
  rotateX(millis() / 1000);
  rotateY(millis() / 3000);
  noStroke();

  // トーラスの中身の描画
  gl.cullFace(gl.FRONT);
  fill(240, 220, 120);
  torus(120, 60, 40, 40);

  // トーラスのアウトラインの描画
  gl.cullFace(gl.BACK);
  fill(0);
  torus(120, 65, 40, 40);
}

カリングを有効にするためには、WebGLコンテキストを取得する必要があるため、glという変数を用意し、そこに格納します(this._renderer.GLを使うとWebGLコンテキストが得られることは、p5.jsライブラリのrendererについての記述から読み解くことができます)。
gl.enable(gl.CULL_FACE)でカリングを有効にしたのち、表面を描画する状態で中身用のトーラスを、裏面を描画する状態で黒に塗り小半径を少し大きくしたアウトライン用のトーラスを描画します。

2. 3 出力結果

donut_2.png

3. 最後に

3. 1 トゥーンシェーダーで私が作った作品の紹介

トゥーンシェーダーと、他の手法を組み合わせることで、p5.jsで下のような作品を作ることができます。

3. 2 参考になるサイト

WebGL 開発支援サイト wgld.org
WebGLの使い方が0から丁寧に説明されたサイトです。p5.jsのライブラリのコードと合わせて見ると、p5.jsのWEBGLモードの仕組みがよくわかります。

processing/p5.js: p5.js is a client-side JS platform that ... - GitHub
p5.jsライブラリのWEBGLモードのソースコードです。WebGLの機能を使いたいときに読み解いてみると、いろいろな発見があります。

3. 3 あとがき

シェーダーやWebGLの機能を使うことで、p5.jsにおける3D表現の幅がグンと広がります!
この記事が、p5.jsでのシェーダーの記述に挑戦するきっかけとなれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】JavaScriptの 「 this」とは

※当方駆け出しエンジニアのため、間違っていることも多々あると思いますので、ご了承ください。また、間違いに気付いた方はご一報いただけると幸いです。

【JavaScript】JavaScriptにおけるクロージャーとは

↑こちらの記事に以下の通り記載しました。

「JavaScriptのスコープには、どの識別子がどの変数を参照するかが静的に決定されるという性質があります。
つまり、どこでinside関数を呼び出そうが、inside内のcountは、outside直下のcountを参照するということですね。

「this」という特別なキーワードだけは、呼び出し元によって動的に参照先が変わります。」

例えば、

function (){
  retrun this;
}

このような関数があった時、返却されるthisは呼び出し元によって変化します。

原則として、thisが参照するのは、ざっくりというと「呼び出し元自体」です。

まず、「呼び出し先が異なる」とはどういう意味なのか。

まず、以下のような内部処理にthisを含む関数があるとします。

function (){
console.log(this.name);
}

上記関数が、以下のようなhuman1, human2オブジェクトのgetNameプロパティーとして定義されているとします。それぞれ、プロパティーに
nameプロパティー(valueは文字列), getNameプロパティ(valueはメソッド(関数))が定義されています。

human1 = {
  name: "Taro",
  getName: function () {
    console.log(this.name);
  }
}

human2 = {
  name: "Ken",
  getName: function () {
    console.log(this.name);
  }
}

ちなみにイメージしやすいように、メソッドプロパティーを key : value で記載しましたが、

メソッドの定義部分は以下のように省略してもかけます。

human2 = {
  name: "Ken",
  getName() {
    console.log(this.name);
  }
}

話を戻しまして

それぞれのメソッドを呼び出してみます。

human1.getName();     ・・・※1
//Taro

human2.getName();     ・・・※2
//Ken

それぞれ、呼び出し元によって異なる文字列が返ってきました。

どのような動きとなっているのか。

※1の場合、getName()メソッドの呼び出し元は、 human1ですね。
つまり、メソッド内部のthisには、 呼び出し元自体の human1オブジェクトが参照されるわけですね。

イメージとしては、↓のような感じですね。

human1 = {
  name: "Taro",
  getName: function () {
    console.log(human1.name);
  }
}

再帰的に、呼び出し元オブジェクトが内部のthisから参照されます。

human2は

human2 = {
  name: "Taro",
  getName: function () {
    console.log(human2.name);
  }
}

ですね。

なぜ、thisから呼び出し元が参照されるかというと、イメージとしては呼び出し時に暗黙的に、仮引数に、呼び出し元オブジェクトを渡す感じです。
どういうことかといいますと、下のようなイメージです。

human1 = {
  name: "Taro",
  getName: function (x) { //暗黙的に引数を受け取る。
    console.log(x.name);
  }
}
console.log(human1.getName(human1));  //暗黙的に呼び出し元オブジェクトを引数に渡す。

thisを用いることによって、メソッド内部の定義を共通化できるのです。
これによりクラスからインスタンスを作った際に、それぞのインスタンスが持つインスタンス変数を参照することができます。

呼び出し元の種類によって、参照先が異なります。

場所 Thisの参照先
トップレベル(関数の外) グローバルオブジェクト
関数 グローバルオブジェクト
コンストラクター 生成したインスタンス
メソッド 呼び出し元のオブジェクト
イベントリスナー イベントの発生元
call/applyメソッド 引数で指定されたオブジェクト

原則は全て、呼び出し元がthisに入るということです。

call/applyメソッドについては、thisの参照先を指定することができます。

一つ一つ例を見ていきます。

関数の外の場合

console.log(this);

//window

呼び出し元であるグローバルオブジェクト(windowオブジェクト)が参照されています。

関数の場合

function test() {
  console.log(this);
};

test();

//window

同様に呼び出し元であるグローバルオブジェクト(windowオブジェクト)が参照されています。

ちなみに、グローバルオブジェクトがもつプロパティを参照した場合は、当然その値が参照されます。

function test() {
  console.log(this.document);
};

test();

//#document
//<html>
// <body>
//   <script src="practice.js"></script>
// </body>
//</html>

コンストラクターの場合

class Member {
  constructor(name) {
    this.name = name;
  }
}

let member = new Member("Taro")
console.log(member);

//{name: Taro}

let member2 = new Member("Ken");
console.log(member2);

//{name: Ken}

まず、new演算子でインスタンスが作成されて、そのインスタンスがコンストラクターを呼び出します。
(その時の引数はmemberでは Taro)
thisには呼び出し元のmemberインスタンスが参照されているので

    this.name = name;

    //これはつまり

    member.name = "Taro"

ということで、memberインスタンスのnameプロパティにTaroが入ります。

メソッドの場合

メソッドの場合は、上記の例通りなので割愛

イベントリスナーの場合

<div id='checkThis'></div>

↑ の上にマウスが乗ったら、色が黄色く変わるというシンプルなコードです。

let elem = document.getElementById('checkThis');

elem.addEventListener('mouseover', function () {
  this.style.backgroundColor = "Yellow";
}, false);

 この場合、thisはイベント発生元である elem が参照されます。
よって、そのelemのスタイルに背景色黄色を設定し変色させます。

call/applyメソッドの場合

call/applyメソッドの場合だけ、特殊でthisは呼び出し元が参照されますが、それを
異なるオブジェクトを指定してしまおうというものです。

human2 = { name: "Ken" };

human1 = {
  name: "Taro",
  getName: function () {
    console.log(this.name);
  }
}

human1.getName();

//Taro

human1.getName.call(human2);

//Ken

普通にgetName()を実行すると、当然human1がthisから参照されます。

しかし、callメソッドで引数にhuman2を渡すと、thisの参照先がhuman2となっています。

callメソッドとapllyメソッドの違いは、関数に渡す引数の渡し方の違いだけです。

func.call("thisに指定するオブジェクト", 引数, 引数, 引数)

//callは、funcに渡す引数を列挙

func.aplly("thisに指定するオブジェクト", [引数, 引数, 引数])

//apllyは配列で渡す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Native でハンバーガーメニューを作る!

完成品

  

パッケージをインストール

今回は React NavigationDrawer Navigation を使うので、以下のコマンドでインストールを行います。

expo install @react-navigation/drawer @react-navigation/native react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

ハンバーガーボタンの作成

まずハンバーガーボタンのコンポーネントを作成します。

import React from 'react';
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { DrawerNavigationProp } from '@react-navigation/drawer';
import { DrawerParamList } from '../types';

type Props = {
    navigation: DrawerNavigationProp<DrawerParamList>;
};

export const DrawerButton: React.FC<Props> = ({ navigation }: Props) => {
    return (
        <TouchableOpacity
            style={styles.hamburgerMenu}
            onPress={() => {
                navigation.openDrawer();
            }}
        >
            {[...Array(3)].map((_, index) => {
                return (
                    <View
                        key={`bar_${index}`}
                        style={styles.hamburgerMenuBar}
                    />
                );
            })}
        </TouchableOpacity>
    );
};

const styles = StyleSheet.create({
    hamburgerMenu: {
        width: 30,
        height: 25,
        position: 'absolute',
        top: 70,
        left: 30
    },
    hamburgerMenuBar: {
        width: 25,
        borderBottomWidth: 3,
        borderColor: '#EDC988',
        marginBottom: 7
    }
});

DrawerNavigationのopenDrawer()というメソッドを使い、タッチするとDrawerが開くようになっています。

ハンバーガーメニューは3本の線を縦に並べることで作っています。[...Array(3)].map((_, index) => {hogehoge}というところで、0,1,2を生成し、各子コンポーネントのkeyに利用しています。

【余談】スクリーンの雛形のスニペット登録

スクリーンや、コンポーネントの雛形はスニペットに登録しておくと便利です。
自分はtypescriptreact.json
に、以下のような雛形を登録しています。

{
  "component": {
    "prefix": "mycomponent",
    "body": [
      "import React from 'react';",
      "import { View, StyleSheet } from 'react-native';",
      "",
      "type Props = {};",
      "",
      "export const Component: React.FC<Props> = ({}: Props) => {",
      "    return <View style={styles.container}></View>;",
      "}",
      "",
      "const styles = StyleSheet.create({",
      "    container: {}",
      "})"
    ],
    "description": "React Native Component with TypeScript"
  },
  "screen": {
    "prefix": "myscreen",
    "body": [
      "import React, { useEffect, useState } from 'react';",
      "import { StyleSheet, SafeAreaView, Text } from 'react-native';",
      "",
      "type Props = {}",
      "",
      "export const Screen: React.FC<Props> = ({}: Props) => {",
      "    useEffect(() => {}, []);",
      "",
      "    return (",
      "       <SafeAreaView style={styles.container}>",
      "            <Text>Screen</Text>",
      "        </SafeAreaView>",
      "    )",
      "};",
      "",
      "const styles = StyleSheet.create({",
      "    container: {",
      "        flex: 1,",
      "        backgroundColor: '#fff',",
      "        alignItems: 'center',",
      "        justifyContent: 'center'",
      "    }",
      "})"
    ],
    "description": "React Native Screen with TypeScript"
  }
}

これにより、VSCode内で、myscreenや、mycomponentと入力すると、スクリーンやコンポーネントの雛形が作成できます。

ソースコード

githubで公開しています。
詳しくはこちらをご参照ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリで端末の位置情報を取得する

#はじめに Androidアプリで、Cordovaプラグインを使用して端末の位置情報を取得する。 #コード 最終的なコード document.addEventListener("deviceready", function(){ let permissions = window.cordova.plugins.permissions; permissions.requestPermissions( [ permissions.ACCESS_FINE_LOCATION ,permissions.ACCESS_COARSE_LOCATION ] ,function( status ){ if( status.hasPermission ){ navigator.geolocation.getCurrentPosition( function(position) { let latitude = position.coords.latitude let longitude = position.coords.longitude console.log(latitude, longitude) }, function(error) { console.log(error) }, { enableHighAccuracy: false, timeout: 30000, } ); } else{ console.log('status of permissions is error') } } ,function(){ console.log('permissions is error') } ); }, false); #解説 ###Cordovaの読み込みが完了した後で、中の関数を実行する。 https://cordova.apache.org/docs/ja/latest/cordova/events/events.deviceready.html document.addEventListener("deviceready", function(){ .... }, false); ###端末で位置情報取得を許可する。 アプリを開いた時に、「~~~に位置情報へのアクセスを許可しますか」という旨の確認ダイアログを表示させる。 cordova-plugin-android-permissionsをインストール。 cd src-cordova cordova plugin add cordova-plugin-android-permissions 複数のパーミッションをリクエストするため、permissions.requestPermissionsを使用。 permissions.ACCESS_FINE_LOCATIONは、おおよその位置にアクセスできるようにする設定。 permissions.ACCESS_COARSE_LOCATIONは、正確な場所にアクセスできるようにする設定。 let permissions = window.cordova.plugins.permissions; permissions.requestPermissions( [ permissions.ACCESS_FINE_LOCATION ,permissions.ACCESS_COARSE_LOCATION ] ,function( status ){ if( status.hasPermission ){ .... } else{ .... } } ,function(){ .... } ); app/AndroidManifest.xmlに下記を追加する。 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /> ###geolocationプラグインをインストール。 cordova plugin add cordova-plugin-geolocation // 古いバージョンのCordovaはこっちをインストール(5.0未満?) cordova plugin add org.apache.cordova.geolocation app/res/xml/config.xmlに下記を設定する。 <feature name="Geolocation"> <param name="android-package" value="org.apache.cordova.GeoBroker" /> </feature> ###位置情報を取得する。 navigator.geolocation.getCurrentPositionを使う。 navigator.geolocation.getCurrentPosition( function(position) { let latitude = position.coords.latitude let longitude = position.coords.longitude console.log(latitude, longitude) }, function(error) { console.log(error) }, { enableHighAccuracy: false, timeout: 30000, } ); enableHighAccuracyはより正確な位置情報を取得するためのオプション。 通常はtrueで設定するが、アプリの起動が遅くなる場合はfalseにすると早くなる。 falseでもほぼ正確な位置情報を取得できる。 #参考サイト https://www.npmjs.com/package/cordova-plugin-android-permissions
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

moment.jsで「〜時間前」を表示させる

#はじめに moment.jsを使用して、指定時刻から計算して「〜日前」「〜時間前」を表示させる。 #moment().fromNow() ###import import Moment from 'moment'; 指定した時刻から現在の時刻までの差を計算する。 Moment('2020-01-01T10:10:30.900Z').fromNow(); #言語を設定する このままでは「〜hour ago」など英語で表示されるので、言語を設定する。 Moment.locale( 'ja' ); #時差をなくす このままでは、UTC(世界標準時間)が適応され、日本からは+9時間の時刻が表示される。 moment-timezoneを使用する。 ###install npm install moment-timezone ###import import MomentTimezone from 'moment-timezone'; ###UTCの時刻を取得 let dateTimeUtc = MomentTimezone.tz('2020-01-01T10:10:30.900Z', 'UTC'); ###デバイス(ブラウザ)で設定されているタイムゾーンを取得 海外でも使用する可能性があればデバイス(ブラウザ)で設定されているタイムゾーンを取得する。 日本のみで使用の場合は不要。 let deviceTz = Intl.DateTimeFormat().resolvedOptions().timeZone; ###指定したタイムゾーンに変換 デバイス(ブラウザ)と同じタイムゾーンに変換。 let upDate = MomentTimezone(dateTimeUtc).tz(deviceTz).format('YYYY-MM-DDTHH:mm:ss.sssZ'); 日本のみで使用の場合は'Asia/Tokyo'を直接入力。 let upDate = MomentTimezone(dateTimeUtc).tz('Asia/Tokyo').format('YYYY-MM-DDTHH:mm:ss.sssZ'); ###moment().fromNow()に入れる let lastUpDate = Moment(upDate).fromNow(); #まとめ import Moment from 'moment'; import MomentTimezone from 'moment-timezone'; // 言語を日本語に設定 Moment.locale( 'ja' ); // デバイスのタイムゾーンを取得 let deviceTz = Intl.DateTimeFormat().resolvedOptions().timeZone; // UTCの時刻を取得 let dateTimeUtc = MomentTimezone.tz('2020-01-01T10:10:30.900Z', 'UTC'); // 指定したタイムゾーンに変換 let upDate = MomentTimezone(dateTimeUtc).tz(deviceTz).format('YYYY-MM-DDTHH:mm:ss.sssZ'); // 「〜日前」「〜時間前」を表示 let lastUpDate = Moment(upDate).fromNow(); #参考サイト https://stackoverflow.com/questions/53017772/moment-js-parse-date-time-ago
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js + moment.jsで、時刻をリアルタイム更新する方法

#はじめに Vue.jsとmoment.jsを使用して、時刻をリアルタイムで更新する時計を作る。 本文ではTypeScriptを使用。 #moment.jsをインストール npm install moment #開発コード インポートする。 import Moment from 'moment'; ###Moment().format()を使用 表示形式は自由に選択できる。 time = Moment().format("YYYY-MM-DD HH:mm:ssZ"); ###setIntervalを使用 createdの中にsetIntervalを入れる。1秒毎に新しい時刻を取得。 created() { setInterval(() => { this.time = Moment().format("YYYY-MM-DD HH:mm:ssZ"); }, 1000) } #参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Canvas2Dについてさっくり復習してみた

Canvas2Dについてさっくり復習してみた

概要

ryokio0129です。

JavaScript Advent Calendar 2020 6日目になります。
初参加です。よろしくお願いいたします。

canvasへの描画に関して、昨今色々なフレームワークやライブラリがあり、そちらを使うことが多いと思います。
かくいう私も、仕事では Phaser というHTML5ゲームフレームワークを使っていることが多いです。

そんな中、たまたま素のCanavs2Dを触らないといけない状況が発生したので、さっくり復習していきます!

Canvas2Dをさっくり復習するどん

Canvas2Dのコンテキスト取得

WebGLにしてもCanvas2Dにしても、HTMLCanvasElementからコンテキストを取得しないと描画できないです。
Canvas2Dに関してはCanvasRenderingContext2Dを取得します。

See the Pen Canvas2Dのコンテキストを取得 by ryokio (@ryokio) on CodePen.

矩形・テキストの描画

矩形の描画は context.fillRect(x, y, width, height)
テキストの描画は context.fillText(text, x, y)

さくっと描画できますが、現状だとどちらも黒で何がなんだかわからないですね……。

See the Pen 矩形・テキストの描画 by ryokio (@ryokio) on CodePen.

色を指定する

色……特に塗りつぶすための色を指定するのは context.fillStyle を使います。
context.fillStyle はCSSの指定そのまま使えるので、例えば context.fillStyle = rgba(255, 0, 0, 0.1) のようにすると、不透明度も反映することが可能です。

色は変わりましたが、これだとどちらも同じ色になってしまいます。
これだと困りますね……。

See the Pen 色を指定する by ryokio (@ryokio) on CodePen.

描画状況の保存・復元

context.save() で今の描画状況を保存します。
context.restore() で保存した状況を復元します。

これで矩形のみがrgba(255, 0, 0, 0.1)になりました。
また、

context.save();

  // この時点での描画を保存
  context.save();
  context.fillStyle = '#00FF00';
  context.restore();

context.restore();

のように入れ子も出来ますが、context.save()context.restore() が抜けると大変なことになりますので、ご注意を。
(今までで context.restore() の記載が抜けてることの方が多かったです)

See the Pen 描画状況の保存・復元 by ryokio (@ryokio) on CodePen.

様々な図形

円弧を描く場合は context.arc() を使えば描けます。
context.beginPath() でパスを使いたい宣言をします。その後に、context.arc() を使って描画します。

See the Pen 円弧を描く by ryokio (@ryokio) on CodePen.

また context.moveTo() でパスの始点を変更することができます。
それを使って、様々な図形を描くことが可能です。

画像の描画

画像の描画は context.drawImage() で行います。
ですが、画像はまず画像を読み込み終えていることが前提ですので、画像が読み込み終わっているかのチェックが必要になります。

See the Pen 画像の描画 by ryokio (@ryokio) on CodePen.

context.drawImage() は画像以外にも色々指定することができます。

変形

図形や画像などは変形することが可能です。

拡縮に関しては context.scale()
移動に関しては context.translate()
回転に関しては context.rotate()

複雑な変形を行う場合は context.transform() もしくは context.setTransform() で可能です。
context.transform() は現状のマトリクスからの変形。
context.setTransform() は現状のマトリクスを破棄して変形(上書き)です。
どちらにしても2Dの行列計算が必要です。調べれば出てくるとはいえ、私は苦手です……。

See the Pen 変形 by ryokio (@ryokio) on CodePen.

合成

直前の描画との合成をする際は、context.globalCompositeOperation を設定します。
私は黒の矩形を画像で抜いて、シルエット画像にする。ということが多いでしょうか。
(これによって、わざわざシルエット画像を用意してもらう必要がなくなる)

See the Pen 合成 by ryokio (@ryokio) on CodePen.

オフスクリーンレンダリング

合成 の際に少しやっていますが、新しいcanvas要素を作り、そこにひとまず描画を行い、
その結果を、実際のcanvasに描画して反映させています。
いわゆるオフスクリーンレンダリングです。

専用の OffscreeCanvas というものもあるようですが、当面はこの手法でやるしかないのかなと思っています。

OffscreenCanvas - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas

終わりに

アドベントカレンダー初参加だ!
と思いつつも、仕事が忙しい(言い訳)ために、今急いで書いた次第であります。
だが、参加することに意義がある! ……と信じつつ。

根っこのCanvas2Dを触れようとしている方の一助になれば幸いです。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブロックチェーン『で』命を賭ける

この記事は クソアプリ2 Advent Calendar 2020 の6日目の投稿です。
昨日は@goosysさんの老舗ゲームメーカーになろうでした。
今日は、20年前のカードゲーム漫画をブロックチェーンに応用してみましょう。

カードゲーム漫画にて…

悪役 『お前のスターチップは既に一つ。そいつを奪えば、お前は早くも落伍者になるわけかぁ。それじゃあ僕もスターチップを一つ賭けるぜぇ』
主人公『いや、2個賭けてもらう!』
悪役 『なに!?』
主人公『言ったハズだぜ!お前とは潰すか潰されるしかない!オレはスターチップひとつと…』

主人公『命を賭けるぜ!!』

ごあいさつ

こんにちは。命、賭けてますか?
なんでもトークンと申します。
僕は、ブロックチェーンに全力投球。

価値をインターネット上で扱う技術である『トークン』の研究開発をしています。
結果として、毎月のお給料がブロックチェーンの利用手数料として溶け続けています。
やばいです。

価値を扱う技術

ブロックチェーンは価値を扱う、金融に近い領域の技術。
単なるプログラムから、コインを借りられるシステムもあります。
https://app.compound.finance/
でもこのシステム、とある問題があるのです。

コインがないとコインを借りられない

手元にコインがないと、コインを借りることができません。
つまり、こういうことです。

【できる】
1カ月後に必ず返すから100万円分のコインを貸して!!
約束を守る証拠金として、システムに200万円分のコインを預けるよ!

【できない】
1か月後に必ず返すから100万円分のコインを貸して!!

借金の取り立てが出来ない

さきほど紹介したCompound、完全に無人で動いています。
借金を取り立てる怖いお兄さんがいません。
それなら、借りたまま誰も返済しないですね…。

命を担保にするのは無理

ブロックチェーンの世界は、担保以上のものを借りるのが不可能なのです。
しかも、現実の物、腎臓、命などは、ブロックチェーン上に存在しないので担保にできません。
お金持ちしかお金を借りることができない。
そんなのずるい。

クソアプリ作ってみた

『命』を担保にコインを借りられるシステム、作ってみました。
システム構成図は次の通り

システム構成図.png

今回は次の2つを作ります。
・命トークン
・命を刈り取るシステム

作り方を解説していきます。
皆さんも『命』を担保に入れてみてくださいね!

命トークン

僕はブロックチェーンが大好きなので、
命トークンの作り方、紹介します!
クソアプリですので、読み飛ばしてもらって大丈夫です。

命トークンのレシピ

材料
・Ethereumブロックチェーン 1つ
・Solidity言語のプログラム 約20行
・Openzeppelinライブラリ 約1500行
・JSONファイル 約300バイト
・命 1つ

video_cooking_man.png
調理方法
①Etherumブロックチェーンを弱火(20Gwei)で熱します。
②Solidityプログラムを、心を込めて記載します。
③メイン材料であるOpenzeppelinライブラリを結合します。
④JSONファイルを添えると、味にコクと深みが出るでしょう。
⑤命はまだ使わずに手元に置いておいてください。

心を込めて記載した、今回のプログラムを紹介します。

LifeIsNFT.sol(②で記載した部分)
contract LifeIsNFT is ERC721 {

bool living = true;

function kill() public {
    // 所有者のみが実行可能
    require (msg.sender == ownerOf(1));
    // 命をオフにする
    living = false;
}

function isliving() public view returns (bool) {
    // 命がオンかどうか
    return living;
}

function burnNFT() public {
    // 所有者のみが実行可能
    require (msg.sender == ownerOf(1));
    // 命トークンを消して操作不可にする
    _burn(1);
}

constructor() ERC721 ("LifeIsNFT" , "LIFE") public {
    // 命トークンを1つだけ発行
    _mint(_msgSender() , 1);
    // メタデータの登録
    _setTokenURI( 1 ,  "https://nandemotoken.github.io/LifeIsNFT/LifeIsNFT.json");
    }
}

参考:Solidity言語を使ったプログラム全文
https://github.com/nandemotoken/LifeIsNFT/blob/gh-pages/LifeIsNFT.sol

命トークンをEthereumテストネットに構築

今回はクソアプリですので、テスト用環境に構築します。
環境はRinkebyネットワークにしました。
image.png

…もし本番環境で作成する場合は、法律チェックが必要です。
……このクソアプリは、法律はともかく倫理的にアウトですね…

命トークンの確認

構築したので命トークンが出来ています。
競売サイトで確認しましょう。
OpenSea有名です。
https://opensea.io/

テストネット上の自分のトークンが確認できるURLにアクセスします。
https://testnets.opensea.io/account

inochi.png

ありました。
今回作成した命トークンは、次のURLで確認できます。
https://testnets.opensea.io/assets/0x6c63483eacac253e65a3445829621d37c5bfc7fc/1/

命トークンの使い方

①Aさんが命トークンを作成。
②ブロックチェーンの競売システムに出品。
③富豪が買い取る。
④Aさんは利子をつけて借金返済。
⑤返済期限をオーバーしたら、kill関数でAさんを殺す。

システム構成図(再掲)
システム構成図.png
命を担保にできます!

命の出品

OpenSeaを操作して、命を出品していきましょう。

image.png

NFTのページにアクセスし、SELLボタンを押します(命の所有者のみが出品できます)

image.png

価格などを設定して出品しましょう。
今回は100ETH(約500万円)にしてみました。
期間を限定したオークションなども開催できますよ!

命を刈り取るシステムの作成

Kill関数により、living変数がfalseになった場合、借り主が死ぬ必要があります。
どのように実現しましょうか…

命を刈り取るシステム:Raspberry Pi

Raspberry Piによる電子工作で実現することにしました。
次の2つの機能で、現実とブロックチェーンを紐付けます。
①電子回路の制御機能(GPIO)⇒現実の装置を操作するために利用
②Web通信機能(Linux)⇒ブロックチェーン上のデータ確認に利用
お値段は約5000円

E55F45E3-023E-4F8B-A491-609BE79FB5D7.jpeg

命を刈り取るシステム(電子部品の用意)

今回はシンプルにサーボモーターで刃物を動かすシステムとします。

0F8EB27B-AEBC-4EDC-BF07-0BF6A40164C5.jpeg

部品
・Raspberry Pi 1式
・サーボモーター 1個
・ジャンプワイヤ 10本
・電池BOX(単3×4) 1個
・ブレッドボード 1枚

命を刈り取るシステム(電子部品の組立)

2CCE0787-56AA-4558-A05B-A008AEC9F969.jpeg

これが命を刈り取るシステムのエンジン部分です。
サーボモータの信号線は右から6番目のGPIO18ピンに接続しました。

命を刈り取るシステム(電子回路制御プログラム)

電子回路を制御するプログラムを書きましょう。
Raspberry PiのGPIO18ピンから制御信号を出します。

GPIO18ピンからサーボモーターを制御するプログラム
//raspi-pwmを使用する。
const raspi = require('raspi');
const PWM = require('raspi-pwm').PWM;

raspi.init(function() {
//GPIO18を制御する
  const  pwm = new PWM('GPIO18');
  setInterval(function(){
//生存判定の結果、duty比を0.03もしくは0.09にする。
  pwm.write(living ? 0.03 : 0.09)
  } , 60000)
});

なお、Raspberry Pi上で次の準備が必要です。

raspi-pwmのインストール
npm install raspi-pwm

今回のサーボモータはduty比を0.03から0.09にすると約90度回転します。
サーボモーターに刃物を着けると、命を刈り取ることができます!

命を刈り取るシステム(ブロックチェーン読取)

ブロックチェーンから『isliving』関数を読み取ります。
Node.jsを利用して実装していますので、infura.io経由で関数を読み取ると良いでしょう。

ブロックチェーン確認
//remixのボタンを押して取得したABIを貼り付け
const abi = "(省略)"

//web3.jsを読み込む
const Web3 = require("web3")
//Infra.ioに登録して取得したAPIキー
const MyInfuraioApiKey = "8cbeaxxxxxxxxxxxxxxxxxxxxx"

async function setup(){
//metamaskの準備
web3js = await new Web3( new Web3.providers.HttpProvider("https://rinkeby.infura.io/v3/"+ MyInfuraioApiKey ));
//スマートコントラクト読み込み
mycontract = await new web3js.eth.Contract(abi, "0x6c63483eacac253e65a3445829621d37c5bfc7fc");
}

//ブロックチェーンの呼び出しは、非同期処理に注意して実装する
async function checkLiving(){
//ブロックチェーン上のisliving関数を呼出
let fromblockchain1 = await mycontract.methods.isliving().call();
console.log(fromblockchain1);
return fromblockchain1;  
}

setup();
let living = checkLiving();

なお、Raspberry Pi上で次の準備が必要です。

web3jsのインストール
npm install web3

命を刈り取るプログラムの全文はこちら。
https://github.com/nandemotoken/LifeIsNFT/blob/gh-pages/LifeIsNFT.js

命を刈り取るシステムの装着方法を考える

命を刈り取るシステムは確実に装着されている必要があります。
次の2つの観点で、装着方法の検討が必要でしょう。
①取り付けたあと、取り外すことがない。
②取り付けたあと、日常生活に支障がない。

つまり、指輪、ネックレス、時計のような位置づけが良さそうです。
しかし、首輪や指輪などは、大きなサイズだと違和感がありますね。

我々が身に着けていて最も違和感のないものは何でしょうか…?
そう。デュエルディスクです。
yu_02.jpg
参考画像:腕に着けてカードを入れるやつ

命を刈り取るシステムのデュエルディスクを作る

E51B7642-9A82-4AD5-B7E0-4A7BF725FAD2.jpeg

家にあった材料でデュエルディスクを作りました!
上の参考画像とほとんど同じですね。

なお、今回取り付けてあるナイフは手品用。
本番運用では殺傷力の高いものを選定してください。

命を刈り取るシステムの装着

FFD5A06D-F37E-4A40-8D5E-C6389ACADA77.png

日常生活をする上で、全く違和感はありませんね

命を刈り取るシステムを動かす

命を刈り取るシステムを動かしてみましょう。
https://nandemotoken.github.io/LifeIsNFT/LifeFarming.html

富豪のアカウントで上記専用サイトに接続し、Kill関数を呼び出します。
命トークンを持っていた場合だけ、kill関数が動作します。
するとブロックチェーン上に設置したliving変数がfalseになります。
(命トークンを持っていない人が呼び出しボタンを押してもエラーになるだけです)

image.png

命を刈り取る

動かしてみましょう。

次の仕組みで命が奪われます。
●Aさんがコインの返済義務を守らない
⇒富豪がkill関数を呼び出す
⇒ブロックチェーンに反映(約15秒)
⇒Raspberry piがブロックチェーンのデータを読み出す(約1分)
⇒Raspberry piのGPIO18番から制御信号送信
⇒Aさんの『命』をシステムが奪う

実際の動作

ezgif-4-42846591aa1f.gif

ナイフの動きが殺意に溢れています。
手品用のナイフでなければ、私の命が奪われていたはずです。

ブロックチェーン『に』命をかける

最近、ブロックチェーン界隈の知り合いが、どんどん会社を辞めています。
ブロックチェーンで生きていくそうです。
たしかに、この分野は革新的で、命を賭ける価値があるでしょう。
僕もプロのブロックチェーンエンジニアになりたい…。

おわりに

世界のトークン化をめざす、なんでもトークンでした。
Twitterでブロックチェーンの情報発信をしております。
https://twitter.com/NandemoToken

明日は @yaju さんによる『英語の歌を歌いたい』!
楽しみです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでScalaっぽく操作できる標準メソッドたち

:star: この記事は、ただの集団 Advent Calendar 2020 の6日目です :star:

昨日は、同じグループのメンバーryoさんの、 JVで人事立ち上げてみて でした :hand_splayed:
毎年、開発部に絞らずにアドベンドカレンダーやってるから、いろんな部署のいろんな人の話が聞ける良い機会で私はこのアドベントカレンダーの時期が大好きです :heart_eyes:

はじめに

私は、社会人になってからプログラミングをはじめました。
そこからずっと主な言語は、Scalaでした :pencil:
他の言語のタスクが来たときは特にScalaだとこのメソッドあるけど、この言語にもあるのかなー。のようなモチベーションでググってます :computer:

今回は、新規プロダクトの立ち上げの際にVueを書く機会があったので、その時に発見したjsの発見をメモとして残します!
※サンプルコードには、TypeScriptも使用しています。

Option.getOrElse

私の大好きなメソッドの一つ

Scala

def getName(nameOpt: Option[String]): String = {
  nameOpt.getOrElse("名前の指定なし")
}

println(getName(Some("ando"))) // ando
println(getName(None)) // 名前の指定なし

JavaScript

function getName(nameOpt?: string): string {
  return nameOpt ?? "名前の指定なし" 
}

console.log(getName("ando")) // ando
console.log(getName(undefined)) // 名前の指定なし

可読性のScala、文字数的にはjsって感じ :thinking:

Collection.map

これも、私の大好きなメソッドの一つ

Scala

def suffixAsobase(hinatazakas: Seq[String]): Seq[String] = {
  hinatazakas.map(hinatazaka => s"${hinatazaka}あそばせ")
}

println(suffixAsobase(Seq("かとし", "こさかな", "みーぱん", "齊藤京子")))
// Seq(かとしあそばせ, こさかなあそばせ, みーぱんあそばせ, 齊藤京子あそばせ)

JavaScript

function suffixAsobase(hinatazakas: string[]): string[] {
  return hinatazakas.map(hinatazaka => hinatazaka + "あそばせ")
}

console.log(suffixAsobase(["かとし", "こさかな", "みーぱん", "齊藤京子"]))
// ["かとしあそばせ", "こさかなあそばせ", "みーぱんあそばせ", "齊藤京子あそばせ"] 

そのまま :point_up:

Collection.find / String.contains

Scala

val morningMusumes = Seq("ゴマキ", "鞘師", "まーちゃん", "尾形")
println(morningMusumes.find(_.contains("ゴ"))) // Some(ゴマキ)

JavaScript

const morningMusumes = ["ゴマキ", "鞘師", "まーちゃん", "尾形"]
console.log(morningMusumes.find(musume => musume.includes(""))) // ゴマキ

戻り値の型注意 :open_mouth:

Collection.headOption / Collection.tail

Scala

val nogizakas = Seq("生田", "なーちゃん", "白石麻衣", "かっきー", "久保ちゃん")
println(nogizakas.headOption) // Some(生田)
println(nogizakas.tail) // Seq(なーちゃん, 白石麻衣, かっきー, 久保ちゃん)

JavaScript

const nogizakas = ["生田", "なーちゃん", "白石麻衣", "かっきー", "久保ちゃん"]
console.log(nogizakas.shift()) // 生田
console.log(nogizakas) // ["なーちゃん", "白石麻衣", "かっきー", "久保ちゃん"]

mutable怖い :scream_cat:

Collection.init / Collection.last

Scala

val sakuras = Seq("天ちゃん", "理佐", "梨加", "ねる", "てち")
println(sakuras.last) // てち
println(sakuras.init) // Seq("天ちゃん", "理佐", "梨加", "ねる")

JavaScript

const sakuras = ["天ちゃん", "理佐", "梨加", "ねる", "てち"]
console.log(sakuras.pop()) // てち
console.log(sakuras) // ["天ちゃん", "理佐", "梨加", "ねる"]

終わりに

getOrElseので感動して書きはじめたけど、意外と数はなかったな :cry:
immutable推奨してる言語と、mutableなので厳密に完全一致じゃないので、ご指摘いただける際はここ以外でお願いします :pray:

(途中から、sample誰にしようかなーの趣旨に変わってたな :blush: )

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナ上でGatsbyを動かす

確認環境

  • OS: macOS Catalina
  • Docker: version 19.03.13
  • docker-compose: version 1.27.4

Docker Compose

まずはComposeファイルを準備します。

docker-compose.yml
version: '3'
services:
  gatsby:
    build: ./docker/gatsby
    volumes:
      - ./gatsby:/usr/src/app
    ports:
      - "8000:8000"
    tty: true

Dockerfile

次はDockerfileを準備します。
Composeファイル内に記載されている./docker/gatsby内に配置します。
ファイル名はDockerfileです。
※おそらくQiitaの仕様で拡張子のないファイルのファイル名を以下で表示させることができないので補足

FROM node:15.3.0-alpine3.10
WORKDIR /usr/src/app
RUN apk update && \
    apk add git && \
    npm install -g gatsby-cli
EXPOSE 8000

コマンド実行

以下コマンドを実行します。

コンテナ起動

$ docker-compose up -d

スターターダウンロード

$ docker-compose exec gatsby gatsby new . https://github.com/gatsbyjs/gatsby-starter-hello-world

サーバ起動

$ docker-compose exec gatsby gatsby develop --host=0.0.0.0

動作確認

http://localhost:8000/
にアクセスして以下画像のように表示されたら成功です。
starter.png
公式ドキュメントより引用

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

数学用ソフトウェアキーボードをJavaScriptで作ってみた

分数の入力ができない

 算数のWeb教材を作成したとき、整数と小数はキーボードから入力できるが、「分数は入力できない」という問題にぶつかりました。このような場合には、「選択肢を用意する」という方法で対処を行うのが普通です。でも、小学生のプリント問題で、分数でしか回答できない場合に「選択肢から選ぶ」というのは、あまりに不自然な感じがします。そこで「分数を入力する」ために、数学用ソフトウェアキーボードをJavaScriptで作ってみました。
 分数に加えて、「ルート √ 累乗 23 パイ π」も入力できるようにして、中学校数学のWeb教材に対応するソフトウェアキーボードの作成を目標としました。

サンプルサイト

 今回のサンプルサイトはこちらです。
http://iwate-manabi-net.sakura.ne.jp/math_software_keyboard/

 ソフトウェアキーボードを利用した教材サンプルはこちらです。
http://www1.iwate-ed.jp/tantou/joho/material/g-tablet-m/index.html

MathJax を使って数式を表示

 数式の表示にはMathJaxをJavaScriptライブラリとして利用しました。
https://www.mathjax.org/
 最新版のMathJax3が公開になっていますが、今回は古いバージョンを使用します。WARNINGが表示されますが、気にせず使っていきましょう。
 使い方についてはこちらのページを参照願います。
http://www.eng.niigata-u.ac.jp/~nomoto/download/mathjax.pdf

HTMLソース

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<script type="text/x-mathjax-config">
  MathJax.Hub.Config({ tex2jax: { inlineMath: [['$','$'], ["\\(","\\)"]] } });
</script>
<script type="text/javascript"
   src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML">
</script>
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.min.js"></script>
<script src="js/jquery.ui.touch-punch.js"></script>
<script src="math_software_keyboard.js"></script>
<link rel="stylesheet" href="math_software_keyboard.css">
<title>数学用ソフトウェアキーボード</title>
</head>
<body>
<h1>数学用ソフトウェアキーボード</h1><br>
<div id="setumei">
1 次の計算をしなさい。<br>
    ※ 例: <span class='siki'>\(2\sqrt{3}\)</span> の入力→ 「2」「ルート」「3」
</div><br>
<div style='float:left'>
(1)  <span class='siki'>\(3\sqrt{5}+6\sqrt{5}\)</span><br>
   <span id='buttonShow1' class='buttonShowClass'>クリックして答えを入力</span>
答  <span id='kotae1' class='siki'><font size='3'></font></span>
  <span id='maru1' class='maru'></span>
</div>
    <div id='kaisetu1' class='kaisetu'></div>
<div style='clear: both'></div>
<div id='inputButton1'></div><br><br>
</body>
</html>

CSSソース

math_software_keyboard.css
/* math_software_keyboard.css */
#setumei{
    font-weight: bold;
    color: #060;
}

.buttonShowClass{
    background-color: #cff;
    padding: 10px 10px 10px 10px;
    border-radius:4px;
    border:1px solid #bbd;
    box-shadow:2px 2px 2px 2px rgba(200, 200, 200, 0.4);
    margin: 0px 5px;
    cursor:pointer;
}

.waku{
    user-select: none; /* CSS3 単語を選択させない */
    -moz-user-select: none; /* Firefox */
    -webkit-user-select: none; /* Safari、Chromeなど */
    -ms-user-select: none; /* IE10かららしい */
    -webkit-tap-highlight-color:#fff;
    background-color: #eef;
    padding: 10px 5px 5px 5px;
    border-radius:4px;
    border:1px solid #888;
    box-shadow:2px 2px 2px 2px rgba(200, 200, 200, 0.4);
    margin: 0px 5px;
    width:800px;
}

.bs{
    user-select: none; /* CSS3 単語を選択させない */
    -moz-user-select: none; /* Firefox */
    -webkit-user-select: none; /* Safari、Chromeなど */
    -ms-user-select: none; /* IE10かららしい */
    -webkit-tap-highlight-color:#fff;
    background-color: #ccf;
    padding: 10px 10px 10px 10px;
    border-radius:4px;
    border:1px solid #999;
    box-shadow:2px 2px 2px 2px rgba(200, 200, 200, 0.6);
    margin: 0px 5px;
    cursor:pointer;
    font-weight:bold;
}

.bc{
    user-select: none; /* CSS3 単語を選択させない */
    -moz-user-select: none; /* Firefox */
    -webkit-user-select: none; /* Safari、Chromeなど */
    -ms-user-select: none; /* IE10かららしい */
    -webkit-tap-highlight-color:#fff;
    background-color: #ffa;
    padding: 10px 5px 10px 5px;
    border-radius:4px;
    border:1px solid #aaa;
    box-shadow:2px 2px 2px 2px rgba(200, 200, 200, 0.6);
    margin: 0px 5px;
    cursor:pointer;
}
.maru{
    user-select: none; /* CSS3 単語を選択させない */
    -moz-user-select: none; /* Firefox */
    -webkit-user-select: none; /* Safari、Chromeなど */
    -ms-user-select: none; /* IE10かららしい */
    -webkit-tap-highlight-color:#fff;
    vertical-align: middle;
    color: red;
    opacity:0;
    font-size: 500%;
}
.siki{
    font-size: 130%;
}

.kaisetu{
    user-select: none; /* CSS3 単語を選択させない */
    -moz-user-select: none; /* Firefox */
    -webkit-user-select: none; /* Safari、Chromeなど */
    -ms-user-select: none; /* IE10かららしい */
    -webkit-tap-highlight-color:#fff;
    color: blue;
    font-size: 120%;
}

JavaScriptソース

math_software_keyboard.js
var mondaiMax= 1;  // 問題数
var kaitouNumber=0;
var kaitouText = new Array();
var buttonText = new Array();
for(var i=0; i<= mondaiMax ; i++){           //問題数
    buttonText[i] = [];
    for(var j=0; j<20; j++){
        buttonText[i][j] = '';
    }
}
var buttonCheckText ="";
var buttonMax  = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; // 初期値 0
var kaisetuText = "";

//    入力用のボタン表示
var bData ="";
    bData = bData + "<div id='inputNow' class='waku'><br>";
    //bData = bData + "<br>";
    bData = bData + "<span id='bbb' class='bs'>分母入力</span><span id='bbe' class='bs'>分数おわり</span>";
    bData = bData + "<span id='brs' class='bs'>ルート</span><span id='bre' class='bs'>ルートおわり</span>";
    bData = bData + "<span id='bpi' class='bs'>π</span><span id='bpw' class='bs'>累乗</span>";
    bData = bData + "<span id='bpm' class='bs'>±</span>";
    bData = bData + " <span id='bal' class='bc'>すべて消す</span><br><br><br>";
    bData = bData + "<span id='bt' class='bs'>+</span><span id='bh' class='bs'>-</span><span id='bk' class='bs'>×</span>";
    bData = bData + "<span id='bw' class='bs'>÷</span><span id='bi' class='bs'>=</span>";
    bData = bData + "<span id='bks' class='bs'>(</span><span id='bke' class='bs'>)</span>";
    bData = bData + "<span id='ma' class='bs'>a</span><span id='mb' class='bs'>b</span><span id='mc' class='bs'>c</span>";
    // bData = bData + "<span id='md' class='bs'>d</span>";
    bData = bData + "<span id='mx' class='bs'>χ</span><span id='my' class='bs'>y</span><span id='mz' class='bs'>z</span>";
    bData = bData + "<span id='bba' class='bc'>←1つもどる</span><br><br><br>";
    bData = bData + "<span id='b1' class='bs'>1</span><span id='b2' class='bs'>2</span><span id='b3' class='bs'>3</span>";
    bData = bData + "<span id='b4' class='bs'>4</span><span id='b5' class='bs'>5</span><span id='b6' class='bs'>6</span>";
    bData = bData + "<span id='b7' class='bs'>7</span><span id='b8' class='bs'>8</span><span id='b9' class='bs'>9</span>";
    bData = bData + "<span id='b0' class='bs'>0</span><span id='bd' class='bs'>.</span>";
    bData = bData + "<span id='bhide' class='bc'>閉じる</span> <span id='bcheck' class='bc'>解答する</span><br><br><br>";
    bData = bData + "</div>";

$(function() {  // jQuery を実行 
    $('#inputButton1').hide();  // キーボードの表示を消しておく

    $('#buttonShow1').on('click', function () {
        funcButtonHide();
        kaitouNumber = 1;                              // 問題番号
        $('#inputButton' +kaitouNumber).html(bData);   // キーボードの表示
        $('#inputButton' +kaitouNumber).show('normal');// アニメーション表示
        buttonReload();                                // ボタンを作動させる
        $('#buttonShow' +kaitouNumber).hide();         //「クリックして答えを入力」ボタンを隠す
    });

    function funcKaitou(){
        switch (kaitouNumber){
            case 1:  //問題番号1 の場合
                // 計算式の表示
                kaisetuText =                   ' \\(3\\sqrt{5}+6\\sqrt{5}\\) ';
                kaisetuText = kaisetuText+      '=\\((3+6)\\sqrt{5}\\) ';
                kaisetuText = kaisetuText+      '=\\(9\\sqrt{5}\\)<br>';
                $('#kaisetu' + kaitouNumber).html(kaisetuText);
                // 答え合わせ
                if((kaitouText[kaitouNumber] == '\\({9}\\sqrt{5}\\)')  //9ルート5
                 ||(kaitouText[kaitouNumber] == '\\({9}\\sqrt{5}\\)\\({}\\)')){ //9ルート5 ルートおわり
                    $('#maru' + kaitouNumber ).css('opacity',1);  //正解の場合
                }else{
                    funcBatu();  //不正解の場合
                }
                break;
        };
        MathJax.Hub.Queue(["Typeset",MathJax.Hub,('kaisetu' + kaitouNumber)]);  //数式を表示
        $('#buttonShow'+ kaitouNumber ).hide();         //キーボード表示ボタンを消します
        $('#inputNow').remove();
        $('#inputNow').html("<div id='inputButton" + kaitouNumber + "'></div>");
        $('#inputButton' + kaitouNumber).hide();
        $(('#buttonShow'+ kaitouNumber)).remove();

    };

    function funcBatu(){       //  不正解の場合、バッテンを表示
        $('#maru' + kaitouNumber).text('×');
        $('#maru' + kaitouNumber).css('opacity',1);
        $('#maru' + kaitouNumber).css('color','blue');
    };

    function funcButtonHide() {         //キーボードを消す
        $('#inputNow').remove();
        $('#inputNow').html("<div id='inputButton" + kaitouNumber + "'></div>");
        $('#inputButton' + kaitouNumber).hide();
        $(('#buttonShow'+ kaitouNumber)).show();
    }

    function buttonReload(){ //     数学用ソフトウェアキーボードが押されたとき
        $('#b1' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="1"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b2' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="2"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b3' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="3"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b4' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="4"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b5' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="5"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b6' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="6"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b7' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="7"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b8' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="8"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b9' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="9"; buttonMax[kaitouNumber]++ ; display(); });
        $('#b0' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="0"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bd' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="."; buttonMax[kaitouNumber]++ ; display(); });
        $('#ma' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="a"; buttonMax[kaitouNumber]++ ; display(); });
        $('#mb' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="b"; buttonMax[kaitouNumber]++ ; display(); });
        $('#mc' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="c"; buttonMax[kaitouNumber]++ ; display(); });
        $('#md' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="d"; buttonMax[kaitouNumber]++ ; display(); });
        $('#mx' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="x"; buttonMax[kaitouNumber]++ ; display(); });
        $('#my' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="y"; buttonMax[kaitouNumber]++ ; display(); });
        $('#mz' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="z"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bt' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]=""; buttonMax[kaitouNumber]++ ; display(); });
        $('#bh' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]=""; buttonMax[kaitouNumber]++ ; display(); });
        $('#bk' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="×"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bw' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="÷"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bi' ).on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]=""; buttonMax[kaitouNumber]++ ; display(); });
        $('#bks').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]=""; buttonMax[kaitouNumber]++ ; display(); });
        $('#bke').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]=""; buttonMax[kaitouNumber]++ ; display(); });
        $('#bpi').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="π"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bpm').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="±"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bbb').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="分母入力"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bbe').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="分数おわり"; buttonMax[kaitouNumber]++ ; display(); });
        $('#brs').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="ルートはじめ"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bre').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="ルートおわり"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bpw').on('click', function () { buttonText[kaitouNumber][buttonMax[kaitouNumber]]="累乗"; buttonMax[kaitouNumber]++ ; display(); });
        $('#bba').on('click', function () { buttonMax[kaitouNumber]-- ; if( buttonMax[kaitouNumber] <0 ){ buttonMax[kaitouNumber] =0 }display(); });
        $('#bal').on('click', function () { buttonMax[kaitouNumber] =0; display(); });
        $('#bhide').on('click', function () {funcButtonHide();});
        $('#bcheck').on('click', function () {funcKaitou();});
    };
    buttonReload();    //  ボタンを使えるようにする

    function display() {
        buttonCheckText ="";
        var myText="\\({";           //\を表示するには2回\\
        for ( var k=0; k <buttonMax[kaitouNumber] ;k++){
            switch (buttonText[kaitouNumber][k]){
                case "1": myText = myText + "1" ;break;
                case "2": myText = myText + "2" ;break;
                case "3": myText = myText + "3" ;break;
                case "4": myText = myText + "4" ;break;
                case "5": myText = myText + "5" ;break;
                case "6": myText = myText + "6" ;break;
                case "7": myText = myText + "7" ;break;
                case "8": myText = myText + "8" ;break;
                case "9": myText = myText + "9" ;break;
                case "0": myText = myText + "0" ;break;
                case ".": myText = myText + "." ;break;
                case "a": myText = myText + "a" ;break;
                case "b": myText = myText + "b" ;break;
                case "c": myText = myText + "c" ;break;
                case "d": myText = myText + "d" ;break;
                case "x": myText = myText + "x" ;break;
                case "y": myText = myText + "y" ;break;
                case "z": myText = myText + "z" ;break;
                case "": myText = myText + "+" ;break;
                case "": myText = myText + "-" ;break;
                case "×": myText = myText + "×" ;break;
                case "÷": myText = myText + "÷" ;break;
                case "": myText = myText + "=" ;break;
                case "": myText = myText + "(" ;break;
                case "": myText = myText + ")" ;break;
                case "π": myText = myText + "\\pi" ;break;
                case "±": myText = myText + "\\pm" ;break;
                case "分母入力": myText = myText + "}\\over{" ;break;
                case "分数おわり": myText = myText + "}\\)\\({" ;break;
                case "ルートはじめ": myText = myText + "}\\sqrt{" ;break;
                case "ルートおわり": myText = myText + "}\\)\\({" ;break;
                case "累乗": myText = myText + "\^" ;break;
            };
            buttonCheckText = buttonCheckText +buttonText[kaitouNumber][k];
        };
        if(buttonMax[kaitouNumber] > 0){
            if(buttonText[kaitouNumber][buttonMax[kaitouNumber] -1] == "累乗"){    //累乗入力の時に表示が変になるを防ぐ
                myText = myText + "";
            };
        };
        myText = myText + "}\\)";
        //MathJax表示
        $(("#kotae" + kaitouNumber)).html(myText);
        MathJax.Hub.Queue(["Typeset",MathJax.Hub,("kotae" + kaitouNumber)]);
        kaitouText[kaitouNumber]= myText;
    };
     $('body').bind('updated',function(){display()});
});

非表示にしたいボタン(キー)があるときには、
// 入力用のボタン表示
の下の部分で
// bData = bData + <span id='brs' class='bs'>ルート・・・
 ↑このように//を使ってコメントアウトすると、「ルート」ボタンが非表示になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueソーシャルシェアリングのバージョン指定

vue-social-sharingのバージョン指定

ターミナル
npm i vue-social-sharing@2.4.6

@2.4.6の部分を好きなバージョンに指定するだけ!

ver.3だとSNSアイコンが表示されない事が多いのでこちらのバージョンを指定するといけました!

以上!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flash Advent Calendar 5日目 - クロージャのスコープ問題 -

今日はちょっと小休憩的な感じで書きたいと思います。

FlashのActionScriptをエミュレーションする中で
クロージャーのスコープ問題にぶつかりました。

JavaScriptの実装になれていればすんなり解決していたと思うのですが・・・

ActionScriptサンプル

import flash.display.MovieClip;
import flash.events.Event;

public class Main extends MovieClip
{
    public function Main ()
    {
        super();
        this.addEventListener(Event.ENTER_FRAME, this._onEnterFrame);
    }

    private function _onEnterFrame ()
    {
        trace(this);
    }
}

_onEnterFrameで出力される情報は

[object Main]

つまり、スコープはクラスに向いています。

これが、JavaScriptだと

new Main(); // <= [Window]が出力される

と、globalのwindowクラスに向いてしまいます。

当時、ここが全然理解できず結構苦戦しました。。。
JavaScript - MDN - Mozillaを徘徊してbindを見つけました。

bind() メソッドは、呼び出された際に this キーワードに指定された値が設定される新しい関数を生成します。
この値は新しい関数が呼び出されたとき、一連の引数の前に置かれます。

ActionScriptをエミュレートするには、JavaScript側でthis._onEnterFrame関数に対してbind(this)をコールしてスコープを確約させる必要があります。

this.addEventListener(Event.ENTER_FRAME, this._onEnterFrame.bind(this));

解決してしまえば、それほど大きな問題ではないのですが
当時は結構苦戦しました。。。

また、この後、ActionScriptのaddEventListenerの仕様で
同一のスコープで、同一の関数は追加しない。っという仕様にぶつかったのですが
きっとJavaScriptだけで実装していれば関わらない仕様だと思うのでサラッと書きます。

ドキュメントにあるようにbind関数をコールすると新しい関数が生成されます。

const method = this._onEnterFrame.bind(this);
console.log(method === this._onEnterFrame); // false

既存の関数から新たにスコープが確約された関数が生成されます。
なので、元の関数と生成された関数は別物として扱われます。

こういった、ActionScriptとJavaScriptの挙動の違いを詰めていく調査と検証に大きな時間がかかってしまいました。

ただ、その副産物でJavaScript特有の仕様や機能など知る事もできたので
言語を理解するという点ではとても勉強になり
今でも仕事で役に立っているので結果オーライですw

明日は、WebWorkerに関して書こうと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flash Advent Calendar 6日目 - クロージャのスコープ問題 -

今日はちょっと小休憩的な感じで書きたいと思います。

FlashのActionScriptをエミュレーションする中で
クロージャーのスコープ問題にぶつかりました。

JavaScriptの実装になれていればすんなり解決していたと思うのですが・・・

ActionScriptサンプル

import flash.display.MovieClip;
import flash.events.Event;

public class Main extends MovieClip
{
    public function Main ()
    {
        super();
        this.addEventListener(Event.ENTER_FRAME, this._onEnterFrame);
    }

    private function _onEnterFrame ()
    {
        trace(this);
    }
}

_onEnterFrameで出力される情報は

[object Main]

つまり、スコープはクラスに向いています。

これが、JavaScriptだと

new Main(); // <= [Window]が出力される

と、globalのwindowクラスに向いてしまいます。

当時、ここが全然理解できず結構苦戦しました。。。
JavaScript - MDN - Mozillaを徘徊してbindを見つけました。

bind() メソッドは、呼び出された際に this キーワードに指定された値が設定される新しい関数を生成します。
この値は新しい関数が呼び出されたとき、一連の引数の前に置かれます。

ActionScriptをエミュレートするには、JavaScript側でthis._onEnterFrame関数に対してbind(this)をコールしてスコープを確約させる必要があります。

this.addEventListener(Event.ENTER_FRAME, this._onEnterFrame.bind(this));

解決してしまえば、それほど大きな問題ではないのですが
当時は結構苦戦しました。。。

また、この後、ActionScriptのaddEventListenerの仕様で
同一のスコープで、同一の関数は追加しない。っという仕様にぶつかったのですが
きっとJavaScriptだけで実装していれば関わらない仕様だと思うのでサラッと書きます。

ドキュメントにあるようにbind関数をコールすると新しい関数が生成されます。

const method = this._onEnterFrame.bind(this);
console.log(method === this._onEnterFrame); // false

既存の関数から新たにスコープが確約された関数が生成されます。
なので、元の関数と生成された関数は別物として扱われます。

こういった、ActionScriptとJavaScriptの挙動の違いを詰めていく調査と検証に大きな時間がかかってしまいました。

ただ、その副産物でJavaScript特有の仕様や機能など知る事もできたので
言語を理解するという点ではとても勉強になり
今でも仕事で役に立っているので結果オーライですw

明日は、WebWorkerに関して書こうと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Javascript] [TypeScript] 実装で見返す用のまとめ

はじめに

普段はC#を使っている者が、JavaScriptとTypeScriptを学んで今後実装する上で気をつけたいことをまとめました。

以下の技術同人誌から学んだことを中心に追加で調べたことなどをまとめています。
りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅰ. 言語・環境編】 - くるみ割り書房 ft. React - BOOTH

JavaScript

もともとはブラウザ上で動く言語だったが、Node.jsなどを使えばサーバーサイドでも動くようになった。
ReactはJavaScriptのライブラリにあたる。
JSX(独自のタグ技術)は、JavaScriptの拡張構文であり、コンパイルするとJavaScriptに変換される。
JavaScript | MDN

ブラウザ上で試したい時

アカウント登録なしで手軽に試せる。
JSFiddle - Code Playground

他にもいろいろあるみたいです。
ブラウザ上でJavaScriptの実行ができるサービスまとめ - Qiita

関数

// 関数宣言文
// 同じ関数名だと再宣言出来てしまうこと、宣言する前に代入可能であることから非推奨
function multiply(x, y) {
   return x * y;
} // ここにセミコロンは必要ありません

// Functionコンストラクタで定義した関数
// 常にグローバルスコープになることから非推奨
var multiply = new Function('x', 'y', 'return x * y');

// 関数式
// 再宣言や宣言する前に代入ができないようにconstにしておくこと
const multiply = function(x, y) {
   return x * y;
};

// アロー関数式
// ES2015で追加。
// 関数外のスコープのthisが使われるためクラス構文の時に分かりやすい。推奨
const multiply = (x, y) => {
   return x * y;
};

関数 - JavaScript | MDN 参照。

残余引数(Rest Parameters)

不定数の引数を配列として表す。
Array インスタンスなので、sort, map, forEach, pop などのメソッドを直接適用できる。

function myFun(a,  b, ...manyMoreArgs) {
  console.log("a", a)
  console.log("b", b)
  console.log("manyMoreArgs", manyMoreArgs)
}

myFun("one", "two", "three", "four", "five", "six")
// console output:
// a, one
// b, two
// manyMoreArgs, [three, four, five, six]

残余引数 - JavaScript | MDN

スプレット構文

引数や配列リテラルの要素などで配列式や文字列などを展開する。
オブジェクトリテラルを展開することもできる。

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];
const obj = { red: 1, blue: 2, yellow: 3 };
const obj2 = { ...obj, green: 4 };

console.log(sum(...numbers));
console.log(obj2);
// console output:
// 6
// Object { red: 1, blue: 2, yellow: 3, green: 4 }

スプレッド構文 - JavaScript | MDN

プロトタイプベース

どのオブジェクトもプロトタイプと呼ばれる、他のオブジェクトへの内部的な繋がりを持っています。
そのプロトタイプオブジェクトも自身のプロトタイプを持っており、あるオブジェクトのプロトタイプが null に到達するまでそれが続きます。
null は、プロトタイプを持たず、プロトタイプチェーンの最終リンクとなります。

継承とプロトタイプチェーン - JavaScript | MDN から引用。

Javaなどのクラスベースと違い、実態のあるオブジェクトを継承していることになる。
コンストラクタ関数は、プロトタイプオブジェクトを継承して、新しくオブジェクトインスタンスを生成するための関数になる。

オブジェクトプロパティのショートハンド

ES6では、オブジェクトリテラルの定義するときにオブジェクトのプロパティ値の記載を省略できる。
プロパティ名がキーと一致しているときのみショートハンドが使える。

// 通常
const obj = { x:x, y:y };

// ショートハンド
const obj = { x, y };

あなたが知らないJavaScriptの便利すぎるショートハンド19選 – WPJ

分割代入

配列から値を取り出す、またはオブジェクトからプロパティを取り出して別の変数に代入ができるようになる。
Reactの開発でも良く見かける。

const user = {
    id: 42,
    is_verified: true
};

const {id, is_verified} = user;

console.log(id); // 42
console.log(is_verified); // true 

分割代入 - JavaScript | MDN

this

関数呼び出しされた時はアクセス演算子.の前のオブジェクトが、new演算子をつけた時は新規に生成されたオブジェクトがthisになる。
関数内にないトップレベルのコードや関数をオブジェクトのメソッドでなくオブジェクトとして呼ぶと、thisはグローバルオブジェクトになる。

JavaScript の this を理解する多分一番分かりやすい説明 - Qiita

?globalThis?と?global?と?this? - Qiita

Strict モード

ES5で追加。
ファイルの先頭行や関数の最初に 'use strict' を記述することで有効になる。
任意のオブジェクトのコンテキストがない場合、this が undefined になる。
関数内のthisはグローバルオブジェクトではなくundefinedになる。
Strict モード - JavaScript | MDN

クロージャ

クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。
関数の中で定義された関数をクロージャ、外側の関数をエンクロージャと呼ぶ。

クロージャ - JavaScript | MDN

function makeFunc() {
  const name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

const myFunc = makeFunc();
myFunc();

myFuncを定義時にエンクロージャmakeFuncによってクロージャdisplayNameのインスタンスの参照が返されている。
そのため、変数nameがGCに回収されず残っている。

TypeScript

AltJSであり、ビルド時にはJavaScriptに変換される。独自のコンパイラを所持している。
TypeScriptは、JavaScriptで型定義、インターフェイス、クラス、null/undefined safeに出来る・使えるようになるなどJSの上位互換といわれている。
特に型定義によって、実行前のビルド時にエラーやバグを検知することが出来ることが大きい。
MS産。
TypeScript: Typed JavaScript at Any Scale.

Introduction - TypeScript Deep Dive

Reactでは、公式で大きな規模での開発時はTypeScriptの使用を薦めている。
静的型チェック – React

ブラウザ上で試したい時

アカウント登録なしで手軽に試せる。
TypeScript Playground

npmパッケージをインストールせずrequireして使える。
Runkit + npm

型について

きちんと型のnull安全性を保障するには、コンパイラオプション strictNullChecks を tsconfig.json ファイルに設定する必要がある。
他にも便利なオプションを含んだstrictオプションを有効にすると便利。

型の互換性は、構造的サブタイピング(Structural Subtyping)に基づいており、型の構造が合致していればエラーにならない。

interface Named {
  name: string;
}

class Person {
  name: string;
}

let p: Named;
// OK, because of structural typing
p = new Person();

TypeScript: Handbook - Type Compatibility

TypeScriptの型入門 - Qiita

クラス

クラスを定義すると、「クラスインスタンスのインターフェース型宣言」と「コンストラクタ関数の宣言」が同時に実行される。
TypeScriptでインポート/エクスポートするときもデフォルトでは型とオブジェクトの両方を同時にインポート/エクスポートする。
TypeScript: Handbook - Classes

クラスの型は、インスタンスではなく下記の型エイリアスで定義する。
型エイリアスは、任意の型に別名を与えて再利用できるようになっている。
インターフェースは、同じ名前のインターフェースに新しいプロパティがある場合、型定義が追加されてしまうという問題がある。

type Tree<T> = {
  value: T;
  left?: Tree<T>;
  right?: Tree<T>;
};

TypeScript: Handbook - Advanced Types

TypeScriptのInterfaceとTypeの比較 - Qiita

ユニオン型

演算子|を挟んで型を並べるといずれかの型が適用される複合的な型が定義できる。

function padLeft(value: string, padding: string | number) {
  // ...
}

const str = padLeft("Hello world", '1'); // OK
const num = padLeft("Hello world", 1); // OK

TypeScript: Handbook - Unions and Intersection Types

リテラル型

JavaScriptのプリミティブのように任意の値(文字列または数値、boolean)以外代入させない型。
ユニオン型と組み合わせ演算子|を挟んで型を並べるといずれかの型が適用される複合的な型が定義できる。
enumよりコンパイル後のコードが短いためこちらが使われることが多い。

type CardinalDirection =
    | "North"
    | "East"
    | "South"
    | "West";

function move(distance: number, direction: CardinalDirection) {
    // ...
}

move(1,"North"); // Okay
move(1,"Nurth"); // Error!

リテラル型 - TypeScript Deep Dive 日本語版

Const アサーション

定数に型注釈を付与してくれる。

const Colors = {
  red: "RED",
  blue: "BLUE",
  green: "GREEN"
} as const;

TypeScript: Handbook - TypeScript 3.4

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む