20200923のJavaScriptに関する記事は27件です。

MQTTクライアントをコマンドラインで簡単に実行する(npx と MQTT.js)

はじめに

こちらのツイートをきっかけに、ふと思いついて試した内容のメモです。

【追記】 @n0bisuke さんも記事を書かれていたので、リンクを掲載します。
 ●グローバルインストールせずにMQTTをCLIで試す - Qiita
  https://qiita.com/n0bisuke/items/13d1442f3cb67664ef60

@n0bisuke さんの記事では、自分の記事では触れていない PubNub の場合の事例も書かれてますので、是非ご覧ください。

MQTTクライアントをコマンドラインで簡単に動かす

IoT系のプロトタイプを作るときに、最近はよく MQTT を使っていました。
そして、以下のような様々な環境で MQTT のクライアントを動かしていました。

  • Node.js
  • HTML+JavaScript で作られた Webサイト
  • Node-RED
  • UIFlow (M5Stack向けのビジュアルプログラミング環境)
  • Mac や Windows の上で動く GUIフロントエンドとなるアプリ
  • ターミナル上などコマンドを使った形

上記の Node.js や Webサイト(HTML+JavaScript)で実装するときは、パッケージ・ライブラリに MQTT.js をよく使っていました。また、Mac のターミナル上のコマンドラインツールで動作させる時も MQTT.js を使いました。

mqttjs_Command_Line_Tools.jpg

この記事の冒頭のツイートを見た時に、この MQTT.js のコマンドラインツールを思い出し、ふと「MQTT.js のコマンドラインツールが使えてしまうのではないか?」と思って試しました。ローカルに mosquitto で MQTTブローカーをサクッと実行できる環境はあったので、 npx mqtt sub -t 'test' -h 'localhost' とか npx mqtt pub -t 'test' -h 'localhost' -m 'from MQTT.js' とかを実行してみて、Pub/Sub の両方とも動作するのを確認し、この記事の冒頭のツイートに以下のコメントをしました。

【追記】 MQTT.js のコマンドラインツールを npx で実行した時の挙動の補足

MQTT.js がインストールされていない環境で実行

サブスクライブのほうは、 npx mqtt sub -t '【トピック】' -h '【ホスト名】' とすると、MQTTブローカーへの接続を維持した状態になり、メッセージを受信し続けてくれてます。

パブリッシュのほうは、 npx mqtt pub -t '【トピック】' -h '【ホスト名】' -m '【メッセージ】' を実行すると、1回パブリッシュした後に処理が終了します。その処理終了の際にパッケージのファイル一式が削除されるので、その後コマンドを実行するごとにインストールが行われるという挙動になります。

MQTT.js をローカルインストールしたフォルダ上で実行

npx は、パッケージがインストールされていない環境だと、処理の実行に必要なパッケージをコマンド実行時に一時的にインストールして、処理が終わるとインストールしたファイルを破棄します。
もし、処理の実行に必要なパッケージがローカルインストールされている場合、そのローカルのパッケージを利用して処理を実行します(インストールされたフォルダ内でコマンドを実行する必要あり)。

そのため、自分でローカルインストールしたファイル一式を手動削除する必要が出てきますが、この方法ならパブリッシュするごとにインストールするような挙動は避けられます。インストールしていない状態での実行、という手軽さが失われてしまいますが・・・。

冒頭のツイートのコメントでのやりとりでも、少し触れてました。

【追記】 npx について

npx を初めて使ってみようと思ったきかっけは、おそらくこちらの記事を見た時でした。

●npm 5.2.0の新機能! 「npx」でローカルパッケージを手軽に実行しよう - Qiita
 https://qiita.com/tonkotsuboy_com/items/8227f5993769c3df533d

npx の利用について、自分は「何か特定のパッケージのコマンドライン実行を、ずっと使わないかもしれないけれど、とりあえずは試してみたい」というときなどに、以下のような使い方をしてたりします。

