20201018のJavaScriptに関する記事は22件です。

Jest の beforeEach() / test() と Promise と setTimeout() の関係

要約

  • beforeEach() は常に Promise が (then() まで含め) 全部満たされてから test() に移る。
  • setTimeout() は呼ばれない。

実行例

test.js
describe('async test', () => {
  let called;

  function promise() {
    console.log(called = 'function');

    setTimeout(() => {
      console.log(called = 'setTimeout()');
    }, 0);

    return new Promise(resolve => {
      console.log(called = 'Promise()');
      resolve();
    }).then(() => {
      console.log(called = 'then()');
    });
  }

  describe('test() only', () => {
    test('without await', () => {
      promise();
      expect(called).toBe('Promise()');
    });
  });

  describe('test() only', () => {
    test('with await', async () => {
      await promise();
      expect(called).toBe('then()');
    });
  });

  describe('beforeEach() and test()', () => {
    beforeEach(() => {
      promise();
    });

    test('without await', () => {
      expect(called).toBe('then()');
    });
  });

  describe('beforeEach() and test()', () => {
    beforeEach(async () => {
      await promise();
    });

    test('with await', () => {
      expect(called).toBe('then()');
    });
  });
});
yarn run v1.22.10
$ jest --verbose
 PASS  ./test.js
  async test
    test() only
      ✓ without await (12 ms)
      ✓ with await (2 ms)
    beforeEach() and test()
      ✓ without await (1 ms)
      ✓ with await (7 ms)

  console.log
    function

      at promise (test.js:5:13)

  console.log
    Promise()

      at Promise.then.console.log.called (test.js:12:15)

  console.log
    then()

      at test.js:15:15

  console.log
    function

      at promise (test.js:5:13)

  console.log
    Promise()

      at Promise.then.console.log.called (test.js:12:15)

  console.log
    then()

      at test.js:15:15

  console.log
    function

      at promise (test.js:5:13)

  console.log
    Promise()

      at Promise.then.console.log.called (test.js:12:15)

  console.log
    then()

      at test.js:15:15

  console.log
    function

      at promise (test.js:5:13)

  console.log
    Promise()

      at Promise.then.console.log.called (test.js:12:15)

  console.log
    then()

      at test.js:15:15

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.979 s, estimated 1 s
Ran all test suites.
✨  Done in 1.83s.

参考

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

JavaScript 経験者が Java を学習する際の入門前メモ

知り合いの JavaScript 経験者が、Java を学びたいと言ってましたので、簡単な入門前メモをまとめてみました。

想定している読者

Web ブラウザで動作する、もしくは NodeJS 環境で動作する JavaScript コードを学んだことがある、もしくは書いたことがある方を想定しています。

きちんと学ぶにはJava入門書を読んだり、良質なコードを多く参照して真似したりすることが有効だと思います。ただ現時点で JavaScript の知識があるのであれば、Java との差分を確認してから学んだほうが理解が早まるのではないか?と思い、このメモをまとめてみました。

このメモが簡単すぎる、冗長すぎると感じる方は、素晴らしいまとめサイトである Java コード入門 をお勧めします。

開発環境

いまの時代、何らかの統合開発環境を利用することを想定しています。とりあえず想定しているのは、昔ながらの Eclipse (統合開発環境) です。入門本や記事は多くありますが、例えば以下のような記事はどうでしょう。

ただし事前に自分で学習する場合はともかく、仕事用のコードを書く方は、仕事先の環境を聞いて開発環境や、Java のバージョンなど合わせたほうが良いでしょう。

より手軽に試してみる

ちょっと Java コードを試したいだけであれば、オンライン上のサービスを利用するのもお手軽で良いです。

また Docker 環境があれば、Java 環境を含んだ Docker Image を実行することで、Java 開発環境をインストールせずに試すこともできますね。私も今年 docker-lambda を使うネタ とか VSCode + Codewindネタ (NodeJSですがJavaも可) とか投稿しているので、興味があれば参照してみてください。

ポイントとなる差分

コンパイラ言語

Java はコンパイラ言語、JavaScript はスクリプト言語とされています。これは確かにそうなのですが、以下の理由であまり気にしなくても良いとおもいます。

  • JavaScript は js ファイルを直接実行する実装が多い
  • それに対して Java は class ファイルにいったん変換(コンパイル)してから実行する実装が多く、それにひと間かかる
  • ただし今どきの統合開発環境はこの「ひと手間」を勝手に実行してくれるので、開発者はあまり意識する必要はない

Java の場合は *.class という中間ファイルが実行時に自動で作成される、ぐらいの知識で十分だと思います。

更に最近の Java では *.java ファイルをそのまま実行できる (ように裏でこっそりコンパイルしてくれる) ので、もはやコンパイル言語かどうかも怪しいかも?です。コンパイル言語という出自から派生した、次の静的型付けだけ理解できれば大丈夫。

静的型付けと動的型付け

Java は静的な型付け言語で、動的な JavaScript とは異なります。型についてチェックが厳しいので、それを意識して書かないとエラーや警告が頻発して驚かされるでしょう。

TypeScript のような静的型付けのできる JavaScript 系言語の経験者は、このセクションはスキップしてOK。

動的型付けのほうが気楽に書けるので、個人的には好きです。が、仕事でコードを管理する場合など、大規模だったり、実装とテストを気楽に繰り返せない状況では、静的型付け言語のほうが結果として楽な気がしますね。

以下の記事がお勧めです。

  • Java、C、C#、VB.netなどは「静的な型付け」
  • JavaScript、Python、Rubyなどは「動的な型付け」

「静的~」は構造をコンパイル時にチェックする。「動的~」は構造を実行時にチェックする。

静的型付けのメリットはなんと言ってもコンパイルエラーが出ること。これに付きます。
これによって実行して初めてわかる問題をかなり減らすことができます。C言語のようにしょぼい型システムだと嬉しさなんてありませんが、JavaやScala, OCaml, Haskellなどだとかなり有用です。

なお、紹介した記事を全部理解する必要はなく、ざっくり読んで「ふーん」とおおまかに感じるだけで良いとおもいます。学習が進んだ後で、もう一度読み返すことを前提として紹介しています。

オブジェクト指向

JavaScript にも オブジェクト指向 の概念は あります が、必須の知識ではなく、実はあまり意識しなくてもコードを書けたりします。

それに対して Java ではオブジェクト指向は根っことなる部分ですし、より複雑な仕組みがあります。

とりあえず入り口としては、継承、インターフェイス ぐらいを理解しておけば良さそうにおもわれます。特にインターフェイスは JavaScript にないもので、かつ、地味なところで、わりとお世話になっていたりします。

インターフェイスとは、ある特定の機能を実現するために必要なメソッドのシグニチャ(名前や引数、戻り値の型)だけを定義したものです。処理そのものは持ちません。

簡単に言えば、クラス定義から実行されるコード部分を省いたものです。つまりはそのクラスの外部仕様を定義するものの、実際の処理内容は記載されていません。C言語であればヘッダファイル(*.h)に相当するものです。

更に Java にはクラスとインターフェイスの中間のような抽象クラスもあり、「部分的にコードを省いた」クラスという感じです。

インターフェスや抽象クラスはそのままでは利用できず、継承(インターフェイスは実装)して、足りない処理コードを補ったクラスを定義する必要があります。と言うと難しそうですが、実は最初は、それほど気にしなくても大丈夫です。

例えば Eclipse では、クラス新規作成時に、以下のようにメソッドの自動生成がデフォルトでチェック済みなので、コード実装がされていない関数(メソッド)があれば、自動的に定義部分も作成しておいてくれます。間違って消したり、実装し忘れがあっても、警告やエラーがしっかり出ますので、すぐわかります。
image.png

メソッドのオーバーロード

Java コードを読んでいると、同じ名前の関数(メソッド)が幾つも定義されていて、どれを呼んでいるのか迷子になることがあります。これは メソッドのオーバーロード によるものです。

Javaでは、「同じ名前で、引数の型、並びだけが異なる」メソッドを複数定義することもできます。これをメソッドのオーバーロードと言います。

JavaScript だと、単に関数名だけみて探せば良いのですが。Java の場合は同じ名前の関数(メソッド)が複数定義されている場合、それらのなかから引数の数や型(継承関係も含む)が一致するものを探さないといけません。

まあ統合開発環境だと、このあたりもチェックしたうえで参照元に移動できますので、それほどは困らないのですが。例えば Eclipse だと、関数を呼び出しているところで F3 キーを押すと、それを定義したコードに移動できます。

文字列の比較

Java 初心者が最初にひっかかる点が「文字コードの比較」ミスです。以下のような例が有名ですね。

    String str1 = "123";
    String str2 = "123";
    String str3 = "12" + "3";
    String str4 = "12";
    str4 += "3";

    System.out.println(str1 == str2); // true (1
    System.out.println(str1 == str3); // true (2
    System.out.println(str1 == str4); // false (3
    System.out.println(str1.equals(str4)); // true (4

JavaScript だと文字列の == 演算は値の比較になるので、上記は全部 true になります。

Java だと基本的に == は「同じオブジェクトかどうか」の判定になり、値の比較には equals メソッドを使用します。

