20200922のJavaScriptに関する記事は25件です。

【JavaScript】関数とPromiseと、ときどきasync await

関数の種類、早見表

名前 ソースコード 補足
名前付き関数 function() 関数名 {}
匿名関数(関数式) const 関数名 = function() {}
高階関数 function(関数) {関数} 引数に関数をとる関数のこと。また、引数の関数をコールバック関数という。

コールバック関数の課題

コールバック関数を使うと、ネストになりがち

new Promise(()=>{}).thenで、ネストを解消
でも、行数が増えがち

async (){ await }で、簡略化

通信成功時と失敗時のコールバック関数

const 非同期関数 = function (成功時関数, 失敗時関数) {
  if (...) {
    成功時関数(成功res)
  } else {
    失敗時関数(失敗res)
  }
}

Promise

new Promise (function (resolve, reject) {
  asynchronous(
    function(成功res) {
      resolve(成功res)
    },
    function(失敗res) {
      reject(失敗res)
    },
  )
})

axios

function getStatus () {
  return new Promise(function (resolve, reject) {
    axios.get("URL")
    .then(function(res) {
      resolve(res.status)
    }).catch(function(error) {
      reject(error.res.status)
    })
  })
}

async await

async function () {
  try {
    const 成功res = await 非同期関数
    成功時処理
  } catch(失敗res) {
    失敗時処理
  }
}

axios

async function getStatus () {
  try {
    const res = await axios.get("URL")
    return res
  } catch(error) {
    return error
  }
}

上記のように、分かりやすくtryも書いたが、いらなくても良い。

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

json2csvで出力したCSVファイルがExcelで文字化けしてしまう

はじめに

以前まとめた、【Node.js】JSON文字列をCSVファイルとして吐き出すという記事で、json2csvというモジュールを使用し、CSVファイルを正常に出力できたと思っていたのですが、ExcelでCSVファイルを開くと文字化けしてしまっていたので、修正を行いました。

結論

withBOMオプションをtrueにする

解説

json2csvのドキュメントにwithBOMオプションの説明が記載されていました。

Unicode Support
Excel can display Unicode correctly (just setting the withBOM option to true). However, Excel can't save unicode so, if you perform any changes to the CSV and save it from Excel, the Unicode characters will not be displayed correctly.

json2csv

withBOMオプションをtrueにするだけで、ExcelではUnicodeを正しく表示できる、的なことが書かれていますね。
なので、インスタンス化時にwithBOMオプションを付与してあげます。

json2csvのインスタンス化
const json2csvParser = new Parser({fields, transforms, withBOM: true});

これで、Excelでも文字化けすることなく、正常にCSVファイルが表示できるかと思います。

BOMって何だ?

「Byte Order Mark」の正式名称で、BOMが付与されたファイルはUnicodeで書かれていることを証明しています。
しかし、Unicodeで書かれたファイル全てにBOMが付与されている訳ではありません。BOMがあってもなくてもどちらでもいいそうです。BOMが存在する理由は、絶対にこのファイルがUnicodeで書かれていることを示す必要がある場合があるからで、今回のExcelでCSVファイルを開く時などが該当します。
ExcelではBOMが付与されたCSVファイルでないと、文字コードが認識されずに文字化けを引き起こしてしまうのです。

参考

バイトオーダマーク(英:Byte Order Mark)とは
簡単に文字化け回避!UNICODE (UTF-8) の CSV データを表示する方法

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

初心者によるVue.jsハンズオン

はじめに

初めて記事を書くので暖かく見守って下さい。
仕事でVue.jsを使用するということで、JS初心者が手っ取り早くVue.jsに触れてみる。

早速触ってみる

用意するものは使い慣れたブラウザとエディターがあれば十分です。

私は今回以下のものを用いて開発を行います。
-ブラウザ:Google Chrome
- エディター:Visual Studio Code

まずはベースページの作成

index.html
<html>

<body>
  Hello, World
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</body>

</html>

こちらはvue.js本体を読み込むだけの空ページの作成を行っています。
今回のハンズオンでは、vue.jsのCDNを使っていきます。

こちらのコードをVSCodeで書いたら保存しましょう。
そしてGoogle Chromeにドラッグ&ドロップすることで表示できます、便利。(今まで右クリックしてブラウザに表示していたので、、、、)

表示されたら、まずブラウザに「Hello, World」と表示されるか確認しましょう。
そして、動作としてvue.jsがリクエストされているか確認するためにChromeの開発者ツールを使いましょう。
確認方法は開発者ツール(windowsの場合は「F12」キー)->Networkです。
以下の画像のようになっていれば、大丈夫です!
image.png

それではVue.jsを実行/展開

index.html
<html>

<body>
  <div id="app"></div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      template: '<button></button>',
    });
  </script>
</body>

</html>

こちらのコードでは、Vue.jsの展開ポイントとなるdivを作成し、newで展開しています。
先ほどのブラウザをリロードしてボタンが表示されたら成功です!

解説
1. 展開ポイントは<p>でもでもなんでもいいですが慣例的に<div>を使います。
2. idもなんでもいいですが同じく慣例的にappと名付けます。

ボタンに振る舞いを追加し。紐づける

index.html
<html>

<body>
  <div id="app">
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      template: '<button @click="showMessage"></button>',
      methods: {
        showMessage() {
          alert('ok');
        },
      },
    });
  </script>
</body>

</html>

先ほどのコードに、ユーザーがボタンをクリックしてアラートを表示させるコードを追加します。
実際にクリックしたらメッセージを表示されたら成功です。

解説
1. Vue.jsの挙動は基本的にmethodsの中に定義していきます
2. Vue.jsでは生のhtmlのonclick="..."と同じようにhtmlタグにリスナ/ハンドラを記述します

状態を保持する

index.html
<html>

<body>
  <div id="app">
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      template: '<button @click="toggle"></button>',
      data: function () {
        return {
          active: false,
        };
      },
      methods: {
        toggle() {
          this.active = !this.active;
        },
        showMessage() {
          alert('ok');
        },
      },
    });
  </script>
</body>

</html>

こちらのコードでは、中でステートを保持して、クリック時にそのステートを変更するようにします。
今回は内部の状態変更のみなので確認できないです。

解説
1. 状態、ステートは基本的にdata配下に定義していきます
2. elやtemplate、methodsと違い、dataの値には無名関数を利用して、状態一覧のオブジェクトを返すようにします
3. テンプレート中でdataの値を参照する場合はthisは付けない、methodsの中で参照する場合はthisを付ける、ので注意

テンプレートに値を埋め込む

index.html
<html>

<body>
  <div id="app">
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      template: '<button @click="toggle">{{ active }}</button>',
      data: function () {
        return {
          active: false,
        };
      },
      methods: {
        toggle() {
          this.active = !this.active;
        },
        showMessage() {
          alert('ok');
        },
      },
    });
  </script>
</body>

</html>

こちらのコードでは、テンプレートに{{ }}で値を埋め込んでいて、クリック時の状態反転に連動してボタンのラベルが変わったら成功です。

解説
1. {{ ステートの変数名 }} でテンプレート中に変数を埋め込むことができます
2. Vueはリアクティブなため、変数の値が変わったら(テンプレートを書き直さなくても)自動的に反映されます

{{ }}の中に簡単なjs処理を記述する

index.html
<html>

<body>
  <div id="app">
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      template: '<button @click="toggle">{{ active ? "Like済み" : "Likeする" }}</button>',
      data: function () {
        return {
          active: false,
        };
      },
      methods: {
        toggle() {
          this.active = !this.active;
        },
        showMessage() {
          alert('ok');
        },
      },
    });
  </script>
</body>

</html>

ここはactive変数そのものだとBooleanがStringにキャストされてtrue/falseと表示されるのでボタンのラベルっぽい表示にしてます。

解説
1.{{ }} の中ではjsコードそのものを記述できます
(ただし過度な記述をすると可読性が下がるので注意)

コンポーネント化する

index.html
<html>

<body>
  <div id="app">
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('like-button', {
      template: '<button @click="toggle">{{ active ? "Like済み" : "Likeする" }}</button>',
      data: function () {
        return {
          active: false,
        };
      },
      methods: {
        toggle() {
          this.active = !this.active;
        },
        showMessage() {
          alert('ok');
        },
      },
    });

    new Vue({
      el: '#app',
      template: '<div><like-button />aaa<br />bbb<br />ccc<br /><like-button /></div>',
    });
  </script>
</body>

</html>

最後に、いままで書いてきた一連のテンプレートと振る舞い、状態をセットとしてコンポーネント化します。

解説
1.コンポーネントとして切り出すことで再利用できるようにします

最後に

今回はここで終了です!
次回も続きを書ければいいなと思います。
最後までご覧いただきありがとうございました!

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

document.createElementより便利

忘れない内にメモ
今まで生のJSを使わないと行けない環境でDomをいじくり回したい時は

var id = "hoge";
var text = "Lorem ipsum";
var header = document.createElement("h1");
header.setAttribute("id",id);
header.textContent = text;
// <h1 id="hoge">Lorem ipsum</h1>

とクッソ面倒な事をやってたけど

var id = "hoge";
var text = "Lorem ipsum";
var parser = new DOMParser();
var temp = "<h1 id=" + id + ">" + text + "</h1>";
var doc = parser.parseFromString(temp, "text/html");

と簡潔に書けるらしい。

テンプレートリテラル使えるなら

var id = "hoge";
var text = "Lorem ipsum";
var parser = new DOMParser();
var temp = `<h1 id="${id}">${text}</h1>`;
var doc = parser.parseFromString(temp, "text/html");

めっちゃ楽になる。

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

適当な文字列を 0 から 1 までの実数に振り分けるロジック (PHP/TypeScript)

PHP-7.2 TypeScript-3.7

最近 A/B テストのように確率的な条件分岐を実現する文脈で、適当な入力文字列から区間 $ [0, 1] $ に含まれる数値を出力するようなハッシュ関数を実装する機会がありました。単に定められた範囲の数値を出力するだけでなく、出力される数値に偏りが出ないように(関数の出力値の分布が連続一様分布とみなせる 1 ように)するという要件もありました。

イメージとしては以下のコードのようになります。

// 同じ文字列を入力に取ると同じ数値が出力される
strToNumberBetweenZeroAndOne('abc');        // output: 0.7283949105904
strToNumberBetweenZeroAndOne('abc');        // output: 0.7283949105904

// 別の文字列を入力に取ると別の数値が出力される
strToNumberBetweenZeroAndOne('Hello');      // output: 0.095208030923864
strToNumberBetweenZeroAndOne('World!');     // output: 0.31755707966684
strToNumberBetweenZeroAndOne('0123456789'); // output: 0.51892998626938

// 色々な文字列に対して出力値をプロットしていくと、数値の確率分布が区間 [0, 1] の一様分布に近づく。

このような関数を PHP と TypeScript で実装したので紹介します。

実装

PHP

PHP の場合は以下のように実装できます。

function strToNumberBetweenZeroAndOne(string $message): float
{
    // 1
    $hashHex = hash('sha256', $message);

    // 2
    $maxHex = str_repeat("f", 64);
    return hexdec($hashHex) / hexdec($maxHex);
}

1 の部分では hash('sha256', $string) で入力文字列を SHA256 2 で64桁の固定長16進数に変換し、 hexdec() でその10進数表現を得ています。文字列から16進数への変換は bin2hex で行うこともできますが、文字列のサイズが大きすぎると桁あふれを起こしてしまうので使用を避けています。

2 の部分では 1 で得られた10進数を64桁の16進数の最大値 str_repeat("f", 64) の10進数表現で割ることで数値を算出しています。

TypeScript

import CryptoJS from 'crypto-js'

const strToNumberBetweenZeroAndOne = (message: string): number => {
  const hashHex = CryptoJS.SHA256(message).toString()

  const maxHex = 'f'.repeat(64)
  return parseInt(hashHex, 16) / parseInt(maxHex, 16)
}

内容は PHP と特に変わりませんが、 SHA256 のハッシュ化を行うために crypto-js というライブラリを利用しています。

検証

実際にランダムな文字列を入力にメソッドの出力値 3 を集計してヒストグラムを作成してみると、おおよそ一様分布になっていることが分かります。

メソッドの出力値を集計して作成したヒストグラム


  1. 統計学に関しては門外漢なので、数学的に厳密な連続一様分布がどう表されるべきかは特に考慮しておらず、 A/B テストのような用途で実用性を損なわない精度の出力が得られることを目指しています。また統計学の用語の使い方が誤っている等あれば編集リクエストをいただけると助かります! 

  2. 単に強度や処理速度などの要件に応じて他のハッシュ関数を用いても構いません。 

  3. ここではヒストグラムの作成用に出力値を100倍して小数点を切り捨てています。 

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

なぜ我々は頑なにforを避けるのか

動機

前回の記事を投稿したことを某SNSで通知したところ、そのSNSでこんなコメントをいただいた。転記する許可を取ったわけでは無いので私なりに要約させていただくと、

なぜそんなトリッキーな書き方をしてまでforを使うのを避けるのか
そんな書き方をして可読性を下げるくらいなら素直にforを使う方が良い

ということだと理解している。

なるほど、一理ありそうだ。しかし一方で、前回貼ったStackOverflowのQ&Aはなかなかの人気記事(質問に1243ポイント、回答に最大で1559ポイント)なので「多少トリッキーなことをしてでもforを書きたくない!!」という意見をもつプログラマも一定以上いるのだろう。当然私もその1人だ。

ということで、この記事で「なぜそこまで意固地になってまでforを書きたくないのか」を説明することにする。

尚、今回は前回の記事つながりで言語はJavaScriptを使うが、基本的にはどの言語でも同じだと思って欲しい。

理由1 そもそもforの構文って結構複雑

こんな配列があるとする。

const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S:スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

この配列を元にジョーカーを除いたトランプ52枚すべてを標準出力(console.log)したい。
例えば、ハートの2なら"H:2"、スペードのキングなら"S:K"と出力する。

forを使って素直に書くならこんな感じだろうか。

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
  }
}

初学者だった時やしばらくプログラミングから離れて久々に書いた時のことを思い出してほしい。

あれ、i < suites.lengthでいいんだっけ? i < suites.length - 1だっけ?
それとも、i <= suites.lengthだっけ??

こんなこと悩んだ経験はないだろうか?

初学者だけではなく、ベテランであっても仕事が立て込んでいて疲れていればi <= suites.lengthと誤って書いてしまうかも知れない。
貴方はソースレビューでこのバグを発見できる自信があるだろうか?テストフェーズで発見できれば良いが、うまく境界値がテストパターンからすり抜けてしまったら...?

一方で、forを避けてArray#forEachで実装した例を見てみよう。

suites.forEach(suite => nums.forEach(num => console.log(suite + ":" + num)));

どうだろう?少なくともforEachの文法を覚えていれば、<<=かといった問題やlength - 1かどうかといった問題に悩まされることも疲れていて間違えてしまうことも無い。ソースレビューで誰がどう読んだってsuitesの要素数とnumsの要素数分ループするのは明らかである。

前者の例と後者の例、どちらが安全だろうか?
forとは本質的にバグりやすい複雑な構文なのである。

理由2 配列に添字でアクセスしたくない

同じサンプルを再掲する。

const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S:スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
  }
}

今度は、

console.log(suites[i] + ":" + nums[j]);

に注目して欲しい。

今回はたまたまsuitesの添字をiに、numsの添字をjにしてループしたのだが、両者を取り違えてsuites[j]nums[i]にアクセスしてしまう可能性はどれくらいあるだろか?

「いやいや、馬鹿にするな!それくらい覚えてるよ!!」

と言えるのは、今回のサンプルがたまたま短くて単純だからだ。

実際の現場で書くソースコードはもっと長くてもっと複雑だ。もちろん、常に短くてシンプルなコードを書くという心意気は大事だが、現実にはそうはならないことだって多々あるのも事実だ。

その前提でもう一度尋ねる。貴方は添字を取り違えたりしないだろうか?
添字を取違いが起きていることをソースレビューで指摘できるだろうか?

Array#forEachで書く例も再掲する。

suites.forEach(suite => nums.forEach(num => console.log(suite + ":" + num)));

こちらではそもそも各要素に添字でアクセスする代わりにsuitenumのような関数への引数を通じてアクセスしている。添字の取り違えなんて起きようがない。

理由3 副作用がないことを保証したい

極端な例で恐縮だが、

const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S:スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
    i = 0;
  }
}

こんなコードを書いたらi = 0;のおかげでこのループは無限ループになってしまう。

「当たり前だし、こんなことをわざとやる奴はいない」って?

ではこんなパターンはどうだろう。

const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S:スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
    i++;
    j++;
  }
}

これなら、徹夜明けか何かで疲れていて、「自分はwhileでループを書いている」と錯覚していれば起こり得そうだ。
わざとやる奴はいない?そりゃそうだ。わざとじゃないからバグなんだ。

やる奴がいるかどうかが問題なのではなくて、起こり得てしまうことが問題な訳で。

繰り返しになるが、このサンプルのような短いシンプルなコードでforを使っているのが問題なのではない。実戦で書くもっと長くて複雑なコードで問題が表面化する。

本当は書きたくも無いし読みたくも無いのだが、実戦ではこんなコードはざらに存在する。

