20201126のJavaScriptに関する記事は29件です。

JavaScript における配列コピー

JavaScript における配列コピーの問題

JavaScript における配列( array )はオブジェクトのため参照コピーされる。
つまり、コピー先の配列を変更すると、コピー元の配列が変更されてしまうといった事象が発生する。
例えば次のようになる。

array = [1,2,3];
arrayCopy = array;
arrayCopy[0] = 4;
console.log(arrayCopy); // [4,2,3]
console.log(array); // [4,2,3] : コピー元の値も変更される

JavaScript における配列コピーの解決策

2020年11月現在ではいくつかの方法があるので代表的なものを紹介する。

slice()

速度が早い、と言われている。

array = [1,2,3];
arrayCopy = array.slice();

concat()

slice() 同様、速度が早い、と言われている。

array = [1,2,3];
arrayCopy = array.concat();

スプレッド構文( ES2015 )

最も直感的な記述だと思われる。

array = [1,2,3];
arrayCopy = [...array];

その他

for() や while() 、map() 、filter() 、reduce()、from() による方法あり。

参考

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

【Nuxt.js】Firebaseとの連携方法

firebaseパッケージのインストール

ターミナル
プロジェクト名$ npm install --save firebase

firebase pluginの作成

ターミナル
プロジェクト名$ touch plugins/firebase.js
plugins/firebase.js
import firebase from 'firebase/app'
import 'firebase/firestore'

if (!firebase.apps.length) {
  firebase.initializeApp(
    {
      apiKey: process.env.FIREBASE_API_KEY,
      authDomain: process.env.FIREBASE_AUTH_DOMAIN,
      databaseURL: process.env.FIREBASE_DATABASE_URL,
      projectId: process.env.FIREBASE_PROJECT_ID,
      storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
      messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
      appId: process.env.FIREBASE_APP_ID,
      measurementId: process.env.FIREBASE_MEASUREMENT_ID
    }
  )
}

export default ({ app }, inject) => {
  inject('firebase', firebase);
}

環境変数の設定方法はこちら↓
【Nuxt.js】「dotenv」を使った環境変数の設定方法

nuxt.config.jsの設定

nuxt.config.js
//...省略

  // Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
  plugins: [
    '~/plugins/firebase'
  ],

//...省略

動作確認

pages/index.vue内に送信ボタンを設け、
クリックでfirestoreにデータを送る。

pages/index.vue
<template>
  <div class="container">
    <button @click="submit">送信</button>
  </div>
</template>

<script>
export default {
  methods: {
    submit() {
      const db = this.$firebase.firestore();
      db.collection('user').add({ name: 'hoge' })
        .then(() => {
          console.log('成功');
        })
        .catch((e) => {
          console.log(e.message);
        });
    }
  }
}
</script>

スクリーンショット 2020-11-26 23.10.54.png

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

algolia(instantsearch.js)でイニシャル検索ってどうやるの??

クリスマスまであと13日!!ヽ(=´▽`=)ノ
本日は再び大野が担当致しますm(_ _)m
(1日目の記事も担当しておりました!)

algolia(instantsearch.js)でページ表示タイミングで検索ってどうやるの??

はい、コレは純粋に私が直面した問題でした。
algoliaはとても便利なのですが如何せん日本語の情報が少ない気がしますね・・・

結論から言うととても簡単です。

instantsearch.widgets.configure({
  filters: filters,
}),

これを使えばいけます!!b(・∀・)

具体的なコードは以下

// お決まりの初期化
const search = instantsearch({
  indexName: searchIndex,
  searchClient: algoliasearch('keystring', 'secretstring'),
  routing: true,
});

// 各種検索設定
search.addWidgets([
  // 検索ボックスの配置
  instantsearch.widgets.searchBox({
    container: '#search',
    placeholder: 'フリーワード...',
    showReset: true,
    showLoadingIndicator: true,
  }),

  // 初期検索条件をセット!
  instantsearch.widgets.configure({
    filters: ['available: true AND (NOT uid: null)'],
  }),

  // 検索結果表示設定
  instantsearch.widgets.hits({
    ・・・

こんな感じで初期検索条件を指定出来るようになっています。
(エアコーディングですが有効フラグtrueでuidがnullじゃないデータと言う検索条件を付与しています。)

初期条件の書き方はコチラを参考にしています。

https://www.algolia.com/doc/api-reference/api-parameters/filters/

コレだと初期検索条件がずっと検索条件に付いちゃうんだけど・・・

はい、その通りです。
完全に初回の検索条件を指定したいだけなんだけど・・・と言う場合はsearchFunctionと言うのを初期化時に指定する事で可能です。

// お決まりの初期化
var initialsearch = false;
const search = instantsearch({
  indexName: searchIndex,
  searchClient: algoliasearch('keystring', 'secretstring'),
  routing: true,
  // 検索前処理の追加
  searchFunction: function(helper) {
    if (false === initialsearch) {
      initialsearch = true;
      // 最初の検索条件を指定
      helper.setQuery(helper.state.query + '&available=true').search();  
    }
  }
});

これで初期化時の最初の検索の時だけ検索条件を変えるなどといった事が出来ます。

https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/#widget-param-searchfunction

参考になりましたら幸いですm(_ _)m

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

Riot.js V4系であえてロケーションハッシュ(#xxx)でルーティングをする

今年もアドベントカレンダーの季節がやってきました!!!ヽ(=´▽`=)ノ
弊社デジクエのカレンダー初日を担当させて頂きます、リードエンジニアの大野です。よろしくお願いしますm(_ _)m