ここでやっかいなのが、(1 と (2 は多くの環境で true の結果になることで、バグ発生の原因となり得ます。これは Java コンパイル時の最適化による影響です。

Java で文字列は immutable (イミュータブル/不変)なもので、プログラム実行中にその値は変わりません。であれば "123" という同じ値の文字列であれば、内部的に「あれ、これ真面目に2個用意しなくても、1個用意して使いまわせばいいよね?」という最適化(手抜き)をします。その結果 (1 が true になります。

更に最近の Java コンパイラは賢いので、"12" + "3" という式がコード中に含まれると「あれ、これ結局 "123" という文字列を使いたいんだよね?」と察してしまい、勝手に式を "123" という結果に置き換えてしまいます。なのでその結果、(1 と同じ理由で (2 も true になります。

なおこの結果は Java8 でのコンパイル結果です。その後のバージョンのもっと賢い Java コンパイラは (3 も true になる最適化をしてくれるかもしれませんねw

というわけで、Java においては値の比較は equals という関数を使うこと、お忘れなく!

また JavaScript にもある概念ですが、Java の ラッパークラス についても、一応は確認しておくと良いでしょう。

言語の拡張について

Java 言語は古い、化石のような存在である、といった印象をもつ方も居ると思います。Java は最新の言語仕様をわりと積極的に取り入れており、特に、JavaScript にどんどん影響されてきています。以下、個人的な感想も含みます。

JavaScript は Java の影響も強く受けているので、Java の子供みたいなものです。そして Java は JavaScript の良いところを順に実装してきています。順に機能が追加されているため Java のバージョンには注意が必要で、詳細は Javaバージョン履歴 を参照してください。

ただし Java は過去との互換性も重視しており、抜本的な構造改革は難しいので、かなり強引な実装になっている部分もあります。新機能を単に使う分には良いのですが、その実装部分に踏み込むとかなり難解な部分もあるので、細かな部分が気になっても、探求は学習が進んだあとに取っておくのをお勧めします。

実装の工夫例

Java にも 無名クラス があり、例えば以下のように利用できますが、

Test02
import java.util.*;

public class Test02 {
    public static void main(String[] args) {
        new Thread(new Runnable() { // ココ
            public void run(){
                System.out.print("Runnable.run");
            }
        }).start();
    }
}

過去との互換性を維持するため、つまりは無名クラスなど知らない実行環境のため、コンパイル時には以下のような class ファイルが自動生成されています。コード上は無名クラスとして記述されていますが、実際には内部で名前(今回は Test02$1)が付与されているわけです。
image.png
※ 私も最初、この謎の class ファイル群はなんだろう?と不思議に思っていました

なお余談ですが JavaSE 8 からは新しい記述の ラムダ式 が使え、より JavaScript っぽい表記ができます。

Test02
    List<String> list = new ArrayList<>(Arrays.asList("aa", "bb"));
    list.replaceAll(str -> str.toUpperCase());
    System.out.println(list); // [AA, BB]

まだ馴染みの薄い拡張

上記のとおり Java 言語は拡張されてきていますが、まだ馴染みが薄いといいますか、わりと先進的な Java 開発の現場でないと見かけない要素もあります。

さきほどのラムダ式もそうですが、JavaScript では普通の書き方でも、Java 言語のなかで使用すると違和感が出てくることもあるでしょう。バージョンの関係で使えたとしても、です。既存のコードを読み、また先輩の開発者に聞いて、雰囲気をみて使いましょう。郷に入っては郷に従え、です。

個人的には ジェネリックス はもう普通に使われているケースが多く、使っていないのは、ものすごく古いコードだけだと思います。

アノテーション はかなり一般的になったと感じますが、独自アノテーションを定義するまで活用する例はまだ少ないと感じます。

Stream API は JavaScript だと多用する書き方ですが、Java のコードではまだあまり積極的に利用されていないように感じます。まあこれは、言語というよりライブラリ設計の話題な気もしますが…

※ これらは案件、現場によって大きく異なるので、繰り返しますがあくまで個人の感想です

ライブラリは大事

どの開発言語でもそうですが、言語ごとの差異はそれほど大きくなく、どちらかというと習得に時間がかかるのはライブラリの把握と上手な利用方法です。

Java は標準ライブラリがなかなか豊富です。まずか以下のような公式の API 仕様を参照してみましょう。とはいえ全部を読むのは難しいため、クラス利用時に詳細を確認しつつ、少しずつ理解の幅を広げていくのが良いでしょう。

java.lang が標準のライブラリで、設定しなくても最初から、自動的に読み込まれます。もし、ちゃんと読んでいくのであれば、最初はここからかな?

JavaScript の場合、NodeJS なら npm サイト で探せば、必要な機能はたいてい見つかるものです。Java の場合はまず Maven Repository を探すのが良いでしょう。各バージョンのコンパイル済みライブラリ(jar)ファイルの入手もできます。

なお NodeJS の npm はダウンロードと設定を同時に実行してくれますが、Java の場合は Java 実行環境から見つかるようちゃんと配慮してあげる必要があります。コマンドラインで実行している場合はクラスパスを設定してあげるか、-cp もしくは -classpath オプションで配置した場所を明示する必要があります。Eclipse の場合はプロジェクトの Java Build Path 設定を確認して、見当たらなければ追加してあげましょう。
image.png
JUnit など有名どころは Eclipse 環境で既に用意されている場合もあるので、まず先に下の Add Library ボタンを押してみるのも良いかもしれません。
image.png

さあ学習を始めましょう

と、最初に気になりそうな点をざっと書いてみました。あとは実際に Java の学習を始めてください。個人的なお勧めは、やはり Java コード入門 サイトです。

プロググラミング自体がまだ不慣れで、より優しい入門書を読みたい場合には スッキリわかるJava入門 第3版 スッキリわかるシリーズ など良いかもしれません。

逆に自信のある方は上記の Web サイトなどで理解した後、資格取得も視野にいれ、バージョンにあわせて以下のような書籍で学習するのも良いかもしれません。

Enjoy!

以上、JavaScript 経験者へ、Java を学ぶ際に知っておいたほうが良さそうなことをまとめてみました。これから Java 言語に親しんでいく際の、敷居が少しでも下がったのであれば嬉しいです。

逆に、よけい混乱させてしまったのであれば、すみません。文章は読み飛ばし、ご紹介した各種リンク先だけでも、参照いただければと思います!

私自身は Java -> JavaScript と学んだ人なので、見落としなど多いとおもいます。記述もいいかげんだったり、まだ十分ではないと思いますが、気がついた点があったら修正・追記していきたいとおもいます。

それではまた!

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

x-means法のJavaScriptによる実装

はじめに

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

今回はx-means法の実装について解説します。

デモはこちらから。(TaskをClusteringにして、ModelのX-Meansを選択)
実際のコードはxmeans.jsにあります。

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

概説

アルゴリズムなどについては既に良い記事がありますので、こちらも参考にしてください。

処理の流れ

fit関数により分割処理を行います。

  1. 初期分割状態の生成
    • 一回目の呼び出しの場合は、k-means法により初期クラスタ数に分割(_split_cluster)し、各クラスタ情報を取得します
    • 二回目以降の場合は、自分自身の状態から各クラスタ情報を取得します
  2. 存在する各クラスタに対してk-means法による分割(_split_cluster)を実施します
    • 分割後のBICが小さくなった場合は、その分割を採用します
    • 逆に大きくなった場合は、その分割は行いません
  3. 指定されただけ、あるいは分割できなくなるまで2.を繰り返します

各クラスタ情報は以下のデータで、BIC計算のために使用するなどします。

  • size : データ数
  • cols : 特徴量数
  • data : データ(size×colsなる配列の配列)
  • cov : 分散共分散行列(cols×colsなる行列)
  • centroid : セントロイド
  • llh : 対数尤度
  • bic : BIC

この情報を初回呼び出し時およびクラスタ分割時に取得(_create_clusters)しています。

また内部で使用するk-means法の処理は、先日投稿した記事で定義したKMeansModelで実行します。

行列演算

各種行列演算は手作り数学ライブラリmath.jsの中で定義してあるMatrixで実行します。

コード中で使用している処理はそれぞれ以下の通りです。

  • プロパティ
    • t : 転置行列を返す
    • value : 内部で持っている一次元配列を返す
    • rows : 行数を返す
    • cols : 列数を返す
  • 関数
    • cov : 分散共分散行列を返す
    • det : 行列式を返す
    • dot : 行列積を返す
    • mean : 平均値を返す
    • row : 行ベクトルを(1行の行列として)返す
    • sub : 減算する(inplace処理)

正規分布の累積分布関数

BICの計算において正規分布の累積分布関数が必要になります。

このサイトによると、標準正規分布$f(x)$についてその累積分布関数$F(x)$は以下の近似式が知られているそうです。

f(x) = \frac{1}{\sqrt{2 \pi}} e^{-\frac{x^2}{2}}
\\
のとき
\\
F(x) = \int_{-\infty}^x f(x) dx \simeq \frac{1}{1 + e^{-1.7 x}}

実装では、この近似式を用いて正規分布の累積分布関数を計算しています。

コード

class XMeans {
    // https://qiita.com/deaikei/items/8615362d320c76e2ce0b
    // https://www.jstage.jst.go.jp/article/jappstat1971/29/3/29_3_141/_pdf
    constructor() {
        this._centroids = [];
        this._init_k = 2;
    }

    get centroids() {
        return this._centroids;
    }

    get size() {
        return this._centroids.length;
    }

    _distance(a, b) {
        return Math.sqrt(a.reduce((acc, v, i) => acc + (v - b[i]) ** 2, 0));
    }

    clear() {
        this._centroids = [];
    }

    fit(datas, iterations = -1) {
        let clusters = null;
        if (this._centroids.length === 0) {
            clusters = this._split_cluster(datas, this._init_k);
            iterations--
        } else {
            clusters = this._create_clusters(this, datas);
        }
        const centers = [];

        while (clusters.length > 0 && (iterations < 0 || iterations-- > 0)) {
            const new_clusters = [];
            while (clusters.length > 0) {
                const c = clusters.shift();
                if (c.size <= 3) {
                    centers.push(c.centroid)
                    continue
                }
                const [c1, c2] = this._split_cluster(c.data);
                const beta = Math.sqrt(c1.centroid.reduce((s, v, i) => s + (v - c2.centroid[i]) ** 2, 0) / (c1.cov.det() + c2.cov.det()));
                // http://marui.hatenablog.com/entry/20110516/1305520406
                const norm_cdf = 1 / (1 + Math.exp(-1.7 * beta))
                const alpha = 0.5 / norm_cdf

                const df = c.cols * (c.cols + 3) / 2
                const bic = -2 * (c.size * Math.log(alpha) + c1.llh + c2.llh) + 2 * df * Math.log(c.size);

                if (bic < c.bic) {
                    new_clusters.push(c1, c2)
                } else {
                    centers.push(c.centroid)
                }
            }
            clusters = new_clusters;
        }
        if (clusters.length > 0) {
            centers.push(...clusters.map(c => c.centroid))
        }
        this._centroids = centers;
    }

    _split_cluster(datas, k = 2) {
        const kmeans = new KMeansModel();
        for (let i = 0; i < k; i++) {
            kmeans.add(datas);
        }
        while (kmeans.fit(datas) > 0);
        return this._create_clusters(kmeans, datas);
    }

    _create_clusters(model, datas) {
        const k = model.size;
        const p = model.predict(datas);
        const ds = [];
        for (let i = 0; i < k; ds[i++] = []);
        datas.forEach((d, i) => ds[p[i]].push(d));
        const clusters = [];
        for (let i = 0; i < k; i++) {
            const mat = Matrix.fromArray(ds[i]);
            const cov = mat.cov();
            const invcov = cov.inv()
            const mean = mat.mean(0);
            const cc = Math.log(1 / Math.sqrt((2 * Math.PI) ** mat.cols * cov.det()))
            let llh = cc * mat.rows;
            for (let j = 0; j < mat.rows; j++) {
                const r = mat.row(j);
                r.sub(mean);
                llh -= r.dot(invcov).dot(r.t).value[0] / 2
            }
            const df = mat.cols * (mat.cols + 3) / 2
            clusters[i] = {
                size: ds[i].length,
                cols: mat.cols,
                data: ds[i],
                cov: cov,
                centroid: model.centroids[i],
                llh: llh,
                bic: -2 * llh + df * Math.log(ds[i].length)
            }
        }
        return clusters;
    }

    predict(datas) {
        if (this._centroids.length == 0) {
            return;
        }
        return datas.map(value => {
            return argmin(this._centroids, v => this._distance(value, v));
        });
    }
}

さいごに

コードを読み直していると、関数名にキャメルケースとスネークケースが混ざっているのがかなり気になります。
自分で書いておいてあれですが。

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

[ネタ] アプリ版ExcelでもTypeScript使いたいメモ

概要

いよいよOffice ScriptsとしてOfficeでTypeScript(Javascript)が使えるようになったのですが、残念ながらWeb版限定でした…。

Web版ExcelでTypeScriptベースの「Office Scripts」提供開始。ユーザー操作を自動で記録可能

というわけで、アプリ版ExcelでもTypeScriptが使えるように試行錯誤してみました。

単純にTypeScript(Javascript)側からExcelを動かすのはActiveXさえ扱えれば簡単で、ググれば使えそうなサンプルがそれなりに出てくる状態なので、

逆に最小限の労力でExcel(VBA)側からTypeScriptのコードを動かせるようにもしてみたい。

新しい仕様のJSを使えるようにする

今回の構想においてJavascript実行環境はActiveXが簡単に使えてユーザー側で追加のインストールが不要なWindowsScriptHost(WSH)を使うのですが、

何もしなければ相当に古いJavascript仕様相当であるJScript環境で動いてしまうのでさすがに現実的ではない。

ただ、WSHはオプションとしてスクリプトエンジンをProgIDやCLSIDで指定することができるようになっており、

Windows10ならEdge(Chromiumじゃないほう)のES6(2015)相当のJavascriptも使えるようになっています。

WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )

いろいろ試してみましたが、今のところsubプロシージャや関数が呼ばれた都度wscript.exeを実行してJavascriptコードを動かすのが確実そうです。

他にVBAからJavascriptを使う方法としてScriptControlを使う方法やHTMLDocumentを使う方法なんかがありますが、いずれもEdge相当のJavascriptは使えないようです。

WSH側からExcel側(起動済み)のオブジェクトを取得

WSH(wscript.exe)を実行する方式にする場合、そのままではExcel関係のActiveXオブジェクトをWSH側に渡す方法が無いのでどうにかする必要があるのですが、

GetObject(,"Excel.Application") では既に別のExcelが動いていたとしても新しいExcelウインドウが開いてしまってうまくいきません。

しかし、GetObjectにはActiveXのクラス名の他にファイルのフルパスも指定できるのですが、既に開いているエクセルファイルの場合は新しく開かずに既に開いているファイルのWorkbookオブジェクトが取得できる仕組みになっているようです。

参考: GetObject(, "Excel~")だけで無く、GetObject(ファイル名)も活用しよう

つまり、呼び出すExcel側のExcelファイルのフルパスさえわかればGetObjectで新しく(二重に)Excelを起動することなくExcel(VBA)側のWorkbookオブジェクトが取れるということです。

VBAから自身のExcelファイルのフルパスは ThisWorkbook.FullName で簡単に取得できるので、

これをコマンドラインオプションとして渡せば実質Excel(VBA)側のWorkbookオブジェクトをWSH側に渡すことができるようになります。

ちなみに (Workbookオブジェクト).Application で親となるApplicationオブジェクトが取得できます。

対象のExcelファイルが開いていない場合は新しく開いたうえでそのWorkbookオブジェクトを取得できるので、工夫すれば同じコードでExcelを開いていない状態でWSH側からスクリプトを動かすということも可能になります。

データ保存領域としてカスタムドキュメントプロパティを使う

WSHを都度起動する方式にした以上は終了するとWSH側の変数はすべて消えてしまうので、プロシージャや関数をまたがってデータをやり取りしたい場合はどこかにデータを保存しておく必要があります。

こういう時は専用のシートでも作ってセルに書き込んでおくのが王道だとは思いますが、もう一つの選択肢として、カスタムドキュメントプロパティも使えるようです。

以下のようにVBA側でヘルパー関数を定義しておけばWSH側から (Applicationオブジェクト).Run(Excelファイル名 + "!関数名", 引数...) で呼び出すことができて便利です。

Sub SetCDP(Pname As String, PValue As String)
    If Not IsCDPExist(Pname) Then
        ThisWorkbook.CustomDocumentProperties.Add Pname, False, msoPropertyTypeString, PValue
    Else
        ThisWorkbook.CustomDocumentProperties(Pname).Value = PValue
    End If
End Sub

Function GetCDP(Pname As String) As String
    GetCDP = ""
    If IsCDPExist(Pname) Then
        GetCDP = ThisWorkbook.CustomDocumentProperties(Pname).Value
    End If
End Function

Sub DelCDP(Pname As String)
    If IsCDPExist(Pname) Then
        ThisWorkbook.CustomDocumentProperties(Pname).Delete
    End If
End Sub

Function IsCDPExist(Name As String) As Boolean
    Dim prop As Object
    IsCDPExist = False
    For Each prop In ThisWorkbook.CustomDocumentProperties
        If prop.Name = Name Then
            IsCDPExist = True
            Exit For
        End If
    Next prop
End Function

WSHからExcelのユーザーフォームを開く

WSHというかExcel関係のActiveXオブジェクトからユーザーフォームを開くのは無理なようなので、VBA側でユーザーフォーム名からフォームオブジェクトを取得するヘルパー関数を作ってみました。WSH側から (Applicationオブジェクト).Run(Excelファイル名 + "!GetFormObject", "フォーム名") でフォームオブジェクトを取得したら (フォームオブジェクト).Show(1) で開いたりできます。存在しないフォーム名を指定するとVBA側ではNothingを返していますが、WSH(JavaScript)側ではnullとなるようです。
また、このままではWSH側からUnload出来ない(hideで隠すことはできる)ので、同様にヘルパー関数が必要のようです。

参考: Show Any Form

Function GetFormObject(FormName As String) As Object
    Dim Obj As Object

    For Each Obj In VBA.UserForms
        If Obj.Name = FormName Then
            Set GetFormObject = Obj
            Exit Function
        End If
    Next Obj

    On Error Resume Next
    Err.Clear
    Set Obj = VBA.UserForms.Add(FormName)
    If Err.Number <> 0 Then
        Set GetFormObject = Nothing
        Exit Function
    End If
    Set GetFormObject = Obj
End Function

Sub UnloadForm(formname As String)
    Dim obj As Object
    Set obj = GetFormObject(formname)
    If Not obj Is Nothing Then
        Unload obj
    End If
End Sub

VBAからWSH(wscript.exe)を起動

VBA内蔵のRunは実行したら即制御が戻る非同期実行となり非常に都合が悪いのでWScript.ShellのRunで同期実行するようにしています。

Function CallJScript(funcname As String, Optional formname As String = "", Optional script As String = "", Optional book As String = "") As Boolean

    Static fso As Object
    Static wsh As Object

    If fso Is Nothing Then Set fso = CreateObject("Scripting.FileSystemObject")
    If wsh Is Nothing Then Set wsh = CreateObject("WScript.Shell")

    If book = "" Then
        'bookパラメータ(対象のExcelファイル)を指定しなかった場合は自身のExcelファイルを設定
        book = ThisWorkbook.FullName
    ElseIf Left(book, 1) = "." Then
        '1文字目が.なら相対パスと判断して絶対パスに変換
        book = fso.GetAbsolutePathName(fso.BuildPath(ThisWorkbook.Path, book))
    End If

    If script = "" Then
        'scriptパラメータを指定しなかった場合はbookパラメータと同じフォルダにある同じファイル名のjsファイルを設定
        script = fso.GetParentFolderName(book) & "\" & fso.GetBaseName(book) & ".js"
    ElseIf Left(script, 1) = "." Then
        script = fso.GetAbsolutePathName(fso.BuildPath(ThisWorkbook.Path, script))
    End If

    If fso.FileExists(book) = False Then
        Err.Raise Number:=53, Description:="ファイル" & book & "が見つかりません"
        Exit Function
    End If

    If fso.FileExists(script) = False Then
        Err.Raise Number:=53, Description:="ファイル" & script & "が見つかりません"
        Exit Function
    End If

    'edgeのJScriptエンジン(Chakra)を指定してスクリプトを実行
    wsh.Run "wscript //E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} " & script & " " & book & " " & funcname & " " & formname, 0, True


    'イベントキャンセル用のカスタムプロパティを取得して戻り値に設定しカスタムプロパティを削除
    CallJScript = False

    If LCase(GetCDP("_JScriptIsCancel")) = "true" Then
        CallJScript = True
    End If

    If IsCDPExist("_JScriptIsCancel") Then
         DelCDP "_JScriptIsCancel"
    End If
End Function

WSHでスクリプトを書く

今のところこんな感じ。

var sh = WScript.CreateObject("WScript.Shell")
var xlBook = WScript.GetObject(WScript.Arguments(0))
var xlApp = xlBook.Application

sh.popup(xlBook.Sheets("Sheet1").Range("A3").Value)
xlBook.Sheets("Sheet1").Range("A1").Value = "test"
var formtest = xlApp.Run(xlBook.Name + "!GetFormObject", "UserForm1")

if(formtest == null){
    sh.popup("Nothing!")
}else{
    formtest.Show(1)
}

xlApp.Run(xlBook.Name + "!SetCDP", "test", "Test Prop2")
sh.popup(xlApp.Run(xlBook.Name + "!IsCDPExist","test"))
sh.popup(xlApp.Run(xlBook.Name + "!GetCDP", "test"))

都度wscript.exeを起動しなくてもいいのでは…

参考:JScript と Chakra を共存させる

をみてよくよく考えたら都度都度wscript.exeを起動しなくてもお互いにカスタムドキュメントプロパティの内容を監視して協調動作すればいいのではと考え中…(リンクのほうはInternetExplorerオブジェクトでやっている模様)

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

Obnizと圧電スピーカーで日が暮れたら「ゆうやけこやけ」を流して切なくなってみた

まずは完成形

CdSセル(照度センサ)で暗くなると、ゆうやけこやけが流れます。
この曲聞くと切なくなるな。。

使った部品

・Obniz Board 1Y
・圧電スピーカー(PKM13EPYH4000-A0)
・CdSセル(MI527/MI5527)
・カーボン抵抗 1/2W330Ω(CFS50J330RB)
・ミニブレッドボード(BB-601)
・ブレッドボード・ジャンパーワイヤ(オス-オス)(BBJ-20)

接続図

image.png

コード

.js
const Obniz = require('obniz');
const obniz = new Obniz('0000-0000'); // Obniz_ID

// 任意の秒数待つことができる関数
// 参考: https://qiita.com/suin/items/99aa8641d06b5f819656
const sleep = (msec) => new Promise(res => setTimeout(res, msec));

// 音階
const Key = {
    "" : 261.626,
    "" : 293.665,
    "" : 329.628,
    "ファ" : 349.228,
    "" : 391.995,
    "" : 440.000,
    "" : 493.883,
    "ド2" : 523.251,
    "レ2" : 587.330
}

obniz.onconnect = async function () {

    // ディスプレイ表示
    obniz.display.clear();
    obniz.display.print('TEST');

    // スピーカー
    const speaker = obniz.wired('Speaker', { signal: 0, gnd: 1 });

    // 照度センサ
    obniz.io9.output(true);  // io9電圧を5Vに(電源+)
    obniz.io11.output(false); // io11電圧を0Vに(電源−)

    // setIntervalで一定間隔で処理
    setInterval(async function () {
        // io10をアナログピンに(照度センサーの値を取得)
        var voltage = await obniz.ad10.getWait();
        console.log(`changed to ${voltage} v`);

        if (voltage < 0.3) {
            // 暗くなったら「ゆうやけこやけ」を流す
            speaker.play(Key[""]); await sleep(1000); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);

            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);

            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);

            speaker.play(Key[""]); await sleep(1500); speaker.stop(); await sleep(500);

            speaker.play(Key[""]); await sleep(1000); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);

            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key["ド2"]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key["ド2"]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);

            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);
            speaker.play(Key[""]); await sleep(500); speaker.stop(); await sleep(100);

            speaker.play(Key["ド2"]); await sleep(1500); speaker.stop(); await sleep(500);
        }
    }, 20000);  // 20秒(約1曲分は待つ)
}