for(let i = 0; i < arr1.length; i++){
    /**
    * なんか100行くらいの処理
    */

    //途中で多重ループに入る。。。
    for(let j = 0; j < arr2.length; j++){
        //このループの中もとても長い。。
    }

    /**
    * さらに100行くらい。。
    */
}

このループが安全であることを確かめるために200行以上のソースを読まなければいけないのが大変な訳だ。
せめてforEachを使ってくれていれば少なくともループは安全に回っていそうというのが一目で分かる。この心理的効果が意外と大きい。

forEachを使うとどうなるか。

suites.forEach(suite => nums.forEach(num => console.log(suite + ":" + num)));

そもそもijがいないのでi++;などをやりようが無いというのもあるし、仮に何らかの事情で添字を使う場合でも、

suites.forEach((suite, i) => nums.forEach((num, j) => {
    console.log(suite + ":" + num);
    i++;//関数の引数であるiがインクリメントされるだけなのでループは普通に回る
}));

このように仮に間違えがあってもループそのものは安全である。添字でのアクセスを避けているのでOutOfIndexにならないのも大きい。

理由4 結果の形や処理・変換の意図をより明確化したい

少し要求を変えて、trumpという変数(もしくは定数)に['H:A','H:2',...,'S:K']という配列を格納したい場合。

forで書くとこう。

let trump = [];
for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    trump.push(suites[i] + ":" + nums[j]);
  }
}

forを避けて、今回はforEachではなくmapで書くとこうなる。

const trump = suites.map(suite => nums.map(num => suite + ":" + num)).flat();

例によって、この程度の長さのシンプルなロジックであれば大した差は無い。問題は、処理が複雑化した時だ。

let result = [];
for(let i = 0; i < arr1.length; i++){
  for(let j = 0; j < arr2.length; j++){
    /**
    * なんか色々処理。分岐もあったりとか。
    */
    result.push[somevalue];
  }
}

この処理を見て、最終的なresultの形を予想できるだろうか?
arr1.length * arr2.lengthの配列になる?
いやいや、途中の処理でbreak;してたら?分岐の中にresult#pushしないパスがあったら?
もっと言えば、プログラマがその複雑な処理に気を取られて、うっかりresult.push[somevalue];を書くのを忘れていたら?
もっと極端なこと言えば、途中でresult = undefined;なんて代入がある可能性すらある。

ここにあげた例のようなことが「無い」って証明するためには、forの中身をいちいち全部読まなければいけない。
これはしんどい。

というかそもそも、この形を見た時に、

このプログラマはresultにarr1とarr2から生成した新たな配列を格納しようとしている

って読み取れるだろうか?読み取れるのはおそらくソースを全部読んだあとだろう。

一方で、

const result = arr1.map(valOfArr1 => arr2.map(valOfArr2 => {
    /**
    * なんか色々処理。分岐もあったりとか。
    */
    return somevalue;
})).flat();

この場合は、resultの中身が長さarr1.length * arr2.lengthの配列であることが保証される。

確かに、jsにおいてはreturnを忘れて、全て値がundefinedになっている可能性はある。
しかし、TypeScriptであれば型チェックに引っかかるのでreturn忘れを事前に気づくことができる。
上のforの例でresult.push(someValue);を忘れた場合は型チェックでは引っかからない。

それよりも何よりも、こちらの方がプログラマが「arr1とarr2を組み合わせて新しい配列を作ろうとしている」という意図が明確になる。

これがかなり重要で、意図が明確かつ結果が保証されているかどうかで、

    /**
    * なんか色々処理。分岐もあったりとか。
    */

を読むストレスが全然違う。これは大きい。

(ちょっと閑話休題)可読性って何だろうね

こういう議論をしているとたまに「可読性」の定義が合わない人がいる。

例えば、

初心者でも(誰でも)読みやすく書くのが可読性の向上だ

という人がいるが、それは違うと思う。

その理屈が通るのであれば、日本語から漢字を撤廃して全てひらがなに統一すべきだ。その方が初心者である幼児や勉強初めたての外国人の方にとってわかりやすい。そうならないのは何故だろうか。

「読める人の多さ」と「表記としての効率性」はトレードオフだ。
幼稚園児に読ませる文章を書くときは全てひらがなで書くべきだが、それを大体の日本人の大人相手にやっていたら効率が悪い。

もっと言えば、例えばある程度オブジェクト指向を勉強した相手に、

ここもうちょっとカプセル化しない?

って提案するのはシンプルでわかりやすいが、プログラミング覚えたての初心者には伝わらない。

このsetterにnull突っ込んだらどうなると思う?
インスタンス化したあと、setHoge()を呼ばずにexecuteFoo()を読んだらエラーになるよね?ちょっと工夫してみようよ。

というように噛み砕いて伝えなければいけない。「効率」を考えた時どちらの方が良いだろうか。

可読性の向上というのは、

想定読者のレベルをある一定だと想定して、問題なく読めてかつ効率的な表記を目指す

ことだと思う。

例えば、初心者に教えている際に

[...Array(100).keys()].map(n => n + 1).map(n => n % 15 === 0 ? 'FizzBuzz' : n % 3 === 0 ? 'Fizz' : n % 5 === 0 ? 'Buzz' : n.toString()).forEach(s => console.log(s));

こんなワンライナーを書けるようになりましょうね

って言ったら流石にバカだと思う。ちゃんとforで回すループを教えるべきだ。
(というか上級者相手でもここまでのワンライナーはほぼ"遊び"だろうが。。)

でも、ある程度ES6を勉強している向けに、

const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S:スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字


const trump = suites.map(suite => nums.map(num => suite + ":" + num)).flat();

このコードを書くのはむしろ読みやすいし、何より(今まで説明した通り)安全な記述だ。

前回の記事でいう、

const N = 100;
[...Array(N).keys()].forEach(i => {
   //...なんか処理(iには0...N-1(=99)が格納される)
});

というハックはそのあたりのバランスが確かに微妙だとは思う。
人によっては、「可読性を損なうデメリットの方が大きい」という人もいるだろう。
もうこれ以上は宗教戦争の域に入りそうだが、それでも私は僅差でforを避けるメリットの方を推したい。

それでもforを勉強すべき理由

さて、散々forの悪口を言っていたのだが、だからと言ってfor(やwhile)を勉強しなくても良い理由にはならない。きちんと勉強すべきだ。

理由は以下の通りだ。

1.様々な言語に対応できる

近頃は大体の言語で配列のmap/filter/reduceなり拡張forなりをサポートしている。あのJavaですら(失礼?)近年ではStreamAPIを導入して関数型っぽい記述をできるようになっている。

ただ、例えば私が知る限りでは例えばC言語には拡張forやmap/filter/reduceなどの組み込み関数は無いように思う。

基本的なforwhileはそれぞれの言語で方言はあれどほぼ全ての言語で使える文法のはずなので、やはり勉強しておくべきである。

2.どうしても使わなければならない/使った方がわかりやすいロジックやアルゴリズムに直面することがある

例えば、ソートやシャッフルなど。

下記はこちらから引用させていただいたフィッシャー・イエーツシャッフルの実装だが、

const shuffle = ([...array]) => {
  for (let i = array.length - 1; i >= 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}

このように添字を使ってアクセスした方がはるかにわかりやすいパターンがある。

こういったアルゴリズムをわざわざforを避けて書くのはあまり賢い選択では無いだろう。

3.漏れのある抽象化の法則

本記事の冒頭で書いたSNSで議論させていただいた相手に教えて頂いた(不勉強ながら初めて知りました。。ありがとうございます!)
かの有名なJoel on Softwareの一節で、この記事の文脈に沿って要約すると、

Array#forEachやArray#mapを使っていて何か問題が生じた時、forやwhileの書き方を知らなかったら問題解決できないよね?

ということらしい。
それは本当にその通りだと思う。

わかりやすい例で言えば、Array#forEachではbreak;によるループからの離脱ができない。
どうしてもbreak;が必要な場合はforwhileに書き換える必要があるが、裏を返せばそれができないレベルのプログラマにはArray#forEachを使いこなすのが難しいということだ。

まとめ:forを"ダサい"と表現したのは軽率だったと反省している

前回の記事で、典型的なfor文を書いて

こんなダサいループを回したく無い

と書いた。

正直、記事冒頭で読み手を惹きつけるためのネタのつもりで書いたのだが、軽率であったと反省している。

そんなことを書いたせいかどうかはわからないが、前回紹介したようなハックに対して

わざとトリッキーに書いて頭いいぶってカッコつけている

という印象を持たれてしまったようだ。それははっきり言って心外だ。

前述した通り、確かに

const N = 100;
[...Array(N).keys()].forEach(i => {
   //...なんか処理(iには0...N-1(=99)が格納される)
});

は可読性的には微妙なラインだ。
でも、それを天秤にかけてもforを避けたい理由がキチンとある。この記事に書いた通り、実利的な理由だ。

前回も引用したStackOverflowの質問者さんの質問文に、

To me it feels like there should be a way of doing this without the loop.
意訳: ループをしないでやる何かうまい方法があるような気がするんだ

と書いてある。
私もそれを思っていた。でも私は、こういった局面で何も工夫をせずに妥協してforを使っていた。

それを妥協しないでちゃんと質問した質問者さんも、それに見事なハックで答えた回答者さんも本当にかっこいいと思う。
"カッコつけている"わけではなくて。

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

サクラエディタで指定した文字数を出力するマクロを作ってみた。

はじめに

入力値のパターンの試験で、色々のパターンの文字列を準備する際に、動的に出力できたら便利だなと思いさくらエディタのマクロを作成してみました。
全角半角混在のパターンなど、まだまだ改良の余地はあるのでブラッシュアップしていきたいと思います。

実行結果

全角文字(5文字)出力の場合

9月-22-2020 17-02-36.gif

全角文字(15文字)出力の場合

9月-22-2020 17-03-46.gif

半角文字(25文字)出力の場合

9月-22-2020 17-19-41.gif

全角記号(128文字)出力の場合

9月-22-2020 17-08-41.gif

半角記号(256文字)出力の場合

9月-22-2020 17-10-41.gif

ソース

OutpurStr.js
// ====================
// =main処理
// ====================
// 出力用全角文字
var arrayForZenStrOutput = ['','','','',''];
// 出力用全角文字(5文字区切り)
var arrayForZenStrOutputLen5 = ['あああああ','いいいいい','ううううう','えええええ','おおおおお'];
// 出力用半角文字
var arrayForHanStrOutput = ['f','g','h','i','j'];
// 出力用半角文字(5文字区切り)
var arrayForHanStrOutputLen5 = ['aaaaa','bbbbb','ccccc','ddddd','eeeee'];
// 出力用半角記号
var arrayForZenSignOutput = ['','','','',''];
// 出力用半角記号(5文字区切り)
var arrayForZenSignOutputLen5 = ['!!!!!','+++++','?????','「「「「「','ーーーーー'];
// 出力用全角記号
var arrayForHanSignOutput = [':','*','@','<','>'];
// 出力用全角記号(5文字区切り)
var arrayForHanSignOutputLen5 = ['?????','+++++','[[[[[','-----',']]]]]'];

// Jscriptはvarのみの定義となるが定数っぽく
// 全角文字出力パターン
var ZEN_STR_OUTPUT_PTN = '0';
// 半角文字出力パターン
var HAN_STR_OUTPUT_PTN = '1';
// 全角記号出力パターン
var ZEN_SING_OUTPUT_PTN = '2';
// 半角記号出力パターン
var HAN_SING_OUTPUT_PTN = '3';

// 指定行に記載した数値を取得
var num = parseInt(Editor.GetLineStr(0));
// 改行 引数は文字コードだが、13は現在の入力改行コード指定により変換されるらしい
Char(13); 
// 出力パターン選択
var outputPtn = InputBox('全角文字:0 | 半角文字:1 | 全角記号:2 | 半角記号:3 ','',1);
// 出力パターン判定
switch(outputPtn) {
    // 全角文字出力パターン
    case ZEN_STR_OUTPUT_PTN:
        outputStr(num, arrayForZenStrOutput, arrayForZenStrOutputLen5);
        break;
    // 半角文字出力パターン
    case HAN_STR_OUTPUT_PTN:
        outputStr(num, arrayForHanStrOutput, arrayForHanStrOutputLen5);
        break;
    // 全角記号出力パターン
    case ZEN_SING_OUTPUT_PTN:
        outputStr(num, arrayForZenSignOutput, arrayForZenSignOutputLen5);
        break;
    // 半角記号出力パターン
    case HAN_SING_OUTPUT_PTN:
        outputStr(num, arrayForHanSignOutput, arrayForHanSignOutputLen5);
        break;
}

// ====================
// =出力関数
// ====================
function outputStr(num, arrayForStrOutput, arrayForStrOutputLen5) {
    if(num <= arrayForStrOutput.length){
        // 1文字ー4文字以内の場合
        for(var i = 0; i < num; i++){
            Editor.InsText(arrayForStrOutput[i]);
        }
    } else {
        // 5文字以上の場合
        // 同一文字列(5文字区切り) + 端数分の文字列を出力

        // 商
        var quotient = Math.floor(num/arrayForStrOutput.length);
        // 余り
        var remainder = num % arrayForStrOutput.length;

        if (quotient < arrayForStrOutput.length) {
            // 5文字ー25文字以内の場合
            for(var i = 0; i < quotient; i++) {
                Editor.InsText(arrayForStrOutputLen5[i]);
            } 
        } else {
            // 26文字以上の場合
            // 商
            var quotient2 = Math.floor(quotient/arrayForStrOutput.length);
            // 余り
            var remainder2 = quotient % arrayForStrOutput.length;
            for (var j = 0; j < quotient2; j++) {
                for(var i = 0; i < arrayForStrOutput.length; i++) {
                    Editor.InsText(arrayForStrOutputLen5[i]);
                }
            }
            for(var i = 0; i < remainder2; i++){
                Editor.InsText(arrayForStrOutputLen5[i]);
            }
        }
        // 端数分の文字列を出力
        for(var i = 0; i < remainder; i++){
            Editor.InsText(arrayForStrOutput[i]);
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript addEventListenerで引数渡したいしremoveもしたい!

環境
初書:2020/09/22
PC:macOS 10.15.6

前置き

javascriptで、addEventListenerを使いたい時に、引数を渡したいと思ったが、同時にremoveEventListenerで削除もしたい時(タイトルの通り)にどうするのか迷ったのでメモ。

前提

IEは知らない

addEventListenerで引数を渡す

これは調べたら比較的すぐに出てくるので、簡単に実装できる(と思う)
参考サイト:【JavaScript】addEventListenerで関数に引数をわたす|北の南|note

index.html
<div id="youso">div要素</div>
<script>
const id = document.querySelector("#youso");
id.addEventListener('click', {arg: "aa", handleEvent: fuc});
handleEvent: fuc});
function fuc(){
    console.log(this.arg); //"aa"
}
</script>

・・・引数という表現でいいのか知らないがとりあえず渡すことはできた。
addEventListenerの第二引数に、呼び出す関数を入れたhandleEventと、その他引数名と値を入れたオブジェクトを渡すことによって、呼び出した関数に値を渡すことが可能。
注意点は、関数内のthisが、addEventListenerの第二引数に全て置き換わる。

removeEventListenerでイベントを消す

普通にやる場合は特に難しいことはなく、removeEventListenerを呼ぶだけでできる。
ただ、上記の方法で追加した場合は、

index.html
<div id="youso">div要素</div>
<script>
const id = document.querySelector("#youso");
id.addEventListener('click', {arg: "aa", handleEvent: fuc});
function fuc(){
    console.log(this.arg); // "aa"
    id.removeEventListener('click', {arg: "aa", handleEvent: fuc});
}
</script>

この方法では削除ができない。
理由は単純で、第二引数のオブジェクトが、{arg: "aa", handleEvent: fuc}==={arg: "aa", handleEvent: fuc} =falseになるからである。
ということは、第二引数のオブジェクトがtrueになるようにすればいい。
無名関数ではないのだが、似たような方法がこちらに記載されていたので、参考にしながら付け足してみる。(そして関数化する)

index.html
<div id="youso">div要素</div>
<script>
let counter = 0;
const eventlist = [];

const num = addlistener("#youso", "click", fuc, {arg :"aa"});
function fuc() {
    console.log(this.arg); // "aa"
    removelistener(num);
}

function addlistener(selectors, type, handleEvent, arg = {}, option = null) {
    counter++;
    const eventlistner = {handleEvent, ...arg};
    const array = {counter, selectors, type, eventlistner, option};
    eventlist.push(array);
    const docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.addEventListener(type, eventlistner, option);
    }
    return counter;
}
function removelistener(num) {
    const find_in = (e) => e.counter === num;
    const value = eventlist.findIndex(find_in);
    const {selectors, type, eventlistner, option} = eventlist[value];
    const docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.removeEventListener(type, eventlistner, option);
    }
}
</script>

このように、addEventListenerの第二引数を保存しておき、removeEventListenerに同じオブジェクトを渡すことで、削除できる。

関数の使い方について
addlistener関数でイベントを追加し、addlistenerの戻り値で得た数値をremovelistenerに渡すことで削除できる。
addlistener関数の引数は、
selectors = querySelectorAllの引数,
type = addEventListenerの第一引数,
handleEvent = addEventListenerの第二引数,
arg = 渡したい引数,
option = addEventListenerの第三引数

引数は引数として渡したい