Riot.js V4系であえてロケーションハッシュ(#xxx)でルーティングをする

弊社では私の好みも多分に含まれますがRiot.jsを使ったSPAを作ることが多いです。
最近ではSSR等も流行なようなのでSSRで昔ながらの 「/(スラッシュ)」によるディレクティブなルーティングをするSPAが増えていますね。

Riot4系のルーターも標準では「/(スラッシュ)」によるルーティングになっているようです。
しかしそんな中ではありますが、あえてディレクティブなものでなく「#(ハッシュ)」を使ったルーティングを設定して見たので、「#(ハッシュ)」派の人は是非ご参考下さい!m(_ _)m

※管理ツールなどで特にSSRを必要しない要件で、さっくりSPAしたい派の方特に必見です!

STEP1. Roit4系のルーターの読み込み

標準?で提供されているコチラをまずルーターとして使います。
https://github.com/riot/route

<!DOCTYPE html>
<html>
  <head lang="ja">
    <meta charset="UTF-8">
    <title>Riotルーターサンプル</title>
  </head>
  <body>
    <h1>Riot4ルーターサンプル</h1>
  </body>
    <!-- riot4本体 -->
    <script src="//unpkg.com/riot@4/riot+compiler.min.js"></script>
    <!-- Riot4用のルーター -->
    <script src="//unpkg.com/@riotjs/route@4.0.0-beta.1/route.js"></script>
</html>

シンプルにベースはこんな感じです。

STEP2. シンプルなHello worldサンプル

先ずは、シンプルに2ページを行き来するサンプルを用意

<!DOCTYPE html>
<html>
  <head lang="ja">
    <meta charset="UTF-8">
    <title>Riotルーターサンプル</title>
  </head>
  <body>
    <h1>Riot4ルーターサンプル</h1>
    <ul>
      <li><a href="./">トップ</a></li>
      <li><a href="./sample">サンプル</a></li>
    </ul>
    <app></app>
  </body>
  <!-- riot4本体 -->
  <script src="//unpkg.com/riot@4/riot+compiler.min.js"></script>
  <!-- Riot4用のルーター -->
  <script src="//unpkg.com/@riotjs/route@7.0.0/route.js"></script>

  <!-- appタグをインライン定義 -->
  <template id="app">
    <app>
      <router>
        <route path="{window.location.pathname}">
          <h2>Hello world!</h2>
        </route>
        <route path="{window.location.pathname}sample">
          <h2>サンプル!</h2>
        </route>
      </router>
    </app>
  </template>

  <script>
    // appタグを読み込み
    riot.inject(riot.compileFromString(document.getElementById('app').innerHTML).code, 'app', './app.html');
    // riotをコンパイル
    riot.compile().then(() => {
      riot.register('route', route.Route);
      riot.register('router', route.Router);
      // ルートディレクトリ外でも動くようにベースパスを設定
      route.setBase(`${window.protocol}//${window.host}${window.location.pathname}`);
      riot.mount('app');
    });
  </script>
</html>

ブラウザで実行したサンプルは以下
スクリーンショット 2020-11-26 17.20.32.png
http://plnkr.co/plunk/IogODWhBcSlmVAzw

ルーターを入れた基本はこんな感じになってます。
サンプルを実際にローカルで実行してみるとわかると思いますが、デフォルトではルーターは「/(スラッシュ)」によるディレクティブなルーターになっていると思います。
本来はこれで十分に満足出来るんじゃないでしょうか。

これを「#(ハッシュ)」ベースのルーターに変えるんですが、実は凄く簡単です。

STEP3. 「#(ハッシュ)」ベースのルーターに変えたサンプル

実はものすごく簡単で

route.setBase(`${window.protocol}//${window.host}${window.location.pathname}`);

の行を#を含めた形に変えて上げて

route.setBase(`${window.protocol}//${window.host}${window.location.pathname}#`);

後はリンクとpathを#式に変えてあげるだけでOKです。

実際の書き換えた後のサンプルは以下

<!DOCTYPE html>
<html>
  <head lang="ja">
    <meta charset="UTF-8">
    <title>Riotルーターサンプル</title>
  </head>
  <body>
    <h1>Riot4ルーターサンプル</h1>
    <ul>
      <li><a href="#">トップ</a></li>
      <li><a href="#sample">サンプル</a></li>
    </ul>
    <app></app>
  </body>
  <!-- riot4本体 -->
  <script src="//unpkg.com/riot@4/riot+compiler.min.js"></script>
  <!-- Riot4用のルーター -->
  <script src="//unpkg.com/@riotjs/route@7.0.0/route.js"></script>

  <!-- appタグをインライン定義 -->
  <template id="app">
    <app>
      <router>
        <route path="{window.location.pathname}">
          <h2>Hello world!</h2>
        </route>
        <route path="{window.location.pathname}#sample">
          <h2>サンプル!</h2>
        </route>
      </router>
    </app>
  </template>

  <script>
    // appタグを読み込み
    riot.inject(riot.compileFromString(document.getElementById('app').innerHTML).code, 'app', './app.html');
    // riotをコンパイル
    riot.compile().then(() => {
      riot.register('route', route.Route);
      riot.register('router', route.Router);
      // ルートディレクトリ外でも動くようにベースパスを設定
      route.setBase(`${window.protocol}//${window.host}${window.location.pathname}#`);
      riot.mount('app');
    });
  </script>
</html>

ブラウザでの実行結果は以下
スクリーンショット 2020-11-26 19.03.54.png
http://plnkr.co/plunk/veYITpKjBuVEc91A

こんな感じで非常に簡単に「#(ハッシュ)」ベースに変えられました!

まとめ

「#(ハッシュ)」ベースのルーターの場合のいいところはSSRと逆行しますがサーバーサイドの設定などは特に不要になる点かなと思います。
管理ツールだったり、非公開のアプリケーションであったりの場合は特にSSRに絶対にしないとならないシチュエーションでは無く、サックリSPAが作れればいい!と言うことも多々あると思ってます。
(特にRiotはブラウザコンパイル出来てとてもサクッとSPA出来るので尚更)
そう言う時のTIPSとして是非ご参考下さいm(_ _)m

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

Google Chrome 開発ツールのwaring:'DevTools failed to load SourceMap' を非表示にする

Google chromeの開発ツールのコンソール画面でこのようなワーニング出た事ありませんか?

スクリーンショット 2020-11-26 18.39.42.png

javascriptファイルをコンパイルして生成する時に、mapファイルというのも生成されるみたいですが、そのファイルがないとwarningが出るみたいです。

デバッグ時に邪魔なので、非表示にする方法を調べました。

非表示の仕方

  1. f12で開発ツール起動
  2. 歯車マーククリック
  3. Sourcesの「Enable Javascript source map」のチェックを外す スクリーンショット 2020-11-26 18.52.02.png

これだけで、javascriptのmapファイルがない事によるwarningはなくなります。

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

javascriptのforEachを利用する際の注意点

javascriptのforEachについて

色々な言語には、for、foreachが用意されています。
javascriptで言うと、foreachはforEach()に当たるっぽいですね。

最近までちょっと勘違いしていたことがあります。
その気付きを共有したいと思います。

他言語とjavascriptのforEachはそもそも考え方が違う。

例えばPHPで言うと、foreachは、配列、オブジェクトで利用できるfor文ですね。
利用できる型が決められていますが、動作は大体forと一緒です。

javascriptの.forEach()も同じだと思ってました。
ですが、そもそもforEach()は根本的な考え方が違うことに気がつきました。

javascriptのforEach()は「繰り返し」の命令ではない!

使い方自体は、他言語と似ていますよね。

forEach.js
const array = ['a','b','c'];

array.forEach(element => console.log(element));

じゃあこれって何が違うの?
もう少し分かり易く書いてみましょう。

forEach2.js
const array = ['a','b','c'];

array.forEach(function(element){
  console.log(element);
});

上記は書き方を変えただけです。
1番重要な点は、これがfunction(即時関数)であると言う点です。
javascriptのforEach()は、配列の順番通り即時関数を叩くと言う命令である。と言うことです。

ふーん。だからどうなの?

ここで重要になってくるのは、即時関数は、javascriptの言語特性上、平行で実行されると言う点です。
for命令は、配列のインデックスの0番目から順番に実行されます。
0番目の処理が完了した後に、1番目の処理を始めます。

一方、forEachで叩かれた即時関数は、即時関数が叩かれるタイミングこそ0番目からですが、0番目が終わった後に1番目を実行。と言う動作にはなりません。

平行処理が行われますので、1番目に叩かれた配列index0が一番最初に終わるとは限らないのです。
ここが最も重要な点です。

forと動作は同じだと思って、forEachを使うと、想定していた結果にならないと言うのは、大体この特性のせいであると、最近気づきました。

javascriptの言語特性と、即時関数である。と言うことに気がつけばすぐたどり着ける内容ではありますが、以前かなりはまった部分なので、私と同じ悩みを抱えている方がいれば、参考にしていただけたらと思います。

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

Web Workerを実装してみる

Web Workerって?

Web Worker とは、ウェブアプリケーションにおけるスクリプトの処理をメインとは別のスレッドに移し、バックグラウンドでの実行を可能にする仕組みのことです。時間のかかる処理を別のスレッドに移すことが出来るため、 UI を担当するメインスレッドの処理を中断・遅延させずに実行できるという利点があります。

by MDN

JSはシングルスレッドですので、本当の意味での並列処理が行えません。
なのでめちゃ重な処理を書いてしまうと、その他のボタンやら何やら効かなくなって、サイトが完全に固まってしまいます。
これを解消してくれるのがWeb Workerです。
昨今ではSPAなどによりでクライアント側で重たい処理をすることが増えていますので、ぜひ使いこなしておきたいと思っています。

現在ブラウザ側の対応はどうなってる?

スクリーンショット 2020-11-26 16.51.36.png
by Can I use
Opera以外はほとんどOKですね。

とにかくコード

index.js
const worker = new Worker('worker.js');

worker.addEventListener('message', (e) => {
  console.log('Workerから受け取ったデータは: ', e.data);
}, false);

worker.postMessage('Hello, world');
worker.js
self.addEventListener('message',(e) => {
  self.postMessage(e.data);
}, false);

ちなみに、workerのスクリプト内のグローバルはワーカー自身なので、下記のように書いてもOKです。

worker.js
addEventListener('message',(e) => {
  postMessage(e.data);
}, false);
結果
Hello, world

図でみるとわかりやすいです。

worker.png

それぞれのjsの onmesssage で、受信時/送信時のイベントを記述することができます。
受信データ/送信データはいずれも messsageイベントのdata属性から取得できます。
思ったよりも非常にシンプルです。

workerを終了したい場合。

worker.terminate();

terminateメソッドを使って直ちに終了することができます。

エラーハンドリング

Worker で実行時エラーが発生すると、 onerror イベントハンドラーが呼び出されます。これは、 ErrorEvent インターフェイスを実装している error イベントを受け取ります。

とのことです。(by MDN)
早速試してみましょう。

index.js
const worker = new Worker('worker.js');

worker.addEventListener('error', function (e) {
  console.log(e)
}, false);
worker.postMessage('Hello World');

worker.js
self.addEventListener('message', (e) => {

  throw new Error('何かしらのエラー')
  self.postMessage(e.data)
}, false)

結果:
スクリーンショット 2020-11-26 17.47.02.png

確かに ErrorEvent をキャッチしています。

たくさんのプロパティがありますが、下記の3つが重要な項目です。

message ・・・ 人間用メッセージ
filename ・・・ エラー発生元のファイル名
lineno・・・エラーが発生した行番号

スクリプト/ライブラリのimport

Workerスレッド側で何かインポートしたい場合は importScripts() を使います。
ここで引数として指定したスクリプトが実行されます。

importScripts('test.js')
importScripts('foo.js', 'bar.js')

別ファイルでなく埋め込みでWorker

ファイルの記述を文字列化しBlobを生成、そしてそのURLを渡せばOK。
実際の実装ではあまり使わなそうだと思ったので例は割愛します。

注意点

Workerは直接DOMを操作できない点、そしてwindowのメソッドやプロパティは使用できない点に注意です。(worker自身がグローバルのため)
逆にそれ以外ならほとんど使用可能です。
MDNに使用できるクラス・関数がまとめられています。
https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers

また、Workerとやり取りする際のデータは共有しているのではなく、それぞれでコピーされています。
なので渡したデータをWorker側で操作しても、コピー元には関与できない点に注意です。

次回はService Workerについて学習予定です。

参考資料

https://www.html5rocks.com/ja/tutorials/workers/basics/
https://www.codit.work/notes/ytq5ztvnhceco3jn2bkb/

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

jQuery学習#01

jQueryの基本

・jQueryまたは$でアクセス。
・基本的な使い方は、要素を選択し、選択した要素群に対して操作を行うというもの。
↓基本的な書き方
$('セレクタ').メソッド('パラメータ[引数]');

#1. マウスクリックで処理を実行

・まず下記のようにHTMLを書く。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>jQuery テスト</title>
  <style>
    .parent {
      height: 100px;
    }
  </style>
</head>
<body>
  <div class="parent">
    <button type="button" class="my-button">ボタン</button>
  </div>
  <div class="parent">
    <button type="button" class="my-button">ボタン</button>
  </div>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  <script src="main.js"></script>
</body>
</html>

と、下のようにボタンが2つ配置されたページが出来上がる。この段階では、ボタンを押しても何も起こらない。

・次にJavaScriptのファイルを、jQueryを使って下記のように書く。

$('.my-button').on('click', (e) => {
  $(e.target)
  .parent()
  .css({ 'background-color': '#ff6666'});
});

説明。
まず、セレクタにclass="my-button"を指定。onメソッドを使用。
<button type="button" class="my-button">
がクリックされたとき、下記関数を呼び出す。

(e) => {
  $(e.target)
  .parent()
  .css({ 'background-color': '#ff6666'});
}

↑関数の説明の前に、まずparent()メソッドe.targetを簡単に説明。
parent()メソッド
指定した要素の一階層上の親要素を取得することができるメソッド。

e.target(eventオブジェクトのtargetプロパティ)
イベントの発生元であるオブジェクトを返す。

というわけで、↑関数は、<button type="button" class="my-button">の一階層上の親要素である<div class="parent">のbackground-colorに#ff6666を指定するというもの。

よって、ボタンをクリックすると、下記のようになる。

#2. マウスオーバーに反応する

HTML: <div class="box"></div>
CSS:

.box {
  width: 200px;
  height: 200px;
  background-color: #ddd;
}

boxという名前のclass属性を持つdiv要素に下記のような装飾を施す。

JavaScript:

const onMouseenter = (e) => {
  $(e.target).css({
    'background-color': '#ff9999',
  });
};
const onMouseleave = (e) => {
  $(e.target).css({
    'background-color': '#dddddd',
  });
};

$('.box')
  .on('mouseenter', onMouseenter)
  .on('mouseleave', onMouseleave);

<div class="box">にマウスが乗ったとき(mouseenter)、下記のように背景色が変わる。

マウスが離れたとき、指定した(#dddddd)に変化する。つまり下の灰色に戻る。

#3. キーボードの入力に反応させる

HTML:

<input type="text" class="my-input">
<div class="box"></div>

JavaScript:

$('.my-input').on('input', (e) => {
  const value = $(e.target).val();
  $('.box').text(value);
});

inputイベント
input要素やtextarea要素へ入力するたびに発生する。

val()メソッド
HTMLタグ内に記述されているvalue属性を取得したり変更することができるメソッド。

text()メソッド
HTML要素内にあるテキスト情報を取得・追加・変更することが可能なメソッド。

説明。
<input type="text" class="my-input">に入力するたびに下記関数を呼び出す。

(e) => {
  const value = $(e.target).val();
  $('.box').text(value);
}

↑定数valuee.target、つまり<input type="text" class="my-input">のvalue属性を取得したものを代入する。
<div class="box">に上で取得したvalue属性をテキスト情報として追加する。

input内にテキストすると、下記のようになる。

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

[Node.js][Deno] オブジェクト合成各種 ベンチマーク比較

先ほどの クラスのベンチマーク の続編。
オブジェクト合成でより良い(というか速い)手法ないかなと悩みつつ。

実験環境

同じく

  • Node.js 14.15.1
  • Deno 1.5.4

実験設定

  • 10要素で構成されるオブジェクト2組を1つのオブジェクトにまとめる
  • 要素のうち5つは名前が重複しており、合成元を合成先に上書きする
  • 合成先はプロトタイプではなく、独立したオブジェクトとして新規に生成される

という前提で

  • 合成先オブジェクトを生成するファンクションと、合成元オブジェクトを用意
  • 実行時間計測
    • 合成先オブジェクト生成ファンクションだけの結果
    • 合成先オブジェクト生成ファンクション+各種アルゴリズムの結果
  • 結果表示
    • 各種アルゴリズムの結果部分を抽出表示

という手順。

オブジェクト合成アルゴリズム ここに集う

今回調査するアルゴリズムを御紹介いたしましょう。

assign
Object.assign()
spread
スプレッド構文
for
for inループ
forEach
keys forEachコールバック
map
keys mapコールバック
entries
entries列挙ループ
lowtech1
(対照用) オブジェクト要素として個別に代入
lowtech2
(対照用) 連想配列要素として個別に代入

多少のオーバーヘッドは仕方ないにしても、できるだけ対照用で挙げた方法に
近い(というか速い)アルゴリズムを採用したいという趣旨。

いざ、実験

今回は記述がコンパクトなので、1ソースでまとめていけます

bench_obj_merge.js
function CreateParent(){return {a:0,b:1,c:2,d:3,e:4,f:5,g:6,h:7,i:8,j:9};}
var sub={f:10,g:11,h:12,i:13,j:14,k:15,l:16,m:17,n:18,o:19};

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    create:()=>CreateParent(),
    assign:()=>Object.assign(CreateParent(),sub),
    spread:()=>{return {...CreateParent(),...sub};},
    for:()=>{
        var t=CreateParent();
        for(var k in sub)t[k]=sub[k];
        return t;
    },
    forEach:()=>{
        var t=CreateParent();
        Object.keys(sub).forEach(k=>t[k]=sub[k]);
        return t;
    },
    map:()=>{
        var t=CreateParent();
        Object.keys(sub).map(k=>t[k]=sub[k]);
        return t;
    },
    entries:()=>{
        var t=CreateParent();
        for(var [k,v] of Object.entries(sub))t[k]=v;
        return t;
    },
    lowtech1:()=>{
        var t=CreateParent();
        t.f=sub.f;
        t.g=sub.g;
        t.h=sub.h;
        t.i=sub.i;
        t.j=sub.j;
        t.k=sub.k;
        t.l=sub.l;
        t.m=sub.m;
        t.n=sub.n;
        t.o=sub.o;
        return t;
    },
    lowtech2:()=>{
        var t=CreateParent();
        t['f']=sub['f'];
        t['g']=sub['g'];
        t['h']=sub['h'];
        t['i']=sub['i'];
        t['j']=sub['j'];
        t['k']=sub['k'];
        t['l']=sub['l'];
        t['m']=sub['m'];
        t['n']=sub['n'];
        t['o']=sub['o'];
        return t;
    },
};
// 計測結果を書き込むところ 
var rec={};

// 動作テスト 
//for(var k in funx)console.log([k,funx[k]()]);

// 繰り返し実行時間計測 
var loop=Array(1000000);
for(var k in funx){
    var f=funx[k];
    var bgn=new Date;
    for(var i of loop)f();
    var end=new Date;
    rec[k]=(end-bgn);
}

// 結果表示 
for(var k in rec)console.log(k+' :'+(rec[k]-rec.create));

ここで予想外の事態。当初は 前回 と同じ1千万ループだったのですが、なかなか処理終わらなかったので一旦止めて桁減らしちゃいました。
それでループ回数が1桁減っているので、前回の結果と比べるときは御注意ください。

Node.js Deno
assign 255.7 265.4
spread 6906.3 6503.3
for 289.2 484.4
forEach 413.6 463.1
map 445.5 505.7
entries 417.3 428.4
lowtech1 10.1 8
lowtech2 10.3 8

まず目を疑ったのが、今(一部で)流行りのスプレッド構文、なんと1桁遅い。
記述が一番シンプルなだけに残念なところ。
で、とりあえず Object.assign() がベストというか幾分マシということで。
もっと素敵な手法ないものかしらん。

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

JavaScriptのデータ型について

JavaScriptで扱うデータの種類について

JavaScriptでは8種類のデータ型が定義されています。
よく出てくるものの例として以下の物が挙げられます。

  • Number(数値)
    • 3, 5, 18, 0 etc...
  • String(文字列)
    • 'Hello', 'Good' etc...
  • Boolean(真偽値)
    • true, false
  • Object(オブジェクト)

またJavaScriptにはデータ型の種類を調べるtypeof演算子というものが用意されています。

console.log(typeof 5);
console.log(typeof 'Hellow');
console.log(typeof true);

出力結果

number
string
boolean

まとめ

まだ初学者のため使い所がいまいちピンと来ていませんが(そういう人僕の他にもいますか?)いつかあ〜typeof演算子ね〜ウンチクウンチクくらいになれたらいいな、、、、

参考

MDN web docs

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

Vue-CLI + Vuexでポケモンのパーティを構築するSPAをつくる。

概要

ポケモンのパーティを構築して遊べるSPAです。
VueでSPAを作りたいなーと思って作り始めた。
加えて、listの高速なフィルター機能も使いたい。
実装はGitHubPagesを使ってフロントエンドのみ。

GitHub pokemon-zukan

readme.jpg

技術・ライブラリ

  • Vue-CLI
  • Vuex
  • lodash
  • moji
  • axios
  • ityped
  • babel-polyfill
  • reset-css
  • progressbar.js

つくったもの

パーティをつくる

ポケモンずかんからポケモンをピックアップし、パーティを構築する。

ずかん

パーティはVuexのstoreで管理し、てもち画面で確認できる。

てもち

ポケモンの詳細をみる

vue-routerを利用して詳細画面を生成。
ポケモンのステータスを確認できる。

しょうさい

詳細設計

ポケモンのステータス、画像

axiosで外部APIから取得。

フォント

pokemon-font

検索機能

computed()でフィルター、ソートを実現。
早くてかっこいい。(全データを取得してるだけだから数が大きくなると問題だけど。)

ソート

  • なまえ
  • ばんごう
フィルター
  • なまえ検索
  • タイプ

どれもjsで操作しているだけなのでレスポンスが早い。
さらになまえ検索は日本語入力未決定前でも検索できるようにしている。早い。

vue
<input @input="handleInput"></input>
js
    handleInput(event) {
      this.wd = event.target.value //未決定の値を取得
    },

タイプフィルターはチェックボックスから選ぶ。

フィルター

おわり

細かい実装やその方針などは省略していますが、素人ながらかなり手こずりました。
良い勉強になりました。

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

Material-UIを使ってダイアログを表示するには?

Material-UIを使ってダイアログを表示するには一筋縄ではいかずパラメータを渡す必要があります。
なので今回は「Material-UIを使ってダイアログを表示する方法」を紹介します。

ダイアログ表示.gif

この機能は3つのファイルで構成されています。

  1. 一覧画面(index.jsx)
  2. アイテムコンポーネント(item.jsx)
  3. ダイアログコンポーネント(dialog.jsx)

1.一覧画面

WS000000.JPG

index.jsx
import React from 'react';
import Box from '@material-ui/core/Box';
import Item from "@comp/item";
import Grid from '@material-ui/core/Grid';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles((theme) => ({
  gridItem: {
    marginTop: theme.spacing(2),
  },
}));

// 表示するアイテムのリスト
const bookList = [
  {
    id: 1,
    title: "ファスト&スロー",
    page: 100,
    image: "http://books.google.com/books/content?id=tNDza_Pb0UMC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api"
  },
  {
    id: 2,
    title: "ファストフードの恐ろしい話",
    page: 200,
    image: "http://books.google.com/books/content?id=ghgzDwAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api"
  },
  {
    id: 3,
    title: "英語でおもてなし・ファストフード食べ歩き",
    page: 300,
    image: "http://books.google.com/books/content?id=HC-gCAAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api"
  },
]

export default function Index(props) {
  const classes = useStyles();
  const compProps = {
    gridItem: {
      item         : true,
      space        : 5,
      xs           : 12,
      md           : 6,
      lg           : 3,
      className    : classes.gridItem
    }
  }

  return (
    <>
      <Box mx={10} mt={4}>
        <Box display="flex" alignItems="center">
          <h2>{props.title}</h2>
        </Box>
        <Grid container spacing={1}>
          {bookList.map(item =>
            <Grid {...compProps.gridItem} key={item.id}>
              <Item 
                bookParam={item}
                />
            </Grid>
          )}
        </Grid>
      </Box>
    </>
  );
}


一覧画面(index.jsx)では定義したリストを順番に並べて表示しています。

まずは、表示するリストのデータを定義しています。通常はAPIやDBから取得したりしますが、今回は見本なので直接定義しています。

そして、定義したリストデータをループで回して各アイテムをBookCardというコンポーネントで表示しています。各データはbookParamという名前でBookCardコンポーネントに渡しています。

2.アイテム

WS000001.JPG

item.jsx
import React, {useState} from 'react';
import Dialog  from "@comp/dialog";
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';
import Link from '@material-ui/core/Link';

const item = (props) => {

  /** 画面パラメータ */
  const [dialogOpenF, setDialogOpenF] = useState(false);

  /**
   * コンポーネントに渡す引数
   */
  const compProps = {
    bookShowDialog: {
      open : dialogOpenF,                     //ダイアログの表示プローアティ
      bookParam: props.bookParam,
      onClose: () => setDialogOpenF(false),   //ダイアログ非表示処理
    },
    showLink: {
      href: "#",
      onClick: () => setDialogOpenF(true)     //ダイアログ表示処理
    }
  }

  return (
    <Box>
      {/* 画像の表示 */}
      <Link {...compProps.showLink}>
        <img src={props.bookParam.image}/>
      </Link>

      {/* タイトルの表示 */}
      <Typography>
        <Link {...compProps.showLink}>
        {props.bookParam.title}
        </Link>
      </Typography>

      {/* ページ数の表示 */}
      <Typography>{props.bookParam.page}ページ</Typography>

      {/* ダイアログの表示 */}
      <Dialog {...compProps.bookShowDialog} />
    </Box>
  );
}

export default item;

各アイテム(item.jsx)では呼び出し元から渡されたデータの表示とダイアログの表示・非表示の処理を行っています

引き渡されたパラメータはprops.bookParamに格納されているため画像URL(image)、タイトル(title)、ページ数(page)をそのまま出力に利用しています。

そして、最後のDialogコンポーネントに各アイテムの情報を渡しています。

2-1.ダイアログの表示・非表示

ダイアログ表示・非表示用のパラメータdialogOpenFを用意しています。このパラメータをtrue・falseに更新することでダイアログの表示・非表示に切り替えているわけです。

後は画像とタイトルがクリックされたときにdialogOpenFを更新するメソッドsetDialogOpenFを使ってtrueに変更しています。これによりopenプロパティーが更新されダイアログが表示されます。

そして、ダイアログが閉じる処理を行ったときにsetDialogOpenFでfalseを指定してダイアログを非表示に更新しています。

3.ダイアログ

WS000002.JPG

dialog.jsx
import React from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';

const dialog = (props) => {

  const compProps = {
    dialog: {
      open: props.open,           //ダイアログの表示・非表示プロパティ
      onClose: props.onClose,     //ダイアログが閉じられた時の処理
      scroll:'paper',
    },
    dialogContent: {
      dividers: true
    },
    dialogContentText: {
      tabIndex: -1
    },
    closeButton: {
      onClick: props.onClose,     //閉じるボタンが押されたときの処理
      color: "primary"
    },
  }

  return (
      <Dialog {...compProps.dialog}>
        {/* タイトル */}
        <DialogTitle>{props.bookParam.title}</DialogTitle>

        {/* 画像とページ数 */}
        <DialogContent {...compProps.dialogContent}>
          <img src={props.bookParam.image} />
          <DialogContentText {...compProps.dialogContentText}>
            <Typography>{props.bookParam.page}ページ</Typography>
          </DialogContentText>
        </DialogContent>

        {/* 閉じるボタン */}
        <DialogActions>
          <Button {...compProps.closeButton}>
            閉じる
          </Button>
        </DialogActions>
      </Dialog>
  );
}

dialog.propTypes = {
    open: PropTypes.bool,       //表示フラグ
    onClose: PropTypes.func,    //閉じる処理
  }

  export default dialog;

最後にダイアログに呼び出し元から渡された情報を出力します。

DialogTitleにはアイテムのタイトル。DialogContentにはアイテムの画像とページ数を表示。そして、ダイアログを閉じる処理を行う「閉じるボタン」を定義します。

閉じるボタンが押されたときは呼び出し元から引き渡されたprops.onCloseを利用します。これにより、呼び出し元からダイアログを非表示にします。

また、propTypesで呼び出し元からのパラメータの型をチェックしまています。openはダイアログの表示・非表示でboolean型。onCloseは閉じる処理でfunc型。これにより異常な値が渡ってこれば警告が表示されるようになっているのです。

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

【Nuxt.js】Vuex基礎編②stateを複数使ってみよう

? この記事はWP専用です
https://wp.me/pc9NHC-en

前置き

今回は前回の基礎編に続き、
stateが複数ある場合の書き方です✍️
基礎編でVuexの基本的な解説はしています!
また基礎編のコードに追記するので
そちらを確認しながらやってみてください?

Vuex基礎編はこちら
https://note.com/aliz/n/n497914c981c8

やりたいこと

picture_pc_f55edca4f826019fede322e05e15c0ba.gif

基礎編のカウンターを2つに増やします!
これだけ!!!

NGパターン

picture_pc_18c865e53858c3abb3d3d4560c782d23.gif

まずはNGパターンから。
まずはstateにsubCounterを追加。
mutationsなどにも同様に
subCounterについて追記します✍️

が!
これだと後述したsubCounterに
全てがまとまってしまいます。。。

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-en

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

nextjs-auth0をdocker上でも使いたい

nextjsでも簡単にauth0を使いたい!
と思ってサンプルに従うと、Dockerビルドってかproduction buildするとエラーで落ちちゃいます。

configのサンプル
auth0/nextjs-auth0: Next.js SDK for signing in with Auth0 (Experimental) - https://github.com/auth0/nextjs-auth0#runtime-configuration

import { initAuth0 } from '@auth0/nextjs-auth0';
import config from './config';

export default initAuth0({
  domain: process.env.AUTHO_DOMAIN,
  clientId: process.env.AUTHO_DOMAIN,
  clientSecret: process.env.APP_SECRRET,
  scope: 'openid profile',

エラー内容こんな感じ。process.env.XXXが見れないから落ちてるのかな。

Automatically optimizing pages...
> Build error occurred
Error: A valid Auth0 Domain must be provided
    at Object.createInstance [as default] (/usr/src/app/node_modules/@auth0/nextjs-auth0/dist/instance.node.js:10:15)
    at initAuth0 (/usr/src/app/node_modules/@auth0/nextjs-auth0/dist/index.js:8:46)
    at Object.xMDF (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:781:127)
    at __webpack_require__ (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:23:31)
    at Module.1TCz (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:147:13)
    at __webpack_require__ (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:23:31)
    at Object.0 (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:99:18)
    at __webpack_require__ (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:23:31)
    at /usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:91:18
    at Object.<anonymous> (/usr/src/app/.next/server/static/GDDYxjIWKG2mlWWc4bTrE/pages/_app.js:94:10)
error Command failed with exit code 1

ボツ案

issue投げて聞いてみたら、 .env を仮データで追加し実行時に再ビルドすると大丈夫らしい。試したらできた。

Can't build on docker. · Issue #86 · auth0/nextjs-auth0 - https://github.com/auth0/nextjs-auth0/issues/86

Dockerfile
FROM node:14 as builder

WORKDIR /app

COPY .env.template .env
COPY package.json yarn.lock ./
RUN yarn install

COPY . .
RUN yarn build

# ------------------
FROM node:14-alpine as release

WORKDIR /app

COPY --from=builder /app/package.json /app/yarn.lock ./
RUN yarn install
COPY --from=builder /app/.next ./.next
COPY . .

EXPOSE 3000
CMD ["yarn", "start:build"]

package.json
  "scripts": {
    "start": "next start",
    "start:build": "yarn build && yarn start",
    "build": "next build",

確かにできるんだけど、CI時にビルド2回ぐらい回しちゃうことになって、めっちゃ時間かかる・・・

改善案

initAuth0のオブジェクトを直接export defaultすると、環境変数がバインドされちゃうので、関数にしてあとからprocess.envを評価すればいいじゃんって案

Document best practices for using nextjs-auth0 with a nextjs production build. · Issue #154 · auth0/nextjs-auth0 - https://github.com/auth0/nextjs-auth0/issues/154

環境変数はgetConfig()経由で受け取り、バインドすると想定通り動いてくれた。やったね!

src/lib/auth0.js
import getConfig from 'next/config';
import { initAuth0 } from '@auth0/nextjs-auth0';

const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();

let auth0 = null;

const proc = () => {
  if (!auth0) {
    auth0 = initAuth0({
      clientId: publicRuntimeConfig.auth0.clientId,
      domain: publicRuntimeConfig.auth0.domain,
      scope: 'openid email profile',
      postLogoutRedirectUri: publicRuntimeConfig.baseUrl,
      redirectUri: `${publicRuntimeConfig.baseUrl}/api/auth/signed-in`,
      clientSecret: serverRuntimeConfig?.auth0?.secret ?? '',
      session: {
        cookieSecret: serverRuntimeConfig?.appSecret ?? '',
        // Set to 8 hours
        cookieLifetime: 60 * 60 * 8,
        storeIdToken: false,
        storeAccessToken: false,
        storeRefreshToken: false,
      },
      oidcClient: {
        // Optionally configure the timeout in milliseconds for HTTP requests to Auth0.
        httpTimeout: 2500,
        // Optionally configure the clock tolerance in milliseconds, if the time on your server is running behind.
        clockTolerance: 10000,
      },
    });
  }

  return auth0;
};

export default proc;
next.config.js
const nextConfig = {
  serverRuntimeConfig: {
    appSecret: process.env.APP_SECRET,
    auth0: {
      secret: process.env.AUTH0_CLIENT_SECRET,
    },
  },
  publicRuntimeConfig: {
    baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
    auth0: {
      clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID,
      domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN,
    },
  },
};

module.exports = nextConfig;
page/auth/api/sign-in.js
diff --git a/pages/api/auth/sign-in.js b/pages/api/auth/sign-in.js
index 7b109a7..d271932 100644
--- a/pages/api/auth/sign-in.js
+++ b/pages/api/auth/sign-in.js
@@ -1,6 +1,8 @@
-import auth0 from '../../../src/lib/auth0';
+import Auth0 from '../../../src/lib/auth0';
 import { checkHeaders } from '../../../src/lib/middleware/auth';

+const auth0 = Auth0();
+
 const login = checkHeaders(async (req, res) => {
   try {
     await auth0.handleLogin(req, res);

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

React-leafletの使い方メモ

React-leafletとは

Open Source Mapを表示するJavaScriptライブラリであるleafletを、React.js上で使えるように拡張するライブラリです。

React Leaflet公式
https://react-leaflet.js.org/

本記事は公式のチュートリアルを順番にやっていくだけですので詳しくは公式サイトを参照してください
https://react-leaflet.js.org/docs/start-introduction

1. プロジェクトの作成

terminal
mkdir react-leaflet
cd react-leaflet
npx create-react-app my-app

2. インストール

今回は開発版をインストールしました

開発版をインストールする場合
npm install leaflet react-leaflet@next --save
安定版をインストールする場合
npm install leaflet react-leaflet --save

3. 地図を表示

こちらのコードを参考にやっていきます
https://qiita.com/sugasaki/items/d225cf548e9a787dbd9c

地図を表示する為のクラスを作成

MapContainerで地図オブジェクトをつくり、TileLayerで地図タイルを取得できるようにすると地図が表示されます。

MapContainerがL.map()、TileLayerがL.tileLayer()、MarkerがL.marker()に対応していますので、leaflet触ったことがある方はイメージしやすいと思います。

my-app/component/simple.js
import React, { Component } from 'react'
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'

export default class SimpleExample extends Component {
  render() {
    const position = [51.505, -0.09];
    return (
      <MapContainer center={position} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <Marker position={position}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
      </MapContainer>
    )
  }
}

App.jsでSimpleExampleクラスを呼ぶように変更

my-app/App.js
import Leaflet from 'leaflet'
import React, { Component } from 'react';
import './App.css';
import 'leaflet/dist/leaflet.css';
import SimpleExample from './components/simple'

Leaflet.Icon.Default.imagePath =
  '//cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.1/images/'

class App extends Component {
  render() {
    return (
      <SimpleExample />
    );
  }
}

export default App;

leafletコンテナの表示幅を指定

指定し忘れると高さゼロ(=表示されない)になってしまうので必ず設定します

my-app/index.js
.leaflet-container {
  width: 600px;
  height: 300px;
  margin: 10px;

サーバーを起動して確認

サーバーを起動します

terminal
npm start

以上でhttp://localhost:3000/ に地図が表示される筈です
image.png

4. イベント管理

useMapEvents()によりleafletのイベントハンドラを使うことができます。以下ではclick()イベントが発生したら端末の現在位置を取得してMarkerを置くという動作を追加しています。

my-app/components/event.js
import React, { Component, useState } from 'react'
import { MapContainer, TileLayer, useMapEvents, Marker, Popup } from 'react-leaflet'


function LocationMarker() {
    const [position, setPosition] = useState(null)
    const map = useMapEvents({
      click() {
        map.locate()
      },
      locationfound(e) {
        setPosition(e.latlng)
        map.flyTo(e.latlng, map.getZoom())
      },
    })

    return position === null ? null : (
      <Marker position={position}>
        <Popup>You are here</Popup>
      </Marker>
    )
  }

export default class EventsExample extends Component {
  render() {
    const position = [51.505, -0.09];
    return (
      <MapContainer center={position} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <LocationMarker />
      </MapContainer>
    )
  }
}
my-app/App.js
import Leaflet from 'leaflet'
import React, { Component } from 'react';
import './App.css';
import 'leaflet/dist/leaflet.css';
import SimpleExample from './components/simple'
import EventsExample from './components/events'

Leaflet.Icon.Default.imagePath =
  '//cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.1/images/'

class App extends Component {
  render() {
    return (
      <div>
        SimpleExample
        <SimpleExample />

        EventsExample
        <EventsExample />
      </div>
    );
  }
}

export default App;

image.png

5. ポリゴンを表示

ポリゴンも簡単に追加できます

my-app/src/components/polygon.js
import React, { Component } from 'react'
import { MapContainer, TileLayer, Popup, Circle, CircleMarker, Polyline, Polygon, Rectangle } from 'react-leaflet'

export default class PolygonExample extends Component {
  render() {
    const center = [51.505, -0.09];

    const polyline = [
      [51.505, -0.09],
      [51.51, -0.1],
      [51.51, -0.12],
    ];

    const multiPolyline = [
      [
        [51.5, -0.1],
        [51.5, -0.12],
        [51.52, -0.12],
      ],
      [
        [51.5, -0.05],
        [51.5, -0.06],
        [51.52, -0.06],
      ],
    ];

    const polygon = [
      [51.515, -0.09],
      [51.52, -0.1],
      [51.52, -0.12],
    ];

    const multiPolygon = [
      [
        [51.51, -0.12],
        [51.51, -0.13],
        [51.53, -0.13],
      ],
      [
        [51.51, -0.05],
        [51.51, -0.07],
        [51.53, -0.07],
      ],
    ];

    const rectangle = [
      [51.49, -0.08],
      [51.5, -0.06],
    ];

    const fillBlueOptions = { fillColor: 'blue' };
    const blackOptions = { color: 'black' };
    const limeOptions = { color: 'lime' };
    const purpleOptions = { color: 'purple' };
    const redOptions = { color: 'red' };

    return (
      <MapContainer center={center} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <Circle center={center} pathOptions={fillBlueOptions} radius={200} />
        <CircleMarker center={[51.51, -0.12]} pathOptions={redOptions} radius={20}>
          <Popup>Popup in CircleMarker</Popup>
        </CircleMarker>
        <Polyline pathOptions={limeOptions} positions={polyline} />
        <Polyline pathOptions={limeOptions} positions={multiPolyline} />
        <Polygon pathOptions={purpleOptions} positions={polygon} />
        <Polygon pathOptions={purpleOptions} positions={multiPolygon} />
        <Rectangle bounds={rectangle} pathOptions={blackOptions} />
      </MapContainer>
    )
  }
}

image.png

6. SVG overlay

SVGを上に重ねることもできます

my-app/src/components/svg.js
import React, { Component } from 'react'
import { MapContainer, TileLayer, SVGOverlay } from 'react-leaflet'

export default class SVGExample extends Component {
  render() {
    const position = [51.505, -0.09];
    const bounds = [
      [51.49, -0.08],
      [51.5, -0.06],
    ];
    return (
      <MapContainer center={position} zoom={13} scrollWheelZoom={false}>
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <SVGOverlay attributes={{ stroke: 'red' }} bounds={bounds}>
        <rect x="0" y="0" width="100%" height="100%" fill="blue" />
        <circle r="5" cx="10" cy="10" fill="red" />
        <text x="50%" y="50%" stroke="white">
          text
        </text>
      </SVGOverlay>
    </MapContainer>
    )
  }
}

image.png

7. LayerGroupとFeatureGroup

leafletにあるLayerGroupとかFeatureGroupも使えます

my-app/src/components/group.js
import React, { Component } from 'react'
import { MapContainer, TileLayer, Popup, LayerGroup, Circle, FeatureGroup, Rectangle } from 'react-leaflet'

export default class LayerGroupExample extends Component {
  render() {
    const center = [51.505, -0.09];
    const rectangle = [
      [51.49, -0.08],
      [51.5, -0.06],
    ];
    const fillBlueOptions = { fillColor: 'blue' };
    const fillRedOptions = { fillColor: 'red' };
    const greenOptions = { color: 'green', fillColor: 'green' };
    const purpleOptions = { color: 'purple' };

    return (
      <MapContainer center={center} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <LayerGroup>
          <Circle center={center} pathOptions={fillBlueOptions} radius={200} />
          <Circle
            center={center}
            pathOptions={fillRedOptions}
            radius={100}
            stroke={false}
          />
          <LayerGroup>
            <Circle
              center={[51.51, -0.08]}
              pathOptions={greenOptions}
              radius={100}
            />
          </LayerGroup>
        </LayerGroup>
        <FeatureGroup pathOptions={purpleOptions}>
          <Popup>Popup in FeatureGroup</Popup>
          <Circle center={[51.51, -0.06]} radius={200} />
          <Rectangle bounds={rectangle} />
        </FeatureGroup>
      </MapContainer>
    )
  }
}

image.png

8. マウスオーバーで注釈を出す

MarkerやCircleにTooltipを追加しておくと注釈を出すことが出来ます

my-app/src/components/tooltips.js
import React, { Component } from 'react'
import { MapContainer, TileLayer, Marker, Popup, Circle, CircleMarker, Polygon, Rectangle, Tooltip } from 'react-leaflet'
import { useState, useMemo } from 'react'

const center = [51.505, -0.09]

const multiPolygon = [
  [
    [51.51, -0.12],
    [51.51, -0.13],
    [51.53, -0.13],
  ],
  [
    [51.51, -0.05],
    [51.51, -0.07],
    [51.53, -0.07],
  ],
];

const rectangle = [
  [51.49, -0.08],
  [51.5, -0.06],
];

function TooltipCircle() {
  const [clickedCount, setClickedCount] = useState(0)
  const eventHandlers = useMemo(
    () => ({
      click() {
        setClickedCount((count) => count + 1)
      },
    }),
    [],
  )

  const clickedText =
    clickedCount === 0
      ? 'Click this Circle to change the Tooltip text'
      : `Circle click: ${clickedCount}`

  return (
    <Circle
      center={center}
      eventHandlers={eventHandlers}
      pathOptions={{ fillColor: 'blue' }}
      radius={200}>
      <Tooltip>{clickedText}</Tooltip>
    </Circle>
  )
}

export default class ToolTipsExample extends Component {
  render() {
    return (
      <MapContainer center={center} zoom={13} scrollWheelZoom={false}>
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <TooltipCircle />
      <CircleMarker
        center={[51.51, -0.12]}
        pathOptions={{ color: 'red' }}
        radius={20}>
        <Tooltip>Tooltip for CircleMarker</Tooltip>
      </CircleMarker>
      <Marker position={[51.51, -0.09]}>
        <Popup>Popup for Marker</Popup>
        <Tooltip>Tooltip for Marker</Tooltip>
      </Marker>
      <Polygon pathOptions={{ color: 'purple' }} positions={multiPolygon}>
        <Tooltip sticky>sticky Tooltip for Polygon</Tooltip>
      </Polygon>
      <Rectangle bounds={rectangle} pathOptions={{ color: 'black' }}>
        <Tooltip direction="bottom" offset={[0, 20]} opacity={1} permanent>
          permanent Tooltip for Rectangle
        </Tooltip>
      </Rectangle>
      </MapContainer>
    )
  }
}

image.png

9. LayersControl

レイヤーを選んで表示するようにすることもできます

my-app/src/components/leyersControl.js
import React, { Component } from 'react'
import { MapContainer, TileLayer, Marker, Popup, LayersControl, LayerGroup, Circle, FeatureGroup, Rectangle } from 'react-leaflet'

export default class LayersControlExample extends Component {
  render() {
    const center = [51.505, -0.09];
    const rectangle = [
      [51.49, -0.08],
      [51.5, -0.06],
    ];

    return (
      <MapContainer center={center} zoom={13} scrollWheelZoom={false}>
        <LayersControl position="topright">
          <LayersControl.BaseLayer checked name="OpenStreetMap.Mapnik">
            <TileLayer
              attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
              url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            />
          </LayersControl.BaseLayer>
          <LayersControl.BaseLayer name="OpenStreetMap.BlackAndWhite">
            <TileLayer
              attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
              url="https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png"
            />
          </LayersControl.BaseLayer>
          <LayersControl.Overlay name="Marker with popup">
            <Marker position={center}>
              <Popup>
                A pretty CSS3 popup. <br /> Easily customizable.
              </Popup>
            </Marker>
          </LayersControl.Overlay>
          <LayersControl.Overlay checked name="Layer group with circles">
            <LayerGroup>
              <Circle
                center={center}
                pathOptions={{ fillColor: 'blue' }}
                radius={200}
              />
              <Circle
                center={center}
                pathOptions={{ fillColor: 'red' }}
                radius={100}
                stroke={false}
              />
              <LayerGroup>
                <Circle
                  center={[51.51, -0.08]}
                  pathOptions={{ color: 'green', fillColor: 'green' }}
                  radius={100}
                />
              </LayerGroup>
            </LayerGroup>
          </LayersControl.Overlay>
          <LayersControl.Overlay name="Feature group">
            <FeatureGroup pathOptions={{ color: 'purple' }}>
              <Popup>Popup in FeatureGroup</Popup>
              <Circle center={[51.51, -0.06]} radius={200} />
              <Rectangle bounds={rectangle} />
            </FeatureGroup>
          </LayersControl.Overlay>
        </LayersControl>
      </MapContainer>
    )
  }
}

image.png

10. stateの値でRectangleの色を変える

この辺からleaflet単独ではなくReactからleafletを操作する手順になります。ここではEffectフックでRectangleの色を変えています。

my-app/src/components/panes.js
import React, { Component, useState, useRef, useEffect } from 'react'
import { MapContainer, TileLayer, Pane, Rectangle } from 'react-leaflet'

const outer = [
  [50.505, -29.09],
  [52.505, 29.09],
]
const inner = [
  [49.505, -2.09],
  [53.505, 2.09],
]

function BlinkingPane() {
  const [render, setRender] = useState(true)
  const timerRef = useRef()
  useEffect(() => {
    timerRef.current = setInterval(() => {
      setRender((r) => !r)
    }, 1000)
    return () => {
      clearInterval(timerRef.current)
    }
  }, [])

  return render ? (
    <Pane name="cyan-rectangle" style={{ zIndex: 500 }}>
      <Rectangle bounds={outer} pathOptions={{ color: 'cyan' }} />
    </Pane>
  ) : null
}

export default class PanesExample extends Component {
  render() {
    return (
      <MapContainer bounds={outer} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <BlinkingPane />
        <Pane name="yellow-rectangle" style={{ zIndex: 499 }}>
          <Rectangle bounds={inner} pathOptions={{ color: 'yellow' }} />
          <Pane name="purple-rectangle">
            <Rectangle bounds={outer} pathOptions={{ color: 'purple' }} />
          </Pane>
        </Pane>
      </MapContainer>
    )
  }
}

横に長いRectangleが紫⇔青と交互に色を変えながら表示されます
image.png

11. Drag可能なMarker

MarkerをDraggableにするかどうかをReact stateにしておいて、途中でDrag可能にしたりDragできなくしたりする例です

my-app/src/components/draggable.js
import React, { Component, useState, useRef, useMemo, useCallback } from 'react'
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'

const center = {
  lat: 51.505,
  lng: -0.09,
}

function DraggableMarker() {
  const [draggable, setDraggable] = useState(false)
  const [position, setPosition] = useState(center)
  const markerRef = useRef(null)
  const eventHandlers = useMemo(
    () => ({
      dragend() {
        const marker = markerRef.current
        if (marker != null) {
          setPosition(marker.getLatLng())
        }
      },
    }),
    [],
  )
  const toggleDraggable = useCallback(() => {
    setDraggable((d) => !d)
  }, [])

  return (
    <Marker
      draggable={draggable}
      eventHandlers={eventHandlers}
      position={position}
      ref={markerRef}>
      <Popup minWidth={90}>
        <span onClick={toggleDraggable}>
          {draggable
            ? 'Marker is draggable'
            : 'Click here to make marker draggable'}
        </span>
      </Popup>
    </Marker>
  )
}

export default class DraggableMarkerExample extends Component {
  render() {
    return (
      <MapContainer center={center} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <DraggableMarker />
      </MapContainer>
    )
  }
}

image.png

12. View Bounds

innerBoundsまたはouterBoundsの内側をクリックすると、クリックした側をinnerHandler、クリックしていない側をouterHandlerに登録して、その範囲が大きく表示されるように

my-app/src/components/viewBounds.js
import React, { Component, useState, useMemo } from 'react'
import { MapContainer, TileLayer, Rectangle, useMap } from 'react-leaflet'

const innerBounds = [
  [49.505, -2.09],
  [53.505, 2.09],
]
const outerBounds = [
  [50.505, -29.09],
  [52.505, 29.09],
]

const redColor = { color: 'red' }
const whiteColor = { color: 'white' }

function SetBoundsRectangles() {
  const [bounds, setBounds] = useState(outerBounds)
  const map = useMap()

  const innerHandlers = useMemo(
    () => ({
      click() {
        setBounds(innerBounds)
        map.fitBounds(innerBounds)
      },
    }),
    [map],
  )
  const outerHandlers = useMemo(
    () => ({
      click() {
        setBounds(outerBounds)
        map.fitBounds(outerBounds)
      },
    }),
    [map],
  )

  return (
    <>
      <Rectangle
        bounds={outerBounds}
        eventHandlers={outerHandlers}
        pathOptions={bounds === outerBounds ? redColor : whiteColor}
      />
      <Rectangle
        bounds={innerBounds}
        eventHandlers={innerHandlers}
        pathOptions={bounds === innerBounds ? redColor : whiteColor}
      />
    </>
  )
}

export default class ViewBoundsExample extends Component {
  render() {
    return (
      <MapContainer bounds={outerBounds} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <SetBoundsRectangles />
      </MapContainer>
    )
  }
}

image.png

13. Animated Panning

checkboxの値でsetViewのanimateを有効にしたり無効にしたりできます

my-app/src/components/animatedPanning.js
import React, { Component, useRef } from 'react'
import { MapContainer, TileLayer, useMapEvent } from 'react-leaflet'

function SetViewOnClick({ animateRef }) {
  const map = useMapEvent('click', (e) => {
    map.setView(e.latlng, map.getZoom(), {
      animate: animateRef.current || false,
    })
  })

  return null
}

function AnimateExample() {
  const animateRef = useRef(false)

  return (
    <>
      <p>
        <label>
          <input
            type="checkbox"
            onChange={() => {
              animateRef.current = !animateRef.current
            }}
          />
          Animate panning
        </label>
      </p>
      <MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <SetViewOnClick animateRef={animateRef} />
      </MapContainer>
    </>
  )
}

export default class AnimatedPanningExample extends Component {
  render() {
    return (
      <AnimateExample />
    )
  }
}

14. External State

逆にleafletの情報をコンポーネント外で使うことも出来ます

my-app/src/components/
import React, {useState, useCallback, useEffect, useMemo} from 'react'
import { MapContainer, TileLayer } from 'react-leaflet'

const center = [51.505, -0.09]
const zoom = 13

function DisplayPosition({ map }) {
  const [position, setPosition] = useState(map.getCenter())

  const onClick = useCallback(() => {
    map.setView(center, zoom)
  }, [map])

  const onMove = useCallback(() => {
    setPosition(map.getCenter())
  }, [map])

  useEffect(() => {
    map.on('move', onMove)
    return () => {
      map.off('move', onMove)
    }
  }, [map, onMove])

  return (
    <p>
      latitude: {position.lat.toFixed(4)}, longitude: {position.lng.toFixed(4)}{' '}
      <button onClick={onClick}>reset</button>
    </p>
  )
}

function ExternalStateExample() {
  const [map, setMap] = useState(null)

  const displayMap = useMemo(
    () => (
      <MapContainer
        center={center}
        zoom={zoom}
        scrollWheelZoom={false}
        whenCreated={setMap}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
      </MapContainer>
    ),
    [],
  )

  return (
    <div>
      {map ? <DisplayPosition map={map} /> : null}
      {displayMap}
    </div>
  )
}

export default ExternalStateExample;

image.png

まとめ

Reactを使う環境でleafletを使うなら単独で使うよりこちらを使った方が良さげです

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

mongoose の model と schema に TypeScript で型をつける

Migration mongoose models and schemas from JavaScript to TypeScript

今年は自宅にこもりがちになって腰椎椎間板ヘルニアと坐骨神経痛のコンボを決めた @algas です。
この記事は TypeScript Advent Calendar 2020 の1日目の記事として作成しました。
記事に登場するコードは github リポジトリで公開しています。
https://github.com/algas/typed-mongoose-example

概要

mongoose という mongoDB の Node.js では有名なライブラリを使うアプリケーションコードを JavaScript から TypeScript に移行する作業を行いました。
JavaScript で定義済みの mongoose のモデルとスキーマに「正しく」型をつけることができたのでそのノウハウを共有します。
基本的には @types/mongoose を使って型をつけています。
スキーマの型定義の一部が不十分だったので独自の定義をして補いました。

対象読者

この記事は次のような読者を対象に想定しています。

  • mongoose または mongoDB を使っている
  • TypeScript を書いたことがある

mongoDB や mongoose, TypeScript の詳しい説明はしません。

背景

この記事を書くに至った背景を説明します。

なぜ mongoose のコードに型をつけると良いのか

わざわざ書くまでもないとは思いますが、コードを静的型付きにすることでコンパイル時に不具合を見つけやすくなったり開発環境によるコード記述の補完を得られるようになります。適切に型をつけることができればスキーマやモデルに対して定義したフィールドやメソッドとして何が含まれているかやその型の情報を使って効率よく安心して開発をすることができます。

mongoose が TypeScript ネイティブではない

mongoose は JavaScript で古くから開発され続けているライブラリで、そのコードは TypeScript に対応していません。@types/mongoose で型情報を別途付与することはできますが、TypeScript のアプリケーションから呼び出すのに便利な実装になっているとは言えません。

mongoose のスキーマやモデルに型をつけるのは難しい

mongoose スキーマやモデルに後から TypeScript で型をつけるのには工夫が必要です。mongoose の Collection スキーマには単純にデータベースに値を保持するフィールドだけではなく Virtual Property, Instance Method, Static Method, Plugin などの機能があります。これらに対応するモデルに型をつけ、さらにスキーマ自体にフィールドの型を付与するのが本記事の試みです。

初めから TypeScript で書く場合やこれから新しく mongoose スキーマを定義する場合には別のライブラリを使うなどの手法をオススメします。

実装例

具体的に mongoose で定義したスキーマとモデルに TypeScript で型をつけてみます。
https://github.com/algas/typed-mongoose-example/blob/main/src/model.ts

執筆時点では次のバージョンのライブラリにそれぞれ対応しています。

  • typescript: 3.x
  • mongoose: 5.x
  • @types/mongoose: 5.x

mongoose の基本的な使い方は公式ドキュメントに書いてあります。
https://mongoosejs.com/docs/index.html
これを知っている前提で話を進めます。

Schema

mongoDB の Collection に入れるデータのスキーマを定義します。
User という Collection を作ることにします。
Instance method として foo という関数を追加しています。

import { Document, model, Model, Schema, Types } from 'mongoose';

interface UserSchemaFields {
  email: string;
  firstName: string;
  lastName: string;
  age?: number;
  friends?: Types.ObjectId[];
}
const userSchemaFields: SchemaDefinition<UserSchemaFields> = {
  email: {
    type: String,
    required: true,
    unique: true
  },
  firstName: {
    type: String,
    required: true
  },
  lastName: {
    type: String,
    required: true
  },
  age: {
    type: Number
  },
  friends: [Schema.Types.ObjectId],
};

const UserSchema: Schema<UserSchemaProperties> = new Schema(userSchemaFields);

// Instance methods
interface UserSchemaProperties extends UserSchemaFields {
  foo: () => void;
}
UserSchema.methods.foo = function() {};

ここではスキーマとそのフィールドの定義に明示的に型を与えているのが一般的な方法との違いです。
Instance method の定義は Schema だけに与えて SchemaDefinition には与えません。
独自に定義し直した SchemaDefinition は次のように書けます。
https://github.com/algas/typed-mongoose-example/blob/main/src/mongoose-util.d.ts

import { Schema, SchemaType, SchemaTypeOpts } from 'mongoose';

type SchemaPropType<T> = T extends string
  ? StringConstructor
  : T extends number
  ? NumberConstructor
  : T extends boolean
  ? BooleanConstructor
  : any;

type SchemaTypeOptions<T> = Omit<SchemaTypeOpts<T>, 'type'> & {
  type?: SchemaPropType<T>;
};

export type SchemaDefinition<T = any> = {
  [K in keyof T]: SchemaTypeOptions<T[K]> | Schema | SchemaType;
};

SchemaTypeOpts から 'type' の定義を取り除いて使いました。
元の実装は次のコードで定義されています。
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/mongoose/index.d.ts

Virtual properties (Document)

mongoose schema には virtual という機能を使って仮想的な要素を追加できます。
ここでは fullName を取得する機能を実装します。
UserDocumentDocumentUserSchemaProperties (fields + instance methods) を継承させて virtual property fullName の型を足します。

// Virtual properties
interface UserDocument extends Document, UserSchemaProperties {
  fullName: string;
}

UserSchema.virtual('fullName').get(function () {
  return [this.firstName, this.lastName].join(' ');
});

mongoose Document の詳細は mongoose の API reference に書かれています。
https://mongoosejs.com/docs/documents.html

Statics variables and functions

mongoose schema には static の変数や関数を追加できます。
UserModel に上で定義した UserDocument を指定した Model<UserDocument> を継承させて static method bar の型を足します。

// Static methods
interface UserModel extends Model<UserDocument> {
  bar: () => string;
}

UserSchema.statics.bar = function(){
  return 'bar';
}

mongoose Model の詳細は mongoose の API reference に書かれています。
https://mongoosejs.com/docs/models.html

Plugins

他のスキーマで定義された関数などを plugin として呼び出すことができます。
plugin で作った変数や関数の interface は本体とは別に定義して個別に呼び出せるようにすべきです。
たとえば SomePluginSchema という名前のスキーマで定義されている static 関数を呼び出す場合には次のように書きます。

interface SomePluginFunctions {
  somePluginFunction: () => void;
}

UserSchema.plugin(SomePluginSchema, {});
interface UserModel extends Model<UserDocument>, SomePluginFunctions { ... }

Model

mongoose model を定義します。
引数にはモデルの名前とスキーマを渡します。
型指定に UserDocument, UserModel を明示することで User モデルの型情報が使えるようになります。

export const User = model<UserDocument, UserModel>('User', UserSchema);

モデルとスキーマを使ってみる

ここまででスキーマとモデルの定義はおしまいです。
モデルを使うにはインスタンス化する必要があります。

const someUserData: UserSchemaFields {
  ...
}
const someUser = new User(someUserData);

someUserUserDocument 型になります。
User() の引数に渡すオブジェクトに UserSchemaFields の型を使えば厳密に定義できます。

また User データを mongoDB から取得するコードは次のように書きます。

User.find(function (err, users) {
  if (err) return console.error(err);
  console.log(users);
})

TypeScript が正しく設定された開発環境を使うと User やそのインスタンスにフィールドやメソッドが適切に補完されることを確認できます。

まとめ

  • 適切に型をつければ TypeScript でも mongoose を使った開発をできる
  • mongoose に TypeScript で適切な型をつけるのはちょっと大変
  • 新しく TypeScript + mongoDB を使った開発をするのであれば mongoose 以外のライブラリを使うべき

注意点

参考文献

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

[javascript] switchを用いて3つ以上の条件分岐を行う。

この記事ではrails5.2.3を使用しています

概要

jsで条件分岐する際にswitchの使用方法で引っかかったので備忘録

if else との違いは?

こちらの記事で分かりやすく紹介されています。
https://qiita.com/taai/items/90cd190764f6dfcf6abd

解説

switch文の基本書式
switch(){
  case 条件1:
    条件1に当てはまる場合の処理
    break;
  case 条件2:
    条件2に当てはまる場合の処理
    break;
  default どのcaseにも当てはまらない場合:
    どのcaseにも当てはまらない場合の処理
}
switch(){
・・・
}

ここの式と条件が一致した場合に指定した処理が行われます。
条件は上から順に読み込まれ、最初にヒットした条件の処理が実行されます。

例えば

let a = "あ"

switch(a){
  case "あ":
    console.log(0)
    break;
  case "い":
    console.log(1)
    break;
  case "あ":
    console.log(2)
    break;
  default:
    console.log(3)
}

>> 0

条件に一致した最初のcaseが実行されました。
3つ目のcaseにも一致する条件がありますが、実行されるのは最初にヒットした1件のみなのでその後の処理は実行されません。

様々な条件の指定方法

こんな使い方もできます。

let a = [1, 2, 3, 4, 5]

switch(true){
  case a.length == 4:
    console.log(0)
    break;
  case a.length == 5:
    console.log(1)
    break;
  default:
    console.log(2)
}

>> 1

こうすると、細かい条件指定ができます。
参考記事にはさらに応用的な使い方も紹介されているので、確認してみてください。

参考

https://www.javadrive.jp/javascript/if/index4.html

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

Denoでshebangを使って実行する

JavaScrptでもTypeScriptでも、これでOK!

#!/usr/bin/env -S deno run
console.log('hello deno!');

deno runで実行する仕様なので、
#!/usr/bin/env nodeと同じ感覚で
#!/usr/bin/env denoと書いてもダメなんですねー。

error: Found argument './hello.js' which wasn't expected, or isn't valid in this context

USAGE:
    deno [OPTIONS] [SUBCOMMAND]

For more information try --help

#!/usr/bin/env deno runでも叱られます。
シェバンにオプションを渡すときは-Sが必要。

/usr/bin/env: ‘deno run’: No such file or directory
/usr/bin/env: use -[v]S to pass options in shebang lines

参考
https://stackoverflow.com/questions/63043929/how-do-i-invoke-deno-from-a-shell-script

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

javaScriptのオブジェクトからkeyを取り出す方法

備忘録なので簡単にまとめる

Object.keys()

  • オブジェクトからkeyを取り出したい
const object = { a: 'somestring', b: 42, c: false };

console.log(Object.keys(object));
// 出力結果: Array ["a", "b", "c"]

という感じらしい。

おわり。

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

[Node.js][Deno] クラス定義各種 ベンチマーク比較

JavaScriptのクラス定義手法はいっぱいあって、これを解説している記事もいっぱいありますが、パフォーマンスに言及しているところがなかったので、自前で実験してみた。

実験環境

  • Node.js 14.15.1
  • Deno 1.5.4

なお、webクライアントでの実行コストはあまり気にしてないので、各種ブラウザでの比較はパス。

実験設定

  • 継承のあるクラスで
    • インスタンス生成コストだけでなく、親メンバへのアクセスコストも重要
  • とにかくランタイムで高速なものを追求
    • プロトタイプ定義コストは、あまり気にしてない
  • 標準的なクラス定義方法との互換性も、あまり気にしてない
    • 各メンバの読み書きができれば充分
    • ただしもちろん、書き込みによってプロトタイプを破壊しないことは重要

そんなわけで、各手法それぞれ

  • TestClassA 定義
    • プロパティ a とメソッド getA() を含める
  • TestClassB 定義
    • TestClassA を継承
    • プロパティ b~i を含める
  • TestClassC 定義
    • TestClassB を継承
    • プロパティ j~z を含める (このぐらい余計に定義入れとけばプロトタイプへのアクセスコストに差が出てくること期待)
  • TestClassC で最初のインスタンス生成
    • ついでに正常にアクセスできるかテスト
  • 実行時間計測
    • 最初のインスタンスでget (プロパティ a 読み出し)
    • 最初のインスタンスでcall (メソッド getA() 呼び出し)
    • 新規生成インスタンスでget
    • 新規生成インスタンスでcall
  • 結果表示
    • get call それぞれ
    • 新規生成インスタンス版結果 - 最初のインスタンス流用版結果 → インスタンス生成コスト
    • 10回実行して平均をとる

な手順で。

実験: 標準的なprototype定義方式

まずは普通に。

bench_class_standard.js
// TestClassA 定義 
function TestClassA(){
    this.a=1;
};
TestClassA.prototype.getA=function(){return this.a;};

// TestClassB 定義 
function TestClassB(){
    TestClassA.call(this);
    this.b=2;
    this.c=3;
    this.d=4;
    this.e=5;
    this.f=6;
    this.g=7;
    this.h=8;
    this.i=9;
}
Object.setPrototypeOf(TestClassB.prototype,TestClassA.prototype);

// TestClassC 定義 
function TestClassC(){
    TestClassB.call(this);
    this.j=10;
    this.k=11;
    this.l=12;
    this.m=13;
    this.n=14;
    this.o=15;
    this.p=16;
    this.q=17;
    this.r=18;
    this.s=19;
    this.t=20;
    this.u=21;
    this.v=22;
    this.w=23;
    this.x=24;
    this.y=25;
    this.z=26;
}
Object.setPrototypeOf(TestClassC.prototype,TestClassB.prototype);

// 既存インスタンス準備 
var t=new TestClassC();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>new TestClassC().a,
    new_call:()=>new TestClassC().getA(),
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get 3.7 427.8
call 7.7 434.7

後発品なのになんでこんな遅いんだよDeno
…というわけで詳細に調べたところ、どうやらコンストラクタで親クラスのcall呼んでるところでやたらと時間かかってる模様。
そんなわけでDeno移行は時期尚早かな…って結論出す前に、まず次いってみましょうか。

実験: いまどきのclass方式

ECMAScript 2015以降の追加仕様ですね。
めんどいprototype定義ともおさらば、やったー
…って喜ぶ前に、パフォーマンスをみてみましょう。

bench_class_modern.js
// TestClassA 定義 
class TestClassA{
    constructor(){
        this.a=1;
    }
    getA(){return this.a;}
}

// TestClassB 定義 
class TestClassB extends TestClassA{
    constructor(){
        super();
        this.b=2;
        this.c=3;
        this.d=4;
        this.e=5;
        this.f=6;
        this.g=7;
        this.h=8;
        this.i=9;
    }
}

// TestClassC 定義 
class TestClassC extends TestClassB{
    constructor(){
        super();
        this.j=10;
        this.k=11;
        this.l=12;
        this.m=13;
        this.n=14;
        this.o=15;
        this.p=16;
        this.q=17;
        this.r=18;
        this.s=19;
        this.t=20;
        this.u=21;
        this.v=22;
        this.w=23;
        this.x=24;
        this.y=25;
        this.z=26;
    }
}

// 既存インスタンス準備 
var t=new TestClassC();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>new TestClassC().a,
    new_call:()=>new TestClassC().getA(),
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get 145.4 91.3
call 139.8 89.3

…遅っ。
だめだこりゃ、次いってみよー

実験: コンストラクタをinit()で代用方式

先ほど、親クラスのcallが遅いって書きました。
で、コンストラクタ使わずに自前で初期化メソッド書いちゃえばいんじゃね作戦。

bench_class_noctor.js
// TestClassA 定義 
function TestClassA(){}
TestClassA.prototype.init=function(){
    this.a=1;
};
TestClassA.prototype.getA=function(){return this.a;};

// TestClassB 定義 
function TestClassB(){}
Object.setPrototypeOf(TestClassB.prototype,TestClassA.prototype);
TestClassB.prototype.init=function(){
    Object.getPrototypeOf(this).init();
    this.b=2;
    this.c=3;
    this.d=4;
    this.e=5;
    this.f=6;
    this.g=7;
    this.h=8;
    this.i=9;
};

// TestClassC 定義 
function TestClassC(){}
Object.setPrototypeOf(TestClassC.prototype,TestClassB.prototype);
TestClassC.prototype.init=function(){
    Object.getPrototypeOf(this).init();
    this.j=10;
    this.k=11;
    this.l=12;
    this.m=13;
    this.n=14;
    this.o=15;
    this.p=16;
    this.q=17;
    this.r=18;
    this.s=19;
    this.t=20;
    this.u=21;
    this.v=22;
    this.w=23;
    this.x=24;
    this.y=25;
    this.z=26;
};

// 既存インスタンス準備 
var t=new TestClassC();
t.init();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>{var u=new TestClassC(); u.init(); u.a},
    new_call:()=>{var u=new TestClassC(); u.init(); u.getA()},
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get 1857.6 1949.5
call 1848.6 1949.3

OMG.
getPrototypeOf() さらに重かった。
しかもこれ、 console.log(t) でTestClassCのぶんしか出ないですよ。
console.log(t.getA()) はちゃんと1って出てるので、継承は正常っぽい。

実験: 親クラスのメソッド転記方式

では、 getPrototypeOf() にも頼らず、親クラスのメソッドを自前転記作戦。

bench_class_selflink.js
// TestClassA 定義 
function TestClassA(){}
TestClassA.prototype.init=function(){
    this.a=1;
};
TestClassA.prototype.getA=function(){return this.a;};

// TestClassB 定義 
function TestClassB(){}
Object.setPrototypeOf(TestClassB.prototype,TestClassA.prototype);
TestClassB.prototype.init_TestClassA=TestClassA.prototype.init;
TestClassB.prototype.init=function(){
    this.init_TestClassA();
    this.b=2;
    this.c=3;
    this.d=4;
    this.e=5;
    this.f=6;
    this.g=7;
    this.h=8;
    this.i=9;
};

// TestClassC 定義 
function TestClassC(){}
Object.setPrototypeOf(TestClassC.prototype,TestClassB.prototype);
TestClassC.prototype.init_TestClassB=TestClassB.prototype.init;
TestClassC.prototype.init=function(){
    this.init_TestClassB();
    this.j=10;
    this.k=11;
    this.l=12;
    this.m=13;
    this.n=14;
    this.o=15;
    this.p=16;
    this.q=17;
    this.r=18;
    this.s=19;
    this.t=20;
    this.u=21;
    this.v=22;
    this.w=23;
    this.x=24;
    this.y=25;
    this.z=26;
};

// 既存インスタンス準備 
var t=new TestClassC();
t.init();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>{var u=new TestClassC(); u.init(); u.a},
    new_call:()=>{var u=new TestClassC(); u.init(); u.getA()},
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get 5.6 6.9
call 9.5 1.4

Denoで見違える結果になりました。
で、getよりcallの方が速い謎が増えた。
あと、 console.log(t) は一通り出てきてます。

実験: メソッドをアロー演算子で簡略表記方式

prototypeでアロー演算子使ってもうまくいかないんだよねぃ。
というわけで、 getA() の定義を init() 内に移してみた。

bench_class_lambda.js
// TestClassA 定義 
function TestClassA(){}
TestClassA.prototype.init=function(){
    this.a=1;
    this.getA=()=>this.a;
};

// TestClassB 定義 
function TestClassB(){}
Object.setPrototypeOf(TestClassB.prototype,TestClassA.prototype);
TestClassB.prototype.init_TestClassA=TestClassA.prototype.init;
TestClassB.prototype.init=function(){
    this.init_TestClassA();
    this.b=2;
    this.c=3;
    this.d=4;
    this.e=5;
    this.f=6;
    this.g=7;
    this.h=8;
    this.i=9;
};

// TestClassC 定義 
function TestClassC(){}
Object.setPrototypeOf(TestClassC.prototype,TestClassB.prototype);
TestClassC.prototype.init_TestClassB=TestClassB.prototype.init;
TestClassC.prototype.init=function(){
    this.init_TestClassB();
    this.j=10;
    this.k=11;
    this.l=12;
    this.m=13;
    this.n=14;
    this.o=15;
    this.p=16;
    this.q=17;
    this.r=18;
    this.s=19;
    this.t=20;
    this.u=21;
    this.v=22;
    this.w=23;
    this.x=24;
    this.y=25;
    this.z=26;
};

// 既存インスタンス準備 
var t=new TestClassC();
t.init();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>{var u=new TestClassC(); u.init(); u.a},
    new_call:()=>{var u=new TestClassC(); u.init(); u.getA()},
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get 10.8 12.9
call 712.6 556.8

init() の度に再定義しているわけだから重いのは必然だし、
console.log(t) で getA まで出てくる代物なのですが、
定義するだけで呼ばないならさほど大きなコストではないらしい。

実験: proto詰め込み方式

console.log(t) を気にしないでいいなら、こんな手法もあり。
プロパティの初期値もprototypeにぶっ込んでしまえば、そのぶんインスタンス生成コストも軽くなるわけで。

bench_class_fullproto.js
// TestClassA 定義 
function TestClassA(){};
TestClassA.prototype.a=1;
TestClassA.prototype.getA=function (){return this.a;}

// TestClassB 定義 
function TestClassB(){}
Object.setPrototypeOf(TestClassB.prototype,TestClassA.prototype);
TestClassB.prototype.b=2;
TestClassB.prototype.c=3;
TestClassB.prototype.d=4;
TestClassB.prototype.e=5;
TestClassB.prototype.f=6;
TestClassB.prototype.g=7;
TestClassB.prototype.h=8;
TestClassB.prototype.i=9;

// TestClassC 定義 
function TestClassC(){}
Object.setPrototypeOf(TestClassC.prototype,TestClassB.prototype);
TestClassC.prototype.j=10;
TestClassC.prototype.k=11;
TestClassC.prototype.l=12;
TestClassC.prototype.m=13;
TestClassC.prototype.n=14;
TestClassC.prototype.o=15;
TestClassC.prototype.p=16;
TestClassC.prototype.q=17;
TestClassC.prototype.r=18;
TestClassC.prototype.s=19;
TestClassC.prototype.t=20;
TestClassC.prototype.u=21;
TestClassC.prototype.v=22;
TestClassC.prototype.w=23;
TestClassC.prototype.x=24;
TestClassC.prototype.y=25;
TestClassC.prototype.z=26;

// 既存インスタンス準備 
var t=new TestClassC();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>new TestClassC().a,
    new_call:()=>new TestClassC().getA(),
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get -6 7.2
call 1.5 3.5

Denoの結果がいまいち。
どうもprototypeが重い御様子。
てゆかNode.js、マイナスってなんだよおい。

実験: Object.create() 方式

prototypeが重いなら、 Object.create() ならどうだ。

bench_class_create.js
// TestClassA 定義 
var TestClassA={a:1};
TestClassA.getA=function(){return this.a;};

// TestClassB 定義 
var TestClassB=Object.create(TestClassA);
TestClassB.b=2;
TestClassB.c=3;
TestClassB.d=4;
TestClassB.e=5;
TestClassB.f=6;
TestClassB.g=7;
TestClassB.h=8;
TestClassB.i=9;

// TestClassC 定義 
var TestClassC=Object.create(TestClassB);
TestClassC.j=10;
TestClassC.k=11;
TestClassC.l=12;
TestClassC.m=13;
TestClassC.n=14;
TestClassC.o=15;
TestClassC.p=16;
TestClassC.q=17;
TestClassC.r=18;
TestClassC.s=19;
TestClassC.t=20;
TestClassC.u=21;
TestClassC.v=22;
TestClassC.w=23;
TestClassC.x=24;
TestClassC.y=25;
TestClassC.z=26;

// 既存インスタンス準備 
var t=Object.create(TestClassC);
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>Object.create(TestClassC).a,
    new_call:()=>Object.create(TestClassC).getA(),
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get -2.8 74
call 2.7 74.9

没。

実験: 空オブジェクトに詰め込み方式

おまけ。
既にprototype以外でfunction定義するとすごい重いって結論出ちゃったからあまり意味なくなっちゃったんだけど…

bench_class_pureobj.js
// TestClassA 定義 
function TestClassA(){
    var t={a:1};
    t.getA=()=>t.a;
    return t;
}

// TestClassB 定義 
function TestClassB(){
    var t=TestClassA();
    t.b=2;
    t.c=3;
    t.d=4;
    t.e=5;
    t.f=6;
    t.g=7;
    t.h=8;
    t.i=9;
    return t;
}

// TestClassC 定義 
function TestClassC(){
    var t=TestClassB();
    t.j=10;
    t.k=11;
    t.l=12;
    t.m=13;
    t.n=14;
    t.o=15;
    t.p=16;
    t.q=17;
    t.r=18;
    t.s=19;
    t.t=20;
    t.u=21;
    t.v=22;
    t.w=23;
    t.x=24;
    t.y=25;
    t.z=26;
    return t;
}

// 既存インスタンス準備 
var t=TestClassC();
//console.log(t);
//console.log(t.getA());

// 実行内容 
var funx={
    dmy:()=>0, // 最初の実行項目は不利な結果が出るのでダミー 
    get:()=>t.a,
    call:()=>t.getA(),
    new_get:()=>TestClassC().a,
    new_call:()=>TestClassC().getA(),
};
// 計測結果を書き込むところ 
var rec={};

// 繰り返し実行時間計測 
var loop=Array(10000000);
for(var k in funx){
    var f=funx[k];
    var a=0;
    var bgn=new Date;
    for(var i of loop)a+=f();
    var end=new Date;
    rec[k]=end-bgn;
}

// インスタンス生成時間を抽出表示 
console.log(rec.new_get-rec.get);
console.log(rec.new_call-rec.call);
Node.js Deno
get 7.4 8.4
call 1173.1 803.3

ということで。
因みに、プロパティ定義で横着して
Object.assign(TestClassA(),{b:2,...})
みたいな書き方すると、さらに悲惨な結果になります。
JavaScriptでのオブジェクト合成って、 何やっても重い ので困ったもんだ。

結論

邪道実装でいい案件なら、proto詰め込み方式を推し進めたいところ。
あと、やはりDenoはパフォーマンス改善まで様子見。

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

リンクを別タブで開かせる際、target="_blank"だけだと危険らしい

はじめに

webアプリケーション作成時に、リンクを別タブで開かせたいと思ってググってみたところ、target="_blank"だけの記述では危険だという記事がヒットした。

リンクを別タブで開かせる基本的な記述

<a href="リンク先" target="_blank">別タブで開く</a>

aタグでリンクを別タブで開かせたいときの基本的な記述。これだけでリンクを別タブで開かせることができる。しかしこれだけだと危険性があるらしい。

セキュリティ面とパフォーマンス面で問題がある

target="_blank"だけの記述では、セキュリティ面とパフォーマンス面で問題があると、googleのデベロッパーサイトで指摘されている。

具体的な問題内容と対策についてまとめてくれている日本語記事があったのでこちらも参考に

対策方法

上のリンク先でも紹介されているが、target="_blank"と併用してrel="noopener"またはrel="noreferrer"を記述することが推奨されている。

<!-- 記述例 -->
<a href="リンク先" target="_blank" rel="noopener">別タブで開く</a>

rel="noopener"rel="noreferrer"はどちらもリンク先からリンク元が参照できなくなるようにする。
これでリンク先からリンク元情報を抜き取られたり操作されなくなる。

さいごに

今回は個人的なwebアプリケーションの中で別タブリンクを貼りたかったので大きく影響のある話ではなかったが、これからwebアプリを開発していくにあたって頭に入れておいたほうが良さそうな内容だと思った。

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

JavaScript勉強日記#3

①繰り返し処理

<script>
  //console.log(1 + '枚');
  //console.log(2 + '枚');
  //console.log(3 + '枚');
  //console.log(4 + '枚');
  //console.log(5 + '枚');
  //console.log(6 + '枚');
  //console.log(7 + '枚');
  //console.log(8 + '枚');
  //console.log(9 + '枚');
  //console.log(10 + '枚');

素直に書く事もできるが記述が多くなってかなり面倒...

  'use strict';
  let i =1;
  while(i <= 10) {
    console.log(i + '枚');
    i += 1;
  }
</script>

while文を使う事で同じ記述を使わない様にできます。

②何回繰り返すかを決めずに繰り返し処理をする

<script>
'use strict';
let enemy = 100
let count = 0;
window.alert('戦闘開始!');

while(enemy > 0) {
  const attack = Math.floor(Math.random() * 30) + 1;
  console.log('怪物に' + attack + 'の損傷!');
  enemy = enemy - attack;
  count += 1
}
console.log(count + '回怪物討伐!')
</script>

③無限ループの抜け方
私は②のコードを書く時にenemy = enemy = attack;と書いてしまい無限ループに陥りましたので、ついでにループの抜け方(強制終了)も記載しておきます。
Macの場合はcommand + option + escでウィンドウを開き、WindowsはCtrl + Alt + Deleteでタスクマネージャーを開きます。

https://gyazo.com/38bd87ffea1c2a4d5b83583dc3f6365a

④functionの使い方
https://gyazo.com/1094994e4e432944c458459049c305b7

<section>
  <p id="output1"></p>
  <p id="output2"></p>
  <p id="output3"></p>
</section>
</main>

<script>
'use strict';

function total(price) {
  const tax = 0.1;
  return price + price * tax;
}
console.log('コーヒーメーカーの値段は' + total(8000) + '円です。');
document.getElementById('output1').textContent = 'コーヒーメーカーの値段は' + total(8000) + '円(税込)です。'
document.getElementById('output2').textContent = 'コーヒー豆の値段は' + total(900) + '円(税込)です。'
document.getElementById('output3').textContent = 'コーヒーフィルターの値段は' + total(250) + '円(税込)です。'

</script>

function total(price) { 加工の内容の記述(priceはこの中だけで使えます) }
今回は、totalと言う名前のファンクションを使用しました。この記述以降total(price)の記述を書くと何度でも呼び出す事ができます。functionは資源です。

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

【JavaScript】for文の入れ子を理解したい!

プログラミング勉強日記

2020年11月26日
for文の入れ子の処理について学習したので、記録します。
間違えている自信しかないので、何かあればコメントください?

for文とは

指定された回数だけ繰り返し処理を行う命令です。
(for-Javascript | MDN)

書き方

for文.
for(初期化式; ループ継続条件式; 増減式) {
 ループ内で実行する命令
}

処理の流れ

1.初期化処理

2.条件判定

3.判定がtrueの場合、ループ内の処理実行、falseの場合、ループの終了

4.増減処理

5.3の判定がfalseになるまで繰り返し

(例) 変数iが10未満の間、コンソールに数値を出力

script.js
for (let i =0; i < 10; i ++) {
    console.log(i);
}

for文の入れ子

for文の中にfor文を書くことが可能です。

※わかりやすい例文が思いつかなかったので、許してください

script.js
for (let i =0; i < 2; i ++) {
        console.log('外側のループです');
        for(let n =0; n < 2 ; n ++) {
            console.log('内側のループです');
    }
}

処理の流れ

1.変数iに0を代入

2.条件判定( i < 2 )

3.コンソールに外側のループですと文字列を出力する

4.内側のループに入る

5.変数nに0を代入

6.条件判定( n < 2 )

7.コンソールに内側のループですと文字列を出力する

8.変数nに1を加算する( n ++ )

9.内側のループが終了すると、外側のループの変数iに1を加算する( i ++ )

10.外側のループが終わるまで繰り返す

結果

スクリーンショット 2020-11-26 9.32.59.jpg

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

title: <input type="file">を初期化する方法

input type="file" を初期化する方法

初期化するには、inputタグにidを指定し、

<input
   id="inputFile"
   type="file"
> 

script内で、inputのidを使って

document.getElementById('inputFile').value = ''

を実行する。

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

<input type="file">を初期化する方法

input type="file" を初期化する方法

初期化するには、inputタグにidを指定し、

<input
   id="inputFile"
   type="file"
> 

script内で、inputのidを使って

document.getElementById('inputFile').value = ''

を実行する。

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

静的サイトジェネレーター Gatsby.js 入門

Gatsby.js入門

第1回は、Gatsby.jsのインストールの仕方について書いていこうと思います

Gatsby.jsを利用するための準備

開発環境の用意
・Node.js(nvm)
・yarn
・git

のインストールが必要です
インストールの仕方については、次の記事を参照してください

Gatsby CLIをインストールする

yarn global add gatsby-cli
と入力して、Gatsby.jsをインストールしていきます。

スターターをダウンロードしてみる

gatsby new
と入力して、サイトを構築していきます。

入力すると、プロジェクト名と使用するスターターを聞いてくるので、自分にあったプロジェクト名とスターターを選んであげましょう。

※スターターについては、https://www.gatsbyjs.org/starters/?v=2
を参照してみてください

ダウンロードできたら、cd プロジェクト名でプロジェクトフォルダに移動し、gatsby developで開発サーバーを起動してみます。

開発サーバーが起動したら、URLからページに飛んでページの表示を確認してみましょう!

サイトを公開する

gatsby buildと入力するとpublic/フォルダにサイトのデータができあがります。
これを公開すればサイトの公開は完了です。

※ちなみにビルドしたサイトの表示を確認する場合、gatsby serveと入力するば、ビルドしたサイトを確認することが可能です。

参考文献:Webサイト高速化のための静的サイトジェネレーター活用入門 GatsbyJSで実現する高速&実用的なサイト構築

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

firebaseのsetメソッドの使い方と活用方法

firebaseって何?

個人的には費用面、手軽さなど、多方面で最強のアーキテクチャだと思っています。

firebaseのデータ登録、更新について

処理内容 登録 更新
addメソッド ×
updateメソッド ×
setメソッド

上記のように、新規登録にも更新にも使えるsetメソッドですが、addメソッド、updateメソッドとちょっと使い方が違うんですよね。
そのあたりをわかりやすくまとめられたらと思っています。

(データの新規追加)addメソッドとsetメソッドの違いについて

一番大きな違いは、ドキュメントIDを自動発行するかどうかという違いです。

addメソッド:ドキュメントIDを自動発行する。
setメソッド:ドキュメントIDを自動発行しない。

使い分け!!
ドキュメントIDにこだわらない場合(どんな値でも良いっす!)はaddメソッドを使うのが楽!
ドキュメントIDを指定したい場合は、setメソッドを利用する。

いろんなところで言われている内容ではあると思いますが、ドキュメントIDを自動発行して、setメソッドを利用することもできます。

set.js
// ドキュメントをランダムIDで発行
const ref = firebase.firestore().collection('test').doc();
// 先ほど取得したドキュメントのIDを明示的に指定
await firebase.firestore().collection('test').doc(ref.id).set({
  name: テスト太郎,
  phone: 080-0000-0000
})
.then(function(){
  // データ登録成功時の処理
  // firebaseコマンドは、promiseオブジェクトなので、thenでつなげられる。
})
.catch(function(err){
  // データ登録失敗時
});

addメソッドの場合は、上記のソース上から、ドキュメントIDを取得してくる処理がまるまる省略できるってことですね。

ざっくり分けて、firebaseのデータ登録機能には以下の2工程に分けられます。
①ドキュメントIDを発行
②データ更新

上記を一つにまとめているのが、addメソッド、別々に分けているのがsetメソッドです。

(データ更新)updateメソッドと、setメソッドの違い

一番大きな違いは、特定のフィールドのみ更新するか、全部上書きするかの違いです。

updateメソッド:指定したフィールドのみ更新
setメソッド:全て更新

例えば、以下のようなコレクションがあったとします。

コレクション名:test
 ドキュメントID:1234567890abcdefg
  name:テスト太郎,
  phone:080-0000-0000,
  address:東京都千代田区

updateメソッド.js
firebase.firestore().collection('test').doc('1234567890abcdefg').update({
  name:テスト次郎
});
setメソッド.js
firebase.firestore().collection('test').doc('1234567890abcdefg').set({
  name:テスト次郎
});

単純にメソッドが違うだけですね。
これが、それぞれどうなるのかを見てみましょう。

updateメソッド
コレクション名:test
 ドキュメントID:1234567890abcdefg
  name:テスト次郎, ← ここのみ更新
  phone:080-0000-0000,
  address:東京都千代田区

setメソッド
コレクション名:test
 ドキュメントID:1234567890abcdefg
  name:テスト次郎,
※全上書きされ、明示的に記載したものだけが残る。

setの活用方法を考えてみた

以下の条件なんかの時はsetメソッドが輝きます。
・ドキュメントIDが指定したIDである。(自動生成ではない)
・新規作成と更新が同ソース内に両方とも含まれる。
→ データがなかった場合は、新規作成、あった場合は更新みたいなケースですね。

上記のようなときは、setメソッドを利用することによって、新規作成なのか、更新なのかを考慮する必要がなく、ソースを記載することができます。

普通に利用しようとすると、ちょっと使いづらいsetメソッドですが、設計次第でかなり便利なメソッドになります。

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

ポケモンとTypeScriptの超入門編(型エイリアスまで)

型エイリアス typeとは

型宣言をtypeで記述することができる。

type Age = number
let age: Age = 15

オブジェクト

もちろんオブジェクトもできる。
?を使うと必ず記述しなくても良くなる。

let o: { a: number, b?: string } = {
  a: 2
}
console.log(o.a) // -> 2
console.log(o.b) // -> undefined

readonly

オブジェクトのプロパティを書き換え禁止にできる。
変数で言うconst。

let o3: {
  readonly id: number
} = { id: 3 }
o3.id = 2 // -> エラー

ポケモンとtypeでいちゃいちゃする

以上を踏まえてあれこれ触る。

type Action = {1: string, 2?: string, 3?: string, 4?: string }
type Pokemon = {
  readonly abst: string,
  readonly name: string,
  actions: Action
}
let pityu: Pokemon = {
  abst: 'ピカチュウの進化前',
  name: 'ピチュー',
  actions: {1: 'たいあたり', 2: 'しっぽをふる'}
}
pikatyu.actions[3] = "でんきショック"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポケモンでTypeScriptおもしれーってなる(型エイリアスまで)

型エイリアス typeとは

型宣言をtypeで記述することができる。

type Age = number
let age: Age = 15

オブジェクト

もちろんオブジェクトもできる。
?を使うと必ず記述しなくても良くなる。

let o: { a: number, b?: string } = {
  a: 2
}
console.log(o.a) // -> 2
console.log(o.b) // -> undefined

readonly

オブジェクトのプロパティを書き換え禁止にできる。
変数で言うconst。

let o3: {
  readonly id: number
} = { id: 3 }
o3.id = 2 // -> エラー

ポケモンとtypeでいちゃいちゃする

以上を踏まえてあれこれ触る。

type Action = {1: string, 2?: string, 3?: string, 4?: string }
type Pokemon = {
  readonly abst: string,
  readonly name: string,
  actions: Action
}
let pityu: Pokemon = {
  abst: 'ピカチュウの進化前',
  name: 'ピチュー',
  actions: {1: 'たいあたり', 2: 'しっぽをふる'}
}
pikatyu.actions[3] = "でんきショック"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む