やってみた感想

圧電スピーカーは周波数で音階を決められるので、曲を流すのは割と簡単でした。
参考:音階周波数

今回、圧電スピーカーで曲を流してみたかっただけですが、なかなか面白かった。ハマりそうです。。スピーカーを複数繋げれば、和音も表現できそうで曲の幅が広がりそうで、時間があったらやってみたい。

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

アラートダイアログのOKが押されたタイミングで処理を実行する

やりたいこと

アラートダイアログの例

このようなアラートダイアログのOKを押されたタイミングで処理を実行したいとします。

確認ダイアログの例

確認ダイアログを使って、以下のようにOKボタンが押された場合とキャンセルボタンが押された場合の処理を書くのが一般的です。

if (confirm('確認ダイアログの例')) {
    console.log('OKボタンが押されました。')
} else {
    console.log('キャンセルボタンが押されました。')
}

キャンセルボタンの押せない、OKボタンのみの確認ダイアログを作りたい

ただ、時には諸々の事情でキャンセルボタンを押してもらいたくない場合もあります。

ということで、キャンセルボタンの押せない、OKボタンのみの確認ダイアログを作っていきます。

alert()の仕様

確認ダイアログでのOKボタンとキャンセルボタンを押された場合の処理の書き分けでは、JavaScriptのconfirm()

  • OKボタンをクリックした場合の戻り値:true
  • キャンセルボタンをクリックした場合の戻り値:false

であることを使って、if文の条件式の中でconfirm()を使うことで実現していました。

一方、alert()では、アラートダイアログのOKボタンを押された際の戻り値はundefinedです。

この特性を使って、

if (!alert('OKを押してください。')) {
    // OKが押された際に実行する処理
}

上記のような条件式を書くことによって、OKが押された際にundefinedの否定、つまりtrueとなり、処理が実行されます。

以下、サンプルです。

サンプル

See the Pen Event After Alert by Kamiyama (@MtDeity) on CodePen.

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

【JavaScript・React】基本知識

JavaScriptやReactの基本となる概念について?

モジュール

コードを再利用しやすい形に細分化して影響範囲を狭めたもの。
違うモジュール同士なら名前が被ってもOK?なので、変数名や関数名が被ってしまう名前空間の問題を解決する。基本的にモジュールは1ファイルに1モジュール定義。

JavaScriptのモジュール形式

  • CommonJS :Node.js(サーバーサイド)で使われるJavaScriptの各種仕様。
  • ES Modules :JavaScript公式の言語仕様。今はこれが主流。

パッケージ管理システム

パッケージとは、コードの一部を使い回すために切り出したもの。
パッケージ管理システムではパッケージをいい感じでインストールし管理してくれる。 
例えば30個の依存関係のあるパッケージがあると、それぞれのパッケージがさらに依存関係を持っている、、、これをいい感じに管理してくれるのがnpm、yarn等のパッケージ管理システム。

主な機能
1. ローカル環境のパッケージを更新、検索
2. パッケージインストール削除
3. 依存関係の解決、必要な依存パッケージ自動インストール
4. 設定を書いて1-3を自動化

  • npm (Node package manager)
    Node.jsの標準パッケージ管理システム。(最近githubに買収された。)

    • package.json:インストールが必要なパッケージリスト
    • package-lock.json: 実際にインストールしたパッケージがどの依存関係を持っているかの記録。チームで共有することで環境を統一できる。node_modules、package.jsonが変更されると自動で作成、変更される
    • node_modules: ディレクトリの中に依存関係のパッケージが入っている。
  • yarn
    npmよりも高速なパッケージマネジャー。npmより厳密にモジュールのバージョンを固定でき、yarn.lockファイルで、各パッケージのインストールバージョンを固定できる。しかもnpmと同じpackage.jsonが使えるため、同一プロジェクトでnpmかyarnかなんて迷わなくてOK?:)
    https://classic.yarnpkg.com/ja/docs/install#mac-stable

ビルド

書いたコードを異なる環境で動かすための準備のこと。npmでインストールしたいろんな形式のモジュールで書かれたパッケージを使えるようになる。JavaScriptのビルドでは(Browserifyや)webpackが使われる。webpackではモジュールに加え画像やcssなども一緒にビルドしてくれる。

  • Browserify
    CommonJS形式で書かれたものをブラウザ向けにバンドル(変換)するツール。CommonJS形式で書けばBrowseryで変換することでブラウザでもNode.js向けのパッケージが使えるようになった。ブラウザでもnpm(CommonJS形式のパッケージがメイン)が使われるようになった。

  • webpack
    Browserifyの上位互換バンドルツール。JavaScript向けのファイルのみでなく、対応するローダーを使ってバンドル(変換)すればHTML,CSS,画像などを読み込めるようになる。ES Modulesにネイティブ対応。
    https://webpack.js.org/

コンパイル

開発時はブラウザで動かないコードを、ブラウザで動くように変換しJavaScriptにする機能。バンドルはモジュール依存関係解決に焦点、コンパイルは事前に変換する機能である。
ES2015の新機能を古いブラウザでもバンドル(トランスパイル)して動くようにしてくれるのがBabel。webpackと併用しコンパイルで事前に変換することでモジュールと依存関係を解消し、かつ、ES6の新しい機能も使えるようになった。
Reactだと、そのままでは動かないjsxファイルを、事前にコンパイルして変換すると、ブラウザで動くようになる。このようにいったんバンドルやコンパイルを挟むことがjQueryと違うところ。

  • Babel
    ReactのJSX(JavaScript XML React)というJavaScriptの拡張版でXMLを出力すると、Babelが自動でJavaScriptに変換してくれる。?
    他にもNodeでは認識されるES6のconst foo = <div> を標準的なvar foo = React.createElement() に変換してくれる。??
    以下のページの左側にXMLを入力すると、右側にリアルタイムでJavaScriptが表示される https://babeljs.io/ JSXを使うことでHTMLのような書き方でh1タグが作成できる。
//JSXでのh1タグ
import React from 'react';
import ReactDOM from 'react-dom';

const element = <h1>Hello!</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

axiosのinterceptorsで、requestのキャンセルを行う

tl;dr

interceptors.request.use()内でaxios.Cancel()をthrowすることでリクエストを直前でキャンセルできます。

?サンプル

認証トークンがlocalStorageに保存されており、トークンがなければAPIリクエストを投げたくないというケースを考えてみます。

認証トークンを取得する関数は以下です。

function getToken(){
  return window.localStorage.getItem("token");
}

axiosのinterceptorsを定義します。

const axios = require('axios');

  axios.interceptors.request.use(config => {
    if (!getToken()) {
      throw new axios.Cancel('トークンがないためリクエストはキャンセルされました');
    } else {
      config.headers.Authorization = "Bearer " + getToken();
      return config;
    }
  });

適当なドライバを作成し、コールしてみます。この時、ローカルストレージは空なので、requestがキャンセルされるはずです。

  async function getZip() {
    try {
      const response = await axios.get('http://api.thni.net/jzip/X0401/JSON/064/0941.js');
      console.log(response.data);
    } catch (error) {
      console.error(error);
    }
  }

  getZip();

実行結果

Cancel { message: 'トークンがないためリクエストはキャンセルされました' }

思惑通り、requestはキャンセルされました。

以上。

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

[JavaScript]eclipseでのJavaScript開発プロジェクト作成手順

JavaScript開発環境をeclipseに作りたいと思ったけど、やったことなかったので調べてみてせっかくなので備忘として残しておく。
ちなみにeclipseインストール済み前提の手順です。