一応上記コードで表題はクリアしているのだが、一つ気になるとすれば、引数が引数として受け取れないこと。
現状引数を受け取るのは、this.xxxという形を取らないといけないので、これをクリアしたい。

ということで、addEventListenerと呼び出す関数の間にワンクッション挟むことで、これを実現していこうと思う。

index.html
<div id="youso">div要素</div>
<script>
let counter = 0;
const eventlist = [];

const num = addlistener("#youso", "click", fuc, ["aa"]);
function fuc(e,arg) {
    console.log(this); //windowオブジェクト
    console.log(e); // mouseeventオブジェクト
    console.log(arg); // "aa"
    removelistener(num);
}

function receivelistener(e) {
    let arg = this.arg_eve.arg;
    let func = this.arg_eve.handleEvent;
    if (!Array.isArray(arg)) {
        arg = [];
    }
    return func.call(window,e, ...arg);
};

function addlistener(selectors, type, handleEvent, arg = [], option = null) {
    counter++;
    let eventlistner = {
        arg_eve: { arg, handleEvent },
        handleEvent: receivelistener,
    };
    const array = {counter, selectors, type, eventlistner, option};
    eventlist.push(array);
    var docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.addEventListener(type, eventlistner, option);
    }
    return counter;
}
function removelistener(num) {
    const find_in = (e) => e.counter === num;
    const value = eventlist.findIndex(find_in);
    const {selectors, type, eventlistner, option} = eventlist[value];
    var docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.removeEventListener(type, eventlistner, option);
    }
}
</script>

ワンクッションとしてreceivelistener関数を用意。
thisとして受け取らないといけなかった引数を、この中で変換し引数として渡す。
また、addEventListener関数の第二引数に関数を直接渡す方法だと、受け取った関数のthiswindowオブジェクトを返すので、.callを使うことでthisにwindowオブジェクトを渡している。(あと第一引数は勿論mouseevent)

使い方としては、const num = addlistener("#youso", "click", fuc, ["aa"]);のように、第一引数にイベントを追加する要素のCSSセレクター、第二引数にイベントの種類、第三引数に呼び出す関数、第四引数に渡したい引数の配列、第五引数にaddEventListenerのオプションを渡す。
イベントを消したい時は、addlistenerの戻り値をremovelistenerに渡すだけ。
また、呼び出された側の関数は、第一引数にmouseevent、第二引数以降に渡された引数がそのまま入る。

終わりに

唯一の問題は、removeをする際に一つずつは消せないこと
あと型チェックは入れていないので、その辺は注意かもしれない

参考サイト

【JavaScript】addEventListenerで関数に引数をわたす|北の南|note
【JavaScript】addEventListenerの無名関数をremoveEventListenerで消す方法 | Web活

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

React触ったことない人間が、Next.jsでアプリ作成し高速でVercelにデプロイするまで

■はじめに

・目的はNext.jsの大枠に触れてみるというところです。
 ※学習コストの低いVueを使ってポートフォリオを作成したもののNext.jsが気になりすぎて:wink:
 ※そのため、アプリ作成と書いてはいますが、アプリの具体的な中身はほぼ空っぽです。
 ※今後、簡単なプロフィールサイトを作成していく予定です

■デプロイ先

・Vercel(https://profsite.vercel.app/)

■手順

1.環境構築

・next.jsのプロジェクト作成

npx create-next-app profsite

・ターミナルでプロジェクトディレクトリに移動

cd profsite

2.node.jsのインストール(ver10.13 以上必要)

・バージョン確認

node -v

※未インストールの場合(こちらの記事がとてもわかりやすいです。)
 https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

3.ローカルサーバー起動

・コンパイル

npm run dev

http://localhost:3000へアクセスするとトップ画面が表示される

4.Editorでトップ画面を少し編集してみる

・/pages/index.jsを編集すると、トップ画面が変更される(pages/index.js = ルート)
代替テキスト

5.新しくディレクトリを切ってみる

・pages/skills/my-skills.jsを作成(自身のスキルセットを表示するページを想定)

export default function showSkills() {
 return (
  <h1>My Skills</h1>
)}

http://localhost:3000/skills/my-skillsへアクセスすると表示される
 つまり、作成したディレクトリ構造がそのままURLになる

【ブラウザ】
代替テキスト

6.クライアントサイドナビゲーション

・ページ遷移をクライアント側のjavascript処理で実行し、サーバには通信を行わないのでサクサク:relaxed:
・リンクコンポーネントを使用する必要があるので、index.jsに読み込む 
【index.js】

import Link from 'next/link'

export default function Home() {
 return (
  <Link href="/skills/my-skills"><a>My Skills</a></Link>
)}

※余談

Adjacent JSX elements must be wrapped in an enclosing tag

・エラーがでてしまった
コンポーネントを以下のように、div等の要素で囲んで上げる必要がある
(Reactで作成するコンポーネントは、 トップレベル(一番親となる階層)の要素を
一つにする必要があるため)

【index.js】

import Link from 'next/link'

export default function Home() {
 return (
  <div>
   <Link href="/skills/my-profile"><a>My Profile</a></Link>
   <Link href="/skills/my-works"><a>My Works</a></Link>
   <Link href="/skills/my-skills"><a>My Skills</a></Link>
   <Link href="/skills/contact"><a>Contact</a></Link>
  </div>
)}

【ブラウザ】
お粗末ですが、ヘッダーメニュー
代替テキスト

リンクをクリックするとクライアントサイドナビゲーション(ページ遷移をクライアント側のjavascript処理で実行し、サーバには通信を行わない)が動作するので、サクサクです。

【ブラウザ】
静止画だと伝わりにくいですが…
代替テキスト

7.画像ファイルの読み込み

・publicフォルダでurl(src)を管理している
・publicをルートとしたルートパスで記載する
代替テキスト
【my-profile.js】

export default function showProfiles() {
 return (
  <div>
   <h1>My Profile</h1>
   <div>
    <img src="/myface.JPG" alt="私の画像" />
   </div>
  </div>
)}

8.メタデータ編集

・headコンポーネントを使用することで、headタグをページごとに設定できる
・下記は一例
【index.js】

import Link from 'next/link'
import Head from 'next/head'

export default function Home() {
 return (
  <div>
   <Head>
    <title>home</title>
   </Head>
   <Link href="/profile/my-profile"><a>My Profile</a></Link>
   <Link href="/works/my-works"><a>/My Works</a></Link>
   <Link href="/skills/my-skills"><a>/My Skills</a></Link>
   <Link href="/contact/contact"><a>/Contact</a></Link>
  </div>
)}

【ブラウザ(titleは"home")】
代替テキスト

【my-profile.js】

import Head from 'next/head'

 export default function showProfiles() {
  return (
   <div>
    <Head>
     <title>my profile</title>
    </Head>
    <h1>My Profile</h1>
    <div>
     <img src="/myface.JPG" alt="私の画像" />
    </div>
   </div>
)}

【ブラウザ(titleは"my profile")】
代替テキスト

9.cssでレイアウトを変更してみる

・小さなcss modulesを作成後、layoutコンポーネントを対象のjsコンポーネントにラップすることで、
 cssを適用することが可能
・cssモジュールは基本、機能ごとに分ける。(文字のサイズ、フォントを整えるためのモジュール、画像のサイズを整えるためのモジュール等)
・ルートにcomponentsディレクトリを作成しlayout.jsを配置
 /components/layout.js

【layout.js】
※childrenはreactの特別なpropsの書き方(Layoutコンポーネントで囲まれた要素を返す)

export default function Layout({ children }) {
 return (
  <div>{children }</div>
)}

・コンポーネント(どれでも良い。例としてmy-profile.js)をLayoutコンポーネントでラップする

【my-profile.js】

import Head from 'next/head'
import Layout from '../../components/layout'

export default function showProfiles() {
 return (
  <div>
   <Layout>
    <Head>
     <title>my profile</title>
    </Head>
    <h1>My Profile</h1>
    <div>
     <img src="/myface.JPG" alt="私の画像" />
    </div>
   </Layout>
  </div>
)}

・上記だけだとdivで囲んだだけなので、css moduleを使ってレイアウトを変更する
・/componentsディレクトリにlayout.module.cssを作成(※拡張子は必ず.module.css)
【layout.module.css】

.container {
 max-width: 36rem;
 padding: 0 1rem;
 margin: 3rem auto 6rem;
}

・layout.module.cssを読み込んで、layout.jsのclassNameに適用する
【layout.js】

import styles from './layout.module.css'

export default function Layout({ children }) {
 return (
  <div className={ styles.container }>
   { children }
  </div>
)}

【ブラウザ】
・Layoutコンポーネントにラップされている要素にcssが適用される
代替テキスト
※class名がユニークな値になる仕様
代替テキスト

10.グローバルcssを適用する

app.js(pages/app.js)にimportしたcssは全てのコンポーネントに適用される
・大元のglobals.cssは
app.jsでのみimportすることができる
・つまり、globals.cssの内容を
app.jsにimportすれば全コンポーネントにcssが適用されるということ

【_app.js】

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
 return <Component {...pageProps} />
}
export default MyApp

【styles/globals.css】
・わかりやすいように、テキストカラーを赤に。

html,
body {
 color:red;
 padding: 0;
 margin: 0;
 font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
 Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
 color: inherit;
 text-decoration: none;
}
* {
 box-sizing: border-box;
}

【ブラウザ】
全てのページのテキストカラーが赤に。
代替テキスト
代替テキスト
代替テキスト

11.gitにpush

いつものやつなので、割愛。。
・githubでリポジトリ作成
・gitにpush

cd profsite
git init
git remote add origin https://github.com/masayan1126/profsite.git
git remote -v 
git add .
git commit -m ""
git push -u origin master

12.Vercel(バーサル)へデプロイ

・初期設定でデプロイすると、static generation(コンパイル時にHTML生成)が適用される
・カスタムドメイン(取得した有料ドメインを使用することも可能)
・自動でSSL化されている(すご:joy:)
・アカウント作成
 ここからは画面の指示通りで簡単な手順なので詳細は割愛します。
 githubのアカウントサインインし、あとはプロジェクト名とか、取り込み対象の
リポジトリを選択してデプロイして完了です。
【リポジトリのインポート】
代替テキスト
【デプロイ完了】
代替テキスト

■最後に

・Next.jsを作られた方、天才すぎます!!:sob:
・所要時間は2時間くらいで、この記事を書く時間よりも短かったです。(笑)

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

Nuxtjsで静的サイト運用時、レイアウト変更したページで2回マウントされてた問題

  • WebサービスのフロントでNuxtjs(v2.14)を使った。
  • 複数のlayoutを使い分ける必要があったため、ファイルを作成し、pageファイル内で指定した。
  • page内で、マウント時に外部サービスへデータをpost(記録)する処理を書いた。
  • 公開はnuxt generateで静的なサイトを作成し、それをアップした。
  • 実際に公開ページにアクセスして処理を走らせてみる
  • 外部サービスのログを見ると、データが2回処理されている???←問題発覚
  • PC、android端末では同様の現象は起きていなかった。

より細かく動作を見ていくと、
iPhone端末のsafariで
Nuxtjsのライフサイクルが二周している。

Nuxtjsのgithubを見ると過去に似たような問題が挙がっていた。
https://github.com/nuxt/nuxt.js/issues/5703

記事中であるように、
「this.$nuxt.layoutName」が一旦defaultで表示されたのち、
指定したものに切り替わっていた。

試しにレイアウトの指定を解除、
または指定をdefaultにすると、問題は起きなくなった。

また、記事中にある「window.〇〇」に処理の経過を記録するやり方の流用で
非同期でのデータ取得をstoreに移し、
データ取得処理の進行状態をstateで記録するようにすると、
二回目の取得処理を回避できるため、
当面の問題(二重記録)はクリアできた。
(uglyな手法って書かれてるけど)

割とぶつかりそうなネタだけど、Qiitaで見当たらなかったので初投稿テストです。

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

JavaScriptで配列をsortする方法

この記事を書いた理由

JavaScriptを勉強したばかりの時期に、あれ?配列をソートするにはどうしたら良いんだ?
と考える機会が増えてくると思います。以下に、昇順と降順のパターンを羅列してます。今回はidを対象に並び替えを実施してますが、名前を対象にすることも可能なので、少し面倒なやり方を選びました。


まずは、配列を作るところからスタート!

main.js
/*idとnameが含まれている配列*/
let arry = [
  { id: 5, name: "ayaka" },
  { id: 7, name: "uadagawa" },
  { id: 8, name: "katou" },
  { id: 3, name: "kentaro" }
];

❶:配列のidを降順で表示させる場合

main.js
arry.sort(function (a, b) {
  if (b.id > a.id) return 1;
  else return -1;
});
document.write(JSON.stringify(arry));

/*結果
[{"id":8,"name":"katou"},
{"id":7,"name":"uadagawa"},
{"id":5,"name":"ayaka"},
{"id":3,"name":"kentaro"}]
*/

❷:配列のidを昇順で表示させる場合

main.js
arry.sort(function (a, b) {
  if (b.id < a.id) return 1;
  else return -1;
});
document.write(JSON.stringify(arry));

/*結果
[{"id":3,"name":"kentaro"},
{"id":5,"name":"ayaka"},
{"id":7,"name":"uadagawa"},
{"id":8,"name":"katou"}]
*/

理屈としては、returnで1や−1と返している部分は、idの並び順です。

ex) 1,3なら、1が1番目に来て、3が2番目に来るって感じ。昇順ならね。
returnの1なんだけど、個人的には+1だと思ってるw

最後に、document.writeの部分は、console.logにしても良いんじゃないのか
という意見もあると思う。

main.js
document.write(JSON.stringify(arry));
/*ここは、console.logに変更してもらっても構わない。*/

codesandboxで、console.logもしくはdocument.writeを記述しても[object][object].......となってしまうのを防ぐために、上記の書き方をしています。jsonパッケージが入ってる場合、少し厄介ですね。


初学者からの視点で記述したので、誰かの勉強の手助けになれば嬉しいです!では!

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

React: コンポーネントのロジックをカスタムフックに切り出す ー カウンターの作例で

Reactにフックが採り入れられて、関数コンポーネントに状態をもたせられるようになりました。

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

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

本稿は簡単なカウンターの作例をとおして、フックの役割や考え方について解説します。

Create React Appでアプリケーションのひな形をつくる

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

npx create-react-app react-custom-hook

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

useStateとプロパティ(props)でカウンターをつくる

まずは、カスタムフックは用いず、useStateとプロパティ(props)によりカウンターをつくりました(コード001)。

アプリケーションのモジュールsrc/App.jsuseStateで状態変数(count)を定め、関数としてはカウンターの減算(decrement())と加算(increment())が備わっています。それらをプロパティ(counter)として受け取るのが、このあと定めるカウンター表示のコンポーネントCounterDisplayです。

コード001■useStateとプロパティを用いたアプリケーションモジュール

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

const initialCount = 0;
function App() {
    const [count, setCount] = useState(initialCount);
    const decrement = () => setCount(count - 1);
    const increment = () => setCount(count + 1);
    return (
    <div className="App">
      <CounterDisplay counter={{ count, decrement, increment }} />
    </div>
    );
}

export default App;

カウンター表示のモジュールsrc/CounterDisplay.jsは、アプリケーションから受け取ったプロパティ(counter)により、カウンタの値(counter.count)表示と減算(counter.decrement)・加算(counter.increment)の処理を行います(コード002)。

コード002■カウンター表示のモジュール

src/CounterDisplay.js
import React from "react";

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

これで簡単なカウンターができ上がりました(図001)。ただカウンターのカウントアップ・ダウンをするだけで、アプリケーションモジュールには、状態を使って何か行うという処理がありません。あとで加わるという想定にして、今回の作例からは省きました。また、CSS(src/index.csssrc/App.css)は、基本的なフォントや余白の設定のみです。確かめたい方は、最後に掲げるCodeSandboxの作例(サンプル001)をご覧ください。

図001■でき上がったカウンター

2009001_001.png

アプリケーションのロジックをカスタムフックに切り出す

アプリケーションモジュールsrc/App.jsのロジック、つまり状態変数とその処理関数を、このあと定めるカスタムフックに切り出しましょう。

src/App.js
// import React, { useState } from 'react';
import React from 'react';

// const initialCount = 0;
function App() {
    /* カスタムフックに切り出す
    const [count, setCount] = useState(initialCount);
    const decrement = () => setCount(count - 1);
    const increment = () => setCount(count + 1);
    */

}

つぎのコード003が、カウンターのカスタムフックのモジュール(src/useCounter.js)です。フックの名前はuseではじめるお約束になっています。状態が備えられ、フックも使えるのは、関数コンポーネントと同じです。違いとしては、JSXの要素を返さなくて構いません。戻り値は、カウンターの値(count)と減算(decrement())・加算(incfrement())の関数を収めたオブジェクトとしました。

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

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

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

カスタムフックを使う

アプリケーションモジュール(src/App.js)はカスタムフック(useCounter)の呼び出しにより、カウンターの状態を操作するための参照(countdecrementおよびincrement)が得られます。前掲コード001とプロパティ名を揃えましたので、カウンター表示のコンポーネント(CounterDisplay)に渡すプロパティやモジュール(src/CounterDisplay.js)のコードは書き替える必要がありません。

src/App.js
import { useCounter } from './useCounter';

function App() {
    const { count, decrement, increment } = useCounter();
    return (
    <div className="App">
      <CounterDisplay counter={{ count, decrement, increment }} />
    </div>
    );
}

