- 投稿日:2020-07-03T22:43:24+09:00
なぜ、Vue Composition APIを使うのか、理解する
はじめに
Vue3のリリースが迫ってきました。
順調に行けば、公式リリースは8月上旬のようです。楽しみですね。Our current target dates are mid July for the RC (release candidate) and early August for the official release of 3.0.
https://github.com/vuejs/rfcs/issues/183Vue3の目玉の機能として、「Composition API」があります。
この記事では、「”なぜ” Composition APIを使うのか」について、まとめています。
「Composition APIの使い方」についての説明は、この記事では割愛します。
Composition APIを使う理由、なんもわからん
これまで、私自身、Composition APIを使う理由が、よくわかっていませんでした。
なんとなく、「setup関数でまとめて処理を登録しておく」くらいのことを雰囲気で理解していました。
「Composition APIで書くほうがイケてるし、カッコいい(重要)」くらいのノリで
メリットもよく分からない状態でした。
Composition APIについて調べていると、↑の図、よく見ませんか?
既存APIとComposition APIのコードを比較して、メリットを提示しているようですけど
私は
「そんなに違わないように見える」
「違いがよくわからん」
っといった感想を持ちました。
私と同じように
「Vueに新しく、Composition APIってのが出るらしいけど、なんとなく使おうかな〜」
「でも、なんのために使うんだっけ」
と思っている方が多いのではないかなと考え、この記事を執筆しています。
この記事が、誰かのお役に立てれば幸いです。
Composition API RFC を読んだら、「なぜ、使うのか」を理解した
先日 @shwld が主催した
「Vue Composition APIを知る会」
で、Composition APIのRFCを読む機会があり、そこで改めて「”なぜ” Composition APIを使うのか」理解することができました。
Composition APIの公式の説明資料として、以下のページにRFCがあります。
Composition API (https://vue-composition-api-rfc.netlify.com)↑を読むと、「”なぜ” Composition APIを使うのか」答えが書いてありました。
以下にRFCに書いてある内容をまとめます。
Composition APIとは
Composition APIは、
ロジックを合成関数(composition function)にカプセル化することで、コンポーネント間でのロジックを再利用を可能にするAPIです。
(React HooksのVue版みたいなもの)サンプルコード
<template> <button @click="increment"> Count is: {{ state.count }}, double is: {{ state.double }} </button> </template> <script> import { reactive, computed } from 'vue' export default { setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) }) function increment() { state.count++ } return { state, increment } } } </script>Vueの作成者であるEvan You氏いわく、Composition APIを提案する背景となった動機について、次のように説明しています。
論理合成(logic composition)は、プロジェクトをスケールアップする場合には、おそらく最も深刻な問題のひとつです。さまざまなタイプのプロジェクトを扱うユーザは、さまざまなニーズに直面します。その中には、オブジェクトベースのAPIを使用して簡単に処理できるものと、できないものがあります。主な例としては、
1. 複数の論理タスクをカプセル化した、大規模な(数百行の)コンポーネント
2. 複数のコンポーネント間においてタスクにロジックを共有するニーズhttps://www.infoq.com/jp/news/2019/10/vue3-function-based-api-rfc/ より引用
”なぜ” Composition APIを使うのか
結論から言いますと、
「”なぜ” Composition APIを使うのか」
その理由は、
「ロジックの抽出と再利用」をするためです。
(他にも「TypeScript 型強化」などが理由として挙げられますが、この記事では割愛します)
ロジックの抽出と再利用
「ロジックの抽出と再利用」は、なぜするのか。
「ロジックの抽出と再利用」が必要な理由は、
複雑に肥大化したコンポーネントを、小分けにして関心事で分別し、クリーンな状態に整理するためです。近年、Vue を使用して大規模なプロジェクトを構築することが増えました。
肥大化してメンテしにくいコンポーネントを目の当たりにして、我々エンジニアが苦しむことも増えました。
肥大化したコンポーネントのコードは、依存関係が複雑で量も多く、コードを読むこと自体難しくなります。
これまでのVue2.xでは、「複数のコンポーネント間でロジックを抽出して再利用するためのクリーンでコストのかからないメカニズム」が欠如していました。
これを解決するのが、Composition APIです。
Composition APIによって、コンポーネントのコードを整理する際に、開発者により高い柔軟性を提供します。
コンポーネント間で、ロジックを抽出して再利用することが、簡単になります。つまり、Composition APIは、肥大化したコンポーネントを小さく切り刻むための聖剣なのです。
Composition APIを使って「ロジックを抽出して再利用する」具体的なやり方について、以下の記事が参考になります。
vue-composition-apiを使って機能単位のコード分割と合成を試してみた
https://qiita.com/s_nagasawa/items/ef70032f996face318e5逆に言えば、100行にも満たないような小さなコンポーネントをComposition APIで書き直しても、あまりメリットを享受できないです。
もし小規模〜中規模なVueプロジェクトにおいて、現状のVue2.xのクラスベースのコードでコンポーネントの肥大化に苦しんでいないのであれば、Composition APIで書き直してもコスパが悪いのでやめておいたほうがよいのではないかなと思います。
(新規プロダクトで、最初から全部Composition APIで書くのはありだと思います)mixinは、使えない?
Vue2.xのロジックの再利用の仕組みとして、「mixin」があります。
しかし、「mixin」には、罠が多くあり、今では「mixin」を使うことはアンチパターンという認識です。
極力「mixin」は使わないようにしましょう。なぜmixinがアンチパターンなのかについては、下記の記事に詳しく書かれてあります。
俺がやらかしたVue mixinのアンチパターンから学ぶmixinの使い方と代替案
https://aloerina01.github.io/blog/2018-12-25-1Composition APIを使う際のデメリット
Composition APIを使うメリットは、理解できたかと思います。
じゃあ、デメリットはないのか?
あります。
デメリットを一言でいうと、「自由すぎ」です。
Composition APIによって、自由にロジックを切り出せるようになりますが、一方で、いままでVueが暗黙的に行ってくれていたレールから外れることを意味します。
どういう単位でロジックを切り出したり、共通化したり、といった疑問に対して、明確な正解がないのです。
つまり、Vueが管理している壁の中の世界から、壁の外の無秩序な荒野へと放り出されるのです。
無秩序な荒野で、テキトーに開発していると、隠れていた猛獣に襲われたり、食料が尽きたりといった致命的なアクシデントに見舞われます。計画的に外の世界を開拓しないと危険がいっぱいなのです。
「設計」や「アーキテクチャ」が重要になる
その荒野に秩序を与えるのは、我々、調査兵団(エンジニア)の「設計」です。
今後は、「アーキテクチャ」が重要になります。
つまり、デメリットは、ちゃんと「設計」や「アーキテクチャ」を考えなければならないことです。
例えば、「クリーンアーキテクチャ」を理解して、実装していくことが求められます。
実装クリーンアーキテクチャ
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8「Vueの中の世界」と「外の世界」を意識して、自由への翼を獲得しましょう。
まとめ
・https://vue-composition-api-rfc.netlify.com を読もう。
・Composition APIを使う理由は、「ロジックの抽出と再利用」のため。(TypeScriptの型強化の側面もあり)
・肥大化したコンポーネントを、小さく切り刻むための聖剣だと理解した。(mixin、お前はダメだ)
・もともと小さいコンポーネントをCompositon APIで書き直しても、あまりメリットはない。
・今後はちゃんと「設計」や「アーキテクチャ」を考えなければならない。
参考リンク
Composition API RFC
https://vue-composition-api-rfc.netlify.comvue-composition-apiを使って機能単位のコード分割と合成を試してみた
https://qiita.com/s_nagasawa/items/ef70032f996face318e5俺がやらかしたVue mixinのアンチパターンから学ぶmixinの使い方と代替案
https://aloerina01.github.io/blog/2018-12-25-1実装クリーンアーキテクチャ
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8【Vue3に備える】実務で使うComposition APIについて考える
https://medium.com/finatext/composition-api-for-vue3-63631dbadcef先取りVue 3.x !! Composition API を試してみる
https://qiita.com/ryo2132/items/f055679e9974dbc3f977Composition API Demo
https://github.com/LinusBorg/composition-api-demosAPI Reference
https://composition-api.vuejs.org/api.htmlVue Composition API v1-beta で使えるリアクティブ関連のAPI一覧
https://qiita.com/ryo2132/items/6dc51ede8082dea75812Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか?
https://qiita.com/tmy/items/a545e44100247c364a71Composition API 勘所など
https://webneko.dev/posts/notices-of-composition-api-in-vue3-eve
- 投稿日:2020-07-03T22:41:47+09:00
JavascriptでiPhone風の電卓アプリを作ってみた
こんにちは、Mottyです。
今回はJavascriptを使ってみました。概要
今回はJavascriptの処女作で電卓アプリを作ってみました。
定番ですが、HTML・JavascriptによるWebアプリ作品です。ソースコード
Calculator.html<!DOCTYPE html> <html lang = "en"> <head> <meta charset="UTF-8"> <meta name="viewport" content = "width = device-width, instial-sale = 1.0"> <meta http-equiv= "X-UA-Compatible" content = "ie=edge"> <title> Document</title> <link rel = "stylesheet" href="Design.css"> </head> <body> <script src = "System.js"></script> <input id ="result" type = "text" size ="20" style = "width:208px"/> <br/> <input type = "button" value = "7" onClick ="disp(7)"/> <input type = "button" value = "8" onClick ="disp(8)"/> <input type = "button" value = "9" onClick ="disp(9)"/> <input type = "button" value = "/" onClick ="disp('/')"/> <br/> <input type = "button" value = "4" onClick ="disp(4)"/> <input type = "button" value = "5" onClick ="disp(5)"/> <input type = "button" value = "6" onClick ="disp(6)"/> <input type = "button" value = "*" onClick ="disp('*')"/> <br/> <input type = "button" value = "1" onClick ="disp(1)"/> <input type = "button" value = "2" onClick ="disp(2)"/> <input type = "button" value = "3" onClick ="disp(3)"/> <input type = "button" value = "-" onClick ="disp('-')"/> <br> <input type = "button" value = "0" onClick ="disp(0)"/> <input type = "button" value = "C" onClick ="clear()"/> <input type = "button" value = "=" onClick ="enter()"/> <input type = "button" value = "+" onClick ="disp('+')"/> </body> </html>Design.cssinput{ width:50px; height:50px; } textbox{ width:200px; height:50px; }System.jsvar EnterFlag = false; function disp(n){ var obj = document.getElementById("result"); obj.value += n; } function enter(){ var obj = document.getElementById("result"); obj.value = eval(obj.value); EnterFlag = true; } function clear(){ var obj = document.getElementById("result"); obj.value == "0"; }結果
しっかり表示してくれました。
パーツとメソッドの繋ぎ方がポイントということを分かっていれば
プログラミング言語が変わっても物自体はちゃんと作れそう。
(しかし配列とかクラスを使えばもっと綺麗なコードをかけたのでは・・・。)
- 投稿日:2020-07-03T22:34:10+09:00
メッセージ送信の非同期化
フォームが送信されたら、イベントが発火するようにしよう
この記述の解説をします。
$(**)には、formのクラス名を記述します。
.on(**,にはイベント名を記述します。
e.preventDefaulでは、非同期通信を行う為にデフォルトのイベントを止めています。イベントが発火したときにAjaxを使用して、messages#createが動くようしましょう
この記述の中のthisは、イベントの発火元であるFormの情報が入っています。
$(this).attr('action');は、Form情報のパスを取得しています。messagesコントローラーの#createアクションでメッセージを保存し、respond_toを使用してJSON形式のリクエストに対してのレスポンスを返せるようにしましょう
if @message.save
リクエストで送られてきた情報を保存している
respond_to do |format|
format.json
json方式で返しているその他アウトプット
クラス名MessageFieldにappend(html)でHTMLを追加している$(".submit-btn").prop('disabled', false);
送信ボタンを一度押すとリロードしないと押せなくなるが
prop('disabled', false);を送信ボタンクラスに記述する事によりロードせずに投稿ができる様になる非同期に失敗した場合の処理
- 投稿日:2020-07-03T22:21:57+09:00
Kinx ライブラリ - パーサ・コンビネータ(その1)
Kinx ライブラリ - パーサ・コンビネータ(その1)
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。今回はパーサ・コンビネータです。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
前回 JIT ライブラリ を紹介しましたが、最後以下の言葉で締めくくりましたね。
これでパーサ・コンビネータとか実装して組み合わせたらたら、ちょっとした JIT 付き言語処理系が作れますね。
ええ、そこで急遽作りましたよ。パーサ・コンビネータ・ライブラリ。その名も Parsek。Parsec ならぬ Parsek。
インタフェースは Parsimmon を参考にしましたが、実装は全くの独自です。API はこんなに充実してませんが、それなりに使えます。インタフェースを追加するのは簡単なので、追々必要に応じて追加しよう。
長くなりそうなので記事を 2 回に分けようかと思います。
今回はこれを使って、四則演算文法をパースして AST(Abstract Syntax Tree = 抽象構文木)を作るところまでいきましょう。次回、最後には JIT コンパイルして実行するところまでいきます。
パーサ・コンビネータとは
小さい(単純な)パーサーを組み合わせて大きなパーサーを作るためのライブラリ。詳しくは他の記事に譲るとして、以下、サンプルを見ながらわかるようにしてみましょう。
サンプル
今回は趣向を変えて、サンプルを使いながら説明してみます。サンプルは正の整数(自然数)による四則演算です。簡単のために負の数は扱いません(結果が負になることはあり得ます)。
普通はここで BNF とか PEG とかの説明に入るのでしょうが、無視します。サンプルを通してまず動かすところからスタートです。
using Parsek
パーサ・コンビネータ・ライブラリは標準組み込みではないので、using しましょう。また、ライブラリはクラスとして提供されているのでインスタンス化しておきましょう。
using Parsek; var $ = new Parsek();何気に
$
は変数名として使えます。小さな(単純な)パーサーとは?
一つずつ例を挙げてみます。
数値をパースするパーサー
まず、正の整数を定義してみましょう。これが一つ目の小さな(単純な)パーサーです。一つ目ですが、いきなり正規表現です。まぁ、それほど難しくないのでわかりやすいでしょう。
ひとつだけ落とし穴なのは、使っているエンジン(=鬼車)が POSIX NFA ではない ので、長くマッチするほうを先に書かないといけません。簡単に言うと、以下の例では
"123"
はきちんと"123"
でマッチしますが、逆(/[0-9]|[1-9][0-9]*/
)に書くと最初に書いた[0-9]
にマッチして検索をやめてしまうので"1"
となって"23"
にマッチしません。注意しましょう。var number = $.regex(/[1-9][0-9]*|[0-9]/);これでこの
number
というパーサーは数値(が書かれた文字列)をパースできるようになります。やってみましょう。実際にパースを行うのは
parseAll()
メソッドです。parse()
というのもありますが、これは途中で終了しても成功するメソッドで、通常は内部で使われます。parseAll()
の場合は全て解析し終わって後処理まで実施して結果を返します。using Parsek; var $ = new Parsek(); var number = $.regex(/[1-9][0-9]*|[0-9]/); System.println(number.parseAll("0")); // => {"position":1,"status":1,"value":"0"} System.println(number.parseAll("10")); // => {"position":2,"status":1,"value":"10"} System.println(number.parseAll("129")); // => {"position":3,"status":1,"value":"129"} System.println(number.parseAll("abc")); // => {"position":0,"status":0,"value":null} System.println(number.parseAll("0129")); // => {"position":1,"status":0,"value":null}復帰値の
position
はパースした文字列の完了位置で、status
が成功・失敗(1 が成功)、value
が実際にパースが成功した文字列です。見て分かる通り、失敗するとvalue
はnull
です。しかしよく見ると
value
は文字列ですね。文字列を解釈しているだけなので当たり前です。ここで、value
に対して変換を行うメソッドが.map()
です。以下のように変換用の関数を与えます。using Parsek; var $ = new Parsek(); var number = $.regex(/[1-9][0-9]*|[0-9]/).map(&(value) => Integer.parseInt(value)); System.println(number.parseAll("129")); // => {"position":3,"status":1,"value":129}数値になりましたね。上記の場合、単に値をパススルーしているだけなので、
Integer.parseInt
を直接渡しても同じです。var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt);このほうが簡潔ですね。
四則演算の演算子をパースするパーサー
演算子によって優先度が違うので 2 つに分けます。
+
または-
*
または/
または%
一文字の or を解釈するのに便利なのが
$.oneOf()
です。以下のように使います。var addsub = $.oneOf("+-"); var muldiv = $.oneOf("*/%");簡単ですねー。早速試してみましょう。
using Parsek; var $ = new Parsek(); var addsub = $.oneOf("+-"); var muldiv = $.oneOf("*/%"); System.println(addsub.parseAll("+")); // => {"position":1,"status":1,"value":"+"} System.println(addsub.parseAll("-")); // => {"position":1,"status":1,"value":"-"} System.println(addsub.parseAll("*")); // => {"position":0,"status":0,"value":null} System.println(muldiv.parseAll("*")); // => {"position":1,"status":1,"value":"*"} System.println(muldiv.parseAll("/")); // => {"position":1,"status":1,"value":"/"} System.println(muldiv.parseAll("%")); // => {"position":1,"status":1,"value":"%"} System.println(muldiv.parseAll("a")); // => {"position":0,"status":0,"value":null}期待通りです。
カッコをパースするパーサー
もう一つ、数値演算には必要なカッコを解釈させましょう。特定の文字列にマッチするパーサーは
$.string()
を使います。ここでは 1 文字ですが、何文字の文字列でも OK です。var lbr = $.string("("); var rbr = $.string(")");これも試してみるとうまく動きます。
$.string()
の効果を見るために別の文字列でも試してみましょう。using Parsek; var $ = new Parsek(); var lbr = $.string("("); var rbr = $.string(")"); var hoge = $.string("hoge"); System.println(lbr.parseAll("(")); // => {"position":1,"status":1,"value":"("} System.println(lbr.parseAll(")")); // => {"position":0,"status":0,"value":null} System.println(rbr.parseAll("(")); // => {"position":0,"status":0,"value":null} System.println(rbr.parseAll(")")); // => {"position":1,"status":1,"value":")"} System.println(hoge.parseAll("hoge")); // => {"position":4,"status":1,"value":"hoge"} System.println(hoge.parseAll("fuga")); // => {"position":0,"status":0,"value":null}正しくマッチしているのが分かります。
組み合わせるとは?(コンビネーター)
これで小さな(単純な)パーサーという道具が揃いました。
var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt); var addsub = $.oneOf("+-"); var muldiv = $.oneOf("*/%"); var lbr = $.string("("); var rbr = $.string(")");これらを組み合わせてみましょう。ここで PEG を出しておきます。BNF でもいいですが、PEG のほうがコンビネーターには合ってますね。文法はこうですよ、というのを示しておかないと何やってるかわからなくなりそうですので。意味は追々触れていきます。
number <- regex(/[1-9][0-9]*|[0-9]/) addsub <- '+' / '-' muldiv <- '*' / '/' / '%' lbr <- '(' rbr <- ')' expression <- term (addsub term)* term <- factor (muldiv factor)* factor <- number / (lbr expression rbr)PEG の優先度付選択の記号
/
と除算の指定'/'
が紛らわしいですが、よく見ると分かります。トップダウン、ボトムアップどちらもありですが、ここではボトムアップでパーサーを構築していきます。
factor
まずは
factor
です。factor <- number / (lbr expression rbr)
factor
はnumber
かlbr expression rbr
か、になります。プログラムにそのまま落とせます。ここで使うのは以下のメソッドです。
- ここではまだ
expression
が定義されていないので、遅延評価させるために$.lazy()
を使います。$.lazy()
を使うと実際に評価されるときにパーサーが作られます。- どちらかを選ぶ、というメソッドは
$.alt()
です。複数のものから最初に成功したパーサーの結果を返します。lbr expression rbr
というように複数のものが連続している、ということを表すのが$.seq()
です。さて書いてみましょう。
expression
は事前に宣言だけしておきます。var expression; var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr)));term
次は
term
です。term <- factor (muldiv factor)*これは、
factor
の後に(muldiv factor)
が 0 回以上続く、という意味です。0 回も許されるので、何も続かない、というのも OK です。muldiv factor
といった感じに並べるのはさっきのlbr expression rbr
と同じで連続していることを意味します。ここで使うメソッドは以下です。
- 0 回以上の繰り返し、はパーサーに対して
.many()
を指定します。では定義してみましょう。
var term = $.seq(factor, $.seq(muldiv, factor).many());これで
term
が定義できました。expression
最後に
expression
です。形はterm
と一緒ですね。expression <- term (addsub term)*そのまま書いてみましょう。
expression = $.seq(term, $.seq(addsub, term).many());これでパーサーが揃いました。試しにパースしてみましょう!
パース
一旦、ソースコードを全部載せてみます。とはいってもそんなにないですね。
using Parsek; var $ = new Parsek(); var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt); var addsub = $.oneOf("+-"); var muldiv = $.oneOf("*/%"); var lbr = $.string("("); var rbr = $.string(")"); var expression; var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr))); var term = $.seq(factor, $.seq(muldiv, factor).many()); expression = $.seq(term, $.seq(addsub, term).many()); // parse expression! System.println(expression.parseAll("1+2*3+2*(14-2)")); // => {"position":14,"status":1,"value":[[1,{}],[["+",[2,[["*",3]]]],["+",[2,[["*",["(",[[14,{}],[["-",[2,{}]]]],")"]]]]]]]} System.println(expression.parseAll("1+2*3+2*(14-2-)")); // => {"position":7,"status":0,"value":null}最初のは(長いですが)成功したことが分かります。結果を読むのは大変ですが、これは後で整形しましょう。そして、2 つ目は失敗していることが分かります。最後の
(14-2-)
がどの規則にもマッチしてないからですね。ではこの結果を整形していきましょう。活躍するのは
number
で使った.map()
です。カッコの式
まず、
$.seq(lbr, expression, rbr)
の部分です。$.seq()
は値として結果の配列を返します。カッコの式というのは、値としてはカッコは不要で中にある式の結果だけあればいいですね。ということで、以下のように変えます。var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr).map(&(value) => value[1])));修正すると結果は次のようになります。
System.println(expression.parseAll("1+2*3+2*(14-2)")); // => {"position":14,"status":1,"value":[[1,{}],[["+",[2,[["*",3]]]],["+",[2,[["*",[[14,{}],[["-",[2,{}]]]]]]]]]]}ちょっと短くなりましたね。
term、expression
次に、
term
とexpression
です。ここでは、後で解析するために AST(Abstract Syntax Tree = 抽象構文木)の形に整形するようにしましょう。基本的には二項演算子なので、LHS(Left Hand Side = 左辺値)と RHS(Right Hand Side = 右辺値)と演算子(Operator)の組み合わせのオブジェクトを作ります。ここで、
$.seq()
をやめて、$.seqMap()
を使うように変更します。これは$.seq()
と.map()
を一緒にしたようなもので、結果リストを引数として最後の引数に指定した関数にコールバックしてくれる便利なメソッドです。こんな風に使います。var term = $.seqMap(factor, $.seq(muldiv, factor).many(), &(first, rest) => { var expr = first; for (var i = 0, l = rest.length(); i < l; ++i) { expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] }; } return expr; });
first
はfactor
の結果で、rest
は$.seq(muldiv, factor).many()
の結果です。なので、rest
は各要素が[演算子, 右辺値]
の形の配列です(空配列の場合もある)。それを AST の形に整形しています。結果、例えば"2 * 3 * 4"
みたいなものは以下のように整形されます。
- コールバック時
- まず、
first
は2
rest
は[['*', 3], ['*', 4]]
expr
に2
が入る- ループに入り、
expr
が{ lhs: 2, op: '*', rhs: 3 }
になる。- もう一つ要素があるので、
expr
が{ lhs: { lhs: 2, op: '*', rhs: 3 }, op: '*', rhs: 4 }
になる。左側の枝が伸びていく形の AST になります(これを左結合という)。今回の演算子は全て左結合です。
expression
も一緒なので、同じように書きましょう。中身は全く同じですなので関数化して使いまわしましょう。function makeAST(first, rest) { var expr = first; for (var i = 0, l = rest.length(); i < l; ++i) { expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] }; } return expr; } var term = $.seqMap(factor, $.seq(muldiv, factor).many(), makeAST); expression = $.seqMap(term, $.seq(addsub, term).many(), makeAST);すっきりしました。
では、プログラム一式です。たったこれだけの定義で四則演算を(演算子の優先順位も考慮された形で)パースできてしまいます。素晴らしいですね!
using Parsek; function makeAST(first, rest) { var expr = first; for (var i = 0, l = rest.length(); i < l; ++i) { expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] }; } return expr; } var $ = new Parsek(); var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt); var addsub = $.oneOf("+-"); var muldiv = $.oneOf("*/%"); var lbr = $.string("("); var rbr = $.string(")"); var expression; var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr).map(&(value) => value[1]))); var term = $.seqMap(factor, $.seq(muldiv, factor).many(), makeAST); expression = $.seqMap(term, $.seq(addsub, term).many(), makeAST); // test System.println(expression.parseAll("1+2*3+2*(14-2)").value.toJsonString(true));結果はこうなります。うまくいってますね!
"lhs": { "lhs": 1, "op": "+", "rhs": { "lhs": 2, "op": "*", "rhs": 3 } }, "op": "+", "rhs": { "lhs": 2, "op": "*", "rhs": { "lhs": 14, "op": "-", "rhs": 2 } }おわりに
さて、目的の AST ができました。次回、これを解釈して実行させます。せっかく作った JIT ライブラリも使いますよ!
ではまた次回!
- 投稿日:2020-07-03T22:12:27+09:00
ScrollReveal.jsでええ感じのパララックス効果を実装する
スクロールすると画像がふわって出てくる感じのを実装する
簡単にパララックス効果を実装できる便利ライブラリをcdnで以下のようにheadタグに記述
<script src="https://unpkg.com/scrollreveal"></script>
次にhtmlを編集。marginはスクロールするスペースを与えるため
index.html<div class="animation" style="margin-top: 1000px;"> <img src="./images/image1.jpg"> </div>次にjs
app.js$(function () { ScrollReveal().reveal('.animation', { delay: 500, duration: 2000, }); });おしまい。
- 投稿日:2020-07-03T19:34:30+09:00
Javascriptでモーダルウィンドウを生成する
JavaScriptを使ったモーダルウィンドウについての解説です。
実装例として、ボタンをクリックすることでモーダルウィンドウを呼び出す処理のサンプルを記載します。HTML
ボタンをひとつ用意します。
要素を取得する為、id="modal-btn"
とします。<button id="modal-btn">ボタン</button>CSS
生成するモーダルに付与するCSSを設定します。
中央配置にする為にleft: 50%
、top: 50%
、transform: translate(-50%, -50%)
を設定しています。.modal { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); } .inner { width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.9); border: solid 3px #dfe4ea; border-radius: 10px; }Javascript
はじめに作成したHTMLのボタンをクリックした時の処理を記述します。
クリックを起点としたイベントを設定し、showModal
メソッドを発火させます。document.getElementById('modal-btn').addEventListener('click', showModal);
showModal
メソッドの詳細を記述します。function showModal() { // モーダルウィンドウと中身の要素を生成・クラスを付与 const modalElement = document.createElement('div'); modalElement.classList.add('modal'); const innerElement = document.createElement('div'); innerElement.classList.add('inner'); // モーダルウィンドウに表示する要素を記述 innerElement.innerHTML = ` <p>モーダルウィンドウ</p> `; // モーダルウィンドウに中身を配置 modalElement.appendChild(innerElement); document.body.appendChild(modalElement); // 中身をクリックしたらモーダルウィンドウを閉じる innerElement.addEventListener('click', () => { closeModal(modalElement); }); }モーダルを閉じる処理を用意します。
function closeModal(modalElement) { document.body.removeChild(modalElement); }これでシンプルなモーダルウィンドウが完成しました。
モーダルウィンドウの使いどころ
モーダルウィンドウは、Webサイトの様々な箇所で使われています。
画面を覆うように表示されるため、ユーザーへの警告や確認の為に使われることが多い機能です。
- ポップアップ広告
- 警告メッセージ
- エラーメッセージ
- ロード中
モーダルウィンドウを使ったポップアップ広告などはユーザーから嫌われる傾向にある為、使用には注意が必要です。
- 投稿日:2020-07-03T19:27:49+09:00
【JavaScript 】Node.jsとは
はじめに
Node.jsとはサーバーサイドのjavascriptのプラットフォーム(実行環境)である。
javascriptは本来クライアントサイドで動作するプログラミング言語だが、Node.jsを使用することでサーバーサイドでもjavascriptが利用できるようになる。公式サイト、またはOSのパッケージ管理ツールを使用してインストールすることでNode.jsが使用できる。
・公式サイト
・色々なパッケージ管理ツールでのインストールクライアントサイドとサーバーサイド
? WEBページをブラウザで閲覧する場合の仕組み
1. ブラウザ(クライアント)でURLを指定、サーバーにリクエスト 2. リクエストを受けたサーバーはページを表示するために 必要なhtmlやそれに関連するCSSやjavascriptをブラウザ(クライアント)に返す 3. クライアント側でそれらを受け取ることでWEBページが閲覧できるようになるクライアントサイド
クライアントサイドの言語とは、クライアント(WEB上であればブラウザのこと)からサーバーにリクエストして得られた結果をブラウザで処理(表示)する際に使われる言語のこと。
javascriptがクライアントサイドの言語として挙げられる。サーバーサイド
サーバーサイドの言語とは、クライアントに結果を渡すためにサーバー内で処理を行う言語のこと。
PHP、Ruby、Pythonなどの言語がサーバーサイドで使用される。npm
npmとはNode.jsのパッケージを管理するツールで、Node.jsをインストールすることで自動的に使えるようになる。
ここでいうパッケージとは、あらかじめ用意されているNode.jsの便利な機能をまとめたもののこと。
npmで様々なパッケージのインストールやアップデートをコマンドラインから行うことができる。
npmでインストールされたパッケージはnode_modules
ディレクトリにインストールされ、package.json
ファイルで管理ができる。npmのインストールの種類
npmでパッケージをインストールするには
グローバルインストール
とローカルインストール
の2種類の方法がある。グローバルインストール
npmをインストールした場所にパッケージがインストールされる。全てのプロジェクト(フォルダ)でインストールしたパッケージが使用できるようになる。↓下記コマンドを打ち込むことでグローバルにインストール
npm install -g パッケージ名ローカルインストール
任意のプロジェクト(フォルダ)内にだけパーケージをインストールする。インストールしたプロジェクト内でしかパッケージが使用できない。↓下記コマンドを打ち込むことでローカルにインストール
npm install パッケージ名npm コマンド色々
npm install は npm i と省略して記述することもできる * 作成したものを動かすために必要なパッケージをインストール (package.json の dependenciesに追加される) npm i -S * 開発に必要なパッケージをインストール (package.json の devDependenciesに追加される) npm i -D * パッケージのインストールリスト npm list * パッケージをアンインストール npm rm * node_modulesフォルダを削除 rm -rf node_modules
- 投稿日:2020-07-03T18:43:39+09:00
[Javascript]まちがえながらも感覚的に実装できるまで昇華していく[後編]
目的
- 関数の実装を繰り返し実施し、実装までの思考法を身につける。
- 関数の概念を頭で理解するだけでなく、感覚的に操作できるまで昇華する。
- 英語でテクニカルな内容を理解する。
今回やること
lodashのライブラリを実装しながら学んだことをアウトプットしていきます。
if文は三項演算子で対応するように意図する。_.lastIndexOf(array, value, [fromIndex=array.length-1])
第3引数のインデックスから逆方向に検索し、値と一致したインデックス番号を返す。なければ-1。
_.lastIndexOf([1, 2, 1, 2], 2); // => 3 // Search from the `fromIndex`. _.lastIndexOf([1, 2, 1, 2], 2, 2); // => 1実装
特に問題なし。
lastIndexOf = (array, value, fromIndex = array.length - 1) => { for (let i = fromIndex; 0 <= i; i--) { if (array[i] === value) { return i } } return -1 }_.last(array)
配列の最後の値を取り出して返す。
_.last([1, 2, 3]); // => 3実装
特に問題なし
last=(array=[])=>{ return array[array.length-1]; }_.flatten(array)
渡した配列を一つずつなくしてくれる。
_.flatten([1, [2, [3, [4]], 5]]); // => [1, 2, [3, [4]], 5]学び
階層になっている時は、頭の中ではJSONを見るような気分になる。
(=階層を意識すること)const num = [1, [2, [3, [4]], 5]]; num[0]// =>1 num[1] // =>[2, [3, [4]], 5] [2, [3, [4]], 5]実装
配列の中を取り出して配列があるかが重要。
再帰と似ていて、まずは、親子みて兄弟は親子見終わって見る。function flatten(array) { const flattendedArray = []; for (let i = 0; i < array.length; i++) { const value = array[i] if (Array.isArray(value)) { flattendedArray.push(...value); } else { flattendedArray.push(value); } } return flattendedArray; }
- 投稿日:2020-07-03T18:01:21+09:00
フリマアプリのカテゴリー実装のエラー解決
前提・実現したいこと
プログラミングスクールのチーム開発でフリマアプリを製作中です。
カテゴリー機能実装でエラーが出たので、それをどのように解決したかを記録することにしました。発生している問題・エラー(YouTube動画)
エラー画面をYouTubeにアップしました。
QiitaにはYouTubeの埋め込みはできないみたいなので、画像をクリックして動画に飛んで見てください。親カテゴリーを選択すると子カテゴリーが出てきて、子カテゴリーを選択すると孫カテゴリーが出てくる設定になっています。
問題なのは、上記の画像の状態からもう一度親カテゴリーを選び直すと、また下に子カテゴリーが出てきてしまうことです。
親カテゴリーを選び直したら、下の画面のようになって欲しいのです。エラー画面のソースコード
category.js(省略) $(window).on('load',function(){ if(document.URL.match('new')) { $('#parent_category').on('change', function(){ var parentCategory = $(this).val(); if (parentCategory != "---"){ $.ajax({ url: 'get_category_children', type: 'GET', data: { parent_id: parentCategory }, dataType: 'json' }) .done(function(children){ $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); var insertHTML = ''; children.forEach(function(child){ insertHTML += appendOption(child); }); appendChidrenBox(insertHTML); }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) } else { $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); } }); $('.category-section__pulldown').on('change', '#child_category', function(){ var childId = $('#child_category option:selected').data('category'); if (childId != "---"){ $.ajax({ url: 'get_category_grandchildren', type: 'GET', data: { child_id: childId }, dataType: 'json' }) .done(function(grandchildren){ if (grandchildren.length != 0) { $('#grandchildren_wrapper').remove(); var insertHTML = ''; grandchildren.forEach(function(grandchild){ insertHTML += appendOption(grandchild); }); appendGrandchidrenBox(insertHTML); } }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) } else { $('#grandchildren_wrapper').remove(); } }); (省略)解決後のソースコード
category.js$(window).on('load',function(){ if(document.URL.match('new')) { $('#parent_category').on('change', function(){ var parentCategory = $(this).val(); if ($('div').hasClass('category-select-child')){ $('.category-select-child').remove(); $('.category-select-grandchild').remove(); $.ajax({ url: 'get_category_children', type: 'GET', data: { parent_id: parentCategory }, dataType: 'json' }) .done(function(children){ var insertHTML = ''; children.forEach(function(child){ insertHTML += appendOption(child); }); appendChidrenBox(insertHTML); }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) } else { $.ajax({ url: 'get_category_children', type: 'GET', data: { parent_id: parentCategory }, dataType: 'json' }) .done(function(children){ var insertHTML = ''; children.forEach(function(child){ insertHTML += appendOption(child); }); appendChidrenBox(insertHTML); }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) } });解決方法
以下のように条件分岐の設定を変えました。
エラーの場合var parentCategory = $(this).val(); //親カテゴリーのidが入っている場合=親カテゴリーの情報が入った場合 if (parentCategory != "---"){ //ajaxで子カテゴリーの情報を受け取りjson形式で返す //子カテゴリーを追加 } else { //(省略)ここの記述間違えてて何の意味もなかった }親カテゴリーが選択されて情報が入っているかいないかで条件分岐されていたところを、
子カテゴリーがあるかないかで条件分岐することにしました。解決後の場合//子カテゴリーのクラスがある場合 if ($('div').hasClass('category-select-child')){ //子カテゴリーを消す $('.category-select-child').remove(); //ajaxの処理 //新たに子カテゴリーを追加 } else { //子カテゴリーのクラスがない場合 //単純に子カテゴリーを追加 }
.hasClass
メソッドは、そのクラスがあるかないかを判定して、trueかfalseで返してくれます。子カテゴリーがあれば、まず既存の子カテゴリーをremoveメソッドで消してから新たに子カテゴリーを追加し、
子カテゴリーがなければ、単純に今まで通り子カテゴリーを追加するだけにしました。解決後の挙動(YouTube動画)
解決後の挙動の動画をYouTubeにアップしました。
※QiitaにはYouTubeの埋め込みはできないみたいなので、画像をクリックして動画に飛んで見てください。補足情報
ruby 2.5.1
Rails 5.2.4.3参考記事
- 投稿日:2020-07-03T17:19:35+09:00
だから僕はVanilla JSを辞めた
だから僕はVanilla JSを辞めた
Vanilla JSとは、普通の何のフレームワークも使わない純粋なJavaScriptのことです。
Pure JSと呼んだりもしますが、あえてこの表記としています。
私が、フレームワークに手を出して、Vanilla JSを辞めた理由を、私の場合の例として述べます。
私の場合がTypeScirptとVue.jsの導入だったのでその話をしますが、これらの推奨が目的ではなく、新たなものへ挑戦する勇気を持ちなさいという記事です。TL;DR
- 純粋なJavaScriptでなんでもできるからと、なんでもしていた私がフレームワークを導入するまで
- フレームワークや、その他新しいことの食わず嫌いはなるべくやめたい
JavaScriptだけで記述する
WEB開発はコンパイラ不要、環境も選ばない、JavaScriptはなんだか緩いと、手を出しやすい分野だと思います。
私はなんでも純粋な状態で書くことに、何と無くこだわりを持っていました。
JavaScriptだけで書いて、jQueryなどには見向きもしない、そんな日々が続いていました。
その理由としては、新たなものを覚えるほどまだJavaScriptに精通していない、学習コストが高そう、無くてもかける、無駄な通信や変数汚染を避けたい、などでした。TypeScriptとの出会い
しかしそんなある日、何気なく書いた次の一文が動かないことに頭を悩ませることになります。
document.getElementsByTagName("a").forEach((e) => { e.onclick = () => location.href = e.href e.removeAttribute("href") })内容は、ページ中の
a
タグのリンクを消去し、代わりにonclick
でページ遷移を行うものです。
これは、PWAでa
タグによるリンクを行うと、PWAを抜けてブラウザが起動してしまうというものを回避するために書いたものでしたが、うまく動作しません。
この理由は、document.getElementsByTagName
の戻り値はHTMLCollection
であってArray
でないことです。今まで考えてもいませんでしたが、JavaScriptにも型が存在したのでした。
上記ではHTMLCollection
というのはObject
型であり、そこにforEach
メソッドが定義されていないため、動作しないのです。
どうにもJavaScriptばかり書いてると、型の考えをしなくなってきます。今でこそlet/const
による記法が標準となってまいりましたが、古い本などではvar
を用いるものも見ます。なんだって再代入できちゃうものだから、動的型付けなんて型がないみたいなものだろうと考えてしまいます。そして思い出したキーワード、それがTypeScriptです。
型を意識しないでめちゃめちゃなことできるからJSは便利なのに、なんでそこに型付けるのか、非常に疑問でしたが、これ使ってたらこんなエラーに数時間溶かすことはなくなるのではないかと、思ったわけです。簡単な例
次のようなコードを書いたことがあるでしょうか
console.log(`${num} is ${(num % 2) ? "odd" : "even"}`)上記は、数字の入った変数
num
によって、それが偶数か奇数かをコンソールに表示するという動作をします。
なぜでしょう。それは0
がfalthy
で、それ以外の数字はtruthy
だからですね。JavaScriptでは暗黙の型変換が結構活躍します。条件式の中では勝手
Boolean
に変換される上記の例もそうですが、ほかにもconst num = document.getElementById("age").value - 0などとすれば、入力された数値を
String
型ではなくNumber
型として利用できます。
これは、文字列に算術演算子を適用するとき、文字列を数値へ暗黙的に変換を行うためで、実際には数値へ変換できないような文字列だとしたらNaN
が代入されることになります。一見便利な暗黙の型変換ですが、変数に入っている方がわからなければ、先ほど挙げた
HTMLCollection
のような、無いメソッドを呼び出してしまうようなミスをしてしまうことが多くなってきます。TypeScriptでは、変数に代入できる型や、メソッドの戻り値の型などはあらかじめ決めておかなければなりません。このため、上記のコードはコンパイルが通りません。算術演算子の左オペランドには文字列は使えないからです。(文字列の結合の演算子としての
+
は使用できますが、これは算術演算子ではありません)これは逆に、変数の型は常にわかっているということです。
なんらかのコードエディタを用いている場合、変数の型がわかっているために、メソッドなども書いている途中でその有無や戻り値の型まで把握できます。最近はNull安全などといったことも目にしますし、ECMAScript2020ではOptional Chaning演算子
?.
や、Null合体演算子??
も追加されます。null
の扱いは面倒ですからね。
一方でTypeScriptではそもそも、その変数がundefined
やnull
となり得るかどうか知ることができます。
なり得る場合は、先述のような演算子や、条件分岐などを必要としますが、それを教えてくれるというわけです。便利ですね。簡単な例2
次のコードの動作はどうなるでしょう。
const num = 5 window.localStorage.setItem("isEven", !(num % 2)) const isEven = window.localStorage.getItem("isEven") console.log(`${isEven ? "even" : "odd"}`)当然
5
は奇数ですからsetItem
されるのは!(5 % 2) = !(1) = false
です。
それがisEven
にローカルストレージ経由で代入され、三項演算子で"odd"
と評価されるはず、と思いますよね。残念ながら、
num
が5
であろうとなんであろうと、コンソールの表示は必ず"even"
になります。
ローカルストレージには文字列しか保存できないんですね。
このため、setItem
の時点でfalse
は"false"
に変換され、truthy
となります。
今回の場合、setItem
されるのは"true" | "false"
と表せます。これはどちらもtruthy
ですから、常に"even"
が表示されます。
型変換されるとわかっていても、必ずJSON.stringify
してからセットし、読みだした後にJSON.parse
するようにしないといけませんね。
一方、TypeScriptでは、Boolean
型はString
型じゃないよって教えてくれます。また、読み出し後もそれがString
型であることを教えてくれるので、このようなミスは防げるかと思います。シチュエーションとしては、チェックボックスやスイッチの状態を保存したいときなんかあると思います。
自分もやらかしてしまいそうだと思いませんか?なぜか
undefined
って画面に出ちゃうことがある人は検討してみると良いでしょう。そもそも画面に表示するところにundefined
となり得る変数を置かなければ良いんですから。型変換を自前で行う必要がある場合や、変数の型を明記する必要がある場合など、コードの記述量は増えてしまうことはあるかもしれませんが、その分バグが減ります。
今まで環境構築や学習コストなどを恐れて導入しなかった昔の自分を諭したいです。
Vue.jsとの出会い
そしてある日、Vue.jsというものを見かけます。
どうやらアプリ開発に便利なフレームワークだそうです。
フレームワークはとはなんや、と思いましたが、流行ってたので乗ることにしました。(コロナも流行ってますが乗らないでください)とりあえず適当にアプリを開発して、Vuetifyというマテリアルデザインのアプリ開発が簡単にできるものも見つけ、使用しているうちにこう感じましたわけです。
「なるほど。今までは車輪の再発明をしていたわけか。」あるいはjQueryなんかも使っていればもっと早くアプリの開発ができたのではないかとも、今になっては思います。もう今となっては使いませんが。
先人が自分より高度な知識で作ったものなんですから、そりゃ私がそれに勝るアプリを作れないでしょう。
それはもう、Array
にはArray.prototype.reverse
があるのに、自前でfor
文で逆に詰めなおすようなことをしているようなものだったわけです。時間の無駄だし、なんなら性能も劣る場合があります(ソートなんかそうですね)。だから僕は
今までの私は間違っていました。
使用できるものは使った方が良いです。なんかnull
になったり、予期しない文字列きたり、対応するのめんどくさいですね。デザインなんかコーディングと同時にするには難しいですし、CSSはいつも思ったのと違う動作するんですもん。そして私は今になって、昔の開発環境を、あるジョーク記事からとってこう呼んでいます。
Vanilla JSと。
それは、純粋なJavaScriptを用いるというフレームワークです。
初学者なんかは構いませんが、ある程度の規模になってくると注意深い開発が必要になり、他人と開発するときなんかにもそのメソッドが何を返す可能性があるのかを明確にしたいときが出てきたりすると思います。
私の場合、TypeScript+Vue.js+Vuetifyというのを採用して、開発の効率と品質が上がり、食わず嫌いで導入を渋っていたことを後悔しました。だから僕はVanilla JSを辞めた。
- 投稿日:2020-07-03T17:10:25+09:00
【Nuxt.js】Nuxt文法編:v-bind
? この記事はWP専用です
https://wp.me/pc9NHC-nh前置き
Nuxt.js Vue.js を使う上で必須の知識です!
これができるとコンポーネントを活かして
表現の幅を広げることができます?例えば!
親によってクラスの有無を切り替えて
色分けをする、といったことができます?基本的な使い方
v-bind:{ 属性名 }="{ 変数 }"
タグの属性を変数にしたい時に使います。
aタグのhref属性をv-bindしてみます。飛びたいリンク先
http://localhost:3000/index.vue<template> <div class="page"> <a v-bind:href="url">リンク</a> </div> </template> <script> export default { data () { return { url: '/', } }, } </script>? この記事はWP専用です
続きはWPでご覧ください?
https://wp.me/pc9NHC-nh
- 投稿日:2020-07-03T17:05:17+09:00
【javascript】ロックとキーをシミュレートする
書籍のアウトプットとして
以下のコードはロックとキーのシステムを構築している。
function lock(secret){ const key = Symbol('key') return { key,unlock(keyUserd){ if(keyUserd==key){ return secret; }else { return '*'.repeat(secret.length || secret.toString().length) } } } } const {key,unlock}=lock(42) console.log(unlock())//** console.log(unlock(Symbol('key')))//** console.log(unlock('key'))//** console.log(unlock(key))//42正しいキーならsecretを取得でき、正しくないならキーをマスクしている。
コードの解説
このコードを読む上でわからないことがいくつかあった。
return{key,unlock(...)}
とはどういうことか- unlock()で実行できるのはなぜか
return {key,unlock(...)}
とはどういうことか
return {}
はオブジェクトを返しているということ。そしてオブジェクトには簡易表記という書き方ができる。const obj = { name: name, method: function() {} } //簡易表記 const obj = { name, method() {} }プロパティ名(name)と変数名が同じ場合、一度指定すればいいだけになる。
{name:name}
と書いていたのが{name}
で済む。
またメソッドに関しては無名関数に限り{method:function(){}}
が{method()}
と書ける。つまりlock関数を書き換えるとこういうこと
function lock(secret){ const key = Symbol('key') return { key:key, unlock:function(keyUserd){ if(keyUserd==key){ return secret; }else { return '*'.repeat(secret.length || secret.toString().length) } } } }そして
const {key,unlock}=lock(42)
で変数keyにSymbol('key')
が代入され、変数unlockには無名関数が代入されている。unlock()で実行できるのはなぜか
これは勘違いであった。
その原因となったコードがこちら
const ffaa={ at(n){ if(n<=2)return 1; return this.at(n-1)+this.at(n-2); } } const {at}=ffaa; const ff={at} const aa={ff:at} ffaa.at(7)//13 at(7)//エラー ff.at(7)//13 aa.ff(7)//エラーこれだけを見ると、無名関数を分割した際、単純に
変数名()
では実行でいなものと思ってしまった。
確かに関数を名前で参照出来ないこともあるが、それは関数内のメンバーを参照するときである。
- 投稿日:2020-07-03T16:50:20+09:00
第四回 JavaScriptの非同期処理について
前提
以降の説明では下記の非同期関数を使用して説明を実施します。
※使用したソースは全て GitHub に上げてありますfile.jsfunction readFile(fileName, success) { setTimeout(() => { if (typeof(success) === 'function') { success(fileName) } }, 500) } function readFileAsync(fileName) { return new Promise((resolve, reject) => { setTimeout(() => resolve(fileName), 500) }) }
プログラムの常識
コードは上から順に実行される
sync.jsconst hoge = readFile('hoge.txt') const fuga = readFile('fuga.txt') const foo = readFile('foo.txt') const bar = readFile('bar.txt') console.log(hoge + fuga + foo + bar)
動かない
なんで?
- 非同期処理だから
- ファイルの読み込みが終わる前に
console.log()
が走っちゃう
なんで非同期なの?
- 同期的な処理だと、IOアクセスの待ち時間中に何もできなくなる
- DOMを操作するときはなにかと非同期で動かしたいことが多い(Ajaxのレスポンスを待っている間にプログレスバーを表示させるとか)
ES6以前の非同期処理
- コールバック地獄
コールバック地獄
async.jsreadFile('hoge.txt', function (hoge) { readFile('fuga.txt', function (fuga) { readFile('foo.txt', function (foo) { readFile('bar.txt', function (bar) { console.log(hoge + fuga + foo + bar) }) }) }) })
- 深いネスト
- エラーをまとめて受け取れない
- function ,function , function ...
ES6 (ECMAScript 2015)以後の世界
Promise
Promiseとは
Promiseは非同期処理を抽象化したオブジェクトとそれを操作する仕組みです。
使いこなすことで、以下のような効果が見込めます。
- コールバック地獄から解放される
- エラー処理をうまく記述できる
- 一連の非同期処理を関数化して再利用しやすくできる
書き方
howto.jsconst promise = function(str) { return new Promise((resolve, reject) => { setTimeout(() => resolve(str), 500) }) } promise('foo') .then( (val) => { console.log(val) return promise('bar') }) .then((val) => console.log(val)) .catch((err) => console.log(err))
解説
Promiseオブジェクト
- Promiseオブジェクトを返す関数を書く
- Promiseオブジェクトの中に非同期に行いたい処理を書く
- 非同期処理が終わったらresolve()を呼び出す
- 非同期処理が失敗したらreject()を呼び出す
解説
Promiseオブジェクトを使う
- 非同期処理を実行したあとに実行したい処理をthen節の中に書く
- 実行結果は引数(val)で受け取る
- 非同期処理が失敗したらcatch節で受け取れる
いいところ
- ネストが深くならない
- 各処理で起こったエラー(例外)をcatch節でまとめて補足できる
これでコールバックともおさらばだぜ!!
promise.jslet hoge, fuga, foo, bar readFileAsync('hoge.txt') .then((result) => { hoge = result return readFileAsync('fuga.txt') }) .then((result) => { fuga = result return readFileAsync('foo.txt') }) .then((result) => { foo = result return readFileAsync('bar.txt') }) .then((result) => { bar = result console.log(hoge + fuga + foo + bar) })
うーん・・・
このままでもできるけど・・・
- 確かにネストは浅くなったが長い
- 外の変数に退避しないと一個前の結果を参照できない
- then,then,then...
もっと同期的に書きたい
理想のコード.jsconst hoge = readFileAsync('hoge.txt') const fuga = readFileAsync('fuga.txt') const foo = readFileAsync('foo.txt') const bar = readFileAsync('bar.txt') console.log(hoge + fuga + foo + bar)
そして ES8(ECMAScript 2017)へ・・・
async/awaitの登場
await.jsasync function readFiles(){ const hoge = await readFileAsync('hoge.txt') const fuga = await readFileAsync('fuga.txt') const foo = await readFileAsync('foo.txt') const bar = await readFileAsync('bar.txt') console.log(hoge + fuga + foo + foo) } readFiles()
解説
- 同期的に処理したい部分を
async function
で囲む- 同期的に処理したい非同期処理関数の手前に
await
をつける- Promiseの処理が終わるまで待ってくれるようになる
ちなみに・・・
- async functionもPromiseを返すので、以下のようなコードは非同期に実行されます
await1.jsasync function readFiles(){ const hoge = await readFileAsync('hoge.txt') const fuga = await readFileAsync('fuga.txt') const foo = await readFileAsync('foo.txt') const bar = await readFileAsync('bar.txt') console.log(hoge + fuga + foo + foo) } readFiles() console.log('readFiles()はまだ終わらない・・・')
- ちゃんとやるなら以下のような感じ
await2.jsasync function readFiles() { const hoge = await readFileAsync('hoge.txt') const fuga = await readFileAsync('fuga.txt') const foo = await readFileAsync('foo.txt') const bar = await readFileAsync('bar.txt') console.log(hoge + fuga + foo + foo) } readFiles().then(() => console.log('readFiles()は終わった!!'))
めでたしめでたし
ちょっとまって
直列処理はいいけど並列処理は?
Promise.all()
Promise.all()
- 複数の非同期処理が完了した後にthen節を呼び出せる
promise_all.jsconst hoge = readFileAsync('hoge.txt') const fuga = readFileAsync('fuga.txt') const foo = readFileAsync('foo.txt') const bar = readFileAsync('bar.txt') Promise.all([hoge, fuga, foo, bar]) .then((values) => console.log(values[0] + values[1] + values[2] + values[3]))
awaitとの合わせ技も可能
promise_all1.jsasync function readFiles() { const values = await Promise.all([ readFileAsync('hoge.txt'), readFileAsync('fuga.txt'), readFileAsync('foo.txt'), readFileAsync('bar.txt'), ]) console.log(values[0] + values[1] + values[2] + values[3]) } readFiles()
まとめ
- async/awaitは神
- Promise.allとかも使って、非同期にすべきところは非同期にしよう
- ちなみに新しい規格なのでIE非対応。使いたいときはトランスコンパイルしましょう
補足資料
ES6,ES8
トランスパイルとは
トランスコンパイラ(他にトランスパイラ、ソース・トゥ・ソースコンパイラ、などとも)は、あるプログラミング言語で書かれたプログラムのソースコードを入力として受け取り、別のプログラミング言語の同等のコードを目的コードとして生成する、ある種のコンパイラである
-- Wikipediaより
- 投稿日:2020-07-03T16:43:38+09:00
[JS] もう少し厳密に URL からファイル名等を取得する正規表現
よく見かける URL の正規表現に比べて、もう少し厳密にパースする正規表現の紹介になります。
正規表現なので他の言語等でも使用できると思いますが、ここでは JavaScript で動くコードを載せます。
1. RFC 3986 に書かれている URL の正規表現
WEB 上で調べると様々なひとたちが URL の正規表現を書いていると思いますが、URL の定義が書かれている RFC 3986 に既に (おそらく) 厳密な正規表現が書かれています。
自身でヘタに正規表現を書くよりもこちらを流用したほうが安全です。
以下、引用・意訳。
^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? 12 3 4 5 6 7 8 92 行目は、マッチ文字列のインデックスを分かりやすくするための数です。
例えば、以下のようにマッチします。
http://www.ics.uci.edu/pub/ietf/uri/#Related
$1 = http: $2 = http $3 = //www.ics.uci.edu $4 = www.ics.uci.edu $5 = /pub/ietf/uri/ $6 = <undefined> $7 = <undefined> $8 = #Related $9 = Related2. 例: ディレクトリとファイル名とクエリ文字列を取得
2.1. 一般的によく使われる JavaScript のコードの問題点
lastIndexOf()
でファイル名やクエリ文字列を分離したり、split()
でディレクトリやファイル名を分離するものが多いですが、厳密にはクエリ文字列そのものに/
や?
を含めることができるため、理論上、正しく動作しない可能性があります。const url = /* 'URL 文字列' */; const query = url.slice(url.lastIndexOf('?') + 1); const fileName = url.slice(0, url.lastIndexOf('?')).slice(url.lastIndexOf('/') + 1);例:
https://example.com/dir1/dir2/index.php?callback=https://example.com/dir3/dir4/index2.php?param
query === 'params' fileName === `index2.php`2.2. (おそらく) 厳密な正規表現
不要な括弧
(
...)
を取り除いたりキャプチャしないように(?:
...)
に変更して、必要な部分だけをキャプチャします。また、ファイル名にマッチする部分 5
([^?#]*)
を(?:([^?#]*/)([^/?#]*))?
に書き換え、(クエリ文字列が取り除かれた) パス文字列の最後のスラッシュから後ろをキャプチャします。ファイル名を含めるには必ずスラッシュ
/
が 1 つ以上あるはずで、スラッシュがない場合にはファイル名が空になるはずなので、正しく動作するはずです….。^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? 12 3 4 5 6 7 8 9 ^(?:[^:/?#]+:)?(?://[^/?#]*)?([^?#]*)(\?[^#]*)?(?:#.*)? 1 2 ^(?:[^:/?#]+:)?(?://[^/?#]*)?(?:([^?#]*/)([^/?#]*))?(\?[^#]*)?(?:#.*)? 1 2 3※ここではクエリ文字列を区切り文字の
?
ごとキャプチャしています。パスを再び組み立てる必要がある場合などは?
を残した方が便利です。逆に、POST 送信する目的などでは?
を除去した方が使いやすいため、状況によって使い分けてください (両方キャプチャすることもできます) 。2.3. JavaScript でのコード
const url = /* 'URL 文字列' */; const matchedFileName = url.match(/^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?(?:([^?#]*\/)([^\/?#]*))?(\?[^#]*)?(?:#.*)?$/) ?? []; const [, dir, fileName, query] = matchedFileName.map(match => match ?? '');例:
https://example.com/dir1/dir2/index.html?params#section1
dir === '/dir1/dir2/' fileName === `index.html` query === '?params'例:
https://example.com/dir1/dir2/index.php?callback=https://example.com/dir3/dir4/index2.php?param
dir === '/dir1/dir2/' fileName === `index.php` query === '?callback=https://example.com/dir3/dir4/index2.php?param'3. おまけ: 拡張子も分離する
拡張子は拡張子で厳密に取り出そうとすると罠が多いため、以下のページを参考にさせていただきました。
^(.+?)(\.[^.]+)?$ 1 2参考「ファイル名から拡張子とそうでない部分を分ける - Qiita」(※ Ruby の正規表現のため、少し記述が異なります)
参考「Railsの正規表現でよく使われる \A \z って何?? - Qiita」const url = /* 'URL 文字列' */; const matchedFileName = url.match(/^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?(?:([^?#]*\/)([^\/?#]*))?(\?[^#]*)?(?:#.*)?$/) ?? []; const [, dir, fileName, query] = matchedFileName.map(match => match ?? ''); const matchedExt = fileName.match(/^(.+?)(\.[^.]+)?$/) ?? []; const [, name, ext] = matchedExt .map(match => match ?? '');例:
https://example.com/dir1/dir2/index.html?params#section1
name === 'index' ext === `.html`
- 投稿日:2020-07-03T16:41:33+09:00
【Vue】漫画の進捗管理ツール作ってみた
あと何コマ?
コマ数で漫画の進捗管理するツールを作りました。
コマ数を入力するとコマが出てきて、
終わったコマをクリックすると塗りつぶされて完了状態になります。
1コマ辺りの作業時間を入力すると、残りの作業時間がわかります。
1コマの作業時間×残りコマ数で残りの作業時間が算出される訳ですね。何故作ろうと思ったのか
ページ単位で管理するツールは既にあるが、
コマ単位で管理するものはなかったため。
漫画制作のモチベーションを維持するためにこういうツールがほしかった。
(コマ単位で管理しないとモチベが保たない)
Vue初心者が悶絶しながら作ったものですが、この記事が他の勉強中の方の参考になればと思います。ソース全文
panels.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>あと何コマ?</title> <!-- Bootstrap --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <!-- fontawesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha256-UzFD2WYH2U1dQpKDjjZK72VtPeWP50NoJjd26rnAdUI=" crossorigin="anonymous" /> <link rel="stylesheet" href="panels.css"> </head> <body> <header class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4"> <nav class="mt-2 my-md-0 mr-md-3"> <a class="px-2 text-white" href="#paneldiv">あと何コマ?</a> <a class="px-2 text-white" href="#timediv">あと何分?</a> </nav> </header> <div class="jumbotron jumbotron-fluid"> <div class="container captionText"> <p>コマ数で進捗管理するツールです。残りコマ数を入力して、入力完了ボタンを押してください。<br> 1コマ辺りの作業時間を入力すると、残りの作業時間がわかります。 </p> </div> </div> <div id="app" class="mb-5"> <div class="container main py-4 mt-sm-3"> <article class="text-center pt-3 pb-4" id="paneldiv"> <div class="alertArea text-center mb-3"> <strong v-show="alertShow"> コマ数を入力してください </strong> </div> <h3 v-if="!resultShow"> あと<input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ? <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultShowFunc" v-if="!resultShow">入力完了</button> </h3> <h3 v-else> 全<input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultReset" v-if="resultShow">リセット</button> </h3> <div v-show="resultShow"> 全{{ remainedPanelsInput }}コマ- 済み<input type="number" v-model.number="filledPanels" v-bind:max="remainedPanelsInput" min="0">コマ = あと<span class="resultText">{{ remainedPanelsNumber }}</span>コマ <p class="text-muted pt-3">終わったコマをクリックすると、塗りつぶされて完了状態になります。完了状態のコマをクリックすると未完の状態に戻ります。</p> </div> <section class="row pricing-header px-3 py-3 pt-md-3 pb-md-1 mx-auto text-center" v-show="resultShow"> <div class="panel" v-for="n in remainedPanelsNumber" @click="panelFinished"> <div class="panelInner">{{ n }}</div> </div> <div class="panel filled" v-for="n in filledPanels" @click="panelReturn"></div> </section> </article> <article class="text-center" id="timediv" v-show="resultShow"> <h3>あと何分?</h3> <div> 1コマ辺りの作業時間<input class="inputPerPanel" type="number" v-model.number=" perPanel" min="0">分×残り{{ remainedPanelsNumber }}コマ= あと<span class="resultText">{{ remainingTime }}</span>分 ({{ remainingHour }}時間) </div> </article> </div> <!-- ツイートボタン --> <div class="contact text-center"> <a href="https://twitter.com/share" class="twitter-share-button" data-url="https://mitaru.github.io/panels/" data-text="進捗どうですか?あと何コマ?" data-size="large" data-hashtags="あと何コマ"> Tweet </a> </div> </div> <footer class="my-1 pt-5 text-muted text-center text-small"> <ul class="list-inline"> <li class="list-inline-item"> <a href="https://twitter.com/SakaiMitaru"> <i class="fab fa-twitter-square mr-1"></i>SakaiMitaru </a> </li> <li class="list-inline-item"><a href="https://github.com/mitaru/panels.git"> <i class="fab fa-github"></i> </a> </li> </ul> </footer> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js" integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd" crossorigin="anonymous"></script> <!-- vue.js --> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script src="panels.js"></script> </body> </html>panels.csshtml { font-size: 14px; } @media (min-width: 768px) { html { font-size: 16px; } } body { color: rgb(31, 45, 65); background: #F7F7FC; } header { background: #37384E; color: #fff; } .jumbotron { margin: 0; background: #37384E; color: #fff; border-radius: 0 0 40% 40%; } .captionText p { margin-bottom: 50px; } .text-muted { color: #9e9fb4 !important; font-size: 14px; margin: 0; } .main { max-width: 2000px; background: #F7F7FC; } .alertArea { height: 20px; } .alertArea strong { background: rgba(255, 255, 255, 0.6) !important; padding: 2px 5px; border-radius: 4px; color: #37384E; } .panelInput { min-width: 10vw; } .btn-purple { color: #fff; background: #16C995; border: 0; } .btn-purple:hover { background: #0f926d; } .pricing-header { max-width: 1000px; } section { justify-content: center; align-items: center; } input { width: 50px; border: 0; background: #fff; border-radius: 4px; padding: 3px; margin: 2px; color: #766DF4; font-weight: bold; text-align: center; } .resultText { background: rgba(118, 109, 244, 0.08) !important; color: #766df4 !important; font-size: 20px; padding: 0 10px; border-radius: 4px; font-weight: bold; } .panel { width: 150px; height: 100px; background: #fff; border: 5px solid #333; margin: 4px; text-align: center; line-height: 100px; cursor: pointer; user-select: none } .filled { background-color: #fff; background-image: radial-gradient(#16C995 14%, transparent 17%), radial-gradient(#16C995 14%, transparent 17%); background-position: 0 0, 4px 4px; background-size: 8px 8px; } footer { background: #F7F7FC; clear: both; } a .fa-github { font-size: 30px; color: #333; } a .fa-github:hover { opacity: 0.8; } @media screen and (max-width: 480px) { .panel { width: 100px; height: 70px; line-height: 70px; } }panels.js(function () { 'use strict'; new Vue({ el: '#app', data: { remainedPanelsInput: 0, filledPanels: 0, perPanel: 0, resultShow: false, alertShow: false, }, watch: { remainedPanelsInput: { handler: function () { localStorage.setItem('remainedPanelsInput', JSON.stringify(this.remainedPanelsInput)); }, deep: true }, filledPanels: { handler: function () { localStorage.setItem('filledPanels', JSON.stringify(this.filledPanels)); }, deep: true }, perPanel: { handler: function () { localStorage.setItem('perPanel', JSON.stringify(this.perPanel)); }, deep: true }, }, methods: { resultShowFunc: function () { if (this.remainedPanelsInput === 0) { this.alertShow = true; } else { this.resultShow = true; this.alertShow = false; } }, panelFinished: function () { this.filledPanels++; }, panelReturn: function () { this.filledPanels--; }, resultReset: function () { this.remainedPanelsInput = 0; this.filledPanels = 0; this.perPanel = 0; this.resultShow = false; }, }, computed: { remainedPanelsNumber: function () { return this.remainedPanelsInput - this.filledPanels; }, remainingTime: function () { return this.remainedPanelsNumber * this.perPanel; }, remainingHour: function () { return Math.round((this.remainingTime / 60) * 10) / 10; }, }, mounted: function () { this.remainedPanelsInput = JSON.parse(localStorage.getItem('remainedPanelsInput')) || 0; this.filledPanels = JSON.parse(localStorage.getItem('filledPanels')) || 0; this.perPanel = JSON.parse(localStorage.getItem('perPanel')) || 0; if (this.remainedPanelsInput > 0) { this.resultShow = true } }, }) // twitter投稿 !function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs'); })();Bootstrap4のこちらの実例をもとに作りました。ほぼ原型残ってないです。
何故わざわざテンプレートをもとに作るかと言うと、レスポンシブ対応が楽だからですね。
全力で先人に頼っていくスタイル。Vue部分
panels.jsnew Vue({ el: '#app', data: { remainedPanelsInput: 0, filledPanels: 0, perPanel: 0, resultShow: false, alertShow: false, }, // 中略 computed: { remainedPanelsNumber: function () { return this.remainedPanelsInput - this.filledPanels; }, remainingTime: function () { return this.remainedPanelsNumber * this.perPanel; }, remainingHour: function () { return Math.round((this.remainingTime / 60) * 10) / 10; }, },
remainedPanelsInput(全○コマの数)から filledPanels(完了したコマ数)を引いて
remainedPanelsNumber(残りコマ数)を出しています。
あと何分?の部分はperPanel(1コマ辺りの作業時間)と
remainedPanelsNumber(残りコマ数)を掛けて算出しています。
また分単位だけでなく時間単位の表記もあった方が親切だと思ったので
remainingHourで計算しました。
Math.round((this.remainingTime / 60) * 10) / 10;
と書くことで、
小数点第二位で切り捨てて表示することができます。panels.html<h3 v-if="!resultShow"> あと<input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ? <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultShowFunc" v-if="!resultShow">入力完了</button> </h3> <h3 v-else> 全<input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultReset" v-if="resultShow">リセット</button> </h3>panels.jsmethods: { resultShowFunc: function () { if (this.remainedPanelsInput === 0) { this.alertShow = true; } else { this.resultShow = true; this.alertShow = false; } },こちらはあと何コマ?部分のコードです。
if (this.remainedPanelsInput === 0)
で
あと何コマ?の入力欄が0の場合、入力完了ボタンを押下コマ数を入力してください」とアラートが表示されます。
1以上の数字が入力されている場合は結果が表示されます。
このような表示の分岐にv-show
やv-if
は大変便利です。panels.html<div class="panel" v-for="n in remainedPanelsNumber" @click="panelFinished"> <div class="panelInner">{{ n }}</div> </div> <div class="panel filled" v-for="n in filledPanels" @click="panelReturn"></div>panels.jsmethods: { // 中略 panelFinished: function () { this.filledPanels++; }, panelReturn: function () { this.filledPanels--; },
こちらはコマ部分です。
白いコマはremainedPanelsNumber(残りコマ数)分、
ドットのコマはfilledPanels(完了したコマ数)分表示されます。
v-for="n in remainedPanelsNumber"
と書けば
remainedPanelsNumberの数だけコマを複製してくれます。楽ちんです。
jsで作ろうとしたらコマの中にコマ数を表示するのも大変そうですが、
Vueなら{{ n }}
と書くだけですみます。おお助かる助かる。
またpanelFinished
とpanelReturn
のクリックイベントで
パネルの完了状態を変更しています。panels.jswatch: { remainedPanelsInput: { handler: function () { localStorage.setItem('remainedPanelsInput', JSON.stringify(this.remainedPanelsInput)); }, deep: true }, filledPanels: { handler: function () { localStorage.setItem('filledPanels', JSON.stringify(this.filledPanels)); }, deep: true }, perPanel: { handler: function () { localStorage.setItem('perPanel', JSON.stringify(this.perPanel)); }, deep: true }, }, // 中略 mounted: function () { this.remainedPanelsInput = JSON.parse(localStorage.getItem('remainedPanelsInput')) || 0; this.filledPanels = JSON.parse(localStorage.getItem('filledPanels')) || 0; this.perPanel = JSON.parse(localStorage.getItem('perPanel')) || 0; if (this.remainedPanelsInput > 0) { this.resultShow = true } },ローカルストレージで
remainedPanelsInput
、filledPanels
、
perPanel
の数を保存しています。
監視している変数の変更をトリガーにして勝手に働いてくれる
監視プロパティくんは便利やでホンマ。
途中保存がうまく行かなかったんですが、
mounted
部分をnew Vue
内で一番最後に配置したら
ちゃんと保存されるようになりました。どうして?(無知)デザイン部分
Bootstrap5のとあるテーマを大いに参考にさせていただきました。
見た目もできるだけ可愛くしたかったんです。
panels.css.jumbotron { margin: 0; background: #37384E; color: #fff; border-radius: 0 0 40% 40%; }
border-radius
だけでdivの下部分を丸くできるものなんですね。
今回調べて初めて知りました。他にも色んな表現ができるみたいです。
歪んだ円まで作れるなんてすごい!
参考:今さら聞けない!? CSSのborder-radiusで様々な角丸に挑戦!panels.css.filled { background-color: #fff; background-image: radial-gradient(#16C995 14%, transparent 17%), radial-gradient(#16C995 14%, transparent 17%); background-position: 0 0, 4px 4px; background-size: 8px 8px; }なんと
radial-gradient
を使えば、CSSだけで漫画のトーンみたいなドットの背景が作れます。
CSSでドット柄(水玉模様)を作成 - ホームページのパーツ作成で好きなドットを作ろう!
他にもradial-gradient
でストライプやチェック柄まで作れるみたいです。実際スゴイ!
参考:CSSグラデーションで作った背景パターンのサンプル感想
- 比較的思った通りに作れた
- 見た目もいい感じになったと思う
課題・問題点
- ページ単位の管理機能も作りたかったがややこしすぎてヤコになった
- 今までの知識の延長線上だなと思うのでもっとレベルアップしたい
- BootstrapVue使おうとして挫折した
ここでも全文載せてますが、GitHubでもコードを公開しております。
アドバイスいただけたら嬉しいです。
https://github.com/mitaru/panels次は何を作ろうかなー。ちょっとネタ切れしてきました。
- 投稿日:2020-07-03T16:21:27+09:00
【Rails】タイマー機能
はじめに
Railsでタイマー機能を実装したくて、調べてもなかなか欲しい情報が見つからず
友人から「javascriptで実装できるよ!」と教えてもらったら、あるわ♪あるわ♪情報が
無知過ぎる自分の戒め&【Rails タイマー】で検索した際、ヒットするよう記事に残します
どんな機能?
時間を指定し、時間になったらアラートがでるという機能になります。
例えば、3秒と入力し、3秒後にアラートがでる。
こんな感じです(Gyazoなのでタイミングによっては見れなくなります)
↓↓↓↓
https://gyazo.com/89773746aac0c2a1640d48e6a536190b実装
view.html.haml.timer__input %form{name: "timer"} %input{type: "text", value: ""}>/ 分 %input{type: "text", value: ""}>/ 秒 %br/ %input{onclick: "cntStart()", type: "button", value: "スタート"}/ %input{onclick: "cntStop()", type: "button", value: "ストップ"}/javascript.js//タイマー処理 var timer1; //カウントダウン関数を1000ミリ秒ごとに呼び出す関数 function cntStart() { document.timer.elements[2].disabled=true; timer1 = setInterval("countDown()",1000); } // タイマー停止関数 function cntStop() { document.timer.elements[2].disabled=false; crearInterval(timer1); } // カウントダウン関数 function countDown() { var min=document.timer.elements[0].value; var sec=document.timer.elements[1].value; if((min=="")&&(sec=="")) { alert("時間を設定してください!"); reSet(); } else { if(min=="")min=0; min=parseInt(min); if(sec=="")sec=0; sec=parseInt(sec); tmWrite(min*60+sec-1); } } // 残り時間を書き出す関数 function tmWrite(int) { int=parseInt(int); if(int <= 0) { reSet(); alert("時間です!"); } else { //残り分すうはintを60で割って切り捨てる document.timer.elements[0].value=Math.floor(int/60); //残り秒数はintを60で割った余り document.timer.elements[1].value=int % 60; } } // フォームを初期状態に戻す(リセット)関数 function reSet() { document.timer.elements[0].value="0"; document.timer.elements[1].value="0"; document.timer.elements[2].disabled=false; clearInterval(timer1); }※上記はCSSはございません
(CSSを実装する前に記事にしてしまったためです)
皆様のデザイン力で素敵なビューを作成してください感想
javascriptは偉大なり
というのが正直な感想です 笑
まだまだ勉強中の身ではありますが、少しずつ知識をつけていきたいですね参考
- 投稿日:2020-07-03T13:45:09+09:00
FullCalendarをそこそこカスタマイズしてみる。
前書き
FullCalendarを結構使ったので、メモメモ。
Fullには使い切ってないけどね。
結構いろいろできるのね。まだまだよくわかんないけど。やること
表示したいカレンダーの内容は以下。
- 祝日(日本の)がわかるようにする(なんの日かは表示しなくていい。休みだ!とわかればいい)
- Google Calendarから予定をひっぱってきて表示する
- 月カレンダーと、月ごとの予定リストを切り替えて表示できる
- 大きいスクリーンサイズは月カレンダー、小さいスクリーンサイズは予定リストをデフォルト表示とする
- 予定リスト表示の場合、予定の場所もリストに表示
- 予定をタップしたら、Google Calendarにリンク、ではなく、ポップアップ詳細を表示。(この記載からは詳細を割愛)
使うもの
- FullClaendar v5.0.1
- jQuery v3.5.1
- FontAwsome v5.4.2
- Sass(scss)
ReactやVueのようなフレームワーク、ライブラリ無し。
<script>
内に記載。なので、FullClaendarのライブラリは全部入っている状態です。(日本語以外のファイルは入れてないけど)※作ってる環境にjQuery入っているので、記載しましたが、脱jQuery方向で書いてあったります。
ふとめんどくさくなって、使ったりしてるので、リストアップに残してあります。さて、設定をしよう
カレンダーのキャンバスを準備
<div id="calendar"></div>表示するカレンダーの設定(FullCalendarのオプション設定)
今回は
- 日本語化
- カレンダーは月曜はじまりという設定を入れようと思います。
document.addEventListener('DOMContentLoaded', function() { var calendar = new FullCalendar.Calendar(calendarEl, { locale: 'ja', height: 'auto', firstDay: 1, headerToolbar: { left: "dayGridMonth,listMonth", center: "title", right: "today prev,next" }, buttonText: { today: '今月', month: '月', list: 'リスト' }, }); //キャンバスにレンダリング calendar.render(); });Google Calendar からAPIを使って日本の祝日&予定をもってくる
Google Calenadr APIの設定は割愛しますが、ブラウザ用のAPIを作って、キーを入力。
(もちろん、下記はダミーです。)日本の祝日として公開されているカレンダーと、実際の予定(イベント)を、
eventSources
に設定します。
- 祝日の方にはイベントと分ける識別子として、クラスも付与してみます。
- 今回は認証使わないので、イベントの方のカレンダーもGoogle Clendar側で公開設定するのを忘れずに。
(※ここまでに書いたソースは省略 以下このスタイルで記載します。) var calendar = new FullCalendar.Calendar(calendarEl, { googleCalendarApiKey: 'xxxxxxxxdummmmmmyyyyyyyyyxxxxxxxx', eventSources: [ { googleCalendarId: 'ja.japanese#holiday@group.v.calendar.google.com', className: 'event_holiday' }, { googleCalendarId: 'hoge-na-calendar@group.calendar.google.com', } ] });タイトル部分とかの日付表記フォーマットを指定する
ドキュメントはこちら https://fullcalendar.io/docs/date-formatting なのですが、
わかりにくくないですか?わたしだけですか。すみません。
各項目ごとに、年月日とか時間をプロパティとして設定値渡すそうです。var calendar = new FullCalendar.Calendar(calendarEl, { views: { dayGridMonth: { titleFormat: { year: 'numeric', month: 'numeric' }, }, listMonth: { titleFormat: { year: 'numeric', month: 'numeric' }, listDayFormat: { month: 'numeric', day: 'numeric', weekday: 'narrow' }, listDaySideFormat: false } }, });曜日にあわせて日にち部分に色をつける
各曜日のclassが付与されているので、cssいじればOKです。
日付部分以外に影響与えないように書いたら、こんなのになりましたが、
もっといい方法があったのかも。// 当月以外の日(月カレンダーに一緒に表示される前後の月) .fc-day-other { .fc-daygrid-day-top, .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion, .fc-list-day-cushion { color: $d-grey; } } // 土曜日 .fc-day-sat { .fc-daygrid-day-top, .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion, .fc-list-day-cushion { color: $d-blue; } } // 日曜日 .fc-day-sun{ .fc-daygrid-day-top, .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion, .fc-list-day-cushion { color: $d-red; } }祝日にあわせて日にち部分に色をつける
過去に↓なことをやってみました。(Vue ver.ですが。)
https://qiita.com/niever66/items/bef24d1f23e9075eb09d理由としては、「祝日のことは祝日データ読み込んだところに書けばいいんじゃない?」
と思ったからなのですが。。
リスト表示の場合、予定がありきで日付部分が作られるので、うまくいかなかったのです。※うまくいかなかった中身は↓です。
「HTMLCollectionのlengthってなになのでしょう?https://teratail.com/questions/274367」
日付部分のDOMをいじりたくても、レンダリング前の状態なゆえに、触れません。ということで、書き換えです。
eventDidMount
を使います。
ちなみに、
- カレンダー表示の場合は、日付部分に色をつける
- 予定リスト表示の場合は、日付部分に色をつける&曜日の表示の横に[祝]の字を追加
します。
※見直しながら思ったのですが、予定に全日が入ってこない前提だったので、予定リストのつくりが甘いです。
var calendar = new FullCalendar.Calendar(calendarEl, { eventDidMount: function(e) { let el = e.el; //普通のイベントとわけて考えるため、条件分岐。 if ( el.classList.contains('event_holiday') ) { if ( e.view.type == "dayGridMonth" ) { //カレンダー(月)表示の場合 //イベントが表示される場所の親をたどって各日の枠にたどり着いたらclassを授けよう el.closest('.fc-daygrid-day').classList.add('is_holiday'); } else if ( e.view.type == "listMonth" ) { //予定リストの場合 //祝日の兄要素(というか、前要素)にclassを授けよう el.previousSibling.classList.add('is_holiday'); /* で、innerText変えたらいいんじゃない?と思ったのだけど、 innerText変えるとinnerHTMLもHTML要素なくなって innerTextと同じ内容になってしまうのですが、、(なぜ?) ということで、文字列継ぎはぎします。 */ let t = el.previousSibling.innerText; let h = el.previousSibling.innerHTML.split(t); t = t.slice(0, -1) + '・祝' + t.slice(-1); el.previousSibling.innerHTML = h[0] + t + h[1]; /* このままだと、祝日だけど、予定ないわ~、暇人だわ、ワシ。 が、丸見えになるので、イベントが無い祝日の日付表示部分を削除。 祝日イベントの次の行が予定でなく、日付だったら削除です。 */ if (el.nextSibling.classList.contains('fc-list-day')) { el.previousSibling.remove(); } //祝日イベントも削除 el.remove() } } } });カレンダー表示の場合、↑だと祝日イベントが表示されてしまいます。
jsの中で非表示処理したらよかったのだけど、めんどくさくなったので、cssで処理。.fc-daygrid-event { &.event_holiday { display: none; } }リストに項目を追加
予定リストの表示には、予定の場所も記載するのでした。
ついでに、アイコン表示も追加します。ドキュメント https://fullcalendar.io/docs/event-render-hooks
eventContent
っていうのがあったのですが、触ってみて、よくわからなかったので、
eventDidMount
に追記して対応します。var calendar = new FullCalendar.Calendar(calendarEl, { eventDidMount: function(e) { if ( el.classList.contains('event_holiday') ) { // ~~ 略(祝日の表記設定) ~~ } else { if ( e.view.type == "listMonth" ) { // 予定の時間には「時計」アイコンを追加 let t = el.querySelector('.fc-list-event-time'); t.insertAdjacentHTML('afterbegin','<i class="far fa-clock">'); // 位置情報を取得して、他の要素とあわせたHTML要素として出力 //「地図のピン」アイコンを追加しておく let location = e.event._def.extendedProps.location; if ( location !== undefined ) { let cell = document.createElement('td'); cell.classList.add('fc-list-event-location') cell.innerHTML = location; cell.insertAdjacentHTML('afterbegin','<i class="fas fa-map-marker-alt">'); el.appendChild(cell); } } } }, });スクリーンサイズに合わせて表示形式を変更する
読み込み時のスクリーンサイズに合わせてデフォルトの設定を変えます。
document.addEventListener('DOMContentLoaded', function() { let calInitialSettings; //他の設定追加したくなったときように、objectにしてみた。 //768pxで分岐させてみました。 if ( window.innerWidth < 768 ) { calInitialSettings = {initialView: 'listMonth'}; } else { calInitialSettings = {initialView: 'dayGridMonth'}; } var calendar = new FullCalendar.Calendar(calendarEl, { initialView: calInitialSettings.initialView, }); });スクリーンサイズが変わったときにも変更を加えます。
(やってみたけど、ユーザーにとって迷惑じゃね?)var calendar = new FullCalendar.Calendar(calendarEl, { windowResize: function() { if ( window.innerWidth < 768 ) { calendar.changeView('listMonth'); } else { calendar.changeView('dayGridMonth'); } }, });カレンダーの日付部分がうざいです
locale: 'ja'
を入れいていると、月カレンダーの日付部分にいちいち日
が付きます。うざいです。
Formatter
の設定系にどうしてもこの項目の設定がみつかりません。
めちゃくちゃ悩みました。
が、結果としてめちゃくちゃあっさりできました。var calendar = new FullCalendar.Calendar(calendarEl, { dayCellContent: function(e) { e.dayNumberText = e.dayNumberText.replace('日', ''); }, });カレンダー・リスト表示のフッターにもページャー追加
カレンダーだけなら気にならなかったんですけどね。リスト表示すると、下までスクロールして「次の月見るには上に戻るんかい!」感ハンパなかったので、ボタンを追加です。
var calendar = new FullCalendar.Calendar(calendarEl, { footerToolbar: { right: "prev,next" }, });ただ、これだと、スクロール位置はそのままでリスト・カレンダーだけ送られるので、
リスト・カレンダーの上部までスクロールも一緒に行ってほしいものです。なので、カスタムボタンを追加しましょう。
const calendarEl = document.getElementById('calendar'); let calendarEl_posT = $(calendarEl).offset().top - 18; //18は上部ゆとり感にテキトウ。 var calendar = new FullCalendar.Calendar(calendarEl, { customButtons: { nextWithScroll: { icon : 'fa-chevron-right', click: function(e) { calendar.next(); $(window).scrollTop(calendarEl_posT); //なるべく使ってなかった急にjQuery! } }, prevWithScroll: { icon : 'fa-chevron-left', click: function(e) { calendar.prev(); $(window).scrollTop(calendarEl_posT); } } }, footerToolbar: { right: "prevWithScroll,nextWithScroll" }, });ボタンアイコンについては ドキュメント https://fullcalendar.io/docs/buttonIcons
themeSystemの方もbootstrapの方も、(わたしの環境のせい?)で記載してもアイコン表示されなかったので、
css側で追記します。(割愛)イベントをクリック
イベントをクリックしたら、放っておくとGoogle Calendarに画面遷移します。
遠慮願いたいです。ということで、ポップアップを作りますが。。割愛します。
document.addEventListener('DOMContentLoaded', function() { var calendar = new FullCalendar.Calendar(calendarEl, { eventClick: function(info) { //カレンダーへのリンクはさせません。 info.jsEvent.preventDefault(); hogehoge(info); } }); function hogehoge(info) { //なんかやりたいこと } });
- 投稿日:2020-07-03T13:37:10+09:00
vue-cliでvue createしたらnpm run serveに失敗した。
環境
PC - Windows 10 Home
node.js - 12.16.3
npm - 6.14.4vueプロジェクト作成
まずはグローバルにvue-cliをインストール
> npm install -g @vue/cliインストールを確認
> vue --version @vue/cli 4.4.6適当に作ったディレクトリでプロジェクト作成
今回はvueprojectという名前で進めます。> vue create .いろいろ聞かれますが、とりあえずデフォルトで進めます。
Successfully created project vueproject. Get started with the following commands: $ npm run serveと出れば成功。
言われた通り、サーバーを起動します。> npm run serveエラー発生
npm ERR! code ELIFECYCLE npm ERR! vueproject@0.1.0 serve: `vue-cli-service serve` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the vueproject@0.1.0 serve script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\user\AppData\Roaming\npm-cache\_logs\2020-07-03T03_35_30_681Z-debug.logなるほど。vue-cli-serviceがおかしい?
よく分からん。解決
ググって発見。
https://teratail.com/questions/219324プロジェクトディレクトリから下記を削除する
- node_modules
- package-lock.jsonその後、モジュールを再インストール
> npm install完了したら、サーバー起動
> npm run serveApp running at: - Local: http://localhost:8080/ - Network: http://192.168.2.107:8080/ Note that the development build is not optimized. To create a production build, run npm run build.起動確認。
http://localhost:8080/
にアクセスし、下記の画面が表示されたらOK。
まとめ
別件でも同手順で対処したことあったし、モジュール入れ直しは割とあるあるなのかも。
エラー時にもっといろいろ出る場合は別の原因がありそう。
- 投稿日:2020-07-03T13:09:32+09:00
【JavaScript】オブジェクトが未定義だった場合のエラーを回避する方法 ?(クエッションマーク)
こんにちは!
今年も夏が来ましたね〜
エンジニアのみなさま、この時期、夏バテにお気をつけてください...!
リモートワークされている人の中にはエアコンなしの部屋でコーディングされている人もいるんだとか。。。(Macはめちゃくちゃ熱こもりますし、火傷しないか心配です。。)今回は「オブジェクトが未定義だった場合のエラーを回避する方法」について、調べてみました。
ご指摘やアドバイス等をいただけると、大変助かります。本記事の対象者
JavaScript初心者〜初中級者
(おさらい)
基本パターン
const obj = {name: 'KumKum'}; console.log(obj.name) // KumKum基本的なオブジェクトの定義ですね
指定したオブジェクトの中身がnullの場合
const obj = null; console.log(obj.name) // Uncaught TypeError: Cannot read property 'name' of nullオブジェクトがnullの場合エラーになります
指定したオブジェクトの中身がundefinedの場合
const obj = undefined; console.log(obj.name) // Cannot read property 'name' of undefinedundefinedの場合も同様ですね
ここから本題
先ほどのエラーを回避するにはどうしたら良いのか。
以下のように?(クエッション)
を使うことで、undefined
をセットすることができます。指定したオブジェクトの中身がnullの場合
const obj = null; console.log(obj?.name) // undefinedエラーにならず、
undefined
が設定されます。指定したオブジェクトの中身がundefinedの場合
const obj = undefined; console.log(obj?.name) // undefinednullと同様、エラーにはなりません。
実際の使用例
const res = getData(); //APIなどの外部からデータを取得 const obj = res?.data; // 取得に失敗した場合、res.dataは存在しない console.log(obj)データ取得に成功した場合→値が設定される
データ取得に失敗した場合→undefined
が設定されるまとめ
オブジェクト名?.プロパティ名
と指定すると、
オブジェクトがnull
またはundefined
だった場合に、
エラーにならず、undefined
を返す
- 投稿日:2020-07-03T13:09:32+09:00
【JavaScript】オブジェクトが未定義だった場合のエラーを回避する方法 ?(クエッション・疑問符)
こんにちは!
今年も夏が来ましたね〜
エンジニアのみなさま、この時期、夏バテにお気をつけてください...!
中には自宅でエアコンなしの部屋でコーディングされている方もいるんだとか。。。(ただでさえ暑いのに、Macとかめちゃくちゃ熱こもりますし、心配です。。)今回は「オブジェクトが未定義だった場合のエラーを回避する方法」について、調べてみました。
ご指摘やアドバイス等をいただけると、大変助かります。本記事の対象者
JavaScript初心者〜初中級者
(おさらい)
基本パターン
const obj = {name: 'KumKum'}; console.log(obj.name) // KumKum基本的なオブジェクトの定義ですね
指定したオブジェクトの中身がnullの場合
const obj = null; console.log(obj.name) // Uncaught TypeError: Cannot read property 'name' of nullオブジェクトがnullの場合エラーになります
指定したオブジェクトの中身がundefinedの場合
const obj = undefined; console.log(obj.name) // Cannot read property 'name' of undefinedundefinedの場合も同様ですね
ここから本題
先ほどのエラーを回避するにはどうしたら良いのか。
以下のように?(クエッション)
を使うことで、undefined
をセットすることができます。指定したオブジェクトの中身がnullの場合
const obj = null; console.log(obj?.name) // undefinedエラーにならず、
undefined
が設定されます。指定したオブジェクトの中身がundefinedの場合
const obj = undefined; console.log(obj?.name) // undefinednullと同様、エラーにはなりません。
まとめ
オブジェクト名?.プロパティ名
と指定すると、
オブジェクトがnull
またはundefined
だった場合に、
エラーにならず、undefined
を返す
- 投稿日:2020-07-03T12:20:15+09:00
Vue Composition API リアクティブ周りの関数一覧
Vue Composition APIのリファレンスを見たらリアクティブ周りのAPIが大量に追加されていたのでまとめてみました。
この記事は以下バージョン時点の情報です。
- vuejs/composition-api:
v1.0.0-beta.1
- vuejs/vue-next:
v3.0.0-beta.17
Vue Composition APIのAPI Reference に掲載されているいくつかのAPIは、まだVue2系で使えるComposition APIのプラグイン(vuejs/composition-api)でサポートされていません。
そのAPIについては マークを末尾につけています。サポート対応状況についてはこちらの記載をもとにしています。reactive
引数に渡したオブジェクトのリアクティブなプロキシを返します。
Vue2系のVue.observable()と同等の機能です。ネストされたオブジェクトもリアクティブな値として保持されます。const obj = reactive({ name: "Foo", address: { city: "Bar" } }) obj.address.city = "Huga" console.log(obj.address.city) // Huga型定義
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;ref
プリミティブな値をrefオブジェクトに変換して返します。refオブジェクトはvalueプロパティでリアクティブな値にアクセスできます。
refの値としてオブジェクトが代入されている場合は、reactiveメソッドによってオブジェクトを深くリアクティブにします。const count = ref(0) console.log(count.value) // 0 count.value++ console.log(count.value) // 1型定義
interface Ref<T = any> { [RefSymbol]: true; value: T; } function ref<T extends object>(value: T): T extends Ref ? T : Ref<UnwrapRef<T>>; function ref<T>(value: T): Ref<UnwrapRef<T>>; function ref<T = any>(): Ref<T | undefined>;computed
コールバック関数を受け取り、戻り値をリアクティブでReadonlyなrefオブジェクトとして返します。
refオブジェクトの値は、コールバック関数内にあるリアクティブな値の変化に応じて再計算されます。
(getterとsetterを持つオブジェクトを渡して書き込み可能なrefオブジェクトを作ることもできます)const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 count.value++ console.log(plusOne.value) // 3 plusOne.value = 4 // Error型定義
// read-only function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>> // writable function computed<T>(options: { get: () => T set: (value: T) => void }): Ref<T>readonly ️
オブジェクト(ref or reactive or プレーン)を受け取り、読み取り専用のリアクティブなプロキシを返します。
軽量な独自storeの周りの実装で使えそうです。※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。
const original = reactive({ count: 0 }) const copy = readonly(original) console.log(copy.count) // 0 original.count++ console.log(copy.count) // 1 console.log(isReactive(copy)) // true copy.count++ // Error型定義
function readonly<T extends object>(target: T): Readonly<UnwrapNestedRefs<T>>;customRef
依存関係の追跡と更新のトリガーを明示的に制御するカスタマイズされたrefオブジェクトを返します。
限定された用途でリアクティブな値を使いたい時に良さそうです。※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。
// 代入と参照時に必ずconsole.log()を実行するref const useLoggerRef = (value: string) => { return customRef((track, trigger) => ({ get() { console.log(value) track() return value }, set(newValue: string) { console.log(newValue) value = newValue trigger() } })) } const foo = useLoggerRef("") foo.value = "Hoge" // console.log("Hoge")が実行される foo.value = "Bar" // console.log("Bar")が実行される const bar = foo.value // console.log("Bar")が実行される型定義
function customRef<T>(factory: CustomRefFactory<T>): Ref<T> type CustomRefFactory<T> = ( track: () => void, trigger: () => void ) => { get: () => T set: (value: T) => void }markRaw
リアクティブ化されない(プロキシされない)オブジェクトを返します。
適宜markRawを使いプロキシ変換をスキップすることで、不変のデータソースを持つ大規模なリストのレンダリングなどでパフォーマンスを向上させることができます。const foo = markRaw({ baz: "baz" }) const bar = reactive(foo) isReactive(bar) // false reactive関数を実行してもリアクティブにならない型定義
function markRaw<T extends object>(value: T): TshallowReactive
浅いリアクティブなオブジェクトを返します。ネストされたオブジェクトはリアクティブ化されません。
const state = shallowReactive({ foo: 1, nested: { bar: 2 } }) isReactive(state) // true isReactive(state.nested) // false型定義
function shallowReactive<T extends object>(target: T): T;shallowRef
浅いリアクティブなrefオブジェクトを返します。ネストされたオブジェクトはリアクティブ化されません。
const foo = ref({}) const bar = shallowRef({}) isReactive(foo.value) // true isReactive(bar.value) // false型定義
function shallowRef<T>(value: T): T extends Ref ? T : Ref<T>;shallowReadonly
浅いreadonlyなオブジェクトを返します。ネストされたオブジェクトはreadonlyになりません。
※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。
const state = shallowReadonly({ foo: 1, nested: { bar: 2 } }) // mutating state's own properties will fail state.foo++ // ...but works on nested objects isReadonly(state.nested) // false state.nested.bar++ // works型定義
function shallowReadonly<T extends object>(target: T): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]>; }>;toRaw
プロキシされたリアクティブなオブジェクトの生データを返します。
const foo = {} const reactiveFoo = reactive(foo) isReactive(toRaw(reactiveFoo)) // false toRaw(reactiveFoo) === foo // true型定義
function toRaw<T>(observed: T): T;unref
引数がrefの場合は
ref.value
の値を、それ以外だったら引数自体を返します。
val = isRef(val) ? val.value : val
の糖衣構文です。const foo = ref(1) const bar = 1 unref(foo) // 1 unref(bar) // 1型定義
function unref<T>(ref: T): T extends Ref<infer V> ? V : T;toRef
reactiveなオブジェクトの指定のプロパティをrefオブジェクトに変換して返します。
propsの値をリアクティブを切らず、setup関数外のコンポーザブルな関数に渡すときなどに使えます。const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') isRef(fooRef) // true型定義
function toRef<T extends object, K extends keyof T>(object: T, key: K): Ref<T[K]>;toRefs
reactiveなオブジェクトの全てのプロパティをrefオブジェクトに変換したオブジェクトを返します。
const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) isRef(stateAsRefs.foo) // true isRef(stateAsRefs.bar) // true型定義
type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]>; }; function toRefs<T extends object>(object: T): ToRefs<T>;isRef
refオブジェクトでラップされているかどうかを判定します。
const foo = ref("foo"); const bar = reactive({ bar: "bar" }); isRef(foo) // true isRef(bar) // false型定義
isRef<T>(value: any): value is Ref<T>;isReactive
reactiveかどうか判定します。reactiveで作られたオブジェクトをラップしたreadonlyなオブジェクトの場合にもtrueを返します。
const foo = ref("foo"); const bar = reactive({ bar: "bar" }); const baz = readonly(bar); const hoge = readonly(foo) isReactive(foo) // false isReactive(bar) // true isReactive(baz) // true isReactive(hoge) // false型定義
function isReactive(value: unknown): boolean;isReadonly
オブジェクトがreadonlyかどうか判定します。
※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。
const foo = ref("foo"); const bar = reactive({ bar: "bar" }); const baz = readonly(bar); isReadonly(foo) // false isReadonly(bar) // false isReadonly(baz) // true型定義
function isReadonly(value: unknown): boolean;isProxy
オブジェクトがProxyか判定します。reactiveかreadonlyで作成したオブジェクトが該当します。
※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。
const foo = ref("foo"); const bar = reactive({ bar: "bar" }); const baz = readonly(bar); isProxy(foo) // false isProxy(bar) // true isProxy(baz) // true型定義
function isProxy(value: unknown): boolean;終わりに
以上「Vue Composition APIのリアクティブ関連の関数一覧」でした。思った以上にAPIが多いですね。
業務ではref, reactive, toRef, toRefs, isRefくらいしか使ってなかったです。
今後はどんどん活用していきたいです。参考
- 投稿日:2020-07-03T11:52:58+09:00
PackerとAnsibleを使用してAlibaba Cloud ECSインスタンス上にReact JSアプリケーションイメージを作成する
このガイドでは、Packerを使ってAlibaba Cloud ECSインスタンス上にマシンイメージを構築する方法と、マシンイメージに持たせたいものを書くためのAnsibleについてお話します。
前提条件
このガイドに従うために専門家である必要はありません。必要なのは、Alibaba Cloudのアカウントと
Access key
だけです。こちらのリンクをクリックすると、Alibaba Cloud上でのアクセスキーの作成方法が表示されます。ステップ1:パッカーのインストール
私たちのシステムにpackerをインストールするには、packerの公式インストールページに従うか、パッケージマネージャ、Windows用のchocolateyとmacOS用のhomebrewを使用することができます。パッケージマネージャを使えば、パスに環境変数を追加する手間が省けます。
chocolatey を使って Windows に packer をインストールするには
1、chocolatey パッケージマネージャをインストールします。chocolatey をインストールするには、管理者として cmd を開き、以下のコマンドを貼り付けて chocolatey パッケージマネージャをインストールします。
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"2、chocolatey がインストールされていることを確認するには、
choco -v
コマンドを実行します。3、以下のコマンドを実行して、packerをインストールします。
choco install packer4、packerのインストールを確認するには、次のように実行します。
packer -v5、Packerのバージョンがインストールされているはずです。
インストールプロセスを示すGif画像homebrewを使ってMacOSにpackerをインストールするには
1、homebrewがまだインストールされていない場合は、インストールしてください。macOSにhomebrewをインストールするには、ターミナルを開いて以下のコマンドを貼り付けます。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"2、以下のコマンドを実行して、packerをインストールします。
brew install packer3、packerのインストールを確認するには、以下を実行します。
packer -v4、Packerのバージョンがインストールされているはずです。
MacOSへのインストールを示すGif画像
ステップ2: Reactアプリのマシンイメージを構築する
マシンイメージを構築するには、テンプレートファイルを作成する必要があります。テンプレート ファイルは、構築するイメージを定義するために使用します。テンプレートファイルは
JSON
形式で、Packerの様々なコンポーネントを設定する異なるキーのセットを持っています。1、example.jsonという名前のテンプレートファイルを作成し、以下のコードを貼り付けます。
{ "variables": { "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" }, "builders": [{ "type": "alicloud-ecs", "access_key": "{{user `access_key`}}", "secret_key": "{{user `secret_key`}}", "region": "us-west-1", "image_name": "ReactJS-Application", "instance_type": "ecs.t5-lc2m1.nano", "source_image": "ubuntu_16_0402_32_20G_alibase_20180409.vhd", "io_optimized":"true", "image_force_delete":"true", "ssh_username": "root", "internet_charge_type": "PayByTraffic" }], "provisioners": [{ "type": "shell", "script": "installAnsible.sh" },{ "type": "ansible", "playbook_file": "playbook.yml" }] }変数セクションでは、テンプレートファイル内のどこでも使用できる変数を定義し、
env関数を指定して環境変数からaccess_keyとsecret_keyの値を取得しています。ビルダーセクションには、特定のビルダーを設定するJSONオブジェクトの配列が含まれています。ビルダーは、マシンを作成し、そのマシンをイメージに変換する役割を担う packer のコンポーネントです。
プロビジョナーセクションでは、組み込みのソフトウェアやサードパーティ製のソフトウェアを使用して、起動後のマシンイメージをインストールしたり設定したりします。このガイドでは、プロビジョナーとして ansible と shell を使います。
2、
ALICLOUD_ACCESS_KEY
とALICLOUD_SECRET_KEY
をエクスポートするには、ターミナルで以下のコマンドを実行します。export ALICLOUD_SECRET_KEY="YOUR_ALICLOUD_ACCESS_KEY" export ALICLOUD_SECRET_KEY="YOUR_ALICLOUD_SECRET_KEY"3、playbook.ymlという名前の新しいAnsibleファイルを作成し、以下のコードを貼り付けます。example.jsonとplaybook.ymlは同じディレクトリまたはフォルダに作成します。
--- - hosts: all become: true vars: NODEJS_VERSION: 8 domain: "localhost" tasks: - name: Add gpg key for nodejs apt_key: url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key" state: present - name: Add nodejs LTS to apt repository apt_repository: repo: "deb https://deb.nodesource.com/node_{{ NODEJS_VERSION }}.x {{ ansible_distribution_release }} main" state: present update_cache: yes - name: Install nodejs apt: name: nodejs state: present - name: Setup React application shell: cmd: | npx create-react-app my-app # Setup our react application cd /root/my-app npm install -g pm2 # Install Pm2, A nodejs process manager which enables us to run our application in the background process - name: Install nginx apt: name: nginx state: present update_cache: yes - name: Remove nginx default configuration file: path: /etc/nginx/sites-enabled/default state: absent - name: enable reverse proxy # This enables us to use the public IP without passing in the port on our browser address bar shell: cmd: | cat > /etc/nginx/sites-available/my-app <<EOF server { listen 80; server_name {{ domain }}; location / { proxy_pass 'http://127.0.0.1:3000'; } } EOF - name: create symlinks for nginx configuration # make sure the nginx configuration files are always the same. file: src: /etc/nginx/sites-available/my-app dest: /etc/nginx/sites-enabled/my-app state: link notify: - restart nginx handlers: - name: restart nginx # restart nginx service service: name: nginx state: restarted4、テンプレートファイルを検証するには、
packer validate example.json
を実行します。これにより、テンプレートファイルに構文エラーなどのエラーがないことを確認します。
5、packer build example.json
を実行してReactJSイメージをビルドします。ステップ3: Reactイメージを使ってインスタンスを起動する
先ほど作成した画像を使用してインスタンスを作成するには、以下の手順に従います。
1、アリババクラウドコンソールにログオン
2、ダッシュボードのサイドナビゲーションバーにあるElastic Compute Serviceをクリックします。
3、インスタンスを作成するには、[Create Instance] の説明が記載されたボタン、またはこのリンクをクリックします。
4、基本設定では、以下の操作を行います。----- 1、
課金方法
については、「Pay-As-You-Go
」をクリックしてください。
----- 2、地域
については、ドロップダウンをクリックして、US West 1 (Silicon Valley)
を選択してください。
----- 3、インスタンスタイプ
では、「エントリーレベル(共有)
」をクリックします。
----- 4、画像
は「Custom Image
」ボタンをクリックします。Custom Image
の下のドロップダウンメニューをクリックして、作成したイメージはReactJS-Application
を選択します。
----- 5、Next: Networking
ボタンをクリックします。5、2つ目のステップであるネットワーキングについては、以下の手順に従ってください。
-----1、Networkセクションでは、「VPC」ドロップダウンメニューをクリックし、デフォルトのVPCを選択します。
-----2、Security Groupセクションでは、「ポート設定に移動」をクリックして、アプリケーションのポートを設定します。
-----3、Add Security Group Ruleをクリックし、Port Rangeを80に、Authorization Objectsを0.0.0.0.0/0に設定します。これを繰り返しますが、ポート範囲を3000に設定します。
ポート80はHTTPリクエストのためのもので、ポート300`はリアクトアプリケーションが実行されているポートです。
-----4、セキュリティグループを設定したら、次へをクリックします。Next: System Configurations ボタンをクリックします。6、3番目のステップであるシステム設定については、以下の手順に従ってください。
-----1、詳細設定(インスタンスRAMのロールまたはクラウドイニットに基づく)ドロップダウンをクリックして、以下のコードをユーザーデータフィールドボックスに貼り付けます。#!/usr/bin/env bash cd /root/my-app pm2 start /root/my-app/node_modules/react-scripts/bin/react-scripts.js --name my-app -- start上記のスクリプトはインスタンスの作成時に実行されるので、アプリケーションを起動するためにインスタンスに SSH 接続する必要はありません。
7、プレビューボタンをクリック
8、利用規約に同意し、「インスタンスの作成」ボタンをクリックします。
9、インスタンスが起動したら、インターネットの IP アドレスをコピーしてブラウザのアドレスバーに貼り付けます。ReactアプリケーションのWebページが表示されるはずです。
インスタンスを起動するプロセスを示すgif画像は以下の通りです。アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-07-03T11:27:27+09:00
javascriptの要素アクセス諸々
HTML要素のタグ内のドキュメント取得
innerHTML:HTMLタグを含めた文字列
textContent:HTMLタグを除いた文字列HTML要素のタグ内のドキュメント設定
textContent:HTMLタグがない場合
HTMLタグ含めたい場合はcreateElementやcreateTextNodeでタグ要素を生成して
appendChildで挿入するHTML要素の親子取得
<ul id="login-users"> <li id="suzuki-dhyi">suzuki</li> <li id="tanaka-ddha">tanaka</li> <li id="sato-xydu">sato</li> </ul>// ul要素取得 let ul = document.getElementById('login-users'); // 添え字アクセス let _user = ul.children; _user[0]; _user[1]; // 子要素をループで回す Array.from(ul.children).forEach(user => { console.log(user); }); // 子要素から親要素を取得 let user1 = document.getElementById('suzuki-dhyi'); let par = user1.parentNode; // 要素内の検索 let tanaka = ul.querySelector('tanaka-ddha'); // 子要素の末尾に追加:appendChild // 子要素の最初に追加:insertBefore // 要素の削除:remove
- 投稿日:2020-07-03T11:16:34+09:00
Webhooksを使ったReactアプリのデプロイとUbuntuでのSlackの統合
このチュートリアルでは、Ubuntuを使用したAlibaba Cloud Elastic Compute Service (ECS)インスタンス上でWebhooksを使用してSlackの通知をトリガーします。
本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
前提条件
1、Alibaba Cloud Elastic Compute Service (ECS)を有効化し、有効な支払い方法を確認する必要があります。新規ユーザーの場合は、Alibaba Cloudアカウントで無料アカウントを取得することができます。ECSインスタンスのセットアップ方法がわからない場合は、このチュートリアルまたはクイックスタートガイドを参照してください。ECSインスタンスは、少なくとも2GB RAMと1コアプロセッサを搭載している必要があります。
2、Alibaba Cloudから登録されたドメイン名。すでにAlibaba Cloudまたは他のホストからドメインを登録している場合は、そのドメインネームサーバーレコードを更新することができます。
3、ドメイン名は、あなたのAlibaba Cloud ECSのIPアドレスを指している必要があります。
4、Alibaba CloudのVNCコンソールまたはPCにインストールされているSSHクライアントにアクセスします。
5、サーバーのホスト名を設定し、root権限を持つユーザーを作成します。Ubuntuのシステムを更新する
パッケージのインストールを進める前に、以下のコマンドを使用してUbuntuシステムをアップデートしてください。このコマンドを実行するには、root ではないユーザーから sudo 権限でログインすることを忘れないでください。このコマンドを実行すると、Is this ok? 'y'と入力してEnterキーを押します。
# sudo apt update && sudo apt upgradeNginxサーバーのインストール
nginxサーバーをインストールするためには、以下の手順に従う必要があります。
ステップ1
インストールするには、以下のコマンドを実行します。
# sudo apt-get install nginxステップ2
以下のコマンドを実行してNginxサーバーを起動します。
# sudo systemctl start nginxNginxサーバーの状態を確認するには、以下のコマンドを実行します。
# sudo systemctl status nginxインストールを確認するには、アリババクラウドECSのIPアドレスか、IPアドレスを指したドメイン名でアクセスします。私の場合はドメイン名経由でアクセスし、以下の画面が読み込まれました。
Gitのインストール
ローカルマシンだけでなく、サーバーにもgitをインストールする必要があります。Gitをインストールして設定するには、以下の手順に従います。
ステップ1
Gitをインストールするには、コマンドを実行します。
# sudo apt-get install gitステップ 2 (オプション)
以下のコマンドを実行して Git を設定します。名前と有効なメールアドレスを指定してコミットメッセージに正しい情報が含まれるようにします。
# git config --global user.name "Aareez" # git config --global user.email "xyz@example.com"Node.js のインストール
Node.jsは、ブラウザの外でJSコードを実行するクロスプラットフォーム・オープンソースのJSランタイム環境です。Node.jsのバージョンはUbuntuのデフォルトリポジトリにあります。PPAを使用してNode.jsをインストールするには、以下の手順に従ってください。
以下のコマンドを実行して、お好みのバージョンのスクリプトを取得します。
# curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.shでは、以下のコマンドでスクリプトを実行します。
# sudo bash nodesource_setup.shインストールするには、コマンドを実行します。
# sudo apt-get install -y nodejsこれで無事にNode.jsのインストールが完了しました。
gccとg++をインストール
また、ネイティブアドオンを構築するための開発ツールも必要になるかもしれません。それらをインストールするには、コマンドを実行します。
# sudo apt-get install gcc g++Yarnのインストール
Yarn パッケージマネージャをインストールするには、以下のコマンドを実行します。
# curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - # echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list # sudo apt-get update && sudo apt-get install yarnnpmを動作させるためには、build-essentialをインストールする必要があります。インストールするには以下のコマンドを実行してください。
# sudo apt-get install build-essentialcreate-react-appを使ってReactアプリを作成する
まず、create-react-appでWebhookをテストするためのアプリケーションを構築します。この後、GitHubのリポジトリを作成し、そのリポジトリにプロジェクトのコードをプッシュする必要があります。
create-react-appのノードモジュールをグローバルリポジトリに追加するには、以下のコマンドを実行してシェルから利用できるようにする必要があります。
# sudo npm install -g create-react-appaareez-projectという名前のプロジェクトを作成するには、以下のコマンドを実行します。
# sudo create-react-app aareez-project以下のコマンドで作成したプロジェクトディレクトリに移動します。
# cd aareez-projectaareez-projectディレクトリにあるgitでリポジトリを初期化します。そのためには、以下のコマンドを実行します。
# sudo git initここで、GitHub の URL と一緒にリモートオリジンを追加します。
# sudo git remote add origin https://github.com/itsaareez1/react-exampleプロジェクトディレクトリにあるファイルをステージします。
# sudo git addそれらをコミットし、以下のコマンドを実行してリポジトリにプッシュします。
# sudo git commit -m "initial commit" # sudo git push -f origin masterReact用ディレクトリの設定とNginxサーバーの設定
ここでは、Githubのリポジトリからreactアプリのクローンを取得する必要があります。
ルートディレクトリに移動します。
# cd ~これでGithubからファイルをクローンします。
# sudo git clone https://www.github.com/itsaareez1/react-example上記のgitリンクを使ってプロジェクトをクローンします。
コマンドでcloneディレクトリに移動します。
# cd react-exampleNginxが動作するようにindex.htmlやJSファイル、CSSファイルなどのページを格納したフォルダを作成するには、yarnのbuildコマンドを実行する必要があります。
# sudo yarn && sudo yarn buildここで、Nginx経由でファイルにアクセスできるようにするために、シンボリックリンクを作成する必要があります。Nginxはアプリケーションを/var/wwwディレクトリに配置します。 シンボリックリンクの作成は以下のコマンドを実行してください。
# sudo ln -s ~/react-example /var/www/react-exampleNginxがシンボリックリンクを正しく動作させるための権限を付与するには、以下のコマンドを実行します。
# sudo chmod -R 755 /var/wwwここで、利用可能なサイトディレクトリにnginxサーバーブロックを設定します。以下のコマンドを実行すると、ファイルが開きます。
# sudo nano /etc/nginx/sites-available/react開いたファイルに以下のテキストをコピーし、softpedia.xyzをドメイン名またはECSのIPアドレスに変更して保存してください。
server { listen 80; root /var/www/react-example/build; index index.html index.htm index.nginx-debian.html; server_name softpedia.xyz; location / { try_files $uri /index.html; } }ここで、利用可能なサイトディレクトリにある react の設定用のシンボリックリンクを、有効になっているサイトディレクトリに作成します。
# sudo ln -s /etc/nginx/sites-available/react /etc/nginx/sites-enabled/reactnginxの構文が正しいかどうかを確認するには、以下のコマンドを実行します。
# sudo nginx -t設定をロードするには、nginx サーバーを再起動します。
# sudo systemctl restart nginxすべての設定が正しく行われているかどうかを確認するために、ブラウザでIPアドレスやドメインにアクセスすると、以下のような画面が表示されます。
Webhooks のインストールと設定
設定可能なエンドポイントを持つ HTTP サーバをフックと呼びます。Webhook サーバは、いくつかの HTTP リクエストを受信したときに、設定可能なルールのセットに従ったカスタマイズ可能なコードを実行します。
ホームに移動する
# cd ~webhookをダウンロードします。
# sudo wget https://github.com/adnanh/webhook/releases/download/2.6.6/webhook-linux-amd64.tar.gzダウンロードしたフォルダを展開します。
# sudo tar -xvf webhook-linux-amd64.tar.gzバイナリを/usr/local/binに移動して、自分の環境で利用できるようにします。
# sudo mv webhook-linux-amd64/webhook /usr/local/binダウンロードしたフォルダを削除します。
# sudo rm -rf webhook-linux-amd64*ここで、主にサードパーティ製のアプリケーションを配置するoptフォルダ内に、スクリプトとフックのためのディレクトリを作成します。
# sudo mkdir /opt/scripts # sudo mkdir /opt/hooksこれらのディレクトリの権限をユーザ名に割り当てます。
# sudo chown -R aareez:aareez /opt/scripts # sudo chown -R aareez:aareez /opt/hooksGitHubがHTTPリクエストを送信すると、JSON配列ファイルで定義されたルールに基づいてwebhookが起動されます。ここで、webhook用の設定ファイルを作成する必要があります。そのためには、以下のコマンドを実行します。
# sudo nano /opt/hooks/hooks.json開いたファイルに以下のテキストをコピーします。
[ { "id": "redeploy-app", "execute-command": "/opt/scripts/redeploy.sh", "command-working-directory": "/opt/scripts", "pass-arguments-to-command": [ { "source": "payload", "name": "head_commit.message" }, { "source": "payload", "name": "pusher.name" }, { "source": "payload", "name": "head_commit.id" } ], "trigger-rule": { "and": [ { "match": { "type": "payload-hash-sha1", "secret": "654321Ab", "parameter": { "source": "header", "name": "X-Hub-Signature" } } }, { "match": { "type": "value", "value": "refs/heads/master", "parameter": { "source": "payload", "name": "ref" } } } ] } } ]GitHub の通知を設定する
GitHub リポジトリで、master へのコミットが発生した際に HTTP リクエストが発生するように設定するには、以下の手順に従う必要があります。
1、GitHub リポジトリを開き、以下のように設定をクリックします。
2、この後、Webhooksセクションに移動します。
4、Payload URL には http://your_server_ip:9000/hooks/redeploy-app と入力してください。softpedia.xyzをECSのIPアドレスまたはドメインに置き換えることを忘れないでください。Content typeには、option application/jsonを選択します。hooks.jsonファイルでは、秘密鍵654321Abを使用していますが、任意のものを使用することができます。秘密鍵には654321Abを使用します。その後、Just the push eventオプションを選択します。アクティブチェックボックスにチェックを入れて、Add webhookボタンをクリックします。
これで、あなたや誰かが GitHub リポジトリにコミットするたびに、GitHub はコミットイベントに関する情報を含む Payload を含む POST リクエストを送信するようになりました。
リデプロイスクリプトを書く
redeploy.shスクリプトにWebhookを指定しているので、スクリプトを作成する必要があります。以下のコマンドを実行して、スクリプトを書くためのファイルを開きます。
# sudo nano /opt/scripts/redeploy.sh以下のコードをコピーして、開いたファイルに貼り付けてください。
#!/bin/bash -e function cleanup { echo "Error occoured" # !!Placeholder for Slack notification } trap cleanup ERR commit_message=$1 # head_commit.message pusher_name=$2 # pusher.name commit_id=$3 # head_commit.id # !!Placeholder for Slack notification cd ~/react-example/ sudo git pull origin master sudo yarn && yarn build # !!Placeholder for Slack notification以下のコマンドでスクリプトを実行可能な状態にします。
# sudo chmod +x /opt/scripts/redeploy.shNginxは/var/wwwフォルダにreact-exampleを置くように設定されているため、上記のスクリプトを実行するとビルドディレクトリが更新され、新しいファイルがサーバに送られてきます。
## run webhook server to test configuration. Execute the command below for it. # webhook -hooks /opt/hooks/hooks.json -verboseすべてが正常に動作すると、以下のような画面が表示されます。
テストのためには、Webhookを起動したまま、複製セッションを開き、以下のコマンドを実行してください。
# sudo git commit --allow-empty -m "Trigger notification" # sudo git push origin master以下のような画面が表示されるはずです。
Slackの通知を統合する
Slackの通知を追加するには、redeploy.shを修正してSlackからの通知を送受信するようにします。Slackの設定は以下の手順で行います。
1、左上のドロップダウンメニューをクリックし、「Slackのカスタマイズ」を選択します。
2、次に、アプリの設定に移動します。
3、管理パネルで、「統合を選択」を選択します。
4、Incoming Hooks integrationを検索します。
5、Add Configurationをクリックします。
6、チャンネルを選択または作成します。
7、Add Incoming Webhooks integrationをクリックします。
Slackのウェブフック設定が表示されます。WebhookのURLをメモしておきます。コマンドを使ってredeploy.shスクリプトを開きます。# sudo nano /opt/scripts/redeploy.sh前のコードを削除し、以下のコードを追加します。slack_webhook_urlをWebhookのURLに置き換えることを忘れないでください。
#!/bin/bash -e function cleanup { echo "Error occoured" curl -X POST -H 'Content-type: application/json' --data "{ \"text\": \"Error occoured while building app with changes from ${pusher_name} (${commit_id} -> ${commit_message})\", \"username\": \"buildbot\", \"icon_url\": \"https://i.imgur.com/JTq5At3.png\" }" your_slack_webhook_url } trap cleanup ERR commit_message=$1 # head_commit.message pusher_name=$2 # pusher.name commit_id=$3 # head_commit.id curl -X POST -H 'Content-type: application/json' --data "{ \"text\": \"Started building app with changes from ${pusher_name} (${commit_id} -> ${commit_message})\", \"username\": \"buildbot\", \"icon_url\": \"https://i.imgur.com/JTq5At3.png\" }" your_slack_webhook_url cd ~/react-example/ sudo git pull origin master sudo yarn && sudo yarn build curl -X POST -H 'Content-type: application/json' --data "{ \"text\": \"Build and deploy finished with changes from ${pusher_name} (${commit_id} -> ${commit_message})\", \"username\": \"buildbot\", \"icon_url\": \"https://i.imgur.com/JTq5At3.png\" }" your_slack_webhook_urlテストは、Webhookを起動した状態で、セッションを複製して以下のコマンドを実行してください。
# sudo git commit --allow-empty -m "Trigger notification" # sudo git push origin masterこれで完了です! Slackチャンネルでは、開始・終了したアプリケーションのビルドに関する通知やメッセージを受け取ることができます。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-07-03T10:18:27+09:00
なぜサーバーサイド言語と合わせて、JabvaScriptを理解しておく必要があるのか。
PHPは通信を行わないと変化が起こらない。サーバーサイド。
例:現在時刻は、リロードした時にだけわかる。
JavaScriptは、通信がなくても変化が起きる。クライアントサイド。
例:現在時刻は、毎秒変わる。javascriptのデメリットは、データの保存ができない。その時その端末のみのアクション、データ、記録。
Ajax(非同期通信)=JavaScript
- 投稿日:2020-07-03T10:15:10+09:00
JavaScript: EventTargetでイベントを送受信する方法
この投稿ではJavaScriptの
EventTarget
を使って、ブラウザ上でイベントを送信したり受信したりする方法を紹介します。サーバサイドでNode.jsを使っていると、ビルトインライブラリにeventsがあり、EventEmitterクラスでイベントの送受信ができて便利ですが、これはNode.js固有のものなのでブラウザでは使えません。代わりに、クライアントサイドではEventTargetを使うことで、EventEmitterのようなことができます。
EventTargetは多くのブラウザが対応
EventTarget
は、多くのブラウザが対応しており、特に外部ライブラリのインストールを必要としません。EventTargetの基本的な使い方
まず、
EventTarget
をnew
します。const events = new EventTarget()イベントを受信するイベントリスナーの登録は、
addEventListener
メソッドでします:const fooListener = ({ type, detail }) => console.log({ type, detail }) events.addEventListener('foo', fooListener)イベントリスナーの第一引数のオブジェクトにはいろいろな情報が入ってきますが、
type
プロパティとdetail
プロパティは重要です。type
プロパティにはイベント名が、detail
プロパティには送信されたイベントのカスタムデータが入ってきます。イベントを送信するには、
dispatchEvent
メソッドを使います:events.dispatchEvent(new CustomEvent('foo', { detail: 'message1' }))
dispatchEvent
の第一引数は、CustomEvent
です。CustomEvent
コンストラクタには第一引数にイベント名、第二引数のdetail
プロパティに一緒に送りたいカスタムデータを入れます。上の例ではdetail
に文字列を入れていますが、配列やオブジェクトでも大丈夫です。以上の基本的な使い方を踏まえて、イベントの送受信をする簡単なサンプルコードが次です:
// EventTargetの作成 const events = new EventTarget() // イベントリスナーの登録 const fooListener = ({ type, detail }) => console.log({ type, detail }) events.addEventListener('foo', fooListener) // イベントの送信 events.dispatchEvent(new CustomEvent('foo', { detail: 'message1' })) events.dispatchEvent(new CustomEvent('foo', { detail: 'message2' })) events.dispatchEvent(new CustomEvent('foo', { detail: 'message3' }))これを実行すると、3つのイベントが送信され、イベントリスナーはその3イベントを受信します。Chromeでの実行結果は次のようになります:
以上が、EventTargetの基本的な使い方です。
関連ページ
- 投稿日:2020-07-03T10:00:29+09:00
JSですべての子孫要素に処理する
jslet parentNodeList = document.querySelectorAll('#parent *'); changeAllDescendants(parentNodeList); function changeAllDescendants(nodeList) { for (let elm of nodeList) { console.log(elm); //やりたい処理 } }もしくは
jslet parentElm = document.querySelector('#parent'); changeAllDescendants(parentElm); function changeAllDescendants(parent) { console.log(parent); //やりたい処理 if (!parent.hasChildNodes) { return } // 次の子へ for (let child of parent.childNodes) { changeAllDescendants(child); } }
- 投稿日:2020-07-03T09:37:02+09:00
【javascript】一言で、 Javascript 他ファイルの呼び出し おまけ付き
【ゴール】
他jsファイルを呼び出し、読み込む
【メリット】
■javascript 理解度向上
■コードの可読性向上 整理出来るので【開発環境】
■ Mac OS catalina
【コード】
以下2点処理必要
①importを使用
*読み込ませたいファイルに
*ファイルの一番最初に記述
*’ ’内はファイル名hoge.jsimport HogeHoge form './hogehoge'; class Hoge{ . . . }②exportを使用
*呼び出したいファイルに記述
*ファイルの末尾に記述
*default後はクラス名hogehoge.jsclass HogeHoge{ . . . } export default HogeHoge;【番外編】定数、変数の呼び出し
*基本的には同じ
import
hoge.jsimport content form './hogehoge'; class Hoge{ . . . }export
hogehoge.jsclass HogeHoge{ const content = "HogeHoge"; . . } export default content;ちなみに
import: 輸入 持ち込む
export: 輸出、書き出す
という意味です。以上
【合わせて読みたい】
■ 【javascript】 テンプレートリテラル とは 一言で。
https://qiita.com/tanaka-yu3/items/9b07bd9fc4126291be28■ 【Javascript】 メソッド まとめ 基礎基本コード メモ
https://qiita.com/tanaka-yu3/items/2438798d159fa402b1d5■ 【Javascript】JS 変数 定数 違い 一言でまとめました
https://qiita.com/tanaka-yu3/items/51b8b0630a1e4e2d52c8
- 投稿日:2020-07-03T09:12:18+09:00
【JavaScript】Base64変換ライブラリで速度対決
こんにちわ。
皆さんもBase64は使われているかと思います。
バイナリを文字列で表現できるのは何かと便利ですよね。しかしJavaScriptには、こうサクっと1発でバイナリ配列とBase64文字列を変換できる実装が用意されていないので、何かしらの手を加える必要があります。
今回は、バイナリとBase64の相互変換を行える3つの方法について、どれが1番速いかを検証してみようと思います。
方法1, 標準実装
ライブラリを使わず、これらのブラウザ標準実装を組み合わせて変換します。
TextEncoder/TextDecoderは、文字列とUint8Arrayを相互変換してくれるAPIです。
そしてatob/btoaは、お馴染みBase64のブラウザ標準実装です。この2つを組み合わせることにより、以下の流れでバイナリとBase64を変換できます。
Uint8Array <=> BinaryString <=> Base64
BinaryStringとは
0x00
~0xFF
までの文字(制御文字含)で構成される文字列です。バイナリを型付き配列やBlobとして扱うのが難しい時によく用いられる表現です。
そしてatob/btoaは、ちょうど
0x00
~0xFF
までの文字を扱えます。
逆にこれしか扱えないので、Unicodeを扱う際によく問題となりますが...TextEncoder/TextDecoder APIはクラスとして提供されるので、Base64向けの拡張クラスを作ってみました。
// Base64 => Uint8Array class Base64Encoder extends TextEncoder{ constructor(){ super(); } encode(text){ return super.encode(atob(text)); } } // Uint8Array => Base64 class Base64Decoder extends TextDecoder{ constructor(){ super(); } decode(buffer){ return btoa(super.decode(buffer)); } }Node.jsはどうするんだって...?
最近話題 のDenoさんが何とかしてくれるでしょう...知らんけど。方法2, base64-js
週間14,700,000ダウンロードの実績があるライブラリです。
恐らくこれが1番メジャーでしょうか。メソッドは3つのみ、シンプルで使いやすいです。
Base64とUint8Arrayを相互変換できます。
方法3, js-base64
週間6,150,000ダウンロードの実績があるライブラリです。
2番手といったところでしょうか。base64-jsと比べてライブラリ容量は大きめですが、メンテナンスは割と最近までされているようです。
こちらも、Base64とUint8Arrayを相互変換できます。
測定
以下のPCで測定を行いました。
- CORE2Quad Q9550
- DDR2-1066 4GB
- Windows7 32bit
- Google Chrome 83
部屋に転がってたPCなので、マシンスペックについては目を瞑ってもらいましょう...
元となるデータは、小さいと一瞬で終わってしまい比較にならないと予想したので、64MBのバイト配列でエンコード/デコードを行います。
これだけ大きいと、コールスタックやヒープなどのメモリ性能もついでに見れるかと思います。// 64MB const test = new Uint8Array(1024 * 1024 * 64); // 1. 標準実装 new Base64Encoder().encode(new Base64Decoder().decode(test)); // 2. base64-js base64js.toByteArray(base64js.fromByteArray(test)); // 3. js-base64 Base64.toUint8Array(Base64.fromUint8Array(test));結果
- 標準実装 ... 1240 ms
- base64-js ... 2870 ms
- js-base64 ... ぴえん?(5秒程の後にスタックオーバーフロー)
という結果になりました。
まじかよ!
ライブラリ使うより普通に標準実装を組み合わせた方が速いじゃん!js-base64に至っては完走すら出来ませんでした。
ブラウザでBase64を扱うなら、標準実装もしくはbase64-jsを使うのが良さそうです。
- 投稿日:2020-07-03T06:42:41+09:00
条件付きレンダリングって?
条件付きレンダリングてなだろう?
プログラミングにおいて複数の反応を用意して、一定の入力によって様々な出力へと変化するいわば制御構文というものがあります。これは一般的には
if文
を用いることで可能になるんですが私が今勉強しているVue.jsで条件付きレンダリングにおいてはv-if
やv-else
というものを使って表現するらしい!
まぁ簡潔に言えば条件付きレンダリングとは制御構文であると言えますね!百聞は一見に如かず!
とりあえず何かしらこの
v-if
とやらを使ってみるのが一番わかりやすいと思うのでサンプルプログラムを作ってみましょう。sample.html<div id="app"> <p v-if="flag" class="ok"> This is correct display! </p> <p v-else class="ng"> uncorrect display!!!!!!! </p> </div> <button onClick="doAction();"> Click </button> <script> var data = { flag: true } var app = new Vue({ el: '#app', data: data }); function doAction() { data.flag = !data.flag; } </script>こんな感じのことを書いといて、webで開くとこんな感じです...
サーセン!背景のこと全く考えてなくて画像の境界線が分からなくなってるけど気にしないでくれると助かります
プログラム自体は至って簡単なものです!
flag
に格納されている値がtrue
であればv-if
の方の表示がされます。ボタンを押すとdoAction
のメソッドが動き、!data.flag
によりdata
のなかのflag
の値の否定、つまりfalse
へ変化しdata.flag
へ格納します。したがって今度は値がfalseのためv-else
の方の表示がされます。
書いて見ればなんてことないただのif文でしたな〜言葉に騙されないで!
参考書とかで勉強してると時々「は⁉︎何これ!初めて聞いたはこの機能!」ということが多々あると思います。でも実際に書いて実行してみると意外と今回のようになんてことない初歩中の初歩だったりする。なんか難しい言葉が出てきたな〜とか思ったらとにかくプログラムを実際に書いてみる!これに限ることを学んだ今日この頃...
この記事ほとんど書く必要なかった〜〜〜