■手順1. プロジェクト・エクスプローラ内で右クリックし、新規>プロジェクト>静的Webプロジェクト を選択
image.png
image.png

■手順2. プロジェクト名をつけて「完了」ボタン押下
image.png

■手順3. "JavaScriptリソース"と"WebContent"が入ってるプロジェクトが作成される!
image.png

■手順4. ④"WebContent"フォルダにjsやhtmlファイルを作成していく
image.png
image.png
image.png
image.png
image.png
image.png

ということでスタートラインに立てたので、これから何か書いて動かしてみる予定です。

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

JSONとは結局なんなのか?

まえがき

プログラミングをしていると、JSONというものをよく目にします。
拡張子が.jsonでなにかの設定を書くファイルや、処理の中でJSON形式のデータを受け取ったりします。
しかし、「なんかJavaScriptのオブジェクトっぽいぞ?」と思ってみても、「なんか、配列のなかにオブジェクトが入ってるのとかもある...?([{},{}]のような)」という具合で、
一体なんなのよくわからず、その場しのぎでJSON.parseやらJSON.stringifyなどして扱っていました。
せっかくなので、調べて簡単にまとめたいと思います。

最初に結論

・異なるプログラミング言語間でデータをやりとりするための、共通のデータ記述形式。
・XMLに代わって、AJAX(非同期通信)やREST APIなどで使われるようになった。
・JavaScriptのオブジェクトを「由来」にしているし、実際、オブジェクトの形式で見かけることも多いが、配列や数値、Boolean単体で記述してもOKで、まったく別物。
・シングルクォートは使えず、ダブルクォートのみが使えて、オブジェクトのkeyは文字列に限るので、

{name: "John Smith", age: 33}

という書き方はダメで、正しくは、

{"name": "John Smith", "age": 33}

・JSON.stringify()でオブジェクトや値をJSON記法に変換、JSON.parse()で引数に渡された値をJSONとして解析して、オブジェクトや値に変換してくれる。

データ記述言語

JavaScript Object Notation(JSON、ジェイソン)はデータ記述言語の1つである。軽量なテキストベースのデータ交換用フォーマットでありプログラミング言語を問わず利用できる[1]。名称と構文はJavaScriptにおけるオブジェクトの表記法に由来する。
JavaScript Object Notation
https://ja.wikipedia.org/wiki/JavaScript_Object_Notation

「JSON とは」といったキーワードで調べると大体上のようなことが最初に書かれています。個人的にはWikipediaの説明が一番シンプルだと思いました。
でもこれ、シンプル過ぎて何言ってるのかよくわからないんですよね。わかる人にはわかる説明だと思うんですが、わからない人には、何を言ってんのかわからないという。

問題は、「データ記述言語」という言葉です。
「いやそりゃなんか書いてるんだし、表現しているのはデータなはずだから、'データ'を'記述'している言語だろそりゃ」(頭悪くてすみません)と思うのですが、これ、ちゃんとした用語なんですね。

データ記述言語(データきじゅつげんご)またはデータ言語とは、コンピュータにおいて扱うデータを記述するための形式言語である。コンピュータ言語の一種だが、プログラミング言語ではない。HTMLに代表されるマークアップ言語などがある。
データ記述言語
https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E8%A8%98%E8%BF%B0%E8%A8%80%E8%AA%9E

これをみると、「え、マークアップ言語がその一種? HTML?」という感じですが、

データ記述言語は、主にデータをテキストデータとして表現し記述するためのコンピューター用言語。プログラミング言語ではないが、プログラミングと関連が深い。
あるプログラミング言語によりメモリー上に表現されたデータ構造は、他のプロセスや他のプログラミング言語で扱うのに不便なので、汎用的な形式で書かれたデータを扱うようにすることが多い。具体的には、次節で挙げるような言語が存在する。
データ記述言語
https://enpedia.rxy.jp/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E8%A8%98%E8%BF%B0%E8%A8%80%E8%AA%9E

と説明されていたり。

それで、また「JSON」に戻ると、

JavaScript だけではなく、Java, PHP, Ruby, Python など、様々な言語間のデータ交換、特に Ajax や REST API などで使用されています。
これまでは、共通データ定義言語として XML が利用されてきましたが、現在では、簡易的な JSON が利用されるケースが増えてきています。
JSONとは
http://www.tohoho-web.com/ex/json.html

と書かれていたり。

しかも、

他のデータ記述法との関係
XML
JSONはXMLと違ってマークアップ言語ではない。ウェブブラウザから利用できるという点では共通している。また両者とも巨大なバイナリデータを扱う仕組みがないことが共通している。
YAML
JSONはYAMLのサブセットと見なしてよい。YAMLにはブロック形式とインライン形式(フロー形式)の表記法があるが、JSONは後者にさらに制約を加えたものと捉えることができる。例えばRubyでは以下のようにしてJSONをYAMLとして読み込むことができる:
JavaScript Object Notation
https://ja.wikipedia.org/wiki/JavaScript_Object_Notation

と、XMLやYAMLと並べて説明されている。

ここで文系出身の私は、詳しいことはよくわからないが、なんとなく雰囲気で察しました。

要するに、異なるプログラミング言語間でデータをやりとりするための、共通のデータ記述形式であり、だからこそ、サーバーからデータを引っ張ってくる時や、逆にクライアントからサーバーにデータを渡す時に、一旦JSONに変換しているのではないかと。
そして、むかし(...いつ?)はXMLがその役割を担っていて、「AJAX」の'X'もXMLのことだったりする、つまりXML形式に変換してデータをやりとりしていたけど、最近ではもっぱらJSONという形式が使われるようになった。
結果的に、私のような末端でさえ、よくわからないけどJSONという形式のデータやファイルを頻繁に目にするようになったのだと。。。

詳しく説明するかと思いきや、雰囲気で察した結論となってしまいましたが、大体こんな感じではないでしょうか。

JavaScriptのオブジェクトじゃないの?

冒頭、

「なんかJavaScriptのオブジェクトっぽいぞ?」と思ってみても、「なんか、配列のなかにオブジェクトが入ってるのとかもある...?([{},{}]のような)」

と書きましたが、この辺に関しては、

JSONとは「JavaScriptのオブジェクト記法を用いたデータ交換フォーマット」です。
JSONとは?データフォーマット(データ形式)について学ぼう!
https://products.sint.co.jp/topsic/blog/json

と書かれていたりして、やっぱり混乱します。

Wikipediaには、

名称と構文はJavaScriptにおけるオブジェクトの表記法に由来する。
JavaScript Object Notation
https://ja.wikipedia.org/wiki/JavaScript_Object_Notation

と書かれています。

しかも、

JSONの型
JSON では下記の型を使用することができます。
文字列 ("...")
数値 (123, 12.3, 1.23e4 など)
ヌル値 (null)
真偽値 (true, false)
オブジェクト ({ ... })
配列 ([...])
JSON入門
http://www.tohoho-web.com/ex/json.html

ともあって、これはWikipediaにも同じ説明が。

またしても文系出身の私は察しました。
「これは、名前と書き方がJavaScriptのオブジェクトに「由来」しているだけで、まったく別物であり、配列でもいいんだ...!」

合ってますか?

ちなみに、JSONは「発見」されたみたいです。

ダグラス・クロックフォード(英語版)はJavaScriptのプログラマで、JSONを広めた一人だが、「The JSON Saga」と題したプレゼンテーション[4]中で「自分はJSONと名付けたが、考案者ではなく、それ自体は“自然に”存在していたもので、早い例としては1996年にはNetscape Navigatorでデータ交換用に使われていた。だから“発見した”ということになるのだが、発見したのも自分が最初ではない」といったように述べている。以上のことを縮めて「JavaScriptのオブジェクト表記法からJSONが発見された。」と表現されている場合がある。
JavaScript Object Notation
https://ja.wikipedia.org/wiki/JavaScript_Object_Notation

※ちなみに、「JavaScriptのオブジェクト記法を用いたデータ形式です」と書いてあって、記法にオブジェクトの例しか書いてないサイトもあったんですが、さすがにこれはミスリードでは...?
([{},{}]に関しては一切触れてなく、混乱します)

記法

Wikipediaから丸々引用させていただきます。

表記方法
JSONで表現するデータ型は以下の通りで、これらを組み合わせてデータを記述する[5]。true, false, null などは全て小文字でなくてはならない。

オブジェクト(順序づけされていないキーと値のペアの集まり。JSONでは連想配列と等価)
配列(データのシーケンス)
数値(整数、浮動小数点数)
文字列(バックスラッシュによるエスケープシーケンス記法を含む、ダブルクォーテーション"でくくった文字列)
真偽値(true と false)
null
数値は10進法表記に限り、8進、16進法表記などはできない。また浮動小数点数としては 1.0e-10 といった指数表記もできる。

文字列は(JSONそれ自体と同じく)Unicode文字列である。基本的にはJavaScriptの文字列リテラルと同様だが、囲むのにシングルクォートは使えない。バックスラッシュによるエスケープがある。

配列はゼロ個以上の値をコンマで区切って、角かっこでくくることで表現する。例えば以下のように表現する:

["milk", "bread", "eggs"]
オブジェクトはキーと値のペアをコロンで対にして、これらの対をコンマで区切ってゼロ個以上列挙し、全体を波かっこでくくることで表現する。例えば以下のように表現する:

{"name": "John Smith", "age": 33}
ここで注意することはキーとして使うデータ型は文字列に限ることである。したがって、

{name: "John Smith", age: 33}
という表記は許されない。この後者の表記はJavaScriptのオブジェクトの表記法としては正しいが、JSONとしては不正な表記である。
JavaScript Object Notation
https://ja.wikipedia.org/wiki/JavaScript_Object_Notation

なので、配列もOKなんですね。
だから、[{"aaa": "AAA"}, {"bbb": "BBB"}]もOKなんですね(「keyと値が文字列であるオブジェクトを要素に持つ、あくまで配列」)

ちなみに、

下記の様に、配列や値のみの表記も JSON に従ったデータとして認められます。
○ ["ABC", "DEF"]
○ "ABC"
○ 123
JSON入門
http://www.tohoho-web.com/ex/json.html

とのことで、やっぱり、「JavaScriptのオブジェクトと同じ記法です」というのは、ちょっと間違いで、「JavaScriptのオブジェクトの記法を由来にしているし、実際、オブジェクトの記法でよく見かけるが、結局別物」と考えてよいのだとおもいます。

ポイントとしては、

基本的にはJavaScriptの文字列リテラルと同様だが、囲むのにシングルクォートは使えない。
ここで注意することはキーとして使うデータ型は文字列に限ることである。したがって、
{name: "John Smith", age: 33}
という表記は許されない。

とあるように、シングルクォートは使えず、ダブルクォートのみが使えて、オブジェクトのkeyは文字列に限る、というのが点だとおもいます。

JSON.stringify JSON.parse

最後はなんとなく使っていたstringifyとparseです。MDNからコードも含めて引用させていただきます。

JSON.stringify() メソッドは、ある JavaScript のオブジェクトや値を JSON 文字列に変換します。置き換え関数を指定して値を置き換えたり、置き換え配列を指定して指定されたプロパティのみを含むようにしたりすることもできます。
JSON.stringify()
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

console.log(JSON.stringify({ x: 5, y: 6 }));
// expected output: "{"x":5,"y":6}"

console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)]));
// expected output: "[3,"false",false]"

console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }));
// expected output: "{"x":[10,null,null,null]}"

console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)));
// expected output: ""2006-01-02T15:04:05.000Z""

JSON.stringify()は、オブジェクトや値を、よしなにJSON記法に変換してくれるんですね。
いままで、stringifyって、「文字列に直してんの?」「オブジェクトはJSON形式なのに、なんでstringifyする必要があるの?」とかおもったりしていました。

JSON.parse() メソッドは文字列を JSON として解析し、文字列によって記述されている JavaScript の値やオブジェクトを構築します。任意の reviver 関数で、生成されたオブジェクトが返される前に変換を実行することができます。
JSON.parse()
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);

console.log(obj.count);
// expected output: 42

console.log(obj.result);
// expected output: true

逆に、JSON.parse()は、渡された引数をJSONとして解析して、オブジェクトや値に変換してくれるんですね。

残った疑問

最後に引用したMDNの記事、JSON.parseに渡すJSON文字列にシングルクォート入ってるし('{"result":true, "count":42}')、JSON.stringifyの方では、オブジェクト自体がダブルクォーテーションで囲まれている("{"x":5,"y":6}")...?

あとがき

だいぶ正確性を疎かにした記事ですが、どうでしょうか。
自分ではどれくらい合ってるのかよくわかりませんが、理解のために、まとめてみました。

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

非同期処理について

はじめに

非同期処理とはそもそもどのようなことを行うのかを初学者の方が見てもわかるようにまとめてみました。
まずは非同期処理を理解するためには、同期処理との違いについて把握しなくてはなりません。

違い

1つの処理が完了するまで次の処理に進まないというのが同期処理です。
※webサイトへアクセスした際、重い処理がある場合にはその処理の完了を待たないと次の処理を行うことができません。
対して非同期処理とは、並行して同時に処理が可能です。

Javascriptの実行場所について

Javascrtiptは、メインスレッドという領域で実行されます。
メインスレッドは、Javascriptの実行とその結果によってレンダリング(再描画、表示の更新)を行う領域です。

※ここでのレンダリングは、ビュー全体を再読み込むする形ではなく、関数の実行によってDOMの一部を操作するイメージです。
具体的には、下記のような形です。
スクリーンショット 2020-10-17 12.50.25.png

同期処理は、実行中の関数がこのメインスレッドを占有し、その処理が完了するまで他の処理を行うことができません。
この点が同期処理では、同時に処理が行えない理由となります。

具体的には下記のコードです。