アプリケーションモジュール(src/App.js)の記述全体は、つぎのコード004のとおりです。ロジックを切り離したので、表示に専念することになりました。作例をCodeSandboxに公開します(サンプル001)。

コード004■ロジックを切り離したアプリケーション

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

function App() {
    const { count, decrement, increment } = useCounter();
    return (
    <div className="App">
      <CounterDisplay counter={{ count, decrement, increment }} />
    </div>
    );
}

export default App;

サンプル001■カスタムフックを使ったカウンター

2009001_002.png
>> CodeSandboxへ

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

Electron(v10.1.2現在)の IPC 通信入門 - よりセキュアな方法への変遷

Electron における IPC 通信

Electron で Desktop アプリケーションを作るにあたって理解しなければならないのは、根幹を成す「IPC通信」かと思います。IPC は Inter-Process Communication、プロセス間通信の略です。

なぜ IPC 通信を使うのか? Electron のベースは、Chromium のため

Electron は、Chromium を使用しているため、Chromium のマルチプロセスアーキテクチャも使用されます。そのため Electron の各 Webページはそれぞれのプロセスで実行され、process というオブジェクトを持って処理を行っています。

Electron アプリケーションアーキテクチャ - Electron 公式ドキュメント
https://www.electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes

Electron のフレームワークの一部である、Node.js も、スレッドで処理するには、どうしても重い処理などはデッドロックを回避できません。そこで Node.js は、ネットワークアプリケーションにおいてすべての処理を「プロセス」として非同期で処理するというノンブロッキング I/O とイベントループという仕様が採用されました。

そのため、Node.js ベースの、Electron は、非同期の「プロセス間通信」の仕様がそのまま採用されています。

メインプロセスと、レンダラープロセス

メインプロセス

アプリケーションが開始されてから終了されるまでを制御します。1アプリケーションにつき、1プロセスしかなく、メインプロセスが複数のレンダラープロセスと通信を行うことでアプリケーションを制御します。また、各OSのネイティブ要素(Node.jsモジュール)の管理も担当します。

逆を言えば、レンダラープロセスから、アプリケーションの終了はできないし、OSのネイティブ要素に直接アクセスすることはできません。

メインプロセス (main process) - Electron 公式ドキュメント
https://www.electronjs.org/docs/glossary#%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9-main-process

レンダラープロセス

レンダラープロセスは、メインプロセスとちがって複数作ることができます。

主に「表示」や「GUIなどのインターフェース」を担当します。レンダラープロセス間同士での通信は行えず、常にメインプロセスを通じて通信することになります。

メインプロセスと各レンダラープロセスとの通信

今までやってきたレンダラープロセスからメインプロセスへの IPC 通信

まず、前提として、レンダラープロセスからメインプロセスへの通信を行うには、レンダラープロセスで Electron や、他の Node.js モジュールを動作させるための「設定」を行わないと行けません。

メインプロセスで、レンダラープロセス(表示側のウェブページ)を読み込むところで nodeIntegration=true としてあげる必要があります。

main.js
mainWindow = new BrowserWindow({
  width: 1024,
  height: 640,
  webPreferences: {
    nodeIntegration: true /** ココ **/
  },
});
// HTMLファイルをロードする
mainWindow.loadURL(`file://${__dirname}/index.html`)

nodeIntegration は、Electron v.5 からデフォルトが false となってしまったので、プログラマー側で有効 true にしてやる必要があります。なぜ、デフォルトが、false となってしまったのかは、セキュリティの問題からですが、これについては別の IPC 通信方法を提示しつつ後述します。

ipcRendererから、ipcMainへのIPC通信

ipcRendererからのipcMainへの通信

非同期で通信する方法で、メインプロセスのコード例は以下の通りです。

main.js
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {  // channel名は「asynchronous-message」
  console.log(arg)  // "ping"を表示
  event.reply('asynchronous-reply', 'pong')
})
index.js
// レンダラープロセス(ウェブページ)
const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-reply', (event, arg) => {
  console.log(arg) // "pong"を表示
})
ipcRenderer.send('asynchronous-message', 'ping')  //  channel名は「asynchronous-message」

上の例は、非同期通信の例ですが、もし同期通信を行いたい場合は、send() の部分を sendSync() に書き換えれば実現できます。

ただし、同期通信にした場合、メインプロセスが呼ばれている間は レンダラープロセス上の処理は完全にブロックされます。メインプロセスからの応答があるまでは、レンダラープロセス側の操作画面はいわゆるフリーズしたような状態になります。描画処理も止まるので、ローディング画面のような CSS アニメーションも容赦なく固まります。

ということから、よっぽどの理由がない限りは、非同期通信を使うことをオススメします。

ipcMain から、ipcRendererへのIPC送信

当然ですが、ipcMain から、ipcRenderer へデータ送信することもできます。

webContents.send(channel, ...args)
https://www.electronjs.org/docs/api/web-contents#contentssendchannel-args

イメージとしては、以下の図の通りです。

ipcMainからipcRendererへの送信

メインプロセス側のコードは、

main.js
// メインプロセス
const { app, BrowserWindow } = require('electron')
let win = null

app.whenReady().then(() => {
  win = new BrowserWindow({ width: 800, height: 600 })
  win.loadURL(`file://${__dirname}/index.html`)
  win.webContents.on('did-finish-load', () => {
    win.webContents.send('ping', 'whoooooooh!') // レンダラープロセスへsendしています
  })
})

受けるレンダラープロセス側はこう書きます。

index.html
<html>
<body>
  <script>
    require('electron').ipcRenderer.on('ping', (event, message) => {
      console.log(message) // 'whoooooooh!' と出力される
    })
  </script>
</body>
</html>

ipcRenderer.invoke() を使う

Electron v.7 から追加になったメソッドです。もともとは、remote モジュールを使うことでレンダラープロセス内であたかもメインプロセス内のオブジェクトを直接触れるように見せかけることができた仕組みを安全に置換したものです。ちなみに、remote モジュールは、Electron v.10 では非推奨で、将来的に使えなくなる可能性は高いです。

参考:[Electron] IPC には新しい ipcRenderer.invoke() メソッドを使ったほうが便利 (v7+)
https://qiita.com/jrsyo/items/abe19dff2d950132d9cd

そこで、ipcRenderer.invoke() メソッドです。

index.js
// レンダラープロセス
ipcRenderer.invoke('some-name', someArgument).then((result) => {
  // ...
})
main.js
// メインプロセス
ipcMain.handle('some-name', async (event, someArgument) => {
  const result = await doSomeWork(someArgument)
  return result
})

こうすることで、ipcRenderer.on() といった受け側を作る必要がなく、メインプロセスから Promise のデータで受け取れるようになるので便利です。受けるメインプロセスが、ipcMain.on() ではなく、ipcMain.handle() になっていることに注意してください。

なお、参考先の例にあるように、

index.js
// renderer から Main プロセスを呼び出す
const data = await ipcRenderer.invoke('invoke-test', 'ping')
console.log(data)

と、ワンライナーで書くこともできます。

セキュアなIPC通信 contextBridge を使う

と、ここまで、レンダラープロセスでも Electron API や Node.js モジュールを使う前提のコード例を書いてきましたが、実際には、Electron v.5 からデフォルトが、nodeIntegration=false となったので、将来的にはレンダラープロセス側で ipcRenderer.on() といった処理が行えなくなるかもしれません。

そこで、よりセキュアな IPC 通信を行うために追加されたのが、contextBridge です。

contextBridge - Electron公式ドキュメント
https://www.electronjs.org/docs/api/context-bridge#contextbridgeexposeinmainworldapikey-api

公式ドキュメントでは、

分離されたコンテキスト間に、安全、双方向で同期されたブリッジを作成します

と、分かりにくい説明がされていますが、分かりやすく図にすると以下の通りです。

contextBridgeの仕組み

ようは、メインプロセスにある Electron API や、Node.js モジュールをレンダラープロセスで扱わせずに、メインプロセスの外部にAPIとして別途定義して、それを関数のように呼ぶという方法です。ただ、contextBridge.exposeInMainWorld(apiKey, api) については、まだ Experimental ですが、積極的に使っていった方が良いでしょう。

これにはドキュメントにあるように、以下の考えから来ています。

メインプロセスとレンダラープロセスの違い - Electron公式ドキュメント
https://www.electronjs.org/docs/tutorial/application-architecture#%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%81%A8%E3%83%AC%E3%83%B3%E3%83%80%E3%83%A9%E3%83%BC%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%81%AE%E9%81%95%E3%81%84

ウェブページでは、ネイティブ GUI 関連の API を呼び出すことは許可されていません。これは、ウェブページがネイティブ GUI リソースを管理することは非常に危険であり、リソースをリークさせるのは容易いからです。 ウェブページで GUI 操作を実行する場合、ウェブページのレンダラープロセスはメインプロセスと通信して、メインプロセスがそれらの操作を実行するよう要求する必要があります。

しかし、contextBridge を使ったからといって安全は担保できない

とはいえ、今やこれも安全な方法とは言えないようです。つまり、contextBridge を使えば、すべて安心というわけでもないのです。

ElectronでcontextBridgeによる安全なIPC通信(受信編)
https://qiita.com/pochman/items/62de713a014dcacbad68

にあるように、汎用的な書き方をするとダメなようです。

const { contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld(
  "api", {
    send: (channel, data) => { // レンダラーからの送信用
      ipcRenderer.send(channel, data);
    },
    on: (channel, func) => { // メインプロセスからの受信用
      ipcRenderer.on(channel, (event, ...args) => func(...args));
    }
  }
);

Electronのセキュリティについて大きく誤認していたこと
https://qiita.com/sprout2000/items/2b65f7d02e825549804b

ここに書いてあるとおり、上記のコードの書き方は、Electron 公式ドキュメントによれば、
https://www.electronjs.org/docs/api/context-bridge#contextbridgeexposeinmainworldapikey-api

// ❌ Bad code
contextBridge.exposeInMainWorld('myAPI', {
  send: ipcRenderer.send
})

という書き方は、unsafe だそうです。

曰く、

いかなる種類の引数フィルタリングなしで強力なAPIを直接公開しています。これにより、どんなウェブサイトでも、可能にしてほしくない任意のIPCメッセージを送信することができるようになります。IPCベースのAPIを公開する正しい方法は、代わりにIPCメッセージごとに1つのメソッドを提供することでしょう
とのこと。

// ✅ Good code
contextBridge.exposeInMainWorld('myAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

つまり、1つの IPC 通信につき、1処理とした方が良いでしょう。

前の図にも描きましたが、レンダラープロセスからは preload.js に登録されている関数を呼び、preload.js の中で、IPC 通信を行うというのが安全ということになります。

worldSafeExecuteJavaScript: ture にしておくべきか

ちなみに Electron の v10.1.2 現在では、BrowserWindowoptions にある、webPreferences: { worldSafeExecuteJavaScript: false } がデフォルト値になっていますが、ドキュメントを見ると、将来的に Electron が v12 になったとき、true となり、Deprecated となるようですので、可能なら true にしておく方が良いでしょう。

ドキュメントによると、webFrame.executeJavaScript で受け渡されるJavaScriptコードの値が安全に引き渡されるようにサニタイズされると書いてありますが、具体的にどう行われるかは書いてありません。そもそも、webFrame.executeJavaScript 自体がイレギュラーっぽい使い方なので、避けておいた方が良いかもしれません。

BrowserWindow を作るときは、以下のような webPreferencesのオプション設定にすると良いでしょう。

main.js
  let mainWindow = new BrowserWindow({
    title: config.name,
    width: 1024,
    height: 640,
    minWidth: 1024,
    minHeight: 640,
    webPreferences: {
      worldSafeExecuteJavaScript: true, // In Electron 12, the default will be changed to true.
      nodeIntegration: false, // XSS対策としてnodeモジュールをレンダラープロセスで使えなくする
      contextIsolation: true, // レンダラープロセスに公開するAPIのファイル
      preload: __dirname + '/preload.js'
    }
  });

サンプルプログラム

これら、セキュアな IPC 通信のサンプルプログラムを作成しました。「3分間タイマー」という、いかにも単純なアプリですが、nodeIntegration=false かつ、contextBridge を使って実装されています。

preload.js
const { contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld(
  "api", {
    TimerStart: () =>
        ipcRenderer.invoke("ipc-timer-start")
            .then(result => result)
            .catch(err => console.log(err)),
    TimerStop: () => ipcRenderer.send("ipc-timer-stop"),
    TimerReset: () => ipcRenderer.send("ipc-timer-reset"),
    DisplayTimer: (channel, listener) => {
      ipcRenderer.on("ipc-display-timer", (event, arg) => listener(arg));
    }
  }
);

この上記コードでは、"api"と key を設定しています。ですので、呼び出す側(レンダラープロセス)の index.html では以下のように呼び出します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" href="style.css">
  <script type="text/javascript">
    window.onload = () => {
      // 「開始」ボタンをクリック
      document.getElementById('button-start').addEventListener('click', async () => {
        const result = await window.api.TimerStart("ipc-timer-start");
        if (result === true) {
          document.getElementById('button-reset').textContent = "停止";
        }
        else {
          document.getElementById('button-reset').textContent = "リセット";
        }
      });
      // 「リセット」or「停止」ボタンをクリック
      document.getElementById('button-reset').addEventListener('click', async () => {
        if ( document.getElementById('button-reset').textContent === "停止" ) {
          document.getElementById('button-reset').textContent = "リセット";
          window.api.TimerStop("ipc-timer-stop");
        }
        else {
          window.api.TimerReset("ipc-timer-reset");
        }
      });
    }
    // タイマー(ミリ秒)の受け取り
    window.api.DisplayTimer("ipc-display-timer", (milliseconds) => {
      // console.log("ipc-display-timer: " + arg);
      if (milliseconds < 900){
        document.getElementById('button-reset').textContent = "リセット";
        return;
      }
      let min = parseInt((milliseconds / 1000) / 60);
      let sec = parseInt(milliseconds / 1000) % 60;
      document.getElementById('timer-number').textContent =
          ('00' + min).slice(-2) + ':' + ('00' + sec).slice(-2);
    });
  </script>
</head>
<body>

<div>
  <div id="timer-number">3:00</div>
</div>

<div class="button-wrapper">
  <button id="button-start" class="button button-color-blue">開始</button>
  <button id="button-reset" class="button button-color-red">リセット</button>
</div>

</body>
</html>

window.api.TimerStart(channel) や、window.api.TimerStop(channel) のように、設定した "api" キーを通して関数を呼んでいます。これらのさらなる詳細、つまりサンプルプログラムは、以下の GitHub にアップロードされています。

SimpleTimer - GitHub
https://github.com/hibara/SimpleTimer

サンプル実行ファイルは以下の通りです。

macOS:
https://github.com/hibara/SimpleTimer/releases/download/v1.0.0/SimpleTimer-darwin-x64.zip

Windows:
https://github.com/hibara/SimpleTimer/releases/download/v1.0.0/SimpleTimer-win32-ia32.zip

参考

ipcRenderer.invoke()
https://qiita.com/jrsyo/items/abe19dff2d950132d9cd

ElectronでcontextBridgeによる安全なIPC通信(受信編)
https://qiita.com/pochman/items/62de713a014dcacbad68

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

あなたがまだ使っていないかもしれないHTML5の便利機能10選

こんにちは、たかとーです?‍?
こちらは、10 useful HTML5 features, you may not be usingの翻訳記事になります。
当記事は、Tapasさんの許可を得て翻訳しています。Tweet


10 useful HTML5 features, you may not be using

10 useful HTML5 features, you may not be using

HTML5は新しいものではありません。最初のリリース(2008年1月)以来いくつかの機能を使用してきました。100DaysOfCodeの取り組みの一環として、HTML5の機能リストをもう一度よく見てみました。何か見つけたかな?私は今のところあまり使っていません。

この記事では、過去にあまり使ったことがなかったが、今では便利になったHTML5の機能を10個挙げています。また、Netlifyでホストされている、実際に動作する例を作成しました。参考になることを願っています。

https://html5-tips.netlify.app/

Detailsタグ

<details>タグは、ユーザーに必要なときだけ詳細を提供します。ユーザーにオンデマンドでコンテンツを表示する必要がある場合は、このタグを使用します。デフォルトでは、ウィジェットは閉じています。開くと、ウィジェットは展開され、コンテンツが表示されます。

<summary>タグは、<details>と一緒に使われ、見出しを指定します。

コード

<details>
  <summary>Click Here to get the user details</summary>
  <table>
    <tr>
      <th>#</th>
      <th>Name</th>
      <th>Location</th>
      <th>Job</th>
    </tr>
    <tr>
      <td>1</td>
      <td>Adam</td>
      <td>Huston</td>
      <td>UI/UX</td>
    </tr>
  </table>
</details>

動作例

detailsタグ動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/details/index.html

Tips

GitHubのReadmeで、必要に応じて詳細情報をで表示するために使ってみましょう。これは、Reactコンポーネントの膨大な量のプロパティリストを隠して、クリックされたら表示する例です。

Content Editable属性

contenteditableは、コンテンツを編集可能にするために要素に設定できる属性です。<div><p><ul>などで動作します。<element contenteditable="true|false">のように指定する必要があります。

注意: contenteditable属性がその要素に指定されていないとき、親要素から値を受け継ぎます。

コード

<h2>Shoppping List(Content Editable)</h2>
<ul class="content-editable" contenteditable="true">
  <li>1. Milk</li>
  <li>2. Bread</li>
  <li>3. Honey</li>
</ul>

動作例

Content Editable属性動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/content-editable/index.html

Tips

この属性を使うことで、<span><div>を編集可能にし、CSSを使ってリッチなコンテンツを追加できます。これはinputフィールドを使用するよりもずっと良いでしょう。ぜひお試しください!

Mapタグ

<map>タグはイメージマップの定義に役立ちます。イメージマップとは、1つ以上のクリック可能な領域を持つ画像のことです。<area>タグでクリック可能な領域を決定します。クリック可能な領域は、矩形、円、多角形の領域のいずれかになります。形状を指定しない場合は、領域を画像全体として考慮します。

コード

<div>
  <img
    src="circus.jpg"
    width="500"
    height="500"
    alt="Circus"
    usemap="#circusmap"
  />

  <map name="circusmap">
    <area shape="rect" coords="67,114,207,254" href="elephant.htm" />
    <area shape="rect" coords="222,141,318, 256" href="lion.htm" />
    <area shape="rect" coords="343,111,455, 267" href="horse.htm" />
    <area shape="rect" coords="35,328,143,500" href="clown.htm" />
    <area shape="circle" coords="426,409,100" href="clown.htm" />
  </map>
</div>

動作例

Mapタグ動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/map/index.html

Markタグ

<mark>タグでテキストをハイライトしてみましょう。

コード

<p>
  Did you know, you can <mark>"Highlight something interesting"</mark> just with
  an HTML tag?
</p>

動作例

Markタグ動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/mark/index.html

data-* 属性

data-*属性は、ページやアプリケーションにプライベートなカスタムデータを保存するために使用されます。保存されたデータはJavaScriptで使用して、さらなるユーザー体験を生み出すことができます。

data-*属性は2つの部分から構成されますThe data-* attributes consist of two parts:
* 属性名に大文字を含めるべきではなく、またdata-プレフィックスの後は1文字以上が必要です
* 属性値は任意の文字列になります

コード

<h2>Know data attribute</h2>
<div
  class="data-attribute"
  id="data-attr"
  data-custom-attr="You are just Awesome!"
>
  I have a hidden secret!
</div>

<button onclick="reveal()">Reveal</button>

JavaScriptで以下のように操作します。

function reveal() {
  let dataDiv = document.getElementById('data-attr');
  let value = dataDiv.dataset['customAttr'];
  document.getElementById('msg').innerHTML = `<mark>${value}</mark>`;
}

注意: JavaScriptでこれらの属性値を読み取るには、完全なHTML名(data-custom-attr)でgetAttribute()を使用できますが、標準ではもっと簡単な方法を定義しています: datasetプロパティを使用です。

動作例

data-* 属性動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/data-attribute/index.html

Outputタグ

<output>タグは計算結果を表します。通常、この要素は計算結果のテキスト出力を表示するための領域を定義します。

コード

<form oninput="x.value=parseInt(a.value) * parseInt(b.value)">
  <input type="number" id="a" value="0" />
  * <input type="number" id="b" value="0" /> =
  <output name="x" for="a b"></output>
</form>

動作例

Outputタグ動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/output/index.html

Datalistタグ

<datalist>タグは、あらかじめ定義されたオプションのリストを指定し、ユーザーはそのリストにさらにオプションを追加することができます。これはautocomplete機能を提供しており、タイプアヘッドで目的のオプションを取得することができます。

コード

<form action="" method="get">
  <label for="fruit">Choose your fruit from the list:</label>
  <input list="fruits" name="fruit" id="fruit" />
  <datalist id="fruits">
    <option value="Apple"></option>
    <option value="Orange"></option>
    <option value="Banana"></option>
    <option value="Mango"></option>
    <option value="Avacado"></option>
  </datalist>

  <input type="submit" />
</form>

動作例

Datalistタグ動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/datalist/index.html

Range(Slider)値

rangeはinputタグのtype属性値で、スライダーのようなセレクタを実現します。

コード

<form method="post">
  <input
    type="range"
    name="range"
    min="0"
    max="100"
    step="1"
    value=""
    onchange="changeValue(event)"
  />
</form>
<div class="range">
  <output id="output" name="result"> </output>
</div>

動作例

Range(Slider)値動作例

ここで実際に触ることができます: https://html5-tips.netlify.app/range/index.html

Meterタグ

<meter>タグを使って、与えられた範囲でデータを測ってみましょう。

コード

<label for="home">/home/atapas</label>
<meter id="home" value="4" min="0" max="10">2 out of 10</meter><br />

<label for="root">/root</label>
<meter id="root" value="0.6">60%</meter><br />

動作例

Meterタグ動作例g

ここで実際に触ることができます: https://html5-tips.netlify.app/meter/index.html

Tips

プログレスバーのようなものを表示する際には<progress>タグを使用してください。

<label for="file">Downloading progress:</label>
<progress id="file" value="32" max="100">32%</progress>

progress

Inputsタグ

inputタイプにはいくつか特別な使い方があります。

コード

required

inputフィールドを必須アイテムにします。

<input type="text" id="username1" name="username" required />

required

autofocus

input要素にカーソルを置くと自動でフォーカスします。

<input type="text" id="username2" name="username" required autofocus />

validation with regex

正規表現をつかって入力値をバリデーションできます。

<input
  type="password"
  name="password"
  id="password"
  placeholder="6-20 chars, at least 1 digit, 1 uppercase and one lowercase letter"
  pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$"
  autofocus
  required
/>

Color picker

シンプルなカラーピッカーです。

<input type="color" onchange="showColor(event)" />
<p id="colorMe">Color Me!</p>

Color picker

訳者感想

Reactなど、JavaScript関連の新しい情報はキャッチアップするようにしているのですが、HTMLに関しては全くできておらず、目からウロコな記事でした。

いままでJavaScriptを使って実現していたようなものをHTMLのみで表現できるのは、開発者にとっても楽で最高ですよね!

素晴らしい記事をありがとうございました Tapas

訳者について

2019年5月よりバンクーバーを拠点に移し、現在スタートアップの開始に向けて試行錯誤しているソフトウェアでデベロッパーです。

近頃は、VCの方と話しながらアイディアのブラッシュアップなどを行いながらMVPの検証を進めています。

フリーランス案件も募集し始めました!React、NodeJs、TypeScript等フロント、バックエンド問わず行えます。是非宜しくお願い致します。

もしよろしければ、以下SNSもよろしく願いします!

Twitter: @taishikat0_Ja
Note: 日本人でも英語圏で戦えることを証明したい。28歳が会社を辞め、個人開発者としてカナダでひたすらもがき続けた一年間とこれから
Linkedin: Taishi Kato

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

Next.jsにjestとenzymeを導入(next/babel使用)

Next.jsにjestとenzymeを導入(next/babel使用)

以前、Next.jsにjestとenzymeを導入するという記事を書きました。

上記の手順でjestの実行はできたのですが、yarn devでアプリ起動するとなにやらbabelに関するエラーが。。

どうやらNext.js起動すると追加したbabelの設定ファイルの方が読み込まれて、babelのエラーが出てしまっているよう。

そこで、jestで使うbabelをnext.jsのbabelに変更したところ、よりすっきりした設定になったのでメモ。

next.jsのbabelが使える

next.jsにはデフォルトでbabelが入っており、これがjsxのトランスパイルなどjestにも適用できることが分かりました。
こちらの方がスッキリとした手順・設定で構築できます。

jestインストール

$ npm install --save-dev jest 

jest設定ファイルを生成

$ jest --init
command not found: jest の場合

以下を実行します。

./node_modules/.bin/jest --init   

Enzymeインストール

yarn add --dev enzyme enzyme-adapter-react-16

Enzymeの利用時は一度だけEnzyme.configure()を呼ぶ必要があるため、下記のスクリプトを追加。

jest.setup.js
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";

Enzyme.configure({ adapter: new Adapter() });

Jestのテスト前に実行されるようにする。

jest.config.js
module.exports = {
  // ...
  setupFiles: ['./jest.setup.js'],
  // ...
}

babel.config.jsを設定

module.exports = {
    "presets": ["next/babel"],
};   

テストファイルのignore

Cypressを導入しており、jest実行でcypressのspecも読まれてしまうので、ignore設定をしました。

jest.config.js
...  
testPathIgnorePatterns: [
    "/node_modules/",
    "/cypress/"
],
...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

let宣言では同じ変数名は使えない、そんなことは分かっているけれど

let宣言では同じ変数名は使えない、そんなことは分かっているけれど

エラー内容

「この変数名は既に宣言されています」

何度ファイル内を見直して、同じ変数名がlet宣言されていないことを確認するも表示されるこのエラー。
見直せど、見直せど原因がわからない。

原因

コードレビューしてもらい発覚した原因。
それはファイルをリファクタリングしたときにミスをしており、ファイルを二重読み込みしていたこと

同じ操作を複数のファイルに記述していたheader箇所を外部ファイル化したときに、その外部ファイル(Bとする)内でも該当のJavaScriptファイル(Cとする)を読み込んでいたのだ。

外部ファイルBを読み込んだファイル(Aとする)の両方でJavaScriptファイルCを呼び出していたため、二重呼び出し(二重読み込み)の状態になりエラーが発生していた。

AーBーC
|
C

<html>
  <head>
    <script defer src="C.js"></script>
  </head>

  <body>
    <header>
     ~~~~~~~
    </header>

  </body>

</html>

header箇所を外部ファイル化するにも関わらず、htmlタグやheadタグ、bodyタグを記述してさらにJavaScriptファイルCまで読み込んでいたことがエラーの原因だった。

<header>
 ~~~~
</header>

本来、外部ファイルBではJavaScriptファイルCを呼び出す必要はなかったため削除し、header箇所だけ記述しておおもとのPHPファイルAでrequire_once('headerファイル');と呼びだすことでエラー解決できた。

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

Javascriptチュートリアル

JavaScriptコードエディター

概要 : このチュートリアルでは、JavaScriptのコードエディター及び、Javascriptコードを記載するためのVisual Studio Codeのインストール方法について学習します。

ポピュラーなJavaScriptコードエディター

Javascriptコードを記載する基本的なエディターとして、Windows上のNotepadのようなテキストエディターがあります。
しかし、Javascriptコードの記載を簡単かつ早く行うためには、Javascriptコードエディターが必要です。

基本的なエディターに対しJavascriptコードエディターは、構文の強調表示、インデント、オートコンプリート、ブレースマッチング機能など様々な機能を提供しています。
また、いくつかのエディターはJavascriptやその他のコードをデバッグするのに便利な機能を提供しています。

Javascriptコードエディターとしてポピュラーなものに以下のようなものがあります。
* Visual Studio Code
* Atom
* Notepad++
* Vim
* Gnu Emacs

これらは全て無料で使用できます。ここではVisual Studio Codeを取り上げます。

Visual Studio Code

Visual Studio CodeはMicrosoftによって開発された無料のオープンソースコードエディターです。
Windows,Linux,MacOSを含むプラットフォームで動作します。
image.png
Visual Studio Codeは高度にカスタマイズされており、テーマ、キーボードショートカットなどのような環境設定や、様々な拡張機能をインストールして使用できます。
また、インテリセンス、デバッグ、フォーマット、コードナビゲーション、リファクタリングの他多くの優れた言語機能を組み込みでサポートしています。

Visual Studio Codeでサポートされている全ての機能を理解するには以下を参照してください。
JavaScript in Visual Studio Code

Visual Studio Codeのダウンロード

Visual Studio Codeは以下のリンクからダウンロードできます。
Download Visual Studio Code

Visual Studio Codeのインストール

Visual Studio Codeは数分で簡単にインストールできます。

Live Server拡張機能のインストール

Live Server拡張機能は、Javascriptのコードを変更するだけでWebブラウザの更新をすることなくページを更新できる機能を持っています。
便利な機能なので是非使用してください。
image.png

このチュートリアルでは、Visual Studio Codeのインストール方法について記載しました。

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

Javascriptコードエディター

JavaScriptコードエディター

概要 : このチュートリアルでは、JavaScriptのコードエディター及び、Javascriptコードを記載するためのVisual Studio Codeのインストール方法について学習します。

ポピュラーなJavaScriptコードエディター

Javascriptコードを記載する基本的なエディターとして、Windows上のNotepadのようなテキストエディターがあります。
しかし、Javascriptコードの記載を簡単かつ早く行うためには、Javascriptコードエディターが必要です。

基本的なエディターに対しJavascriptコードエディターは、構文の強調表示、インデント、オートコンプリート、ブレースマッチング機能など様々な機能を提供しています。
また、いくつかのエディターはJavascriptやその他のコードをデバッグするのに便利な機能を提供しています。

Javascriptコードエディターとしてポピュラーなものに以下のようなものがあります。
* Visual Studio Code
* Atom
* Notepad++
* Vim
* Gnu Emacs

これらは全て無料で使用できます。ここではVisual Studio Codeを取り上げます。

Visual Studio Code

Visual Studio CodeはMicrosoftによって開発された無料のオープンソースコードエディターです。
Windows,Linux,MacOSを含むプラットフォームで動作します。
image.png
Visual Studio Codeは高度にカスタマイズされており、テーマ、キーボードショートカットなどのような環境設定や、様々な拡張機能をインストールして使用できます。
また、インテリセンス、デバッグ、フォーマット、コードナビゲーション、リファクタリングの他多くの優れた言語機能を組み込みでサポートしています。

Visual Studio Codeでサポートされている全ての機能を理解するには以下を参照してください。
JavaScript in Visual Studio Code

Visual Studio Codeのダウンロード

Visual Studio Codeは以下のリンクからダウンロードできます。
Download Visual Studio Code

Visual Studio Codeのインストール

Visual Studio Codeは数分で簡単にインストールできます。

Live Server拡張機能のインストール

Live Server拡張機能は、Javascriptのコードを変更するだけでWebブラウザの更新をすることなくページを更新できる機能を持っています。
便利な機能なので是非使用してください。
image.png

このチュートリアルでは、Visual Studio Codeのインストール方法について記載しました。

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

GASでTwitterのアカウントがBANされているかを調べる

目標

GoogleスプレッドシートにTwitterIDを羅列して
それらを自動でAPIに投げて結果を書き込んでくれるようにします。

スクリーンショット 2020-09-22 6.42.00.png

Twitterのアカウントの状態を調べるには

shadowban
(https://shadowban.eu/)
こちらにIDを打ち込んでTwitterアカウントの状態を調べることができます。
今回はサイト内で使われているAPI(https://shadowban.eu/.api/)
をGAS内で使います。

TwitterアカウントのBANの種類
4つのTwitterシャドウバンの種類と解説 気づかぬうちになってるかも)
(https://kanasys.com/tech/824)

STEP1 スプレッドシートを読み込もう!!

二つの方法がありますが

script.gas
function main() {
 const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
 const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName); /こっ}
  • 一番目は、GASと紐づけられた(スプレッドシート内のツールからGASを記述するとき)スプレッドシートに有効です。
  • 二番目はスプレッドシートのIDシート名を指定して読み込めます。

今回は二番目のスプレッドシートのIDとシート名を記述して読み込む方法を使います。

script.gas
const spreadsheetId = 'google.com/spreadsheets/d/<-----ID------>/edit';
const sheetName = 'シート1';
// const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); //GASがスプレッドシートと紐づいている時の取得方法
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

STEP2 取得するテーブルの幅を設定し、取得しよう!

セル単品を取得する場合は

script.gas
const accountsArr = sheet.getRange(columnLength, rowLength).getValue();

getvalue()
で値を配列になって取得できます。

今回は幅を選択して取得するので

script.gas
const accountsArr = sheet.getRange(startColumn, startRow, endColumn, endRow).getValue();

getValues()をつかって幅丸ごと取得します。

その前に!!
各、値を設定しましょうか

script.gas
    const headerLength = 1;  // アカウント名 状態 などのヘッダー情報の高さ
    const accountColumn = 1; //アカウント名が羅列してあるカラム
    const statusColumn = 2;  //状態を記録するカラム
    const accountLength = sheet.getLastRow() - headerLength; // アカウントの数(ヘッダーの高さを考慮)

各記録を設定したら、実際に取得します。

script.gas
function main() {
    const spreadsheetId = 'XXXXXXXXXXXXXXXXXXXXXXXX';
    const sheetName = 'シート1';
    const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

    const headerLength = 1;
    const accountColumn = 1;
    const statusColumn = 2;
    const accountLength = sheet.getLastRow() - headerLength;

    const accountsArr = sheet.getRange(accountColumn + headerLength ,accountColumn, accountLength, accountColumn).getValues();
}

console.logすると。。。
スクリーンショット 2020-09-22 7.15.35.png

script.gas
[['配列1'],['配列2'],['配列3']]

こんな感じで取得できましたね。

STEP3 GAS内でAPIをコールしてみよう!

簡単。。。

script.gas
response = UrlFetchApp.fetch(url).getContentText();
let json = JSON.parse(response);

なので、、、
shadowban APIのURLを入れます。
パスパラメータがないと404が帰ってくるので、安倍晋三さんのIDを入れます。(一番有名?なので)

script.gas
const shadowbanApi = 'https://shadowban.eu/.api/AbeShinzo';
response = UrlFetchApp.fetch(shadowbanApi).getContentText();
let json = JSON.parse(response);

帰ってくるjsonの中身はこんな感じでした。

response.json
{
    "profile": {
        "sensitives": {
            "possibly_sensitive": 0,
            "counted": 1111,
            "possibly_sensitive_editable": 758
        },
        "protected": false,       // 鍵アカウントの場合はtrueになります。
        "exists": true,           // 存在するか。
        "screen_name": "AbeShinzo",
        "has_tweets": true        // tweetが1回以上あるか
    },
    "timestamp": 1600727024.7363904,
    "tests": {
        "more_replies": {
            "ban": false,        // true ならばReply deboosting banになります
            "tweet": "1150369849270398978",
            "in_reply_to": "1150369468402417664"
        },
        "search": "1307120105478934528",
        "typeahead": true,
        "ghost": {
            "ban": false        // true ならばghost banになります。
        }
    }
}

このjsonで一つ注意なのですが、
アカウントが凍結されている場合
testsのプロパティがつきません(凍結されるとshadowbanのテストができないため)

STEP4 スプレッドシートから取得したIDでAPI回そう書き込もう!

めんどくさくなりました。説明すると長くなるので全部記述します。

script.gas
function main() {
    const spreadsheetId = 'XXXXXXXXXXXXXXXXXXXXXXXX';
    const sheetName = 'シート1';
    const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

    const headerLength = 1;
    const accountColumn = 1;
    const statusColumn = 2;
    const accountLength = sheet.getLastRow() - headerLength;

    const accountsArr = sheet.getRange(accountColumn + headerLength ,accountColumn, accountLength, accountColumn).getValues();

    for(let i = 1; i <= accountLength; i++) {
        const url = shadowbanApi + accountsArr[i-1][0];
        const response = callApi(url);
        const status = searchStatus(response)
        sheet.getRange(i + headerLength, statusColumn).setValue(status.message.join(','));
        sheet.getRange(i + headerLength, statusColumn).setBackground(status.color);
    }
}

function callApi(url) {
    try {
        response = UrlFetchApp.fetch(url).getContentText();
        let json = JSON.parse(response);
        return json;
    } catch (e) {
        console.log('APIエラー:', e);
        return {};
    }
}

function searchStatus(json) {
    let status = {
        message: [],
        color: ""
    }

    if(json === undefined || json === null || Object.keys(json).length === 0) return status;

    if (!json.profile.exists) {
        status.message.push('存在しません');
        status.color = 'red';
    } else if (json.profile.protected) {
        status.message.push('鍵アカウント');
    } else if (json.profile.suspended) {
        status.message.push('凍結されています');
        status.color = 'red';
    } else if (!json.profile.has_tweets) {
        status.message.push('tweetされていません');
    }

    if(json.tests) {
        if (json.tests.typeahead === false) {
            status.message.push('Search suggestion ban.');
            status.color = 'yellow';
        }
        if (json.tests.search === false) {
            status.message.push('Search ban.');
            status.color = 'red';
        }
        if (json.tests.ghost.ban === true) {
            status.message.push('Ghost ban');
            status.color = 'red';
        }

        if (json.tests.more_replies) {
            if (json.tests.more_replies.error === 'ENOREPLIES') {
                status.message.push('has not made any reply tweets.');
            } else if (json.tests.more_replies.ban === true) {
                status.message.push('Reply deboosting ban.');
                status.color = 'yellow';
            }
        }
    }
    if(status.message.length === 0) status.message.push('正常');
    return status;
}

投げやりになりました。
肝は

script.gas
    for(let i = 1; i <= accountLength; i++) {
        const url = shadowbanApi + accountsArr[i-1][0];
        const response = callApi(url);
        const status = searchStatus(response)
        sheet.getRange(i + headerLength, statusColumn).setValue(status.message.join(','));
        sheet.getRange(i + headerLength, statusColumn).setBackground(status.color);
    }

ここですね。

setValue('値')で指定したCellに書き込むことができます。
setBackground('red') でセルの背景に色をつけることができます。今回は

4つのTwitterシャドウバンの種類と解説 気づかぬうちになってるかも?
(https://kanasys.com/tech/824)

スクリーンショット 2020-09-22 7.35.18.png

程度になっているものは'yellow'
程度になっているものは'red'
で背景色をつけました。

※一部shadowbanのフロントエンドのソースコードを参考にしました。

STEP5 お試しあれ

一応もう一度コード記述します。

script.gas
function main() {
    const spreadsheetId = 'XXXXXXXXXXXXXXXXXXXXXXXX';
    const sheetName = 'シート1';
    const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

    const headerLength = 1;
    const accountColumn = 1;
    const statusColumn = 2;
    const accountLength = sheet.getLastRow() - headerLength;

    const accountsArr = sheet.getRange(accountColumn + headerLength ,accountColumn, accountLength, accountColumn).getValues();

    for(let i = 1; i <= accountLength; i++) {
        const url = shadowbanApi + accountsArr[i-1][0];
        const response = callApi(url);
        const status = searchStatus(response)
        sheet.getRange(i + headerLength, statusColumn).setValue(status.message.join(','));
        sheet.getRange(i + headerLength, statusColumn).setBackground(status.color);
    }
}

function callApi(url) {
    try {
        response = UrlFetchApp.fetch(url).getContentText();
        let json = JSON.parse(response);
        return json;
    } catch (e) {
        console.log('APIエラー:', e);
        return {};
    }
}

function searchStatus(json) {
    let status = {
        message: [],
        color: ""
    }

    if(json === undefined || json === null || Object.keys(json).length === 0) return status;

    if (!json.profile.exists) {
        status.message.push('存在しません');
        status.color = 'red';
    } else if (json.profile.protected) {
        status.message.push('鍵アカウント');
    } else if (json.profile.suspended) {
        status.message.push('凍結されています');
        status.color = 'red';
    } else if (!json.profile.has_tweets) {
        status.message.push('tweetされていません');
    }

    if(json.tests) {
        if (json.tests.typeahead === false) {
            status.message.push('Search suggestion ban.');
            status.color = 'yellow';
        }
        if (json.tests.search === false) {
            status.message.push('Search ban.');
            status.color = 'red';
        }
        if (json.tests.ghost.ban === true) {
            status.message.push('Ghost ban');
            status.color = 'red';
        }

        if (json.tests.more_replies) {
            if (json.tests.more_replies.error === 'ENOREPLIES') {
                status.message.push('has not made any reply tweets.');
            } else if (json.tests.more_replies.ban === true) {
                status.message.push('Reply deboosting ban.');
                status.color = 'yellow';
            }
        }
    }
    if(status.message.length === 0) status.message.push('正常');
    return status;
}

GAS内ではこうですね。
ちゃんとスプレッドシートのIDとシート名入れてくださいね!

スクリーンショット 2020-09-22 7.39.14.png

終わりに

GAS簡単ですね。
気になったのはログ表示が遅いです。

スクリーンショット 2020-09-22 7.41.39.png

普通にjavascript書いててconsole.log()で待つことってあまりないですから、辛くなりました。恵まれてますね。

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

プログラミング TypeScript スケールするJavaScriptアプリケーション開発のレビュー(読みながら)

どうも初めましてNakZです。はじめてQiitaの記事を書きます。
よろしくお願いします。
表題の通り読みながらレビューしていこうと思います。
レビューというかメモ書きというかあやふやな記事になってます。読み終わったら綺麗にまとめなおします。
全力で著作権に配慮しますので、違反してる箇所があればご指摘くださいm(_ _)m
念のため記事中のコードは全て自分で書き、できる限りシンプル(書籍のオリジナリティを侵害しないよう)になるように心掛けてます。

読んでる書籍はこちら
https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0TypeScript-%E2%80%95%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8BJavaScript%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA-Boris-Cherny/dp/4873119049

目的

  • 記事を更新しながら読むことで積読しないようにする
  • Qiitaで記事書いてみたい

最近アルバイトでTypeScriptを使うことになったのでモチベーション維持のために書きます。
独学で雑にプログラミングの勉強してきたので型とかクラスとか真面目に勉強するのも悪くないなと思って読むことにしました。

全体の感想(読んでる途中だけど)

訳書なのに全体的にこなれた文章なので読みやすい(特にジョークが面白い)。ただ、込み入った説明の部分は直訳的になってしまっているようで多少意味が通りづらく実際にコードを動かしてみないと何を伝えたいのか汲み取りづらかったりする。(コードを動かすのは良いことなので別に良いけども)
JavaScriptの経験がないとコードサンプルが読みづらいかもしれない。僕もJavaScriptは簡単なDOM操作のためにしか使った経験がなかったのでコードサンプルの中のコードだとか、細かい文法を調べながら読むことが割と多い。
言語の仕様を詳しく知ることでできることの幅が広がりそうだなというのを感じながら読めるので買って良かったと思ってます。今まで雑に使ってきたGoとかPythonの仕様もちゃんと勉強したくなった。

以下、章ごとのレビュー

1章 イントロダクション

静的型付け言語であることのメリットとTypeScriptの柔軟性がたくさん語られていました。

2章 TypeScript:全体像

  • TypescriptのコンパイラはTypeScriptのコードをJavaScriptのコードにコンパイラすることの説明
  • tsconfig.jsonの書き方。(TypeScriptはルートディレクトリにtsconfig.jsonがないといけない)
  • "Hello,World!"プログラム
  • 練習問題(練習問題というより型チェックの体験)

型チェックのおかげで可愛いバリスタとデートできるようになるらしい。

VSCodeでのTypeScriptのデバッグの方法
本に書いてあるコードを気軽に試したかったのでVSCodeでデバッグする方法調べました。

  1. プロジェクトのディレクトリに移動してShift + command + Dでデバッグ画面を表示させます。
  2. To customize Run and Debug create a launch.json file.ってのをクリックする
  3. Node.jsを選択
  4. .vscode/launch.jsonってファイルができるのでそこの"program": ~の~を実行したいファイルのパスに変える。僕の場合、本の手順にしたがってtsconfig.jsonを書いたので"program":${workspaceFolder}/dist/index.jsになっている。これはtsconfig.jsで"outDir": "dist"としたからである。

これでデバッグができる。ちなみにTypeScriptのファイルを書き換えたあとデバッグすると自動でtsファイルをコンパイルして実行してくれる(便利)

3章 型について

今まで型についてなあなあにやってきていたので勉強する良い機会になりました。

  • いろんな型があるんだなあ。。。ってなるし一つ一つ結構詳しく書いてくれてる。

練習問題に解答がついているのが凄くありがたい。
今まであまり型について真面目に考えたことなかったので良い勉強になりました。この章から少し内容が堅くなってきてる気がする。(この本は最初に理論の話をしてから実装のパートに移るらしい)

4章 関数

  • レストパラメーター便利そう。Pythonの*argsみたいな感じで使えば良いんかな。
  • JavaScriptはクラスだけじゃなくて関数にもthisがついてるのはじめて知りました。
  • 呼び出しシグネチャちゃんと定義したら、関数が変な挙動しそうだったら事前にわかるし便利そう。
  • 関数式のオーバーロードよくわからん。関数宣言のオーバーロードの方が使いやすくないの?

- ジェネリック型便利そう

type Foo = {
    bar? :string
    baz?: string
}
let hoge :Foo
hoge ={}
console.log(hoge.bar)

これhoge.barに値入れてないからエラーになると思ったけど、一応、undefinedって出力されるのか。
ジェネリック関数便利、以下、書き方忘れたときに思い出すときのためのコード

type Fuga ={
    <T>(bar:T):T
}// type Hoge = <T>(foo:T)=> T と同じ
let fuga:Fuga = function (bar){
    return bar
}
console.log("fuga:",fuga("fuga"))

この書き方だと、関数fugaに引数を渡して呼び出すタイミングでジェネリック型Tの型を決定する。しかし、

type Hoge<T> ={
    (foo:T):T
}// type Hoge<T> = (foo:T)=> T と同じ

let hoge:Hoge<string> = function (foo :string){
    return foo
}
console.log("hoge:",hoge("hoge"))

の書き方だと、関数の宣言時にジェネリック型Tの型を決定する。
複数使うときは<T,U>って感じにする。


type Filter = {
    <T,U>(farray: T[],garray:U[] ,f:(fitem:T)=>boolean, g:(gitem :U) => boolean):(T|U)[] | undefined
}
let filter :Filter = (farray,garray,f,g) =>{
    let result = []
    for(let element of farray){
        if( f(element) ===true ){
            result.push(element)
        }
    }
    for(let element of garray){
        if( g(element) ===true ){
            result.push(element)
        }
    }
    return result
}
let f = (n:number)=> {return n >1}
let g = (str:string)=>{return str.length === 3}

let out = filter([1,2,3],["bar","foo"],f,g)
if (out !== undefined){
    console.log(...out)
}else{
    console.log("out is undefined.")
}

JavaScriptの配列ってどうなってんの?

JavaScriptって下のコード書けるのか。PythonだとIndexError: list assignment index out of rangeって怒られたけど。

let hoge = []
hoge[2] = "hoge"
console.log(hoge)

気になったので調べてみると

let hoge = []
hoge[2] = "hoge"
console.log(hoge[0])

だとundefinedが出力された。0,1がなくても2にメモリ割り当てられるの?気が向いたら調べたい。

5章 クラスとインターフェース

6章 高度な型

7章 エラー処理

8章 非同期プログラミングと並行、並列処理

9章 フロントエンドとバックエンドのフレームワーク

10章 名前空間とモジュール

11章 JavaScriptとの相互運用

12章 TypeScriptのビルドと実行

13章 終わりに

付録A 型演算子

付録B 型ユーティリティー

付録C 宣言の振る舞い

付録D サードパーティーJavaScriptモジュールのための宣言ファイルの書き方

付録E トリプルスラッシュ・ディレクティブ

付録F 安全性に関するTSCコンパイラーフラグ

付録G TSX

付録H ESLintとAST

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

ブログの表示高速化のためにやったこと・調べたこと

元記事

https://coincidence.netlify.app/post/blog-display-speed
(ブログからの転載になります。)

表示が遅い!!

このブログ、現状でもかなり表示が遅いのですが、本記事での取り組みをやる前はさらに遅かったです。
参考までにLighthouseのスコアをお示ししますと、Performanceスコアが32〜36点ほどでした。
この状態から以下の対応を行なったことで、50点以上を安定して取れるようになりました。正直このブログにとっては意味のない対応もありましたが、勉強の意味で残しておこうと思います。
各施策の最初には星印で効果の度合いを示しました。黒星(ぬりつぶし)が多い方が、効果があったものになります。ただしこれは、私のSPAブログ環境ではこうだった、という話ですので、設計が異なれば有効な対策も異なるということはご注意くださいませ。(大丈夫だと思いますけど)

施策1:APIへの問合せ回数減

効果 ★★★☆☆

これは、トップページ(/page/1)の表示時間短縮には直接影響しない部分ですが、画面遷移時のオーバーヘッドを減らす意味で非常に役立ちました。
SPAの基本的な振る舞いとして、「情報の更新が必要であればその都度サーバーサイドに問合せ、結果を取得して表示更新する」という像があると思うのですが、WebページというよりWebアプリエンジニアの私はこの考えに縛られすぎていたようです。ページの表示、タグ一覧の表示、タグに紐づく記事一覧の表示…といった振る舞いをSPAブログにさせるために、毎回Contentfulに問い合わせをしていたのです。情報の更新はされていないにもかかわらず。
これはあまりに無駄が大きいので、ビルド実行前にContentful APIから情報を取得してjsonとして保存するスクリプトを書きました。記事数が増えてきたらまた対応を考えなければならないかもしれませんが、とりあえずは表示速度改善の意味で満足できる結果になりました。

施策2:コードチャンクの整理(highlight.js)

効果 ★★★★☆

スコア向上の意味ではこれの効果が大きかったです。
Vue CLIで作成したプロジェクトは、yarn run build --reportすると/dist/report.htmlを生成してくれます。webpackでバンドルしたコードチャンクのうちなにが重いか?を可視化してくれる素晴らしいツールです。(単体ではwebpack-bandle-analyzerとして配布されています。有名だから今更かも。)
見ると、highlight.jsが大量のチャンクを吐き出していたので、何事かと思い調べたら、どうやら単にrequireするだけでは不要な言語パックまで引き連れてきてしまうそうです。面倒でもちゃんとregisterLanguageした方がいいみたいね。

// このやり方だと重くなります
import hljs from "highlight.js";

// こうしたらマシです(ちなみにhtmlはデフォルトでハイライトしてくれるっぽい)
import hljs from "highlight.js/libs/highlight";
import javascirpt from "highlight.js/lib/languages/javascript";
import css from "highlight.js/lib/languages/css";

hljs.registerLanguage("javascript", javascript);
hljs.registerLanguage("css", css);

ちなみに、最新バージョンだとregisterLanguageがまともに動いてくれないバグがあるようなので、大人しくバージョンを下げて使いましょう。package.jsonを以下のとおり更新して、yarnもしくはnpm iです。

{
  "dependencies": {
    "highlight.js": "~9.18.1",
    ...
  }
}

施策3:トップページのプレレンダリング(prerender-spa-plugin)

効果 ★☆☆☆☆

SPA高速化の手段の一つとして代表的なのがプレレンダリングですが、私の場合はそもそもHTMLの構造が複雑なために速度低下を招いていたわけではなさそうなので、これはあまり効果がありませんでした。また、ブログという性質上ルーティングが動的に変わるので、とりあえずよく参照されるトップページだけこの対応を埋め込みましたが、全てのブログページをプレレンダリングするのはやや設定が面倒そうな印象でした。
(ちょっと頑張ればやれなくもなさそうですが、記事数が増えるに従ってビルド時間がとんでもないことになりそうな気がしています。)
ページ数が限定されているSPAなら、やってみるのは大いにアリなのではないでしょうか。

施策4:ルーティングコンポーネントの非同期読み込み

効果 ★★★☆☆

これもなかなか良い感じに速度を改善してくれました。
メインの領域はVue Routerで切り替えているのですが、どのルートにアクセスしても、直近で必要のないJSを読み込んでしまっていたようなので、とりあえずはそのルートに必要なJSだけ呼び出してもらうように設定しました。Vue Router公式ドキュメントが参考になりましたが、要するにrouterの処理を書いているファイル内のコンポーネントのimport文を、なにも考えずに書き換えれば良いです。

// before..最初に全部読み込み
import HogeComponent from "@/components/HogeComponent.vue";

// after ..遷移時に必要に応じて読み込み
const HogeComponent = () => import("@/components/HogeComponent.vue");

Nuxt.jsとかGridsome、VuePressに移行したい

施策3でプレレンダリングを試してみたとはいえ、結局のところは静的サイトジェネレータに対応した上記のライブラリを使った方がよいのでは?感が拭えない結果となりました。Lighthouseのスコアも50止まりですしね。大人しく、JAMStackな設計のベストプラクティスに従ったほうがよさそうだ、というなんともしょっぱい結論となり恐縮です。
Nuxtとか使う時はまた記事書きます。ではでは。

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

PubNubで5分でリアルタイムWebこと初め + MQTTでデバイス連携も #protoout #iotlt #ヒーローズリーグ

PubNubを使うと簡単にリアルタイムWeb的なものを作れます。老舗サービスですが今更ながらまとめてみます。IoTLTで紹介したやつですね。

#ヒーローズリーグのハッカソンでPubNubを触ったので改めてメモ。

M5Stackハッカソンでやった内容メモです。

とりあえず簡単なリアルタイムWebとMQTTの利用を試してみます。

5分で、って書いたのは一旦ブラウザ上だけで試すところくらいまでです。

SDKの数が多いので色々なアプリケーションの接着剤的に使えるかなと思います。

PubNubとは

PUbNubはリアルタイムなメッセージングやMQTTなどの裏側の仕組みを肩代わりしてくれるBaaSサービスです。

リアルタイムチャットなどでWebsocketやSocket.ioのサーバーを自前で用意しなくても、デバイスとのやりとりでMQTTブローカーを自前で用意しなくても良いという便利サービスです。

しかもけっこう無料で使えます。(そんなに調べてない)

アプリケーション作成し、Pub/Subキーの取得

まずはアカウント作成しましょう。

https://www.pubnub.com/

  • ログイン後の画面

スクリーンショット 2020-09-21 23.50.38.png

  • APP作成、 CREATE NEW APPから

今回M5Stackで使いたかったのでfor_m5stackというアプリ名にしました。Chat App
Other Messaging Use Cases
が選択できますが、 Other Messaging Use Casesを選択します。

まず試すだけだとこちらの方が入りやすいです。

作成したアプリを選択するとPublish KEYSubscribe Keyがわかります。

Publish KeySubscribe KEYを記録しておきましょう。

ブラウザで試す

まずはブラウザで簡単なメッセージングを試します。

PubNubはWebsocketの接続を提供してくれます。

片方のブラウザを操作すると、もう片方のブラウザでも反応がある といった仕組みをコピペで作ってみます。

HTMLファイルの作成

index.htmlを作成、VScodeなどで作成しましょう。

キーは適宜変更しましょう。

<!DOCTYPE html>
<html>
<head>
  <title>Publish Subscribe Tutorial</title>
</head>

<body>
<input id="publish-button" type="submit" value="Click here to Publish"/>
</body>

<script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.21.7.min.js"></script>
<script>

  const uuid = PubNub.generateUUID();
  const pubnub = new PubNub({
    publishKey: "pub-c-xxxxxxxxxxxxxxx",
    subscribeKey: "sub-c-xxxxxxxxxxxxxxx",
    uuid: uuid
  });

  const button = document.getElementById('publish-button');

  button.addEventListener('click', () => {
    pubnub.publish({
      channel : "pubnub_onboarding_channel",
      message : {"sender": uuid, "content": "Hello From JavaScript SDK"}
    }, function(status, response) {
      //Handle error here
    });
  });

  pubnub.subscribe({
    channels: ['pubnub_onboarding_channel'],
    withPresence: true
  });

  pubnub.addListener({
    message: function(event) {
      let pElement = document.createElement('p');
      pElement.appendChild(document.createTextNode(event.message.content));
      document.body.appendChild(pElement);
    },
    presence: function(event) {
      let pElement = document.createElement('p');
      pElement.appendChild(document.createTextNode(event.uuid + " has joined. That's you!"));
      document.body.appendChild(pElement);
    }
  });

  pubnub.history(
    {
      channel: 'pubnub_onboarding_channel',
      count: 10,
      stringifiedTimeToken: true,
    },
    function (status, response) {
      let pElement = document.createElement('h3');
      pElement.appendChild(document.createTextNode('historical messages'));
      document.body.appendChild(pElement);

      pElement = document.createElement('ul');
      let msgs = response.messages;
      for (let i in msgs) {
        msg = msgs[i];
        let pElement = document.createElement('li');
        pElement.appendChild(document.createTextNode('sender: ' + msg.entry.sender + ', content: ' + msg.entry.content));
        document.body.appendChild(pElement);
      }
    }
  );

</script>
</html>

試す

Live Serverなどでローカルサーバーを起動させてアクセスします。

こんな表示になります。そして 複数のブラウザでアクセスし、片方のボタンを押すと、もう片方にも反応があれば成功です。

ブラウザ(クライアント)間でのメッセージングが出来ている状態です。

同じパソコン内でやってるとイメージがつきにくいかもしれないですが、別のパソコンやスマホ同士でも問題なく動作します。

おまけ: MQTTも試す

ブラウザではWebSocketですが、デバイスに接続する際はMQTTも利用できます。

mosquitto_pubとmosquitto_sub でMQTT確認

ここまでで一旦のメッセージングは

インストールはbrewで(macのみ)

$ brew install mosquitto

Subscribe(mosquitto_sub)

n0bisukeがクライアントID。任意の文字列で大丈夫。
m5stackがトピック名。これも任意の文字列で大丈夫。

$ mosquitto_sub -h mqtt.pndsn.com \
-t 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/m5stack' \
-i 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/n0bisuke' \

Publish(mosquitto_pub)

n0bisukeがクライアントID。任意の文字列で大丈夫。
m5stackがトピック名。これも任意の文字列で大丈夫。

$ mosquitto_pub -h mqtt.pndsn.com \
-t 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/m5stack' \
-i 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/n0bisuke' \
-m 'ほげほげ'

ためす

mosquitto_subを起動した状態で、mosquitto_pubを実行すると、-mで送ったメッセージ(今回はほげほげ)がmosquitto_sub側に表示されます。

Arduinoで試す

ESP32用ですが、Arduinoで試す方法もこちらに簡単にまとめてます。

M5Atom(Arduino)とPubNubでMQTTのサンプル #ヒーローズリーグ #protoout

ブラウザ+デバイス(MQTT)

ブラウザとブラウザ(WebSocket)、デバイスとデバイス(MQTT)を試しましたが、ブラウザ+デバイスを試します。

先ほどのブラウザのコードを変えずに試してみます。

  • ブラウザ -> デバイス

元々のコードでチャンネル(MQTT的にはトピック)がpubnub_onboarding_channelとなっていました。なのでmosquitto_subでは以下のように設定して試してみましょう。

$ mosquitto_sub -h mqtt.pndsn.com \
-t 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/pubnub_onboarding_channel' \
-i 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/n0bisuke' \

ブラウザのボタンを押すと、mosquitto_subに反応があります。

  • デバイス -> ブラウザ

デバイスからブラウザに送るときはmosquitto_pubで{"content":"Hello!!! from MQTT!"}といったメッセージを送るとブラウザ側にメッセージが表示されます。

$ mosquitto_pub -h mqtt.pndsn.com \
-t 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/pubnub_onboarding_channel' \
-i 'pub-c-xxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxx/n0bisuke' \
-m '{"content":"Hello!!! from MQTT!"}'

まとめ

  • ブラウザ+ブラウザ
  • デバイス+デバイス
  • ブラウザ+デバイス (双方向)

を簡単に紹介しました。デバイスと言いつつMac上のMQTTクライアントなので実際にはデバイス側コードは各自試してみてください。

HTTPだけしか知らないと、デバイス+デバイスや、ブラウザ->デバイスはなかなか難しいのでIoT系の何かを作るときにも使えますし、デバイス無しでチャットアプリみたいなものを作るときも使えると思います。

使いこなしていきたい。

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

カウントダウンタイマーの実装

はじめに

ポートフォリオで実装予定のカウントダウンタイマーを実装する。
タイマーを実装するにはjQueryが必要と書いてある記事もあるが、今回はjQueryを利用せずに実装を進めていくことにする。

カラムの作成

まずは、いつものように

rails generate migration Addカラム名Toテーブル名 カラム名:データ型

でマイグレーションファイルを作成する。
今回の場合、制限時間を表示させる場所を表示させたいのでdeadlineをカラム名として、

rails generate migration AddDeadlineToMission deadline:datetime

とする。

class CreateMissions < ActiveRecord::Migration[6.0]
  def change
    create_table :missions do |t|
      t.integer :user_id
      t.text    :content
      t.string  :penalty
      t.datetime   :deadline


      t.timestamps
    end
  end
end

上記のマイグレーションファイルの7行目にt.datetime :deadlineを追加する。
そして、親の顔より見た

$ rails db:migrate

を実行して、データベースに保存する。

タイマーの実装

スクリーンショット 2020-09-22 0.33.23.png

HTMLの記述

上記のような赤字のタイマーを設定するには、

app/views/missions/new.html.erb
 <p>
     <%= f.hidden_field :deadline, :id => "deadline.id" %>
        <input type="text" id="userYear" >年
        <input type="text" id="userMonth">月
        <input type="text" id="userDate" >日
        <input type="text" id="userHour" >時
        <input type="text" id="userMin"  >分
        <input type="text" id="userSec"  >秒
 </p>
       <p id="RealtimeCountdownArea" ></p> #ここにタイマーが表示される

とする。
ここで、:id => deadline.id は後のjavascriptの記述において効果を発揮するため、記述している。

javascriptの記述

今回は、app/views/missions/new.html.erbのscriptタグにjavascriptを記述することとする。
以下の通りである。

app/views/missions/new.html.erb
<script>

function set2fig(num) {
   // 数値が1桁だったら2桁の文字列にして返す
   var ret;
   if( num < 10 ) { ret = "0" + num; }
   else { ret = num; }
   return ret;
}
function isNumOrZero(num) {
   // 数値でなかったら0にして返す
   if( isNaN(num) ) { return 0; }
   return num;
}
function showCountdown() {
   // 現在日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var nowDate = new Date();
   var dnumNow = nowDate.getTime();
 
   // 指定日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var inputYear  = document.getElementById("userYear").value;
   var inputMonth = document.getElementById("userMonth").value - 1;
   var inputDate  = document.getElementById("userDate").value;
   var inputHour  = document.getElementById("userHour").value;
   var inputMin   = document.getElementById("userMin").value;
   var inputSec   = document.getElementById("userSec").value;
   var targetDate = new Date( isNumOrZero(inputYear), isNumOrZero(inputMonth), isNumOrZero(inputDate), isNumOrZero(inputHour), isNumOrZero(inputMin), isNumOrZero(inputSec) );
   var dnumTarget = targetDate.getTime();
 
   // 表示を準備
   var dlYear  = targetDate.getFullYear();
   var dlMonth = targetDate.getMonth() + 1;
   var dlDate  = targetDate.getDate();
   var dlHour  = targetDate.getHours();
   var dlMin   = targetDate.getMinutes();
   var dlSec   = targetDate.getSeconds();
   var msg1 = "期限の" + dlYear + "/" + dlMonth + "/" + dlDate + " " + set2fig(dlHour) + ":" + set2fig(dlMin) + ":" + set2fig(dlSec);
 
   // 引き算して日数(ミリ秒)の差を計算
   var diff2Dates = dnumTarget - dnumNow;
   if( dnumTarget < dnumNow ) {
      // 期限が過ぎた場合は -1 を掛けて正の値に変換
      diff2Dates *= -1;
   }
 
   // 差のミリ秒を、日数・時間・分・秒に分割
   var dDays  = diff2Dates / ( 1000 * 60 * 60 * 24 );   // 日数
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 * 24 );
   var dHour  = diff2Dates / ( 1000 * 60 * 60 );   // 時間
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 );
   var dMin   = diff2Dates / ( 1000 * 60 );   // 分
   diff2Dates = diff2Dates % ( 1000 * 60 );
   var dSec   = diff2Dates / 1000;   // 秒
   var msg2 = Math.floor(dDays) + "日"
            + Math.floor(dHour) + "時間"
            + Math.floor(dMin) + "分"
            + Math.floor(dSec) + "秒";
 
   // 表示文字列の作成
   var msg;
   if( dnumTarget > dnumNow ) {
      // まだ期限が来ていない場合
      msg = msg1 + "までは、あと" + msg2 + "です。";
   }
   else {
      // 期限が過ぎた場合
      msg = msg1 + "は、既に" + msg2 + "前に過ぎました。";
   }
 
   // 作成した文字列を表示
   document.getElementById("RealtimeCountdownArea").innerHTML = msg;
   document.getElementById("deadline.id").value =  targetDate; #最重要記述

}
// 1秒ごとに実行
setInterval('showCountdown()',1000);


</script>

ここで、先程の:id => deadline.id が活きてくる。このような記述を追加することで初めて、
javascriptで処理された結果をvalue(値)として受け取り、それを画面上で表示させることができる。これでようやく、タイマーを実装することができる。

タイマーだけを表示させたい場合

なお、日時の記入欄を表示させたくない場合も考えられる。下の写真のように期限とタイマーの残り時間だけを表示させたいという場合には、
スクリーンショット 2020-09-22 0.49.38.png

app/views/missions/show.thml.erb
      <p>期限 <%= @mission.deadline %>
      <br>
        <input type="hidden" id="userYear" value = "<%= @mission.deadline.year %>"  > 
        <input type="hidden" id="userMonth"value = "<%= @mission.deadline.month %>" >
        <input type="hidden" id="userDate" value = "<%= @mission.deadline.day %>" >
        <input type="hidden" id="userHour" value = "<%= @mission.deadline.hour %>" >
        <input type="hidden" id="userMin"  value = "<%= @mission.deadline.min %>" >
        <input type="hidden" id="userSec"  value = "<%= @mission.deadline.sec %>" >
      </p>
      <p id="RealtimeCountdownArea" ></p>

<script>



function set2fig(num) {
   // 数値が1桁だったら2桁の文字列にして返す
   var ret;
   if( num < 10 ) { ret = "0" + num; }
   else { ret = num; }
   return ret;
}
function isNumOrZero(num) {
   // 数値でなかったら0にして返す
   if( isNaN(num) ) { return 0; }
   return num;
}
function showCountdown() {
   // 現在日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var nowDate = new Date();
   var dnumNow = nowDate.getTime();
 
   // 指定日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var inputYear  = document.getElementById("userYear").value;
   var inputMonth = document.getElementById("userMonth").value - 1;
   var inputDate  = document.getElementById("userDate").value;
   var inputHour  = document.getElementById("userHour").value;
   var inputMin   = document.getElementById("userMin").value;
   var inputSec   = document.getElementById("userSec").value;
   var targetDate = new Date( isNumOrZero(inputYear), isNumOrZero(inputMonth), isNumOrZero(inputDate), isNumOrZero(inputHour), isNumOrZero(inputMin), isNumOrZero(inputSec) );
   var dnumTarget = targetDate.getTime();
 
   // 表示を準備
   var dlYear  = targetDate.getFullYear();
   var dlMonth = targetDate.getMonth() + 1;
   var dlDate  = targetDate.getDate();
   var dlHour  = targetDate.getHours();
   var dlMin   = targetDate.getMinutes();
   var dlSec   = targetDate.getSeconds();
   var msg1 = "期限の" + dlYear + "/" + dlMonth + "/" + dlDate + " " + set2fig(dlHour) + ":" + set2fig(dlMin) + ":" + set2fig(dlSec);
 
   // 引き算して日数(ミリ秒)の差を計算
   var diff2Dates = dnumTarget - dnumNow;
   if( dnumTarget < dnumNow ) {
      // 期限が過ぎた場合は -1 を掛けて正の値に変換
      diff2Dates *= -1;
   }
 
   // 差のミリ秒を、日数・時間・分・秒に分割
   var dDays  = diff2Dates / ( 1000 * 60 * 60 * 24 );   // 日数
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 * 24 );
   var dHour  = diff2Dates / ( 1000 * 60 * 60 );   // 時間
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 );
   var dMin   = diff2Dates / ( 1000 * 60 );   // 分
   diff2Dates = diff2Dates % ( 1000 * 60 );
   var dSec   = diff2Dates / 1000;   // 秒
   var msg2 = Math.floor(dDays) + "日"
            + Math.floor(dHour) + "時間"
            + Math.floor(dMin) + "分"
            + Math.floor(dSec) + "秒";
 
   // 表示文字列の作成
   var msg;
   if( dnumTarget > dnumNow ) {
      // まだ期限が来ていない場合
      msg =  "Mission終了まで、あと" + msg2 ;
   }
   else {
      // 期限が過ぎた場合
      msg = msg1 + "は、既に" + msg2 + "前に過ぎました。";
   }
 
   // 作成した文字列を表示
   document.getElementById("RealtimeCountdownArea").innerHTML = msg;
   document.getElementById("deadline.id").value =  targetDate;

}
// 1秒ごとに実行
setInterval('showCountdown()',1000);

</script>


上記のようにinput type = "hidden"とすれば、記入欄が画面上に表示されないものの、
<input type = ・・・>の6つが削除されている訳ではないためこれで正常に起動する。

まとめ

なかなか難易度が高かったが、達成感はすごかった。少しでも参考にしていただけたら幸いである。
参考記事
https://www.nishishi.com/javascript-tips/realtime-countdown-deadline.html

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

Rails6 カウントダウンタイマーの実装

はじめに

ポートフォリオで実装予定のカウントダウンタイマーを実装する。
タイマーを実装するにはjQueryが必要と書いてある記事もあるが、今回はjQueryを利用せずに実装を進めていくことにする。

作成順序

具体的な手順としては
①制限時間のカラムであるdeadlineを作成する
②タイマーを画面上に表示させるため、HTMLの記述をする
③制限時間を表示させたいので、javascriptを使って記述を行う

カラムの作成

まずは、いつものように

$rails generate migration Addカラム名Toテーブル名 カラム名:データ型

でマイグレーションファイルを作成する。

今回の場合、投稿部分に制限時間を表示させたいのでdeadlineをカラム名として、

$rails generate migration AddDeadlineToMission deadline:datetime

とする。

db/migrate/20200903084112_create_missions.rb
class CreateMissions < ActiveRecord::Migration[6.0]
  def change
    create_table :missions do |t|
      t.integer :user_id
      t.text    :content
      t.string  :penalty
      t.datetime   :deadline


      t.timestamps
    end
  end
end

上記のマイグレーションファイルの7行目にt.datetime :deadlineを追加する。
そして、親の顔より見た

$ rails db:migrate

を実行して、データベースに保存する。

タイマーの実装

スクリーンショット 2020-09-22 0.33.23.png

HTMLの記述

上記のような赤字のタイマーを設定するには、

app/views/missions/new.html.erb
<p>
     <%= f.hidden_field :deadline, :id => "deadline.id" %>
        <input type="text" id="userYear" >年
        <input type="text" id="userMonth">月
        <input type="text" id="userDate" >日
        <input type="text" id="userHour" >時
        <input type="text" id="userMin"  >分
        <input type="text" id="userSec"  >秒
 </p>
       <p id="RealtimeCountdownArea" ></p> #ここにタイマーが表示される

とする。
ここで、:id => deadline.id は後のjavascriptの記述において効果を発揮するため、記述している。

javascriptの記述

今回は、app/views/missions/new.html.erbのscriptタグにjavascriptを記述することにする。

以下の通りである。

app/views/missions/new.html.erb
<script>

function set2fig(num) {
   // 数値が1桁だったら2桁の文字列にして返す
   var ret;
   if( num < 10 ) { ret = "0" + num; }
   else { ret = num; }
   return ret;
}
function isNumOrZero(num) {
   // 数値でなかったら0にして返す
   if( isNaN(num) ) { return 0; }
   return num;
}
function showCountdown() {
   // 現在日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var nowDate = new Date();
   var dnumNow = nowDate.getTime();
 
   // 指定日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var inputYear  = document.getElementById("userYear").value;
   var inputMonth = document.getElementById("userMonth").value - 1;
   var inputDate  = document.getElementById("userDate").value;
   var inputHour  = document.getElementById("userHour").value;
   var inputMin   = document.getElementById("userMin").value;
   var inputSec   = document.getElementById("userSec").value;
   var targetDate = new Date( isNumOrZero(inputYear), isNumOrZero(inputMonth), isNumOrZero(inputDate), isNumOrZero(inputHour), isNumOrZero(inputMin), isNumOrZero(inputSec) );
   var dnumTarget = targetDate.getTime();
 
   // 表示を準備
   var dlYear  = targetDate.getFullYear();
   var dlMonth = targetDate.getMonth() + 1;
   var dlDate  = targetDate.getDate();
   var dlHour  = targetDate.getHours();
   var dlMin   = targetDate.getMinutes();
   var dlSec   = targetDate.getSeconds();
   var msg1 = "期限の" + dlYear + "/" + dlMonth + "/" + dlDate + " " + set2fig(dlHour) + ":" + set2fig(dlMin) + ":" + set2fig(dlSec);
 
   // 引き算して日数(ミリ秒)の差を計算
   var diff2Dates = dnumTarget - dnumNow;
   if( dnumTarget < dnumNow ) {
      // 期限が過ぎた場合は -1 を掛けて正の値に変換
      diff2Dates *= -1;
   }
 
   // 差のミリ秒を、日数・時間・分・秒に分割
   var dDays  = diff2Dates / ( 1000 * 60 * 60 * 24 );   // 日数
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 * 24 );
   var dHour  = diff2Dates / ( 1000 * 60 * 60 );   // 時間
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 );
   var dMin   = diff2Dates / ( 1000 * 60 );   // 分
   diff2Dates = diff2Dates % ( 1000 * 60 );
   var dSec   = diff2Dates / 1000;   // 秒
   var msg2 = Math.floor(dDays) + "日"
            + Math.floor(dHour) + "時間"
            + Math.floor(dMin) + "分"
            + Math.floor(dSec) + "秒";
 
   // 表示文字列の作成
   var msg;
   if( dnumTarget > dnumNow ) {
      // まだ期限が来ていない場合
      msg = msg1 + "までは、あと" + msg2 + "です。";
   }
   else {
      // 期限が過ぎた場合
      msg = msg1 + "は、既に" + msg2 + "前に過ぎました。";
   }
 
   // 作成した文字列を表示
   document.getElementById("RealtimeCountdownArea").innerHTML = msg;
   document.getElementById("deadline.id").value =  targetDate; #最重要記述

}
// 1秒ごとに実行
setInterval('showCountdown()',1000);


