- 投稿日:2020-03-15T23:25:59+09:00
vue cliでmixinを作成し、インポートする方法
備忘録です。
したいこと
・vue cliでmixinを作成し、componentファイルで使いたい
同じ動作を複数箇所で使用したい場合は、mixinが便利です。SCSS/SASSで使うmixinと同じ役割です。
Step 1: mixinはjsファイルに書く
mixinはvueファイルではなく、jsファイルに書きます。ここではmyFirstMixin.jsという名前のファイルを作りました。
補足:src のフォルダー内に mixinフォルダー を作成し、その中に mixinを書いたjsファイルたち を保存しておくと管理しやすそうです。
書き方はVueファイルで作ったコンポーネントの、scriptタグの中身と一緒です。ここではcomputedを使ったmixinを書いています。mixinしたい内容を書きます。
myFirstMixin.jsファイル内
export default { computed: { filteredBlogs: function(){ return this.blogs.filter((blog) => { return blog.title.match(this.search); }) } } }Step 2: コンポーネントファイルで読み込む
component.vueファイル内
<script> import myFirstMixin from '../mixins/myFirstMixin';//ここでインポートする export default { data() { return { // } }, methods: { // }, mixins: [ myFirstMixin ] }//ここで登録する </script>これで使えるようになりました
初心者なので、何か誤解・勘違いがあれば、ご指摘いただけると幸いです
- 投稿日:2020-03-15T22:34:13+09:00
Web コンソールヘルパーのMDNを読んで勉強してみた
ただこちらのリンクを読んで試してみただけの投稿になります。リンク先に書いてある以上のことはやっていないので、リンクを見た方が早いです、よろしくお願いします。
最新のchromeかfirefoxで試しています。
$(selector, element)
document.querySelector()
と同等
$$(selector, element)
document.querySelectorAll() と似ていますが、NodeList ではなく配列を返す
$0
現在調査されている要素
$_
コンソールのコマンドラインで最後に実行した式の結果
$x(xpath, elemen, resultType)
XPathで指定できる
keys()
オブジェクトのキーの一覧を返す
values()
そのオブジェクトの値の一覧を返し
clear()
inspect()
オブジェクトを与えると、そのオブジェクトのオブジェクトインスペクターを開く
pprint()
指定された値を、読みやすい形式に整形する
firefoxでは動くのは確認できました
:help
ヘルプを表示する
fifefoxで確認しました
cd()
ページ内の別の iframe にカレントが切り替わる
copy()
Firefox 38 の新機能。 引数で指定したものをクリップボードにコピーします。引数が文字列である場合は、そのままコピーします。また引数が DOM ノードである場合は、ノードの outerHTML をコピーします。他の値である場合は引数に対して JSON.stringify を呼び出して、その結果をコピーします。
clearHistory()
実行履歴を削除
devtoolsで
↑
ボタンを押せば履歴見れるが、それが削除されていたのを確認しました
:screenshot
スクリーンショット作成
↓取れたスクショ
以上です。見ていただいてありがとうございました。m(_ _)m
- 投稿日:2020-03-15T22:25:35+09:00
「感染を封じこめる」をJavaScriptとHTML(SVG, canvas)を使って可視化してみた
プログラマとして、なにかできることはないのか、、、。
リアルなウイルスに関して、無力感を感じていたところに、以下のエントリに出会った。https://www.washingtonpost.com/graphics/2020/world/corona-simulator/
なるほど、こうやって可視化することで、パニックにならず冷静な対応をすることができるわけか、、、
このようなコードなら、プログラマには朝飯前? (これは、普段からあまり表には出てこない、プログラマという職業が、みなさまのお役にたてるチャンスかもしれないじゃないですか。)
「感染を封じ込める」を可視化する
「START」ボタンを押すと、入力した条件で、どのくらい感染を封じ込められるのか(広がるのか)がシュミレートされます。
そして、いろいろ数値を変えて実行してみることで、どのパラメータが感染に効果があるのかもわかってきます。See the Pen I tried to visualize the infection by yamazaki.3104 (@yamazaki3104) on CodePen.
初期条件では、おそらく収束するまでに 80% 近い感染者数になると思います。
トータルの感染者数を減らすには、・移動速度を減らす → movespeed
・素早く治療する(もしくは閉じこもる) → cure_days
・接触しない(手を洗う、マスクをする?) → rangeどれも効きますが、どれが効果があると思いますか?
ここに結論を書くよりも、みなさまで実際に試してみて、なにが効果的なのか各自で感じるとよいと思います。(わたしなりの結論は、このエントリの最後に書きます。)また、コードもすべて載せておきますので、CodePenからでも、このページからでもご自由にご利用ください。ただし、
このコードが正しく動作しているかどうかは、まったく保証できないし、計算された数値をどう解釈するかは各自の責任の範囲で使ってください
。「こうしたほうがいいよ」とか「こういう条件も追加してシミュレートしてみたいのだけど、、、」といったコメントも歓迎しています。(ぶっちゃけ、プログラマは「すげー!」とか「ありがとう!」という言葉に飢えていると思います。)
全コード 174行
<html> <body> <script> // このコードが正しく動作しているかどうかは、まったく保証できないし、計算された数値をどう解釈するかは各自の責任の範囲で使ってください // Dashboard var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(b){var c=0;return function(){return c<b.length?{done:!1,value:b[c++]}:{done:!0}}};$jscomp.arrayIterator=function(b){return{next:$jscomp.arrayIteratorImpl(b)}};$jscomp.makeIterator=function(b){var c="undefined"!=typeof Symbol&&Symbol.iterator&&b[Symbol.iterator];return c?c.call(b):$jscomp.arrayIterator(b)}; var DbNode=function(b,c){this.con_text=this.elm_tag_name=null;this.elm_attribute={};this.elm_contents=[];this.self_node=null;this.parent_node=c;this.update(b)};DbNode.prototype.delete_node=function(){this.elm_contents=[];this.elm_attribute={};this.con_text=this.elm_tag_name=null;this.parent_node.removeChild(this.self_node)}; DbNode.prototype.update=function(b){if("[object Object]"==toString.call(b)){if(this.elm_tag_name!=b.tag_name){for(var c=$jscomp.makeIterator(this.elm_contents),d=c.next();!d.done;d=c.next())d.value.delete_node();null!=this.self_node&&this.parent_node.removeChild(this.self_node);""==b.tag_name?this.self_node=this.parent_node:("svg-"!=b.tag_name.slice(0,4)?this.self_node=document.createElement(b.tag_name):this.self_node=document.createElementNS("http://www.w3.org/2000/svg",b.tag_name.slice(4)),this.parent_node.appendChild(this.self_node)); this.elm_tag_name=b.tag_name;this.con_text=null;this.elm_attribute={};this.elm_contents=[]}for(var e in this.elm_attribute)e in b.attribute||this.self_node.removeAttribute(e);c={};for(var f in b.attribute)c.$jscomp$loop$prop$atr_key$2$7=f,"[object Function]"==toString.call(b.attribute[c.$jscomp$loop$prop$atr_key$2$7])?(b.self_node=this.self_node,this.self_node[c.$jscomp$loop$prop$atr_key$2$7]=function(c){return function(d){b.attribute[c.$jscomp$loop$prop$atr_key$2$7](b.self_data,d,b.self_node)}}(c)): this.self_node.hasAttribute(c.$jscomp$loop$prop$atr_key$2$7)?this.self_node.setAttribute(c.$jscomp$loop$prop$atr_key$2$7,b.attribute[c.$jscomp$loop$prop$atr_key$2$7]):(d=document.createAttribute(c.$jscomp$loop$prop$atr_key$2$7),d.value=b.attribute[c.$jscomp$loop$prop$atr_key$2$7],this.self_node.setAttributeNode(d)),c={$jscomp$loop$prop$atr_key$2$7:c.$jscomp$loop$prop$atr_key$2$7};this.elm_attribute=b.attribute;if(this.elm_contents.length>b.contents.length)for(f=this.elm_contents.length-1;f>=b.contents.length;f--)this.elm_contents[f].delete_node(), this.elm_contents.pop();for(var g in b.contents)g<this.elm_contents.length?this.elm_contents[g].update(b.contents[g]):this.elm_contents.push(new DbNode(b.contents[g],this.self_node))}else this.con_text!=b&&(null!=this.self_node&&this.parent_node.removeChild(this.self_node),this.self_node=document.createTextNode(b),this.parent_node.appendChild(this.self_node),this.elm_tag_name=null,this.con_text=b)}; var Dashboard={table:[],child:{},_UI_Ary_to_HTML_obj:function(b,c){var d={tag_name:"",attribute:{},contents:[],self_data:c};if("[object Array]"!=toString.call(b)||1>b.length)return d;if("[object String]"!=toString.call(b[0])){for(var e in b)d.contents.push(Dashboard._UI_Ary_to_HTML_obj(b[e],c));return d}d.tag_name=b[0];for(var f in b)if(0!=f){e=b[f];var g=toString.call(e);if("[object Object]"==g)for(var h in e)d.attribute[h]=e[h];else"[object Array]"==g?d.contents.push(Dashboard._UI_Ary_to_HTML_obj(e, c)):d.contents.push(e)}return d},default_ui:function(b){var c=function(b){var d="",f=toString.call(b);if("[object Array]"==f)for(var g in b)d+='[ "div", { "style": "margin-left: 20px;" }, "'+g+': ", '+c(b[g])+" ], ";else if("[object Object]"==f)for(var h in b)d+='[ "div", { "style": "margin-left: 20px;" }, "'+h+': ", '+c(b[h])+" ], ";else d+='"'+b+'", ';return d.slice(0,-2)};return JSON.parse("[ "+c(b)+" ]")},bind:function(b){b.backup="";Dashboard.table.push(b);return b},_draw_animation_frame:function(){for(var b= $jscomp.makeIterator(Dashboard.table),c=b.next();!c.done;c=b.next()){c=c.value;var d=JSON.stringify(c.data);if(c.backup!=d){c.backup=d;for(var e in c.view)if("[object Function]"==toString.call(c.view[e])&&(d=document.querySelector(e),null!=d)){var f=Dashboard._UI_Ary_to_HTML_obj(c.view[e](c.data),c.data);e in Dashboard.child?Dashboard.child[e].update(f):Dashboard.child[e]=new DbNode(f,d,c.data)}}}window.requestAnimationFrame(Dashboard._draw_animation_frame)}};window.requestAnimationFrame(Dashboard._draw_animation_frame); </script> <div id='div_ui' class='Dashboard' ></div> <div id='div_svg' class='Dashboard' ></div> <div id='result' class='Dashboard' ></div> <canvas id='chart' width='555px' height='100px'></canvas> <script> class Point { constructor() { this.x = Math.random() * AREA_W this.y = Math.random() * AREA_H this.movespeed_x = (Math.random() - 0.5) this.movespeed_y = (Math.random() - 0.5) this.stat = 'nomal' this.elapsed_date = -1 // 経過日数 } move() { // 移動 this.x += this.movespeed_x * ui.data.movespeed this.y += this.movespeed_y * ui.data.movespeed if ( this.x < 0 ) { this.x *= -1 ; this.movespeed_x *= -1 } if ( this.y < 0 ) { this.y *= -1 ; this.movespeed_y *= -1 } if ( this.x >= AREA_W ) { this.x = AREA_W - (AREA_W - this.x); this.movespeed_x *= -1 } if ( this.y >= AREA_H ) { this.y = AREA_H - (AREA_H - this.y); this.movespeed_y *= -1 } } check( _a ) { // 確認 if ( this.stat != 'have' ) return this.elapsed_date++ // 経過日数 if ( this.elapsed_date > ui.data.complete_cure_days ) { this.stat = 'CompleteCure' return } const r = ui.data.range for ( const a of _a ) { const rx = this.x - a.x if ( rx > r ) continue if ( rx < -r ) continue const ry = this.y - a.y if ( ry > r ) continue if ( ry < -r ) continue if ( rx * rx + ry * ry > r * r ) continue if ( a.stat == 'nomal' ) { a.stat = 'have' a.elapsed_date = 0 } } } } const ui = Dashboard.bind( { data: { people: 1000, movespeed: 10, complete_cure_days: 14, range: 10 }, view: { 'div#div_ui.Dashboard': ( _d ) => [ [ 'div', 'people : ', [ 'input', { type:'number', value: _d.people, oninput: (_dt,_ev,_el)=>{ _dt.people = _el.value } } ] ], [ 'div', 'movespeed: ', [ 'input', { type:'number', value: _d.movespeed, oninput: (_dt,_ev,_el)=>{ _dt.movespeed = _el.value } } ] ], [ 'div', 'cure_days: ', [ 'input', { type:'number', value: _d.complete_cure_days, oninput: (_dt,_ev,_el)=>{ _dt.complete_cure_days = _el.value } } ] ], [ 'div', 'range : ', [ 'input', { type:'number', value: _d.range, oninput: (_dt,_ev,_el)=>{ _dt.range = _el.value } } ] ], [ 'button', 'START', { onclick: (_dt,_ev,_el)=>{ start_sim( _dt.people ) } } ], ], } } ) const percent = (_p) => { return parseInt( _p / ui.data.people * 1000 ) / 10 } const result = Dashboard.bind( { data: { nomal_cnt: 0, have_cnt: 0, cure_cnt: 0, day: 0 }, view: { 'div#result.Dashboard': ( _d ) => [ [ 'div', `nomal: ${ _d.nomal_cnt}, ${ percent(_d.nomal_cnt) }%` ], [ 'div', `have : ${ _d.have_cnt }, ${ percent(_d.have_cnt ) }%` ], [ 'div', `cure : ${ _d.cure_cnt }, ${ percent(_d.cure_cnt ) }%` ], [ 'div', `day : ${ _d.day }` ], ] } } ) const AREA_W = 555 const AREA_H = 333 const div_svg = Dashboard.bind( { data: { p: [] }, view: { 'div#div_svg.Dashboard': (_d) => [ [ 'svg-svg', { width: AREA_W+5*2, height: AREA_H+5*2, stroke: "#111", fill: "#ddd" }, [ 'svg-rect', { x: 0, y: 0, width: AREA_W+5*2, height: AREA_H+5*2, } ], _d.p.map( (_it)=> [ 'svg-circle', { cx: _it.x+5, cy: _it.y+5, r: '5px', fill: _it.stat=='nomal'?'hsl(95, 46%, 75%)':_it.stat=='have'?'red':'hsl(300, 41%, 59%)' } ] ) ] ] } } ) const animation = () => { for ( let p of div_svg.data.p ) p.move() for ( let p of div_svg.data.p ) p.check( div_svg.data.p ) let nomal_cnt = 0 let have_cnt = 0 let cure_cnt = 0 for ( let p of div_svg.data.p ) { if ( p.stat == 'nomal' ) nomal_cnt ++ else if ( p.stat == 'have' ) have_cnt ++ else cure_cnt ++ } result.data.nomal_cnt = nomal_cnt result.data.have_cnt = have_cnt result.data.cure_cnt = cure_cnt // チャートに線を引く const ctx = document.querySelector('canvas#chart').getContext('2d') const draw_line = ( _x1, _y1, _x2, _y2, _color ) => { ctx.beginPath() ctx.moveTo( _x1, _y1 ) ctx.lineTo( _x2, _y2 ) ctx.moveTo( _x1-1, _y1 ) ctx.lineTo( _x2-1, _y2 ) ctx.strokeStyle = _color ctx.stroke() } const n = percent( nomal_cnt ) const h = percent( have_cnt ) draw_line( result.data.day+1, 0, result.data.day+1, 100, '#fff' ) draw_line( result.data.day, 0, result.data.day, n, 'hsl(95, 46%, 75%)' ) draw_line( result.data.day, n, result.data.day, n+h, 'red' ) draw_line( result.data.day, n+h, result.data.day, 100, 'hsl(300, 41%, 59%)' ) result.data.day++ if ( result.data.day > 555 ) result.data.day = 0 if ( have_cnt > 0 ) window.setTimeout( animation, 1000/20 ) // 20fps } const start_sim = () => { div_svg.data.p = [] for ( let i=0 ; i<ui.data.people ; ++i ) div_svg.data.p.push( new Point() ) div_svg.data.p[0].stat = 'have' result.data.day = 0 window.setTimeout( animation, 1000/20 ) // 20fps } </script> </body> </html>細かい解説をしなくても、緑の丸がワシャワシャ動いているメインのところを SVG で描いていて、下のチャートの部分は canvas で描いています。どちらも一長一短がありますので、適材適所で使い分けています。
いまのところ、Chrome で動作の確認を行っています。
わたしの考察を書くと
「感染には移動が効く。とにかく一定時間じっとしているといい。ぶっちゃけ感染期間に何人と接触したのか?で決まる」
です。
このコードでは、movespeed が3以下になれば、ほどんと感染が広がらないことが確認できます。もしくは、以下の2点で感染者数が劇的に変化することがわかると思います。お試しあれ。
movespeed: 5 ← 出歩かない(移動を半分にする) cure_days: 14 range : 5 ← 感染距離を半分にする(手洗いをする)みなさま、ふんばりどころです。がんばりましょう。
このコードが少しでもみなさまのお役にたてば幸いです。
Dashboard.js
コードの先頭に minify されているコードは、Dashboard と名付けた UI ライブラリですが、まだ、テストも不十分なので、しばらく minify のままとさせてください。みなさまからの声が集まったら、しっかりテストしたのちに公開、、、するかもしれないです。ってか、UIのテストって、どうやって書くんでしょう??
Dashboard.js に関してもコメント募集中です。よろしくお願いいたします。
- 投稿日:2020-03-15T22:25:35+09:00
JavaScript と HTML を使って「感染を封じこめる」を可視化してみた
プログラマとして、なにかできることはないのか、、、。
リアルなウイルスに関して、無力感を感じていたところに、以下のエントリに出会った。https://www.washingtonpost.com/graphics/2020/world/corona-simulator/
なるほど、こうやって可視化することで、パニックにならず冷静な対応をすることができるわけか、、、
このようなコードなら、プログラマには朝飯前? (これは、普段からあまり表には出てこない、プログラマという職業が、みなさまのお役にたてるチャンスかもしれないじゃないですか。)
「感染を封じ込める」を可視化する
「START」ボタンを押すと、入力した条件で、どのくらい感染を封じ込められるのか(広がるのか)がシュミレートされます。
そして、いろいろ数値を変えて実行してみることで、どのパラメータが感染に効果があるのかもわかってきます。See the Pen I tried to visualize the infection by yamazaki.3104 (@yamazaki3104) on CodePen.
初期条件では、おそらく収束するまでに 80% 近い感染者数になると思います。
トータルの感染者数を減らすには、・移動速度を減らす → movespeed
・素早く治療する(もしくは閉じこもる) → cure_days
・接触しない(手を洗う、マスクをする?) → rangeどれも効きますが、どれが効果があると思いますか?
ここに結論を書くよりも、みなさまで実際に試してみて、なにが効果的なのか各自で感じるとよいと思います。(わたしなりの結論は、このエントリの最後に書きます。)また、コードもすべて載せておきますので、CodePenからでも、このページからでもご自由にご利用ください。ただし、
このコードが正しく動作しているかどうかは、まったく保証できないし、計算された数値をどう解釈するかは各自の責任の範囲で使ってください
。「こうしたほうがいいよ」といったコメントも募集しています。
全コード 174行
<html> <body> <script> // このコードが正しく動作しているかどうかは、まったく保証できないし、計算された数値をどう解釈するかは各自の責任の範囲で使ってください // Dashboard var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(b){var c=0;return function(){return c<b.length?{done:!1,value:b[c++]}:{done:!0}}};$jscomp.arrayIterator=function(b){return{next:$jscomp.arrayIteratorImpl(b)}};$jscomp.makeIterator=function(b){var c="undefined"!=typeof Symbol&&Symbol.iterator&&b[Symbol.iterator];return c?c.call(b):$jscomp.arrayIterator(b)}; var DbNode=function(b,c){this.con_text=this.elm_tag_name=null;this.elm_attribute={};this.elm_contents=[];this.self_node=null;this.parent_node=c;this.update(b)};DbNode.prototype.delete_node=function(){this.elm_contents=[];this.elm_attribute={};this.con_text=this.elm_tag_name=null;this.parent_node.removeChild(this.self_node)}; DbNode.prototype.update=function(b){if("[object Object]"==toString.call(b)){if(this.elm_tag_name!=b.tag_name){for(var c=$jscomp.makeIterator(this.elm_contents),d=c.next();!d.done;d=c.next())d.value.delete_node();null!=this.self_node&&this.parent_node.removeChild(this.self_node);""==b.tag_name?this.self_node=this.parent_node:("svg-"!=b.tag_name.slice(0,4)?this.self_node=document.createElement(b.tag_name):this.self_node=document.createElementNS("http://www.w3.org/2000/svg",b.tag_name.slice(4)),this.parent_node.appendChild(this.self_node)); this.elm_tag_name=b.tag_name;this.con_text=null;this.elm_attribute={};this.elm_contents=[]}for(var e in this.elm_attribute)e in b.attribute||this.self_node.removeAttribute(e);c={};for(var f in b.attribute)c.$jscomp$loop$prop$atr_key$2$7=f,"[object Function]"==toString.call(b.attribute[c.$jscomp$loop$prop$atr_key$2$7])?(b.self_node=this.self_node,this.self_node[c.$jscomp$loop$prop$atr_key$2$7]=function(c){return function(d){b.attribute[c.$jscomp$loop$prop$atr_key$2$7](b.self_data,d,b.self_node)}}(c)): this.self_node.hasAttribute(c.$jscomp$loop$prop$atr_key$2$7)?this.self_node.setAttribute(c.$jscomp$loop$prop$atr_key$2$7,b.attribute[c.$jscomp$loop$prop$atr_key$2$7]):(d=document.createAttribute(c.$jscomp$loop$prop$atr_key$2$7),d.value=b.attribute[c.$jscomp$loop$prop$atr_key$2$7],this.self_node.setAttributeNode(d)),c={$jscomp$loop$prop$atr_key$2$7:c.$jscomp$loop$prop$atr_key$2$7};this.elm_attribute=b.attribute;if(this.elm_contents.length>b.contents.length)for(f=this.elm_contents.length-1;f>=b.contents.length;f--)this.elm_contents[f].delete_node(), this.elm_contents.pop();for(var g in b.contents)g<this.elm_contents.length?this.elm_contents[g].update(b.contents[g]):this.elm_contents.push(new DbNode(b.contents[g],this.self_node))}else this.con_text!=b&&(null!=this.self_node&&this.parent_node.removeChild(this.self_node),this.self_node=document.createTextNode(b),this.parent_node.appendChild(this.self_node),this.elm_tag_name=null,this.con_text=b)}; var Dashboard={table:[],child:{},_UI_Ary_to_HTML_obj:function(b,c){var d={tag_name:"",attribute:{},contents:[],self_data:c};if("[object Array]"!=toString.call(b)||1>b.length)return d;if("[object String]"!=toString.call(b[0])){for(var e in b)d.contents.push(Dashboard._UI_Ary_to_HTML_obj(b[e],c));return d}d.tag_name=b[0];for(var f in b)if(0!=f){e=b[f];var g=toString.call(e);if("[object Object]"==g)for(var h in e)d.attribute[h]=e[h];else"[object Array]"==g?d.contents.push(Dashboard._UI_Ary_to_HTML_obj(e, c)):d.contents.push(e)}return d},default_ui:function(b){var c=function(b){var d="",f=toString.call(b);if("[object Array]"==f)for(var g in b)d+='[ "div", { "style": "margin-left: 20px;" }, "'+g+': ", '+c(b[g])+" ], ";else if("[object Object]"==f)for(var h in b)d+='[ "div", { "style": "margin-left: 20px;" }, "'+h+': ", '+c(b[h])+" ], ";else d+='"'+b+'", ';return d.slice(0,-2)};return JSON.parse("[ "+c(b)+" ]")},bind:function(b){b.backup="";Dashboard.table.push(b);return b},_draw_animation_frame:function(){for(var b= $jscomp.makeIterator(Dashboard.table),c=b.next();!c.done;c=b.next()){c=c.value;var d=JSON.stringify(c.data);if(c.backup!=d){c.backup=d;for(var e in c.view)if("[object Function]"==toString.call(c.view[e])&&(d=document.querySelector(e),null!=d)){var f=Dashboard._UI_Ary_to_HTML_obj(c.view[e](c.data),c.data);e in Dashboard.child?Dashboard.child[e].update(f):Dashboard.child[e]=new DbNode(f,d,c.data)}}}window.requestAnimationFrame(Dashboard._draw_animation_frame)}};window.requestAnimationFrame(Dashboard._draw_animation_frame); </script> <div id='div_ui' class='Dashboard' ></div> <div id='div_svg' class='Dashboard' ></div> <div id='result' class='Dashboard' ></div> <canvas id='chart' width='555px' height='100px'></canvas> <script> class Point { constructor() { this.x = Math.random() * AREA_W this.y = Math.random() * AREA_H this.movespeed_x = (Math.random() - 0.5) this.movespeed_y = (Math.random() - 0.5) this.stat = 'nomal' this.elapsed_date = -1 // 経過日数 } move() { // 移動 this.x += this.movespeed_x * ui.data.movespeed this.y += this.movespeed_y * ui.data.movespeed if ( this.x < 0 ) { this.x *= -1 ; this.movespeed_x *= -1 } if ( this.y < 0 ) { this.y *= -1 ; this.movespeed_y *= -1 } if ( this.x >= AREA_W ) { this.x = AREA_W - (AREA_W - this.x); this.movespeed_x *= -1 } if ( this.y >= AREA_H ) { this.y = AREA_H - (AREA_H - this.y); this.movespeed_y *= -1 } } check( _a ) { // 確認 if ( this.stat != 'have' ) return this.elapsed_date++ // 経過日数 if ( this.elapsed_date > ui.data.complete_cure_days ) { this.stat = 'CompleteCure' return } const r = ui.data.range for ( const a of _a ) { const rx = this.x - a.x if ( rx > r ) continue if ( rx < -r ) continue const ry = this.y - a.y if ( ry > r ) continue if ( ry < -r ) continue if ( rx * rx + ry * ry > r * r ) continue if ( a.stat == 'nomal' ) { a.stat = 'have' a.elapsed_date = 0 } } } } const ui = Dashboard.bind( { data: { people: 1000, movespeed: 10, complete_cure_days: 14, range: 10 }, view: { 'div#div_ui.Dashboard': ( _d ) => [ [ 'div', 'people : ', [ 'input', { type:'number', value: _d.people, oninput: (_dt,_ev,_el)=>{ _dt.people = _el.value } } ] ], [ 'div', 'movespeed: ', [ 'input', { type:'number', value: _d.movespeed, oninput: (_dt,_ev,_el)=>{ _dt.movespeed = _el.value } } ] ], [ 'div', 'cure_days: ', [ 'input', { type:'number', value: _d.complete_cure_days, oninput: (_dt,_ev,_el)=>{ _dt.complete_cure_days = _el.value } } ] ], [ 'div', 'range : ', [ 'input', { type:'number', value: _d.range, oninput: (_dt,_ev,_el)=>{ _dt.range = _el.value } } ] ], [ 'button', 'START', { onclick: (_dt,_ev,_el)=>{ start_sim( _dt.people ) } } ], ], } } ) const percent = (_p) => { return parseInt( _p / ui.data.people * 1000 ) / 10 } const result = Dashboard.bind( { data: { nomal_cnt: 0, have_cnt: 0, cure_cnt: 0, day: 0 }, view: { 'div#result.Dashboard': ( _d ) => [ [ 'div', `nomal: ${ _d.nomal_cnt}, ${ percent(_d.nomal_cnt) }%` ], [ 'div', `have : ${ _d.have_cnt }, ${ percent(_d.have_cnt ) }%` ], [ 'div', `cure : ${ _d.cure_cnt }, ${ percent(_d.cure_cnt ) }%` ], [ 'div', `day : ${ _d.day }` ], ] } } ) const AREA_W = 555 const AREA_H = 333 const div_svg = Dashboard.bind( { data: { p: [] }, view: { 'div#div_svg.Dashboard': (_d) => [ [ 'svg-svg', { width: AREA_W+5*2, height: AREA_H+5*2, stroke: "#111", fill: "#ddd" }, [ 'svg-rect', { x: 0, y: 0, width: AREA_W+5*2, height: AREA_H+5*2, } ], _d.p.map( (_it)=> [ 'svg-circle', { cx: _it.x+5, cy: _it.y+5, r: '5px', fill: _it.stat=='nomal'?'hsl(95, 46%, 75%)':_it.stat=='have'?'red':'hsl(300, 41%, 59%)' } ] ) ] ] } } ) const animation = () => { for ( let p of div_svg.data.p ) p.move() for ( let p of div_svg.data.p ) p.check( div_svg.data.p ) let nomal_cnt = 0 let have_cnt = 0 let cure_cnt = 0 for ( let p of div_svg.data.p ) { if ( p.stat == 'nomal' ) nomal_cnt ++ else if ( p.stat == 'have' ) have_cnt ++ else cure_cnt ++ } result.data.nomal_cnt = nomal_cnt result.data.have_cnt = have_cnt result.data.cure_cnt = cure_cnt // チャートに線を引く const ctx = document.querySelector('canvas#chart').getContext('2d') const draw_line = ( _x1, _y1, _x2, _y2, _color ) => { ctx.beginPath() ctx.moveTo( _x1, _y1 ) ctx.lineTo( _x2, _y2 ) ctx.moveTo( _x1-1, _y1 ) ctx.lineTo( _x2-1, _y2 ) ctx.strokeStyle = _color ctx.stroke() } const n = percent( nomal_cnt ) const h = percent( have_cnt ) draw_line( result.data.day+1, 0, result.data.day+1, 100, '#fff' ) draw_line( result.data.day, 0, result.data.day, n, 'hsl(95, 46%, 75%)' ) draw_line( result.data.day, n, result.data.day, n+h, 'red' ) draw_line( result.data.day, n+h, result.data.day, 100, 'hsl(300, 41%, 59%)' ) result.data.day++ if ( result.data.day > 555 ) result.data.day = 0 if ( have_cnt > 0 ) window.setTimeout( animation, 1000/20 ) // 20fps } const start_sim = () => { div_svg.data.p = [] for ( let i=0 ; i<ui.data.people ; ++i ) div_svg.data.p.push( new Point() ) div_svg.data.p[0].stat = 'have' result.data.day = 0 window.setTimeout( animation, 1000/20 ) // 20fps } </script> </body> </html>いまのところ、Chrome で動作の確認を行っています。
わたしの考察は、感染には、移動が効く。とにかく一定時間じっとしているといい。
このコードでは、movespeed が3以下になれば、ほどんと感染が広がらないことが確認できます。もしくは、以下の2点で感染者数が劇的に変化することがわかると思います。お試しあれ。
movespeed: 5 ← 出歩かない(移動を半分にする) cure_days: 14 range : 5 ← 感染距離を半分にする(手洗いをする)みなさま、ふんばりどころです。がんばりましょう。
このコードが少しでもみなさまのお役にたてば幸いです。
Dashboard.js
コードの先頭に minify されているコードは、Dashboard と名付けた UI ライブラリですが、まだ、テストも不十分なので、しばらく minify のままとさせてください。みなさまからの声が集まったら、しっかりテストしたのちに公開、、、するかもしれないです。ってか、UIのテストって、どうやって書くんでしょう??
Dashboard.js に関してもコメント募集中です。よろしくお願いいたします。
- 投稿日:2020-03-15T22:17:05+09:00
vue cliのプロジェクトにaxiosを読み込む方法
備忘録です。
したいこと
・vue cliのプロジェクトでHTTP Requestを行いたい
方法
vue.jsにはHTTP Requestをする機能が備わっていないので、HTTP RequestができるようJavascriptのライブラリを読み込む必要があります。簡単にHTTP Requestを行えることからaxiosが多くの人に好まれているようです。
まずはaxiosをプロジェクトにインストールします。
ステップ1
プロジェクト内のターミナルからaxiosをインストール?
(npmはすでにインストール済みと仮定します)
補足:Visual Studio Codeを使用している場合、プロジェクトファイルを開いた状態で表示 ? ターミナル
に行くと、ターミナルが開けます。私の場合は、vuejs
というファイルがプロジェクトファイルなので、そのなかにaxiosをインストールします。USER-no-MacBook-Air-2:vuejs user$ npm install axios数秒待つとインストールされます。
ステップ2
使用するためにはインストールするだけでなく、インポートする必要があります。プロジェクト内で使えるようにするにはmain.js内にインポートします。
main.jsファイル内
import Axios from 'axios' Vue.use(Axios)ステップ3
さらにcomponentsのなか、あるいはrootとなるapp.vueなどで使う場合は、そこにもインポートする必要があります。
app.vue内など、HTTP Requestを行いたいvueファイルの中
<script> import axios from 'axios';//ここで読み込む export default { data() { return { // } }, methods: { // } } } </script>これで使える状態になります。
補足:
.get
して情報を画面に表示させるここではJSON Placeholderというダミーテキストを返してくれるAPIサーバーを例に説明します。ここではJSON Placeholderの/postsを使います。
たとえば
#showBlogs
というdivのなかにある、<h2></h2>
には JSON Placeholder/postsのtitle を、<article></article>
には JSON Placeholder/postsのbody を表示したい場合。.getメソッドで情報を取得しなければいけません。そのためには
created(){}
をexport default
内に使います。<template> <div id="showBlogs"> <h1>Blog Articles</h1> <div v-for="(blog, idx) in blogs" v-bind:key="idx" class="single-blog"> <h2>{{ blog.title }}</h2>//ここで情報を表示する <article>{{ blog.body }}</article>//ここで情報を表示する </div> </div> </template> <script> import axios from 'axios'; export default { data() { return { blogs:[] } }, methods: { // }, created(){ axios.get('https://jsonplaceholder.typicode.com/posts').then(response => { this.blogs = response.data })//ここで情報をくれ〜〜〜というリクエストをする } } </script>このようにして私は無事画面に情報を表示することができました。とはいえ初心者なので何かあればご指摘いただけると幸いです?♀️
- 投稿日:2020-03-15T22:04:34+09:00
Node.js (TypeScript) におけるキャッシュの実装方法とその戦略
現代の Web アプリケーションにおいて、キャッシュはもはや不可欠と言っていいくらい需要な技術でしょう。アプリケーションの負荷を軽減し、ユーザーへのレスポンスを高めます。
本記事では Node.js (Typescript) を使用したバックエンド API を実装する際に、どのようなキャッシュのテクニックが使えるか解説します。※なお、説明の簡略化のためエラーハンドリングなどは省略しています。
Node.js を使用した非同期バッチパターンとキャッシュ機構
本章では以下の3つの実装パターンを比較し、Node.js を使用したキャッシュを実装していきます。
- キャッシュのないサンプルアプリケーション
- 非同期バッチ処理パターン
- キャッシュパターン
1. キャッシュのないサンプルアプリケーション
キャッシュの実装をする前に、簡単な Web API で提供されるアプリケーションを考えます。
例えば、チーム参加型の競技において個人の点数をチーム毎に集計するような機能を実装するとしましょう。
データベースには以下のように、名前
、チーム名
,点数
が含まれています。
このデータから点数を集計して返却しましょう。data.tsexport const data = [ { name: "bob", team: "A", point: 30 }, { name: "sam", team: "A", point: 83 }, { name: "john", team: "B", point: 22 }, { name: "mark", team: "B", point: 30 }, { name: "tanaka", team: "C", point: 10 }, { name: "steven", team: "C", point: 52 } ];このアプリケーションはクエリパラメータにチーム名を指定すると、そのチームの合計点数を返却します。
app.tsimport * as http from "http"; import * as url from "url"; import totalScore from "./totalScore"; http .createServer(async (req, res) => { const query = url.parse(req.url, true).query; const sum = await totalScore(query.team); res.writeHead(200); res.end(`チーム${query.team}の合計点数は${sum}です。\n`); }) .listen(8080, () => { console.log("server is now listening htttp://localhost:8080"); });キャッシュの効果を体感するために、わざと合計する処理に時間がかかるようにしておきます。今回は簡単な機能を実装していますが、実際の世界では複雑な計算をすることが多いでしょう。サーバサイドの処理で 5 秒かかってしまうアプリケーションは正直使い物になりませんね。キャッシュの仕組みを理解するには十分な題材です。
totalScore.tsimport { data } from "./data"; const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); const total = (team: string) => { let sum = 0; for (const item of data) { if (item.team === team) sum += item.point; } return sum; }; export default (team: string): Promise<number> => { return new Promise(async (resolve, reject) => { console.log(`チーム: ${team} の集計処理を開始します。`); const sum = total(team); // 無理やり時間がかかる処理に偽装する await sleep(5000); console.log(`チーム: ${team} の集計処理が完了しました。`); resolve(sum); }); };それでは実際に動作を確認してみましょう。以下では、3つのクライアントがサーバに対してリクエストを送っています。それぞれ独立して処理が実行されていることが確認できます。
さて、ここまでの処理の流れを整理しておきましょう。複数のクライアントからの処理はそれぞれ独立して実行されています。つまりクライアント A からのリクエストもクライアント B からのリクエストも同様に 5 秒ずつかかっているのです。
2. 非同期バッチ処理パターン
それではキャッシュを導入する前に、まずは Node.js 特有の非同期処理に目をつけて非同期バッチパターンを実装してみましょう。
同じ API に対して複数の非同期処理の呼び出しがある場合、呼び出される処理をバッチ処理としてしまおうという発想です。非同期処理が終わらないうちにもう一度同じ非同期処理を呼び出すなら、新しいリクエストを作成するのではなく、すでに実行中のバッチの処理結果を返すような仕組みです。
処理の流れは以下のようになります。
この方法は極めてシンプルでありながら、アプリケーションの負荷を抑えつつキャッシュ機構を使う必要がありません。さて、実際に実装の流れを確認していきましょう。まずは Batch を呼び出す Handler の実装方法を考えます。
API が呼び出された時に、すでに実行中の処理があれば、コールバック関数をキューに追加します。このコールバック関数はチームの点数の集計結果を返します。非同期処理が完了した時点で、キューに保存された全てのコールバック関数を呼び出します。この結果、同じリクエストを送ってきた全てのクライアントに対して一斉にレスポンスを返却できます。
totalScoreBatchHandler.tsimport totalScore from "./totalScore"; const queues = {}; export default async (team: string, callback) => { // 他のリクエストによってすでにキューに入っている場合は、自身のリクエストも同じキューに入れるだけ if (queues[team]) return queues[team].push(callback); queues[team] = [callback]; const score = await totalScore(team); // キューに入っている全ての callback 関数に計算結果を渡す queues[team].forEach(cb => cb(null, score)); // キューのクリア queues[team] = null; };Batch の Handler を実装したので、リクエストを受けつける箇所からの呼び出し方も少し変えなければいけません。大した変更ではありませんね。
app.tsimport * as http from "http"; import * as url from "url"; import totalScoreBatchHandler from "./totalScoreBatchHandler"; http .createServer(async (req, res) => { const query = url.parse(req.url, true).query; totalScoreBatchHandler(query.team, (err, sum) => { res.writeHead(200); res.end(`チーム${query.team}の合計点数は${sum}です。\n`); }); }) .listen(8080, () => { console.log("server is now listening htttp://localhost:8080"); });アプリケーションの振る舞いを確認してみましょう。ここで、2つのクライアントはチーム A をクエリパラメータに指定し、1つのクライアントはチーム B をクエリパラメータに指定していることに注目して下さい。
チーム A を指定したリクエストが送られたあとで、2番目のクライアントが同じくチーム A を指定してリクエストを送っています。サーバのログには集計バッチ処理の開始と終了を出力するようにしていますが、チーム A の集計処理開始のログは1つしか出ていません。これは2番目のリクエストによる新たなバッチは起動されず、キューにコールバック関数が保存されるだけとなっているためです。
そして、1、2 番目のリクエストは(ほぼ)同時に 2 つのクライアントにレスポンスが返却されています。
3. キャッシュパターン
さあ、キャッシュを導入していきましょう。非同期バッチ処理パターンだけでも強力なテクニックでしたが、キャッシュを導入することでよりアプリケーションの負荷を減らし、スループットを向上させます。
非同期バッチ処理パターンよりも考え方は簡単かもしれません。処理が終わったものをキャッシュに有効期限つきで保存するだけです。先ほどの Handler にキャッシュの機構を足していきます。集計処理が終わったら結果を一意なキー付きで Cache に格納します。一意となるキーは今回の場合、チーム名とします。キャッシュの保持期間は 10 秒とし、保持期間のうちに再度同じパラメータのリクエストがあった場合は Cache から値を取得してクライアントに返却します。
実際のユースケースではアプリケーションサーバはスケールアウトし、複数のプロセスに分散していることが一般的です。その場合は永続化する共有領域を Redis や memcached などに持たせることが好まれます。今回は説明を簡単にするため、グローバル変数にキャッシュを持つことにします。
totalScoreBatchHandler.tsimport totalScore from "./totalScore"; const queues = {}; const cache = {}; export default async (team: string, callback) => { if (cache[team]) { console.log(`キャッシュ ${team}: ${cache[team]} にヒットしました。`); return process.nextTick(callback.bind(null, null, cache[team])); } // 他のリクエストによってすでにキューに入っている場合は、自身のリクエストも同じキューに入れるだけ if (queues[team]) return queues[team].push(callback); queues[team] = [callback]; const score = await totalScore(team); // キューに入っている全ての callback 関数に計算結果を渡す queues[team].forEach(cb => cb(null, score)); // キューのクリア queues[team] = null; // キャッシュの保存 cache[team] = score; // キャッシュの削除予約 scheduleRemoveCache(team); }; function scheduleRemoveCache(team: string) { function delteCache(team) { console.log(`キャッシュ ${team}: ${cache[team]} を削除します`); delete cache[team]; } // 10 秒したらキャッシュを削除 setTimeout(() => delteCache(team), 10 * 1000); }実行してみると、その効果を体感できます。非同期バッチ処理パターンはそのまま保っています。さらに処理結果をキャッシュに保存することで、キャッシュの保持期間(10 秒間)は即座にレスポンスを返却できていることがわかります。また、実際に合計値計算を行わないためアプリケーションの負荷も下がることが期待されます。
それぞれの手法を評価する
最後に3つの実装方法でどの程度パフォーマンスに差が出るのか確認してみましょう。
検証には artillery を使用します。秒間 100 リクエストが 10 秒間、合計 1000 リクエスト発生するように負荷をかけていきます。
$ artillery quick -d 10 -r 100 -o cache.json http://localhost:8080/?team=A結果は以下のようになりました。
No バッチ処理 キャッシュ RPS 最小(ms) 最大(ms) 平均(ms) 1 なし なし 66.8 5003.6 5029.1 5006 2 あり なし 90.5 345.2 5340.8 2955.1 3 あり あり 95.6 3.1 5021.9 325.8
テスト結果の詳細結果(クリックして開く)
1. キャッシュのないサンプルアプリケーション
All virtual users finished Summary report @ 22:49:31(+0900) 2020-03-14 Scenarios launched: 1000 Scenarios completed: 974 Requests completed: 974 RPS sent: 66.8 Request latency: min: 5003.6 max: 5029.1 median: 5006 p95: 5009.5 p99: 5017.5 Scenario counts: 0: 1000 (100%) Codes: 200: 974 Errors: ENOTFOUND: 262. 非同期バッチ処理パターン
Summary report @ 22:51:21(+0900) 2020-03-14 Scenarios launched: 1000 Scenarios completed: 975 Requests completed: 975 RPS sent: 90.5 Request latency: min: 345.2 max: 5340.8 median: 2955.1 p95: 4904.8 p99: 5027.1 Scenario counts: 0: 1000 (100%) Codes: 200: 975 Errors: EMFILE: 15 ENOTFOUND: 103. キャッシュパターン
Summary report @ 22:53:23(+0900) 2020-03-14 Scenarios launched: 1000 Scenarios completed: 974 Requests completed: 974 RPS sent: 95.6 Request latency: min: 3.1 max: 5021.9 median: 325.8 p95: 4610.3 p99: 4988.9 Scenario counts: 0: 1000 (100%) Codes: 200: 974 Errors: ENOTFOUND: 26想定通り、キャッシュがあるの場合は最小数 ms でレスポンスを返却できています。あたりまえの話ですが、どの手法を使っても最大(ms)は 5 秒から変わりません。いくらキャッシュを使用しても、本来時間がかかる処理時間は減らないのです。キャッシュがない状態で受けたリクエストに対してはどうしても計算時間がかかってしまいます。ではこの課題に対する解決策はどのように考えたらよいでしょうか?
答えはいくつか考えられます。
本来時間がかかっている処理を見直す
DB からの取得がボトルネックであれば、DB のインデックスや検索条件をチューニングする。
アプリケーションの集計処理が雑なロジックの場合、高速化が見込めないか検討する。別プロセスで実行するバッチ処理に任せる
リクエストを受けてから計算するのではなく、事前に計算しておいた結果をキャッシュ用データストアに保存しておく。
この方式を採用する場合、ほぼ全てのクエリパラメータに対してバッチによる計算処理を実行するため、よほどサーバリソースが豊富に使用できる場合に限られる。また、リクエストの多いクエリパラメータを判定し、優先度をつけてバッチ処理をするなどの複雑な機構が要求される。今回は別プロセスで実行するバッチ処理に任せる方式を実装してみましょう。実行するマシン(あるいはプロセス)が異なるため、グローバル変数にキャッシュを持たせている今の仕組みは使えません。今こそ Redis を使用する時がきました。
Redis を使用して分散システムに対してキャッシュの機構を作る
スケーラブルなバッチ処理を行うために必要な永続化ストレージとして Redis を採用します。今回は Docker 上でオーケストレーションされるインフラを想定して、Redis は Docker コンテナで起動することとします。
$ docker run --name some-redis -d redis -p 6379:6379起動された Redis に対して、JavaScript からアクセスしましょう。まずはクライアントライブラリをインストールします。
$ npm install redisいままでグローバル変数でキャッシュさせていた部分を Redis に接続するように変更するだけです。コールバック関数を Promise に変換する便利なライブラリ util/promisify を使用しています。コールバック関数で実装されている非同期処理を自分でラップして実装する手間が省けて便利です。
totalScoreBatchHandlerRedis.tsimport totalScore from "./totalScore"; import * as redis from "redis"; import { promisify } from "util"; const client = redis.createClient(); const getAsync = promisify(client.get).bind(client); const setAsync = promisify(client.set).bind(client); const delAsync = promisify(client.del).bind(client); const queues = {}; export default async (team: string, callback) => { const cache = await getAsync(team); if (cache) { console.log(`キャッシュ ${team}: ${cache} にヒットしました。`); return process.nextTick(callback.bind(null, null, cache)); } // 他のリクエストによってすでにキューに入っている場合は、自身のリクエストも同じキューに入れるだけ if (queues[team]) return queues[team].push(callback); queues[team] = [callback]; const score = await totalScore(team); // キューに入っている全ての callback 関数に計算結果を渡す queues[team].forEach(cb => cb(null, score)); // キューのクリア queues[team] = null; // キャッシュの保存; setAsync(team, score); // キャッシュの削除予約; scheduleRemoveCache(team); }; async function scheduleRemoveCache(team: string) { function delteCache(team) { console.log(`キャッシュ ${team} を削除します`); delAsync(team); } setTimeout(async () => delteCache(team), 30 * 1000); }バックエンドで完全に独立したバッチを実行する
さて、これで分散システムにおけるキャッシュ機構の準備が整いました。バックエンドで完全に独立して実行されるバッチを記述しましょう。
ここでは簡単のために node-cron ライブラリを使用して cron 実行することにしています。
サーバの cron によって実現したり、AWS であれば CloudWatch Events 、GCP であれば Cloud Scheduler などを使用すると良いでしょう。スケジューラとバッチ処理を分離することで、バッチ処理するサーバを常に起動することなく必要なときだけ立ち上げる構成を取ることができます。コンピューティング環境には Lambda や CloudFunction などの FaaS を使用しても良いでしょう。totalScoreAllTeam.tsimport totalScoreBatchHandler from "./totalScoreBatchHandlerRedis"; const main = () => { ["A", "B", "C"].forEach(team => { totalScoreBatchHandler(team, (err, sum) => { console.log(`バッチ処理が完了しました。`); console.log(`チーム${team}の合計点数は${sum}です。`); }); }); }; const cron = require("node-cron"); cron.schedule("*/10 * * * * *", () => main());結果は以下のようになりました。完全にバックグラウンドでバッチを独立して実行させることにより、常にキャッシュがある状態でユーザリクエストを受け付けることができるようになりました。実際のユースケースでは今回の例のようにチームが 3 つしかないような理想的な条件ではないでしょう。その場合はリクエストが多く集中するデータを優先的にキャッシュするような機構を考える必要がある場合もあるでしょう。
実装方式 RPS 最小(ms) 最大(ms) 平均(ms) 従来のキャッシュ方式 95.6 3.1 5021.9 325.8 完全にバッチを独立させる 95.5 5.7 139.3 9 最終的な構成はこのようになりました。複数のサーバが共有できるキャッシュ用の永続化ストレージを Redis を使用することで実現しました。あとは API サーバへのリクエストを LoadBalarncer によって分散させることでスケーラブルな Web API にできます。
以上が Node.js を使用したキャッシュの基本的な考え方と戦略です。最後に説明したバッチをバックグラウンドで処理する方法は、場合によっては求められる要件に対してオーバーエンジニアリングとなることもあるでしょう。ユーザリクエストが秒間 200~300 程度であれば特に気にする必要はないかもしれませんが、秒間 1000 リクエストを超えたあたりからキャッシュとは真剣に向き合わなければいけません。適切な構成を採用し、サイトのパフォーマンスを上げていきたいですね。
- 投稿日:2020-03-15T21:41:30+09:00
[開発]javascriptフレームワーク比較
■ 【JavaScriptフレームワーク】
No JavaScriptフレームワーク 開発 公開年 バージョン(確認日時) 概要 1 Angular Google及びコミュニティ 2016年 8.2.9(2019.12) モダンなWebアプリのための機能を多く標準で備えています 2 AngularJS Google及びコミュニティ 2012年 1.7.x(2019.12) 3 Vue.js Evan You(元Google) 4 jQuery ジョン・レシグ 5 React.js Facebook及びコミュニティ 最小限の状態から開始 6 Riot.js シンプルかつ軽量なコンポーネント指向のUIライブラリ 7 Hyperapp 8 Backbone.js Angular
TypeScriptとRxJS
AngularJS
動作端末が多いアプリケーションを作成出来る
Vue.js 開発者Evan You(元Google)
ライブラリ
jQuery 開発者ジョン・レシグ
React.js
JSX、Flow、Redux
Riot.js
非常にシンプルかつ学習コストの低いコンポーネント指向のフレームワーク
Hyperapp 開発者Jorge Bucaran(Qiitaの中の人)
Backbone.js
■ 【CSSフレームワーク】
No CSSフレームワーク 開発 公開年 バージョン(確認日時) 概要 1 Bootstrap 王道 2 Materialize Google推奨のデザインガイドラインである「マテリアルデザイン」に準じたフレームワーク 3 Milligram シンプルな作りで動作が軽いのが特徴 Bootstrap
Materialize
Googleが推奨するデザインのガイドラインである「マテリアルデザイン」に準じたフレームワーク
Milligram
シンプルな作りで動作が軽いのが特徴
- 投稿日:2020-03-15T21:02:35+09:00
180日でWeb開発者になる方法(コンピューターサイエンスの学位なしで)
こちらの記事は、Indrek Lasn 氏により2019年10月に公開された『 Become a Web Developer in 180 Days (Without a CS Degree) 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。幾つものプログラミング言語、何百ものフレームワーク、何千ものライブラリが存在するなか、ウェブ開発の世界への明確な道筋は存在しません。どのような順番で、どのようなことを学べば良いのでしょうか?
私は独学から始めたプログラマーで、これまで様々なスタートアップやスイスの大手銀行などの大企業でシニアエンジニアとして働いてきました。ここでは、私が学んできたことと、その知識を活用する方法について説明します。どんな人でもWeb開発者になれます。大事なのは粘り強さと努力です。そこに本気で取り組めれば、あなたもWeb開発者になれるのです。
前置きはこれくらいにして、180日でウェブ開発者になれる方法を紹介しましょう。Web開発の分野でプログラミングのキャリアをスタートさせるには、180日あれば十分です。しかし、必ず180日でなれるとは保証できません。それよりも時間がかかる場合もあれば、短くなる場合もあるでしょう。すべては、自分がどの程度のレベルを望んでいるか、そしてそれに対してどの程度の努力を注げるかによります。
私がウェブ開発の勉強を始めたのは19歳のときです。当時は当然ながらお金があまりなく、できるだけ倹約して学ばなければなりませんでした。私が描いてきた道は、できるだけ安く、そして費用効果の高いものです。
プログラミングのブートキャンプに何百万円も出す必要はありません。探す場所さえ分かれば、無料のリソースは山ほどあります。
私はそれらのプログラミングブートキャンプがいけないと言っているのではありません。効果的であることも証明されています。しかし、同時に高すぎる費用に対する効果がないことも証明されています。このことについては次のRedditスレッドが参考になるでしょう。“The Good, Bad, and the Ugly of Web Dev Coding Bootcamps “— In Depth Explanation
アドバイス: コースや課題などを達成した時にすぐに参照できるよう、この記事をブックマークすることをおすすめします。
1〜15日目: HTMLとCSSの基礎を学ぶ
ここがあなたの旅の始まりです。基本を正しく理解することに集中しましょう。基礎がしっかりしていれば、さまざまなことがより早く理解できるようになります。
まずは小さなことから始めましょう。ハイパーテキストマークアップ言語(HTML)とは何か、そしてどのように動作するのかを学びます。HTMLは、すべてのWebサイトとWebアプリケーションの構成要素です。そしてそのスキルを完成させるために、カスケーディングスタイルシート(CSS)を学んでいきます。CSSは、WebサイトおよびWebアプリケーションのスタイル設定に使われます。
Mozillaが簡単に取り組める最新の無料学習リソースを提供しています。具体的には以下のようなものがあります:
MozillaのWeb開発入門ガイド
- Web 入門
- 基本的なソフトウェアのインストール
- ウェブサイトをどんな外観にするか
- ファイルの扱い
- HTML の基本
- CSS の基本
- JavaScript の基本
- Web サイトの公開
- Web のしくみ
本格的なWebサイトを作るのは大変な作業なので、Web開発に慣れていない人は、小さなところから始めることをおすすめします。
第二のFacebookを今すぐ作ることはできません。ですが、あなただけのシンプルなウェブサイトをオンラインに公開するのは難しくないので、まずはそこから始めましょう。
https://developer.mozilla.org/ja/docs/Learn/Getting_started_with_the_web※訳注:リンク先は日本語ページです
16〜50日目: JavaScriptの基礎を学び始める
JavaScriptは最も一般的なプログラミング言語です。ほとんど全てのウェブサイトがJavaScriptを使用しているほど、JavaScriptはとても広く普及しています。モダンブラウザをお使いなら、JavaScriptをオフにしてWebをブラウジングしてみてください。Webサイトの9割超が正常に機能しないか、ロードすらしないことがすぐにわかると思います。
WebはJavaScriptで動いているとすら言えます。Web開発の仕事には、必ず何らかの形のJavaScriptプログラミングが含まれます。なぜJavaScriptがこれほど人気があるのかについては、私の過去記事「What Makes JavaScript Popular?」をご覧ください。
その高い人気のおかげで、JavaScriptが学べる場所はたくさんあります。まだ学習を始めたばかりなら、FreeCodeCampが提供するJavaScriptカリキュラムから始めてみることをおすすめします。
FreeCodeCampのJavaScriptカリキュラムは無料で、効果的に、様々なインタラクティブなチャレンジを解きながら学ぶことができます。
このコースを修了する頃には、JavaScriptの基本とJavaScriptがWeb開発の全体像の中でどのように機能するのかを理解できるようになります。FreeCodeCamp JavaScriptコースは非常に多くのトピックをカバーしており、そのすべてに取り組むには数週間はかかるでしょう。Web開発においては、その場その場で様々な知識を吸収し、StackOverflowから様々な質問に対する答えを見つけ出す必要があります。
51~81日目: 30日間Vanilla JSコーディングチャレンジ
「Javascript30」はJavaScriptの基礎とライブラリを使わずにDOMを操作することの両方に慣れたい初級から中級の開発者やデザイナーのためのものです。
このコースは手取り足取り教えてくれるようなJavaScript101コースではありません。そのため、予めある程度のJavaScriptが扱えるようになっている必要があります。アプリケーションの開発を通じ、様々なシチュエーションに合わせてそれらを解決する手法、その理由と内容についての講義によって学習できるようになっています。
このコースでは、独断的なデザインパターン、フレームワーク、ライブラリ、そしてあらゆる種類の抽象化を避けています。これらは素晴らしいものであり、しばしば必要とされますが、それによって学習が妨げられ初期の理解が制限されることもあります。
これらのチュートリアルはブラウザAPIに大きく依存しており、そのほとんどがページとのやりとりによって進めていくものです。console.log()から抜けられないのなら、このビデオレッスンは、あなたの次のステップに最適でしょう。このコースで作っていくものもどれも非常に楽しいものばかりですよ!
このコースは無料で、楽しく、チャレンジ性もあります。毎日合計30日間、新たなチャレンジに挑戦できます。このコースを修了する頃には、JavaScript、DOM、そしてブラウザーAPIを使いこなせるようになっているはずです。
82〜120日目: フロントエンドライブラリを学ぶ(React、Vue、Angular)
競合他社に今Web開発において何かしらのフロントエンドライブラリーを使用しているかと尋ねれば、答えはほぼ必ず「はい」になるでしょう。当時(2000年代)、企業はそれぞれ独自のフレームワークやライブラリを各々のニーズに合わせて開発していましたが、現代のWeb開発においてはReact、Vue、Angular、Svelteといったオープンソースのフレームワークやライブラリを使うことがすべてになっています。
現在、フレームワークの選択肢はあまりにも多く、どのフレームワークを学習すべきかを決めるのは困難です。
ですが、最終的な目標はWeb開発の分野で仕事を見つけることであるということを忘れないでください。私のアドバイスとしては、あなたの住んでいる地域でどのフレームワークが最も人気があるかを調べ、それに従うことです。ある都市ではReact開発者の求人が100あり、またある都市ではAngular開発者の求人が1000あるかもしれません。あなたの地域で最も人気のあるものを選ぶことはあなたの利益につながるでしょう。
個人的には実際に当時選んだフレームワークでもあるReactをひいきしていますが、決断を下す前によく考えてください。そこには正しい答えも間違った答えもありません。
メモ: Reactはユーザインターフェースを構築するためのJavaScriptライブラリです。Reactについては、ホームページや公式チュートリアルで学ぶことができます。
以下が相互に繋がったWeb開発技術のグラフになります:
フレームワークを選んだら、早速何かを作ってみましょう!上達するための唯一の方法は、コンフォートゾーンから常に離れて、自分自身を押し出すことです。
何をコーディングするかについてのアイデアが必要であれば、フロントエンドのコーディング課題6選-このフロントエンドの課題、実装できますか?を参考にしてみてください。フレームワークを選んで、目標に向かって進んでいきましょう!
※訳注:翻訳記事を公開しています。
もし助けが必要ならば、GithubとStackOverflowには活発なコミュニティがあり、いつでも助けてくれるでしょう。あなたは決して一人ではありません。
121~170日目: Node、MongoDB、 PostgreSQLを学ぶ(バックエンド開発)
もうここまで来てしまったのですから、あきらめることはできません。フロントエンド開発の仕組みがわかったら、バックエンドの知識を取り込んでいきましょう。
Web開発を実際にしていくには、どんなアプリケーションにもバックエンドが必要です。近年で言うバックエンドとは、フロントエンドと対話するアプリケーションプログラミングインターフェース(API)のことを指します。
しかし、なぜバックエンドが必要なのでしょうか?たとえば、ユーザーがアプリケーションを操作するときにユーザーを作成できるとします。将来それを使用できるよう、ユーザーデータをどこかに保管する必要があるため、ユーザーを作成できるようにするためのバックエンドサービスが必要になります。バックエンドは、データベースと対話し、APIの通信を介してフロントエンドにデータを提供する仲介者の役割を担います。
バックエンドが必要なもう1つの大きな理由はセキュリティです。バックエンドとはサーバ側、つまりブラウザの外で実行することを意味します。フロントエンドのコードは誰でも開発者ツールを使って調べることができるので、機密データをそこに保存するのはセキュリティの観点から言って理にかなっていません。
もし興味を持ったのなら、Wes Bos氏の「Nodeを学ぶ」コースから始めると良いでしょう。
すべてのテクノロジーがどのように繋がっているかついての、より大きな図がこちらです。一つ一つをすべて把握する必要はありませんが、全体像を把握して自分がどこに適しているかを知っておく役に立つでしょう。
テクノロジーは、同じ開発者によって利用されることが多い関連するエコシステムに集まるものです。このネットワークグラフは、どのテクノロジーが最も相互に関連性が高いかを示しています。
171〜179日目: DevOpsとGitを学ぶ(Docker、Heroku、AWS)
あなたのWeb開発スキルを世界に示すには、Webアプリケーションをホストすることは必須です。雇用主はたとえ未完成であったとしても、その人の過去の作品を見たいはずです。
Gitを使ってGithub Pagesでプロジェクトをホストする方法を学びましょう。GitHub Pagesは無料で利用できますが、唯一の欠点はクライアント側のコードしかホストできないことです。APIをホストしたければ、HerokuやAWS、Digital Oceanなどを使って自分のサーバを立ち上げる必要があります。
およそ10チーム中9チームがGitを使ってコードを共有しています。Gitが使えれば、チームコラボレーションも容易になるため、雇用主にとって雇用しやすくなるでしょう。
最後に
最後まで読んでくれてありがとう、新しい旅がうまくいくように祈っています。
始めるのは決して簡単ではありませんが、諦めるのは簡単です。夢を諦めず、戦い続けましょう。
新しいことを学ぶのに抵抗しないこと、わからないことは聞いて、常にハングリー精神と好奇心を持ちましょう。
関連記事
フロントエンドのコーディング課題6選-このフロントエンドの課題、実装できますか?
翻訳協力
Original Author: Indrek Lasn
Thank you for letting us share your knowledge!この記事は以下の方々のご協力により公開する事が出来ました。
改めて感謝致します。
選定担当: yumika tomita
翻訳担当: siho1
監査担当: takujio
公開担当: @aoharu私たちと一緒に記事を作りませんか?
私たちは、海外の良質な記事を複数の優秀なエンジニアの方の協力を経て、日本語に翻訳し記事を公開しています。
活動に共感していただける方、良質な記事を多くの方に広めることに興味のある方は、ぜひご連絡ください。
Mailでタイトルを「参加希望」としたうえでメッセージをいただく、もしくはTwitterでメッセージをいただければ、選考のちお手伝いして頂ける部分についてご紹介させていただく事が可能です。
※ 頂いたメッセージには必ずご返信させて頂きます。ご意見・ご感想をお待ちしております
今回の記事は、いかがだったでしょうか?
・こうしたら良かった、もっとこうして欲しい、こうした方が良いのではないか
・こういったところが良かった
などなど、率直なご意見を募集しております。
いただいたお声は、今後の記事の質向上に役立たせていただきますので、お気軽にコメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
みなさまのメッセージをお待ちしております。
- 投稿日:2020-03-15T18:19:14+09:00
JavaScriptを書いてGitHub Actionsのハッカソンに参加しよう
はじめに
GitHub ActionsとはCircleCiやJenkinsのような継続的インテグレーションをGitHub内で行うことができるサービスです。これを使うと外部サービスとの連携を行わずに自動ビルド・デプロイといった設定を組むことができます。
そして今回の内容ですが、実は3/5~3/31にかけてこのGitHub Actionsのハッカソンが開催されています。参加方法は、じぶんのGitHubレポジトリにaction.yml
を用意して何かしら動作するGitHub Actionsを用意して、ここのエントリーページにアクセスして提出します。GitHub Actionsってどうやって作るの?
おそらく多くの方がこのGitHub Actionsを作成する方法にハードルを感じるような気がします。実際私も同じく難しそうだなと思ってました。しかし実際やってみると2時間くらいで何かしら動作するアクションを作成することができました。
まず、GitHub Actionsを作るためにDockerfile
を作る必要があるのかなと思ってましたが、実際はなくても良くてJavaScriptを書くだけで作成できるということを今回初めて知りました。実は公式のヘルプページでJavaScriptでアクションを作成する方法が書いてあります。とりあえず作ってみた
今回初めて自分のアクションを作ってみました。ここで公開しています。使い方は以下のような感じです。
- デベロッパーサイトで適当なLINE botを作成してUserIdとChannel access tokenを取得する
- ワークフローを書く。
user-id
とchannel-access-token
という必須パラメーターがあるので1.
で用意したものを入れる- LINEにプッシュ通知が行く
以下は、レポジトリにpushしたときにLINE通知を送るワークフローです。
on: [push] jobs: line_push_message_job: runs-on: ubuntu-latest name: LINE push message steps: - name: Push LINE message uses: ufoo68/line-push-message@v1.4 with: user-id: ${{ secrets.USER_ID }} message: 'test' channel-access-token: ${{ secrets.CHANNEL_ACCESS_TOKEN }}
uses: ufoo68/line-push-message@v1.4
でアクションを呼んで、with:
でパラメーターを入力します。message
が実際に送るメッセージになり、特に用意していない場合はYou are commited on repositoryというメッセージを送ります。
とりあえずこれをどうやって作ったのかを次から説明していきます。action.ymlを書く
このファイルだけはどうしても必要なので用意します。
name: 'LINE push message' description: 'LINE push message action using GitHub actions' branding: icon: message-square color: green inputs: user-id: description: 'User ID to push message' required: true message: description: "Text message to push message" default: "You are commited on repository" channel-access-token: description: "Channel access token" required: true runs: using: 'node12' main: 'dist/index.js'
inputs:
でwith
を使って与えてほしいパラメーターを定義します。branding:
はアイコンの形とか色を定義します。マーケットプレイスへの公開をするためには必須となるので用意しましょう。後はruns:
で実行環境とかファイルをしていします。JavaScriptを書こう
ここでようやくJavaScriptの出番になります。本当はTypeScriptでやりたかったですがコードの記述量もそんなになかったのでやめときました。書いたコードはこれだけです。
const core = require('@actions/core') const line = require('@line/bot-sdk') const { buildReplyText } = require('line-message-builder') const client = new line.Client({ channelAccessToken: core.getInput('channel-access-token') }) try { const userId = core.getInput('user-id') const message = core.getInput('message') client.pushMessage(userId, buildReplyText(message)) } catch (error) { core.setFailed(error.message) }なんとなくコードを見てわかると思いますが、
@actions/core
というパッケージを使って先程のinputs:
で定義したパラメーターを呼び出します。後はそれを使って自由きままにコードを書くだけです。distにパッケージをまとめる
今回、いくつかのパッケージを使ったので本当は
node_modules
もコミットする必要があるのですが、個人的にもあまりやりたくはないので公式のヘルプページを参考にzeit/nccを使って直接index.js
ファイルを実行できるようにコンパイルしました。
とりあえず最初はインストールからnpm i -g zeit/ncc
あとはコンパイルを実行します。
ncc build index.jsこれで
dist/
というディレクトリが作成されてコンパイルされたindex.js
が置かれているはずです。そして最初に書いたaction.yml
もmain: 'dist/index.js'
としてdist
直下のjs
ファイルを読むように指定していたはずです。GitHubへプッシュ!
後は
tag
をつけてレポジトリにプッシュします。コマンドの例はこんな感じです。git add action.yml dist/index.js node_modules/* git commit -m "Implemented action" git tag -a -m "My first action release" v1 git push --follow-tagsとりあえずこれで提出できる形のものは作成できました。
さいごに
思ったよりGitHub Actionsを作成するのは簡単でした。みなさんも是非これを機にアクションを作ってみてはどうでしょうか。
- 投稿日:2020-03-15T18:11:00+09:00
スクロールフェードインをjQuery無しで
はじめに
jQuery使わず、フェードイン実装。いつでも見れるようにメモ的な感じでQiitaに残してます。
main.jswindow.onscroll = function(){ function fadeIn(el, duration) { var node = document.getElementById(el); // display: noneでないときは何もしない if (getComputedStyle(node).display !== 'none') { return; }else { node.style.display = 'block'; } //opacityの操作 node.style.opacity = 0; var start = performance.now(); requestAnimationFrame(function tick(timestamp) { // イージング計算式(linear) var easing = (timestamp - start) / duration; // opacityが1までの値を代入 node.style.opacity = Math.min(easing, 1); // opacityが1より小さいとき if (easing < 1) { requestAnimationFrame(tick); } }); } //ページトップ以外の時は関数を呼び出す(ページトップ時以外の時はボタンは表示される) var scrollTop = window.pageYOffset ; if(scrollTop > 0) { fadeIn('js-button', 100); } }index.html<div id="fadein">フェードインさせる要素</div>解説
スクロールするごとに自作したfadeIn関数を呼び出す処理。
fadeIn関数が呼ばれているとき、requestAnimationFrame関数は取得したDOM要素のopacityの値を検知する役割にを果たす。
そのとき、opacityが1より小さい値が取得される間はrequestAnimationFrame関数は呼び出され続け、1以上の値になったら、呼び出しを停止させる。perfomance.now()
Date.now()にするとうまく動かなくなる。
多分perfomance.now()は常に一定の割合で増加して、Date.now()は増加しなくて計算が続かないから?おわりに
フェードアウトも書きます
- 投稿日:2020-03-15T17:54:07+09:00
魔法JS☆あれい 第3話「もうsortもreverseも怖くない」
登場人物
丹生(にゅう)あれい
魔法少女「魔法(マジカル)JS(女子小学生)☆あれい」として活動中。イテレー太
正体不明の魔法生物。sort()
イテレー太「あれい、状況説明をサボるけど、今戦闘の真っ最中だよ!」
あれい「ついにサボるとか言い出したなこの駄犬」
イ「そしていつものように、僕が魔法世界から召喚したデータを、あれいの配列魔法を使って、敵の弱点に合わせた値に変換してreturn
する事で、ダメージを与えて敵を倒してよ!」
あ「そこだけはセリフが説明的なんだよな」
イ「さて、今回僕が魔法世界から召喚したデータは……これだよ!」items = [ { name: '越前おろしそば', price: 650 }, { name: '海老天おろしそば', price: 1100 }, { name: 'あげおろしそば', price: 850 }, { name: 'とろろおろしそば', price: 800 }, ];あ「あー、今度は福井の蕎麦屋のメニューか」
イ「そして、今回の敵の弱点は、『要素のprice
の値が低い順に並んだ配列』だよ!」
あ「なんだよその弱点。あと第3話にして急に難度が上がったな」
イ「さあ、あれいの配列魔法で、メニューを安い順に並べ替えてreturn
して倒してよ!」
あ「面倒くせえなあ……」items.sort((a, b) => a.price - b.price);あ「これでいいのか」
イ「あれい、もうちょっと試行錯誤して盛り上がろうよ」解説
配列の要素をin placeでソートします。このソートは stable ではありません(訳注:同じ序列を持つ値の順番が保証されません)。 デフォルトではUnicodeコードポイントの昇順にソートされます。
(MDNより)
sort()
メソッドは、その名の通り、配列の要素をルールに基づいて並べ替えます。
引数無しでsort()
メソッドを実行した場合、配列の要素は値の小さい順(昇順)に並べ替えられますが、具体的には下記の比較関数の戻り値によって順番が判定されます。items.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);まず2つの要素(
a
、b
)を選び、値を比較します。a
が比較対象のb
より小さければ順位が上、大きければ順位が下、同じならば同点として判定します。この繰り返しにより、すべての要素が順番に並べ替えられます。
しかし、今回の例のような階層構造の配列の場合、この方法では大小を判定できないため、意図どおりに並べ替えることができません。そういう場合は、比較関数の中で『どの値を比較対象にするか』を明示する必要があります。今回の例では、要素であるオブジェクトのprice
の値を比較しています。items.sort((a, b) => a.price < b.price ? -1 : a.price > b.price ? 1 : 0);さらに、比較対象が単純な数値であれば、下記のように簡単に記述することができます。比較の結果、正の数・負の数・0のいずれかが返ればよいからです。
items.sort((a, b) => a.price - b.price);reverse()
イ「さて、次の敵が出てきたよ!」
あ「おう」
イ「そして、次の敵の弱点は、なんと『要素のprice
の値が高い順に並んだ配列』だよ!」
あ「ああ」
イ「さあ、あれいの配列魔法で、今度は高い順に並んだメニューをreturn
して倒してよ!」
あ「面倒くせえなあ……」return items.reverse();あ「これでいいのか」
イ「あれい、お願いだからもうちょっとやる気出してよ」解説
配列の要素を In-place アルゴリズム で反転させます。最初の要素は最後に、最後の要素は最初になります。
(MDNより)
reverse()
メソッドは、配列の要素の順番を反転させるだけの単純なメソッドです。これ以上特に説明することがありません。
さて、魔法JS☆あれいは、魔法(JavaScript)の力でこの世界を守ることが出来るのか!? 次回に続く!
- 投稿日:2020-03-15T16:51:54+09:00
aspidaでフロントエンドからAPIを見える化する
Nuxt.js(TypeScript)で開発していく際にaspidaというOSSを使用すると開発が捗ったので紹介します
主に以下のような不満を解消してくれるものになると思います
- APIも型定義したい
- フロントから叩いているAPI一覧が分からん
- フロントで利用するAPIを一元管理したい
- APIの返り値が分からんからconsole.log()で出力してみるってのを毎回やってる...
aspidaとは
ギリシャ語で「盾」って意味らしいです
型安全な HTTP クライアントラッパーを提供してくれるOSSです
日本語ドキュメントのページでかなり詳細に説明されていましたこちらに使用例まで用意されています
APIのInterfaceを静的に定義できるので、VSCodeで補完が効くようになったり、APIが仕様変更で返り値やエンドポイントが変更になっても血眼でgrepせずとも/api以下の定義を変えれば良くなります
![]()
![]()
個人的に好きなのは
APIのパス定義がNuxtのpages/ と同じような作りで見やすい点です例えば、
任意のユーザの任意の記事の取得と更新をするエンドポイントがそれぞれGET users/:user_id/article/:article_id PUT users/:user_id/article/:article_id user_id => autoincrementで生成される数字 article_id => ハッシュ値で定義されていたとします
その際クライアントサイドでは以下の様なディレクトリを切って/apis <- /apis以下に作られる(一応設定で変えられる |--$api.ts <- ビルドするとAPI定義が生成されるファイル |--users |--_userId@number <- 変数の型も定義できる |--articles |--_articleId@string |--index.ts <- このファイルでAPIのやりとりを定義する
index.tsでは以下のように書きます
index.tsexport type Article = { id?: string user_id?: string title: string body: string } export interface Methods { get: { // APIの返り値 resBody: Article } put: { // put時に投げる reqBody: { title: string bodyParser: string } // APIの返り値 resBody: Article } }そして、
aspida --build
を叩きます
めんどくさいのでscriptsにコマンド登録しておくと良いですpackage.json"scripts": { "api": "aspida --build", },コマンド登録して
npm run api
コマンドが無事成功すると
apis/$api.ts was built successfully. と出力されます
これで準備完了です
apis/$api.ts
を見にいくとなにやらいっぱい書かれていると思いますが
このファイルはlockファイルみたいなもので、触っちゃいけませんアプリケーションでの利用
アプリケーションではインポートして利用するのですが、
pluginでinjectしておくと便利です
pluginでinjectするやり方は、こちらの記事(aspidaの作者さんの記事です)がとても参考になるのでここでは省きます
設定が済めば、以下のようにappやthisのコンテキストから利用できます<script lang="ts"> import Vue from 'vue' ~(略)~ export default Vue.extend({ async asyncData ({ app, params, error }) { const article = await app.$api.users ._userId(parseInt(params.userId)) .articles ._articleId(params.articleId) .$get() .catch(() => { error({ statusCode: 404 }) }); return { article }; }, methods: { submit (article: Article) { this.$api.users ._userId(parseInt(this.$store.state.user.id)) .articles ._articleId(this.$route.params.articleId) .put({ data: article }) .then(({ data }) => { if (data.statusCode === 200) ... if (data.statusCode === 204) ... }) .catch((err) => { ... }) } } </script>既存のプロジェクトで
this.$axios
とかでAPIとやりとりしている箇所はもちろんそのまま機能するので、
徐々に移行できる点が良いですねあのエンドポイントだけはめちゃくちゃ複雑だから型定義しておきたい...
のような需要も満たしてくれそうです![]()
![]()
また、上記のサンプルコードで
$get()
やpost()
など$
が付いたり付かなかったりのHTTP Methodが出てきました
これはapis/$api.ts
の中を見るとわかるのですが、
Response.dataが欲しいかResponseが欲しいかって違いですResponse.metaから取れるメタ情報からハンドリングをする必要があれば
$
なしを利用して、
単純にデータが欲しければ$
ありを利用する感じですまた、
$
ありを利用すれば
axiosを使っていると、こんな感じで冗長になりがちなコードがconst user = app.$axios.$get(`/users/${params.userId}`).then(({ data }) => { return data })このようにスッキリします
const user = app.$api.users._userId(params.userId).$get();
TypeScriptを導入済みならすぐにでもaspidaは導入できるし、徐々に移行していこうってスタンスが取れるのがaspidaの良いところかなと思います
APIの型定義の際に手動でCLIを叩きますが現在、Swaggerをaspidaの型定義に変換するモジュールを開発中のようです
フロントから利用するHTTPクライアントが型安全になる世界が来るのも遅くはなさそうですね![]()
![]()
また、aspidaは最近公開されたOSSでContributeどしどし募集しているようです
![]()
![]()
- 投稿日:2020-03-15T16:29:58+09:00
worldmapjs を使ってみた
https://worldmapjs.org/ を使ってみました。
が、先に書いておくと、うまく利用できませんでしたやったこと
- https://worldmapjs.org/ から
worldmap.v1.js
とworldmap.v1.css
をダウンロード- 上記を自前の index.html にくみこむ。
- DOWNLOADS & INSTALLATION を参考に次のようにしてみた。
<html> <head> <title>WorldMap.js のサンプル</title> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css"> <!-- Latest compiled JavaScript --> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="https://cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js"></script> <script src="https://d3js.org/d3.v3.min.js"></script> <script src="https://d3js.org/topojson.v1.min.js"></script> <script src="https://d3js.org/d3.geo.projection.v0.min.js"></script> <script src="https://d3js.org/queue.v1.min.js"></script> </head> <body> <div id="mapcontainer" align="center"></div> <link rel="stylesheet" href="worldmap.v1.css"> <script src="worldmap.v1.js"></script> <script> var createmap = new Worldmap({ elementid: "#mapcontainer", }); </script> </body> </html>結果
ほかの地図ライブラリも試したいので、いったんここで調査打ち切り。
- 投稿日:2020-03-15T16:05:41+09:00
文字列のエンコード・デコードを瞬殺するencodeMaxを作りました
文字列のエンコード・デコードについては、「URLエンコード オンライン」みたいにググったらなんぼでもツールが出てくるのですが、だいたいどこのサイトも、「文字列を入力して変換をクリック」ってそのクリックする手間がイラッとしません?
あげくサーバサイドに通信しにいってHTTPの応答を待つ時間とかもうほんとに無駄!あと、「URLエンコード・デコードができるサイトです」とか、それしか出来ないんかーい!どうせどれも似たような処理なんだから一つのサイトで出来たっていいじゃん。
よく働くコンピュータの代わりにぼくがどんどん怠惰になった結果、そんなふうに今あるサイトが不便に感じるようになってきたので、文字列を入れたら、その瞬間にエンコード・デコードを一通り試して結果が出力されるサイトというのを作りました。
構成
たった1ページしかないのにNuxt.jsでgenerateした(だって楽だし)ものをNetlifyでホスティングしています。
ユーザーさんの入力をv-modelのバインディングで変数につっこんで、エンコード・デコードのロジックをcomputedで書いておけば、入力が更新されるたびに勝手に演算されて結果が更新される感じですね。
<textarea v-model="original_code"<label> Base64 Decode </label> <input type="text" :value="base64_decode" />computed: { base64_encode: function(){ return window.btoa(unescape(encodeURIComponent(this.original_code))) },あと演算結果欄は、余計な編集ができると使いづらいので、read-onlyにしつつ、こういうコードを付けておくことで、タップした瞬間全選択状態になって、ctrl+Aを押さなくていいようにしてあります。
methods: { on_click: function(event){ event.target.select(); }よかったら使ってみてください。
- 投稿日:2020-03-15T15:50:41+09:00
趣味WebエンジニアがVue.js+Flask(&GCP)でWebアプリ開発
きっかけ
ここ1年くらい、Twitter等のSNSでいろんな方が発信されている自作Webアプリを色々見ているせいか、筆者の中でWeb技術・Web開発に対する熱が高く、なにか便利なWebアプリを作りたいという想いがあります。ただ、バックエンドのAPサーバやDBサーバを構築するための知識は現時点でそこまでありません。
そこで、「コーディングはなるべくHTMLやJavaScript、CSS(とそれらのフレームワーク)のようなフロントエンド中心に行い、なおかつ積極的にSaaSを使うことでサーバレスでさくっとWebアプリを構築できないか」を模索し、実際にアプリを作成・公開したのでここに手法を書き記します。作成するWebアプリ
今回作るアプリですが、友人からアイデアをもらい「こんな内容のマンガが読みたい、というざっくりとしたきもちからオススメの作品を推薦するアプリ」を協力して作ることにしました。機能はひとまずシンプルで良いと思ったので、次のような要件で作成します。
スマホでのアクセス・閲覧を前提とするがPCでも見られる
つまりモバイルファーストのレスポンシブデザインである。Webアプリ内でユーザ認証はなし
URLにアクセスするだけで誰でも使える。その代わりサービス内でユーザデータは保持しない(DBサーバの不使用)。ユーザのキーワード入力に対し、必要なものを検索してサイトに表示する
ただし、検索はDBに対してではなくHTMLやJSにハードコーディングされたもの、またはデータ検索用に公開されているWebAPIに対して行い、その結果データを取得し表示することとする(DBサーバの不使用)。クライアント(ブラウザ)側で動的なページ構築を行う
JavaScriptを使い、取得データを表示するためのhtmlをユーザ側で動的に作成し表示する(APサーバの不使用)。使用するWeb要素技術
要件を踏まえ、今回使用した言語【フレームワーク】を以下に記します。
HTML
CSS【Bootstrap】
公式ページはこちら(Bootstrap - 世界で最も人気のあるフロントエンドのコンポーネントライブラリ)。Bootstrapを使うと、整ったデザイン・レスポンシブデザインを楽に作ることができる。JavaScript【Vue.js】
公式ページはこちら(Vue.js)。Vue.jsを使うと、JavaScriptがクライアント(ブラウザ)側でhtmlを操作する動作をかなり直感的に記述できる(データバインディングという仕組みによりJavaScript上のデータを更新するとhtmlも同時に更新される)。Python【Flask】
公式ページはこちら(Flaskへ ようこそ — Flask v0.5.1 documentation)。Flaskを使うと、Pythonの文法を使いシンプルな記述でWebAPIを記述することができるので、もし必要なデータを公開しているWebAPIがなかったらFlaskを使って自分で作成できる。今回はキーワードをもとに作品名一覧を返却するAPIを作成した。Webアプリ構築に使用するSaaS
今回の要件でWebアプリを構築するために使用可能なSaaSの例を以下に記します。
さくらインターネット(今回はレンタルサーバライトプランで可)
公式ページはこちら(さくらのレンタルサーバ | 高速・安定WordPressなら!無料2週間お試し)。HTML、CSS、JavaScript等を配置して公開することができるWebホスティングサービス。その他に、独自ドメイン取得やSSL証明書の設定オプションサービスもある。各種設定は、基本Webインタフェースでポチポチ操作できるので初心者としてはとっつきやすい。無料お試し期間のあとは月額定額制。
その他、類似のホスティングSaaSとしては、Firebase Hosting(公式ページ)や、AWS(公式ページ)等があります。これらは、従量課金制で無料枠もあったりするので初期費用を抑えられる。Google App Engine
サービスの概要はこちら(App Engine | Google Cloud)。
上述のFlaskで記述したWebAPIを配置して公開するために利用できる。アクセスや負荷に応じて自動でスケールしてくれ、料金は従量課金制。システム構成図
これまでの話を踏まえ、フレームワーク・SaaSを活用したWebアプリの構成図(例)は以下のようになります。
FlaskでWebAPIを公開している部分ですが、Google App Engineにアップロードしているファイル構造は次のようなかんじです。
data_list.csv
が作品名とその特徴量(タグ)が入ったリストデータ、main.py
がFlaskのpythonコード、それ以外はApp Engineのお作法で用意する設定ファイルです。設定ファイル作成には、次の記事を参考にさせていただきました。
- Google App Engine + Flask(Python3)で「Hello Flask!!」してみた part2 ~デプロイする~ - Qiita
- PythonのFlaskアプリをGoogle App Engineにデプロイしてみた - Qiita
main.py
の中身は次のようになっており、ユーザがWebAPIに対して作品検索用のデータ(json)を投げてきたときに、結果データ(json)を返却するように記述しています。main.pyfrom flask import Flask, jsonify, request from flask_cors import CORS import os import csv # このスクリプトが在るディレクトリの絶対パスが入る変数 CWD = os.path.dirname(__file__) # 作品データのファイル名 DATA_LIST_FILE = "data_list.csv" LEARN_DATA = None # 推薦に使うデータオブジェクト # CSVのパスを受け取って読み込み def loadStractualData(target_file): global LEARN_DATA # グローバル変数に代入するために必要な宣言 csv_list = [] # 単純にCSVをリストに変換しただけのリスト with open(target_file, 'r', encoding='utf-8', newline="") as f: csv_list = [row for row in csv.reader(f)] # 2次元リスト output = [] for row in csv_list: # CSVを一行ずつ処理 ################################################# # CSVの行を構造化データにしてoutputに格納していく処理(割愛) ################################################# print("file loading finished!") LEARN_DATA = output ############################### ## ここからサーバプロセスの設定 ## ############################### loadStractualData(os.path.join(CWD, LEARNED_FILE)) # CSVファイルを読み込む app = Flask(__name__) app.config['JSON_AS_ASCII'] = False # 出力JSONの日本語を文字化けさせない設定 CORS(app) # Access-Control-Allow-Originの設定 # HTTPのPOSTで/post_tagsにユーザ選択タグが送られて来たときの処理 @app.route('/post_tags', methods=['POST']) def post_tags(): json = request.get_json() # POSTされたJSONを取得 input_tags = json["tags"] # ユーザが入力したタグのリスト ########################################################### # ユーザの送ってきたタグで作品リストをフィルタしout_listに格納する処理(割愛) ########################################################### return jsonify({"title_num": len(out_list), "titles": out_list}) #jsonを返す # python実行時のエントリーポイント if __name__ == "__main__": print(" * Flask starting server...") app.run() # サーバプロセス起動Vue.jsからFlaskで作成したWebAPIにリクエストを投げる部分は、Axiosを使用しています。以下の記事を参考にしました。
完成したアプリ
完成したアプリはこんな感じです(emore | "きもち"で探すマンガ検索)。トップページ、検索ページ、検索結果ページからなるシンプルなアプリで、要件にあったようにスマホ前提のレスポンシブデザインになっています(Bootstrapのおかげ)。また、検索ページでは、特にVue.jsによるブラウザ側での動的描画が活かせている(ユーザがタグを選択すると逐一WebAPIに送信し結果の表示を更新するような、動きがあるデザインになっている)と思うので、是非見ていただければ幸いです。
所感
Webアプリ開発における、フロントエンド技術やSaaSがかなり発達していて、個人開発でも手軽にある程度のアプリは作れるようになっていると感じます。今回要件に含めなかったユーザ認証にしても、例えばSaaSのFirebase Authenticationを使えば、マルチプラットフォームログイン(Twitterでログイン、Facebookでログイン等)を実装できますし、ユーザデータを保存するDBにしてもFirebase,FireStore等のWebAPI経由で利用できるものがあります。
Vue.jsもFlaskも、高機能なWebサービスを作ろうと思ったらいくらでも技術的な発展性を含んでいるので、今後も適度に学んだ知識を作品としてアウトプットしつつ、引き続き技術にもチャッチアップして学習していこうと思います(とりあえず、Vue.jsのフレームワークであるNuxt.jsや、Flaskより高機能なDjangoについて勉強中)。
- 投稿日:2020-03-15T15:03:40+09:00
Azure Functions[HTTP trigger]を試してみた
はじめに
Azure for Studentsという学生に優しいサービスがあった!!
だからAzureをいろいろ試してみることにした!
第1弾として、Azure Functions
※自身が今後利用するためのメモ書きAzure Functions
関数アプリからサービスを作成
Azureのポータルメニューから、「関数アプリ」を選択する。
左上の「追加」ボタンをクリックする。
プロジェクトの詳細を設定する。
・サブスクリプション:Azure for Students
・リソースグループ:ない場合は新規作成
・関数アプリ名:任意の関数アプリ名を指定
・ランタイムスタック:使用する言語を選択(今回はNode.jsを選択)
・バージョン:ランタイムスタックのバージョンを選択
・地域:地域を選択する
「確認および作成」ボタンをクリックする。
作成後、真ん中上の「新しい関数」ボタンをクリックする。
今回は、「HTTP trigger」を選択する。
function.jsonでnameプロパティを$returnに変更する。function.json{ "type": "http", "direction": "out", "name": "$return" }index.jsを次のようにする。
index.jsmodule.exports = async function (context, req) { context.log('JavaScript HTTP trigger function processed a request.'); return { body: "Hello, world!" }; };「実行」ボタンをクリックする。
そして、「</>関数のURLの取得」をクリックして、そのURLにアクセスして確かめる。おわりに
メモ書きのため、伝わりにくい部分があったら申し訳ありません。
アドバイスをコメントなどでもらえると助かります。
- 投稿日:2020-03-15T13:22:10+09:00
svgMap.js を使ってみた
世界地図を描きたい。というわけで、地図の描画ライブラリを試している。
今回試してみたのは svgMap.js。
- svgMap.js - https://www.cssscript.com/interactive-svg-world-map/ (公式サイトはここでいいのだろうか...)
ここから、 svgMap-master.zip をダウンロードし、利用してみた。
簡単メモ
- 準備する HTML タグは div 1つ
- その div の後ろで script タグで svgMap.js を読み込む必要がある (div の前に読み込むとダメ)
- データは js 内でいろいろ準備する
- サンプルでは、マウスオーバーで国の情報を tooltip 的に表示してくれる。
- 国より粒度の小さいデータ (日本でいうと「県」とか) が含まれているかどうか、は調べきれなかった。
- 国情報内の国旗データは https://cdn.jsdelivr.net/gh/hjnilsson/country-flags@latest/svg/us.svg などから取得するようになっている。
単体で完結しないのはちょっと不便かも (インターネットがない環境とか)。
- 国旗データの供給元は、オプションで変更できるようだった。
new svgMap({ targetElementID: 'svgMapExample', data: svgMapDataGPD, // here !!! flagURL: 'https://cdn.jsdelivr.net/gh/hjnilsson/country-flags@latest/svg/{0}.svg', });試した結果
アフガニスタンだけ詳細データが表示されるのは、後述する
my-worldmap.js
にて、データを入力しているため。試したコード
index.html
<html> <head> <title>svgMap のサンプル</title> <link href="svgMap-master/dist/svgMap.css" rel="stylesheet"> <!-- svgMap-master.zip に入っているファイル --> <script src="svgMap-master/dist/svgMap.js"></script> <!-- 同上 --> <link href="my-worldmap.css" rel="stylesheet"> </head> <body> <div id="svgMapExample"></div> <script src="my-worldmap.js"></script> </body> </html>my-worldmap.css
(これは適当。なくてよい)
#svgMapExample:before { content: "my worldmap"; border-bottom: 1px solid #999; display: block; text-align: center; font-family: consolas; padding: 7px; background-color: #f9f9f9; border-radius: 7px 7px 0 0 ; } #svgMapExample { border-radius: 7px; border: 1px solid #999; min-height: 100px; }my-worldmap.js
https://www.cssscript.com/interactive-svg-world-map/ 内のサンプルコードをただ写経した感じ。
// GDP data var svgMapDataGPD = { data: { gdp: { name: 'GDP per capita', format: '{0} USD', thousandSeparator: ',', thresholdMax: 50000, thresholdMin: 1000 }, change: { name: 'Change to year before', format: '{0} %' }, gdpAdjusted: { name: 'Purchasing Power Parity', format: '{0} USD', thousandSeparator: ',', thresholdMax: 50000, thresholdMin: 1000 }, changeAdjusted: { name: 'Change to year before', format: '{0} %' } }, applyData: 'gdpAdjusted', values: { AF: {gdp: 587, change: 4.73, gdpAdjusted: 1958, changeAdjusted: 0.02}, // ... } } new svgMap({ targetElementID: 'svgMapExample', data: svgMapDataGPD, });さらなるオプションは?
https://www.cssscript.com/interactive-svg-world-map/ にある通り。
svgMap のコンストラクタとして渡してあげればよい様子。
- 投稿日:2020-03-15T13:12:59+09:00
Tabulatorでツールチップをカスタマイズしてみた
前提条件
http://tabulator.info/ ⇒ V4.5
内容
Tabulatorでツールチップを実装するのは簡単だが
カスタマイズしたい機会があったので備忘録として。
(Tabulatorの標準ツールチップ機能はカスタマイズができない。。。)参考
https://so-zou.jp/web-app/tech/programming/javascript/event/handler/mouse/
http://tabulator.info/実装
・標準のツールチップ
ツールチップの実装はここを参考に
http://tabulator.info/docs/4.5/format#tooltipsvar table = new Tabulator("#example-table", { height: 205, data: table_data, layout: "fitColumns", columns: [ { title: "Name", field: "name", width: 64, // これだけでツールチップが実装される tooltip: true }, { title: "Age", field: "age", align: "left", formatter: "progress" }, { title: "Favourite Color", field: "col" }, { title: "Date Of Birth", field: "dob", sorter: "date", align: "center" }, ], });
・ツールチップのカスタマイズ
※jQueryを導入しています。ツールチップ用のタグを追加
<!-- Bodyの中に追記 --> <div class="tabulator-tooltip"> <table class="tooltip-style"> <tr> <td id="tabulator-tooltip-value"></td> </tr> </table> </div> <!-- -->
headタグにCSSを追記
※ もちろん別ファイルに追記でOK<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Tabulator-test</title> <style> /* ここを新規に追加 */ .tabulator-tooltip { opacity: 0; background-color: #fff; min-width: 150px; width: auto; height: auto; position: absolute; max-width: 330px; border-radius: 5px; padding: 10px; color: black; border: 1px solid black; } </style> </head>
先ほどのTabulatorの生成コードを変更var table = new Tabulator("#example-table", { height: 205, data: table_data, layout: "fitColumns", tooltips: function (cell) { var cellElement = $(cell.getElement()); cellElement.on('mouseover', function () { $('.tabulator-tooltip #tabulator-tooltip-value').text(cell.getValue()); }); cellElement.on('mousemove', function (e) { $('.tabulator-tooltip').css('opacity', 1); $('.tabulator-tooltip').css('left', e.pageX + 30 + 'px'); $('.tabulator-tooltip').css('top', e.pageY - 10 + 'px'); }); cellElement.on('mouseleave', function (e) { $('.tabulator-tooltip').css('opacity', 0); }); return ''; }, columns: [ { title: "Name", field: "name", width: 64, }, { title: "Age", field: "age", align: "left", formatter: "progress" }, { title: "Favourite Color", field: "col" }, { title: "Date Of Birth", field: "dob", sorter: "date", align: "center" }, ], });
- 投稿日:2020-03-15T12:38:35+09:00
要素を順番にアニメーションさせる方法【CSS,JS】
順番にアニメーションさせる。
この記事では、javascript(ES6)を使い要素に順番にアニメーションさせるソースの紹介しています。
完成形
最初に完成形を見せておきます。
See the Pen for animation by wd30gsrc (@wd30gsrc) on CodePen.
HTML
まずはhtmlから
index.html<ul class="list"> <li class="item">BOX_01</li> <li class="item">BOX_02</li> <li class="item">BOX_03</li> <li class="item">BOX_04</li> <li class="item">BOX_05</li> </ul>CSS
.itemは最初、opacity:0で非表示にさせて、jsで.activeクラスをあとから付ける。
style.css.list { display: flex; justify-content: center; } .item { width: 100px; height: 100px; background: orange; margin: 3px; text-align: center; line-height: 100px; opacity: 0; transition: all 1s; } .active { opacity: 1; }js
今回は即時関数を使っています。
実際に使用する場合はスクロールイベント等で要素が見えたときに動くようにすることが多いかと思いますが、そこは適宜調整が必要です。app.js(()=> { const items = document.querySelectorAll('.item'); items.forEach((item,index)=> { const order = index + 1; const delay = order * 300; setTimeout(()=> { item.classList.add('active'); }, delay); }); })();以上です。
- 投稿日:2020-03-15T12:32:49+09:00
Reactの環境構築
【React】npxでのReactの環境構築までのステップについて
create-react-appコマンドを使用
※注意事項:事前にnode.jsをインストールしてください。
まだインストールされていない方はこちらから!
→node.js1.ターミナルでコマンドを実行
①"ls"コマンドを入力してディレクトリがすでに存在しているかの確認をします。
例)react-directory(お好きな名前)をディレクトリ名とする。②"npx create-react-app react-directory"と入力をします。
2.実行結果を確認
実行完了後、"ls"コマンドを入力して"react-directory"が実行できていることを確認します。
3.packge.jsonを確認
①ダウンロードされたディレクトリにpackage.jsonがあることを確認します。
4."npm start"でReactアプリを起動
エディタで"npm start"を入力してURLを出力する。
5.環境構築成功
エディタで出力されたURLをクリックして画像のように表示されれば成功です。
お疲れ様でした!
- 投稿日:2020-03-15T12:32:49+09:00
【React】npxでのReactの環境構築までのステップについて
create-react-appコマンドを使用
※注意事項:事前にnode.jsをインストールしてください。
まだインストールされていない方はこちらから!
→node.js1.ターミナルでコマンドを実行
①"ls"コマンドを入力してディレクトリがすでに存在しているかの確認をします。
例)react-directory(お好きな名前)をディレクトリ名とする。②"npx create-react-app react-directory"と入力をします。
komorioBookpuro:~ komorishuhei$ npx create-react-app react-directory npx: 99個のパッケージを5.733秒でインストールしました。 Creating a new React app in /Users/komorishuhei/reac-directory. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template...2.実行結果を確認
実行完了後、"ls"コマンドを入力して"react-directory"が実行できていることを確認します。
Success! Created react-directory at /Users/komorishuhei/react-directory Inside that directory, you can run several commands: npm start Starts the development server. npm run build Bundles the app into static files for production. npm test Starts the test runner. npm run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd react-directory npm start Happy hacking!3.packge.jsonを確認
①ダウンロードされたディレクトリにpackage.jsonがあることを確認します。
②package.jsonの中身をエディターで確認します。
{ "name": "react-directory", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", "react": "^16.13.0", "react-dom": "^16.13.0", "react-scripts": "3.4.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }4."npm start"でReactアプリを起動
エディタで"npm start"を入力してURLを出力する。
Compiled successfully! You can now view project in the browser. Local: http://localhost:3000 On Your Network: http://192.168.10.104:3000 Note that the development build is not optimized. To create a production build, use npm run build.5.環境構築成功
エディタで出力されたURLをクリックして画像のように表示されれば成功です。
お疲れ様でした!
- 投稿日:2020-03-15T09:43:26+09:00
初心者によるプログラミング学習ログ 260~262日目
100日チャレンジの260~262日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
260~262日目は、おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) March 11, 2020
260日目
・自作ポートフォリオ改造
・cssをsassで書く練習#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCodeおはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) March 12, 2020
261日目
・自作ポートフォリオ改造
・cssをsassで書く練習#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCodeおはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) March 13, 2020
262日目
・自作ポートフォリオ改造
・cssをsassに書く練習#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode
- 投稿日:2020-03-15T09:18:44+09:00
魔法JS☆あれい 第2話「shiftはとってもunshiftって」
登場人物
丹生(にゅう)あれい
魔法少女「魔法(マジカル)JS(女子小学生)☆あれい」として活動中。イテレー太
正体不明の魔法生物。shift()
イテレー太「あれい、また説明をキレイさっぱり省略するけど、今戦闘の真っ最中だよ!」
あれい「また楽しやがったなこの駄犬」
イ「そして、話の流れ的に勘付いてるとは思うけど、今回の敵の弱点は『配列の最初の要素』だよ!」
あ「相変わらずマイペースだな。皮剥ぐぞ」
イ「さて、今回僕が魔法世界から召喚したデータは……これだよ!」items = ['へしこ', 'ソースカツ丼', '焼き鯖寿司'];あ「今度は福井の名物かよ。福井駅前の居酒屋のメニューか」
イ「さあ、あれいの配列魔法で、敵の弱点である『配列の最初の要素』をreturn
して倒してよ!」
あ「面倒くせえなあ……」return items.shift();イ「おおっ! やったよ、あれい! へしこが炸裂したよ! 福井名産へしこで敵が苦しんでるよ!」
あ「だからお前黙ってろ。口縫うぞ」解説
shift()
メソッドも、配列を操作する基本的なメソッドです。
MDNによると……
shift()
メソッドは、配列から最初の要素を取り除き、その要素を返します。このメソッドは配列の長さを変えます。前回のpop()では『積み重ねられたカード』に例えましたが、今回は『タピオカ店の行列に並んでいる人達』をイメージすると良いかもしれません。つまり、
shift()
メソッドは、行列の一番前の人を呼び出すようなイメージです。これも、配列のもう一つの側面である『キュー』データ構造に対する、直感的でわかりやすい動きです。unshift()
イ「では、続いての敵にいってみましょう」
あ「ラジオパーソナリティーかよ」
イ「さて、僕が魔法世界から召喚したデータは、今こんな状態になっているよ!」items = ['ソースカツ丼', '焼き鯖寿司'];あ「そうだな」
イ「そして、新たに魔法世界から召喚したデータは、これだ!」newItems = ['越前おろしそば', 'ボルガライス'];イ「このデータを……」
あ「元の配列の最初に追加した上で、その最初の要素を敵にぶつけりゃいいのか?」
イ「……なんでそんな無駄なことするの?」
あ「お前が前回そうしろっつったんだろ。大根おろしを目にぶっかけるぞ」
イ「まあまあ。今回は、元の配列の最初に追加して、その配列の要素数をreturn
すればいいよ!」
あ「面倒くせえなあ……」return items.unshift(newItems);イ「やったね、あれい! 4で敵が苦しんでるよ!」
あ「いまだに仕組みが良くわからんな」解説
unshift()
メソッドも、基本的な操作の一つです。
MDNによると……
unshift()
メソッドは、配列の最初に 1 つ以上の要素を追加し、新しい配列の長さを返します。つまり、タピオカ店の行列の一番前に、何人かが割り込みするようなイメージです。とても理不尽ですね。
また、このメソッドの戻り値は、push()
メソッドと同じく、新しい配列の要素の数になります。
さて、魔法JS☆あれいは、魔法(JavaScript)の力でこの世界を守ることが出来るのか!? 次回に続く!
- 投稿日:2020-03-15T07:35:00+09:00
PDF.js にコントリビュートした話
開発に参加している LaTeX Workshop では PDF.js をヘビーに使用していて、ちょくちょくやりとりもあるので、恩返しとコードの理解のために PDF.js にコントリビュートしてみました。対象の issue には CJK ユーザー以外は対応しそうにない縦書きの表示のバグの修正を選びました。
無事PRがマージされて、Firefox へもマージされました。 Firefox 75から有効になるはずです。Firefox Nightly ではすでに有効になっています。オンラインデモで試すことも可能です。
https://www.nta.go.jp/taxes/tetsuzuki/shinsei/shinkoku/zoyo/yoshiki2019/pdf/006.pdf
修正前
修正後
修正前
修正後
- 投稿日:2020-03-15T02:58:10+09:00
Node.jsの非同期処理をPromiseから理解しようとしてみた
はじめに
今回は、Node.jsの非同期処理について、自分の備忘録も兼ねて記事を作成しました。
私はJavaScriptの言語仕様なんかをあまり知らない状態でNode.jsを触ったせいで、非同期処理に関する部分ではまり、多くの時間を無駄にしてしまいました。
かなり初歩的な内容かもしれませんが、勉強した内容をまとめます。
同期処理とは?
上から順番にプログラムが実行されていくことです。
「上から順番に」という言葉が適切かどうか分かりませんが、「一つ一つの処理が、一個前の処理の終了をまって処理されていく」っていう説明よりは個人的に分かりやすい気がします。
コードにすると、下記の通りです。
console.log(1); console.log(2); console.log(3); console.log(4); console.log(5);実行結果は、下記のようになります。
1 2 3 4 5非同期処理とは?
同期処理ではないものが非同期処理なので、上から順番にプログラムを実行されないことと言えます。
JavaScriptでは、ユーザーの入力やAPIを叩いてデータを持ってくる時、それからファイルを操作する時などに、非同期処理になります。
これはJavaScriptがシングルスレッドなので、そういった「制約」を非同期で処理することによっってフォローしています。
コードにすると、下記の通りです。
ここでは例として、遅延処理を用いています。
const three = () => { setTimeout(() => { console.log(3); }, 1000); } console.log(1); console.log(2); three(); console.log(4); console.log(5);実行結果は、下記のようになります。
1 2 4 5 3setTimeoutで処理の実行が1秒後に設定されたthree関数が呼び出されています。
これが同期処理であれば、実行結果としては順番に1から5までの数字が出力されますが、Node.jsではこういった処理は非同期になるので、three関数の終了を待たずに次の処理へ進み、最後にthree関数の結果が返されています。
これが非同期処理です。
Promiseとは?
なぜPromiseが必要か?
上記で述べた非同期処理ですが、何でもかんでも非同期処理にすると不都合なこともあります。
例えば、ファイルの内容を読み込んで、ファイルの中身を出力するような場合、
(hogeと書かれたhoge.txtというファイルが存在するとします)
const fs = require('fs'); const result = fs.readFile('hoge.txt'); console.log(result);上記のコードだと、hoge.txtを読み込む処理は非同期に処理されます。
しかし、hoge.txtを読み込み終わるより先にresultが出力されてしまうので、上記のようなコードでは想定する結果を得ることができません。
そんな時に使えるのが、Promiseです!
Promiseは、その名の通り、その処理を行うことを約束することができます。
もっと簡単にいうと、Promiseを使えば本来非同期に行われる処理を、同期処理のように書くことができます。
上記のようなケースを解決するためには、Promiseが必須なのです。
Promiseの使い方
Promiseオブジェクトを返す関数を定義します。基本的にはそれだけです。
試しに先ほどのhoge.txtを読み込むコードのうち、実際にファイルを読み込む処理の部分をPromiseを使って同期的に処理できるように書き換えてみます。
const fs = require('fs'); const readAsync = return new Promise((resolve, reject) => { resolve(fs.readFile('hoge.txt')); })これでPromiseオブジェクトを返す関数を作成できました。
あとはconsole.logで出力するタイミングを、この関数の処理が実行された後になるように全体のコードを書き換えます。
このような処理をするときは、非同期関数に.then節を記述します。
const fs = require('fs'); const readAsync = return new Promise((resolve, reject) => { resolve(fs.readFile('hoge.txt')); }) readAsync().then((result) => { console.log(result); })これでファイルが読み込まれるのを待ってからconsole.logでファイルの中身が出力されるようになります!
Promiseオブジェクト作成の際に引数に渡しているresolveとrejectですが、
resolveには非同期処理が成功した時に値が入り、失敗した時にはrejectに値が入ります。
例えば、存在しないfuga.txtというファイルを読み込もうとした場合、非同期関数の結果は失敗になるので、上記の例のようにresolveではなく、rejectに値が入ります。
const fs = require('fs'); const readAsync = return new Promise((resolve, reject) => { reject('指定されたファイルは見つかりません'); }) readAsync().then((result) => { console.log(result); }).catch((err) => { console.log(err); })Promiseオブジェクトを返す非同期関数の.catch節でエラーをハンドリングしています。
上記のコードの場合、下記のような出力になります。
指定されたファイルは見つかりません
Promise.allとPromise.raceについて
Promise.allとPromise.raceはどちらも複数の非同期関数を実行するためのものです。
それぞれの違いは、Promise.allは指定した全ての関数がresolveでもrejectでも全ての関数が実行されます。
Promise.raceは、指定した全ての関数の中で一つでもresolveまたはrejectになったら、その関数の結果のみを返して処理を終了します。
また、Promise.allでは実行の順番を保証するので、例えば下記のようなコードでも出力は順番通りになります。
const a = return new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 10 * 1000); // 10秒待つ }) const b = return new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 5 * 1000); // 5秒待つ }) const c = return new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 1 * 1000); // 1秒待つ }) Promise.all([a, b, c]).then((values) => { console.log(values[0]); console.log(values[1]); console.log(values[2]); })setTimeoutの値を見ると、出力される値の順番的には3, 2, 1となりそうですが、実際の出力は、
1 2 3となります。
Promise.raceのソースは下記のようになります。
返す値は関数一つ分なので、Promise.allのように配列で受け取る必要はありません。
const a = return new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 10 * 1000); // 10秒待つ }) const b = return new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 5 * 1000); // 5秒待つ }) const c = return new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 1 * 1000); // 1秒待つ }) Promise.race([a, b, c]).then((value) => { console.log(value); })上記のコードだと、一番早く処理が終わるのは非同期関数bなので、出力は、
2
となります。
おわりに
ジャバスクリプトムズカシイ。
- 投稿日:2020-03-15T01:21:15+09:00
React Material-UI Snackbarのカスタマイズに挑戦した
前提
React Material-UIのSnackbarコンポーネントアニメーションを自分好みにカスタマイズしてみまました。
React × Material-UIの記事が少ないので、内容コアですが、記事にしてみました。Snackbar(スナックバー)とは
スナックバーは、実行プロセスをユーザーに通知するための表現です。
こちらのスライドアニメーションのカスタマイズに挑戦します。
Snackbarコンポーネントの構造
Material-UIの構造の多くは、atom要素を構成しています。
今回のSnackbar
も同様に、複数のatom要素でできています。ReactのChrome拡張ツールなどでコンポーネント構造を見ると以下のようになっています。
<Snacbar> <ClickAwayListener> <Transition> <Slide> // デフォルトは<Grow> <SnackbarContent>もちろん、利用時に、テキスト構造など
Snackbar
のコンポーネント内に渡してあげると構造は変化します。APIについて
Snackbar
コンポーネントに用意されたコンポーネントは主に以下があります。(公式サイト)
- action
- スナックバーの最後にレンダリングされる要素
- anchorOrigin
- アンカー要素
- autoHideDuration
- スナックバーの表示時間
- message
- スナックバーに表示されるメッセージ
- onClose
- 閉じるリクエストが起こった場合のコールバック
- open
- 表示
- TransitionComponent
- トランジションに使用されるコンポーネント
- TransitonProps
- トランジション要素のプロパティ
- transitonDuration
- トランジション期間の設定
Snackbarの実装
特別な要素指定を行わずに
Snackbar
の組み込みをしてみました。import React from 'react'; import Button from '@material-ui/core/Button'; import Snackbar from '@material-ui/core/Snackbar'; import Slide from '@material-ui/core/Slide'; import ClearIcon from '@material-ui/icons/Clear'; // FIXME:せっかくTransitionPropsプロパティあるのでそちらでdirection設定したい... function Transition(props) { return <Slide {...props} direction="left" />; } export default function App() { const [open, setOpen] = React.useState(false); const handleOpen = () => { setOpen(true); }; const handleClose = () => { setOpen(false) }; return ( <div className="App"> <Button variant="contained" onClick={handleOpen}>Register</Button> <Snackbar open={open} message="Complete!" onClose={handleClose} TransitionComponent={Transition} action={<ClearIcon onClick={handleClose}/>} /> </div> ); }
TransitionProps
がよくわからない
Slide
コンポーネントの組み込み方が気持ち悪いですが、なせか、TransitionProps
がうまく効きませんでした...
(原因わかったり、対応方法見つけたらまたそのタイミングで...わかる方いましたらコメントいただけると嬉しいです。。)コードについて
Button
コンポーネントの押されたタイミングでスナックバーが表示されます。
Snackbar
のaction
に閉じるアイコンを設置してonClick
イベントでスナックバーを閉じれるように設定しています。
Snackbar
のonClose
オプションを指定して、いると、スナックバー外の要素アクションでも閉じるようです。アニメーションカスタマイズ
スナックバーで
TransitionComponent
を指定しなければGrow
コンポーネントが標準で用意されているようです。
上のコードではSlide
コンポーネントを使って横から入ってくるようにしています。標準のTransitionが動くと、結構モーションが速いので、少しゆっくりめにカスタマイズしてみます。
transitonDuration
プロパティこちらのプロパティで
enter
とexit
指定で入ってくるモーションと、出ていくモーションの指定が行えます。transitionDuration={{ enter: 800, exit: 800, }}モーション指定を行った後のソースは以下です。
import React from 'react'; import Button from '@material-ui/core/Button'; import Snackbar from '@material-ui/core/Snackbar'; import Slide from '@material-ui/core/Slide'; import ClearIcon from '@material-ui/icons/Clear'; // FIXME:せっかくTransitionPropsプロパティあるのでそちらでdirection設定したい... function Transition(props) { return <Slide {...props} direction="left" />; } export default function App() { const [open, setOpen] = React.useState(false); const handleOpen = () => { setOpen(true); }; const handleClose = () => { setOpen(false) }; return ( <div className="App"> <Button variant="contained" onClick={handleOpen}>Register</Button> <Snackbar open={open} message="Complete!" onClose={handleClose} TransitionComponent={Transition} transitionDuration={{ enter: 800, exit: 800, }} action={<ClearIcon onClick={handleClose}/>} /> </div> ); }アニメーションの変更ができました!
さいごに
Material-UIを使うと手軽に様々なモーション、インタラクションの活用ができてすごく便利ですが、そこに縛られてしまうこともあります。
この辺りの細かいオプションが用意されていればすごく利便性上がりますが、ここに苦しめられることも多くあります。同じようなコンポーネントでも用途が違うコンポーネントとして用意されているものも多くあります。
皆さんはReact開発ではどんなコンポーネント開発行ってますか?是非聞かせて欲しい。。2020/3/15 CodeSandbox リンク追加しました!
- 投稿日:2020-03-15T00:37:16+09:00
関数の呼び出しと参照
はじめに?
JavaScriptを使用するにあたり、関数はオブジェクトと考えたほうが良いです。
※オブジェクトについての説明は、下記を参照下さい。
オブジェクト引数として関数に渡したり、変数に代入したりすることができます。
注意する点として、
関数の呼び出しと参照を区別しましょう。
記述例
実行される例 function sports() { return "baseball"; } console.log(sports()); // baseball関数名に () を付けると関数の呼び出しになります。
上記では、sports という、関数をbaseballと定義し、
実行されました。実行されない例 function sports() { return "baseball"; } console.log(sports); // function sports()関数名に () を付けずに関数名だけを書くと、
参照しているだけに、
なってしまいます参照では、変数や定数への代入が可能になります。
const food = sports; // 関数を food に代入 console.log(food()); // baseball上記では、 sports を food という名前で呼び出しています。
まとめ
参照では、
定義した関数名を他の場所で、
名前を変えて、実行する事ができます。ただし、
代入が可能になってしまうと、
コードが長くなってしまった時、
定義されている物が、分からなくなってしまいます。
記述ミスが出てきます。使用する際は、注意しましょう。