function blockTime(timeout) { 
  const startTime = Date.now();
  // `timeout`ミリ秒経過するまで無限ループをする
  while (true) {
      const diffTime = Date.now() - startTime;
      if (diffTime >= timeout) {
          return; // 指定時間経過したら関数の実行を終了
      }
  }
}
console.log("処理を開始");
blockTime(5000); // 他の処理を5000ミリ秒の間、メインスレッドを占有します。
console.log("5秒経過したので出力します。");

下記のような形になります。
スクリーンショット 2020-10-18 15.21.08.png

※下記動画が非同期処理について非常にわかりやすく説明されてます。素晴らしい動画です。
web万屋エンジニアさんの非同期処理について

非同期処理

非同期処理もメインスレッド上で実行されますが、複数の処理を同時に実行することができます。

※非同期処理も同期処理と同様に呼び出された順番で実行されますが、別の非同期処理の完了を待たずに次の処理を行うことができる点が同期通信とは異なります。
ですが、他の関数と同時に実行されるという訳ではありません。
例えば、他の同期処理が先にメインスレッドを占有していればその処理が終わるまでは処理が実行されません。

今回は非同期処理を行うためのAPIであるsetTimeoutを用いて実装します。

<button id="eventBtn">btn</button>
window.addEventListener("load", ()=>{

  function blockTime(timeout) { 
    const startTime = Date.now();
    // `timeout`ミリ秒経過するまで無限ループをする
    while (new Date() - startTime < timeout);
    console.log("block done");
  }

  setTimeout(function(){
    console.log("2秒経過したのでコールバックします")
    blockTime(3000)
  },2000);

  document.getElementById("eventBtn").addEventListener("click", () => {
    console.log("clicked");
  });

})

上記コードを実行すると以下のようになります。
①setTimeout関数に渡したblockTimeは非同期処理として、メインスレッドから切り離される。(blockTimeout関数は2秒後に実行されます。)
②setTimeout関数によって2秒間はメインスレッドが空くため、他の処理が並行して行うことができます。
③blockTime関数がコールバックされるとメインスレッドは占有される。
④blockTimeが完了すると他の処理を実行することができます。

スクリーンショット 2020-10-18 17.16.04.png

処理のイメージです。
スクリーンショット 2020-10-18 17.37.50.png

まとめ

Javascriptはメインスレッドで実行されていることがわかりました。
非同期処理は、メインスレッドから切り離されるためその間同時に処理が同時に実行が可能ということですね。

参考資料

Javascriptの非同期処理演習

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

JavaScriptの型判定

型判定について調べることがあったので、まとめてみました。

先に結論から

型判定にはObject.prototype.toString.call()を使うと幸せになれる。

型の種類

JavaScriptは動的型付け言語のため、静的型付け言語のような変数の型はありません。
しかし、文字列や真偽値のような値の型は存在します。

この値の型データ型と呼ばれ、大きく分けて下記の2つに分類されます。

プリミティブ型(基本型)

  • Boolean(真偽値)
    • truefalseの2つがある
  • String(文字列)
    • "あいうえお"などのテキストデータ
  • Number(数値)
    • 423.14159 などの数値のデータ
  • null
    • 値が存在しないことを意味する。nullの1種類しかない
  • undefined
    • 値が未定義であることを意味する
  • Symbol
    • ES2015から追加された一意で不変な値を意味する(正直使い所がよくわかっていない。。)
  • BigInt(巨大な整数)
    • ES2020から追加された9007199254740992nなどの任意の精度で精度で表すことができる(正直使い所がよくわかっていない。。)

オブジェクト型(複合型)

  • プリミティブ型以外のデータ
    • オブジェクト、配列 関数、正規表現、Dateなど

型判定してみる

typeof演算子を使うと型を調べることができる

console.log(typeof true); // boolean
console.log(typeof "あいうえお"); // string
console.log(typeof 1234); // number
console.log(typeof null); // object (なぜ?!?!)
console.log(typeof undefined); // undefined
console.log(typeof Symbol("シンボル"));// symbol
console.log(typeof 9007199254740992n); // bigint
console.log(typeof ["hoge", "fuga"]); // object (なぜ?!?!)
console.log(typeof { "key": "value" }); // object
console.log(typeof function() {}); // function
console.log(typeof(new Date())); // object (なぜ?!?!)
console.log(typeof(new Array())); // object (なぜ?!?!)

typeofでは厳密な判定が出来ない問題

判定結果を見ると、nullや配列やnewした際にobjectとして判定されてしまう。。。。
これでは細かなオブジェクト型の判定はできなく、配列/null/日付等の区別もできない:fearful:

ちなみに、typeof nullobjectと判定されてしまうのは、歴史的経緯のある仕様のバグ1らしい(初めて知った)

Object.prototype.toString.call() を使って厳格な型判定をする

const toString = Object.prototype.toString;

console.log(toString.call(true)); // [object Boolean]
console.log(toString.call("あいうえお")); // [object String]
console.log(toString.call(1234)); // [object Number]
console.log(toString.call(undefined)); // [object Undefined]
console.log(toString.call(Symbol("シンボル"))); // [object Symbol]
console.log(toString.call(9007199254740992n)); // [object BigInt]
console.log(toString.call(["hoge", "fuga"])); // [object Array]
console.log(toString.call({ "key": "value" })); // [object Object]
console.log(toString.call(function() {})); // [object Function]
console.log(toString.call(new Date())); // [object Date]
console.log(toString.call(new Array())); // [object Array]

  1. JavaScriptが最初にNetscapeで実装された際にtypeof null === "object"となるバグがありました。このバグを修正するとすでにこの挙動に依存しているコードが壊れるため、修正が見送られ現在の挙動が仕様となりました。 詳しくは https://2ality.com/2013/10/typeof-null.html 参照。 

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

Vue 3 Composition API を使ってみよう

Composition APIとは、

Composition APIとは、2020年09月18に正式リリースされた、Vue 3に追加された目玉機能です。

Composition APIはコンポーネントを構築するための新しい手法です。
Vueが抱えていた以下の課題を解決するためのものです。

  • TypeScriptのサポート
  • ロジックの再利用の難しさ
  • アプリケーションが巨大になると、コードの把握が難しくなる

Vue CLIでVue 3を導入する

早速、Vue CLIでVue 3を使っていきましょう。
まずは、最新のVue CLIをインストールします。

yarn global add @vue/cli@next
# OR
npm install -g @vue/cli@next
vue -V
@vue/cli 4.5.4

次に、いつも通りプロジェクトを作成します。
Vue 3が選べるようになっています。

vue create vue3-project

Vue CLI v4.5.4

? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
❯ Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
  Manually select features 

今回は、Manyally select featuresから選択します。

? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

プロジェクトの作成が完了したら、早速起動してみましょう。

cd vue3-project
npm run serve

スクリーンショット 20201017 20.19.14.png

TODOアプリの作成を例にして、Composition APIを使っていきましょう。

コンポーネントの例

まずは、Composition APIで作成されたコンポーネントの全体像を見ていきましょう。
次のように、以前の元とは大きく変わっていることがわかります。

// MyTodo.vue
<template>
  <todo-list
    v-for="todo in sortTodo"
    :todo="todo"
    :key="todo.id"
    @toggle="toggleTodo"
    @remove="removeTodo"
  />
  <add-todo
    @add="addTodo"
  />
</template>

<script lang="ts">
import { computed, defineComponent, reactive, watchEffect, onMounted } from 'vue'
import TodoList from '@/components/TodoList.vue'
import AddTodo from '@/components/AddTodo.vue'
import { fetchTodo } from '@/api'
import { Todo } from '@/types/todo'
import { v4 as uuid } from 'uuid'

interface State {
  todos: Todo[];
}

export default defineComponent({
  components: {
    TodoList,
    AddTodo
  },
  setup () {
    const state = reactive<State>({
      todos: []
    })

    onMounted(async () => {
      state.todos = await fetchTodo()
    })

    const sortTodo = computed(() => state.todos.sort((a, b) => {
      return b.createdAt.getTime() - a.createdAt.getTime()
    }))

    const addTodo = (title: string) => {
      state.todos = [...state.todos, {
        id: uuid(),
        title,
        done: false,
        createdAt: new Date()
      }]
    }

    const removeTodo = (id: string) => {
      state.todos = state.todos.filter(todo => todo.id !== id)
    }

    const toggleTodo = (id: string) => {
      const todo = state.todos.find(todo => todo.id === id)
      if (!todo) return
      todo.done = !todo.done
    }

    watchEffect(() => console.log(state.todos))

    return {
      sortTodo,
      addTodo,
      removeTodo,
      toggleTodo
    }
  }
})
</script>
// TodoList.vue
<template>
  <div>
    <span>{{ todo.title }}</span>
    <input type="checkbox" value="todo.done" @change="toggle" />
  </div>
  <div>
    {{ date }}
  </div>
  <div>
    <button @click="remove">削除</button>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { Todo } from '@/types/todo'

export default defineComponent({
  props: {
    todo: {
      type: Object as PropType<Todo>
    }
  },
  emits: ['toggle', 'remove'],
  setup (props, context) {
    const date = computed(() => {
      if (!props.todo) return
      const { createdAt } = props.todo
      return `${createdAt.getFullYear()}/${createdAt.getMonth() + 1}/${createdAt.getDate()}`
    })

    const toggle = () => {
      context.emit('toggle', props.todo!.id)
    }

    const remove = () => {
      context.emit('remove', props.todo!.id)
    }

    return {
      date,
      toggle,
      remove
    }
  }
})
</script>
// addTodo
<template>
  <input type="text" v-model="state.inputValue" />
  <button @click="onClick" :disabled="state.hasError">追加</button>
  <p v-if="state.hasError" class="error">タイトルが長すぎ!</p>
</template>

<script lang="ts">
import { defineComponent, reactive, watchEffect } from 'vue'

interface State {
  inputValue: string;
  hasError: boolean;
}
export default defineComponent({
  emits: ['add'],
  setup (_, context) {
    const state = reactive<State>({
      inputValue: '',
      hasError: false
    })

    const onClick = () => {
      context.emit('add', state.inputValue)
      state.inputValue = ''
    }

    watchEffect(() => {
      if (state.inputValue.length > 10) {
        state.hasError = true
      } else {
        state.hasError = false
      }
    })

    return {
      state,
      onClick
    }
  }
})
</script>

<style scoped>
.error {
  color: red;
}
</style>

以前のような構造(これをOptions APIと呼びます)と異なり、date・methods・computed・ライフサイクルメソッドなどの区別がなくなり、全てがsetupメソッドの中に記述されています。一方で、componentsやpropsの記述は以前と変わりありません。

全てがsetupメソッド内に記述された結果、他のプロパティにアクセスする際にthisが不要になりました。以前までのVueではこのthisの制約によりアロー関数で記述することが敬遠されていたのですが、Composition APIではアロー関数により記述が可能になりました。

setupメソッド内のデータは、returnされたものだけがtemplate内で使用できるようになります。
そのため、例えばMyTodo.vueのsetupメソッドではstateが宣言されていますがreturnされていないためこれを使用することはできません。stateの値は直接使用せずに、conputedを通して使用するという意図を伝えることができます。

それでは、もう少し具体的にここのプロパティを見ていきましょう。

コンポーネントの宣言

Vue 3では、今までのVue.extendの宣言に変わり、defineComponentが使われます。これにより型推論が効くようになります。JavaScriptで記述する場合には必要ありません。

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({})
</script>

リアクティブなデータ reactive または ref

reactiveまたはrefは以前のdataに相当するものです。どちらもジェネリクスで型定義を渡すことができます。

reactive

個人的に、reactiveのほうが以前のdataに近い印象を受けます。reactiveは1つのオブジェクトとしてデータを定義します。

interface State {
  inputValue: string;
  hasError: boolean;
}
const state = reactive<State>({
  inputValue: '',
  hasError: false
})

reactiveの値にはオブジェクト形式でアクセスします。

state.inputValue
state.hasError

分割代入すると、リアクティブにならない

reactiveを使用する注意点として、分割代入した値に対してはリアクティブ性が失われるという点があります。

AddTodoコンポーネントを例に試してみましょう。

<template>
  <input type="text" v-model="inputValue" />
  <button @click="onClick" :disabled="hasError">追加</button>
  <p v-if="hasError" class="error">タイトルが長すぎ!</p>
</template>

<script lang="ts">
import { defineComponent, reactive, watchEffect } from 'vue'

interface State {
  inputValue: string;
  hasError: boolean;
}
export default defineComponent({
  emits: ['add'],
  setup (_, context) {
    // 分割代入でここのプロパティを受け取るように変更
    let { inputValue, hasError } = reactive<State>({
      inputValue: '',
      hasError: false
    })

    const onClick = () => {
      context.emit('add', inputValue)
      inputValue = ''
    }

    watchEffect(() => {
      if (inputValue.length > 10) {
        hasError = true
      } else {
        hasError = false
      }
    })

    return {
      inputValue,
      hasError,
      onClick
    }
  }
})
</script>

<style scoped>
.error {
  color: red;
}
</style>

reactive.gif

リアクティブ性が失われていることがわかります。

このような状況に対する解決策として、toRefs関数が用意されています。toRefsはリアクティブオブジェクトをプレーンオブジェクトに変換します。結果のオブジェクトの各プロパティは、元のオブジェクトの対応するプロパティの参照です。

<template>
  <input type="text" v-model="inputValue" />
  <button @click="onClick" :disabled="hasError">追加</button>
  <p v-if="hasError" class="error">タイトルが長すぎ!</p>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs, watchEffect } from 'vue'