</script>

ここで、先程の:id => deadline.id が活きてくる。このような記述を追加することで初めて、
javascriptで処理された結果をvalue(値)として受け取り、それを画面上で表示させることができる。これでようやく、タイマーを実装することができる。

タイマーだけを表示させたい場合

なお、日時の記入欄を表示させたくない場合も考えられる。下の写真のように期限とタイマーの残り時間だけを表示させたいという場合には、
スクリーンショット 2020-09-22 0.49.38.png

app/views/missions/show.thml.erb
      <p>期限 <%= @mission.deadline %>
      <br>
        <input type="hidden" id="userYear" value = "<%= @mission.deadline.year %>"  > 
        <input type="hidden" id="userMonth"value = "<%= @mission.deadline.month %>" >
        <input type="hidden" id="userDate" value = "<%= @mission.deadline.day %>" >
        <input type="hidden" id="userHour" value = "<%= @mission.deadline.hour %>" >
        <input type="hidden" id="userMin"  value = "<%= @mission.deadline.min %>" >
        <input type="hidden" id="userSec"  value = "<%= @mission.deadline.sec %>" >
      </p>
      <p id="RealtimeCountdownArea" ></p>

<script>



function set2fig(num) {
   // 数値が1桁だったら2桁の文字列にして返す
   var ret;
   if( num < 10 ) { ret = "0" + num; }
   else { ret = num; }
   return ret;
}
function isNumOrZero(num) {
   // 数値でなかったら0にして返す
   if( isNaN(num) ) { return 0; }
   return num;
}
function showCountdown() {
   // 現在日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var nowDate = new Date();
   var dnumNow = nowDate.getTime();
 
   // 指定日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var inputYear  = document.getElementById("userYear").value;
   var inputMonth = document.getElementById("userMonth").value - 1;
   var inputDate  = document.getElementById("userDate").value;
   var inputHour  = document.getElementById("userHour").value;
   var inputMin   = document.getElementById("userMin").value;
   var inputSec   = document.getElementById("userSec").value;
   var targetDate = new Date( isNumOrZero(inputYear), isNumOrZero(inputMonth), isNumOrZero(inputDate), isNumOrZero(inputHour), isNumOrZero(inputMin), isNumOrZero(inputSec) );
   var dnumTarget = targetDate.getTime();
 
   // 表示を準備
   var dlYear  = targetDate.getFullYear();
   var dlMonth = targetDate.getMonth() + 1;
   var dlDate  = targetDate.getDate();
   var dlHour  = targetDate.getHours();
   var dlMin   = targetDate.getMinutes();
   var dlSec   = targetDate.getSeconds();
   var msg1 = "期限の" + dlYear + "/" + dlMonth + "/" + dlDate + " " + set2fig(dlHour) + ":" + set2fig(dlMin) + ":" + set2fig(dlSec);
 
   // 引き算して日数(ミリ秒)の差を計算
   var diff2Dates = dnumTarget - dnumNow;
   if( dnumTarget < dnumNow ) {
      // 期限が過ぎた場合は -1 を掛けて正の値に変換
      diff2Dates *= -1;
   }
 
   // 差のミリ秒を、日数・時間・分・秒に分割
   var dDays  = diff2Dates / ( 1000 * 60 * 60 * 24 );   // 日数
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 * 24 );
   var dHour  = diff2Dates / ( 1000 * 60 * 60 );   // 時間
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 );
   var dMin   = diff2Dates / ( 1000 * 60 );   // 分
   diff2Dates = diff2Dates % ( 1000 * 60 );
   var dSec   = diff2Dates / 1000;   // 秒
   var msg2 = Math.floor(dDays) + "日"
            + Math.floor(dHour) + "時間"
            + Math.floor(dMin) + "分"
            + Math.floor(dSec) + "秒";
 
   // 表示文字列の作成
   var msg;
   if( dnumTarget > dnumNow ) {
      // まだ期限が来ていない場合
      msg =  "Mission終了まで、あと" + msg2 ;
   }
   else {
      // 期限が過ぎた場合
      msg = msg1 + "は、既に" + msg2 + "前に過ぎました。";
   }
 
   // 作成した文字列を表示
   document.getElementById("RealtimeCountdownArea").innerHTML = msg;
   document.getElementById("deadline.id").value =  targetDate;

}
// 1秒ごとに実行
setInterval('showCountdown()',1000);