1) 適当にパッケージのお試しをするフォルダを作る
2) 上記1)で作ったフォルダで npm install 【パッケージ名】 でパッケージをローカルインストール
3) `npx 【パッケージ名】' でローカルインストールしたパッケージを実行
4) お試しが終わったら、上記1)で作ったフォルダを丸ごと削除

ローカルインストールした場合、コマンドを実行したフォルダ直下に node_modules フォルダが作られて、その下にファイルが置かれるような構成になるので、コマンドを実行したフォルダを削除するだけでサクッとお試し用に追加されたファイル一式を丸ごと削除できます。

おわりに

他にも、Node.js のパッケージでコマンドラインツールが使えるようなものがあったと思うので、MQTT.js 以外でも便利に使えそうなものがないか試してみようと思います。

【追記】 wscat(WebSocket関連)で試してみて、記事を書きました。

●WebSocket のサーバー・クライアントをコマンドラインで簡単に実行する(npx と wscat) - Qiita
 https://qiita.com/youtoy/items/72bbd5f6b0893756da36

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

今更聞けないJavaScriptのクラス

業務中にJavaScriptのクラスが出てきて、自分の中で咀嚼できていないな、と感じたのでメモがてらJavaScriptのクラスについて書いていこうかなと思います。

基本構文

test.js
// class
class test {
  constructor(text) {
    this.text = text;
  }  
}

// classを使ってObjectを生成
let testObj = new test("test object");

// constructorで設定した値を表示してみる
console.log(testObj.text)

上記がclassの基本文法になります。
classとコメントが書かれたした5行がクラスになります。
classの中身にはconstructorというものが設定されています。
constructorというのはクラスがnewされたタイミングで実行され、引数をオブジェクトに保持させます。
今回だとtextにtest objectという値を保持させるようにしています。
そのためこちらのコードを実行すると、ログにtest objectと表示されます。

メソッド

続いてクラスにメソッドを追加します。

test.js
class test {
  constructor(text) {
    this.text = text;
  }
  // メソッド
  showText() {
    console.log(this.text);
  }
  // 静的メソッド
  static staticMethod() {
    console.log("static method")
  }
}

let testObj = new test("test obj")

// メソッドを実行する
testObj.showText()

// 静的メソッドを実行する
test.staticMethod()

classの中にメソッドを定義できます。
この時、functionという記述は不要です。
オブジェクト名.メソッド名()で実行できます。
また、staticをつけたメソッドは静的メソッドといい、クラス名.メソッド名()で実行ができます。

その他機能

getter, setter get/setをつけた関数をclass内で宣言することで使える。該当のメソッドを属性値にように呼べたりするので便利。
extends クラスはextendsを使えば継承することができる。宣言するクラス名の後ろでextends 継承する親クラスと書くことでできる。親クラスのメソッドや変数を子が使えるようになる。
super 親のコンストラクタを呼び出す時に使う。
クラス式 クラス名を持たないクラスを作成できる。

test.js
let test = class {
  constructor() {
    this.text = "text":
  }
}
console.log(test.text);

静的メソッドを使うタイミング

静的メソッドはオブジェクトを生成しなくても呼ぶことができます。
また、オブジェクトを生成してしまうと呼ぶことができません。
そのため、静的メソッドはUtilなどの関数を作るのに使われます。

個人的な所感

classはちゃんと使えるといい感じにコードがまとまりそう。
まだどんな時に使えばいいか(特にクラス式で匿名クラスを作るのはいつかなど)がしっかりとわかっていないので手を動かして覚えていきたい。
今回paiza.ioを使って動作を確認したが、とてもいいのでおすすめ。

参考文献

クラス
JavaScript初心者にclassを伝える

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

Vue.js~jestを使ってのテスト~

はじめに

なかなかできなくてやっと簡単なテストならできたので
忘れないうちにメモ
間違いがあったらご指摘いただけると嬉しいです。

テスト対象

<template>
  <div>
    <p>{{num}}</p>
    <button class="increment__button" @click="increment">足す</button>
    <button class="decrement__button" @click="decrement">引く</button>
    <button class="reset__button" @click="reset">リセット</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      num: 0,
    };
  },
  methods: {
    increment() {
      return this.num++;
    },
    decrement() {
      if(this.num <= 0) return
      return this.num--;
    },
    reset() {
      return (this.num = 0);
    },
  },
};
</script>

ボタンをクリックで数字を足したり、引いたり
ただそれだけの物。

そしてこれをテストしてみる

テストコード

import Test1 from '../../src/components/test1'
import {mount} from '@vue/test-utils'

const wrapper = mount(Test1)
const vm = wrapper.vm
describe('test1', () => {
  it('初期値は0?', () => {
    expect(wrapper.html()).toContain('<p>0</p>')
  })
  it('buttonがあるか?', () => {
    expect(wrapper.contains('button')).toBe(true)
  })
})

describe('test2', () => {
  it('increment', () => {
    const button = wrapper.find('.increment__button')
    expect(vm.num).toBe(0)
    button.trigger('click')
    expect(vm.num).toBe(1)
    button.trigger('click')
    expect(vm.num).toBe(2)
  })
  it('decrement', () => {
    vm.num = 1
    const button = wrapper.find('.decrement__button')
    button.trigger('click')
    expect(vm.num).toBe(0)
    button.trigger('click')
    expect(vm.num).toBe(0)
  })
  it('reset', () => {
    vm.num = 5
    const button = wrapper.find('.reset__button')
    expect(vm.num).toBe(5)
    button.trigger('click')
    expect(vm.num).toBe(0)
  })
})

まず始めにテスト対象のコンポーネントをmount()の引数に入れて
それをwrapperとして色々するみたい

コンポーネントから取得

const button = wrapper.find('.increment__button')

これはTest1コンポーネント内のclass="increment__button"を取得
JavaScriptのdocument.getElementById()みたいな感じ?
そして今回のだとclickイベントが各ボタンにあるので、それをtrigger()を使う事で実行できる

button.trigger('click')

dataの取得

wrapper.vm.num//0
wrapper.vm.num = 10
wrapper.vm.num//10

これでコンポーネントのdatanumの値を取得
代入もできる

以上を踏まえてもう一度確認

const button = wrapper.find('.increment__button')
    expect(vm.num).toBe(0)
    button.trigger('click')
    expect(vm.num).toBe(1)
    button.trigger('click')
    expect(vm.num).toBe(2)

最初の結果はなにもしてないので(0)
次にclickイベントが実行されるのでインクリメントされた結果が(1)
となる。
残り二つのボタンもやってることはほぼ同じ
今わかってるのはここまで。

Vuex使用時とかのテストをできなきゃなぁ。。。

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

【Vue.js】Firebase独自のメソッドをまとめてみよう

firebaseモジュールのセットアップ

$ yarn add firebase --save
main.js
import firebase from "firebase";

const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: ""
};
firebase.initializeApp(config);

コンポーネントでの準備

router/index.js
import signup from "@/components/signup";

export default new Router({
  routes: [
    {
      path: "/signup",
      component: signup
    },
  ]
})
components/signup.vue
# コンポーネント毎に、モジュール読み込み
import firebase from "firebase";

Usage ユーザー作成、認証系

サインアップ

signUp: function() {
  firebase
    .auth()
    .createUserWithEmailAndPassword(this.email, this.password)
    .then(user => {
      alert(user.email);
    })
    .catch(error => {
      alert(error.message);
    });
}

サインイン

signIn: function() {
  firebase
    .auth()
    .signInWithEmailAndPassword(this.email, this.password)
    .then(user => {
      alert("success");
      this.$router.push("/profile");
    })
    .catch(error => {
      alert(error.message);
    });
}

ログイン中ユーザーのデータ取得

data () {
  return {
    email: firebase.auth().currentUser.email
  }
}

ユーザーがログイン中なら、ユーザー情報を返すメソッド(ログインチェック)

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    //ユーザーログイン時の処理
  } else {
    //ユーザーログアウト時の処理
  }
})

Usage クラウドデータベース

firestoreの全件データを取得

firebase.firestore().collection("message").get()
  .then(function(query) {
    query.forEach(function(doc) {
      console.log(doc.data());
    });
  }).catch(function(e) {
    alert(e);
  });

firestoreの全件データの中から、条件を絞って取得

firebase.firestore().collection("message").where("name", "==", "tom").get()
  .then(function(query) {
    query.forEach(function(doc) {
      console.log(doc.data());
    });
  }).catch(function(e) {
    alert(e);
  });

firestoreにデータを追加

firebase.firestore().collection("message").add({
  name: this.nameData
})
  .then(function() {
    alert("成功しました");
  }).catch(function(e) {
    alert("失敗しました");
  });

firestoreのデータを削除

firebase.firestore().collection("message").doc("directMessage").delete(
  .then(function() {
    alert("成功しました");
  }).catch(function(e) {
    alert("失敗しました");
  });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

# DMM英会話の予約ページをカスタマイズしてみた

DMM英会話の予約ページをカスタマイズしてみた

やったこと

  • DMM英会話を毎日続けているが予約ページが少し使いづらい
  • Google Chrome拡張のTamperMonkeyでカスタマイズして自分にとっての使い勝手を向上する

※ TamperMonkeyとはGoogle Chromeの拡張機能でユーザの任意のスクリプト(通称Greasemonkey scripts)を任意のページにて追加することができる。

追加した機能

  • 毎日の決まった時間に予約日時を変更するdailyボタンを追加する
    • (後ほどブックマークでよかったと気づく)
  • 開始時刻を変えた時に終了時刻を連動させる

デモ

スクリーンショット 2020-09-23 22.02.56.png

書いたコード

 // ==UserScript==
// @name         dmm english daily reserve
// @version      0.1
// @description  daily reserve button and change end time as start time.
// @author      tomyam
// @match       https://eikaiwa.dmm.com/list/*
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.13.0/jquery.min.js
// ==/UserScript==

 $('h1').append('<br><button id="daily">daily button</button>');

// daily button to set daily schedule.
 $('#daily').click(function () {
     $('#start_time').val('13:00');
     $('#end_time').val('13:00');
     $('#e_list_index_select_gender1').val(2);
});

// end time changes when start time is changed. 
 $('#start_time').change(function () {
     $('#end_time').val($('#start_time').val());
     $('#e_list_index_select_gender1').val(2);
});

今後

  • 時刻だけ指定して完全に自動で予約できるようにする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】デカい定数を遅延で読み込む

const HOGEHOGE = バカみたいにメモリを食う定数;

const getHOGEHOGE = function() {
    return バカみたいにメモリを食う定数;
};

として定数を利用するときにgetHOGEHOGEメソッドを呼び出せば
その時だけ読み込まれてメモリの節約になるよん、という
恐らく世界で今の俺だけにしか得のない話でした。

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

[JavaScript] 破壊的メソッドと非破壊的メソッド

JavaScriptの破壊的メソッドと非破壊的メソッドとはなにか、簡単にまとめようと思います。

破壊的メソッド(非破壊的メソッド)とは

破壊的メソッドとは レシーバであるオブジェクトそのものに変更を加えるメソッドのことです。
非破壊的メソッドは、その場の実行結果のみに影響しますが、破壊的メソッドは自分自身に変更を加えてしまいます。

以下具体例

破壊的メソッド①

push() の場合

以下の例は、配列に対し直接要素を追加しています。
結果変数の値がどうなったのかがわかりにくく、バグの温床となりかねないので注意が必要です

例1
const test = ["foo", "bar"];
const result = test.push("baz");

console.log(test); // ["foo", "bar", "baz"]

非破壊的メソッド①

concat()

上記のpush()で行ったことを、非破壊的メソッドを使って行います

例2
const test = ["foo", "bar"];
const result = [].concat(test, "baz");

console.log(test); // ["foo", "bar"]
console.log(result); // ["foo", "bar", "baz"]

非破壊的メソッドは元の配列 test を変更しないため、安全かつ直感的に理解がしやすいです
  

破壊的メソッド②

sort() の場合

以下の例は、配列の並び順を直接変更しています。

例3
const test = [3, 2, 1];
const result = test.sort();

console.log(test); // [1, 2, 3]
console.log(result); // [1, 2, 3]

非破壊的メソッド②

上記のsort()で行ったことを、スプレッド構文を利用して非破壊的に実装できます

例4
const test = [3, 2, 1];
const result = [...test].sort();

console.log(test); // [3, 2, 1]
console.log(result); // [1, 2, 3]

スプレッド構文で新たな配列を生成し、それに対してsort()をしているため
元の配列は変更せず、非破壊的にsort()を使用できます。

まとめ

 
破壊的メソッドは元の変数を変更してしまうものであり、変数が今どうなっているのかをわかりにくくします。
結果的にバグの温床やコードの可読性を下げる恐れがあるため、
なるべく意識的に非破壊的メソッドを使用するのが良いでしょう。

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

Yarn: ○○が依存している□□だけをアップデートする

npmパッケージの中で、node-forge というライブラリに脆弱性があったので、それだけアップデートしようとしました。

% yarn upgrade node-forge

ところが、yarn.lock には何も変化なし。

webpack-dev-server が selfsigned に依存していて、selfsigned が node-forge に依存しているという関係です。

% yarn upgrade selfsigned

これでも何も起きず。

yarn.lock を直接開き、該当するパッケージの情報を削除してから yarn install するとうまくいきました。

yarn.lock
- node-forge@0.9.0:
-   version "0.9.0"
-   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
-   integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==

- selfsigned@^1.10.7:
-   version "1.10.7"
-   resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b"
-   integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==

参考: https://github.com/yarnpkg/yarn/issues/4986#issuecomment-395036563

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

IntersectionObserver 遅延読み込み

IntersectionObserver

Intersection Observer APIは直訳すると交差監視APIという名前です。これは要素と要素の交差を検知するAPIになります。(参照: https://ics.media/entry/190902/)

従来の遅延読み込み

従来はscrollイベントや
lazyloadといったライブラリなどを使って遅延読み込みを行います。

なぜInterSectionObserverを使ったか

・SEO的に良さそうだから
 Googlebotが認識できる
・パフォーマンス的に良さそうだから
 scrollイベントのように絶えず要素の位置を確認することを避けられる
*参照: https://liginc.co.jp/498877

その他にも、個人的に
・ライブラリを読み込まず生のJavaScriptで実装できる
・JavaScriptを書くので実装している感が味わえるのではないか

と思い、今回使ってみました

実際に使ってみた

See the Pen qBZLyzX by なかもと ゆうと (@nakamoto_yuto) on CodePen.

感想

思ったより簡単にシンプルに実装できて楽でした:v_tone4:
生のJavaScriptで書いているので実装感があり楽しかったです。
Babel polyfillでIE対応そのままできてくれないかなーと願っていたのですがさすがにそこまでは無理でした:baby_tone5:
もし,いやbabel polyfillでいけるよ?という方いらっしゃいましたらぜひご教授していただけるととても嬉しいです。

IE対応

今回はintersection-observerというnpmを使用し,IE対応を行いました。
npmのドキュメントにはいくつか対応の方法が書かれていまして、実際に行なった方法を最初に紹介し、その後にその他の方法を紹介していきます。

今回の方法(npmをインストールし対応)

//terminalにて intersection-obseverをインストール
$npm install --save-dev intersection-observer

インストール後遅延読み込みを行うjsファイルにて

import 'intersection-observer'; // intersection-observerをimportし対応。

const lazyLoad = function () {
    // ターゲット指定
    const targets = Array.from(document.querySelectorAll("img[data-src]"));
    const img_path = "data-src";
    const options = {
      // 上下100px手前で発火
      rootMargin: "100px 0px"
    };
.....下記省略

もしくは

require('intersection-observer'); 

const lazyLoad = function () {
    // ターゲット指定
    const targets = Array.from(document.querySelectorAll("img[data-src]"));
    const img_path = "data-src";
    const options = {
      // 上下100px手前で発火
      rootMargin: "100px 0px"
    };

記述量かなり少なく済んだので楽でした!

HTML内でnpmを読み込み対応

<!-- Load the polyfill first.(IE対応するためのnpmを最初に読み込む) -->
<script src="path/to/intersection-observer.js"></script>

<!-- Load all other JavaScript. -->
<script src="app.js"></script> <!-- 遅延読み込みを行うjsファイル -->

モジュールバンドラーにて読み込みを行い対応(Webpack or Browserify)

// Require the polyfill before requiring any other modules.
require('intersection-observer');

require('./foo.js');
require('./bar.js');

おわりに

IntersectionObserver 他に比べて導入コストもほとんど変わらず楽にできるし,
jsを書いている感覚で楽しいのでぜひ一度も使ってみたことない方いらっしゃいましたら使ってみることをおすすめします!:muscle_tone2:

参照記事

ics.media JSでのスクロール連動エフェクトには
Intersection Observerが便利

画像を遅延読み込みしてみよう!Intersection Observer編
lazyload

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

【超解説】Azure AD B2C でシングルページ Web アプリケーションを作成する

Azure AD B2C は、企業-消費者間 ID をサービスとして提供する Microsoft のサービスです。Azure AD と聞くと、どーせマイクロソフトアカウントしか使えないんでしょ?と想像する方もいらっしゃるかもしれませんが、Azure AD B2C は好みのソーシャルアカウントを使用することもできますし、ローカル ID を使用することもできます!
image.png
今回は Microsoft が提供しているドキュメントを基に、どうやって Azure AD B2C 環境を構築するのか見ていきたいと思います。

サンプルのリソース

今回試して見るサンプルは以下の GitHub に上がっているソースコードを使用します。Javascript ベースのシングルページアプリケーションです。
https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp

このサンプルは既にデモの Azure AD B2C 環境で動くように作成されています。Git Clone でダウンロードして、自身のマシンで実行するとどんな感じに動くのか試すことができます。
(このアプリケーションを動かすには Node のインストールが必要です。)

サンプルコードをデモ環境で実行してみる

自身で作成したオリジナルの Azure AD B2C 環境を試す前に、まずはサンプルコードそのままでデモ環境を使用して動かしてみます。

サンプルコードを Git Clone する

Git がインストールされた環境で、以下 Git Clone を実行してみます。

git clone https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp.git

アプリケーションを実行する

Node がインストールされた環境かどうか確認してください。(Node -v とかで確認できます。)
クローンされたディレクトリに移動し、Node アプリケーションを以下コマンドで実行します。

cd active-directory-b2c-javascript-msal-singlepageapp
npm install && npm update
npm start

アプリケーションが実行されたら、「http://localhost:6420/」にブラウザからアクセスしてみます。以下のような Azure AD B2C の Sign In ボタンがあるような画面が表示されるかと思います。
image.png
Sign In ボタンを押すと、以下のように、サインイン画面がポップアップウィンドウで表示されます。アドレスをみると 'fabrikamb2c' というテナントに向けてサインインを実行しているのだと推測できます。サンプルでは fabrikam というデモ用のテナントが準備されているのだとわかります。
image.png
これと同じ動きを、自分自身が作成したオリジナルの Azure AD B2C テナントで実行する場合の方法を次に見ていきたいと思います。

オリジナルの Azure AD B2C 環境を使用してアプリケーションを実行する

Azure AD B2C 環境を作成する

Azure Portal にアクセスし、新しいリソースの作成から Azure AD B2C を選択します。
image.png
作成する Azure AD B2C の組織名、初期ドメイン名を入力して、確認および作成 をクリックします。今回はテストとして以下の値を入力しました。

タイトル 入力値
組織名 mikogarage
初期ドメイン名 mikogarage
国/地域 日本

image.png
検証が無事終わると、作成 ボタンを選択できるようになるのでクリックします。テナントの作成が開始されますが、だいたい3,4分かかるかと思います。
image.png
作成されたらこんな感じの Azure AD B2C のトップページに遷移することができます。ここまでで Azure AD B2C テナントの作成は完了です!
image.png

Azure AD B2C にアプリを登録する

次に今回作成するアプリケーションを Azure AD B2C に登録します。左側のメニューの 管理 から アプリ登録 を選択し、上の 新規登録 をクリックします。
image.png
アプリケーションの登録画面に移ります。今回は以下のように値を入力しました。

タイトル 入力値
名前 SampleApp
サポートされているアカウントの種類 Accounts in any identity provider or organizational directory (for authenticating users with user flows)
リダイレクト URI http://localhost:6420

image.png
アプリの登録が完了すると、作成したアプリの認証の部分を少し編集します。デフォルトでは、暗黙の付与 というところにチェックが外れているので、どちらもチェックを入れ保存します。
image.png
ここまでで、アプリの登録は完了です。後ほどアプリのコードでこのアプリ登録したクライアントIDの情報を入力するので、概要 に戻り、Client ID をコピペしておきます。
image.png

ポリシーを作成する

ここでいうポリシーとはユーザーフローのことです。Azure AD B2C のトップに戻り、左側のメニューから、ユーザーフロー を選択します。
image.png
新しいユーザーフローを設定します。ここは GUI がしっかりしているので簡単にできると思います。ここで認証に必要な情報 (表示名や役職名など) を設定します。ユーザーフローの設定方法は以下のドキュメントにそのまままとまっているので必要に応じてご覧ください。
https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows
image.png
今回作成したユーザーフローは3つで、以下の通りです。

ユーザーフローの名前 タイプ
B2C_1_passwordreset1 パスワードのリセット (推奨)
B2C_1_profileediting1 プロファイルの編集 (推奨)
B2C_1_signupsignin1 サインアップとサインイン (推奨)

API を構成する

次に、Azure AD B2C で API を使用できる環境を作成します。以下の Microsoft Docs が参考になります。
https://github.com/Azure-Samples/active-directory-b2c-javascript-nodejs-webapi

WebAPI サンプルコードのダウンロード

以下のように Git Clone を実行して、自身のマシンにソースコードをダウンロードします。

git clone https://github.com/Azure-Samples/active-directory-b2c-javascript-nodejs-webapi.git

コードは後で編集するので、とりあえずこここではダウンロードだけでOKです!

Azure AD B2C で WebAPI を登録する

アプリケーションの登録から、webapi1 という新しいアプリケーションを登録します。
image.png
作成したら、左メニューの API の公開 から アプリ ID の URI を設定します。ここでは api とだけ入力します。
image.png

スコープの追加

次にスコープを設定します。+ Scope の追加 から二つの Scope を追加します。
値はこんな感じで入力しました。

スコープ名 管理者の同意の表示名 管理者の同意の説明
demo.read Read access to demo API Allows read access to the demo API
demo.write Write access to demo API Allows write access to the demo API

image.png
image.png
ふたつとも正しく入力できるとこんな感じになります。
image.png

SmapleApp のアプリケーションに戻り、右側のメニューの API のアクセス許可 を選択します。ここで今追加した API を追加します。+アクセス許可の追加 を押すと、先ほど追加した二つの API が出てくるので、二つとも選択し アクセス許可の追加 をクリックします。
image.png
これだけではアクセスが付与されていません。 XXXXX(テナント名)に管理者の同意を与えます というボタンがクリックできるようになりますので、それをクリックします。
image.png
状態が緑になり、XXXXXX(テナント名)に付与されました と緑で出てくれば成功です。
image.png

サンプルコードの編集

サンプルコードを Visual Studio Code で開きます。コードの中の config.js を開き以下のように編集します。

config.js
const clientID = "253cc4e6-4b4e-4ea1-9568-ba75aa6028aa"; // webapi1 のクライアント ID に置き換える
const b2cDomainHost = "mikogarage.b2clogin.com"; //Azure AD B2C のテナント名に書き換える
const tenantId = "mikogarage.onmicrosoft.com"; // ここも同じ
const policyName = "B2C_1_signupsignin1"; //設定したポリシー名に書き換える

const config = {
    identityMetadata: "https://" + b2cDomainHost + "/" + tenantId + "/" + policyName + "/v2.0/.well-known/openid-configuration/",
    clientID: clientID,
    policyName: policyName,
    isB2C: true,
    validateIssuer: false,
    loggingLevel: 'info',
    loggingNoPII: false,
    passReqToCallback: false
}

module.exports = config;

API の方で変更する部分は上記の部分のみです。

API アプリケーションを実行する

クローンしたディレクトリに移動して、以下コマンドを実行し、Web API を実行します。

npm install && npm update
npm start

実行すると色々コメントが出てきますが、アプリケーションが実行され、Listening on port 5000 と表示されたら成功です。

Listening on port 5000

ここまでで WebAPI の方の準備が整いました。

アプリケーションのコードを編集する

WebApp のサンプルコードは JavaScriptSPA 下の 3 つのファイルを編集します。

apiConfig.js
authConfig.js
policies.js

authConfig.js
const msalConfig = {
  auth: {
    clientId: "625ebccb-ed3a-4324-94db-e2681e8c8a1e", // ここを SampleApp の Client ID に変更
    authority: b2cPolicies.authorities.signUpSignIn.authority,
    validateAuthority: false
  },
  cache: {
    cacheLocation: "localStorage", // This configures where your cache will be stored
    storeAuthStateInCookie: false // Set this to "true" to save cache in cookies to address trusted zones limitations in IE (see: https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/Known-issues-on-IE-and-Edge-Browser)
  }
};

/** 
 * Scopes you enter here will be consented once you authenticate. For a full list of available authentication parameters, 
 * visit https://azuread.github.io/microsoft-authentication-library-for-js/docs/msal/modules/_authenticationparameters_.html
 */
const loginRequest = {
  scopes: ["openid", "profile"],
};

// Add here scopes for access token to be used at the API endpoints.
const tokenRequest = {
  scopes: apiConfig.b2cScopes,  // e.g. ["https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read"]
};
policies.js
// Enter here the user flows and custom policies for your B2C application
// To learn more about user flows, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview
// To learn more about custom policies, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-overview

const b2cPolicies = {
    names: {
        signUpSignIn: "B2C_1_signupsignin1", //作成したポリシー名に変更
        forgotPassword: "B2C_1_passwordreset1", //作成したポリシー名に変更
        editProfile: "B2C_1_profileediting1" //作成したポリシー名に変更
    },
    authorities: {
        signUpSignIn: {
            authority: "https://mikogarage.b2clogin.com/mikogarage.onmicrosoft.com/B2C_1_signinsignin1", // 作成したテナント名に変更
        },
        forgotPassword: {
            authority: "https://mikogarage.b2clogin.com/mikogarage.onmicrosoft.com/b2c_1_reset",// 作成したテナント名に変更
        },
        editProfile: {
            authority: "https://mikogarage.b2clogin.com/mikogarage.onmicrosoft.com/b2c_1_edit_profile"// 作成したテナント名に変更
        }
    },
}

apiConfig.js
// The current application coordinates were pre-registered in a B2C tenant.
const apiConfig = {
  b2cScopes: ["https://mikogarage.onmicrosoft.com/api/demo.read"], //作成したスコープに変更
  webApi: "https://localhost:5000" //localhost のアドレスに変更
};

以上の 3つでコードの編集は完了です。

サンプルアプリを実行する

では実行してみましょう!通常の Node App と同じように npm start を実行します。

C:\Users\komiyasa\source\repos\qiita\active-directory-b2c-javascript-msal-singlepageapp>npm start

> active-directory-b2c-javascript-msal-singlepageapp@1.1.0 start C:\Users\komiyasa\source\repos\qiita\active-directory-b2c-javascript-msal-singlepageapp
> node server.js

Listening on port 6420...

ブラウザで 'http://localhost:6420/' にアクセスします。
image.png
サインインボタンを押すと、、
image.png
認証ウィンドウが出てきます。試しにサインアップでアドレスを入力してみましょう。
image.png
作成したアカウントでログインできることが確認できました!!
image.png

Azure AD B2C の方の ユーザー で登録されたユーザーを確認することができます。
image.png

これで Azure AD B2C 認証を使用したシングルページアプリを作成することができました。
Azure AD B2C は google や Facebook といった SNS アカウントでも認証をできるので、そちらも今度試して見たいと思います。

参考

今回参照した URL を纏めておきます。

タイトル URL
Azure Active Directory B2C のドキュメント https://docs.microsoft.com/ja-jp/azure/active-directory-b2c/
クイック スタート:Azure Active Directory B2C を使用したシングルページ アプリのサインインの設定 https://docs.microsoft.com/ja-jp/azure/active-directory-b2c/quickstart-single-page-app
active-directory-b2c-javascript-msal-singlepageapp サンプルコード https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp
active-directory-b2c-javascript-nodejs-webapi サンプルコード https://github.com/Azure-Samples/active-directory-b2c-javascript-nodejs-webapi
チュートリアル:Azure Active Directory B2C 内にユーザー フローを作成する https://docs.microsoft.com/ja-jp/azure/active-directory-b2c/tutorial-create-user-flows

尚、今回作成したテナントはセキュリティの観点から既に消してあります。上記のサンプルコードをそのままコピペしても動きませんのでご了承くださいー。

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

【Vue.js】Vue.jsの基礎知識

Vue.js とは

  • javascriptのフレームワークの一つ。
  • JQueryよりも記述が簡単。
  • ディレクティブ機能により、HTMLとjavascriptの間でデータ連携を行う役割を果たす。
  • Raectが単方向のデータバインディングであるのに対し、Vue.jsは双方向のデータバインディングが可能(※)。
  • React、Angularに並び人気があるフレームワーク。
  • ECMAScript 5 準拠のブラウザに対応。IE8以前のバージョンはサポートされていないため注意。 (詳しくはこちらを参照)

双方向データバインディング
UI、データのいずれか一方が更新されれば、もう一方も更新される仕組みのこと。(=データとUIが常に同期される)

Vue.jsの読み込み(インストール)

事前にお使いのブラウザにVue Devtoolsをインストールしておくことをお勧めします。
筆者はchromeを使うので拡張機能vuejs-devtoolsを入れました。

Vue.jsの読み込み方法

  1. CDN ←プロトタイピングや学習を目的とする場合はこちらがオススメ
  2. NPM
  3. 直接組み込み

今回は学習用なので、CDNを読み込みました。

下記コードをbody終了タグの直前に挿入すれば完了。

html
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
or
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

(例1)html内にスクリプトを記述する場合

html
<body>
  <!-- html -->
  <div>
     <span>TEST</span>
  </div>

  <!-- js -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
      // js記述部分
  </script>
</body>

(例2)jsファイルを別途作成する場合

html
<body>
  <!-- html -->
  <div>
     <span>TEST</span>
  </div>

  <!-- js -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="js/test.js"></script>  // vue.jsの下で読み込むこと
</body>

Vue.jsの基礎

目次

1.マスタッシュ構文(単方向データバインディング)
2.ディレクティブ
  2-1.v-bind
  2-2.v-if
  2-3.v-show
  2-4.v-for
  2-5.v-on
  2-6.v-model(双方向データバインディング)
3.コンポーネント

1.マスタッシュ構文(単方向データバインディング)

マスタッシュ構文とは?

{{ }}で囲った構文のこと。({{ }}がマスタッシュ=口ひげに見えるからこのような名前になったとか)
マスタッシュ構文を使用することで、Vueインスタンス内に作ったdataオブジェクトのプロパティを反映させることができます。

まずは「Hello World」

次のようなディレクトリ構造で作成していきます。
image.png

まずはhtml内に次のように書いてみましょう。

main.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="css/common.css">
        <link rel="stylesheet" href="css/main.css">
        <title>Main</title>

    </head>
    <body>
        <div id="app">
            {{ message }}
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="js/main.js"></script>
    </body>
</html>
  • <div>タグにappというidを付与し、その中にマスタッシュ構文で{{ message }}と記述しました。
  • この時点でhtmlを開いても、{{ message }}がテキストとして表示されるのみです。

次にjsファイルに次のように記述。

main.js
var app = new Vue({

    el: '#app',
    data: {
        message: 'Hello World!'

    }
})

  • var 変数名 = new Vue({ ... }) で新規のVueインスタンスが作成できます。
  • el:(elementの略)に#appと指定することで、<div id="app">内にのみ有効なVueインスタンスとすることが出来ます。 (id名の先頭に#を付けるところはCSSでの指定方式と同じです。)
  • data: {}内にmessage: 'Hello World!'と記述します。

結果

image.png
js側で宣言したmessageの内容がDOMに反映されていることが分かります。

2.ディレクティブ

ディレクティブとは?

v-から始まるデータバインディング(HTMLとjavascriptを紐づける)を行うための機能。

2-1.v-bind

v-bindで要素属性のバインディングを行うことが出来ます。

main.html
        <div id="app-2">
            <span v-bind:title="message">
                今何時?
            </span>
        </div>
main.js
var app2 = new Vue({

    el: '#app-2',
    data: {
        message: '現在時刻は ' + new Date().toLocaleString()
    }

})

【html】

  • v-bind:[属性名]=""で要素属性にdataオブジェクト内のプロパティをバインディングします。

【js】

  • マスタッシュ構文の時と同様、Vueインスタンスを作成し、el:<div>タグのidを記述します。
  • dataオブジェクト内にはmessage: '現在時刻は ' + new Date().toLocaleString()と記述します。 (new Date().toLocaleString()で、ローカル言語に合わせた書式の日時を取得できます。)

結果

image.png
「今何時?」にカーソルを合わせると、title属性にバインディングされた現在日時が表示されました。

注意点

タグの属性内にマスタッシュ構文を使うことは出来ません。
属性に適用したい場合は上記の手順でv-bindを使用した書き方にしてください。

main.html
        <div id="app-2">
            <span title={{ message }}>  ←このような記述は出来ません!
                今何時?
            </span>
        </div>

2-2.v-if

v-ifで要素の表示/非表示を切り替えることが出来ます。

main.html
        <div id="app-3">
            <span v-if="seen">
                見えるかな?
            </span>
        </div>
main.js
var app3 = new Vue({

    el: '#app-3',
    data: {
        seen: true
    }

})
  • js側でseenプロパティを作成します。
  • html側で表示/非表示を切り替えたい要素内にv-if="seen"と記述します。

結果

image.png
seen: trueのため、「見えるかな?」というテキストが表示されました。

試しに[F12]でデベロッパーツールを起動し、コンソールからapp3.seen = falseと打ってみましょう。
image.png

「見えるかな?」が非表示になりました。
image.png

DOMを見るとコメントアウトになっています。
image.png

2-3.v-show

v-showでもv-ifと同じように、要素の表示/非表示を切り替えることが出来ます。

main.html
        <div id="app-3">
            <span v-show="seen">
                見えるかな?
            </span>
        </div>
main.js
var app3 = new Vue({

    el: '#app-3',
    data: {
        seen: true
    }

})

v-ifとv-showの違い

v-ifでfalse指定した際には要素内がコメントアウトになったのに対し、
v-showの場合はdisplay: none;が付与されます。(cssのdisplayプロパティと同じはたらき)
v-ifより軽いため、頻繁に表示/非表示を変える場合はv-showがおすすめです。
image.png

2-4.v-for

v-forを使って、配列内のデータを反映させることが出来ます。

main.html
        <div id="app-4">
            <ol>
                <li v-for="todo in todos">
                    {{ todo.text }}
                </li>
            </ol>
        </div>
main.js
var app4 = new Vue({

    el: '#app-4',
    data: {
        todos: [
            { text: 'りんご' },
            { text: 'みかん' },
            { text: 'ぶどう' }

        ]
    }

})
  • dataオブジェクト内にtodosという配列を作成します。
  • v-for="[変数] in [配列名]"で配列の内容を取得するループ処理を作成出来ます。
  • マスタッシュ構文内で[変数]を使用することが出来ます。

結果

image.png
dataオブジェクト内に作った配列をDOMに反映させることができました。

2-5.v-on

v-onでmethods内の処理を実行させることが出来ます。

main.html
        <div id="app-5">
            <p>{{ message }}</p>
            <button v-on:click="reverseMessage">メッセージをひっくり返すよ</button>
        </div>
main.js
var app5 = new Vue ({

    el: '#app-5',
    data: {
        message: 'stressed'
    },
    methods: {
        reverseMessage: function() {
            this.message = this.message.split('').reverse().join('')
        }
    }
})
  • dataオブジェクトに加えてmothodsオブジェクトを作成します。
  • methodsオブジェクト内にreverseMessageというfunctionを作成します。
  • function内にmessageの文字列を逆にする処理を記述します。(thisはVueインスタンス(app5)自身を指しています。)
  • html側には、messageのマスタッシュ構文とその下に<button>タグを記述します。
  • <button>タグ内にv-on:[イベント]="[処理名]"の形で記載すると、Vueインスタンスのmethods内の処理が呼び出されます。
  • 今回はクリック時にreverseMessageを実行したいので、v-on:click="reverseMessage"と記述します。

結果

ボタンクリック前
image.png
ボタンクリック後
image.png

2-6.v-model(双方向データバインディング)

【双方向データバインディングの特徴】
 ・jsのdata側とテンプレート側の両方で変更可能。
 ・テンプレートの値を変更すると、dataオブジェクトのプロパティも変更される。

main.html
        <div id="app-6">
            <p>{{ message }}</p>
            <input v-model="message">
        </div>
main.js
var app6 = new Vue({

    el: '#app-6',
    data: {
        message: 'こんにちは!'
    }
})
  • <p>タグの中にマスタッシュ構文を使いmessageの内容(こんにちは!)を出力するようにしました。
  • 直後の<input>タグでv-model="[オブジェクト名]"と指定することで、<p>タグ同様messageの内容を適用しています。

結果

image.png
テキスト、テキストボックスの両方にmessageの内容が出力されました。

image.png
試しに下のテキストボックスの文字列を変更すると、上のテキストもそれに連動して書き換えられます。

3.コンポーネント

コンポーネントとは?

必要なパーツ(template(HTMLタグ)、methods等)を「コンポーネント」としてパッケージ化できる機能。
一度コンポーネントとして登録すれば再利用が可能なため、同じ組み合わせのタグを繰り返し使う時などに便利。

本項はコチラの動画の記述を参考にいたしました。
Vue.js入門 #08:コンポーネントで再利用可能なパーツを作ろう

main.html
        <div id="app-7">
            <alert-box>IDが未入力です。</alert-box>
        </div>
main.js
Vue.component('alert-box',{
    template: `
    <div class="alert">
        <strong>Error!</strong>
        <slot></slot>
    </div>
    `
})
var app7 = new Vue({
    el: '#app-7'
})
main.css
.alert{
    background-color: #fcc;
    padding: 10px;
    border: 1px solid #f33;
}
  • Vue.component('[コンポーネント名]',{template: [HTMLタグ]})で、コンポーネントを登録します。
  • template:内には、コンポーネント化したいHTMLタグを記述します。
  • <slot>タグは、コンポーネントを使用時にタグ中に記述したテキスト(「IDが未入力です。」)を反映させるための独自タグです。
  • あわせて、Vueインスタンスの作成も忘れず行ってください。
  • コンポーネント化したタグにalertというクラスを付与し、cssにてスタイル編集を行っています。
  • <[コンポーネント名]>で、登録したコンポーネントを使用することが出来ます。

結果

image.png
image.png

  • <alert-box>タグで囲った箇所が、コンポーネントとして登録したHTMLタグ(<div class="alert">...)に置き換わっています。
  • <alert-box>タグ内に記述したテキスト(「IDが未入力です。」)が、<slot>タグによって文字列として適用されています。

コンポーネントの再利用

次のように、<alert-box>を繰り返し使うと・・・

main.html
        <div id="app-7">
            <alert-box>IDが未入力です。</alert-box>
            <alert-box>IDが未入力です。</alert-box>
            <alert-box>IDが未入力です。</alert-box>
            <alert-box>IDが未入力です。</alert-box>
        </div>

その分だけコンポーネントが適用されます。
このように、一度登録したコンポーネントは再利用可能なことが分かります。
image.png

mothodsの追加

コンポーネント内にはHTMLタグだけでなく、処理も記述することが出来ます。

main.js
Vue.component('alert-box',{
    template: `
    <div class="alert" v-on:click="caution">
        <strong>Error!</strong>
        <slot></slot>
    </div>
    `,
    methods: {
        caution: function(){
            alert('クリックされました。');
        }
    }
})
var app7 = new Vue({
    el: '#app-7'
})
  • template:内の<div>タグに、v-on:click="caution"を追記しました。
  • methods:を追記し、画面上に「クリックされました。」というアラートを表示させるcaution処理を追記しました。

image.png

[Error!]をクリックすると、アラートが表示されました。

さいごに

本記事で紹介したものは基礎知識となります。
ディレクティブ機能などの独自の記述が多いですが、慣れればシンプルで非常に書きやすいフレームワークだと感じました。
学習目的であればCDNで読み込みも簡単なので、ご興味のある方は是非お試しいただければと思います。

Vue.js学習に役に立ったサイトまとめ

公式サイト(ガイド)
公式サイト(Vueオプションオブジェクト一覧)
基礎から学ぶVue.js
【Vue.js】vue.jsの基礎(yuta-38さん)
【Vue.js】Vue.jsの基礎(smkhkcさん)
Vue.js入門(動画)

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

しまぶーさんのJavaScript要約

しまぶーさんJavaScript講座 要約

JavaScript 1995年降臨爆誕(後藤輝樹)

ECMAScriptは、netscapeがJavaScriptを国際的な標準化団体(Ecma innternational)に提出した時に生まれた。おんぎゃあ
(豆知識:ES4以下はリリース順。ES5以上は年号表記。ES2015が正解)

ECMASCriptとは?
➡️JavaScriptの中核になる言語仕様。どの実行環境でも共通な動作のみが定義
実際はコンパイルする必要がある

ブラウザでモジュールを使うために模索した結果、コードを事前に変換が主流に

◎モジュールとは
ただの1つのファイル(モジュール)。相互に読み込んだりexportとimportを使用して機能をやり取りしたり、あるモジュールの関数を別のモジュールから呼び出せる。
スコープの概念あり。

Node.jsが2009年に開発。CommonJSのモジュールAPIに準拠

JavaScriptにはモジュールの仕様が複数存在してる。

◎パッケージとパッケージ管理システムとは
・パッケージとは
「package.json」で記述されたファイルやディレクトリ
➡️メリット
機能の細分化ができて、名前空間問題が解決できる
➡️共有したい!
・改めてパッケージとは
共有したい機能の単位

・パッケージ管理システムとは

・パッケージ管理システムがやってくれること
1.リポジトリの購読
2.パッケージのインストール・削除
3.依存関係の解決
パッケージに必要な別のパッケージを自動的にインストールできる
「package-lock.json」のこと。「node_modules」に依存関係が入ってる。
expressをインストールする時に必要な依存関係のものを一瞬でDLできる
例)jQueryUIを使うにはjQueryが必要で、先にjQueryを読まないとエラーを吐く
4.設定管理
設定書いたら1,2,3を自動で行える。手動の手間が省けてチームで環境を揃えられる

◎Bunbleとは
1.開発時はCommonJSモジュールで開発
2.モジュールの依存関係を解決して1ファイルに変換(これがバンドル)
3.いつも通りscriptタグで読み込む

◎Browserifyとは
CommonJSで書かれたものをブラウザ向けにバンドルするツール。require文を使える。そのおかげでNode.jsのパッケージが使えるようになった➡️npmが主流へ
CJS形式モジュールをバンドルするよ。

◎webpackが2012年に降臨爆誕
そして覇権を取る。環境最強ですね。
cjsとかブラウザで使えないファイルを何でもバンドルできるのがwebpack。
codesplittingを使ってチャンク分割している(難しいからスルー)

◎ESmodule降臨爆誕。
独自仕様だったので、共通の仕様が欲しかった。
んで、ES2015からJavaScriptの言語仕様としてモジュールの仕組みが導入される。
簡単に言うと、requireからimportになる。

◎wepack誕生までの流れまとめ
モジュールバンドラーが流行る(特にwebpack)➡️CJSやESM形式でモジュールを使ってコード掛けるから
CJS主流だったnpmクライアント開発で使える=パッケージ管理システムも使える

◎コンパイルとは

◎コンパイルを使うと
1.開発時にはブラウザで動かないけど、便利な機能を使ってコードを書ける
2.書いたコードをブラウザで動くように変換➡️いつもどおり読み込む

◎Babel(6to5)の誕生(元々は6to5だったけど、Babelに改名された)
wepackとbabelを一緒に使える。
元々はES2015の新機能(letとか)が使えなかったけど、babelのおかげで使えるようになった?

◎コンパイルの可能性
コンパイルが当たり前になって、便利なパッケージが流行りだす
React(.jsx)・Vue(.vue)・TypeScript(.ts)

◎モダンなJS環境になるために
コードを事前に変換が主流になった。モジュールを使えたり依存関係を解決でき、ES2015仕様が使える。パラダイムシフト

◎覚えるべきもの
Node.js パッケージ管理システム npm(package.json) 事前変換(Bundle, Compile)
webpack babel ES module ES2015 React Vue TypeScript

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

JavaScript ドットインストール

ドッドインストール JavaScript講座メモ 「・」を「# 」 に変えてね。

・typeof
文字列の型を表示できる

console.log(typeof 11); # number

・NaN
Not a Number(ナンと呼ぶ)。数値が表せないよ〜ってこと。

・文字列の評価
- false(falseと表示されるもの)
- 0
- null
- undifined
- 「’’」(空文字)
- false
- true
- 上記以外全てtrue

・論理演算子「!a」
NOT演算のこと。〜ではない。「a以外なら何でもおk」。rubyのunlessに似てる。
何か前にも躓いた気がする。

      a = 1;
      console.log(!(a === 0));
    # true

      a = 1;
      console.log(!(a === 1));
    # false

・switch
まんまrubyのwhenですね。

test.js
      a = 1;
      switch (a) {
        case 0:
          console.log(0);
        case 1:
          console.log(1);
        default:
          console.log("aaa");
      }
// 出力結果: 1

・テンプレートリテラル
ES6から使えるようになったやつ。
バッククオート「`」で範囲を囲む!
変数や計算式を入れられる。