interface State {
  inputValue: string;
  hasError: boolean;
}
export default defineComponent({
  emits: ['add'],
  setup (_, context) {
    const { inputValue, hasError } = toRefs(reactive<State>({
      inputValue: '',
      hasError: false
    }))

    const onClick = () => {
      context.emit('add', inputValue)
      inputValue.value = ''
    }

    watchEffect(() => {
      if (inputValue.value.length > 10) {
        hasError.value = true
      } else {
        hasError.value = false
      }
    })

    return {
      inputValue,
      hasError,
      onClick
    }
  }
})
</script>

<style scoped>
.error {
  color: red;
}
</style>

toRefsを適用すると、後述するrefで宣言されたものと同等の状態になります。そのため、template以外の場所からアクセスする際には、inputValue.valueのように.valueの値に対してアクセスします。

reactiveref.gif

ref

リアクティブなデータを宣言するもう一つの方法はrefを使用することです。
refはそれぞれの変数として宣言されます。

let inputValue = ref('')
let hasError = ref(false)

refで宣言された値にtemplate以外の場所からアクセスする場合、.valueからアクセスします。

inputValue.value
hasError.value

reactiveとrefどっちを使えばいい?

このように、Composition APIではリアクティブなデータの宣言にreactiverefどちらも使用することができます。しかし、どちらを使用するかのベストプラクティスはまだ存在していないようです。

Ref vs Reactive

Composition APIを効率的に理解するためには、どちらの方法を理解しておくべきだと述べられています。

メソッド

以前のmethodsに相当するものは、通常のJavaScriptの関数の宣言にになります。

const addTodo = (title: string) => {
  state.todos = [...state.todos, {
    id: uuid(),
    title,
    done: false,
    createdAt: new Date()
  }]
}

const removeTodo = (id: string) => {
  state.todos = state.todos.filter(todo => todo.id !== id)
}

const toggleTodo = (id: string) => {
  const todo = state.todos.find(todo => todo.id === id)
  if (!todo) return
  todo.done = !todo.done
}

メソッドの宣言も同様に、returnしていないものはtemplate内で使用できません。

computed

computedは関数をcomputedで包むことで実装できます。

const sortTodo = computed(() => state.todos.sort((a, b) => {
  return b.createdAt.getTime() - a.createdAt.getTime()
}))

何度も言うように、computedの値もreturnする必要があります。

データの監視 watch watchEffect

watchもdataのようにwatchwatchEffect2つの方法が使えるようになりました。
以前のような使い方と近いのはwatchです。

watch

watchは第一に引数に監視する対象のリアクティブな値を指定します。リアクティブな値とは、refreactivecomputedで宣言された値を指します。

第二引数に実行するメソッドを渡します。

watch(state.todos, (oldTodo: Todo, newTodo: Todo) => {
  console.log(oldTodo, newTodo)
})

監視対象が複数ある場合、配列で指定します。

watch([state.inputValue, state.hasError], ([oldInputValue, newInputValue], [oldHasError, newHasError]) => {
})

watchEffect

watchEffectは、監視対象を指定しません。computedのように、関数内の値が変更されたときに実行されます。

watchEffect(() => console.log(state.todos))

基本的に、watchEffectよりもwatchのほうが機能が優れているようで、例えばwatchEffectと比べてwatchは次のような機能を提供します。

  • 副作用の遅延実行。
  • 監視対象の値を渡すので、意図が明確。
  • 監視状態の以前の値と現在の値の両方にアクセスできる。

ライフサイクルメソッド

Composition APIのライフサイクルメソッドはonというプレフィックスがつけられました。

onMounted(async () => {
  state.todos = await fetchTodo()
})

Options APIとの対応は以下の通りです。

Options API Conposition API
beforeCreate use setup()
created use setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured

beforeCratecrateに相当するフックはなくなり、setup()で記述するようになりました。
さらに、以下のライフサイクルフックが追加されました。

  • onRenderTracked
  • onRenderTriggered

props

propsををsetupメソッド内で使用するために、setupメソッドは引数を受け取ります。第一引数はpropsそのものです。