</script>


上記のようにinput type = "hidden"とすれば、記入欄が画面上に表示されないものの、<input type = ・・・>の6つが削除されている訳ではないためこれで正常に起動する。

 また、<input type="hidden" id="userYear" value = "<%= @mission.deadline.year %>" >
value="<%= @mission.deadline.year %>"の部分がなぜそのような記述となるかについて説明する。これは、deadlineはdatetimeというデータ型をとるカラムであるのだが、datetimeには年・月・日・時・分・秒といった日時の情報が保存されているからである。したがって、上記のような記述でnew.html.erbの部分で記述した期限の日時が取り出せることになる。

まとめ

なかなか難易度が高かったが、達成感はすごかった。少しでも参考にしていただけたら幸いである。

参考記事
https://www.nishishi.com/javascript-tips/realtime-countdown-deadline.html

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

webpackでReact+Typescriptの環境構築をする

Qiita初投稿です。これからどんどん書いていこうとおもうのでご意見・ご要望ありましたら気軽にコメントしていただけると、自身の成長にもつながるのでよろしくお願いします!

webpackとは?

一言で表すとJavascriptを1つのファイルにまとめる事ができるツールです。
他にも様々な機能がありますが、ここでは割愛します。

1つのファイルにまとめるメリット

1つにまとめることでブラウザとサーバーの通信回数を減り、通信速度が速くなります。

