- 投稿日:2019-11-15T21:27:08+09:00
AtCoderに登録したら解くべき精選過去問10を「JSFuck」で解いてみた
まえがき
久しぶりにWikipediaの難解プログラミング言語の記事を見たら言語例にJSFuckという言語が追加されていました1.
やればできそうなのでこの言語を使って精選10問2を解いてみることにしました.What's JSFuck?
JSFuckとは
JavaScriptのような文法の言語。構成文字は
[
,]
,(
、)
、!
と+
のみ
(Wikipedia -難解プログラミング言語)とのこと.
例えば
alert(1)というプログラムは
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()と書けます.
JavaScriptでなるべく少ない記号で書き表すことを追求した結果生まれた言語で,実はJavaScriptのプログラムとして実行できます.(Wikipediaの説明は語弊があってJavaScriptのような文法,というよりはJavaScriptの文法を制限したサブセットと言ったほうがわかりやすいのではないでしょうか)
JSのサブセットがどうやったらこんなBrainfuckじみたコードを生むのかというと,JSの変態的な暗黙の型変換と文字列を評価して実行する機能が鍵を握っています.すなわち
hoge.fuga
はhoge["fuga"]
で呼び出せる![]
=false
+true
=1
false+[]
="false"
eval("hoge")
でhoge
の内容を実行できるなどを組み合わせ,
1
=+!![]
"a"
="false"[1]
=(![]+[])[+!![]]
などのようにしていろいろなリテラルを得ることができます.
さらに,eval
は[]["filter"]["constructor"]( hoge )()
で呼び出せるため,"filter"
,"constructor"
とhoge
を上の方法で文字列として表せば,任意プログラム実行が可能になるというわけです.つまり,
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]( hoge )()
ですね.3その他詳しくはWikipediaを御覧ください.https://ja.wikipedia.org/wiki/JSFuck
また,本記事ではWikipediaと jsfuck.com の記述を参考にしましたが,「
[
,]
,(
,)
,!
,+
」の6文字以外にも「[
,]
,(
,)
,=
,+
」のように任意プログラムの実行が可能な記号セットを取ることが可能であることが知られています.4
任意プログラムのコーディングが可能な記号セットは6文字が最小であるようです.5
参考:https://github.com/aemkei/jsfuck ,本格JavaScript記号プログラミング(1) 6種類の記号だけでJavaScriptを書こうさて,JavaScriptとして実行できる,ということはAtCoderで提出ができるということ.というわけで精選10問を解いていきましょう.
一般解
結論から言うと以下のようなコードを書いて一字ずつ文字列に変換してevalすれば(ほぼ)どんなコードでも実行できます.
echoのコードfunction main(input){ console.log(input); } var input = ''; process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', c=>{input += c}); process.stdin.on('end', ()=>main(input))入力は
input
に配列として格納されています.
Node.jsで標準入出力を扱うテンプレとしてはvar input = require('fs').readFileSync('/dev/stdin', 'utf8');が知られています6が,これを使わないのは
requre
がセキュリティ権限の問題でeval
で実行できないため7IOを監視して順次配列に格納するという方法を取っています8.ソースコードを一文字ずつJSFuck化するのは大変なので機械にまかせてしまいましょう.
こちらのサイト http://www.jsfuck.com/ の入力フォームにソースコードを入力すれば(適宜;
をつけてワンライナー化する必要があります.)JSFuckで文字列として解釈できるソースコードに変換してくれます.
「Eval Source」にチェックを入れれば上記のeval
で囲んだものになり,元のコードと同じ動作をするようになります.この記事の内容としてはこれでほぼ終わりです.出入力だけ注意してJavaScriptで解答し,変換すればOKです.
正直,JSFuckに変換したコードは全部同じにしか見えない(難読化の手法として上げられるだけのことはある)ので変換前のコードだけ提示します.0. PracticeA - Welcome to AtCoder
解答はこんな感じです.
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var t=i.split('\n'),a=+t.shift(),r=t.shift().split(' '),u=t.shift(),n=+r.shift(),l=+r.shift(); console.log(a+n+l+' '+u) })やたらと変な変数名をつけているのはコード量削減のためです.
JSFuckではオブジェクトの文字列表記などから文字を取得するなどいろいろと遠回りをしているのであまり長いコードになるとJavaScriptとして解釈するだけで結構実行時間を取られてしまいます.
"f", "t", "a"あたりの文字は13, 14, 15Byteで表せますが,"]"などは6591Byte必要です(このあたりは文字コードを指定して取得しているらしい).
"v"あたりも1768Byteとそこそこ長いためvar
よりもconst
のほうが短くなるなど,単なるコードゴルフとは違った戦略が求められそうです.JSFuckに変換して提出すると,コード長は198851 Byte,実行時間は343 msとなりました.(変換前に改行やインデントは削除しています.)
https://atcoder.jp/contests/abs/submissions/8383312
コード解釈にかなり時間がかかっていそうですね.1. ABC086A - Product
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ const t=i.split(' '),a=+t.shift(),r=+t.shift(); console.log((a*r%2==0)?'Even':'Odd') })↓
https://atcoder.jp/contests/abs/submissions/8394727
コード長 205291 Byte,実行時間 362 ms2. ABC081A - Placing Marbles
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var a=i.split(''),r=+a.shift(); r+=+a.shift(); r+=+a.shift(); console.log(r) })↓
https://atcoder.jp/contests/abs/submissions/8394847
コード長 171766 Byte,実行時間 313 ms3. ABC081B - Shift only
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var f=i.split('\n'),a=f[1].split(' '),r=0; while(1) if(a.every(t=>+t%2==0)){a=a.map(t=>+t/2);r+=1} else{break} console.log(r) })↓
https://atcoder.jp/contests/abs/submissions/8400487
コード長 223468 Byte,実行時間 375 ms4. ABC087B - Coins
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var t=i.split('\n'),a=+t.shift(),b=+t.shift(),c=+t.shift(), x=+t.shift(),r=0; for(var s=0;s<=a;s++) for(var t=0;t<=b;t++) for(var u=0;u<=c;u++) if(500*s+100*t+50*u==x)r+=1 console.log(r) })↓
https://atcoder.jp/contests/abs/submissions/8400694
コード長 287213 Byte,実行時間 473 ms変数が増えてきてコード長を切り詰めるのがつらくなってきました.
5. ABC083B - Some Sums
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var t=i.split(' '),n=+t.shift(),a=+t.shift(),b=+t.shift(),r=0,s,u; for(s=0;s<=n;s++){ u=(s+[]).split('').reduce((x,y)=>x+ +y,0); if(a<=u&&u<=b)r+=s } console.log(r) })https://atcoder.jp/contests/abs/submissions/8400819
コード長 248794 Byte,実行時間 436 msu=(s+[]).split('').reduce((x,y)=>x+ +y,0);のところは
数値→文字列に変換→配列に変換→各文字を数値に変換しながら総和を取る
という操作になっています.
大変読みにくいですね,実務でこんなコードを書いた日には殴られそうです.6. ABC088B - Card Game for Two
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var t=i.split('\n'),n=+t[0], a=t[1].split(' ').map(x=>+x), sa=0,sb=0; a.sort((x,y)=>x<y?1:-1).forEach((x,k)=>(k%2==0)?sa+=x:sb+=x); console.log(sa-sb) })↓
https://atcoder.jp/contests/abs/submissions/8404271
コード長 250855 Byte,実行時間 417 ms7. ABC085B - Kagami Mochi
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var t=i.split('\n'),n=+t.shift(); console.log((new Set(t)).size-1) })↓
https://atcoder.jp/contests/abs/submissions/8404356
コード長 161183 Byte,実行時間 289 ms8. ABC085C - Otoshidama
var i='',f=process.stdin; f.resume(); f.setEncoding('utf8'); f.on('data',c=>{i+=c}); f.on('end',()=>{ var a=i.split(' '),n=+a.shift(),y=a.shift(),r='-1 -1 -1'; for(var k=0;k<=n;k++){for(var l=0;k+l<=n;l++)if(k*10000+l*5000+(n-k-l)*1000==y){r=`${k} ${l} ${n-k-l}`;break}} console.log(r) })↓
https://atcoder.jp/contests/abs/submissions/8383748
コード長 326046 Byte,実行時間 810 ms9. ABC049C - 白昼夢 / Daydream
初め,以下のコードを提出しようと試みました.
i='',f=process.stdin, l=s=>s.length c=(s,t)=>l(s)>=l(t)&&s.substring(0,l(t))==t, r=(s,b)=>s.substring(b); f.resume();f.setEncoding('utf8');f.on('data',t=>{i+=t}); f.on('end',()=>{ i=i.substring(0,l(i)-1).split('').reverse().join(''); while(i){ if(c(i,'remaerd')){i=r(i,7)} else if(c(i,'maerd')){i=r(i,5)} else if(c(i,'resare')){i=r(i,6)} else if(c(i,'esare')){i=r(i,5)} else{console.log('NO');return}} console.log('YES') })これをJSFuckに変換すると365905 Byte になりますが,413 Request Too Largeで弾かれてしまいました.コード長制限は512KiBですし,以前 375197 Byte で提出できた例もあったので問題ないはずですが何故でしょう……
ちょっとここからコード長を詰めるのは辛かったので正規表現を使った解法で通すことにしました9.
var i='',f=process.stdin,r=(g,t)=>i.replace(g,t); f.resume();f.setEncoding('utf8');f.on('data',t=>{i+=t}); f.on('end',()=>{ i=i.trim(); i=r(/eraser/g,'('); i=r(/erase/g,'('); i=r(/dreamer/g,'('); i=r(/dream/g,'('); i=r(/\(/g,''); console.log(i?'NO':'YES') })↓
https://atcoder.jp/contests/abs/submissions/8404356
コード長 315631 Byte,実行時間 520 ms10. ABC086C - Traveling
var i='',f=process.stdin; f.resume();f.setEncoding('utf8');f.on('data',t=>{i+=t}); f.on('end',()=>{ i=i.trim().split('\n'); var n=+i.shift(); console.log(i.every((x)=>{ var r=x.split(' '),a=+r.shift(),b=+r.shift(),c=+r.shift(); return a>=b+c&&a%2==(b+c)%2 })?'Yes':'No'); })↓
https://atcoder.jp/contests/abs/submissions/8428369
コード長 290242 Byte,実行時間 566 msあとがき
コード長が膨れ上がり提出制限512KiBを超えるという懸念がありましたが,9.でちょっと引っかかった以外はなんとかすべて無事に通せました.
Brainfuckと違いどの記号がどんな処理をするか,といった対応がないため人力で書くのはなかなか骨が折れますがツールを使えば意外と簡単です.先人の研究に感謝です.元のJSの時点で意味のない1文字変数が飛び交ったり,parseの代わりに暗黙の型変換を使ったり,グローバルを汚染したりとなかなかなクソコードでのお目汚し,大変失礼致しました.10
変更履歴によれば2019/05/23のことである. ↩
@drken さんによる記事 AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~ に提示された初心者向け問題を集めた10問.言語を触って遊んでみるのに丁度いい難易度の問題が揃っています.現在はAtCoder Beginners Selectionとして問題セットがまとめられています. ↩
"filter"は
(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]
,"constructor"は([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]
となる. ↩Qiita上で確認できる研究ではこちらのほうが優勢のようですが,なぜWikipediaには前者が載る事になったのかは不明です. ↩
|>
演算子が導入されれば夢の5文字「[
,]
,+
,|
,>
」が可能になるそう. ↩早速「(ほぼ)どんなコードでも実行できる」の反例が出てしまいました. ↩
参考:https://qiita.com/alucky0707/items/46f8a3e0c523d3bd4034 ↩
参考:https://qiita.com/kotatsugame/items/2619398606b30aaf097b#%E7%AC%AC9%E5%95%8Fabc049c---%E7%99%BD%E6%98%BC%E5%A4%A2--daydream ↩
ところでJavaScriptで競プロを解くこと自体慣れていないのでより良い処理方法がありましたらご教授いただけると幸いです. ↩
- 投稿日:2019-11-15T21:17:17+09:00
YYTypeScript#9「TS初心者がハマりやすいところ教えて 」「執筆中の『マンガでわかるTypeScript』�に意見欲しい! 」「英語で書かれている有益なサイトある? 」「EventListenerとIndexedDBの型周りについて」「eBookを共同編集する上でのベストプラクティス」
これは2019年11月15日に開催したTypeScriptイベントYYTypeScript#9のイベントレポートです。
YYTypeScriptは一言で「TypeScripterの部室」です。発表者の話を聞く「一方向的な勉強会」とは真逆で、TypeScriptについて、雑に・ゆるく・ワイワイ話しながらTypeScripter同士の交流を深める「双方向的な座談会」の形式になります。集まった人たちで「今日話たいこと」「聞きたいこと」をいくつか挙げていき、それをテーマに雑談していきます。
今回の配信動画
#YYTypeScript #9 の生配信の収録がアップされました!https://t.co/tUWw4snNcr
— suin❄️TypeScriptが好き (@suin) November 15, 2019過去回の配信動画 → YouTubeプレイリスト「YYTypeScript」
前回 → YYTypeScript#8「セミコロンは省略すべき?」「Vueと一緒にTSも導入すべき?」「Vueテンプレートの型、みんなどうやってる?」「TSの罠にハマった体験談と対策」「Composition APIについて聞きたい」「default exportは避けるべき?」「TypeScriptのバージョンアップのタイミングはいつ?」「TS+Vue+Electronって大丈夫?」「TSX+Vueなら、Reactで良くない?」 - Qiita雑談
TSまだ初めてちょっとしかたってない人に、どんなところで詰まったか聞きたい
- JSを雰囲気で書いてたので、TSから入ったらJSがわからないので先にJSをやったほうがよいかなとおもっている。
- 具体的てきにTSとJSってどうちがうの?
- JSのVueがわからない、のか?フレームワーク側のはなし?
- JSをすっとばして、VueのTSからはいったのでもっとカオスだった。そのときは型が浸透していなかったのでほとんどAnyだった。VueがわからないならVueのJSから入ったほうがいいのではというきがする。
- JSフレームワークを考えないのであればTSから入ってもいいとおもうが、フレームワークと混ぜるとごっちゃになって難しい。
- それぞれ個別で考える。
- PHPもおなじ。フレームワークなのかPHPの機能なのかわからない。
- Genericsでつまった
- いままではPHPとかJSとか型がない言語でやっていたので、複雑な型の考えかたない。
- Genericsは総称型とか汎用型と呼ばれる。使う側で型が決められる。
- 差し替えられる型。
// ジェネリクスの例 class List<T> { add(item: T): void; get(inde: number): T; } const stringList: List<string> = /**/ stringList.add("test") // OK stringList.add(123) // コンパイルエラー
- TS勉強し始めてまもない
- まずコンパイルしてみようからはいる。JSができたー。からのなにをしたらいいのかわからないひとがおおいのでは?
- 小規模なアプリをTSでつくってみようみたいなレポジトリない?
- 第二ステップで手を動かして作れるような。
- フロントエンドかバックエンドによって違うと思うが、
- TSを教えているが、フロントエンドはいきなりだと、Webpackだのなんだので爆発しがちなので、バックエンドから教えてる。
- microsoft/TypeScript-Handbook: The TypeScript Handbook is a comprehensive guide to the TypeScript language
- todomvc
- React • TodoMVC
- JSに限らず、いろんな言語でToDoリストを作ったサンプルがある。
- JavaとかPHPとは型システムがぜんぜん違うところ
- JavaとかPHPは型が Nominal Type
- TSは Structural Type
- 型合成
- 公称型(Nominal)のJavaに対して、構造型(Structural)のTypeScript
- TSはチェックツールの役割
- JSに戻した時の実行時のことも考えて書かないといけない。
- JSを書いてることを忘れるとよくない。
- JSになるので実際の実装と型定義とが違っている場合がある
- 型のテストもある
- 外からくる値はちゃんとチェックしないといけない。(TSは特に)
「マンガでわかるTypeScript」のプロットを見て!
湊川さんが進められたら進めたいと思っているWeb連載。
・・・
TSが生まれた背景
TSとJSの比較
TSの環境構築
2話からはinterfaceとclassに行きたい
TS使いたい人ってどういう人なんですかね?
- やっぱりフロントエンドじゃないですかね
- Reactで型つけてやりたいとか
JavaScriptの存在を隠して、TSを教えるアプローチ
- JSから教えると、JSでいいじゃんってなりそうだなぁと
「とりあえず書いてみよう」のツールとして、playgroundの案内が有っても良いかもと思いました
書けるようにするのではなく、TSの良さを伝えることにフォーカスしたほうがいいかも
- 使ってみる意義がわからない、という人も多いので
最近英語の本を読む機会が増えてきたが、英語で書かれているプログラミング言語の有益なサイトがあったら教えてほしい
- https://basarat.gitbooks.io/typescript/
- getify/You-Dont-Know-JS: A book series on JavaScript. @YDKJS on twitter.
- Safariっていうオライリーがやっているサブスクモデルの洋書読み放題サービス
- 月39ドル
- http://books.acm.org/
- ACMに会員になると年間99ドルくらいで洋書が読み放題
- 特典としてSafariも読み放題
- 年99ドルでオライリーの英語技術書を読み放題にする方法 - Qiita
- martinfowler.com
- TSに限らない設計とか幅広いところですが...
- 最近はmartin fowlerのページよく見ます
Event listenerの型をTSでどうやって書いたらいいか
自分は大体ラップして、ゴリゴリに型定義したイベント登録/emitメソッド作成すること多いです
class Hoge { // `event` という引数には、 “aaa” “bbb” という文字列しか受け付けません、という型定義になります on(event: 'aaa', () => void) on(event: 'bbb', (arg: string) => void) on(event: 'aaa' | 'bbb', listener: (...args: any) => void) { this.eventEmitter.on(event, listener) } emit(event: 'aaa') emit(event: 'bbb', arg: string) emit(event: 'aaa' | 'bbb', ...args: any): void { this.eventEmitter.emit(event) } } const hoge = new Hoge() hoge.on('aaa', () => console.log('aaa')) // OK hoge.on('ccc', () => console.log('ccc')) // コンパイルエラーIndexedDBを扱うクラスを書いたのでコードレビューして
- 結構ライブラリもある。
- なかみてみると意外と重量系で、ライトウェイトなのが欲しくて自分でいろいろ書いてみた。
export interface DatabaseConfig { database: string, version: number, storeKeys: { storeName: string, key: IDBObjectStoreParameters }[], } export class IDBWrapper { config: DatabaseConfig; db: IDBDatabase | null = null; isEnabled: boolean = false; constructor(config: DatabaseConfig) { this.config = config; } async open() { if (!window.indexedDB) { this.db = null; this.isEnabled = false; } else { // レビュー: 即時関数しなくていいと思う // this.db = await new Promise<IDBDatabase>(/*...*/) this.db = await (() => { return new Promise<IDBDatabase>((resolve, reject) => { const req: IDBOpenDBRequest = indexedDB.open(this.config.database, this.config.version); req.onsuccess = (ev: Event) => resolve((<IDBRequest>ev.currentTarget).result); req.onerror = (ev: Event) => reject(); req.onupgradeneeded = (ev: IDBVersionChangeEvent) => { const d = (<IDBRequest>ev.currentTarget).result; this.config.storeKeys.forEach(val => { d.createObjectStore(val.storeName, val.key); }); }; }).then(value => { this.isEnabled = true; return value; }).catch(reason => { this.isEnabled = false; return null; }); })(); } } // なんでも入れられるように any にしたけど、もっといい型指定ってない? // => keyの型をジェネリック型にすれば? find(store: string, key: any): Promise<any> { // ... } insert(store: string, value: any): Promise<boolean> { // ... } delete(store: string, key: any): Promise<boolean> { // ... } }
- localStorageとの違いって?
- workerで使えない
- 容量が小さめ(ブラウザによるが5MB程度らしい)
- indexedDBはタブを複数開いたときにも、トランザクションがサポートされていて安全。
eBookを共同編集する上でのベストプラクティス
TypeScriptについてのeBook? wiki?をみんなで書きたいが、どういうふうに進めるのがいいのか?
共同編集のベストプラクティスが知りたい。
- 最終的に紙にするものかどうかで変わってくる
- SEOしっかりしたい
- SEOを狙うならScrapboxは微妙になってくる
- リアルタイム共同編集に特化しているので、1行ずつ
<span>
で囲まれている- Promiseの本みたいな感じでサンプルコードを実行できるとよさそうだなとか思いました
- gitのリポジトリでやるのもあり
- hexoみたいなのでデプロイされるようにしておく
- GitBook
- vueって何使ってるのか?
- hexo
- esaのコミュニティ版
...
wiki的なツール
- esa.io (階層構造)
- ドキュメントとしてかっちり書ける
- Markdown
- private前提。publicにはできない?ぽい
- コミュニティ向け無償版がある
- help/OSS・Web技術関連コミュニティ向け無償提供(ツールスポンサー)
- 月額500円/人
- Scrapbox (not階層構造)
- 気軽に書ける
- 投稿ボタンがない
- Markdownじゃない
- public、private両方可
- 企業利用じゃなければ無料
- Scrapbox活用例
- 参考 esaとScrapboxとGoogleドキュメントの比較表
Wiki以外
- GitBook(https://www.gitbook.com)
- Google ドキュメント
- コメントなどを残しやすい(吹き出し)
- Dropbox Paper(https://paper.dropbox.com)
- Markdownで書ける
- 共同編集したとき日本語が化けるときがある
冊子にする予定があるかどうか?
- 自動組版システム Re:VIEW
- マンガでわかるDocker3はRe:VIEWで書きました
- Re:VIEW独自の記法
- CSS組版 Vivliostyle
- マンガでわかるRubyはVivliostyleで書きました
- Markdownで書いてRubyで変換
GraphQLのクライアントライブラリ、apolloが定番なのですかね?
- TSでどうなの?
- わからないのでやってみまーす!
開始前の雑談
Scrapbox
- e-book、Scrapboxで書く「Scrapbook」という考え方もありますよ。
- Scrapboxなら共同編集は簡単です。
- ちらっとしか使ったことないですが、良さそうだなと思ってました
- マンガでわかるScrapbox
自動運転の事故
- 自動運転、最近事故があったようですね。
- 自動運転は責任の範囲が難しそう。
- 自動運転についてのマンガを書きました。
- マンガでわかる未来のIT/第2話「自動運転時代の保険システムとは? - sight 次世代を生み出すテクノロジーの視点
- メーカー側が訴えられることが増えてくるだろうから。
- 事故が誰のせいなのかもAIに判断させたらどうだろうか?
- あなたは30%で、あの人は70%でとかw
- AIに裁かれる時代ですね
- AIの中立性、平等性にも問題があるそうです
- 肌の色とかでバイアスがかかっているらしい
- 以前、Googleの画像認識で、黒人がゴリラと判定された事例が。
- YouTubeの収益化剥奪の判定もAIがやってるらしい
- ゆっくり解説動画がひっかかるという事例も
参加してよかったこと(参加者の感想)
- 自分にない知見が得られる。知らないパッケージの情報を得られる
- 毎回自分が知らない分野の情報も得られて、ためになってます。
- 99$読み放題はとても有益な情報でした
- 参考になるリポジトリやツールを教えてもらえたこと、みなさん親切で優しいこと
- 突然のコード貼り付けでも,ささっとレビューしていただいてとてもありがたかったですm(_ _)m
YYTypeScriptは毎週やってます
YYTypeScriptについてワイワイ話したい方は、YYTypeScriptのイベント情報をチェックしてみて下さい。
以上、YYTypeScriptのレポートでした。次回もワイワイやっていきたいと思います! では、また来週!
- 投稿日:2019-11-15T20:41:31+09:00
年末まで毎日webサイトを作り続ける大学生 〜28日目 スライダーを自作する〜
はじめに
こんにちは!最近週末は温泉に通っている@70days_jsです!
今日はスライダーを自作しました。hover時に画像をうっすら白くするところ以外は、何も参考にせず0から作ってみました。
扱う技術レベルは低いですが、同じように悩んでる初心者の方を勇気付けられれば幸いです。
今日は28日目。(2019/11/15)
よろしくお願いします。サイトURL
やったこと
スライダーを作ってみました。
矢印ボタンか、サムネイルをクリックすれば画像が変わります。
まずはgifでどうぞ↓
やったことはだいたいこんな感じです。↓
1. 矢印ボタンのクリックで画像を変更する
2. サムネイルを表示する
3. サムネイルのクリックで画像を変更する
4. 画像hover時に色を変更するでは1から順に書いていきます。
1. 矢印ボタンのクリックで画像を変更する
矢印はfont awesomeを使いました。
htmlはこんな感じです。<div class="slider"> <div id="left"><i class="fas fa-angle-left"></i></div> <img id="image" src="day24_image/photo1.jpg" alt="slider"> <div id="right"><i class="fas fa-angle-right"></i></div> </div>id="left"が左の矢印で、rightが右矢印です。
id="image"の部分で画像を表示しています。次にjavascriptですが、スライダーに使う画像を配列に入れています。↓
let image = [ 'day24_image/photo1.jpg', 'day24_image/photo2.jpg', 'day24_image/photo3.jpg', 'day24_image/photo4.jpg', 'day24_image/photo5.jpg' ];あとは要素を取得して、イベントリスナーをつけて、配列から表示する画像を選んでいます。↓
let img = document.getElementById('image'); let left = document.getElementById('left'); let right = document.getElementById('right'); let count = 0; //現在表示している画像の配列番号 let imageLength = image.length - 1; //4 left.addEventListener('click', imageLeftClick); right.addEventListener('click', imageRightClick); function imageLeftClick() { if (count === 0) { count += imageLength; } else { count--; } let src = img.getAttribute('src'); img.setAttribute('src', image[count]); } function imageRightClick() { if (count === imageLength) { count = 0; } else { count++; } let src = img.getAttribute('src'); img.setAttribute('src', image[count]); }let count = 0;が画像表示の肝になっており、右矢印を押せば一つ下の数字の配列番号、左クリックなら一つ上の数字の配列番号を表せるようになっています。
もしcountが0なら配列の長さ分を足し、逆にcountの値が配列分の長さに達していたら0を代入することで画像がループするようにしています。2. サムネイルを表示する
次にサムネイルの表示ですが、これはidがthumbnailの要素を取得して、javascriptでimg要素を作って、配列の画像をすべて入れて、idがthumnailの要素の下に加えるということをやっています。↓
let thumbnail = document.getElementById('thumbnail'); for (var i = 0; i < image.length; i++) { let imgElement = document.createElement('img'); let src = image[i]; imgElement.setAttribute('src', src); imgElement.setAttribute('class', "thumbnail"); thumbnail.appendChild(imgElement); }途中でclassをセットしていますが、これは画像をリサイズするためのクラスです。
imgElement.setAttribute('class', "thumbnail");
classはあらかじめcssの方で書いておきます。↓
.thumbnail { display: inline-block; width: auto; height: 90px; max-width: 100%; max-height: 100%; border: .1px solid white; }以前やったやつですね。max-width: 100%;とmax-height: 100%;をつけると画像がボックス内にうまく収まってくれます。今回はheightの値は指定して、widthが可変になるようにしています。
3. サムネイルのクリックで画像を変更する
次はサムネイルをクリックすると、画像も変わる部分です。
let thumbnailList = document.getElementsByClassName('thumbnail'); for (var i = 0; i < thumbnailList.length; i++) { let thumbnail = thumbnailList[i]; thumbnail.addEventListener('click', function displayImage(e) { let src = e.target.getAttribute('src'); img.setAttribute('src', src); }); }さっきサムネイル画像を生成するときにつけておいたclass(画像をリサイズするためのクラス、thumbnailクラス)を利用します。
全てのサムネイルにthumbnailクラスが付いているのでgetElementsByClassNameで一括取得して、そのあとにfor分でそれぞれのサムネイルにイベントリスナーをつけていきます。
引数にeを渡すことでクリックされた要素を特定し、その要素のsrcを取得し、そのsrcをimg変数(画像を表示させる部分)にセットすることでうまく画像を表示してくれます。4. 画像hover時に色を変更する
最後に、いまいちスライダー感がなかったのでcssで画像にhoverしたらうっすら白くなる演出を加えてみました。とはいえここのやり方は思いつかなかったのでサイトの記事を参考にさせていただきました。
仕組みは簡単で、例えばこのhtml構造なら↓<div class="slider"> <div id="left"><i class="fas fa-angle-left"></i></div> <img id="image" src="day24_image/photo1.jpg" alt="slider"> <div id="right"><i class="fas fa-angle-right"></i></div> </div>画像を表示させるid="image"の親要素であるclass="slider"の背景を白にします。
.slider { background-color: white; }その後、画像を表示させるid="image"がhover(擬似クラス)されたらopacityというプロパティで透明度を調整できるので、画像の透明度を少し下げます。↓
#image:hover { opacity: .7; }これだけで画像は透明になり背景の白が浮かんでくるので、hoverされたら画像がうっすら白くなったように見えます。
感想
やっとwebサイトっぽいもの(スライダー)を作れました。嬉しいです。ただスライダーを調べているうちにカルーセルというものに出くわしました。違いが全く分かりません・・・。という感じで一歩進んで二歩下がりながら頑張っております。この調子で年末にはきちんとしたwebサイトを作れるようになっていたら嬉しいな・・・。
最後までお読みいただきありがとうございます。明日も投稿しますのでよろしくお願い致します。
参考
- マウスが載ったときだけ画像を半透明にするCSSの書き方 - スタイルシートTipsふぁくとりー (https://www.nishishi.com/css/translucent-image-opacity.html)
hover時の挙動を参考にさせていただきました。ありがとうございます!
- 投稿日:2019-11-15T18:58:14+09:00
【JavaScript】htmlのスクロール�を任意の位置にアニメーションする
はじめに
利用規約を作ったときにに埋め込みスクロールが長すぎたので
自動スクロールさせて任意の場所に移動しそこから読めるようにすれば便利だと思います。
その機能をJavaScriptで実装させてみようと思います。実装
まず移動させたい文章のとこでクラスを定義します。
h2.scroll hogehoge p hugahuga次にjsでアニメーションしていきます。
var scroll_possition = $(".scroll").offset().top; var term_posittion = $(".term").offset().top; var posittion = scroll_possition - term_posittion $(".term").animate({ scrollTop: posittion });簡単なアニメーションで実装できました。
まずtermはファイル先頭のクラスになります。
そして移動させたい位置から全体の位置を引くとジャストで希望の位置が取得できます。
これをアニメーションさせてやれば読み込みと同時にアニメーションされます。
- 投稿日:2019-11-15T18:28:54+09:00
reactNative
reactNative
React Native とは
ReactはFacebookが開発したJavaScriptのフレームワークであり、React Nativeはそれをモバイルで使えるようにしたものである。
React Nativeを使用することでネイティブに描画されるiOSとAndroidのアプリを作ることができる。1つのコードで、両方のプラットフォームで動くものが作れる。さらに、JavaやObjective-Cのライブラリを自分で書いてReact Native自体を拡張することもできる。WebでReactを使っているなら、ターゲットがモバイルに代わるだけなので簡単に使い始めることができる。
React同様、React NativeもJavaScriptと、JSX (なんとなくHtml風に書けるJavaScriptの拡張構文) と呼ばれるXMLライクなマークアップを使って記述される。内部では、ネイティブのレンダリングAPIが呼び出されるので、WebViewではなく、ちゃんとしたモバイルUIコンポーネントが描画される。もちろん、プラットフォームの機能であるカメラや位置情報といったものも利用できる。
※参考
https://qiita.com/kyrieleison/items/78b3295ff3f37969ab50React Nativeのここがすごいよ
他の多くのクロスプラットフォームを謳う開発方法(CordovaやIonic)と違い、WebViewではなくネイティブで描画されるのが大きな利点となる。
これらの開発方法ではネイティブでの表現(UI/UX)を再現するために多大な努力をしているが、完全な再現とまでは行っていないし、最先端からは一歩遅れた表現(UI/UX)になってしまう。また、パフォーマンスもあまり良くない。一方でReact NativeではネイティブなUIが使われるし、また、メインのUIスレッドとは別に動くからパフォーマンスも高く維持される。
React NativeのアップデートサイクルはReactと同じで、propsやstateが変化したときにビューが再描画される。開発の方法はほとんどReactと変わらないので、Reactを使ったことのある人にとって学習コストが低いのも大きい。同様に、開発メンバーも集めやすい。また、以下で述べるように普通のモバイル開発に比べて開発がしやすいのも大きなポイントである。
リストと欠点
React Nativeにおける最も大きな危険性はおそらく、React Nativeがまだ発展段階にあるということだ。2015年の3月にiOS用としてリリースされ、同年9月にAndroid対応が発表されたばかりである。
まだネット上にも情報が多いとはいえないし、ドキュメントも整備されていない部分がある。ただし、足りないAPIは自分で作ることができるので、完全な「詰み」になることはないだろう。react Nativeが成熟していないため、二の足を踏む企業もあるニュース
https://project.nikkeibp.co.jp/idg/atcl/idg/14/481709/082000458/React Nativeの学習コスト
React NativeはFacebookが中心となって開発しているネイティブアプリのためのフレームワークです。特徴としては、Web技術、知識、HTML、CSS、JavaScript、Reactなどを使って、iOS、Androidのアプリを開発できます。
またReactの思想で「Learn once, Write anywhere」というのがあり、「一度学んだら、どこでも書ける」的な意味を持っており、WebでReactを触っていれば、React Nativeを使って、ネイティブアプリの開発もできます。
つまり、WEB開発経験者であればそのまで学習コスト高くないのも特徴になりますReact Nativeの導入実績
https://bagelee.com/programming/react-native/react-native-apps-example/
より簡単なのはReact NativeそれともiOS / Android?
JavaScriptは、Java、Objective-C、Swiftとは対照的に、学習しやすくてデバッグも簡単です。しかし、この手軽さにはデメリットもあります。 JavaScriptは堅牢な言語ではなく、書いたコードに多くのエラーが隠れていても気づきにくいことがあります。
一方、Objective-C / Swift / Javaは、多くの潜在的なエラーを、コード実行前に取り除くことができる「コンパイル時の型チェック」という仕組みを持っているという意味で、堅牢な言語です。
Swiftは明らかに非常にモダンな言語ですが、Objective-CとJavaもモダン化し続けていて、モダンな言語に求められる機能性やパフォーマンスに関しても決して不十分ではありません。しかし、Google検索すればすぐ分かるように、JavaScriptにはさまざまな欠陥があります。
よって、学習そのものはReact Nativeの方が簡単です。
しかし、JavaScriptの欠陥が伴うおそれがあります。また、クロスプラットフォームなフレームワークを利用する場合は必ず、いわゆる「一度書いたら、どこでもデバッグする必要が出てくる」という問題に対処しなければなりません。
どちらを学習するべきか
もしあなたがアプリ開発を予定していて、いつかアプリ開発者の仕事を探したい場合には、さまざまな理由からiOSまたはAndroidのネイティブ言語を強くおすすめします。
またReact Nativeを学ぶという選択肢もあります。これは興味深いテクノロジーですが、いくつか警告があります。
すべての開発者は、強い型付けの、コンパイラ型の、オブジェクト指向言語を習得する必要があります。そしてJava /Objective-C / Swiftはいずれもこれに最適な選択です。あなたが望むかどうかにかかわらず、いずれとにかくJavaScriptは学ぶことになります。
React Nativeは、AppleとGoogleのどちらかにも正式にサポートされていません。これはつまり、OSやAndroidから新しいアナウンスメントが公表されても、React Nativeで完全には動作しない可能性があることを意味します。たとえば、iOS 10iMessageアプリの新機能が追加されたとアナウンスされた時点では、React Nativeによってそのようなアプリのコードを書く方法はなかったと思います。こういうケースでは、iOSのネイティブアプリの開発方法を習得している必要があるのです。また現在でも、React Nativeを使ってApple Watchアプリを開発できるかどうかさえ定かではありませんが、ネイティブ開発を学んでおけば何の問題もないでしょう。
第三に、プロジェクトの寿命を念頭に置く必要があります。FacebookのParseサービス停止の例を思い出してください。現在のところ、React Nativeは健全に運営されていて、いくつかの主要企業もこれを支持しています。しかし、React Nativeをサポートしていない一方で、 今後もiOSとAndroidを長くサポートすることが予測されるAppleやGoogleとは異なり、Facebookやその他多くの企業にとっては、永久にReact Nativeのサービスを提供し続ける理由はないかもしれません。
どんな言語
・divやspanなどのDOMを使えない
・DOMを使えない替わりに「View」や「Text」などコンポーンネントとして使う
・テキストを扱う場合は「Text」。入れ物として扱う場合は「View」
・CSSは使えないので、替わりにに「CSS in JS式」でスタイリングするサンプル()
```
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';export default class App extends Component {
render() {
return (
I am 2.
<View style={[styles.base, styles.box2]}> <Text style={styles.text}>I am 5.</Text> </View> <View style={[styles.base, styles.box3]}> <Text style={styles.text}>I am 1.</Text> </View> </View> );}
}const styles = StyleSheet.create({
container: {
flex: 1,
},
text: {
fontSize: 24,
color: 'white',
},
base: {
justifyContent: 'center',
alignItems: 'center',
},
box1: {
flex: 2,
backgroundColor: 'black'
},
box2: {
flex: 5,
backgroundColor: 'red',
},
box3: {
flex: 1,
backgroundColor: 'yellow',
},
});AppRegistry.registerComponent('Native', () => App);
```・入力項目あり
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, TextInput, } from 'react-native'; export default class App extends Component { constructor(props) { super(props); this.state = { text: '', } } _onChangeText = (text) => { this.setState({ text }); } render() { const { text, } = this.state; return ( <View style={styles.container}> <TextInput style={styles.input} onChangeText={this._onChangeText} underlineColorAndroid='transparent' /> <Text>{text}</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, input: { height: 30, width: 200, borderBottomWidth: 1, borderBottomColor: '#008080', } }); AppRegistry.registerComponent('Native', () => App);最後に
ネイティブ開発はまだまだ成熟していない技術て、日に日に進歩をしている元気な技術です
android もjava → kotlin が公式言語(google曰く)になったし
ios も objective-c → swift に変更になり言語も技術もどんどん進化しています現在ほとんどのアプリでandroid ios両方でもリリースが当たり前になってきています。
両方の端末で開発をするとなると技術者不足や工数不足、お金の不足など沢山の問題にぶち当たります
そのため、クロスプラットフォーム技術もどんどん進化して行きます
今回紹介した React Native以外にもkotlin nativeなど似たような技術も存在しています今後も自分の価値をあげるためにも、こういう技術はどんどん取り入れていきましょう
- 投稿日:2019-11-15T17:23:07+09:00
JavaScript(ES6) メソッドと関数の違い
はじめに
エンジニアになる上で、最初の難関は用語だと思う。
用語の意味をしっかりと覚えることで、正しい理解をすることに繋がる。
その最初の一歩として「メソッド」と「関数」についてお話する。結論から
『メソッド』
クラスから生成されたインスタンスの行える処理がひとまとめにされたもの
『関数』
関数自体が独立して定義でき、処理がひとまとめにされたもの詳細
※メソッドを理解する前に、把握しておく概念がある、それは『クラス』と『インスタンス』だ。
上記2つの理解が不十分の方は先に理解を深めてから、読むことをオススメする。【メソッド】
メソッドはインスタンスの「動作」のようなものです。
例えば「計算を行う」、「文字を出力する」といった処理をクラスの中でひとまとめにして表しています。
そのため、メソッドはクラスの中でのみ定義をされるものです。
例class Human { //下記がメソッド greet(){ console.log("こんにちは"); } } //インスタンスの生成 const human = new Human(); //メソッドの呼び出し human.greet(); //処理結果:こんにちは【関数】
関数はいくつかの処理をまとめたものです。
処理をまとめたものという点でメソッドと同じですが、違う点は
関数は独立して定義されるのに対し、メソッドはクラスの中で定義されます。例
//関数の定義(クラス内ではない) const greet = ()=>{ console.log("こんにちは!"); } //関数の呼び出し greet(); //処理結果:こんにちは!まとめ
「共通点」:処理をひとまとめにしている
「相違点」:クラス内で定義されている→メソッド
独立して定義されている→関数
- 投稿日:2019-11-15T15:44:37+09:00
JavaScriptを始めようと思い立った人のためのJavaScript超入門
はじめに
この記事は、JavaScriptを始めようと思い立った際に古くなった情報を頼りに学習した結果、それに気づかず古臭いコードを書き散らかしてしまった
(しかもそれが嫌すぎて全部書き直した)自分の反省から、JavaScriptを勉強し始めた駆け出しの方が同じ目に合わないように、一人でも救えるようにと願いを込めて書いたものです。
内容自体は至極超入門なものなので、初学者の方は安心して読み進めて頂ければと思います。また見識の深い方には、僕が初学者の方に間違いを教えてしまっている場合はその点指摘していただけると幸いです。今回は基礎中の基礎、「変数」「関数」「for文」「if文」の4項目の書き方について記述していきます。
変数
varは使わない
古い本を買ってしまうと、大体変数は
var
を使っているでしょう。ですがこれ、やめた方がいいです。JavaScriptでは、変数宣言に
var, let, const
の3種類が利用可能です。「再宣言」と「値の上書き」の2点から、それぞれの違いについて簡単に説明していきます。
種類 再宣言 再代入 var 〇 〇 let ✕ 〇 const ✕ ✕ ご覧の通りなわけですが、
var
は一度宣言した変数を簡単に上書きできてしまいます。これが一概に悪というわけではありませんが、思いもしないところでエラーを引き起こす可能性があるので、避ける傾向にあります。詳しくはこちらのブログに分かりやすくまとまっていますのでご確認下さい。
基本的には
const
、値を上書きする必要がある値の場合はlet
を使うようにしましょう。関数
アロー関数を使う
この辺りは正直好みの問題かもしれませんが、個人的には関数定義の際に都度
function( ) { }
と書いていられないという気持ちなので、時短の意味合いと文字数削減のためにアロー関数を利用しています。書き方は他の言語と同様です。
アロー関数// 通常の関数 function functionName( ) { "ここに処理を記述" } // アロー関数 const functionName = ( ) => { "ここに処理を記述" }for文
ループは基本的にforループで
ループ処理といえば
for
の他に、while
やdo...while
などがありますが、特別な理由がない場合はfor
一択とすることをおすすめします。
理由は2点あります。
1. 分かりやすいから
2. 処理スピードが速いから分かりやすいから
こちらも主観の入った意見となり恐縮ですが、for
ループの条件式って非常に読みやすいですよね。初期値、ループを抜ける条件、インクリメントの順番は、言語化してもそのままなので非常にすっきりとしています。JavaScriptのforループfor(let i = 0; i < 10; i++){ }
while
文やdo...while
文の場合はカウンターとなる変数を事前に定義していなければならないため、条件式の中だけを見て最終的に何回処理を行うのかパッと見て分かりづらいです。処理スピードが速いから
こちらの記事にループ処理の速度がまとめられています。可読性が高くて速度が速いとなると、
for
でいいじゃんってなりますよね。配列のループ
実際に
for
文を使うときは、上記の例のように回数を指定して行うことはあまりありません。何か処理をしたい値の集まり(配列など)に対して処理を行うことが多いので、その個数を取得してループの終了条件に利用します(配列については特段難しいことはないので、説明は割愛させて頂きます)。配列を利用したforループconst array = [1, 2, 3, ... , 98, 99, 100] // 1~100までの値がある配列 for(let i = 0, len = array.length; i<len; i++){ // 繰り返す処理を記述 }実はこれも処理速度をより早くできる書き方と言われています。
第2引数に直接i < array.length
と書くこともできますが、第一引数でカウンターと併せて配列の個数len
を定義しておく方が処理が早く済みます。条件分岐
if文
基本的にはif文でいいかと思います。特に難しいところはないのですが、判定の時に
==
または、===
を使うことを覚えておいてください。
==
の場合は、文字列であろうと、数値であろうと、値自体が同じであれば等しいと判定されます。
===
の場合は、文字列なのか数値なのかまで厳密に判断して等しいかどうかを判定します。また、もう一つ大事なポイントとして、条件式の中は必ず比較したい値を左側に書くようにしてください。こういった細かなルールを作っておけば、作業の引継ぎなどが発生した場合も問題が起きにくいです。
その他の比較演算子は以下の通りです。if文の構文if(a < b){ } // aはbより小さい if(a <= b) { } // aはb以下 if(a > b){ } // aはbより大きい if(a >= b) { } // aはb以上 if(a != b) { } // aとbは等しくない if(a !== b) { } // aとbは等しくない(型まで評価)switch文
if
文、else if
文で表記すると雑多になってしまうような場合は、switch
文の利用をおすすめします。switch文const name = "tanaka"; switch(name){ case 'sato': console.log('1番多い苗字ですね。'); break; case 'suzuki': console.log('2番目に多い苗字ですね。'); break; case 'tanaka': console.log('3番目に多い苗字ですね。'); break; default: console.log("ふ~ん"); break; }上のように、比較したい変数名を引数に入れ、
case
文の後ろに比較する値を入れてあげるだけです。
ポイントとしては、各case
の処理の最後には最後に必ずbreak;
を入れるようにしてください。また、想定外の値に備えてdefault:
も記述するようにして下さい。まとめ
といった具合で、今回はJavaScriptの基本中の基本となる項目を確認しました。
次は何か題材を見つけて、HTMLの要素を制御する処理などについて解説していこうと思います。
- 投稿日:2019-11-15T15:22:10+09:00
配列内の特定条件の値群のみ先頭or末尾に集める並び替え
要件
- 配列にn個の要素がある
- 条件に合う要素のみ、固めて配列の先頭に移動したい
- 条件に合わない要素の順列は保存する
条件(移動したい値)は複数あるとし、配列で持っていることとする。
結論コード
filter
で条件に合う配列と条件に合わない配列を作ってつなげる。
こちらは非破壊的な方法。const priority = ['dog', 'cat', 'human'] const target = [ {id:'caw'}, {id:'pig'}, {id:'dog'}, {id:'car'}, {id:'cat'}, {id:'bird'}, {id:'human'}, ] console.log( target.filter( a => priority.includes(a.id) ).concat( target.filter( b => !priority.includes(b.id)) ) )出力[ { "id": "dog" }, { "id": "cat" }, { "id": "human" }, { "id": "caw" }, { "id": "pig" }, { "id": "car" }, { "id": "bird" } ]sort版
想定していたやりかたができそうになかったので、
sort
の自作比較関数で行うことに。
破壊的であることが好きな場合に。Array.prototype.sort() - JavaScript | MDN
const priority = ['dog', 'cat', 'human'] const target = [ {id:'caw'}, {id:'pig'}, {id:'dog'}, {id:'car'}, {id:'cat'}, {id:'bird'}, {id:'human'}, ] console.log(target.sort((a,b) => { const abool = priority.includes(a.id) const bbool = priority.includes(b.id) if((abool && bbool) || (!abool && !bbool)) { return 0 } if (abool) { return -1 } return 1 }))
(!abool && !bbool)
のパターンを忘れていて非一致値の順番がめちゃくちゃになったりしたので注意。
filter版の対称性のほうがわかりやすい気もするので意図の汲み取りやすさも含めてお好みで。(
filter
にあるthisArg
がないしsortなのにまったく外部の値を使うのもちょっと不気味なのかもしれない)当初想定していたやりかた
「条件に合う値だけの配列を削除して返り値にする破壊的関数」を求めたが、調べても
slice
やfind
など複数まとめて取得できなかったりfilter
は非破壊的だったりと一発では行かず。集めた配列 + あまった配列 で完成させたかったのだが。
map
などで破壊しながら新しい配列を作ってconcat
するぐらいならfilter
を反転してそれぞれ作るほうが綺麗かなと結論コードに。たいしたコストではないが「あまりものの再利用」ができなかったのが少し不満。
他の配列操作系
- 投稿日:2019-11-15T15:06:44+09:00
スマホ用のJavascript/CSSコンソールを作ったお話
スマホ版ブラウザでPC版Chromeコンソールのようなものが(即座に!)使えればいいのになと思ったので、非常に簡易的なものではありますが、作りました。
ブックマークレットのコード
bookmarkletjavascript:window.outerJsConsole%3D%7Be%3A function(v%2Cf)%7Bvar a%3Ddocument.createElement(v)%3Bif(v%3D%3D"script")%7Ba.setAttribute("type"%2C"text%2Fjavascript")%3Bif(!f)%7Ba.setAttribute("id"%2C"newScriptTag")%3B%7Da.classList.add("OJCscript")%3B%7Delse if(v%3D%3D"style")%7Ba.setAttribute("type"%2C"text%2Fcss")%3Bf %3F a.setAttribute("id"%2C"baseStyleTag")%3A a.classList.add("OJCstyle")%3B%7Delse%7Ba.classList.add("OJCelm")%3B%7Dreturn a%3B%7D%2Calert%3A function(v%2Ccaution)%7Bvar a%3Dnew Date()%2Cah%3Da.getHours()< 10 %3F "0" %2B a.getHours()%3A a.getHours()%2Cam%3Da.getMinutes()< 10 %3F "0" %2B a.getMinutes()%3A a.getMinutes()%2Cas%3Da.getSeconds()< 10 %3F "0" %2B a.getSeconds()%3A a.getSeconds()%2Cb%3Dthis.now %3F "(JS)" %3A "(CSS)"%2Cc%3Dcaution %3F %60 class%3D"OJCcaution"%60 %3A ""%2Cd%3D%60<table%60 %2B c %2B %60><tbody><tr><td style%3D"font-style%3A italic%3Bwidth%3A 6em">%60 %2B ah %2B "%3A" %2B am %2B "%3A" %2B as %2B %60<br><b style%3D"color%3A %23aaa">%60 %2B b %2B %60<%2Fb><%2Ftd><td><pre style%3D"background%3A transparent%3Bborder%3A none%3Bborder-radius%3A 0%3Bfont-family%3A sans-serif">%60 %2B v %2B %60<%2Fpre><%2Ftd><%2Ftr><%2Ftbody><%2Ftable>%60%2Ce%3Ddocument.getElementById("OJCalert")%3Be.innerHTML%3Dd %2B e.innerHTML%3B%7D%2Carea%3A function()%7Bvar a%3Dthis.e("style"%2Ctrue)%2Cb%3Dthis.e("div")%3Ba.innerHTML%3D%60%23OJCconsole %23OJCalert%7Bfont-family%3A sans-serif%3B%7D%23OJCconsole %23OJCalert%7Bposition%3A fixed%3Bbackground%3A %23f0f0f0%3Bborder%3A 1px solid %23aaa%3Bbottom%3A 30vh%3Bfont-size%3A 0.9em%3Bleft%3A 0%3Bpadding%3A 0.75em%3Bheight%3A 5rem%3Bwidth%3A 100%25%3Boverflow-y%3A scroll%3B-webkit-overflow-scrolling%3A touch%3B%7D%23OJCconsole %23OJCalert table%7Bborder-bottom%3A 1px solid %23aaa%3Bdisplay%3A block%3Bpadding%3A 0.5em 0%3B%7D%23OJCconsole %23OJCalert table.OJCcaution%7Bbackground%3A %23fff0f0%3B%7D%23OJCconsole .OJCwrap%7Bposition%3A fixed%3Bbottom%3A 0%3Bleft%3A 0%3Bheight%3A 30vh%3Bwidth%3A 100%25%3B%7D%23OJCconsole .OJCwrap *%7Bposition%3A relative%3Bappearance%3A none%3B-webkit-appearance%3A none%3B-moz-appearance%3A none%3B%7D%23OJCconsole .OJCwrap textarea%7Bborder%3A 1px solid %23aaa%3Bborder-radius%3A 0%3Bfont-size%3A 16px%3Btransform%3A scale(0.8)%3Btransform-origin%3A top left%3Bheight%3A calc((100%25 - 4em)%2F 0.8)%3Bpadding%3A 0.75em%3Bwidth%3A calc(100%25 %2F 0.8)%3B-webkit-overflow-scrolling%3A touch%3B%7D%23OJCconsole .OJCwrap div%7Bdisplay%3A flex%3Bheight%3A 3em%3Bjustify-content%3A center%3B%7D%23OJCconsole .OJCwrap div button%7Bbackground%3A %23b01%3Bborder%3A 1px solid %23fff%3Bcolor%3A %23fff%3Bdisplay%3A block%3Bflex-grow%3A 1%3Bfont-family%3A sans-serif%3Bfont-size%3A 15px%3Bfont-weight%3A bold%3Bpadding%3A 0.5em%3Bwidth%3A 15%25%3B%7D%60%3Bb.setAttribute("id"%2C"OJCconsole")%3Bb.innerHTML%3D%60<div id%3D"OJCalert"><%2Fdiv><div class%3D"OJCwrap"><div><button onclick%3D"outerJsConsole.change(this)%3B" style%3D"background%3A %23065fd4%3B">JS<%2Fbutton><button onclick%3D"outerJsConsole.apply()%3B">apply<%2Fbutton><button onclick%3D"outerJsConsole.code()%3B">code<%2Fbutton><button onclick%3D"outerJsConsole.close()%3B">close<%2Fbutton><%2Fdiv><textarea id%3D"outerJsTgt"><%2Ftextarea><%2Fdiv>%60%3Bdocument.head.appendChild(a)%3Bdocument.body.appendChild(b)%3B%7D%2Cnow%3A true%2Cchange%3A function(v)%7Bvar a%3D(b%2Cc)%3D>%7Bthis.now%3Db%3Bv.innerHTML%3Dc%3B%7D%3Bthis.now %3F a(false%2C"CSS")%3A a(true%2C"JS")%3B%7D%2Capply%3A function()%7Bvar x%3Ddocument.querySelectorAll(".OJCscript%2C.OJCstyle").length%2Ca%3Ddocument.getElementById("outerJsTgt")%3Btry%7Bvar b%3Ddocument.getElementById("newScriptTag")%3Bb.removeAttribute("id")%3B%7Dcatch(e)%7B%7Dvar c%3Dthis.now %3F this.e("script")%3A this.e("style")%2Cd%3Da.value.replace(%2F%5E%5Cn%2B%7C%5Cn%2B%24%2Fg%2C"")%3Bc.innerHTML%3Dd%3Bdocument.head.appendChild(c)%3Bvar y%3Ddocument.querySelectorAll(".OJCscript%2C.OJCstyle").length%3Bif(x !%3D%3Dy)%7Bthis.alert(d)%3B%7D%7D%2Ccode%3A function()%7Bvar a%3Dthis.now %3F document.querySelectorAll(".OJCscript")%3A document.querySelectorAll(".OJCstyle")%3Bif(a.length%3D%3D0)%7Breturn this.alert("No code.")%3B%7Dvar b%3D""%3Bfor(var i%3D0%3Bi < a.length%3Bi%2B%2B)%7Bb %2B%3Da%5Bi%5D.innerHTML %2B "%5Cn"%3B%7Dreturn this.alert(b)%3B%7D%2Cclose%3A function()%7Bwindow.onerror%3D""%3Bvar a%3Ddocument.querySelectorAll(".OJCelm%2C%23baseStyleTag")%3Bfor(var i%3D0%3Bi < a.length%3Bi%2B%2B)%7Ba%5Bi%5D.parentNode.removeChild(a%5Bi%5D)%3B%7Dwindow.outerJsConsole%3D%7B%7D%3B%7D%2Cinit%3A function()%7Bwindow.onerror%3Dfunction(msg%2Curl%2Clinenumber)%7BouterJsConsole.alert(msg %2B "(at%3A " %2B linenumber %2B ")."%2Ctrue)%3Bvar a%3Ddocument.getElementById("newScriptTag")%3Ba.parentNode.removeChild(a)%3Breturn true%3B%7D%3Bthis.area()%3Bthis.now%3Dtrue%3B%7D%7D%3BouterJsConsole.init()%3Bvoid(0);以上のコードをコピペして登録してください。
コピペの際には周りの文章ごと選択してメモ帳などで余分な部分を消すと楽です。iPhoneから見るとコードが全部表示されてしまうようですね…。なんてこった…。ブックマークレットの登録方法は以下のページが詳しいですので、参考までに。
ブックマークレットの登録方法:
https://qiita.com/aqril_1132/items/b5f9040ccb8cbc705d04見た目/できること
ブックマークレットを起動すると、上記のようなコンソールが現れます。
各ボタンの概要:
- 青いボタン
- 「JS(JavaScript)」モードと「CSS」モードを切り替えます。
- apply
- テキストエリア内のコードを適用します。
- code
- これまでに適用したコードを全てリザルトに表示します。
- close
- コンソールを閉じます。
JS(JavaScript)モード
特に説明することは無いです。PC版Chromeコンソールのようにオブジェクトの中身を覗いたりといったことは出来ず、ただスクリプトをそのページに追加するだけです。
ただしそのスクリプトに問題があったときは、以下のような警告をリザルトに表示します。
この時、そのスクリプトの適用はされません。
Tips: オブジェクトの中身を見たい場合は、alertやJSON.stringify()などを駆使すると良いです。
JavaScriptalert(JSON.stringify(<Object>));CSSモード
CSSを変更できます。シンプルですね。例えばqiita(このサイト)で使用する場合、
CSS.it-MdContent { color: red; }このCSSを適用することで、記事の本文を真っ赤に染め上げることができます。
最後に
ブックマークレットに加工する前のコードを以下に載せます。
JavaScriptwindow.outerJsConsole = { e: function(v, f) { var a = document.createElement(v); if(v == "script") { a.setAttribute("type", "text/javascript"); if(!f) { a.setAttribute("id", "newScriptTag"); } a.classList.add("OJCscript"); }else if(v == "style") { a.setAttribute("type", "text/css"); f ? a.setAttribute("id", "baseStyleTag") : a.classList.add("OJCstyle"); }else { a.classList.add("OJCelm"); } return a; }, alert: function(v, caution) { var a = new Date(), ah = a.getHours() < 10 ? "0" + a.getHours() : a.getHours(), am = a.getMinutes() < 10 ? "0" + a.getMinutes() : a.getMinutes(), as = a.getSeconds() < 10 ? "0" + a.getSeconds() : a.getSeconds(), b = this.now ? "(JS)" : "(CSS)", c = caution ? ` class="OJCcaution"` : "", d = `<table` + c + `><tbody><tr><td style="font-style: italic; width: 6em">` + ah + ":" + am + ":" + as + `<br><b style="color: #aaa">` + b + `</b></td><td><pre style="background: transparent;border: none;border-radius: 0;font-family: sans-serif">` + v + `</pre></td></tr></tbody></table>`, e = document.getElementById("OJCalert"); e.innerHTML = d + e.innerHTML; }, area: function() { var a = this.e("style", true), b = this.e("div"); a.innerHTML = ` #OJCconsole #OJCalert { font-family: sans-serif; } #OJCconsole #OJCalert { position: fixed; background: #f0f0f0; border: 1px solid #aaa; bottom: 30vh; font-size: 0.9em; left: 0; padding: 0.75em; height: 5rem; width: 100%; overflow-y: scroll; -webkit-overflow-scrolling: touch; } #OJCconsole #OJCalert table { border-bottom: 1px solid #aaa; display: block; padding: 0.5em 0; } #OJCconsole #OJCalert table.OJCcaution { background: #fff0f0; } #OJCconsole .OJCwrap { position: fixed; bottom: 0; left: 0; height: 30vh; width: 100%; } #OJCconsole .OJCwrap * { position: relative; appearance: none; -webkit-appearance: none; -moz-appearance: none; } #OJCconsole .OJCwrap textarea { border: 1px solid #aaa; border-radius: 0; font-size: 16px; transform: scale(0.8); transform-origin: top left; height: calc((100% - 4em) / 0.8); padding: 0.75em; width: calc(100% / 0.8); -webkit-overflow-scrolling: touch; } #OJCconsole .OJCwrap div { display: flex; height: 3em; justify-content: center; } #OJCconsole .OJCwrap div button { background: #b01; border: 1px solid #fff; color: #fff; display: block; flex-grow: 1; font-family: sans-serif; font-size: 15px; font-weight: bold; padding: 0.5em; width: 15%; }`; b.setAttribute("id", "OJCconsole"); b.innerHTML = `<div id="OJCalert"></div><div class="OJCwrap"><div><button onclick="outerJsConsole.change(this);" style="background: #065fd4;">JS</button><button onclick="outerJsConsole.apply();">apply</button><button onclick="outerJsConsole.code();">code</button><button onclick="outerJsConsole.close();">close</button></div><textarea id="outerJsTgt"></textarea></div>`; document.head.appendChild(a); document.body.appendChild(b); }, now: true, change: function(v) { var a = (b,c) => {this.now = b;v.innerHTML = c;}; this.now ? a(false,"CSS") : a(true,"JS"); }, apply: function() { var x = document.querySelectorAll(".OJCscript, .OJCstyle").length, a = document.getElementById("outerJsTgt"); try { var b = document.getElementById("newScriptTag"); b.removeAttribute("id"); }catch(e) {} var c = this.now ? this.e("script") : this.e("style"), d = a.value.replace(/^\n+|\n+$/g, ""); c.innerHTML = d; document.head.appendChild(c); var y = document.querySelectorAll(".OJCscript, .OJCstyle").length; if(x !== y) { this.alert(d); } }, code: function() { var a = this.now ? document.querySelectorAll(".OJCscript") : document.querySelectorAll(".OJCstyle"); if(a.length == 0) { return this.alert("No code."); } var b = ""; for(var i = 0; i < a.length; i++) { b += a[i].innerHTML + "\n"; } return this.alert(b); }, close: function() { window.onerror = ""; var a = document.querySelectorAll(".OJCelm,#baseStyleTag"); for(var i = 0; i < a.length; i++) { a[i].parentNode.removeChild(a[i]); } window.outerJsConsole = {}; }, init: function() { window.onerror = function(msg, url, linenumber) { outerJsConsole.alert(msg + " (at: " + linenumber + ").", true); var a = document.getElementById("newScriptTag"); a.parentNode.removeChild(a); return true; }; this.area(); this.now = true; } }; outerJsConsole.init();リザルト用のtableに直接styleを付けたりしていて汚いですが、各サイト毎のCSSに対応する&文字数削減のためのものですので、ご了承ください。
- 投稿日:2019-11-15T14:16:10+09:00
いまからはじめるReact
この資料は 11/16(土)開催の勉強会 いまからはじめるReact の資料になります。
React未経験者/初学者向けに チュートリアルを通してReact(およびHooks)について学ぶためのものです。
そのため、サンプルコードには例外処理などが不十分な箇所があります。ご注意ください。Reactとは?
Reactとは Facebookが中心となってオープンソースで開発されている ユーザーインターフェースを構築するためのJavaScriptライブラリです。
(2019/10/30現在、v16.10.2 が公開されています)React – ユーザインターフェース構築のための JavaScript ライブラリ
https://ja.reactjs.org/コンポーネント(部品)を作成し、これらを組み合わせることでSingle Page Applicationのような複雑なユーザーインターフェースを構築できるので、ピュアなJavaScriptやjQueryで実装する場合に比べてコードの見通しがよく、デバッグしやすいものになります。
開発環境の準備
以下のツールが必要です。
- エディタ (VisualStudio Codeがオススメです)
- Node.js (頻繁にバージョンアップするので、nvmなどのバージョン管理ツールを使用することをオススメします)
Reactの開発ではOSを選びません。
Windows/Mac/Linuxどれでも好きな環境で開発できます。
Reactをはじめる前に
Reactを使用する際に頻出する JavaScript (ECMAScript2015) の基本文法について確認します。
変数の宣言 let, const
JavaScriptにおける変数/定数の宣言方法は3つあります。
-var
-let
-const
varの問題点 その1: 巻き上げ
参考: https://developer.mozilla.org/ja/docs/Learn/JavaScript/First_steps/Variables
varの巻き上げ(hoisting)
変数の宣言 (および一般的な宣言) はコードを実行する前に処理されますので、変数はコード内のどこで宣言しても、コードの先頭で宣言したものと等価になります。また、変数を宣言する前に変数を使用することもできます。この動作は、変数の宣言が関数やグローバルのコードの先頭に移動したように見えるため、"巻き上げ (hoisting)" と呼ばれます。myName = 'Chris'; function logName() { console.log(myName); } logName(); var myName;上記の例で
var
をlet
に変更すると、エラーで失敗します。varの問題点 その2: 変数の上書き
var
を使用するとき、好きなだけ同じ変数を何度でも宣言することができます、しかしlet
ではできません。var myName = 'Chris'; var myName = 'Bob';上記の例で
var
をlet
に変更すると、エラーで失敗します。これらの問題点は潜在的なバグの要因になりかねません。
Reactの開発においてvar
が必要になることはありませんので、使用しないこと!const
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/const
const 宣言は、値への読み取り専用の参照を作ります。その値が不変ということではなく、その変数識別子が再代入できないというだけです。
const number = 42; try { number = 99; } catch(err) { console.log(err); // expected output: TypeError: invalid assignment to const `number' // Note - error messages will vary depending on browser } console.log(number); // expected output: 42objectのプロパティなどは変更できます。
const obj = { number: 42, }; try { obj.number = 99; } catch(err) { console.log(err); } console.log(obj.number); // => 99JavaScriptでは型がないため、変数にどのような値が格納されるのか制限できません。
変数を定義する際はconst
で宣言することで、意図しない値が格納されることを防げます。
ループのカウンタなど、どうしても再代入が必要な変数のみlet
を使用するのがオススメです。arrow function
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions
アロー関数式は、より短く記述できる、通常の function 式の代替構文です。
2 つの理由から、アロー関数が導入されました。1 つ目の理由は関数を短く書きたいということで、2 つ目の理由は this を束縛したくない、ということです。
アロー関数は以下のように使用します。
const materials = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ]; console.log(materials.map(material => material.length)); // expected output: Array [8, 6, 7, 9]構文
状況によってカッコを省略できます。
カッコの有無ではなく、=>
を見てアロー関数かどうかを判断してください。(param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // 上記の式は、次の式と同等です: => { return expression; } // 引数が 1 つしかない場合、丸括弧 () の使用は任意です: (singleParam) => { statements } singleParam => { statements } // 引数がない場合、丸括弧を書かねばいけません: () => { statements }スプレッド構文
...
スプレッド構文 - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax
スプレッド構文を使うと、関数呼び出しでは 0 個以上の引数として、Array リテラルでは 0 個以上の要素として、Object リテラルでは 0 個以上の key-value のペアとして、Array や String などの iterable オブジェクトをその場で展開します。// 配列の展開 const arr1 = [1, 2, 3]; const arr2 = [4, 5, ...arr1]; console.log(arr2); // => [4, 5, 1, 2, 3]; // オブジェクトの展開 (ECMAScript 2018以降) const obj1 = { firstName: 'kazunori', familyName: 'kimura' }; const obj2 = { ...obj1, age: 40 }; console.log(obj2); // => { firstName: 'kazunori', familyName: 'kimura', age: 40 } // 関数の引数 const sum = (...args) => { // 引数が args という配列に格納される let value = 0; args.forEach(arg => value += arg); return value; }; console.log(sum(1, 3, 5, 7)); // => 16 // 関数の呼び出し const multi = (a, b) => { return a * b; } const arr = [3, 5, 7]; console.log(multi(...arr)); // => 15分割代入 (Destructuring assignment)
分割代入 - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
分割代入 (Destructuring assignment) 構文は、配列から値を取り出して、あるいはオブジェクトからプロパティを取り出して別個の変数に代入することを可能にする JavaScript の式です。配列の分割代入
const [one, two] = [1, 2, 3, 4]; console.log(one); // => 1 console.log(two); // => 2 const [a, b, c] = [1, 2]; console.log(a); // => 1 console.log(b); // => 2 console.log(c); // => undefined// 既定値の設定 const [a, b = 4, c = 5] = [1, 2]; console.log(a); // => 1 console.log(b); // => 2 console.log(c); // => 5// スプレッド構文との組み合わせ const [a, b, ...arr] = [1, 2, 3, 4, 5]; // [a, b, ...arr,] <= 余剰なカンマはエラーとなる console.log(a); // => 1 console.log(b); // => 2 console.log(arr); // => [3, 4, 5]TIPS: Tupleの代替
分割代入によって他言語にある
Tuple
に似た機能を実装できます。const calc = (a, b) => { return [a + b, a * b]; }; const [sum, multi] = calc(3, 5); console.log(sum); // => 8 console.log(multi); // => 15 // 掛け算の結果だけほしい const [, m] = calc(4, 8); console.log(m); // => 32TIPS: 変数の入れ替え
配列の分割代入を使用すると、変数の値の入れ替えが簡単に行えます。
let a = 1; let b = 2; [a, b] = [b, a]; console.log(a); // => 2 console.log(b); // => 1オブジェクトの分割代入
配列の分割代入とイメージは大差ありません。
const obj = { name: 'kimura', age: 40 }; const { name } = obj; console.log(name); // => kimuraオブジェクトから変数を取り出して、オブジェクトのプロパティとは異なる名前を持つ変数に代入できます
const obj = { name: 'kimura', age: 40 }; const { name: userName } = obj; console.log(userName); // => kimuraTIPS 関数の引数に既定値を設定する
関数の引数にオブジェクトを渡すようにすることで、名前付き引数のような機能を実現できます。
また、既定値を設定することで省略可能な引数を定義できます。const drawRect = ({ width = 100, height = 100, position = { x: 0, y: 0 } } = {}) => { return `x1=${position.x},y1=${position.y},x2=${position.x + width},y2=${position.y + height}`; }; console.log(drawRect()); // => x1=0,y1=0,x2=100,y2=100 console.log(drawRect({ width: 200, position: {x: 50, y: 100} })); // => x1=50,y1=100,x2=250,y2=200
はじめてのReact
やっと本題に入ります。
それでは、Reactのプロジェクトを作成しましょう。create-react-app
Reactのプロジェクトを作成するには
create-react-app
コマンドを使用します。
create-react-app
はnpm
でインストールできます。$ npm install -g create-react-appでは、プロジェクトを作成します。
$ create-react-app todo-app $ cd todo-app $ code .
create-react-app
でプロジェクトを作成すると、すでにいくつかのファイルが生成されており
すぐに実行することが可能です。$ npm start
ブラウザが立ち上がり、Reactのロゴが表示されます。
まずはこのファイルを変更して、Reactの基本を学習します。App.js
App.jsimport React from 'react'; import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;
App
ファンクションが定義されています。
AppファンクションはHTMLのようなものを返却しています。これはJSX
とよばれる JavaScriptの構文の拡張です。
import
はライブラリやファイルの読み込みです。
export
は他のファイルから指定した要素を参照できるようにします。JSXに式を埋め込む
{} 中カッコ
の中に式を埋め込むことで表示内容を動的に変更できます。App.jsimport React from 'react'; import './App.css'; function App() { const message = 'Hello, React!'; return ( <div className="App"> <header className="App-header"> <p> {message} </p> </header> </div> ); } export default App;ファイルを保存すると、自動的にブラウザがリロードされて変更が反映されます。
(以降、App.js内のaタグ, imgタグは不要なので削除しておきます)あらゆる有効な JavaScript の式を JSX 内で中括弧に囲んで使用できます。
以下は配列の中身を書き出す例です。
App.jsimport React from 'react'; import './App.css'; function App() { const libraries = [ 'jQuery', 'React', 'Vue.js' ]; return ( <div className="App"> <header className="App-header"> {libraries.map(item => <p>{item}</p>)} </header> </div> ); } export default App;コンポーネント
それでは、独自のコンポーネントを定義してみます。
分かりやすいようにcomponents
フォルダを作成し、その配下にコンポーネントを作成していきます。$ mkdir src/components $ touch src/components/Message.js
Message.js
はメッセージを表示するコンポーネントです。Message.jsimport React from 'react'; function Message() { return ( <p>Original Message.</p> ); } export default Message;
App.js
にMessage
コンポーネントを配置します。App.jsimport React from 'react'; import './App.css'; import Message from './components/Message'; function App() { return ( <div className="App"> <header className="App-header"> <Message /> </header> </div> ); } export default App;コンポーネント名は常に大文字で始めてください。
コンポーネントに値を渡す: props
App
からMessage
に値を渡して、動的にメッセージを組み立ててみます。
name
というプロパティに文字列を渡します。App.jsimport React from 'react'; import './App.css'; import Message from './components/Message'; function App() { return ( <div className="App"> <header className="App-header"> <Message name="kimura" /> </header> </div> ); } export default App;
Message
にて受け取った値を表示するように修正します。
Reactはコンポーネントを呼び出す際にprops
というobjectに与えられた属性やタグ内の値を渡します。Message.jsimport React from 'react'; function Message(props) { return ( <p>Hello, {props.name}!</p> ); } export default Message;コンポーネントは繰り返し使用できます。
App.jsimport React from 'react'; import './App.css'; import Message from './components/Message'; function App() { return ( <div className="App"> <header className="App-header"> <Message name="kimura" /> <Message name="tanaka" /> <Message name="suzuki" /> </header> </div> ); } export default App;React は柔軟ですが、1 つだけ厳格なルールがあります:
自分自身の props は決して変更してはいけません。では、テキストボックスの入力値を画面に反映させる場合はどのようにすればよいのでしょうか?
ユーザーの入力を扱う: state
コンポーネントで変化する値を扱う場合は
state
を使用します。予め用意した文字列ではなく、テキストボックスに名前を入力して
Message
コンポーネントに渡してみます。
state
の機能を使用するにはuseState
メソッドを使用します。App.jsimport React, { useState } from 'react'; import './App.css'; import Message from './components/Message'; function App() { const [name, setName] = useState(''); const handleTextInput = (e) => { setName(e.target.value); }; return ( <div className="App"> <header className="App-header"> <div className="form"> <input type="text" onChange={handleTextInput} /> </div> <Message name={name} /> </header> </div> ); } export default App;テキストボックスの内容が変わると
handleTextInput
メソッドが呼ばれます。
handleTextInput
でsetName
メソッドにテキストボックスの値を渡します。
name
の値が更新されるとMessage
が再描画されます。子から親に値を渡す
フォームをコンポーネント化することを考えてみましょう。
まずは名前を入力するフォームのコンポーネントを作成します。$ touch src/components/NameForm.js子から親にデータを渡すためには、親から子にコールバック関数を渡します。
子にてprops
に渡されたコールバック関数を実行します。NameForm.jsimport React from 'react'; function NameForm(props) { const handleTextInput = (e) => { props.onChangeName(e.target.value); }; return ( <div className="form"> <input type="text" value={props.name} onChange={handleTextInput} /> </div> ); } export default NameForm;
hendleTextInput
メソッドにてprops
にセットされたonChangeName
を実行しています。
これで、テキストボックスの内容が変わるたびにその値がonChangeName
メソッドを通じて親のコンポーネントに渡されます。App.jsimport React, { useState } from 'react'; import './App.css'; import Message from './components/Message'; import NameForm from './components/NameForm'; function App() { const [name, setName] = useState(""); return ( <div className="App"> <header className="App-header"> <NameForm name={name} onChangeName={value => setName(value)} /> <Message name={name} /> </header> </div> ); } export default App;
onChangeName
で受け取ったvalue
を stateにセットします。
子コンポーネントのテキストボックスの内容が変更されるとstateに反映され、Messageコンポーネントに引き渡されます。Reactではこのようにバケツリレーのようにして親から子に、子から親にデータを渡していきます。
Todoアプリの実装
それでは、もう少し複雑なアプリの開発を通してReactについて解説していきます。
今回はTodoアプリを作成します。
Todoのデータ設計
Todoは以下の項目を持つものとします。
- ID: TodoごとにユニークなIDを持つ
- Content: 内容
- Done: 完了フラグ
- CreatedAt: 作成日時
- UpdatedAt: 更新日時
下準備
App.css
の内容を修正しておきます。App.css.App { padding: 10px; } .theme-selector { padding: 10px; } .theme-selector label { margin-left: 20px; }Todoコンポーネントの作成
では、Todoコンポーネントを実装していきます。
$ touch src/components/Todo.js $ touch src/components/Todo.cssまずはスタイルを定義します。
Todo.css.todo { display: flex; width: 100%; min-height: 60px; align-items: stretch; border: 1px solid #ccc; border-bottom: 0; } .todo:last-child { border-bottom: 1px solid #ccc; } .todo .check { width: 40px; display: flex; justify-content: center; align-items: center; color: #00cc00; font-weight: bold; font-size: xx-large; } .todo .body { flex: 1; display: flex; flex-direction: column; align-items: stretch; } .todo .actions { width: 60px; display: flex; flex-direction: column; justify-content: center; align-items: center; } .todo .body .header { width: 100%; display: flex; flex-direction: row; justify-content: flex-start; align-items: center; } .todo .body .header .date { font-size: x-small; padding: 4px; } .todo .body .content { padding: 4px; } .todo .body textarea { width: calc(100% - 12px); height: 100%; margin: 3px; } .btn { width: 50px; height: 50px; margin: 5px; }つづいて Todoコンポーネント を作成します。
Todo.jsimport React from 'react'; import './Todo.css'; function Todo(props) { return ( <div className="todo"> <div className="check"> {/* Doneがtrueならチェックマークを表示 */} {props.Done && <span>✓</span>} </div> <div className="body"> <div className="header"> <span className="date">CreatedAt: {props.CreatedAt}</span> <span className="date">UpdatedAt: {props.UpdatedAt}</span> </div> {/* contentをそのまま表示 */} <div className="content">{props.Content}</div> </div> <button className="btn">Edit</button> <button className="btn">Delete</button> </div> ); } export default Todo;
props
にデータ設計で定義したTodoの内容がそのままセットされる想定です。
App.js
を修正し、いくつかTodoを表示してみます。App.jsimport React, { useState } from 'react'; import './App.css'; import Todo from './components/Todo'; function App() { const [todos, setTodos] = useState([ { ID: 1, Content: 'hoge', Done: true, CreatedAt: (new Date()).toISOString(), UpdatedAt: (new Date()).toISOString(), }, ]); return ( <div className="App"> {todos.map(item => <Todo key={item.ID} {...item} />)} </div> ); } export default App;
toISOString
は日付をUTCの文字列 (ISO8601形式) に変換します。参考: Date.prototype.toISOString() - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOStringコンポーネントを
map
メソッドなどで複数登録する場合、Reactが個々のコンポーネントを区別できるようにkey
プロパティを指定する必要があります。この状態で表示内容を確認してください。 hoge という項目が一つ表示されているはずです。
Todoの追加
Todoを追加する機能を実装していきます。
TodoForm
コンポーネントを作成します。$ touch src/components/TodoForm.jsまた、TodoのIDを重複なく採番するために、
uuid
というパッケージをインストールします。$ npm install --save uuid完了フラグとTodoの内容を入力するフォームを作成します。
Saveボタンが押されたら入力された内容を親コンポーネントに引き渡します。TodoForm.jsimport React, { useState } from 'react'; import './Todo.css'; function TodoForm(props = { Done: false, Content: '', onSave: () => {} }) { const [done, setDone] = useState(!!props.Done); const [content, setContent] = useState(props.Content); const handleSave = () => { const data = { Done: done, Content: content, }; props.onSave(data); // フォームの初期化 setDone(false); setContent(''); }; return ( <div className="todo"> <div className="check"> <input type="checkbox" checked={done} onChange={e => setDone(e.target.checked)} /> </div> <div className="body"> <textarea value={content} onChange={e => setContent(e.target.value)} /> </div> <button className="btn" onClick={handleSave}>Save</button> </div> ); } export default TodoForm;
App
にTodoForm
を追加します。
TodoForm
のSave
ボタンが押されたらその結果をstate
の配列に追加します。App.jsimport React, { useState } from 'react'; import uuid from 'uuid'; import './App.css'; import Todo from './components/Todo'; import TodoForm from './components/TodoForm'; function App() { const [todos, setTodos] = useState([ { ID: 1, Content: 'hoge', Done: true, CreatedAt: (new Date()).toISOString(), UpdatedAt: (new Date()).toISOString(), }, ]); const handleCreate = data => { // IDを採番 data.ID = uuid.v4(); // 現在日時を取得 const now = (new Date()).toISOString(); data.CreatedAt = now; data.UpdatedAt = now; // 末尾に追加 setTodos([...todos, data]); }; return ( <div className="App"> <TodoForm onSave={handleCreate} /> {todos.map(item => <Todo key={item.ID} {...item} />)} </div> ); } export default App;
handleCreate
メソッドではフォームから受け取った値に対して ID と作成日時、更新日時の項目を追加した上で
stateのTodo配列の末尾に追加しています。ここまで実装して、Todoが追加されることを確認します。
Todoを削除する
TodoコンポーネントにあるDeleteボタンをクリックすると該当のTodoが削除されるように実装していきます。
App.js// ... 省略 ... const handleDelete = id => { // IDが一致する項目のindexを取得 const index = todos.findIndex(item => item.ID === id); if (index >= 0) { // 新しい配列を生成 const newList = [...todos]; // 配列から該当要素を削除 newList.splice(index, 1); // stateに反映 setTodos(newList); } }; return ( <div className="App"> <TodoForm onSave={handleCreate} /> {todos.map(item => ( <Todo key={item.ID} {...item} onDelete={handleDelete} />) )} </div> ); } export default App;
handleDelete
を定義し、Todo
コンポーネントに渡します。
handleDelete
メソッドはTodoのIDを受け取って、該当するIDの項目をstateの配列から削除しています。Todo.jsimport React from 'react'; import './Todo.css'; function Todo(props) { return ( <div className="todo"> <div className="check"> {/* Doneがtrueならチェックマークを表示 */} {props.Done && <span>✓</span>} </div> <div className="body"> <div className="header"> <span className="date">CreatedAt: {props.CreatedAt}</span> <span className="date">UpdatedAt: {props.UpdatedAt}</span> </div> {/* contentをそのまま表示 */} <div className="content">{props.Content}</div> </div> <button className="btn">Edit</button> <button className="btn" onClick={() => props.onDelete(props.ID)}>Delete</button> </div> ); } export default Todo;Deleteボタンのクリック時に
App
から渡されたonDelete
メソッドを実行します。
その際、TodoのIDを引数に渡します。削除処理が正常に動くことを確認します。
Todoを更新する
更新処理は少し複雑です。
ひとつずつ実装していきます。編集モードの切り替え
まず、TodoコンポーネントのEditボタンをクリックすると、該当Todoが編集モードに切り替わるように実装します。
Todo.jsimport React, { useState } from 'react'; import TodoForm from './TodoForm'; import './Todo.css'; function Todo(props) { const [edit, setEdit] = useState(false); if (edit) { return ( <TodoForm {...props} onSave={() => {})} /> ); } return ( <div className="todo"> <div className="check"> {props.Done && <span>✓</span>} </div> <div className="body"> <div className="header"> <span className="date">CreatedAt: {props.CreatedAt}</span> <span className="date">UpdatedAt: {props.UpdatedAt}</span> </div> <div className="content">{props.Content}</div> </div> <button className="btn" onClick={() => setEdit(true)}>Edit</button> <button className="btn" onClick={() => props.onDelete(props.ID)}>Delete</button> </div> ); } export default Todo;編集モードのフラグをstateで管理します。
初期値はfalse
としておきます。編集モードの場合は従来のTodoコンポーネントの変わりに
TodoForm
コンポーネントを表示するようにします。
TodoForm
にはTodo
コンポーネントが受け取ったprops
を展開してセットします。
とりあえずonSave
には空の関数を渡しておきます。Editボタンをクリックしたら
edit
の値をtrue
に変更するように実装します。この状態で、Editボタンをクリックしたら編集モードに切り替わることを確認します。
編集モードのキャンセル
TodoForm
コンポーネントにキャンセルボタンを追加し、キャンセルボタンがクリックされたら編集モードが解除されるように実装します。まずは
TodoForm
にキャンセルボタンを実装します。TodoForm.jsimport React, { useState } from 'react'; import './Todo.css'; function TodoForm(props = { Done: false, Content: '' }) { const [done, setDone] = useState(!!props.Done); const [content, setContent] = useState(props.Content || ''); const handleSave = () => { const data = { Done: done, Content: content, }; props.onSave(data); setDone(false); setContent(''); }; return ( <div className="todo"> <div className="check"> <input type="checkbox" checked={done} onChange={e => setDone(e.target.checked)} /> </div> <div className="body"> <textarea value={content} onChange={(e) => setContent(e.target.value)} /> </div> <button className="btn" onClick={handleSave}>Save</button> {/* IDが存在する場合はキャンセルボタンを表示 */} {props.ID && ( <button className="btn" onClick={props.onCancel}>Cancel</button> )} </div> ); } export default TodoForm;最上位にある登録フォームにはキャンセルボタンが不要なので、
ID
の有無で登録か編集かを判定します。
TodoForm
ではTodo
から渡されたprops
にあるonCancel
を実行します。
onCancel
ではTodo
コンポーネントのstateedit
を更新するよう実装します。Todo.jsimport React, { useState } from 'react'; import TodoForm from './TodoForm'; import './Todo.css'; function Todo(props) { const [edit, setEdit] = useState(false); if (edit) { return ( <TodoForm {...props} onSave={() => {})} onCancel={() => setEdit(false)} /> ); } // ...省略...ここまで実装できたら、編集モードと通常モードの切り替えができることを確認します。
更新処理を実装する
仕上げに更新処理を実装します。
App
にTodo
コンポーネントから呼ばれる更新メソッドhandleUpdate
を実装します。
handleUpdate
の引数にはhandleCreate
同様、Todoのデータがobjectで渡される想定です。変更箇所のみ抜粋します
App.js// ... 省略 ... const handleUpdate = data => { const now = (new Date()).toISOString(); data.UpdatedAt = now; setTodos(todos.map(item => { // IDが一致する要素を差し替える if (item.ID === data.ID) { return data; } return item; })); }; // ... 省略 ... <Todo key={item.ID} {...item} onSave={handleUpdate} onDelete={handleDelete} /> // ... 省略 ...更新処理本体となる
handleUpdate
を追加します。
stateのTodo配列のうち、受け取ったデータとIDが一致するものを差し替えて、新しい配列をstateにセットしています。Todoコンポーネントの
onSave
にhandleUpdate
を渡します。Todoコンポーネントでは受け取った
onSave
をさらにTodoForm
に渡しますが、更新が完了したら自身の編集モードを終了するようフラグを更新しておきます。Todo.jsimport React, { useState } from 'react'; import TodoForm from './TodoForm'; import './Todo.css'; function Todo(props) { const [edit, setEdit] = useState(false); // TodoFormに引き渡す更新メソッド const handleUpdate = data => { props.onSave(data); setEdit(false); // 編集モード終了 }; if (edit) { return ( <TodoForm {...props} onSave={handleUpdate} onCancel={() => setEdit(false)} /> ); } // ... 省略 ...
TodoForm
コンポーネントではDone
,Content
だけでなくID
やCreatedAt
などの値も引き渡すように修正します。
面倒なので受け取ったprops
をすべてTodoデータに展開してしまいます。TodoForm.jsimport React, { useState } from 'react'; import './Todo.css'; function TodoForm(props = { Done: false, Content: '' }) { const [done, setDone] = useState(!!props.Done); const [content, setContent] = useState(props.Content || ''); const handleSave = () => { const data = { ...props, // 受け取ったpropsを展開 Done: done, Content: content, }; props.onSave(data); setDone(false); setContent(''); };これで入力した値が
TodoForm
->Todo
->App
に渡るように実装できました。追加、更新、削除が問題なく動作することを確認してください。
WebAPIとの連携
ここまではクライアントサイドの実装のみでしたので、データを保持する手段がなく、リロードすると登録したTodoの内容が消えてしまいます。
ここからはWebAPIと連携することでTodoを保存できるように実装していきます。WebAPIについて
今回はあらかじめ簡単なWebAPIをAWS上に作成しました。
APIの詳細は以下を確認してください。SwaggerHub
https://app.swaggerhub.com/apis/Kazunori-Kimura/TodoAPI/1データの取得
fetch
メソッドを使用して、WebAPIからデータを取得します。
外部から取得したデータを反映するには、副作用フック (Effect Hook) を使用します。副作用フックの利用法 – React (https://ja.reactjs.org/docs/hooks-effect.html)
データの取得、購読の設定、あるいは React コンポーネント内の DOM の手動での変更、といったものはすべて副作用の例です。App.jsimport React, { useState, useEffect } from 'react'; import uuid from 'uuid'; import './App.css'; import Todo from './components/Todo'; import TodoForm from './components/TodoForm'; const url = 'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/latest/todo'; function App() { const [todos, setTodos] = useState([]); useEffect(() => { const getTodoes = async () => { const response = await fetch(url, { method: 'GET', mode: 'cors', cache: 'no-cache', }); const res = await response.json(); setTodos(res); }; getTodoes(); }, []); // ... 省略 ..
useEffect
の第2引数を空の配列にすると、App
コンポーネントが描画されたときにだけ呼び出されます。補足: fetch
Fetch API - Web API | MDN (https://developer.mozilla.org/ja/docs/Web/API/Fetch_API)
Fetch API には (ネットワーク越しの通信を含む) リソース取得のためのインターフェイスが定義されています。XMLHttpRequest と似たものではありますが、より強力で柔軟な操作が可能です。補足: cors
オリジン間リソース共有 (CORS) - HTTP | MDN (https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)
オリジン間リソース共有 (CORS: Cross-Origin Resource Sharing) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行します。データの更新
追加/更新/削除でもAPIを呼び出すように修正します。
App.js// ... 省略 ... const handleCreate = data => { const createTodo = async () => { // ID, CreatedAt, UpdatedAtはAPI側で設定される const response = await fetch(url, { method: 'POST', mode: 'cors', headers: { "Content-Type": 'application/json; charset=utf-8', }, body: JSON.stringify(data), }); console.log(response.status); }; createTodo(); }; const handleUpdate = data => { const updateTodo = async () => { const response = await fetch(`${url}/${data.ID}`, { method: 'PUT', mode: 'cors', headers: { "Content-Type": 'application/json; charset=utf-8', }, body: JSON.stringify(data), }); console.log(response.status); }; updateTodo(); }; const handleDelete = id => { const deleteTodo = async () => { const response = await fetch(`${url}/${id}`, { method: 'DELETE', mode: 'cors', }); console.log(response.status); }; deleteTodo(); }; // ... 省略 ...Google Chromeの開発者ツールを表示した状態で実行してみてください。
登録/更新/削除処理は成功するものの、画面には反映されないと思います。
画面をリロードすれば取得処理が実行され、登録/更新/削除した内容が反映されます。都度画面をリロードしなければならないのは不便なので、登録/更新/削除が完了したら自動的に取得処理が実行されるように実装します。
useEffect
の第2引数にstateから取得した変数を与えます。
useEffect
は第2引数の値が変わる度に呼び出されますので、stateを更新すればリストが更新されるようになります。App.jsimport React, { useState, useEffect } from 'react'; import './App.css'; import Todo from './components/Todo'; import TodoForm from './components/TodoForm'; const url = 'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/latest/todo'; function App() { const [todos, setTodos] = useState([]); const [refresh, setRefresh] = useState(0); // リフレッシュ用のstate useEffect(() => { const getTodoes = async () => { const response = await fetch(url, { method: 'GET', mode: 'cors', cache: 'no-cache', }); const res = await response.json(); // 作成日時順に返す setTodos(res.sort((a, b) => a.CreatedAt < b.CreatedAt ? 1 : -1)); }; getTodoes(); }, [refresh]); // useEffectの第2引数にリフレッシュ用stateをセット const handleCreate = data => { const createTodo = async () => { // ID, CreatedAt, UpdatedAtはAPI側で設定される const response = await fetch(url, { method: 'POST', mode: 'cors', headers: { "Content-Type": 'application/json; charset=utf-8', }, body: JSON.stringify(data), }); console.log(response.status); setRefresh(Date.now()); // リフレッシュ用stateの値を更新 }; createTodo(); }; const handleUpdate = data => { const updateTodo = async () => { const response = await fetch(`${url}/${data.ID}`, { method: 'PUT', mode: 'cors', headers: { "Content-Type": 'application/json; charset=utf-8', }, body: JSON.stringify(data), }); console.log(response.status); setRefresh(Date.now()); // リフレッシュ用stateの値を更新 }; updateTodo(); }; const handleDelete = id => { const deleteTodo = async () => { const response = await fetch(`${url}/${id}`, { method: 'DELETE', mode: 'cors', }); console.log(response.status); setRefresh(Date.now()); // リフレッシュ用stateの値を更新 }; deleteTodo(); }; // ... 省略 ...これで登録/更新/削除するたびにリストが更新されるようになりました。
Contextフック
コンポーネントのデータ管理の基本は
state
とprops
ですが、コンポーネントが多階層になった場合にバケツリレーのごとくデータを渡すのは非常に面倒です。そういったときに使える機能が
Context Hook
になります。例えば、現在の認証済みユーザー・テーマ・優先言語といったデータを共有する場合に有用です。
Contextの使用例
今回はTodoアプリにテーマ選択機能を追加してみます。
App.jsimport React, { useState, useEffect, createContext } from 'react'; // createContextを追加 import './App.css'; import Todo from './components/Todo'; import TodoForm from './components/TodoForm'; const url = 'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/latest/todo'; // テーマごとのスタイル定義 const Themes = { light: { color: '#000', backgroundColor: '#fff', }, dark: { color: '#fff', backgroundColor: '#000', }, }; // 現在選択されているテーマを共有するContext // exportして他コンポーネントからも参照できるようにする export const ThemeContext = createContext(Themes.light); function App() { const [todos, setTodos] = useState([]); const [refresh, setRefresh] = useState(0); // 現在選択されているテーマを管理するstate const [theme, setTheme] = useState('light'); // ... 省略 ... // テーマの切り替え const handleTheme = (e) => { setTheme(e.target.value); }; return ( <div className="App"> {/* Providerの配下でContextが共有される */} <ThemeContext.Provider value={Themes[theme]}> {/* テーマの選択用ラジオボタン */} <div className="theme-selector"> <label><input type="radio" name="theme" value="light" defaultChecked={theme === 'light'} onChange={handleTheme} />Light</label> <label><input type="radio" name="theme" value="dark" defaultChecked={theme === 'dark'} onChange={handleTheme} />Dark</label> </div> <TodoForm onSave={handleCreate} /> {todos.map(item => ( <Todo key={item.ID} {...item} onSave={handleUpdate} onDelete={handleDelete} />) )} </ThemeContext.Provider> </div> ); } export default App;まず、テーマごとのスタイルを定義します。
Reactではstyle
プロパティに CSSのプロパティ名をkeyに、値をvalueにセットしてあるobjectを渡すとCSSを適用してくれます。
ただbackground-color
などのハイフンを含むプロパティ名の場合、backgroundColor
のようにハイフンを取り除いて次の文字を大文字にする必要があります。
ThemeContext
がテーマを共有するためのContext
になります。
他のコンポーネントから参照するため、export
しておきます。
Context
を共有する範囲をThemeContext.Provider
コンポーネントで括ります。
Provider
のvalue
に現在の値を渡します。
他コンポーネントはexport
されたThemeContext
を介してvalue
にセットされている値を取得することができます。
Context
を参照する側は以下のように実装します。Todo.jsimport React, { useState, useContext } from 'react'; // useContextを追加 import TodoForm from './TodoForm'; import './Todo.css'; import { ThemeContext } from '../App'; // AppからContextをimport function Todo(props) { const [edit, setEdit] = useState(false); const theme = useContext(ThemeContext); // useContextを使用してContextの値を取り出す const handleUpdate = (data) => { setEdit(false); props.onSave(data); }; if (edit) { return ( <TodoForm {...props} onSave={handleUpdate} onCancel={() => setEdit(false)} /> ); } return ( {/* styleにContextから取り出した値(object)をセット */} <div className="todo" style={theme}> <div className="check"> {props.Done && (<span>✓</span>)} </div> <div className="body"> <div className="header"> <span className="date">CreatedAt: {props.CreatedAt}</span> <span className="date">UpdatedAt: {props.UpdatedAt}</span> </div> <div className="content">{props.Content}</div> </div> <button className="btn" onClick={() => setEdit(true)}>Edit</button> <button className="btn" onClick={() => props.onDelete(props.ID)}>Delete</button> </div> ); } export default Todo;TodoForm.jsimport React, { useState, useContext } from 'react'; // useContextを追加 import './Todo.css'; import { ThemeContext } from '../App'; // AppからContextをimport function TodoForm(props = { Done: false, Content: '' }) { const [done, setDone] = useState(!!props.Done); const [content, setContent] = useState(props.Content || ''); const theme = useContext(ThemeContext); // useContextを使用してContextの値を取り出す const handleSave = () => { props.onSave({ ...props, Done: done, Content: content, }); setDone(false); setContent(''); }; return ( {/* styleにContextから取り出した値(object)をセット */} <div className="todo" style={theme}> <div className="check"> <input type="checkbox" checked={done} onChange={e => setDone(e.target.checked)} /> </div> <div className="body"> <textarea value={content} onChange={(e) => setContent(e.target.value)} /> </div> <button className="btn" onClick={handleSave}>Save</button> {props.ID && ( <button className="btn" onClick={props.onCancel}>Cancel</button> )} </div> ); } export default TodoForm;
App.js
のラジオボタンを変更すると各Todo、Formのテーマが切り替わります。サンプルが単純なためあまり威力が実感できないかもしれませんが、コンポーネント数が多かったり、孫・ひ孫のコンポーネントに値を渡したい場合に便利な機能です。
参考
- 投稿日:2019-11-15T13:47:06+09:00
VScodeを使って簡単にローカルサーバーを立てる
vscode-live-server
VSCodeからボタンひとつでローカルサーバーが立てられる拡張機能です。
https://github.com/ritwickdey/vscode-live-server良いところ
- ボタン一つでお手軽簡単。
hot reload
が有効。インストール
VSCode
のExtensions
からLive Server
で検索install
を押す使い方
- 右下に
Go Live
のボタンが増えるので、立ち上げたいhtml
をVSCode
で開いてGo Live
を押す- ローカルサーバーが立ち上がる
設定
liveServer.settings.root
- デフォルトは
/
- index.htmlがルートにない場合(例:/src/index.html)デフォルトのままだとjsファイルやcssファイルの参照が404になってしまうので、よしなに設定(例:./src)してあげる。
liveServer.settings.port
- デフォルトは
5500
- よしなに設定
liveServer.settings.https
- デフォルトは
false
- httpsの設定も可能
詳細はこちら
https://github.com/ritwickdey/vscode-live-server/blob/master/docs/settings.md
- 投稿日:2019-11-15T11:58:45+09:00
とあるラーメン店のWordpressサイトをNuxt.js+Firebaseで作り直した話
お店と Web サイトの紹介
中華そば四つ葉
https://yotsuba628.com/ラーメンが好きならご存じの方もいらっしゃるのではないでしょうか。
最寄駅から徒歩 90 分という衝撃のアクセスの不便さ。
にもかかわらず多数の祭事出店、テレビ出演、都内から足繁く通う芸能人もいる埼玉県川島町が誇る名店です。経緯
何を隠そう私は川島町出身なのですが、学生時代に店舗に通っているうちに店長に顔を覚えてもらうことができ、Web ページの作成をさせてほしいと申し出たところ承諾を頂けて 2018 年の 3 月頃に Web ページをリリースしました。
しかし当時の技術力不足もあり、Wordpress の入門書を写経しながらのものとなり・・・
サイズの大きい画像を多数保有しているため表示が遅く、見た目が芋臭いというなんともお粗末な状態で 1 年以上運用を続けていました。時がたち社会人になってからいくつかの技術的知識を付け、このお粗末な状態でも多くの閲覧者がいる現状に恥ずかしさを覚え、つい先日リニューアルを決意。約 3 日で完了することができました!(Nuxt.jsとFirebase便利すぎです)
どのようなスケジュールで行ったか、リニューアルした点などを記録に残しておきたいと思い、今回記事を書かせていただいております。
スケジュール
1 日目 現行サイトの改善したい点洗い出し
- 表示速度の改善
- レイアウトの一新 など
2 日目 コーディング
3 日目 確認依頼、OK が出た後新規ドメインを取得して接続。旧ドメインのリダイレクト設定
実装内容
エックスサーバ + Wordpress ⇒ Nuxt.js + Firebase
- 見た目
Vuetify を使用しました。テンプレートそのまま使えるものが多く、非常にスピーディに開発が進められて助かりました。
またこだわった点として写真を非常に良いものを使っています。プロのカメラマンさんに依頼して店舗側ともアポを取り、時間を取って写真を取っていただきました。
- 機能
一部紹介させていただくと、お品書きページで各メニューにお気に入り機能のようなものを実装しています。以前のページでは一方的で、今回リニューアルにあたって少しだけ閲覧者の方からリアクションを貰える仕組みを追加出来たらうれしいなと思ったのがきっかけです。
コードの一部分はこんな感じです。
template 部分は<v-card> <v-img :src="rcard.src" class="white--text align-end" height="300px"></v-img> <v-list-item two-line> <v-list-item-content> <v-list-item-title class="subtitle">{{ rcard.title }}</v-list-item-title> <span>{{ rcard.content }}</span> </v-list-item-content> </v-list-item> <v-card-actions v-if="rcard.isFavorite"> <v-alert color="pink lighten-4" icon="mdi-emoticon-happy">Thanks!</v-alert> <v-spacer></v-spacer> <v-btn icon> <v-icon>mdi-heart</v-icon> {{ favs[index].count }} </v-btn> <v-btn icon @click="twitterShare()"> <v-icon>mdi-twitter</v-icon> </v-btn> </v-card-actions> <v-card-actions v-else> <v-spacer></v-spacer> <v-btn icon @click="addFavoriteRamen(index, rcard.id, rcard)"> <v-icon>mdi-heart-outline</v-icon> {{ favs[index].count }} </v-btn> <v-btn icon @click="twitterShare()"> <v-icon>mdi-twitter</v-icon> </v-btn> </v-card-actions> </v-card>script 部分は
addFavoriteRamen: function(index, id, shina) { var favRef = firebase .firestore() .collection("favorite") .doc(id); var count = this.favs[index].count; favRef.set({ title: shina.title, src: shina.src, content: shina.content, count: (count += 1) }); this.ramencards[index].isFavorite = true; }よくあるお気に入り機能のようにもう一度押したら解除できるように最初はしていたのですが、短時間に何度もデータを書き込まれてしまうことを懸念してこのような仕様にしました。
(※防ぐ良い方法が思いつかなかった・・・ご教授いただければ幸いです。)おいしそう!と感じたものがありましたらぜひクリックしてみてください。
ドメインの接続、リダイレクト設定
Firebase、 エックスサーバ、 お名前ドットコムの設定部分ですが結構躓きました・・・
時間あるときに記録しておきたいと思います。
おわりに
最後まで見てくださりありがとうございました!
本当においしいラーメン屋さんなのでぜひ一度行ってみてください。
Web サイトも随時更新予定なので見て頂けたらうれしいです。こちらも
JobQuest ジョブマッチングSNS風サイト
https://jquest.jp/Vue.js + Firebaseで作ってます。宜しければご覧ください!
- 投稿日:2019-11-15T10:36:43+09:00
JSXを使わずにJS内にHTMLを記述する
結論
IE以外はテンプレートリテラル
IEは無名関数 + コメントアウト
を使用する※htsignさんにコメント頂いた内容を更新しました。ありがとうございます。
IE以外(ES6対応ブラウザ)
getHtmlTemplete.jslet insert_html = ` <div class="greeting"> Hello World! </div> `; document.getElementById("insert_target").innerHTML = insert_html;IE
getHtmlTemplete.jslet insert_html = function(){/* <div class="greeting"> Hello World! </div> */}).toString().match(/\/\*([^]*)\*\//)[1]; document.getElementById("insert_target").innerHTML = insert_html;
- 投稿日:2019-11-15T10:10:04+09:00
JSでconsole.logの出力をしない
コンソール出力を表示しないメモ
var DEBUG_MODE = false; if(!DEBUG_MODE) { window.console = {}; window.console.log = function(i){return;}; window.console.time = function(i){return;}; window.console.timeEnd = function(i){return;}; }
- 投稿日:2019-11-15T09:47:02+09:00
OpenplayerJSの使用法
はじめに
こんにちは streampack チームのメディです。
https://cloudpack.jp/service/option/streampack.htmlIn my opinion OpenPlayerJS is a really interesting player and I would like to show you common use cases. OpenPlayerJS is simple and easy to use.
Plyr は興味深いプレイヤーなので、一般的な使用法を説明します。Plyrはシンプルで使いやすいです。Copyrights of videos
Big Buck Bunny
© copyright 2008, Blender Foundation | www.bigbuckbunny.org
Sintel
© copyright Blender Foundation | www.sintel.org
Tears of Steel
© copyright Blender Foundation | https://mango.blender.org/What is OpenPlayerJS ・OpenPlayerJSとは
OpenPlayerJS is an open sources JavasScript media player for the web.
OpenPlayerJS はウェブ用のオープンソースJavascriptメディアプレーヤーです。Objective・目的
Learning how to use OpenPlayerJS through simple examples.
OpenPlayerJSの簡単な例を学ぶこと。Simple example・ 簡単な実装
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.css"> </head> <body> <video class="op-player op-player__media" id="video" controls playsinline> <source src="//commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" type="video/mp4"></video> <script src="//cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.js"></script> </body> </html>Demo・デモ
https://codepen.io/mr1985/pen/OJLeZJP
Alternative way ・別の方法
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.css"> </head> <body> <script src="//cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.js"></script> <video class="op-player op-player__media" id="video" controls playsinline> <source src="//commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" type="video/mp4"> </video> </body> <script> var player = new OpenPlayer('video'); player.init(); player.getElement().addEventListener('play', function() { console.log('play'); }); </script> </html>Demo ・デモ
https://codepen.io/mr1985/pen/gOYNzbY
HLS
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.css"> </head> <body> <script src="//cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.js"></script> <video class="op-player op-player__media" id="video" controls playsinline> <source src="//bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8" type="application/x-mpegURL"> </video> </body> <script> var player = new OpenPlayer('video', false, // Fullscreen by default { //Other options hls: { // all HLS options available at https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning. debug:true }, }); player.init(); </script> </html>Demo ・デモ
https://codepen.io/mr1985/pen/qBWzYEJ
MPEG Dash
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.css"> </head> <body> <script src="//cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.js"></script> <!-- TODO add dash js --> <video class="op-player op-player__media" id="video" controls playsinline> <source src="//amssamples.streaming.mediaservices.windows.net/683f7e47-bd83-4427-b0a3-26a6c4547782/BigBuckBunny.ism/manifest(format=mpd-time-csf)" type="application/dash+xml"> </video> </body> </html> <script> var player = new OpenPlayer('video', false, // Fullscreen by default { dash:{ //Dash options here } } ); player.init(); </script> </html>Demo ・デモ
https://codepen.io/mr1985/pen/ExYBLjL
Subtitles・字幕
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.css"> </head> <body> <script src="//cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.js"></script> <video class="op-player op-player__media" id="video" controls playsinline crossorigin="anonymous" width="640" height="360" src="//iandevlin.github.io/mdn/video-player-with-captions/video/sintel-short.webm"> <track src="//iandevlin.github.io/mdn/video-player-with-captions/subtitles/vtt/sintel-en.vtt" kind="subtitles" srclang="en" label="English"> <track src="//iandevlin.github.io/mdn/video-player-with-captions/subtitles/vtt/sintel-es.vtt" kind="subtitles" srclang="es" label="Spanish"> <track src="//iandevlin.github.io/mdn/video-player-with-captions/subtitles/vtt/sintel-de.vtt" kind="subtitles" srclang="de" label="German"> </video> </body> <script> var player = new OpenPlayer('video'); player.init(); player.getElement().addEventListener('play', function() { console.log('play'); }); </script> </html>Demo・デモ
https://codepen.io/mr1985/pen/NWKZMbb
Google IMA ads
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.css"> </head> <body> <script src="//cdn.jsdelivr.net/npm/openplayerjs@1.12.1/dist/openplayer.min.js"></script> <video class="op-player op-player__media" id="video" controls playsinline> <source src="//commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" type="video/mp4"> </video> </body> <script> var player = new OpenPlayer('video','https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpod&cmsid=496&vid=short_onecue&correlator='); player.init(); player.getElement().addEventListener('play', function() { console.log('play'); }); <script/>Demo・デモ
https://codepen.io/mr1985/pen/qBWerby
Information sources ・ 情報源
https://github.com/openplayerjs/openplayerjs
https://www.openplayerjs.com/
https://iandevlin.github.io
- 投稿日:2019-11-15T02:11:20+09:00
【初心者】JavaScript コールバック関数/JSのES5/復習・練習【備忘録15】
11/12~11/14に勉強したこと
コールバック関数
引数に渡される関数のことをコールバック関数という。
【例】
const petDog=()=>{ console.log("ココちゃん"); }; const call=(callback)=>{ console.log("コールバック関数を呼び出します"); callback(); }; //コールバック関数を呼び出すときは「();」が必要。 call(petDog); //コールバック関数を渡すときは「()」は付けない!【出力】
コールバック関数を呼び出します。
ココちゃん直接、引数の中で関数を定義する場合
【例】
const call=(callback)=>{ console.log("コールバック関数を呼び出します"); callback(); }; call(()=>{ console.log("ココちゃん"); });【出力】
ココちゃんJSのES5
jQueryに入る前に、ES6だけでなく元々使用されていたES5も学習
しておこうと思い、一巡した。- 変数がletでなくvarで書かれていた。
- {name:"犬",color:"brown"};と表現していた分は、オブジェクトではなく
【連想配列】と呼ばれており表記についてもnameの部分にも[""]が付いていた。
- 上記をの名前のみを出力させる場合、ES6では【console.log(animal.name);】のみ
学習していたが、ES5では【console.log(animal["name"]);】とも書けるということを学習した。JSの復習において、学んだ点。
BMIを求める記述
下記のような、BMIを求める例題でBMIを求める式を
コンソールの中でしてしまったので、気を付ける。
後にBMIの数値を使う際に面倒になるので、まず定義してから使用する。
【私の書いた例】⇒ここでは18.5未満:痩せ気味/25未満:普通/それ以上:肥満var height = 1.7; var weight = 60; console.log("BMIは"+ weight/height/height +"です"); var bmi=20.761245674740486; if(bmi<18.5){ console.log("痩せ気味です"); }else if(bmi>=18.5 && bmi<25){ console.log("普通です"); }else{ console.log("肥満気味です"); }※こうしてしまうと、BMIの数値を手打ちしなければならなくなり、手間・時間がかかった。
【先に数値を定義してから書いた例】
var height = 1.7; var weight = 60; var bmi = weight / height / height; console.log("BMIは" + bmi + "です"); if (bmi < 18.5) { console.log("痩せ気味です"); } else if (bmi < 25) { console.log("普通です"); } else { console.log("肥満気味です"); }※先にBMIの数値を出しておくことによって、「変数bmi」として他で書くときも
使えるので、まず数値を出しておくことを癖づける。FizzBuzz
1から100まで繰り返し出力する中で、「3で割り切れる際はFizz」
「5で割り切れる際はBuzz」「3でも5でも割り切れる際はFizzBuzz」
「それ以外の時は数字」を出力という問題。for(var i = 0 ; i <= 100 ; i ++){ if(i % 3 ===0 && i % 5 ===0){ console.log("FizzBuzz"); }else if(i % 5 ===0){ console.log("Buzz"); }else if(i % 3 ===0){ console.log("Fizz"); }else{ console.log(i); } }※はじめに書いた際に、一番初めの条件式に(i % 3 ===0)と書いてしまったので
うまく出力ができなかった。「3かつ5で割り切れる」という一番
条件として細かいものを一番はじめに持ってくる。平均点
スコアがずらっと並べられており、その平均点を出す記述。
まず、全スコアの合計を繰り返しを使用して出力。
そのあと 合計 / スコア項目数で割り出す。var scores=[88, 62, 65, 21, 47, 92, 57, 89, 79, 89, 54, 82, 69, 72, 74, 54, 66, 92, 64, 96, 47, 89, 95, 93, 70, 30, 84, 93, 81, 98, 78, 96, 32, 56, 64, 42, 67]; var sum = 0; for(var i = 0 ; i <scores.length ; i++){ sum += scores[i]; // ↑ 0に繰り返しスコアの項目を追加していく。(sum= sum + scores[i];) } var average= sum / scores.length; console.log(`平均点は${average}点です。`);最高点
先ほどの章で求めた平均点を求めた後、最高点を出す方法。
var scores = [88, 62, 65, 21, 47, 92, 57, 89, 79, 89, 54, 82, 69, 72, 74, 54, 66, 92, 64, 96, 47, 89, 95, 93, 70, 30, 84, 93, 81, 98, 78, 96, 32, 56, 64, 42, 67]; // この下にコードを書いてください var bestScore = 0; var sum = 0; for(var i = 0 ; i <scores.length ; i++){ sum += scores[i]; if(scores[i]>bestScore){ bestScore=scores[i]; //↑次々スコアを代入していくイメージ! } } var average= sum / scores.length; console.log(`平均点は${average}点です`); console.log(`最高点は${bestScore}点です`);【気を付けること】
bestScore = scores[i];の代入部分が慣れない表記なのでしっかり覚える。情報を順に出力
連想配列の情報を順に出力していく書き方。
var users = [ { name: '太郎', age: 21, nationality: '日本', }, { name: 'リリー', age: 37, nationality: 'イギリス', }, { name: 'ジョン', age: 16, nationality: 'アメリカ', }, ]; // この下にコードを書いてください for(var i =0;i<users.length;i++){ var number=i+1; console.log(`${number}人目`); console.log(`名前:${users[i].name}`); console.log(`年齢:${users[i].age}`); console.log(`国籍:${users[i].nationality}`); }【出力】
1人目
名前:太郎
年齢:21
国籍:日本
2人目...という風に順に表示ができる。【気を付ける】
numberの表示は「i」だけにしてしまうと「0人目」の表記から始まるので気を付ける。振り返り
実際練習問題で応用として考えると、少しずつ考えていかないとまだ
わからなかったり、bestScoreの最高点の書き方に関しては「代入!」という
イメージをしっかりできていなかった(=をイコールと捉えると頭がこんがらがる!)ある程度型になっているものがあるので、そこはしっかり慣れていく。
- 投稿日:2019-11-15T01:55:27+09:00
アニメーションライブラリ Lottie
Lottie(ロッティー) は、Airbnb 社が開発したアニメーションライブラリで、2017年2月頃に発表されたものです。
これがとても面白かったので、メモとして残します。Lottie の特徴
- Lottie で使用するデータは、Bodymovin というオープンソースライブラリで作成します。
- 作成したアニメーションデータを Adobe After Effects で json 形式に変換し、javascript 内で指定の形式で記述すれば、そのアニメーションを表示できます。
- 通常の html, css, javascript でプログラミングした場合よりも、デザイナーの方が考えたアニメーションを再現できます。
Lottie を使用するメリット
アニメーションデータが json 形式なので、画像や動画などの多数の、容量が大きなデータを保持する必要がなくなります。
なので、システム上のディスク使用率をかなり節約できます。Lottie を使用するデメリット
Lottie の Web サイトのサポート機能一覧にあるように、現状実現できない動作はあるようです。
Lottie でできないこと
事前に作成したアニメーションデータを json 形式に変換しているので、実行速度を可変にすることはできないようです。
環境の構成方法
Adobe After Effects を導入します。
ZXP Installter をダウンロードします。
Bodymovin をダウンロードし、ZXP Installter を使用してインストールします。
アニメーションデータ作成方法
アニメーションの元となる素材を作成します。
Adobe After Effects でアニメーションを作成します。
Adobe After Effects で Bodymovin プラグインを立ち上げます。
2 で作成したデータを json 形式のデータに変換します。
実装方法
使用するファイルは次の 3 つです。
- index.html
- main.js
- animation.json
index.html<html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.5.9/lottie.js" type="text/javascript"></script> </head> <body> <div id="lottie"></div> <script src="main.js" type="text/javascript"></script> </body> </html>lottie の js ファイルの URLを参考に、lottie.js を読み込んでいます。
ちなみに、Github にファイルもあります。main.js(アニメーション設定)var ani = lottie.loadAnimation({ container: document.getElementById("lottie"), renderer: "svg", loop: true, autoplay: true, path: "animation.json" });animation.json については、多数のサンプルがある LottieFiles からお借りしました。
注意事項
Chrome ではローカルのファイル読み込みでエラーが出力されますので、Chrome のアイコンを右クリック->プロパティから、リンク先に記載のプログラム名の後に以下を記載するとローカルで読み込み可能になります。
--user-data-dir="<ファイルが配置されているフォルダ(フルパス)>" --disable-web-security所感
アニメーションが小容量のデータで実現できるので、PWA (Progressive Web Apps) との親和性もあると思いました。
オフラインでも問題なく実行できそうな気がします。参考
Lottie の使い方等について、参考にさせていただきました。
- 投稿日:2019-11-15T00:27:47+09:00
window.showModalDialogを移行した話
ようやく移行しました
Google Chromeからwindow.showModalDialogが使えなくなって久しいのですが、
アプリ利用ユーザの標準ブラウザがIEになっているので長らく棚上げしていました。ChromeからshowModalDialogが完全になくなったのが2015年5月、FireFoxからも2017年9月になくなり、OperaはとっくにChromiumでEdgeもChromiumベースになろうととしていて、Callbackベースで移行したいなと考えていて方法を思いつきました。
今まで私のshowModalDialogの使い方と、それを踏襲した実装方法を順を追って書いていきます。
今までのshowModalDailogの使いかた
子画面から値を返すのは例えば
window.opener.document.main.elementId.value = 'xxx';などと書いたら戻せるのですが、子画面側が値をセットするエレメントIDを知る必要があり子画面の汎用性がなくなるため、window.returnValue を使う方法を使ってました。
window.returnValue を使えば予め必要となる値をJSONにして返し、呼び出し元が必要な値をJSONから引き出して使えばよく親画面、子画面を疎結合にできます。親画面(parent.html)
<input id="cd"> <input id="name"> <input id="dept"> <input type="button" value="社員" onclick=" var json = window.showModalDialog('child.html'); if(json) { //子画面が×で閉じられたらnull document.getElementById('cd').value = json.cd; document.getElementById('name').value = json.name; document.getElementById('dept').value = json.dept; //他にも age,email,tel とかjsonに入ってる somefunc(); //値が選ばれたタイミングでやりたいことがあれば書く } "/>子画面(child.html)
<input type="button" value="山田さんを選択" onclick=" window.returnValue = { cd: '22102', name: '山田太郎', kana: 'ヤマダタロウ', dept: '総務', age: '26', tel: '09011112222', email: 'yamada@dome.com', position: '一般' //必要そうな値を予めJSONに詰めて返す }; window.close(); "/>代替案 window.open を使った callbackの実装
コードを見ての通りこれまでのやり方のいいところは、子画面で値が選ばれたタイミングでやりたい処理が行えることです。モーダルにして他の処理をブロックしたいわけではないのですが、子画面で値を選んで自画面で必要な処理を行う一連の処理を、同期的に記述できるが魅力でした。それに、他の画面からも child.html を変更することなく呼び出せます。
window.showModalDialog が使えないなか子画面で値選択のタイミングで処理をかけるには 指定したCallback を呼び出してほしい。しかし、子画面はどのコールバックを呼んだらいいかわからないし、コールバックに命名規則を設けると parent.html に複数コールバックを持てなくなる(入力欄ごとに子画面がある場合など変数名が重複する可能性あり)のです。
そこで「値選んだらこのコールバックを呼んでね」を window.open の第2引数 windowName を使って渡したのがこんな実装です
親画面(parent.html)
<input id="cd"> <input id="name"> <input id="dept"> <input type="button" value="社員" onclick=" var callback_id = 'callback_xxxx'; //適当にIDをふる window[callback_id] = function(json) { //windowにコールバックを登録 document.getElementById('cd').value = json.cd; document.getElementById('name').value = json.name; document.getElementById('dept').value = json.dept; callfunc(); //値が選ばれたタイミングでやりたいことがあれば書く } //第2引数でcallbackのID名を渡す。子画面側では window.name として取得できる。 window.open('child.html', callback_id, 'width=700,height=500,statusbar=yes'); "/>子画面(child.html)
<input type="button" value="山田さんを選択" onclick=" var callback_id = window.name; //window.nameに第2引数で指定した値が入っている var json = { cd: '22102', name: '山田太郎', kana: 'ヤマダタロウ', dept: '総務', age: '26', tel: '09011112222', email: 'yamada@dome.com', position: '一般' //必要そうな値を予めJSONに詰めて返す }; window.opener[callback_id](json); //指定されていたコールバックを呼び出す window.close(); "/>これで無事、子画面にコールバック関数を指定でき、値を汎用的に戻すことができました。
Chrome, IE10, IE11, Edge, Safari, FireFox で動作OKでした。まとめ
- コールバック関数を親画面のwindowオブジェクト(など)に登録。
- window.open の第2引数でコールバック関数名を渡す。
- 子画面はwindow.nameでコールバック関数名を受け取る。
- 子画面はwindow.opener[window.name](xxx)で親画面のコールバックを呼んで値を返す。
この方法の欠点は、他の操作をブロックしないので本当の意味ではモーダルではない点ですが、逆に子画面を開きながら別の操作もできるのでメリットは大きいです。
多少冗長ですが、子画面Open と 子画面Close 処理を関数にまとめてしまえば問題もないでしょう。もし同じような実装をしていて、Chromeに対応させたい方がおられたら参考にして下さい。
- 投稿日:2019-11-15T00:24:18+09:00
一週間で遊戯王風ARカードゲームを作ってみた
AR.jsを使って遊戯王カード(アニメ版)ぽいゲームを作ってみた
初めてQiitaに投稿いたします。
10月からG's Academyというプログラミングスクールエンジニア養成学校に通っており、入学一ヶ月の成果としてARカードゲームを作りました。
昔からの夢が実現できて嬉しかったのでみんなにも見て貰いたいです。お前は誰だ
前職は大手SIerで保守運用SEをやってました。
元々IT技術が好きだったのですが、SIerの管理メインの業務に違和感を持って先月晴れて退職をキメました。ビバ無職プログラミングは独学でdotinstallなどを少しだけ触ってましたが、本格的に始めたのは一ヶ月ほど前にG's Academyに入学してからです。
ちなみに前職では障害対応を通じてLinuxコマンドとSQLを叩きまくる経験を手に入れました。作ったもの
これです。
制作期間は約一週間です。G’s Academyというエンジニア養成学校に通って一ヶ月目の課題で遊戯王風ARカードゲームを作りました。
— Sugiemon@SIerを退職してエンジニア修行 (@ySugiemon) November 14, 2019
小学校の頃からの夢が一つ叶いました。
見やすくするためにフィールドカードは隠してます。
※音が出ますのでご注意。 pic.twitter.com/mhn2MxTAOcフィールドカードをセットするとデュエルスタート
それぞれのマーカーを読み込ませてモンスター召喚、ターン変更、バトルを実行します。
先にモンスターを2体倒すと勝利です。やったー!こだわりポイント
モンスターの攻撃モーションの多彩さ
この後出てきますが、全て
a-entity
タグにanimation
属性を付けたり外したりして実装してます。
setTimeout地獄になっています。もっとスマートにコードを書きたい。状態管理
1ターンに行動(召喚・攻撃)できるのは一度までで、それを超えるとメッセージが出るようにしています。
使った技術・サービス
ここからは実装についてのお話
A-Frame / AR.js
game.html<!doctype HTML> <html> <head> <title>AR Card Game</title> <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script> <script src="https://cdn.rawgit.com/jeromeetienne/AR.js/1.7.8/aframe/build/aframe-ar.js"></script> </head> <body style='margin : 0px; overflow: hidden;'> <a-scene embedded arjs="patternRatio:0.9;"> <a-entity camera></a-entity> </a-scene> <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> <script src="js/game.js"></script> </body> </html>A-Frameの後にAR.jsを読み込ませるだけでページ起動時にカメラが起動してマーカーを読み取るようになります。
DOMの操作は全てJavaScriptに寄せているため、HTMLはこれだけです。
基本的な使い方はこちらの記事を参考にしました。
A-FrameとAR.jsで超簡単AR(PC・スマホ・マルチマーカー対応)game.jslet monsters = [{ name:'plesiomonster', atk:2200, hp:2000, dead:0, },{ name:'dimonstrodon', atk:2800, hp:2800, dead:0, }] $.each(monsters,(i,v)=>{ let $monster = $('<a-marker class="monster" '+ 'id="'+v.name+'" '+ 'type="pattern" '+ 'url="marker/pattern/pattern-'+v.name+'.patt" '+ 'registerd-events>'+ '<a-entity gltf-model="model/'+v.name+'.gltf" position="0 1 0">'+ '</a-entity>'+ '</a-marker>'); $('a-scene').append($monster); } AFRAME.registerComponent("registerd-events", { init : function() { let marker = this.el; marker.addEventListener("markerFound", function() { if($(marker).attr('class') == 'monster'){ // モンスター召喚 } } } }一部抜粋
a-marker
タグにカスタムのpattファイルを指定して認識させ、その中にa-entity
を置いてオブジェクトを読み込んでいます。
registerd-events
を記載しておくとmarkerFound
やmarkerLost
などのイベントを拾うことができるので、モンスターの召喚の他にもフィールドカードやBattleカードのマーカーを作って、markerFound
した時にそれに応じた処理を実行しています。オブジェクトの動きは
animation
属性を追加して実装しています。
例えばモンスター召喚時の迫り上がってくる動きは以下ようになってます。
地道な作業でした。game.js$('#'+marker.id).children('a-entity').attr('animation','property:position;from:0 -1 0;to:0 1 0;dur:2000;easing:linear;');カスタムマーカーの作成は以下の記事を参考にAR.js Marker Trainingを使いました。
「AR.js」でオリジナルのマーカーを設定する方法google Poly / Blender
もしアプリケーションを公開したい場合はアセットのライセンスを必ず確認しましょう。
Polyのアセットの多くはCC-BYライセンスで公開されています。
Polyから取得したオブジェクトはサイズや向きがバラバラなことが多いです。
最初はa-entity
タグ内でscale
やrotation
を調整していたのですが、最終的にはBlenderでオブジェクト自体のサイズを調整しました。Blenderの使い方にけっこう戸惑いましたが、こっちの方が後々楽ですもんね。
感想
この他にも担任の先生を最強モンスターとして登場させたりして、クラスのメンバーからはかなり好評をいただきました。
webGLに初めて触れたのが2週間ほど前でしたが、A-Frame、AR.jsはHTMLとJavaScriptの基礎知識があれば追加の学習はごく僅かで済むので、非常に効率的に開発が進められると思います。
Webの世界では当たり前なのかもしれませんが、こんなものが無料で使えるなんて凄すぎますね。個人的には2018年のスピルバーグ監督映画の「Ready Player One」のような世界を実現できたら本当に面白そうだなと思っているので、引き続きXR系の技術で色々作っていきたいです。
- 投稿日:2019-11-15T00:12:14+09:00
constっぽい処理を書いた。
先日やたらレガシーな案件で、スポットのお手伝いとしてJSを書きました。
その時constの代わりが欲しくなって変わりの処理を書いたので一応メモ書き。/** GlobalRegistor CLASS */ (function () { "use strict"; // constructor function GlobalRegistor () {} // static accessor Object.defineProperty(GlobalRegistor, 'global', { /** * @return {Object} global object */ get: function() { return new Function('return this')(); } }); // static Object.defineProperty(GlobalRegistor, 'readOnly', { writable: false, /** * @param {String} name * @param {Any} value * @return {Void} */ value: (function readOnly (name, value) { if (GlobalRegistor.global[name] === void 0) { Object.defineProperty(GlobalRegistor.global, name, { writable: false, value: value }); } else { throw new Error(name + ' is Declare Property'); } }) }); GlobalRegistor.readOnly('GlobalRegistor', GlobalRegistor); })();使い方
.jsGlobalRegistor.readOnly('globalModules', {}); window.globalModules.fn = function () { console.log('success!'); }; window.globalModules = null; window.globalModules.fn(); // success! widnow.globalModules.foo = 1; widnow.globalModules.foo = 2; console.log(widnow.globalModules.foo); // 2
- 投稿日:2019-11-15T00:12:14+09:00
constっぽい処理。
先日やたらレガシーな案件で、スポットのお手伝いとしてJSを書きました。
その時constの代わりが欲しくなって変わりの処理を書いたので一応メモ書き。/** GlobalRegistor CLASS */ (function () { "use strict"; // constructor function GlobalRegistor () {} // static accessor Object.defineProperty(GlobalRegistor, 'global', { /** * @return {Object} global object */ get: function() { return new Function('return this')(); } }); // static Object.defineProperty(GlobalRegistor, 'readOnly', { writable: false, /** * @param {String} name * @param {Any} value * @return {Void} */ value: (function readOnly (name, value) { if (GlobalRegistor.global[name] === void 0) { Object.defineProperty(GlobalRegistor.global, name, { writable: false, value: value }); } else { throw new Error(name + ' is Declare Property'); } }) }); GlobalRegistor.readOnly('GlobalRegistor', GlobalRegistor); })();使い方
.jsGlobalRegistor.readOnly('globalModules', {}); window.globalModules.fn = function () { console.log('success!'); }; window.globalModules = null; window.globalModules.fn(); // success! widnow.globalModules.foo = 1; widnow.globalModules.foo = 2; console.log(widnow.globalModules.foo); // 2 GlobalRegistor.readOnly('foo', 1); console.log(widnow.foo); // 1 widnow.foo = 999; console.log(widnow.foo); // 1 GlobalRegistor.readOnly('foo', 999); // error