export default defineComponent({
  props: {
    todo: {
      type: Object as PropType<Todo>
    }
  },
  setup (props, context) {
    const date = computed(() => {
      if (!props.todo) return
      const { createdAt } = props.todo
      return `${createdAt.getFullYear()}/${createdAt.getMonth() + 1}/${createdAt.getDate()}`
    })

    return {
        date
    }
})

propsプロパティは今まで通りです。
propsプロパティに型定義がされている場合、そのとおりに推論されます。

スクリーンショット 20201018 14.41.51.png

注意点として、以下のようにpropsを分割して受け取ると、リアクティブ性が失われてしまいます。

setup({ todo }, context) {}

emit

setupメソッドの第二引数には、contextオブジェクトを受け取ります。contextオブジェクトからはOptions APIでthisからアクセスできた一部のプロパティを提供します。

context.attr
context.slots
context.emit

どのプロパティも、先頭の$が外れていることに注意してください。
propsは使用せずに、contextオブジェクトだけ使用したいときには、第一引数を_にして受け取らないようにします。

setup(_, context) {}

contextオブジェクトの中にemitが含まれているので、以前のように使用することができます。

export default defineComponent({
  emits: ['add'],
  setup (_, context) {
    const state = reactive<State>({
      inputValue: '',
      hasError: false
    })

    const onClick = () => {
      context.emit('add', state.inputValue)
      state.inputValue = ''
    }
    return {
      state,
      onClick
    }
  }
})

さらに注目すべき変更点として、Vue 3からはemitsオブジェクトとしてそのコンポーネントがemitする可能性があるイベントを配列として宣言するようになりました。

必須のオプションではないですが、コードを自己文章化することができ、推論もされるようになります。

スクリーンショット 20201018 14.53.53.png

モジュールに分割する

ここまでただ単にComposition APIに書き換えてみただけなので、結局コンポーネント内で宣言されており、またstateの値に依存しているのであまり恩恵を感じないように思えます。

ここからは、Composition APIの真骨頂であるコードの分割にコマを進めていきましょう。
Composition APIではdate、computedなので区切りがなくなったことで関心ことの分離が可能になりました。

分割した関数は、src/composables配下に配置していきます。
それでは、まずはsortTodoを切り出してみましょう。

実装は次のようになります。

// src/composable/use-sort-todo.ts
import { computed, isRef, Ref } from 'vue'
import { Todo } from '@/types/todo'

export default (todos: Ref<Todo[]>) => {
  const sortTodo = computed(() => todos.value.sort((a, b) => {
    return b.createdAt.getTime() - a.createdAt.getTime()
  }))

  return {
    sortTodo
  }
}

元々state.todoでアクセスしていた箇所を引数で受け取るようにしました。引数の型Refとしています。ロジック部分に変わりはありません。

使用側では以下のように使います。

import { toRefs, defineComponent, reactive, watchEffect, onMounted } from 'vue'
import useSortTodo from '@/composables/use-sort-todo'

interface State {
  todos: Todo[];
}

export default defineComponent({
  setup () {
    const state = reactive<State>({
      todos: []
    })

    const { todos } = toRefs(state)

    const { sortTodo } = useSortTodo(todos)

    // 中略

    return {
      sortTodo,
      addTodo,
      removeTodo,
      toggleTodo
    }
  }
})

これで、sortTodoはVueオブジェクトに依存することなく、再利用可能な関数として取り出すことができました。

その他のメソッドも切り出してみると、だいぶコンポーネントがスッキリしました。

<template>
  <todo-list
    v-for="todo in sortTodo"
    :todo="todo"
    :key="todo.id"
    @toggle="toggleTodo"
    @remove="removeTodo"
  />
  <add-todo
    @add="addTodo"
  />
</template>

<script lang="ts">
import { defineComponent, watchEffect } from 'vue'
import TodoList from '@/components/TodoList.vue'
import AddTodo from '@/components/AddTodo.vue'
import useTodos from '@/composables/use-todos'
import useSortTodo from '@/composables/use-sort-todo'
import useActionTodo from '@/composables/use-action-todo'

export default defineComponent({
  components: {
    TodoList,
    AddTodo
  },
  setup () {
    const { todos } = useTodos()
    const { sortTodo } = useSortTodo(todos)
    const { addTodo, removeTodo, toggleTodo } = useActionTodo(todos)

    watchEffect(() => console.log(todos.value))

    return {
      sortTodo,
      addTodo,
      removeTodo,
      toggleTodo
    }
  }
})
</script>

変更はこちらのレポジトリから参照できます。

https://github.com/azukiazusa1/vue3-project

参考

Vue Composition API
Vue3への移行は可能?Vue2のコードをComposition APIで書き換えてみた
Vue.js 3.0で搭載される Composition APIをリリースに先駆けて試してみた
Vue 3 に向けて Composition API を導入した話
Vue 2.xのOptions APIからVue 3.0のComposition APIへの移行で知っておくと便利なTips
Vue3リリース直前!導入されるcomposition APIを使ってみよう

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

JavaScriptをJavaで呼び出してみる

はじめに

今回は、Javaアプリケーション内でJavaScriptを使う方法を実践してみます。
JSPの中でJavaScriptを実践知る方法とJavaファイルの中で実践する方法の二つがあります。

開発環境

eclipse
tomcat
java

JSPファイルでJSを実行

IndexServlet.java
index.jsp
main.js
を作成していきます。

image.png

このような階層となっています。

servlet.IndexServlet.java
package servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class IndexServlet
 */
@WebServlet("/IndexServlet")
public class IndexServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public IndexServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

サーブレットはノータッチでOKです。(あくまでもJSPでJSを実行することが目的なので)

index.jsp
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<style>
.box1 {
    width: 200px;
    height: 200px;
    background-color: #ccffcc;
}
.box2 {
  width: 200px;
  height: 200px;
  background-color: #ddd;
}
</style>
<body>
    <h1>JSPでのJS実行確認</h1>
    <div class="box1">box1</div>

    <div class="box2">box2</div>

    <form action="IndexServlet" method="post">
      ユーザID:<input type="text" name="user_id" id="user_id">
      パスワード:<input type="password" name="pass" id="pass">
      <input type="submit" value="ログイン" id="btn">
    </form>
  <input type="button" value="パスワードをみる" id="passshow">
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="main.js"></script>
</body>
</html>

・クリックすると色が変わるボックス
・マウスホバーで色が変わるボックス
・ログインフォームのバリデーション
の三つを実装していきます。

main.js
/* ---------クリックした時の処理---------*/
$('.box1').on('click', (e) => {
    $(e.target).css({'background-color': 'skyblue' });
});
/*  ------マウスをホバーした時の処理----------*/
const onMouseenter = (e) => {
  $(e.target).css({
    'background-color': '#ff9999',
  });
};
const onMouseleave = (e) => {
  $(e.target).css({
    'background-color': '#dddddd',
  });
};
$('.box2')
  .on('mouseenter', onMouseenter)
  .on('mouseleave', onMouseleave);
/*-------- パスワードを見れるようにする--------------*/
$('#passshow').on('click', () => {
  $('#pass').attr('type', 'text');
});
/*----------ログイン時にバリデーションを設定する(空欄だった場合弾く)----------- */
$('#btn').on('click', (e) => {
  const id = $('#user_id').val();
  const pass = $('#pass').val();
  if(!id){
    e.preventDefault();
    console.log(id);
    alert('IDが空欄です');
  }
  if(!pass){
    e.preventDefault();
    console.log(pass);
    alert('パスワードが空欄です');
  }
})

以上のようにJSを実装することができた。
(HTMLの時と全く同じように実装することができた。)

JavaファイルでJSを実行する

次に、JavaファイルのなかでJSを実行する方法の一つを紹介する。
解説は全てコード内にあるのでみていただければ。。。

下記のコードは、オラクルのJavaドキュメントガイドの下記のページを解説した物になります。
https://docs.oracle.com/javase/jp/8/docs/technotes/guides/scripting/prog_guide/api.html

test.java
import java.io.File;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class test {

    public static void main(String[] args) throws Exception{
        //ScripteEnginManagerインスタンスを作成
        ScriptEngineManager factory = new ScriptEngineManager();
        //ScripteEngineManagerインスタンスのgetEngineByNameメソッドでJSのスクリプトエンジンを指定
        ScriptEngine engine = factory.getEngineByName("JavaScript");

        //JSエンジンの活用---------------------------------
        //コンソールに表示・スクリプトをJS文として評価
        engine.eval("print('Hello World')");

    //文字ファイルを読み込むためのクラスjava.io.FileReaderを使うことでJSファイルごと実行できる
        //同じパッケージ同じ階層にある時
        File file1 = new File("src/test.js");
        engine.eval(new java.io.FileReader(file1));
        //srcと同じ階層のresourcesフォルダにある時
        File file2 = new File("resources/test.js");
        engine.eval(new java.io.FileReader(file2));

        //put(キー名, ソースコード)でキー名にソースコードを保存できる
        engine.put("file", file1);
        //getAbsolutePathで絶対パスを取得する・スクリプトをJS文として評価
        engine.eval("print(file.getAbsolutePath())");

        //変数に関数などを代入可能
        String script1 = "function helle(name) { print('Hello' + name);}";
        //変数名で呼び出し(engineに保存されている)・スクリプトをJS文として評価
        engine.eval(script1);
        //engineに保存されている関数やメソッドを呼び出す。→関数などの実行ができる *invocable 呼び出し可能という意味
        Invocable inv1 = (Invocable) engine;
        //今回は関数なのでinvokeFunction("呼び出される関数名", "引数")で実行できる
        inv1.invokeFunction("helle", "太郎");

        //"obj"というオブジェクトを作成して、"obj"オブジェクトのメソッドも定義
        String script2 = "var obj = new Object(); "
                + "obj.goodbye = function(name){print('Goodbye' + name + 'さん');}";
        //engineに保存・スクリプトをJS文として評価
        engine.eval(script2);
        //engineを呼び出し可能にする
        Invocable inv2 = (Invocable) engine;
        //呼び出したいメソッドを持つオブジェクトを取得する(扱えるようにする)
        Object obj1 = engine.get("obj");
        //今回はメソッドなので、invokeMethod(オブジェクト名, "メソッド名", "引数")で実行
        inv2.invokeMethod(obj1, "goodbye", "次郎");

    //Runnableインターフェースのメソッドとしてスクリプト関数を扱うことができる・グローバルに使えるようになる
        //引数の無い関数生成
        String script3 = "function run() { print('java.lang.Runnableインターフェースの呼び出し'); }";
        //スクリプトをJS文として評価・呼び出し可能状態にする
        engine.eval(script3);
        Invocable inv3 = (Invocable) engine;
        //Runnable(インターフェース)を取得し、スクリプト関数(inv3)をメソッドr1として実装
        Runnable r1 = inv3.getInterface(Runnable.class);
        //Threadオブジェクト(Runnableインターフェースが実装されている)にメソッドr1を渡すし作成
        Thread th1 = new Thread(r1);
        //実行開始
        th1.start();


        //引数の無いメソッド生成
        String script4 = "var obj = new Object();"
                + "obj.run = function() {print('Runnableメソッドの呼び出し');}";
        //スクリプトとして評価
        engine.eval(script4);
        //objをオブジェクトとして扱えるようにする
        Object obj2 = engine.get("obj");
        //呼び出し可能にする
        Invocable inv4 = (Invocable) engine;
        //Runnable(インターフェース)を取得し、スクリプト関数(inv4)をメソッドr2として実装
        Runnable r2 = inv4.getInterface(obj2, Runnable.class);
        //Threadオブジェクト(Runnableインターフェースが実装されている)にメソッドr1を渡し作成
        Thread th2 = new Thread(r2);
        th2.start();

    }
}

最後に

今回紹介したようにJavaとJSを組み合わせることができれば、より動的なページが作れそうですね。

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

まるばつゲームの制作

まるばつゲームの制作

3×3のマス目を作り、マスをクリックすると○と×を交互に表示できるようにします。

まず、body要素にonload属性を付与して、ブラウザがページを読み込んだときにマス目を画面に表示するように設定します。

<body onload="init()">
  <h2>⚪︎×ゲーム</h2>
  <table id="table"></table>
  <p>※ マスをクリックしてください。</p>
</body>

script要素内ではまず、変数countを定義します。初期値はfalseにします。

init関数内はtable要素に「createElement」メソッドで作成したtr要素を3つ追加します。tr要素には同じく「createElement」メソッドで作成したtd要素を3つ追加します。
「document.createElement("要素名")」で指定した要素を作成することができます。
「要素名.appendChild(要素名2)」で親要素(要素名)に子要素(要素名2)を追加することができます。
tdにはonclickプロパティでmark関数を代入します。これでtd要素であるマス目をクリックしたときにマス目に○や×を表示できるようにします。

  <script>
    let count = false;

    function init() {
      for(let r = 0; r < 3; r++) {
        let tr = document.createElement("tr");
        for(let d = 0; d < 3; d++) {
          let td = document.createElement("td");
          td.onclick = mark;
          tr.appendChild(td);
        }
        table.appendChild(tr);
      }
    }

mark関数には引数にeを指定します。マス目をクリックしたときのクリックしたtdのデータを取得することができます。
「let src = e.target;」で取得したデータを変数srcに代入します。
その後のif文は変数countがfalseのときは「src.textContent = "○";」でマス目に○が表示されるようにします。そしてcountにtrueを代入します。
elseの後はcountがfalseでないときはマス目に×を表示してcountにfalseを代入します。
countがfalseのときは○、trueのときは×を表示することを交互に行えるようにしています。

    function mark(e) {
      let src = e.target;
      if(count === false) {
        src.textContent = "⚪︎";
        count = true;
      } else {
        src.textContent = "×";
        count = false;
      }
    }
  </script>

以下のコードをコピーしてファイルに貼り付ければ試すことができます。

<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <title>marubatu</title>

  <style>
    table {
      border-collapse: collapse;
    }
    td {
      width: 100px;
      height: 100px;
      font-size: 60px;
      text-align: center;
      border: 1px solid black;
    }
  </style>

  <script>
    let count = false;

    function init() {
      for(let r = 0; r < 3; r++) {
        let tr = document.createElement("tr");
        for(let d = 0; d < 3; d++) {
          let td = document.createElement("td");
          td.onclick = mark;
          tr.appendChild(td);
        }
        table.appendChild(tr);
      }
    }

    function mark(e) {
      let src = e.target;
      if(count === false) {
        src.textContent = "⚪︎";
        count = true;
      } else {
        src.textContent = "×";
        count = false;
      }
    }
  </script>
</head>

<body onload="init()">
  <h2>⚪︎×ゲーム</h2>
  <table id="table"></table>
  <p>※ マスをクリックしてください。</p>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cypress でカスタムヘッダーを付ける方法

はじめに

Cypress を使ったE2Eテストに取り組んでおり、環境に応じたrequest headerを付ける必要があった。
公式ドキュメントに従ったやり方だとうまくheaderを付けることができなかったが、ワークアラウンドを見つけたのでQiitaにメモすることにする。

開発環境

  • cypress: 4.11.0

システム構成

Browser <--> CDN <--> Reverse Proxy <--> Application Server 

やりたかったこと

テストの特性上、CDNを経由せずReverse Proxyに直接接続したかったのだが、CDNを経由する際にcustom request headerが付与されるため、headerを明示的に付与する必要があった。

Cypressのドキュメントを確認するとrequest headerには2つの方法が用意されていたが、いづれも今回のユースケースには合わなかった。

方法1: visit()のオプションとしてヘッダーを渡す。

cy.visit(url, {
  headers: {'CUSTOM-HEADER':  'Header value'}
})

この方法の問題はドキュメントにも記載のあるとおり、initial request にしかヘッダーが付与されないこと。つまりHTML内の画像/JS/CSSなどのサブリクエストにはヘッダーが付与されない

Cypressから発せられるすべてのリクエストに対してカスタムヘッダーを付与したいため、このやり方だと今回のユースケースでは使えない。

方法2: server()のonAnyRequestをフックしてヘッダーを付与する。

cy.server({
  onAnyRequest: (route,  proxy) => {
    proxy.xhr.setRequestHeader('CUSTOM-HEADER',  'Header value')
  }
})

この方法はドキュメントに記載があるのだが、私が試したところコード自体は発動していいないように思える。または最初の一発だけ発動して他ではトリガーされない感じ、、、

ワークアラウンド:visit() + ModHeader Extension

テストはChromiumで動けば良いので、ModHeader というChrome拡張でヘッダーを付与できないか試したところ、visit()には付与されなかったが、サブリクエストには付与されることがわかった。

そのためあまりスマートな解決策ではないが、下記で対応することにした
1. initial request は visit()のオプションでカスタムヘッダーを付与
2. サブリクエストはModHeader で付与

initial request は visit()のオプションでカスタムヘッダーを付与

cy.visit(url, {
  headers: {'CUSTOM-HEADER':  'Header value'}
})

サブリクエストはModHeader で付与

  1. cypress panel からChromiumを起動
  2. Chromiumから ModHeader を検索し普通にChromeExtensionとして普通にインストール image.png
  3. インストールが完了したら ModHeader のアイコンをクリックし +ボタンをクリック -> Request header をクリック image.png
  4. カスタムヘッダーを登録する。 image.png

ModHeaderの設定内容はChromiumを閉じても維持されるため、次回Cypress経由でChromiumを起動したときはカスタムヘッダーが不要される設定の状態で起動する。

最後に

全くスマートさにかける解決策なので onAnyRequest の正しい使い方を教えてください。

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

Node.jsでTop-Level Awaitを試す

Node.jsでTop-Level Awaitがサポートされ(て)たので、非同期通信と言えばなaxiosで試してみます。

Top-Level Await

今まではawaitを利用する際に、async関数内じゃないと使えませんでしたが、async関数を宣言せずにawaitを使えるようになります。

v14.3.0でサポート、v14.8.0でフラグなし

Top-Level AwaitはNode.js v14.3.0でサポートされましたが、この時点だと--experimental-top-level-awaitのフラグを付けて実行する必要がありました。

v14.8.0以降でフラグ無しで利用できます。

axiosで利用してみる

(一応)今回試した環境はNode.js v14.14.0です。

package.jsonに"type": "module"を追記して利用できます。
また、拡張子をmjsにするだけでも利用できます。↓ではmjsで試してみます。

$ npm init -y
$ npm i axios
  • app.mjsを作成

ES Modules形式でimportします。

app.mjs
import axios from 'axios';

const res = await axios.get(`https://protoout.studio`);
console.log(res.data);

めちゃシンプルに書けますね。

  • 実行

実行も(.jsではなく).mjsのファイルを実行します。

$ node app.mjs 

参考: Top-Level Await support in Node.js v14.3.0

補足: 今までの書き方

今までだと、CommonJS 形式でモジュールを読み込み、async関数の中でawait呼び出しをするというのが通常だったと思います。

app.js
'use strict;'

const axios = require('axios');

(async () => {
    const res = await axios.get('https://protoout.studio');
    console.log(res.data);
})();

あと'use strict;'の表記もありますね。ESM形式だとStrictモードがデフォルトで有効なので省略できてます。

こちらは実行は通常通り。

$ node app.js 

所感

ちょっとしたことを試す時にasync関数を書くのは結構めんどくさかったので、Top-Level Awaitはありがたいですね。
Common JS(require)からES Modules(impot/from)への移行の流れもあるのでちょっとしたところから慣れていきたい。

.jsを使わずに.mjsを基本とする流れでも良いのかな...? この辺気になります。

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

#google-mapで検索が行えない。。

googlemapで住所検索を設定するまでの過程

↓こちらを参考にさせていただきました。誠にありがとうございます。
https://qiita.com/nagaseToya/items/e49977efb686ed05eadb

起こったエラー名

this api key is not authorized to use this service or api
(サービスやAPI使うに当たって適切な権限を持っていませんよ)*意訳

いろいろと調べてみて結果、、口を揃えて 「Geocoding API」が有効化されてないよ。。。という

*(Geocoding APIは住所位置を認証する権限、、みたいな)

①まず左にあるリストから『APIとサービス』を選びましょう

スクリーンショット 2020-10-18 11.43.59.png

②『認証情報』を選択

スクリーンショット 2020-10-18 11.44.11.png

③指定されたAPIキーを選びましょう

スクリーンショット 2020-10-18 11.44.29.png

④アプリケーションの制限を選択して保存

スクリーンショット 2020-10-18 11.44.49.png

以上です。上の検索画面からGeocoding APIと調べると、Geocoding APIを有効にするという項目が出ますが

これをやってもうまくいかなかったので上記の手順を踏んでみてください!

以上ご一読ありがとうございました!

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

Jotai: 状態?! 日本語っぽい名称のReact用ステート管理ライブラリ

はじめに

本記事ではJotaiというReactのライブラリを紹介します。名称は日本語の「状態」から来ています。最近、Zustandというライブラリのメンテナスを任されているのですが(紹介記事)、その議論の中でZustandの枠には収まらないアイデアが出てきて、じゃあもう一つ作ればいいじゃん、となりました。Zustandはドイツ語の「状態」なのですが、誰かが日本語の「状態」を調べて、Jotaiって言うんだ、npmでもまだ使われていないパッケージ名だ、となりました。

そんなこんなで9月にリリースしました。リリース時のツイートがこちら

Jotaiとは

Reactのステート管理のライブラリです。一言で言うと、Recoilに近いです。人によってはほぼ同じと言う感想を持つ人もいますし、人によってはRecoilのAPIは複雑すぎるけどJotaiのAPIはシンプルで違うと言う感想を持つ人もいます。JotaiはRecoilにインスパイアされて作られていますし、あえて似せている部分もあります。しかし、背景は少し違います。JotaiはContext APIのパフォーマンス問題を解決することが目的で、その際のAPIとしてatomが都合良かったということです。atomモデルのAPIを実現するだけであれば、ZustandのようにContext APIではなくReactの外にstoreを作る方が筋がいいかもしれません。

JotaiのAPI

JotaiのAPIはuseStateやuseContextに似ています。Jotaiが提供するAPIは Provider, atom, useAtom の3つです。ProviderはContext Providerのように使いますが、全atomで共通でありpropsを受け取りません。atomはatom(の設定オブジェクト)を生成します。useAtomはuseContextのように使いますが、contextの代わりにatomを指定します。

import { Provider, atom, useAtom } from 'jotai';

const textAtom = atom('hello');

const Component = () => {
  const [text, setText] = useAtom(textAtom);
  return (
    <input value={text} onChange={e => setText(e.target.value)} />
  );
};

const App = () => (
  <Provider>
    <Component />
  </Provider>
);

textAtomは他のコンポーネントでも使うことができます。その際はuseStateとは異なり値はProvider内で共有されます。

useStateからuseAtomに変更する例はこちら

派生atom

上記の利用方法だけでも一定の有用性はありますが、atomの有用性は派生atomにもあります。派生atomは、すでに定義されている一つまたは複数のatomから、新しいatomとして定義するものです。例えば、先ほどのtextAtomから次のような派生atomを作ることができます。

const uppercaseAtom = atom(get => get(textAtom).toUpperCase());

uppercaseAtomはtextAtomが変化した際にのみ再評価されるため、それをuseAtomで利用するコンポーネントの再レンダリングが最小限に抑えられます。

リポジトリ

GitHubのページはこちらです。

https://github.com/pmndrs/jotai

基本的な使い方はREADMEにコンパクトに書かれていますのでぜひご参照ください。

紹介YouTube

YouTubeに紹介動画を上げてくれた方がいるので、合わせてご覧ください。

おわりに

本記事では、あまり詳細な使い方に踏み込まず表面的な紹介をしました。興味がある方はまずこの基本的な使い方から試してみてください。より詳細なトピックとしては、atomの書き込みのカスタマイズ(writable atom)、非同期処理(suspense)やConcurrent Mode対応などがあります。少し触って慣れてきた後に試してみるのが良いでしょう。

質問などありましたら、お気軽に〜。

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

express.jsアプリをFirebase Functionsにデプロイする

前回:express.jsアプリをAWS CloudFormationでデプロイする

前提

  • Googleアカウント
  • firebaseプロジェクト
  • firebase-tools

プロジェクト初期化

$ firebase init functions

expressアプリ追加

  • firebase-functionsの読み込み
  • functions.https.onRequestイベントハンドラにアプリを渡す
  • exportする
// index.js
const functions = require('firebase-functions');
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  console.log(req.method, req.url);
  res.send({ status: true });
});