実際にやってみる

実際にwebpackを使って、React+Typescriptの環境構築をしてみましょう。

ディレクトリの作成

まず、ターミナルで以下のコマンドでディレクトリを作り、VSCodeで開きます(ファイル名の部分は好きな名前で大丈夫です)。codeコマンドがない場合は普通にファイル作って、VSCodeで開けば大丈夫です。

ターミナル
mkdir [ファイル名]

code [ファイル名]

何もファイルがない状態だと思います。

必要なパッケージのインストール

次にVSCodeのターミナルで以下のコマンドを打つと、package.jsonが作成されます。

VSCodeのターミナル
//npmを使う場合
npm init -y

//yarnを使う場合
yarn init -y

その後必要なパッケージをダウンロードしていきます。

VSCodeのターミナル
//npm
npm install --save react react-dom

npm install --save-dev @types/react-dom @types/webpack @types/webpack-dev-server ts-loader ts-node typescript webpack webpack-cli webpack-dev-server

//yarn
yarn add react react-dom

yarn add -D @types/react-dom @types/webpack @types/webpack-dev-server ts-loader ts-node typescript webpack webpack-cli webpack-dev-server

インストールしたパッケージの簡単な説明を表にまとめました。

react,react-dom Reactを書くのに必要
@types/~ @typesに続くパッケージの型宣言ファイルが含まれている
ts-loader TypescriptをJavascriptにコンパイルするのに使う
ts-node Typescriptのファイルを直接実行できる
typescript Typescriptを書くのに必要
webpack Javascriptのファイルを1つにまとめる
webpack-cli webpackコマンドを使うのに必要
webpack-dev-server ファイルを変えた時に差分ビルドをしてくれる

webpackの設定

次に以下のコマンドを実行して、webpackの設定ファイルである、webpack.config.tsを作成します。

VSCodeのターミナル
touch webpack.config.ts

作成されたwebpack.config.tsに以下のように記述します。webpack.config.jsでも大丈夫です。その場合はConfigurationの部分を消します。

設定ファイルは勉強も兼ねて、デフォルトと同じになっているところも書いているため、少し冗長な部分があります。

webpack.config.ts
import path from 'path';
import { Configuration } from 'webpack';

const config: Configuration = {
    context: path.join(__dirname, 'src'),
    entry: './index.tsx',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: '/assets',
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
            },
        ],
    },
    mode: "development",
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
    devtool: "inline-source-map",
    devServer: {
        contentBase: path.join(__dirname, 'static'),
        open: true,
        port: 3000,
    },
};

export default config;

webpack.config.ts(js)の設定ファイルの簡単な説明を以下の表にまとめました。

path ファイルやディレクトリのpathを操作できる。デフォルトでnode_modulesに入っている
Configuration configのtype
__dirname カレントディレクトリを示す
output ファイルの出力設定(path:出力ファイルのディレクトリ名、filename:出力ファイル名、publicPath:バンドルファイルをアップロードする場所)
module rules(test:コンパイルするファイル、use:コンパイルに使うツール)
mode 開発(development)か本番(production)か
resolve extensions:importで省略したい拡張子
devtool デバッグ用のツール(mode:develop)
devServer 開発用のサーバー(contentBase:サーバーの起点とするディレクトリ、open:ブラウザを自動で起動するか、port:ポート番号)

Typescriptの設定

次に以下のコマンドを実行して、Typescriptの設定ファイルである、tsconfig.jsonを作成します。

VSCodeのターミナル
touch tsconfig.json

作成されたtsconfig.jsonに以下のように記述します。

tsconfig.json

    "compilerOptions": {
        "sourceMap": true,
        "baseUrl": "./",
        "target": "es5",
        "strict": true,
        "module": "commonJs",
        "jsx": "react",
        "lib": ["ES5", "ES6", "DOM"],
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "isolatedModules": true,
    }
}

tsconfig.jsonの設定の簡単な説明を以下の表にまとめました。

sourceMap ソースマップを見れるようにするか
baseUrl tsconfig.jsonの場所
target どのバージョンでJavascriptを出力するか
strict 型付けのルールを厳しくする
module Typescriptのモジュールをどのバージョンで出力するか
jsx jsxの書式を有効化
lib コンパイルに使用する組み込みライブラリ
allowSyntheticDefaultImports default importを使うか
esModuleInterop import * 以外も使えるようにするか
isolatedModules exportを必須にするか

次に以下のコマンドでsrcディレクトリを作り、その中にindex.tsxを作成します。

VSCodeのターミナル
mkdir src && touch src/index.tsx

作成したsrc/index.tsxに以下のように記述します。

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<h1>Hello World!</h1>, document.getElementById('app'));

次に以下のコマンドでstaticディレクトリを作成し、その中にindex.htmlを作成します。

VSCodeのターミナル
mkdir static && static/index.html

作成したstatic/index.htmlに以下のように記述します。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <div id="app"></div>
        <script src="/assets/bundle.js"></script>
    </body>
</html>

 実行

これで最低限の環境は整いました。
VSCodeのターミナルで以下のコマンドを実行します。

VSCodeのターミナル
//npm
npx webpack-dev-server

//yarn
yarn webpack-dev-server

するとlocalhost:3000がブラウザで開かれて、Hello World!が表示されると思います。

image.png

ここまで読んでいただきありがとうございます。少しでもwebpackについてイメージできたら嬉しいです。
今後も記事を書いていこうと思うので感想などいただけるとモチベーションにつながります。

参考記事

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