`aaa`
${1+1 + a}

・デフォルト引数
メソッドのデフォルトで表示する引数を決められる。値ありなしでもエラー起きないよ。

      function showAd(message = "Ad") {
        console.log(`--- ${message} ---`);
      }
      showAd("Ad");
      showAd();

// 出力結果
—- Ad ---
—- Ad ---

・ブロックで囲うとエラーが出ない(ブロックスコープ)
⬇️下記はエラーが出る(定数がすでに宣言されてる!エラー)

index.html
  <script src="js/main.js"></script>
  <script>
      const x = 300;
      console.log(x);
  </script>
main.js
  const x = 100;
  console.log(x);

⬇️下記はエラーが出ません(定数が別々で定義されているから)

index.html
  <script src="js/main.js"></script>
  <script>
    {
      const x = 300;
      console.log(x);
    }
  </script>
main.js
{
  const x = 100;
  console.log(x);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】基礎的な文法編

編集中です!

・typeof
文字列の型を表示できる

console.log(typeof 11); # number

・NaN
Not a Number(ナンと呼ぶ)。数値が表せないよ〜ってこと。

・文字列の評価
- false(falseと表示されるもの)
- 0
- null
- undifined
- 「’’」(空文字)
- false
- true
- 上記以外全てtrue

・論理演算子「!a」
NOT演算のこと。〜ではない。「a以外なら何でもおk」。rubyのunlessに似てる。
何か前にも躓いた気がする。

      a = 1;
      console.log(!(a === 0));
    # true

      a = 1;
      console.log(!(a === 1));
    # false

・switch
まんまrubyのwhenですね。

test.js
      a = 1;
      switch (a) {
        case 0:
          console.log(0);
        case 1:
          console.log(1);
        default:
          console.log("aaa");
      }
// 出力結果: 1

・テンプレートリテラル
ES6から使えるようになったやつ。
バッククオート「`」で範囲を囲む!
変数や計算式を入れられる。

`aaa`
${1+1 + a}

・デフォルト引数
メソッドのデフォルトで表示する引数を決められる。値ありなしでもエラー起きないよ。

      function showAd(message = "Ad") {
        console.log(`--- ${message} ---`);
      }
      showAd("Ad");
      showAd();

// 出力結果
—- Ad ---
—- Ad ---

・ブロックで囲うとエラーが出ない(ブロックスコープ)
⬇️下記はエラーが出る(定数がすでに宣言されてる!エラー)

index.html
  <script src="js/main.js"></script>
  <script>
      const x = 300;
      console.log(x);
  </script>
main.js
  const x = 100;
  console.log(x);

⬇️下記はエラーが出ません(定数が別々で定義されているから)

index.html
  <script src="js/main.js"></script>
  <script>
    {
      const x = 300;
      console.log(x);
    }
  </script>
main.js
{
  const x = 100;
  console.log(x);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript基礎

Udemy JavaScript初級者〜中級者講座

・よく分からん箇所。後でまとめよう
13〜18
関数は先に読み込まれる。未定義はエラー。定義されてたらundifined
ホスティングは順番の問題らしい。

?JavaScriptは実行する環境によって使える機能が異なる
◎ブラウザ環境
・ECMAScript + WebAPIs(DOM API) etc..

◎Node.js環境
・ECMAscript + CommonJS

◎universal JavaScript
色んな環境で動くJavaScript

◎即時関数
関数の最後に()をつけると、すぐに読み込む

◎厳格な等価性
下記のコードは、変数aと変数bの型をチェックした後に、中身が一致するかチェックする。
下記の場合だと、エラーになる。数値と文字列だから。

a = “a”
b = 1
console.log(a === b);

下記はおk.抽象的な等価性だから、中身が一致してるかしかチェックしない。ザル。

a = "1";
b = 1;
console.log(a == b);

◎参照とconst
定数には再代入できないけど、定数内のオブジェクトには再代入できる。

const b = {
  prop: "hi",
};
// b = {}; // これを表示するとエラーになります。定数がすでに入ってるエラー
b.prop = "vue";
console.log(b);

◎参照と引数
引数でとった変数を参照すると、参照元も変更される。

let b = {
    prop: 0
}
function fn2(arg2) {
    arg2.prop = 1;
    console.log(b, arg2);
}
fn2(b);
// 出力結果:{prop: 1} {prop: 1}

◎分割代入
「{ a }」を使うと、参照元の影響を受けずに代入できる?要検証。用途が分からん

◎関数とは
実行可能なオブジェクト!
()をつけると実行されるよ。
下記みたいにプロパティやメソッドを定義できるよ。

function a() {
    console.log('hello');
}
a.prop = 0;
a.method = function() {
    console.log('method');
}
console.log(a.prop);
// 出力結果:hello, method, 0

◎関数のreturn(すごーく当たり前だけど)
returnした変数の内容が入ってきます。

function fn(a) {
const b = a;
return b;
}

let c = fn(1);
console.log(c);
```

◎コールバック関数
引数で関数を渡して実行するみたいなイメージ

function bye() {
console.log("bye");
}

function fn(cb) {
cb();
}
fn(bye);
```

◎thisの豆知識(実用性なさそう)
window.nameで変数指定しなくてもthisで取ってこれる

window.name = "John2";
function a() {
  console.log("Hello " + this.name);
}
a();
出力結果:Hello John2

◎bind
引数を上書きします。bindによるthisの束縛とも言うらしい。メンヘラこっわ

function a(name) {
  console.log("hello " + name);
}
const b = a.bind(null, "Tim");
b(“AAA”);

function a() {
  console.log("hello " + this.name);
}
const b = a.bind({ name: "Tim" });
b("11");

// 出力結果:hello Tim(両方とも同じ結果)

◎applyとcallの違い
applyは配列、callはそれぞれ独立した変数。定数timの必要性は要検証

function a(name, name1) {
  console.log("hello " + name, name1);
}
const tim = { name: "Tim" };
a.apply(tim, ["Tim1", "Bob2"]);
a.call(tim, "Tim3", "Bob4");

// 出力結果:hello Tim1 Bob2, hello Tim3, Bob4

◎アロー関数
無名関数の省略記法。でも厳密に同じではない。returnの役割を理解したほうが良い。戻り値を定義しようね。
アロー関数はthisを取らない

const b = (name, name1) => {
  return "hi" + name + name1;
};
console.log(b("1", "2"));

◎アロー関数とthisの関係性
関数内にthisがあればそちらを参照、なかったら(?)グローバルを参照

window.name = "John";
const a = () => console.log("Bye " + this.name);
a(); // Bye John
const person = {
  name: "Tom",
  hello() {
    const c = () => console.log("Bye " + this.name);
    c(); // Bye tom
  },
};
person.hello();
function b() {
  const a = () => console.log("Bye " + this.name);
  a();
}
b(); // Bye John

◎コンストラクター関数
オブジェクトを生成するための関数

◎インスタンス化
オブジェクト化すること。newを使う

◎インスタンス
オブジェクトそのもの

◎プロトタイプ
メモリ化のためにprototypeを使う。Person.prototype === bob.prototype // true
つまり、インスタンス化した時にprototypeの参照がprotoにコピーされる

◎instanceof
どのコンストラクターから生成されたオブジェクトかを確かめる

◎関数の引数の余分なやつ
3番目は、関数のボディ部分になる(実行する内容)

const fn1 = new Function("a", "b", "return a + b");
const result = fn1(1, 2); // 3

◎プロトタイプ継承
プロトタイプで作ったものを継承できるよ。他に引数を追加したりできる。 

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.hello = function () {
  console.log("hello " + this.name);
};
function Japanese(name, age, gender) {
  Person.call(this, name, age);
  this.gender = gender;
}
Japanese.prototype = Object.create(Person.prototype);
const taro = new Japanese("Taro", 23, "Men");
console.log(taro);
taro.hello();

◎ビルドインオブジェクト
コード実行前にJSエンジンによって自動的に生成されるオブジェクト

◎ディスクリプター
プロパティの設定値。これによって値が変更できたり削除できたりする。すげえ

◎静的メソッド・スタティックメソッド
インスタンス化を行わずに使用できるメソッド

◎演算子と優先順位
演算に優先順位があり、++を行うタイミングで実行する順番が変わる

let a = 0;
let b = a++;
console.log(a, b);
// 出力結果:1 0

let a = 0;
let b = ++a;
console.log(a, b);
// 出力結果:1 1

◎for…of
イテレータを持つオブジェクトの反復操作を行う

◎イテレーターとは
反復操作を行う時に使用するオブジェクト(反復可能オブジェクト)。ルール有り。

◎ジェネレーターとは
イテレーターを生成するための特殊な関数(より簡略化して記述可能!)

◎スプレッド演算子
反復可能や列挙可能オブジェクトの展開を行う

◎スレッド
連続して実行される一本の処理の流れ(英語で糸と言う、2ちゃんのスレとかだね)

◎非同期処理
メインスレッドから一時的に切り離される処理

◎タスクキュー
実行待ちの非同期処理の行列➡️非同期処理の実行順を管理!先入れ先出しします。(商品管理かな?)

◎Promiseとは
非同期処理をよりかんたんに、可読性が上がるように書けるようにしたもの

◎マクロタスク
タスクキュート読んでいたもの。
下記がマクロタスク行き
・setTimeout
・setInterval

◎マイクロタスク
タスクキューとは別で存在する非同期処理の待ち行列(ジョブキュー)こいつの方が優先される?
下記がマイクロタスク行き
・Promises
・queueMicrotask

◎Async(Await)
Promiseを返却する関数の宣言を行う.Promiseを更に直感的に記述できるようにしたもの

◎Await
Promiseを返却する関数の非同期処理が完了するまで待つ.Promiseを直感的(略
awaitを使うとresolve(val)で渡された引数が受け取れるらしい。Promise内か明示的だと使えるよ!

◎Promiseの(メソッド?)あれこれ
・then 成功した時
・catch エラーの時。throw new Errorで投げられたらちゃんと受け取れ!
・finally 最後の時 引数取れない

◎モジュール
ソースコードを機能毎に分割してメンテナンスしやすくする仕組み。
代表的なものにESM(ESModule)とCSJ(CommonJS)があるよ〜

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

SVGでクリックすると音の出るピアノを作る

作ったもの

https://perzikanz.github.io/play-audio/

image.png

やること

  • 鍵盤のsvgを作る
  • 音源を作る
  • クリックすると音が鳴るようにする

鍵盤のSVGを作る

Inkscapeを使ってSVGを作ります。1つのキーを1つの<path>で描き、その音階と同じidを振ります。idはオブジェクトのプロパティから編集できます。

image.png

ドの音にあたる位置にはC0~7のキーを書きます。どこのドなのか分かりやすくするためです。

出来上がったらプレーンSVGで保存します。

image.png

保存したSVGをテキストエディタで開くとこのようになってます。
image.png

<svg>タグの中身をHTML内にコピー&ペーストします。
<path>にはfillstrokeを指定するためのstyleが付いていますが、styleはcssで指定したいので削除し、代わりにclassをつけます。

index.html
<svg>
    <path d="M 50,25 v 405 h 80 v -180 h -30 v -225 Z" id="c0" class="white" />
    <path  d="M 100,25 v 225 h 50 v -225 Z" id="cs0" class="black" />
    .
    .
    .
    <path d="M 3970,25 v 405 h 80 v -405 Z" id="c7" class="white" />
    <text x="3988" y="400" class="text">C7</text>
</svg>

(実は初めてのInkscapeだったので上手く作れず、線がガタガタになって見た目が悪かったので、テキストエディタでpathを全て書き直して綺麗に整えました)

CSSでスタイルを指定

SVGでは塗りつぶしはfill、線はstrokeで指定します。
hoverは普通のCSSと同じように指定できます。

キーが分かるように書いたtextは、そのままでは鍵盤をクリックする邪魔になるので、pointer-eventsnoneに指定し、クリックやホバーなどのポインターイベントの対象にならないようにします。
更にuser-selectnoneに指定し文字を選択できないようにします。

piano.css
.white {
  fill: #fff;
  stroke: #000;
  stroke-width: 1px;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

.white:hover {
  fill: #ccc;
}

.black {
  fill: #000;
  stroke: #000;
  stroke-width: 1px;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

.black:hover {
  fill: #222;
}

.text {
  fill: #444;
  font-size: 30px;
  font-weight: bold;
  pointer-events: none;
  user-select: none;
}

音源を作る

たまたまインストールされていたStudio Oneと、KEYSCAPEの音源を使ってピアノ音源を作りました。
ピアノを1音ずつ鳴らしたものを、mp3で書き出します。ファイル名は鍵盤のidと同じ音階にします。

image.png

クリックで音を鳴らす

JavaScriptで鍵盤をクリックしたら音が鳴るようにします。
JavaScriptで音声を扱うにはAudioクラスを使います。引数には音声ファイルのパスを指定します。

play.js
const path = document.querySelectorAll('path');

for (let i = 0, l = path.length; l > i; i++) {
  const file = path[i].getAttribute('id');
  const src = `./src/audio/${file}.mp3`;
  const audio = new Audio(src);

  path[i].addEventListener('click', () => {
    audio.currentTime = 0;
    audio.play();
  });
}

querySelectAllで全ての<path>を取得します。

そこからgetAttributeで取得したidをファイル名としてパスを指定し、Audioインスタンスを作成します。

.play()で再生できますが、そのままだと連打しても1つの音の再生が終わるまで他の音を再生してくれないので、
よりピアノっぽくする為に、巻き戻してから再生するようにします。再生位置を決めるcurrentTimeに、0をセットするだけです。

参考にしたページ

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

[JavaScript] includesメソッドの使い方

JavaScriptのIncludesメソッドについて簡単まとめたので、備忘録もかねて記事を書きます。

includesメソッドの概要

includesメソッドを使うと、配列式や文字列の中から特定の要素、文字列が含まれているかチェックをすることができます。
以下、実際の例です。

配列

特定の要素が配列内に含まれるかチェックし、true or falseを返します

例1
const test = ["foo", "bar"];

console.log(test.includes("foo")); // true
console.log(test.includes("baz")); // false

  
第2引数を指定することで、検索を開始する位置を指定することができます。

例2
const test = ["foo", "bar", "baz"];

console.log(test.includes("foo", 0)); // true
console.log(test.includes("foo", 1)); // false

文字列

文字列に対してもincludesメソッドは利用できます。

例3
const test = "includesメソッド";

console.log(test.includes("メソッド")); // true
console.log(test.includes("関数")); // false

大文字小文字は区別されます。

例4
const test = "includesメソッド";

console.log(test.includes("includes")); // true
console.log(test.includes("INCLUDES")); // false

大文字小文字の判定で困ったときは、toLowerCase or toUpperCaseが使えます。

例5
const test = "includesメソッド";

console.log(test.includes("INCLUDES")); // false
console.log(test.toUpperCase().includes("INCLUDES")); // true

indexOfメソッドとの違い

indexOfメソッドが、指定した要素が含まれている場合はその位置(インデックス)を、含まれない場合は-1を返します。
取得した要素に対し、何か処理を加えたい場合は、こちらを使うのが良いでしょう。

例6
const test = ["foo", "bar", "baz"];

console.log(test.indexOf("baz")); // 2
console.log(test.includes("faa")); // -1

 

最後まで読んでいただきありがとうございますm(__)m

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

【Ruby on Rails】トップに戻るボタン

目標

arrow.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

上記機能はなくても大丈夫です。

流れ

1 写真の用意
2 viewの編集
3 cssの編集
4 jsファイルの編集(ページスクロールにアニメーションを追加する場合)

写真の用意

このような画像を用意してください。
ちなみにこの画像はipadで作成しましたので、ご自由にお使いください。
arrow.jpg

画像ファイルをapp/assets/images配下に格納してください。

viewの編集

今回は全てのページで表示したいため、下記場所に記載。

app/views/layouts/application.html.erb
<body>
  <%= yield %>
  <span id="back">
    <a href="">
      <%= image_tag asset_path('arrow.jpg'), data: {"turbolinks"=>false}, class: "arrow" %>
    </a>
  </span>
</body>

補足【image_tag asset_path( )】
app/assets/images配下の画像を読み込むことが出来ます。

補足【data: {"turbolinks"=>false}】
turbolinksの誤作動を防ぎます。

cssの編集

app/application.css
#back {
  position: fixed;
  right: 20px;
  bottom: 20px;
}
.arrow{
  width:      50px;
  height:     50px;
}

補足【position: fixed;】
position: fixed;により表示する場所を固定しています。

jsファイルの編集

gem 'jquery-rails'を導入しておいてください。
マウスでクリックするとアニメーションを動作させます。

app/assets/javascripts/application.js
$(function() {
  $('#back a').on('click',function(event){
    $('body, html').animate({
      scrollTop:0
    }, 800);
    event.preventDefault();
  });
});

補足【$('#back a').on('click',function(event)】
$('.セレクタ名').on('click',function(event) {  
イベント発生時に行われる処理
});

補足【$('body, html').animate】
$('セレクタ名').animate({   
変化対象のプロパティ名:変化値  
}, アニメーションの動作時間); 

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

jsでもifやswitchを式として扱いたい

忘れない内にメモ
こうすれば行ける

const result = ((some) => {
    if(some === 1) {
        return 1;
    } else {
        return 2;
    }
})(some);
const result = ((some) => {
    switch(some) {
        case 1:
            return 1;
        case 2:
            return 2;
        default:
            return 3;
    }
})(some);

ifじゃなくて三項演算子使うのもアリか

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

TypeScriptでArray<T>から重複要素を排除してユニークにする

JavaScriptでArrayをuniqにするを元に

const defaultComparator = <T>(a: T, b: T): boolean => a === b

export const distinctBy = <T>(
  array: Array<T>,
  comparator: (a: T, b: T) => boolean = defaultComparator
): Array<T> =>
  array.filter(
    (elem, index, self) => self.findIndex(e => comparator(elem, e)) === index
  )

type Dog = {
  id: string
  name: string
}
const comparator = (a: Dog, b: Dog) => a.id === b.id
const dogsDistinct = (dogs: Array<Dog>) => distinctBy(dogs, comparator)

みたいな。

ところでJavascriptにはビルトインの等価性アルゴリズムが4種類ある(演算子としては3種類ある)のでcomparatorを実装する時はどれに合わせるのか気をつけたほうが良いと思います。

[1, 2] == '1,2' //true
[1, 2] === '1,2' //false
Object.is([1, 2], '1,2') //false

NaN == NaN //false
NaN === NaN //false
Object.is(NaN, NaN) //true

+0 == -0 //true
+0 === -0 //true
Object.is(+0, -0) //false

TypeScriptを使っていると型の上でも馴染みが深い、構造が一致するかどうかという比較はビルトインでは提供されていないため、必要であればdeep-equalのような外部パッケージを利用すると便利です。

see also

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

front-end に Nuxt.js と Laravel が混在するシステムで Cookie を用いてクライアントを識別する

Nuxt-2.14.4 Laravel-6.x

前回の記事の続きですが、 A/B テストのような文脈でクライアント(ブラウザ)ごとに一意なランダムの ID を割り当て、 Cookie を用いてリクエストごとにクライアントを分類できるようにする処理を実装する機会がありました。この記事では front-end に Nuxt.js と Laravel (の view )が混在するシステムで、両方から ID を生成・利用できるようにする方法について紹介します。

方針と注意

基本的な方針は以下の通りです。

  1. リクエストの Cookie ヘッダから ID が得られなかった場合(初回訪問)は ID を生成して Cookie に追加 1 する。
  2. リクエストの Cookie ヘッダから ID を得られた場合(2回目以降の訪問)は、それを利用する。

今回の実装では Nuxt.js と Laravel のどちら側でも Cookie から ID を読み取れるようにするため、 HttpOnly 属性を外し、暗号化を行いません。このため、ログインセッション用の Cookie など、第三者による Cookie の盗聴や改ざんがセキュリティ上のリスクとなるような情報に関しては本記事中の実装方法を避け、フレームワークが提供する機能などを利用してください。

また front-end がすべて Nuxt.js で構成されている場合には Laravel 側の処理は不要なので、 Laravel のセクションを読み飛ばしてください。

実装

Nuxt.js

以下の useClientId は ID の生成および Cookie 操作の処理を集約した composition です。

app/compositions/client-id.ts
import { Store } from 'vuex/types'
import { IncomingMessage } from 'connect'
import Cookie from 'universal-cookie'
import {
  name,
  StoreAbTesting,
  ACTIONS,
  GETTERS
} from '~/store/abTesting'

export function useClientId(store: Store<StoreAbTesting>, req: IncomingMessage) {
  const CLIENT_ID_KEY = 'client_id'

  const cookie = process.server
    ? new Cookie(req.headers?.cookie ?? '')
    : new Cookie()

  const getIdFromStore = (): string => {
    return store.getters[`${name}/${GETTERS.clientId}`]
  }

  const prepareId = async (): Promise<void> => {
    if (process.client) {
      throw new Error('CSR でのメソッド呼び出しは不正です')
    }

    const generateId = (): string => {
      // ランダムな ID を生成して返す
    }

    await store.dispatch(
      `${name}/${ACTIONS.setClientId}`,
      getIdFromCookie() ?? generateId()
    )
  }

  const getIdFromCookie = (): string|undefined => {
    return cookie.get(CLIENT_ID_KEY)
  }

  const isIncludedInCookie = (): boolean => {
    return getIdFromCookie() !== undefined
  }

  const setCookie = (): void => {
    if (process.server) {
      throw new Error('SSR でのメソッド呼び出しは不正です')
    }

    if (isIncludedInCookie()) {
      return
    }

    cookie.set(CLIENT_ID_KEY, getIdFromStore())
  }

  return {
    prepareId,
    setCookie,
    getId: getIdFromStore
  }
}

主要な処理について解説します。

まず Cookie の操作に関しては universal-cookie というライブラリを利用しています。

  const cookie = process.server
    ? new Cookie(req.headers?.cookie ?? '')
    : new Cookie()

このライブラリを利用すると Nuxt の SSR モードでもリクエストヘッダから Cookie を取得することができます。使用例に関しては以下の記事が参考になります。

Nuxt は Context から context.req のようにしてリクエストの情報が取得できるので、 useClientId の利用時はこの変数を引数に入力します。この変数から req.headers?.cookie のようにして Cookie ヘッダの値を文字列として取得できるので、これを Cookie コンストラクタの引数に渡してインスタンス生成しています。

次に Cookie から ID を取得、または生成を行う prepareId メソッドです。

  const prepareId = async (): Promise<void> => {
    if (process.client) {
      throw new Error('CSR でのメソッド呼び出しは不正です')
    }

    const generateId = (): string => {
      // ランダムな ID を生成して返す
    }

    await store.dispatch(
      `${name}/${ACTIONS.setClientId}`,
      getIdFromCookie() ?? generateId()
    )
  }

最初の処理で CSR でのメソッド呼び出しを禁止しています。これは直アクセス時の SSR の時点で ID を用意しておけば以降の処理で ID を使い回せるので、 CSR での呼び出しが不要になるためです。

getIdFromCookie() ?? generateId() の部分で Cookie から ID を取得するか、取得できなければ生成を行っています。取得した ID は Vuex の Store を用いて保存しておきます 2。 Store に関しても Nuxt の Context から context.store のように取得できます。何となく cookie.set(id) のようにしておけば後から cookie.get() で ID を取得できそうな気もしてしまいますが、 SSR で cookie.set() を呼び出してもブラウザ上の Cookie には影響がないため、このような方法では実現できません。

次に Cookie を設定する setCookie メソッドです。

  const setCookie = (): void => {
    if (process.server) {
      throw new Error('SSR でのメソッド呼び出しは不正です')
    }

    if (isIncludedInCookie()) {
      return
    }

    cookie.set(CLIENT_ID_KEY, getIdFromStore())
  }

前述の通り SSR で cookie.set() を呼び出しても意味がないので、 SSR でのメソッド呼び出しを禁止しています。そして ID が Cookie に含まれていない場合のみ Store に保存しておいた ID を取得して Cookie に設定します。

このように実装した useClientId の諸々の処理は plugins から呼び出します。

app/plugins/ab-testing.ts
import { Context } from '@nuxt/types'
import { useClientId } from '~/compositions/libs/ab-testing/client-id'

export default (context: Context) => {
  if (process.server) {
    useClientId(context.store, context.req).prepareId()
  } else {
    useClientId(context.store, context.req).setCookie()
  }
}

前述した通り、 prepareId は SSR で、 setCookie は CSR で呼び出します。これらの処理を Middleware から呼び出しても問題ないように思えてしまいますが、 Middleware の場合は直アクセス時に SSR の処理しか実行されないため Cookie が設定されません。 Nuxt のライフサイクルに関しては以下の記事が参考になります。

後は nuxt.config.js に plugins を追加し、 Vue コンポーネント等から useClientId(store, req).getId() のような形で ID を取得する流れになります。

Laravel

大まかな内容は Nuxt.js と変わりませんが、 Laravel の場合は Store の代わりに単なる singleton インスタンスとしてメモリに保持する方針で実装します 3

まず singleton インスタンスの元となるクラス AbTestingCookie を以下のように作成します。

app/Http/Cookie/AbTestingCookie.php
<?php

namespace App\Http\Cookie;

use Illuminate\Http\Request;

class AbTestingCookie
{
    private $id;

    public function __construct(Request $request)
    {
        $key = 'client_id';

        if ($request->hasCookie($key)) {
            $this->id = $request->cookie($key);
            return;
        }

        $this->id = $this->make();
    }

    public function make()
    {
        // ランダムな ID を生成して返す
    }

    public function id()
    {
        return $this->id;
    }
}

コンストラクタでは Request を引数に取り、 Cookie から ID を取得するか、取得できなければ生成し、 ID をプロパティに保持しておきます。

singleton を生成するための ServiceProvider の実装は以下のとおりです。

app/Providers/AbTestingServiceProvider.php
<?php

namespace App\Providers;

use App\Http\Cookie\AbTestingCookie;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;

class AbTestingServiceProvider extends ServiceProvider
{
    public function boot(Request $request)
    {
        $this->app->singleton(AbTestingCookie::class, function ($app) use ($request) {
            return new AbTestingCookie($request);
        });
    }
}

次にレスポンスの Set-Cookie ヘッダに ID を付与する処理を Middleware で実装します。

app/Http/Middleware/IssueAbTestingClientId.php
<?php

namespace App\Http\Middleware;

use App\Http\Cookie\AbTestingCookie;
use Closure;

class IssueAbTestingClientId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $key = 'client_id';

        if ($request->hasCookie($key)) {
            return $next($request);
        }

        return $next($request)->withCookie(cookie(
            $key,
            app(AbTestingCookie::class)->id(),
            0,
            null,
            null,
            false,
            false,  // front-end からも利用するため httpOnly にしない
            false,
            null
        ));
    }
}

この Middleware は ID の生成・利用が必要なすべてのルートで呼び出されるように Kernel.php に設定しておきます。また withCookie() メソッドはデフォルトで HttpOnly 属性を付与しますが、 Nuxt.js からも読み出せるように HttpOnly 属性を外しています。

最後に Nuxt.js から Cookie を読み出せるように EncryptCookies の暗号化の処理対象から除外しておきます。

app/Http/Middleware/EncryptCookies.php
<?php

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware
{
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [
        'client_id', // front-end からも利用するため暗号化しない
    ];
}

後は view 等から app()->make(AbTestingCookie::class)->id() のようにして ID を取得する流れになります。


  1. サーバー側ではレスポンスに Set-Cookie ヘッダを、クライアント側では document.cookie を利用します。 

  2. 単に State に clientId を保存するだけなので、 Store の実装は割愛します。 

  3. Stack Overflow の Q&A を参考にしています。 

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

React + Unstated Next: 複数コンポーネントのツリーの中で状態を共有して管理する

Unstated Nextは、複数コンポーネントにより組み立てられたツリーの中で、状態を共有して管理するライブラリです。簡単なサンプルをつくりながら、使い方についてご紹介します。

Unstated Nextの特徴

Reactにフックが採り入れられて、useContextを使えばReduxに頼らなくても、扱う状態の規模がさほど大きくなければ手軽に管理できるようになりました。Unstated Nextは、それをさらにシンプルにしてくれるライブラリです。

Reactのカスタムフックコンテクストがわかっていれば、すぐに使いはじめられます。APIが最小限にまとめられ、ライブラリのサイズはわずか200バイトです。

Reactのコンテクストに当たる状態のまとめ役を、Unstated Nextではコンテナと呼びます。ひとつの状態をまとめて管理するReduxと比べると、コンテナは小分けできることもパフォーマンスの点からは有利です。ただしよく考えて設計しないと、結局コンテナが何重にも入れ子になってしまうことには、注意しなければなりません。

はじめの一歩

まず、Reactアプリケーションのひな形は、Create React Appでつくりましょう。コマンドラインツールでnpx create-react-appにつづけて、アプリケーション名(今回はreact-unstated-next-example)を打ち込んでください。

npx create-react-app react-unstated-next-example

アプリケーション名でつくられたディレクトリに切り替えて(cd react-unstated-next-example)、コマンドyarn startでひな形アプリケーションのページがローカルホスト(http://localhost:3000/)で開くはずです。

つぎに、このディレクトリにインストールするのはUnstated Next(unstated-next)です。yarn addコマンドでライブラリが加えられます。

yarn add unstated-next

yarnでなく、npm installコマンドでインストールしても構いません。

npm install --save unstated-next

カスタムフックとuseContextを使ってつくるカウンター

カスタムフックとコンテクストがわかれば、Unstated Nextはすぐに使えます。ということで、まずは素のReactのカスタムフックとuseContextだけで、ライブラリは使わずにカウンターのアプリケーションをつくってみましょう。

カスタムフックをつくる

「フックとは、関数コンポーネントにstateやライフサイクルといった Reactの機能を"接続する(hook into)"ための関数です」(「要するにフックとは?」)。さらに、フックを独自につくって、コンポーネントからロジックを切り出すこともできます。そうすれば、コンポーネントのコードがすっきり見やすくなるとともに、そのカスタムフックを使い回すこともできるのです。

自分独自のフックを作成することで、コンポーネントからロジックを抽出して再利用可能な関数を作ることが可能です。
(「独自フックの作成」より)

カスタムフックのモジュールsrc/useCounter.jsの定めはコード001のとおりです。カウンターのロジックですから、値の状態変数(count)にその設定関数(setCount())、減算(decrement())と加算(increment())の関数を加えました。

カスタムフックは、関数コンポーネントと異なり、JSXで要素を返す必要はありません。戻り値のオブジェクトに収めたのは、状態変数(count)と減算(decrement())および加算(increment())の関数です。なお、カスタムフックの基本的な役割や考え方については「React: コンポーネントのロジックをカスタムフックに切り出す ー カウンターの作例で」をお読みください。

コード001■カウンターのカスタムフック

src/useCounter.js
import { useState } from "react";

export const useCounter = (initialState = 0) => {
    const [count, setCount] = useState(initialState);
    const decrement = () => setCount(count - 1);
    const increment = () => setCount(count + 1);
    return { count, decrement, increment };
};

カウンターを表示・操作するコンポーネントの作成

カウンターを表示・操作するコンポーネントが以下のコード002です。フックuseContextは、このあとアプリケーション(App)でつくられるコンテクスト(CounterContext)から、カスタムフック(useCounter)が返すオブジェクト(counter)を取り出します。その中の状態変数(counter.count)や減算(counter.decrement)・加算(counter.increment)の関数を、それぞれの要素に割り当てればよいのです。

コード002■カウンター表示のコンポーネント

src/CounterDisplay.js
import React, { useContext } from "react";
import { CounterContext } from './App';

const CounterDisplay = () => {
    const counter = useContext(CounterContext);
    return (
        <div>
            <button onClick={counter.decrement}>-</button>
            <span>{counter.count}</span>
            <button onClick={counter.increment}>+</button>
        </div>
    );
}
export default CounterDisplay;

コンテクストのProviderで子コンポーネントを包む

アプリケーションのモジュール(src/App.js)でコンテクスト(CounterContext)をつくります(コード003)。そのために呼び出す関数がcreateContext()です。コンテクストはContext.Providerコンポーネントを備えています。このコンポーネントに含めた子はすべて、コンテクストが参照できるという仕組みです。

コンテクストに与える変数や関数の参照は、Context.Providerコンポーネントのvalueプロパティに与えてください。今回はカスタムフックuseCounterから得たオブジェクト(counter)が渡されました。

コード003■コンテクストのProvidervalueに参照するオブジェクトを与える

src/App.js
import React, { createContext } from 'react';
import { useCounter } from './useCounter';
import CounterDisplay from './CounterDisplay';
import './App.css';

export const CounterContext = createContext();
function App() {
    const counter = useCounter();
    return (
        <CounterContext.Provider value={ counter }>
            <div className="App">
                <CounterDisplay />
            </div>
        </CounterContext.Provider>
    );
}
export default App;

これで、コンテクストを使ったカウンターができあがりました(図001)。

図001■コンテクストを使ったカウンター

2009001_001.png

Unstated Nextでカウンターをつくり替える

カスタムフックとコンテクストがわかりましたので、カウンターをUnstated Nextで動くようにつくり直して見ましょう。コンテクストに替えて、コンテナをつくります。

カスタムフックからコンテナをつくる

モジュールsrc/useCounter.jsのカスタムフックは基本的に変わりません。フックを関数createContainer()でコンテナに包むのです。コンテナ(CounterContainer)は、いわばカスタムフック(useCounter)のロジックを備えたコンテクストといえます。

src/useCounter.js
import { createContainer } from "unstated-next";

// export const useCounter = (initialState = 0) => {
const useCounter = (initialState = 0) => {

};

export const CounterContainer = createContainer(useCounter);

コンポーネントをコンテナのProviderで包む

アプリケーションモジュールsrc/App.jsは、コンテクストをUnstated Nextのコンテナに差し替えます。コンテナにもコンテクストと同じように<Container.Provider>が備わっているのです。Providerもコンテクストからコンテナに書き替えてください。ただし、valueプロパティは要りません。

src/App.js
// import React, { createContext } from 'react';
import React from 'react';
// import { useCounter } from './useCounter';
import { CounterContainer } from './useCounter';

// export const CounterContext = createContext();
function App() {
    // const counter = useCounter();
    return (
        // <CounterContext.Provider value={ counter }>
        <CounterContainer.Provider>

        {/* </CounterContext.Provider> */}
        </CounterContainer.Provider>
    );
}

コンテナのロジックをコンポーネントが使う

コンテナはカスタムフックのロジックを備えているのでした。コンテナ(CounterContainer)に対してuseContainer()を呼び出すと、ロジックの参照が得られるのです。参照はコンテクストを使ったときと同じ変数(counter)に収めれば、ほかに書き直すところはありません。

src/CounterDisplay.js
// import React, {useContext} from "react";
import React from "react";
// import { CounterContext } from './App';
import { CounterContainer } from './useCounter';

function CounterDisplay() {
  // const counter = useContext(CounterContext);
  const counter = CounterContainer.useContainer();

}

これでカウンターはUnstated Nextのコードに書き替えられました。モジュール3つの記述を以下のコード004にまとめます。カスタムフックとコンテクストでも組み立ては簡単でした。でも、ふたつをまとめたコンテナを使うことでさらにシンプルになったでしょう。CodeSandboxに作例をサンプル001として掲げました。

コード004■Unstated Nextを使ったカウンター

src/useCounter.js
import { useState } from "react";
import { createContainer } from "unstated-next";

const useCounter = (initialState = 0) => {
    const [count, setCount] = useState(initialState);
    const decrement = () => setCount(count - 1);
    const increment = () => setCount(count + 1);
    return { count, decrement, increment };
};

export const CounterContainer = createContainer(useCounter);
src/App.js
import React from 'react';
import { CounterContainer } from './useCounter';
import CounterDisplay from './CounterDisplay';
import './App.css';
function App() {
    return (
        <CounterContainer.Provider>
            <div className="App">
                <CounterDisplay />
            </div>
        </CounterContainer.Provider>
    );
}
export default App;
src/CounterDisplay.js
import React from "react";
import { CounterContainer } from './useCounter';

const CounterDisplay = () => {
  const counter = CounterContainer.useContainer();
    return (
        <div>
            <button onClick={counter.decrement}>-</button>
            <span>{counter.count}</span>
            <button onClick={counter.increment}>+</button>
        </div>
    );
}
export default CounterDisplay;

サンプル001■ Unstated Nextでカウンターをつくる

2009001_002.png
>> CodeSandboxへ

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

【JavaScript】clmtrackrで顔認識して遊ぶ

真夏のピークも過ぎて、すっかり過ごしやすい季節になってきましたね!
というわけで、ここ最近jsで顔認識して遊んでみたのでその過程を記事にしてみたいと思います。(ここに至るまでに色々と詰まったところもあったので、もしかしたらもしかするとこの知見が誰かの役に立つことを願いつつ)

何を作ったか

shimabox/F3: Face(eyes)! Face(nose)! Face(mouth)! です。

これは

  • jsを使って顔認識をします
  • 目、鼻、口をそれぞれ描画します
    • 顔だけを描画することもできます
  • 停止中に顔部品をドラッグすることができます
  • デバッグモードで座標の描画と顔部品を実際の座標に描画し直します

という、ぶっちゃけ誰得?なものになっていますが、jsだけでここまでできるぞっていうことが伝われば幸いです。
※ clmtrackrを使ったサンプルはけっこう出ていますけども、、
※ 自分は生粋のペチパーなので間違っているところがあれば突っ込みお願いします

Demo

demo.gif
https://shimabox.github.io/F3/
qr.png

実装の流れ

作った過程を一つずつ書いていきます。
(コミットログは違いますが、こんなイメージだと捉えていただければと。。)

1. videoをcanvasに描画

まず、videoタグを使ってwebカメラから取得した画像をcanvasに描画します。
ここは稚拙ではありますが、shimabox/v2c: Video(webcam) to canvas. を利用しました。

こんなイメージ
img1.png

2. canvasをclmtrackrに食わせる

canvasに描画された画像をclmtrackrに食わせて顔を認識させて顔座標情報を取得します。

こんなイメージ
img2.png

3. レイヤーを被せて、その上に顔部品を描画する

レイヤー(空のdiv)を用意し、顔を描画しているcanvasに被せていったん隠します。
2.で取得した顔座標を使って顔部品を作成しcanvasに描画します。

こんなイメージ
img3.png

Reference にある顔座標から、左目(眉)、右目(眉)、鼻、口、顔のみを抽出してそれぞれ描画しています。
※ 後述しますが、フロントカメラで描画するときはtransform: scaleX(-1);を意識する必要があります

4. カメラスイッチ、デバッグモード、顔部品をコントロールするUIなどを設置する

顔部品をコントロールするUIはdataarts/dat.gui を利用しました。

最終的にこうなります
img4.png

実装の流れはこんな感じです。
余談ですが、顔認識して座標をそれぞれ返すようなライブラリであればclmtrackrでなくても大丈夫だと思います。

苦労したところ

実装の流れの中で特に苦労した点を書いていきます。

フロントカメラで描画するときはtransform: scaleX(-1);を意識する

どういうことなんだぜ?

フロントカメラを利用する場合、videoを描画するcanvasにはtransform: scaleX(-1);をつけるのがセオリーっぽいです。そうしないと普段フロントカメラで見ているような鏡のような描画で映らないです。で、transform: scaleX(-1);をつけていると、そこから返却されるx座標が反転された位置?で返ってくるので上手く計算して描画しないと意図しない描画となってしまいます。

今回で言うと以下の計算式で、顔部品のx座標を求めました。
videoを描画しているcanvasの横幅 - (clmtrackrから返却される顔部品のx座標 - 顔部品の横幅) + 顔部品の余白
これでフロントカメラ利用時に、ピッタリ描画されるようになりました。

これプラス、フロントカメラを利用している場合はclmtrackrから返却される座標を入れ替える処理も入れています。このあたりは、clmtrackr.jsで顔認識してへのへのもへじを描画する | Shimabox Blogでも記載しています。

なお、リア(背面)カメラ利用時は以下の計算式になります。
clmtrackrから返却される顔部品のx座標 - 顔部品の余白

スマホでドラッグするとエラー

停止中に顔部品をドラッグする機能を JavaScriptを使って要素をドラッグ&ドロップで移動 | q-Az を参考にして謎につけたのですが、コンソールでエラーを吐いてしまいました。

こんなエラー

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.

これはググってみるとどうやら、スクロールジャンクというやつみたいです。
touchmove内でpreventDefaultを呼び出す(デフォルトのイベントを止めたい)場合は、AddEventListenerOptions(addEventListener:第3引数)のpassiveの値をfalseにしないといけないみたい。いやぁ、全然知らんかったわぁ。

というわけで、2019年、JavaScriptでのスクロール一時禁止はこれだ!(スマートフォン) - Qiitaの記事を参考に解決させていただきました。

また、素直にtouchmoveのハンドラー内でe.preventDefault();だけを記述していたところ

Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.

が発生していたので、javascript - Touch move getting stuck Ignored attempt to cancel a touchmove - Stack Overflow を参考に

if (e.cancelable) {
    e.preventDefault();
}

の記述をしています。

iOSのSafariでまったく動かなかった

class構文使えるやんけ!ほんなら使ってみよう!と思い当初意気揚々とこういった形で実装していたのですが、

class FaceTracker {
    _v2c;
    _ctracker;
    _stage;

    constructor(v2c, ctracker, stage) {
         this._v2c = v2c;
         this._ctracker = ctracker;
         this._stage = stage;
    }

    // 〜 略 〜

}

こんなエラーが出てiOSのSafariでまったく動きませんでした。

Unexpected token '='. Expected an opening '(' before a method's parameter list

これはパブリッククラスフィールドをサポートしていないのが原因でしたので、コンストラクタ内ですべて初期化する形にしています。トランスパイラを使えばいい感じにしてくれたかもしれませんが、そんな知識が自分にはないサクッと作りたかったので生のjsで書きました。

なお、iOS14でサポートされるようなので今なら上手く動くかもしれません。
※ 実装中はFireFoxでも動かなかった気がしたのですが、今ならパブリッククラスフィールドを使っても動いているようです

ドラッグ後の座標反映に困った

顔部品にそれぞれ、_distance(距離)_degree(度)を持たせてJavaScriptの三角関数とcanvasで円運動アニメーションを作るを参考にグリグリと移動するようにしていたのですが移動の停止中にドラッグ可能としたことでドラッグ後の距離と度を反映させる必要がありました。そうしないと再び移動を開始した際にドラッグ前の座標に戻ってしまいます。

そこで、

の記事を参考に、停止した時点の座標(x1:this._lastLeftPosition, y1:this._lastTopPosition)からドラッグ後の座標(x2:dragEndX, y2:dragEndY)を使って_distanceと_degreeを再度反映させるようにしドラッグ後の解決をさせていただきました。

// 停止中のパーツ座標(this._lastLeftPosition, this._lastTopPosition)と
// ドラッグ後の座標(dragEndX, dragEndY) から距離と角度を計算して反映
this._distance = Math.sqrt(Math.pow(dragEndX - this._lastLeftPosition, 2) + Math.pow(dragEndY - this._lastTopPosition, 2));
this._degree = Math.atan2(dragEndY - this._lastTopPosition, dragEndX - this._lastLeftPosition);

※ ソースから抜粋

dat.guiでラジオボタンを表現する

顔部品のコントロールにdataarts/dat.guiを利用しているのですが、目線の種類を選ぶ部分でラジオボタンにする必要がありましたが、どうにもラジオボタン的なものはない様子でしたので以下を参考に実装しました。

javascript - Radio buttons with dat.gui - Stack Overflow

_setUpEyeLineController() {
    const eyeLine = this._gui.addFolder('Eye Line');
    eyeLine.open();

    const none = eyeLine.add(this._guiParameter, 'none')
        .listen()
        .onChange(() => setEyeLine('none'));
    const mosaic = eyeLine.add(this._guiParameter, 'mosaic')
        .listen()
        .onChange(() => setEyeLine('mosaic'));
    const line = eyeLine.add(this._guiParameter, 'line')
        .listen()
        .onChange(() => setEyeLine('line'));

    const setEyeLine = (prop) => {
        const eyeLineParameter = [
            'none',
            'mosaic',
            'line'
        ]
        for (const param of eyeLineParameter){
            this._guiParameter[param] = false;
        }
        this._guiParameter[prop] = true;

        if (prop !== 'none') {
            this._faceTracker.addEyeLine(prop);
            return;
        }
        this._faceTracker.addEyeLine('');
    }
}

※ ソースから抜粋

おわりに

ふと気づいたら無駄に長くなってしまいました。
冒頭で述べたとおり、誰得な内容なのですが誰かのお役に立てれば幸いです。

ちなみになのですが、自分はこういった顔認識して遊ぶ系がなぜか好きで 顔認識 | Shimabox Blog に今まで作ったやつを書いていたりするので、こちらもお暇があれば覗いてくれると嬉しいです。

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

Reactでローカルストレージとstateを連携させる例とNext.jsの注意点

はじめに

Reactで以下のようにローカルストレージとstateを同期させる場合のサンプルを掲載します。

初回のstate:

  • ローカルストレージに値が保存されていたらその値を使う
  • そうでなければ任意の初期値を与える

stateの更新時:

  • state更新に用いた値をローカルストレージに保存する

ツールはcreate-react-app(以下CRA)とNext.jsの2種類になります。
両者ともに余計なコンポーネントや既存のCSSは消去して確認しています。
なおCRAとNext.jsの基礎的なセットアップ方法と仕組みについては省略します。

サンプルはライトテーマとダークテーマの切替を想定しています。
これを動かすと以下のような画面になります。

スクリーンショット 2020-09-23 6.15.59.png

ちなみにローカルストレージの動きはChromeのApplicationタブ等で確認できます。

記事を書いた背景

言語切替機能を付けたWebサイトをNext.jsで実装していたのですが、Next.jsで少しハマる部分がありました。
機能概要は、初回は日本語で読み込み、日本語か英語か選択した後は次回読み込み時にその状態が保存されるものです。

詳細はstateによりreact-helmetでhtmlタグのlang属性を弄り、また言語切替用Contextの値に代入する仕組み(※)ですが、Context等は本題でないので省略して簡素な例を掲載します。

※ 参考にしたページ:https://ja.reactjs.org/docs/context.html#dynamic-context

その際にローカルストレージ周りについて少しコードを書いて整理したノウハウを記録しておきたいと思いました。

CRA

  1. themeステートの初期値はローカルストレージのthemeキーがあればその値を、無ければ'light'を代入します。
  2. ボタンの押下によりthemeステートに'light'または'dark'を代入し、ローカルストレージにもその値をセットします。
  3. Styled textのスタイルはthemeステートが'light''dark'かにより変わります。
App.js
import React, { useState } from 'react';

function Sample() {
  const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light');

  const setLight = () => {
    setTheme('light');
    localStorage.setItem('theme', 'light');
  };

  const setDark = () => {
    setTheme('dark');
    localStorage.setItem('theme', 'dark');
  };

  const getStyleFromTheme = () => {
    if (theme === 'light') return { backgroundColor: 'lightgray', color: 'black' };
    if (theme === 'dark') return { backgroundColor: 'black', color: 'white' };
  };

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={setLight}>Light</button>
      <button onClick={setDark}>Dark</button>
      <div style={getStyleFromTheme()}>Styled text</div>
    </div>
  );
}

function App() {
  return (
    <div>
      <Sample />
    </div>
  );
}

export default App;

Next.js

CRAの処理をそのまま流用しようと試みましたが、これは上手くいきません。

_app.js
import React, { useState } from 'react';

function Sample() {
  const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light');

  const setLight = () => {
    setTheme('light');
    localStorage.setItem('theme', 'light');
  };

  const setDark = () => {
    setTheme('dark');
    localStorage.setItem('theme', 'dark');
  };

  const getStyleFromTheme = () => {
    if (theme === 'light') return { backgroundColor: 'lightgray', color: 'black' };
    if (theme === 'dark') return { backgroundColor: 'black', color: 'white' };
  };

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={setLight}>Light</button>
      <button onClick={setDark}>Dark</button>
      <div style={getStyleFromTheme()}>Styled text</div>
    </div>
  );
}

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Sample />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;
index.js
export default function Home() {
  return <div></div>;
}

スクリーンショット 2020-09-23 6.28.34.png

これには多少の工夫が必要です。

どうすれば良いのかというと、themeの初期値はundefinedを代入しておきます。
そしてuseEffect(もしくはcomponentDidMount)の内部でローカルストレージによる条件分岐を書きます。

実際のコード例は以下のようになります。

_app.js
import React, { useState, useEffect } from 'react';

function Sample() {
  const [theme, setTheme] = useState(undefined);

  useEffect(() => {
    setTheme(localStorage.getItem('theme') || 'light');
  }, []);

  const setLight = () => {
    setTheme('light');
    localStorage.setItem('theme', 'light');
  };

  const setDark = () => {
    setTheme('dark');
    localStorage.setItem('theme', 'dark');
  };

  const getStyleFromTheme = () => {
    if (theme === 'light') return { backgroundColor: 'lightgray', color: 'black' };
    if (theme === 'dark') return { backgroundColor: 'black', color: 'white' };
  };

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={setLight}>Light</button>
      <button onClick={setDark}>Dark</button>
      <div style={getStyleFromTheme()}>Styled text</div>
    </div>
  );
}

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Sample />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

index.js
export default function Home() {
  return <div></div>;
}

これで正常に表示され、一件落着です。

蛇足

調べるとcomponentDidMountuseEffect内でstateを更新することはアンチパターンという情報がありますが、どの程度厳密に守るべきなのか分かりません。筆者はまだReactについて知識が曖昧な点が沢山あるので優しい方はご教示頂けると嬉しいです。

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

Reactでローカルストレージとstateを連携させるサンプル

はじめに

Reactで以下のようにローカルストレージとstateを同期させる場合のサンプルを掲載します。

初回のstate:

  • ローカルストレージに値が保存されていたらその値を使う
  • そうでなければ任意の初期値を与える

stateの更新時:

  • state更新に用いた値をローカルストレージに保存する

ツールはcreate-react-app(以下CRA)とNext.jsの2種類になります。
両者ともに余計なコンポーネントや既存のCSSは消去して確認しています。
なおCRAとNext.jsの基礎的なセットアップ方法と仕組みについては省略します。

サンプルはライトテーマとダークテーマの切替を想定しています。
これを動かすと以下のような画面になります。

スクリーンショット 2020-09-23 6.15.59.png

ちなみにローカルストレージの動きはChromeのApplicationタブ等で確認できます。

記事を書いた背景

言語切替機能を付けたWebサイトをNext.jsで実装していたのですが、Next.jsで少しハマる部分がありました。
機能概要は、初回は日本語で読み込み、日本語か英語か選択した後は次回読み込み時にその状態が保存されるものです。

詳細はstateによりreact-helmetでhtmlタグのlang属性を弄り、また言語切替用Contextの値に代入する仕組み(※)ですが、Context等は本題でないので省略して簡素な例を掲載します。

※ 参考にしたページ:https://ja.reactjs.org/docs/context.html#dynamic-context

その際にローカルストレージ周りについて少しコードを書いて整理したノウハウを記録しておきたいと思いました。

CRA

  1. themeステートの初期値はローカルストレージのthemeキーがあればその値を、無ければ'light'を代入します。
  2. ボタンの押下によりthemeステートに'light'または'dark'を代入し、ローカルストレージにもその値をセットします。
  3. Styled textのスタイルはthemeステートが'light''dark'かにより変わります。
App.js
import React, { useState } from 'react';

function Sample() {
  const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light');

  const setLight = () => {
    setTheme('light');
    localStorage.setItem('theme', 'light');
  };

  const setDark = () => {
    setTheme('dark');
    localStorage.setItem('theme', 'dark');
  };

  const getStyleFromTheme = () => {
    if (theme === 'light') return { backgroundColor: 'lightgray', color: 'black' };
    if (theme === 'dark') return { backgroundColor: 'black', color: 'white' };
  };

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={setLight}>Light</button>
      <button onClick={setDark}>Dark</button>
      <div style={getStyleFromTheme()}>Styled text</div>
    </div>
  );
}

function App() {
  return (
    <div>
      <Sample />
    </div>
  );
}

export default App;

Next.js

CRAの処理をそのまま流用しようと試みましたが、これは上手くいきません。

_app.js
import React, { useState } from 'react';

function Sample() {
  const [theme, setTheme] = useState(localStorage.getItem('theme') || 'light');

  const setLight = () => {
    setTheme('light');
    localStorage.setItem('theme', 'light');
  };

  const setDark = () => {
    setTheme('dark');
    localStorage.setItem('theme', 'dark');
  };

  const getStyleFromTheme = () => {
    if (theme === 'light') return { backgroundColor: 'lightgray', color: 'black' };
    if (theme === 'dark') return { backgroundColor: 'black', color: 'white' };
  };

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={setLight}>Light</button>
      <button onClick={setDark}>Dark</button>
      <div style={getStyleFromTheme()}>Styled text</div>
    </div>
  );
}

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Sample />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;
index.js
export default function Home() {
  return <div></div>;
}

スクリーンショット 2020-09-23 6.28.34.png

これには多少の工夫が必要です。

どうすれば良いのかというと、themeの初期値はundefinedを代入しておきます。
そしてuseEffect(もしくはcomponentDidMount)の内部でローカルストレージによる条件分岐を書きます。

実際のコード例は以下のようになります。

_app.js
import React, { useState, useEffect } from 'react';

function Sample() {
  const [theme, setTheme] = useState(undefined);

  useEffect(() => {
    setTheme(localStorage.getItem('theme') || 'light');
  }, []);

  const setLight = () => {
    setTheme('light');
    localStorage.setItem('theme', 'light');
  };

  const setDark = () => {
    setTheme('dark');
    localStorage.setItem('theme', 'dark');
  };

  const getStyleFromTheme = () => {
    if (theme === 'light') return { backgroundColor: 'lightgray', color: 'black' };
    if (theme === 'dark') return { backgroundColor: 'black', color: 'white' };
  };

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={setLight}>Light</button>
      <button onClick={setDark}>Dark</button>
      <div style={getStyleFromTheme()}>Styled text</div>
    </div>
  );
}

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Sample />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

index.js
export default function Home() {
  return <div></div>;
}

これで正常に表示され、一件落着です。

蛇足

調べるとcomponentDidMountuseEffect内でstateを更新することはアンチパターンという情報がありますが、どの程度厳密に守るべきなのか分かりません。筆者はまだReactについて知識が曖昧な点が沢山あるので優しい方はご教示頂けると嬉しいです。

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

【図解】reduxの使い方

reduxとは?

UIのステートをアプリ全体で管理するためのフレームワーク。ReactやAngularJS、Vueなどで使用することができます。

なぜreduxが必要なのか?

なぜreduxが必要なのかというと、ステートをアプリ全体で管理することで読みやすいコードを書けるからです。

reduxを使わなければ保守性が落ちてしまいます。
1.reduxを使わない
2.コンポーネント間のやり取りが増える
3.どこでステートが更新されたのかがわかりづらい

その問題を解決するのがreduxです。
1.ステートをアプリ全体で管理する
2.コンポーネント間のやり取りが減る
3.他のコンポーネントに依存しにくいコードが書ける

reduxの流れ

reduxでのステートを更新の流れは主に4ステップです。

1.UIでイベントが発生
2.reduxに通知(イベントリスナー)
3.ステートの更新(イベントハンドラー)
4.UIのリレンダリング

1.イベントの発生

ユーザーがボタンを押したり、文字を入力したタイミングでイベントが発生します。

2.reduxへの通知

コンポーネントで発生したイベントに該当するアクションをストアーに渡し、
ステートの更新が必要なことを通知します。
アクションには、
・どんなイベントが発生したのか?(type属性)
・どんな値を渡すのか?(payload属性)
という情報が含まれます。

このアクションをストアーに通知するためにdispatchメソッドを経由します。
言い換えるとdispatchはイベントリスナーとしての働きです。

3.ステートの更新

ストアーに変更が通知されるとstoreの中のreducerで
・引き渡されたアクション
・前回のステート
の2つを基に新しいステートに更新します。
言い換えるとreducerはイベントハンドラーとしての働きです。

4.UIのリレンダリング

最後にストアーのステートが更新されるとUIに変更を通知します。
その後、UIはリレンダリングされます。

reduxの実装方法

それでは、実際にReactでreduxを実装していきましょう。
サンプルプログラムは入力項目に入力した内容とセットした回数をカウントするプログラムです。

実装手順は次の5ステップ。
1.reducerの作成
2.アクションクリエーターの作成
3.ストアーの作成
4.ストアーとコンポーネントの連携
5.UIのイベントハンドラーの実装

1.reducerの作成

まずはアクションがストアーに渡されたときに新しいステートに更新する処理を記述していきます。

reducer.jsx
// ステートの初期化
const initialState = {
  count:0,
  input:null
}

// リデューサーを定義 
export default function reducer(state = initialState, action) {
  switch(action.type) {
    case 'SET_INPUT':
      return {
          count:   state.count + 1  //カウントアップ
          , input: action.input     //入力内容をセット
        }
    default:
      return state
  }
}

reducer関数の引数は次の2つです。
・第一引数:前回のステート
・第二引数:アクション
アクションのtype属性で「どんな処理が発生したのか」がわかるので条件分岐させてイベントハンドラーを記述します。

今回のソースでは
・count:前回の回数に+1
・input:入力された値をセット
の2つのステートを更新します。

そして、デフォルト引数では「どんなステートがセットされるのか」を宣言します。
これにより、アクションが初めて呼ばれる際のステートを決めることができます。

2.アクションクリエーターの作成

ストアーに引き渡すアクションを記述していきます。

action.jsx
export function setInput(input) {
  return {
    type: 'SET_INPUT',
    input: input
  }
}

アクションには
・type属性:どんな処理を行うのか?
・payload属性:どんな値が渡ってきたのか?
の2つを記述していきます。payload属性は任意ですが、type属性は必須です。

3.ストアーの作成

アプリ全体で一つのストアーを管理したいためルートコンポーネントであるApp.jsで2つのことを行います。
・ストアーの作成
・ストアーの引き渡し

App.jsx
import React,{ Component } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { createStore } from 'redux'
import reducer from './redux/reducer'
import { Provider } from 'react-redux';

//screens
import click from './redux/click-con';
import show from './redux/show-con';

// 1.ストアの作成
const store = createStore(reducer);

class App extends Component {
  render() {
    return (
      <BrowserRouter>
        {/* 2.ストアーの引き渡し */}
        <Provider store={store}>
          <Switch>
              <Route exact path="/click" component={click} />
              <Route exact path="/show" component={show} />
          </Switch>
        </Provider>
      </BrowserRouter>
    );
  }
}

export default App;

redux.createStoreメソッドに先ほど作ったreducerを渡して、ストアーを作成します。
このままだとストアーができただけで、コンポーネントにストアーが渡されていない状態です。
そのため、「react-redux」のProviderタグを使って各コンポーネントに渡します。

4.ストアーとコンポーネントの連携

connectメソッドを使ってストアーとコンポーネントを連携します。

click-con.jsx
import { connect } from 'react-redux'
import { setInput } from './action'
import click from '../screen/click'

const mapStateToProps = state => {
   const { input, count } = state
   return { input, count }
 }

 const mapDispatchToProps = dispatch => {
   return {
    setInputClick: (input) => { 
      dispatch(setInput(input));
      alert(`「${input}」がセットされました!`)
    }
   }
 }

export default connect(mapStateToProps,mapDispatchToProps)(click)
show-con.jsx
import { connect } from 'react-redux'
import show from '../screen/show'

const mapStateToProps = state => {
   const { input, count } = state
   return { input, count }
 }
export default connect(mapStateToProps)(show)

このconnectには主に2つの引数を渡します。
・mapStateToProps:必要なデータをストアーから取得
・mapDispatchToProps:ストアーにアクションを通知する関数

mapStateToProps

ストアーから渡されたステートをコンポーネントに渡します。
・第一引数:現在のステート
・戻り値:コンポーネントに渡すステート
また、ストアーのステートをそのまま渡すだけではなく、mapStateToProps内でデータを整形して渡すことができます。
そして、戻り値に設定したステートはコンポーネントの引数に追加されます。

mapDispatchToProps

コンポーネントにストアーに変更を通知するアクションクリエーターを渡します。
・第一引数:dispatchメソッド
・戻り値:ストアーに通知するアクションオブジェクト

dispatchメソッドにアクションオブジェクトを渡すことでストアーに通知できます。
mapStateToPropsと同じように戻り値に渡したオブジェクトはコンポーネントの引数に追加されます。

5.UIのイベントハンドラーの実装

最後にUIのイベントハンドラーにconnectメソッドで引き渡されたdispatchでラップされたアクションクリエータを呼び出します。

click.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom'

export default (props) => {

  const style = {
    textAlign: "center",
    marginTop: "10px"
  };

  const [input, setInput] = useState(props.input);

  /**
   * inputを変更
   */
  const changeInput = (e) => {
    setInput(e.target.value);
  }

  return (
    <div style={style}>
      <input type="text" value={input} onChange={changeInput}></input>
      <button onClick={() => props.setInputClick(input)}>値セット</button>
      <br/><Link to="/show">表示へ</Link>
    </div>
  );
}
show.js
import React from 'react';
import { Link } from 'react-router-dom'

export default (props) => {
  const divStyle = {
    textAlign: "center",
    marginTop: "10px",
    "ul" : {
      listStyle: "none"
    }
  };

  const ulStyle = {
    listStyle: "none"
  }

  return (
    <div style={divStyle}>
      <ul style={ulStyle}>
        <li>カウント:{props.count}</li>
        <li>値:{props.input}</li>
      </ul>
      <Link to="/click">セットへ</Link>
    </div>

  );
}

QA

Q.全てのステートを渡すのはダメ?

A.全てステートをコンポーネントに渡すことは可能ですが、避けたほうが無難です。
コンポーネントに渡すステートが変更されたかどうかを判定してUIはリレンダリングするのかを判断しています。
そのため、全てのステートを渡すと無駄にリレンダリングされる可能性があります。
パフォーマンスを良くしたいのであれば、必要なステートだけをコンポーネントに渡すのが最善です。

ちなみに再処理される条件は以下の通りです。
・mapStateToProps ⇒ ステートが変更されたとき
・UIのリレンダリング ⇒ mapStateToPropsの戻り値が変更されたとき

また、redux内部では変更判定をシャロー比較(===)しているため、配列処理等(Array.filter、 Array.concat)でデータを再生成している際は注意が必要です。

参考

redux公式
react-redux公式
githubサンプル

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

【JavaScript】Mapの値を配列に変換する

はじめに

Mapで格納した値のvaluesのみを抽出して配列に変換したい時に使えるTipsです。

一般的な方法

index.js
const sampleMap = new Map();
sampleMap.set(1, 'a');
sampleMap.set(2, 'b');
const values = Array.from(sampleMap.values());
// ["a", "b"]

スプレッド演算子を用いた方法

index.js
const sampleMap = new Map();
sampleMap.set(1, 'a');
sampleMap.set(2, 'b');
const values =[...sampleMap.values()];
// ["a", "b"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む