const api = functions.https.onRequest(app);

module.exports = { api };

ごくごく簡単なアプリ

確認

エミュレータ起動

$ firebase emulators:start

上のアプリの場合はhttp://localhost:5001/<YOUR PROJECT ID>/<REGION>/apiといったURLで確認できる。exportsに渡した名前がそのままpathになるようだ。

デプロイ

  • Firebase Functionsは従量課金(Blaze)プランでないとデプロイできない
$ firebase deploy --only functions

その他

  • 途中でつまづくようなところはなし。AWS CloudFormation(lambda + API Gateway)より簡単な印象。
  • 個人でさくっとなにかしたいならFirebaseがよさそう。

参考

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

ESPカードの作成

ESPカードの作成

今回はESPカードを作成します。STARTボタンをクリックするとカードの絵柄がシャッフルされるので、それぞれの絵柄が何か当ててください。カードをクリックすると表面にひっくり返ります。

button要素にonclick属性を付与して、ボタンをクリックしたときに「clicked」関数が発火します。

<body>
  <table id="table"></table>
  <p>※ ☆、○、×、△、□を当ててください
  </p>
  <button onclick="clicked()">START</button>
</body>

script要素内はclicked関数とflip関数に分かれます。
clicked関数内はカードをシャッフルする箇所とカードを並べる箇所に別れます。
flip関数はカードをクリックしたときに裏面から表面にひっくり返す機能があります。

  <script>
    let cards = ["", "", "×", "", ""];
    function clicked() {
      for(let i = cards.length - 1; i >= 0; i--) {
        let j = Math.floor(Math.random()*i+1)
        let k = cards[i];
        cards[i] = cards[j];
        cards[j] = k;
      }

      let table = document.getElementById("table");
      table.innerHTML = "";

      for(let l = 0; l < cards.length; l++) {
        let td = document.createElement("td");
        td.className = "back";
        td.value = cards[l];
        td.onclick = flip;
        table.appendChild(td);
      }
    }

    function flip(e) {
      let td = e.srcElement;
      td.className = "";
      td.textContent = td.value;
    }
  </script>

まず、配列cardsを作成して星や丸などの図形を要素に指定します。

for(let i = cards.length-1; i >= 0; i--) {
  ・
  ・
}

の箇所でカードをシャッフルします。
イメージとしては配列の一番最後の要素にランダムに別の配列の値を代入して、ランダムに選ばれた要素には最後の要素の値を代入することを繰り返します。最後の要素はfor文で繰り返すごとに一つずつ減っていきます。最終的にcards内の値だけシャッフルされます。

次にシャッフルされたカードを並べていきます。
「let table = document.getElementById("table");」でbody内のtable要素を取得します。「table.innerHTML = "";」はボタンを2回目以降クリックした際にそれまでのtable内の要素が残ったままになっているので、tableを空にしています。つまり、これをしないと画面に古いカードが残ったままになってしまいます。

      let table = document.getElementById("table");
      table.innerHTML = "";
for(let l = 0; l < cards.length; l++) {
  ・
  ・
}

の箇所でカード並べていきます。table要素に新たに作成したtd要素を追加していきます。td要素にはvalueプロバティにcards内の値を代入し、onclickプロパティに関数flipを代入します。

flip関数内は引数にeを指定することでカードをクリックした際にそのカードのデータを取得できるようにします。「let td = e.srcElement」で取得したデータを変数tdに代入します。「td.className = "";」でカードに付与されているclass属性を空にすることでカードを表面にします。「td.textContent = td.value;」で表面のカードに図形を表示します。

以下のコードをコピーしてファイルに貼り付ければ試せます。style要素内の.backセレクタに指定しているbackground-imageのurlの画像だけ自分で設定してください。

<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <title>ESPcard</title>

  <style>
    button {
      width: 100px;
      height: 30px;
    }
    td {
      width: 60px;
      height: 100px;
      border: 1px solid black;
      border-radius: 10px;
      font-size: 50px;
      text-align: center;
      vertical-align: middle;
    }
    .back {
      background-image: url(card.png);
      background-size: 60px 100px;
    }
  </style>

  <script>
    let cards = ["", "", "×", "", ""];
    function clicked() {
      for(let i = cards.length - 1; i >= 0; i--) {
        let j = Math.floor(Math.random()*i+1)
        let k = cards[i];
        cards[i] = cards[j];
        cards[j] = k;
      }

      let table = document.getElementById("table");
      table.innerHTML = "";

      for(let l = 0; l < cards.length; l++) {
        let td = document.createElement("td");
        td.className = "back";
        td.value = cards[l];
        td.onclick = flip;
        table.appendChild(td);
      }
    }

    function flip(e) {
      let td = e.srcElement;
      td.className = "";
      td.textContent = td.value;
    }
  </script>
</head>

<body>
  <table id="table"></table>
  <p>※ ☆、○、×、△、□を当ててください
  </p>
  <button onclick="clicked()">START</button>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Apps Script でよく使うスニペット集 30 選

はじめに

Google Apps Script では非常に簡単に Google Apps (スプレッドシート、 Google カレンダー、 Gmail、 Google Drive など) 間の連携ができます。例えば Google カレンダーのイベント一覧をスプレッドシートにエクスポートする際には以下のような処理の流れになります。

  1. Google カレンダーからイベント一覧を取得
  2. スプレッドシートに入力する形式にデータ変換
  3. Google スプレッドシートに値を挿入

1, 3 のような Google Apps から取得、挿入する部分はよく使い回す部分となるので、スニペットとして使い回すと便利です。今回は私がよく使うスニペット集を紹介しようと思います。

この記事は DevFest 2020 の「Google Apps Script 入門 2020」で紹介しました。

スプレッドシート

スプレッドシートへのアクセス(スタンドアローン型)

https://script.google.com ( or https://script.new ) からスクリプトを作成する場合には以下の形式でシートにアクセスできます。 Spreadsheet ID はスプレッドシート URL の以下の部分を指します。https://docs.google.com/spreadsheets/d/[ Spreadsheet ID ]/edit

const spreadsheet = SpreadsheetApp.openById("Spreadsheet ID")

スプレッドシートへのアクセス(インストール型)

特定のスプレッドシートからスクリプトを作る場合には以下の形式でもシートにアクセスできます。

const spreadsheet = SpreadsheetApp.getActive()

スプレッドシートから値取得

getDataRange を使うと範囲指定せずに勝手にデータが挿入されている範囲を指定してくれるので便利です。

const values = spreadsheet
  .getSheetByName('シート名')
  .getDataRange()
  .getValue()

シートへ値挿入

range には A1:C3 のような値を入れ、 values には 2 次元配列が入ります。

spreadsheet
  .getRange(range)
  .setValues(values)

カスタムメニュー追加

const onOpen = () => {
  SpreadsheetApp
    .getActiveSpreadsheet()
    .addMenu('メニュー名', [
      {name: 'サブメニュー1', functionName: '関数名1'},
      {name: 'サブメニュー2', functionName: '関数名2'},
    ])
}

参考記事: スプレッドシートにカスタムメニューを追加して、拡張機能を実装する方法

スプレッドシート編集時に発火

変更のあった Range や値などが取得できる。

const onEdit = e => {
  const { range, oldValue, value } = e
}

スプレッドシートの選択範囲変更時に発火

選択中の Range やユーザー情報などが取得できる。

const onSelectionChange = e => {
  const { range, user } = e
}

Google カレンダー

イベント作成

title は文字列、 start, end は Date 型で指定。

CalendarApp.getDefaultCalendar().createEvent(title, start, end)

イベント取得

start, end は Date 型で指定。

const events = CalendarApp
  .getDefaultCalendar()
  .getEvents(start, end)
  .map(event => ({
    title: event.getTitle(),
    description: event.getDescription(),
    start: event.getStartTime(),
    end: event.getEndTime()
  }))

上記の値 (title, description など) 以外の取得は CalendarEvent クラスを参照
https://developers.google.com/apps-script/reference/calendar/calendar-event

共有カレンダーへのアクセス

実行アカウントのデフォルトカレンダー以外のカレンダーにアクセスする場合には、カレンダー ID を指定してカレンダーにアクセス。

const calendar = CalendarApp.getCalendarById('Calendar ID')

Gmail

メール送信

GmailApp.sendEmail('tanabee@example.com', '件名', '本文')

スレッド検索

最新の 10 件のスレッド取得

const threads = GmailApp.search('', 0, 10)

第一引数に入れる検索演算子は以下を参照
https://support.google.com/mail/answer/7190?hl=ja

メッセージ一覧取得

const messages = GmailApp.search('', 0, 10).flatMap(thread => 
  thread.getMessages().map(message => ({
    subject: message.getSubject(),
    body: message.getBody(),
    date: message.getDate(),
    from: message.getFrom(),
    to: message.getTo(),
  }))
)

上記の値 (subject, body など) 以外の取得は GmailMessage クラスを参照
https://developers.google.com/apps-script/reference/gmail/gmail-message

Google ドライブ

特定フォルダ配下のフォルダ、ファイル取得

const folder = DriveApp.getFolderById('Folder ID')

const files = folder.getFiles()
while (files.hasNext()) {
  let file = files.next()
  console.log('file: ', file.getName())
}

const folders = folder.getFolders()
while (folders.hasNext()) {
  let folder = folders.next()
  console.log('folder: ', folder.getName())
}

参考記事: Google Apps Script で Google Drive のフォルダ配下のオーナー権限を一括で譲渡する

Google フォーム

フォーム送信内容の取得

const onSubmit = event => {
  const answer = event.response
    .getItemResponses()
    .map(itemResponse => ({
      item: itemResponse.getItem().getTitle(),
      response: itemResponse.getResponse()
    }))
}

フォーム送信時に onSubmit 関数を発火させるために、別途トリガー設定が必要
参考記事: Google Apps Script ハンズオン資料

Google ドキュメント

ドキュメントへのアクセス(スタンドアローン型)

https://script.google.com からスクリプトを作成する場合には以下の形式でドキュメントにアクセスできます。 Document ID は URL の以下の部分を指します。https://docs.google.com/document/d/[ Document ID ]/edit

const doc = DocumentApp.openById('Document ID')

ドキュメントへのアクセス(インストール型)

特定のドキュメントからスクリプトを作る場合にはこちらの形式でもドキュメントにアクセスできます。

const doc = DocumentApp.getActiveDocument()

カスタムメニュー追加

const onOpen = () => {
  DocumentApp
    .getUi()
    .createMenu('メニュー')
    .addItem('アイテム', '関数名')
    .addToUi()
}

Utilities

日付のフォーマット指定

const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'YYYY-MM-dd HH:mm:ss')

sleep

1 秒間スリープしたい場合は以下のように書く。

Utilities.sleep(1000)

UUID 生成

const uuid = Utilities.getUuid()

トリガー

1 分後に特定の関数を実行

6 分の実行時間制限にかかる場合、残り 1 分等のタイミングで次の実行を予約すると便利です。

const date = new Date()
date.setMinutes(date.getMinutes() + 1);// 1 分後
ScriptApp.newTrigger('関数名').timeBased().at(date).create();

PropertiesService (プロパティ管理)

プロパティから値取得

PropertiesService.getScriptProperties().getProperty('キー名')

プロパティへ値保存

PropertiesService.getScriptProperties().setProperty('キー名', '')

LanguageApp (翻訳)

翻訳

const text = LanguageApp.translate('Hello World', 'en', 'ja')

参考記事: 3 分で作る無料の翻訳 API with Google Apps Script

Web 公開 (Web サイト、 API 等)

Web サイト(HTML テンプレートなし)

const doGet = e => {
  const params = JSON.stringify(e.parameter)
  return HtmlService.createHtmlOutput(params)
}

Web サイト(HTML テンプレートあり)

const doGet = e => {
  return HtmlService.createHtmlOutputFromFile('index')
}

別途 index.html を用意する。

GET API

引数 e の e.parameter にクエリストリングの値が入っている。

const doGet = e => {
  const params = JSON.stringify(e.parameter)
  return ContentService
    .createTextOutput(params)
    .setMimeType(MimeType.JSON)
}

POST API

引数 e の e.postData.contents に request body のデータが入っている。

const doPost = e =>  {
  const body = e.postData.contents
  return ContentService
    .createTextOutput(body)
    .setMimeType(ContentService.MimeType.JSON)
}

UrlFetchApp

UrlFetchApp を使うと外部の API にリクエストすることができる。

GET API リクエスト

const content = UrlFetchApp.fetch(url).getContentText()

POST API リクエスト

const res = UrlFetchApp.fetch(url, {
  method: 'POST',
  headers: { "Content-Type": 'application/json' },
  payload: JSON.stringify(data)
})

まとめ

以上個人的によく使う処理をまとめました。厳密には 31 個ありそうですが、今後もちょっとずつ足していくかもしれないのでアバウトに 30 選としました。よかったら Google Apps Script のコードを書く時に参考にしてみてください。

関連記事

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