20191115のJavaScriptに関する記事は21件です。

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.fugahoge["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 ms

2. 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 ms

3. 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 ms

4. 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 ms

u=(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 ms

7. 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 ms

8. 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 ms

9. 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 ms

10. 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


  1. 変更履歴によれば2019/05/23のことである. 

  2. @drken さんによる記事 AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~ に提示された初心者向け問題を集めた10問.言語を触って遊んでみるのに丁度いい難易度の問題が揃っています.現在はAtCoder Beginners Selectionとして問題セットがまとめられています. 

  3. "filter"は(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]],"constructor"は([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]となる. 

  4. Qiita上で確認できる研究ではこちらのほうが優勢のようですが,なぜWikipediaには前者が載る事になったのかは不明です. 

  5. |>演算子が導入されれば夢の5文字「[,],+,|,>」が可能になるそう. 

  6. 参考:https://qiita.com/ytanto/items/caf7bf0ba287da81b20f 

  7. 早速「(ほぼ)どんなコードでも実行できる」の反例が出てしまいました. 

  8. 参考:https://qiita.com/alucky0707/items/46f8a3e0c523d3bd4034 

  9. 参考:https://qiita.com/kotatsugame/items/2619398606b30aaf097b#%E7%AC%AC9%E5%95%8Fabc049c---%E7%99%BD%E6%98%BC%E5%A4%A2--daydream 

  10. ところでJavaScriptで競プロを解くこと自体慣れていないのでより良い処理方法がありましたらご教授いただけると幸いです. 

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

YYTypeScript#9「TS初心者がハマりやすいところ教えて 」「執筆中の『マンガでわかるTypeScript』�に意見欲しい! 」「英語で書かれている有益なサイトある? 」「EventListenerとIndexedDBの型周りについて」「eBookを共同編集する上でのベストプラクティス」

これは2019年11月15日に開催したTypeScriptイベントYYTypeScript#9のイベントレポートです。

YYTypeScriptは一言で「TypeScripterの部室」です。発表者の話を聞く「一方向的な勉強会」とは真逆で、TypeScriptについて、雑に・ゆるく・ワイワイ話しながらTypeScripter同士の交流を深める「双方向的な座談会」の形式になります。集まった人たちで「今日話たいこと」「聞きたいこと」をいくつか挙げていき、それをテーマに雑談していきます。

今回の配信動画

過去回の配信動画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でつくってみようみたいなレポジトリない?
    • JavaとかPHPとは型システムがぜんぜん違うところ
      • JavaとかPHPは型が Nominal Type
      • TSは Structural Type
      • 型合成
      • 公称型(Nominal)のJavaに対して、構造型(Structural)のTypeScript
    • TSはチェックツールの役割
      • JSに戻した時の実行時のことも考えて書かないといけない。
      • JSを書いてることを忘れるとよくない。
    • JSになるので実際の実装と型定義とが違っている場合がある
    • 型のテストもある
    • 外からくる値はちゃんとチェックしないといけない。(TSは特に)

「マンガでわかるTypeScript」のプロットを見て!

湊川さんが進められたら進めたいと思っているWeb連載。

・・・

  • マンガでわかるTypeScript 1話プロット - mwt

  • TSが生まれた背景

  • TSとJSの比較

  • TSの環境構築

  • 2話からはinterfaceとclassに行きたい

  • TS使いたい人ってどういう人なんですかね?

    • やっぱりフロントエンドじゃないですかね
      • Reactで型つけてやりたいとか
  • JavaScriptの存在を隠して、TSを教えるアプローチ

    • JSから教えると、JSでいいじゃんってなりそうだなぁと
  • 「とりあえず書いてみよう」のツールとして、playgroundの案内が有っても良いかもと思いました

  • 書けるようにするのではなく、TSの良さを伝えることにフォーカスしたほうがいいかも

    • 使ってみる意義がわからない、という人も多いので

最近英語の本を読む機会が増えてきたが、英語で書かれているプログラミング言語の有益なサイトがあったら教えてほしい

Event listenerの型をTSでどうやって書いたらいいか

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?をみんなで書きたいが、どういうふうに進めるのがいいのか?
共同編集のベストプラクティスが知りたい。

...

GraphQLのクライアントライブラリ、apolloが定番なのですかね?

  • TSでどうなの?
    • わからないのでやってみまーす!

開始前の雑談

Scrapbox

  • e-book、Scrapboxで書く「Scrapbook」という考え方もありますよ。
    • Scrapboxなら共同編集は簡単です。
    • ちらっとしか使ったことないですが、良さそうだなと思ってました
    • マンガでわかるScrapbox

自動運転の事故

  • 自動運転、最近事故があったようですね。
  • 自動運転は責任の範囲が難しそう。
  • 自動運転についてのマンガを書きました。

参加してよかったこと(参加者の感想)

  • 自分にない知見が得られる。知らないパッケージの情報を得られる
  • 毎回自分が知らない分野の情報も得られて、ためになってます。
  • 99$読み放題はとても有益な情報でした
  • 参考になるリポジトリやツールを教えてもらえたこと、みなさん親切で優しいこと
  • 突然のコード貼り付けでも,ささっとレビューしていただいてとてもありがたかったですm(_ _)m

YYTypeScriptは毎週やってます

YYTypeScriptについてワイワイ話したい方は、YYTypeScriptのイベント情報をチェックしてみて下さい。

以上、YYTypeScriptのレポートでした。次回もワイワイやっていきたいと思います! では、また来週!

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

年末まで毎日webサイトを作り続ける大学生 〜28日目 スライダーを自作する〜

はじめに

こんにちは!最近週末は温泉に通っている@70days_jsです!
今日はスライダーを自作しました。hover時に画像をうっすら白くするところ以外は、何も参考にせず0から作ってみました。
扱う技術レベルは低いですが、同じように悩んでる初心者の方を勇気付けられれば幸いです。
今日は28日目。(2019/11/15)
よろしくお願いします。

サイトURL

やったこと

スライダーを作ってみました。
矢印ボタンか、サムネイルをクリックすれば画像が変わります。
まずはgifでどうぞ↓
test.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サイトを作れるようになっていたら嬉しいな・・・。

最後までお読みいただきありがとうございます。明日も投稿しますのでよろしくお願い致します。

参考

  1. マウスが載ったときだけ画像を半透明にするCSSの書き方 - スタイルシートTipsふぁくとりー (https://www.nishishi.com/css/translucent-image-opacity.html)

hover時の挙動を参考にさせていただきました。ありがとうございます!

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

【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はファイル先頭のクラスになります。
そして移動させたい位置から全体の位置を引くとジャストで希望の位置が取得できます。
これをアニメーションさせてやれば読み込みと同時にアニメーションされます。

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

reactNative

reactNative

image.png

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/78b3295ff3f37969ab50

React 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を学ぶという選択肢もあります。これは興味深いテクノロジーですが、いくつか警告があります。

  1. すべての開発者は、強い型付けの、コンパイラ型の、オブジェクト指向言語を習得する必要があります。そしてJava /Objective-C / Swiftはいずれもこれに最適な選択です。あなたが望むかどうかにかかわらず、いずれとにかくJavaScriptは学ぶことになります。

  2. React Nativeは、AppleとGoogleのどちらかにも正式にサポートされていません。これはつまり、OSやAndroidから新しいアナウンスメントが公表されても、React Nativeで完全には動作しない可能性があることを意味します。たとえば、iOS 10iMessageアプリの新機能が追加されたとアナウンスされた時点では、React Nativeによってそのようなアプリのコードを書く方法はなかったと思います。こういうケースでは、iOSのネイティブアプリの開発方法を習得している必要があるのです。また現在でも、React Nativeを使ってApple Watchアプリを開発できるかどうかさえ定かではありませんが、ネイティブ開発を学んでおけば何の問題もないでしょう。

  3. 第三に、プロジェクトの寿命を念頭に置く必要があります。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);
```

image.png

・入力項目あり

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);

image.png

最後に

ネイティブ開発はまだまだ成熟していない技術て、日に日に進歩をしている元気な技術です
android もjava → kotlin が公式言語(google曰く)になったし
ios も objective-c → swift に変更になり言語も技術もどんどん進化しています

現在ほとんどのアプリでandroid ios両方でもリリースが当たり前になってきています。
両方の端末で開発をするとなると技術者不足や工数不足、お金の不足など沢山の問題にぶち当たります
そのため、クロスプラットフォーム技術もどんどん進化して行きます
今回紹介した React Native以外にもkotlin nativeなど似たような技術も存在しています

今後も自分の価値をあげるためにも、こういう技術はどんどん取り入れていきましょう

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

JavaScript(ES6) メソッドと関数の違い

はじめに

エンジニアになる上で、最初の難関は用語だと思う。
用語の意味をしっかりと覚えることで、正しい理解をすることに繋がる。
その最初の一歩として「メソッド」と「関数」についてお話する。

結論から

『メソッド』
クラスから生成されたインスタンスの行える処理がひとまとめにされたもの
『関数』
関数自体が独立して定義でき、処理がひとまとめにされたもの

詳細

※メソッドを理解する前に、把握しておく概念がある、それは『クラス』と『インスタンス』だ。
上記2つの理解が不十分の方は先に理解を深めてから、読むことをオススメする。

【メソッド】

メソッドはインスタンスの「動作」のようなものです。
例えば「計算を行う」、「文字を出力する」といった処理をクラスの中でひとまとめにして表しています。
そのため、メソッドはクラスの中でのみ定義をされるものです。

class Human {
//下記がメソッド
  greet(){
    console.log("こんにちは");
  }
}

//インスタンスの生成
const human = new Human();
//メソッドの呼び出し
human.greet();
//処理結果:こんにちは

【関数】

関数はいくつかの処理をまとめたものです。
処理をまとめたものという点でメソッドと同じですが、違う点は
関数は独立して定義されるのに対し、メソッドはクラスの中で定義されます。

//関数の定義(クラス内ではない)
const greet = ()=>{
  console.log("こんにちは!");
}
//関数の呼び出し
greet();
//処理結果:こんにちは!

まとめ

「共通点」:処理をひとまとめにしている
「相違点」:クラス内で定義されている→メソッド
      独立して定義されている→関数

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

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の他に、whiledo...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の要素を制御する処理などについて解説していこうと思います。

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

配列内の特定条件の値群のみ先頭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なのにまったく外部の値を使うのもちょっと不気味なのかもしれない)

当初想定していたやりかた

「条件に合う値だけの配列を削除して返り値にする破壊的関数」を求めたが、調べてもslicefindなど複数まとめて取得できなかったりfilterは非破壊的だったりと一発では行かず。

集めた配列 + あまった配列 で完成させたかったのだが。

mapなどで破壊しながら新しい配列を作ってconcatするぐらいならfilterを反転してそれぞれ作るほうが綺麗かなと結論コードに。

たいしたコストではないが「あまりものの再利用」ができなかったのが少し不満。

他の配列操作系

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

スマホ用のJavascript/CSSコンソールを作ったお話

スマホ版ブラウザでPC版Chromeコンソールのようなものが(即座に!)使えればいいのになと思ったので、非常に簡易的なものではありますが、作りました。

ブックマークレットのコード

bookmarklet
javascript: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から見るとコードが全部表示されてしまうようですね…。なんてこった…。

q-1-1.png

ブックマークレットの登録方法は以下のページが詳しいですので、参考までに。

ブックマークレットの登録方法:
https://qiita.com/aqril_1132/items/b5f9040ccb8cbc705d04

見た目/できること

q-1-2.pngq-1-3.png
寿司

ブックマークレットを起動すると、上記のようなコンソールが現れます。

各ボタンの概要:

  • 青いボタン
    • 「JS(JavaScript)」モードと「CSS」モードを切り替えます。
  • apply
    • テキストエリア内のコードを適用します。
  • code
    • これまでに適用したコードを全てリザルトに表示します。
  • close
    • コンソールを閉じます。

JS(JavaScript)モード

特に説明することは無いです。PC版Chromeコンソールのようにオブジェクトの中身を覗いたりといったことは出来ず、ただスクリプトをそのページに追加するだけです。

ただしそのスクリプトに問題があったときは、以下のような警告をリザルトに表示します。

q-1-3.png

この時、そのスクリプトの適用はされません。

Tips: オブジェクトの中身を見たい場合は、alertやJSON.stringify()などを駆使すると良いです。

JavaScript
alert(JSON.stringify(<Object>));

CSSモード

CSSを変更できます。シンプルですね。例えばqiita(このサイト)で使用する場合、

CSS
.it-MdContent {
    color: red;
}

このCSSを適用することで、記事の本文を真っ赤に染め上げることができます。

最後に

ブックマークレットに加工する前のコードを以下に載せます。

JavaScript
window.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に対応する&文字数削減のためのものですので、ご了承ください。

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

いまからはじめる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;

上記の例で varlet に変更すると、エラーで失敗します。

varの問題点 その2: 変数の上書き

var を使用するとき、好きなだけ同じ変数を何度でも宣言することができます、しかし let ではできません。

var myName = 'Chris';
var myName = 'Bob';

上記の例で varlet に変更すると、エラーで失敗します。

これらの問題点は潜在的なバグの要因になりかねません。
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: 42

objectのプロパティなどは変更できます。

const obj = {
  number: 42,
};

try {
  obj.number = 99;
} catch(err) {
  console.log(err);
}

console.log(obj.number);
// => 99

JavaScriptでは型がないため、変数にどのような値が格納されるのか制限できません。
変数を定義する際は 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); // => 32

TIPS: 変数の入れ替え

配列の分割代入を使用すると、変数の値の入れ替えが簡単に行えます。

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); // => kimura

TIPS 関数の引数に既定値を設定する

関数の引数にオブジェクトを渡すようにすることで、名前付き引数のような機能を実現できます。
また、既定値を設定することで省略可能な引数を定義できます。

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-appnpm でインストールできます。

$ 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.js
import 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.js
import 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.js
import 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.js
import React from 'react';

function Message() {
  return (
    <p>Original Message.</p>
  );
}

export default Message;

App.jsMessage コンポーネントを配置します。

App.js
import 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.js
import 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.js
import React from 'react';

function Message(props) {
  return (
    <p>Hello, {props.name}!</p>
  );
}

export default Message;

コンポーネントは繰り返し使用できます。

App.js
import 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.js
import 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 メソッドが呼ばれます。
handleTextInputsetName メソッドにテキストボックスの値を渡します。
name の値が更新されると Message が再描画されます。

子から親に値を渡す

フォームをコンポーネント化することを考えてみましょう。
まずは名前を入力するフォームのコンポーネントを作成します。

$ touch src/components/NameForm.js

子から親にデータを渡すためには、親から子にコールバック関数を渡します。
子にて props に渡されたコールバック関数を実行します。

NameForm.js
import 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.js
import 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.js
import 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.js
import 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.js
import 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;

AppTodoForm を追加します。
TodoFormSave ボタンが押されたらその結果を state の配列に追加します。

App.js
import 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.js
import 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.js
import 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.js
import 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 コンポーネントのstate edit を更新するよう実装します。

Todo.js
import 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)}
      />
    );
  }

// ...省略...

ここまで実装できたら、編集モードと通常モードの切り替えができることを確認します。

更新処理を実装する

仕上げに更新処理を実装します。

AppTodo コンポーネントから呼ばれる更新メソッド 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コンポーネントの onSavehandleUpdate を渡します。

Todoコンポーネントでは受け取った onSave をさらに TodoForm に渡しますが、更新が完了したら自身の編集モードを終了するようフラグを更新しておきます。

Todo.js
import 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 だけでなく IDCreatedAt などの値も引き渡すように修正します。
面倒なので受け取った props をすべてTodoデータに展開してしまいます。

TodoForm.js
import 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.js
import 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.js
import 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フック

コンポーネントのデータ管理の基本は stateprops ですが、コンポーネントが多階層になった場合にバケツリレーのごとくデータを渡すのは非常に面倒です。

そういったときに使える機能が Context Hook になります。

例えば、現在の認証済みユーザー・テーマ・優先言語といったデータを共有する場合に有用です。

Contextの使用例

今回はTodoアプリにテーマ選択機能を追加してみます。

App.js
import 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コンポーネントで括ります。
Providervalue に現在の値を渡します。
他コンポーネントは export された ThemeContext を介して value にセットされている値を取得することができます。

Context を参照する側は以下のように実装します。

Todo.js
import 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.js
import 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のテーマが切り替わります。

サンプルが単純なためあまり威力が実感できないかもしれませんが、コンポーネント数が多かったり、孫・ひ孫のコンポーネントに値を渡したい場合に便利な機能です。


参考

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

VScodeを使って簡単にローカルサーバーを立てる

vscode-live-server

VSCodeからボタンひとつでローカルサーバーが立てられる拡張機能です。
https://github.com/ritwickdey/vscode-live-server

良いところ

  1. ボタン一つでお手軽簡単。
  2. hot reloadが有効。

インストール

  1. VSCodeExtensionsからLive Serverで検索
  2. installを押す

使い方

  1. 右下にGo Liveのボタンが増えるので、立ち上げたいhtmlVSCodeで開いてGo Liveを押す
  2. ローカルサーバーが立ち上がる

設定

  • 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

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

とあるラーメン店のWordpressサイトをNuxt.js+Firebaseで作り直した話

お店と Web サイトの紹介

中華そば四つ葉
https://yotsuba628.com/

y_soba2.jpg

ラーメンが好きならご存じの方もいらっしゃるのではないでしょうか。

最寄駅から徒歩 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で作ってます。宜しければご覧ください!

https://qiita.com/em0/items/44bf334575ce28bc6c47

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

JSXを使わずにJS内にHTMLを記述する

結論

IE以外はテンプレートリテラル
IEは無名関数 + コメントアウト
を使用する

※htsignさんにコメント頂いた内容を更新しました。ありがとうございます。

IE以外(ES6対応ブラウザ)

getHtmlTemplete.js
let insert_html = `
 <div class="greeting">
   Hello World!
 </div>
`;

document.getElementById("insert_target").innerHTML = insert_html;

IE

getHtmlTemplete.js
let insert_html = function(){/*
 <div class="greeting">
   Hello World!
 </div>
*/}).toString().match(/\/\*([^]*)\*\//)[1];

document.getElementById("insert_target").innerHTML = insert_html;

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

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;};
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenplayerJSの使用法

Screen Shot 2019-02-19 at 10.30.58.png

はじめに

こんにちは streampack チームのメディです。
https://cloudpack.jp/service/option/streampack.html

In 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・ 簡単な実装

simple_opj2.jpg

<!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

opjs2.jpeg

<!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

opjs3.jpg

<!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・字幕

opjs5.jpg

<!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

Screen Shot 2019-10-02 at 14.37.36.png

<!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

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

【初心者】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の最高点の書き方に関しては「代入!」という
イメージをしっかりできていなかった(=をイコールと捉えると頭がこんがらがる!)

ある程度型になっているものがあるので、そこはしっかり慣れていく。

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

アニメーションライブラリ Lottie

Lottie(ロッティー) は、Airbnb 社が開発したアニメーションライブラリで、2017年2月頃に発表されたものです。
これがとても面白かったので、メモとして残します。

Lottie の特徴

  • Lottie で使用するデータは、Bodymovin というオープンソースライブラリで作成します。
  • 作成したアニメーションデータを Adobe After Effects で json 形式に変換し、javascript 内で指定の形式で記述すれば、そのアニメーションを表示できます。
  • 通常の html, css, javascript でプログラミングした場合よりも、デザイナーの方が考えたアニメーションを再現できます。

Lottie を使用するメリット

アニメーションデータが json 形式なので、画像や動画などの多数の、容量が大きなデータを保持する必要がなくなります。
なので、システム上のディスク使用率をかなり節約できます。

Lottie を使用するデメリット

Lottie の Web サイトのサポート機能一覧にあるように、現状実現できない動作はあるようです。

 参考:Supported Features

Lottie でできないこと

事前に作成したアニメーションデータを json 形式に変換しているので、実行速度を可変にすることはできないようです。

環境の構成方法

  1. Adobe After Effects を導入します。

  2. ZXP Installter をダウンロードします。

  3. Bodymovin をダウンロードし、ZXP Installter を使用してインストールします。

アニメーションデータ作成方法

  1. アニメーションの元となる素材を作成します。

  2. Adobe After Effects でアニメーションを作成します。

  3. Adobe After Effects で Bodymovin プラグインを立ち上げます。

  4. 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

参考:ChromeにてAjaxでローカルファイルにアクセス

所感

アニメーションが小容量のデータで実現できるので、PWA (Progressive Web Apps) との親和性もあると思いました。
オフラインでも問題なく実行できそうな気がします。

参考

Lottie の使い方等について、参考にさせていただきました。

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

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でした。

まとめ

  1. コールバック関数を親画面のwindowオブジェクト(など)に登録。
  2. window.open第2引数でコールバック関数名を渡す
  3. 子画面はwindow.nameでコールバック関数名を受け取る。
  4. 子画面はwindow.opener[window.name](xxx)で親画面のコールバックを呼んで値を返す。

この方法の欠点は、他の操作をブロックしないので本当の意味ではモーダルではない点ですが、逆に子画面を開きながら別の操作もできるのでメリットは大きいです。
多少冗長ですが、子画面Open と 子画面Close 処理を関数にまとめてしまえば問題もないでしょう。

もし同じような実装をしていて、Chromeに対応させたい方がおられたら参考にして下さい。

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

一週間で遊戯王風ARカードゲームを作ってみた

AR.jsを使って遊戯王カード(アニメ版)ぽいゲームを作ってみた

初めてQiitaに投稿いたします。
10月からG's Academyというプログラミングスクールエンジニア養成学校に通っており、入学一ヶ月の成果としてARカードゲームを作りました。
昔からの夢が実現できて嬉しかったのでみんなにも見て貰いたいです。

お前は誰だ

前職は大手SIerで保守運用SEをやってました。
元々IT技術が好きだったのですが、SIerの管理メインの業務に違和感を持って先月晴れて退職をキメました。ビバ無職

プログラミングは独学でdotinstallなどを少しだけ触ってましたが、本格的に始めたのは一ヶ月ほど前にG's Academyに入学してからです。
ちなみに前職では障害対応を通じてLinuxコマンドとSQLを叩きまくる経験を手に入れました。

作ったもの

これです。
制作期間は約一週間です。

フィールドカードをセットするとデュエルスタート
それぞれのマーカーを読み込ませてモンスター召喚、ターン変更、バトルを実行します。
先にモンスターを2体倒すと勝利です。やったー!

こだわりポイント

モンスターの攻撃モーションの多彩さ

この後出てきますが、全てa-entityタグにanimation属性を付けたり外したりして実装してます。
setTimeout地獄になっています。もっとスマートにコードを書きたい。

動画に出てこなかった子たち
ish.gif
buzz.gif

状態管理

1ターンに行動(召喚・攻撃)できるのは一度までで、それを超えるとメッセージが出るようにしています。
スクリーンショット 2019-11-15 0.21.44.png

使った技術・サービス

ここからは実装についてのお話

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.js
let 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を記載しておくとmarkerFoundmarkerLostなどのイベントを拾うことができるので、モンスターの召喚の他にもフィールドカードや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

オブジェクトはgoogle Polyから取得しました。
poly.gif

もしアプリケーションを公開したい場合はアセットのライセンスを必ず確認しましょう。
Polyのアセットの多くはCC-BYライセンスで公開されています。
poly_licence.png

Polyから取得したオブジェクトはサイズや向きがバラバラなことが多いです。
最初はa-entityタグ内でscalerotationを調整していたのですが、最終的にはBlenderでオブジェクト自体のサイズを調整しました。Blenderの使い方にけっこう戸惑いましたが、こっちの方が後々楽ですもんね。
blender.gif

感想

この他にも担任の先生を最強モンスターとして登場させたりして、クラスのメンバーからはかなり好評をいただきました。

webGLに初めて触れたのが2週間ほど前でしたが、A-Frame、AR.jsはHTMLとJavaScriptの基礎知識があれば追加の学習はごく僅かで済むので、非常に効率的に開発が進められると思います。
Webの世界では当たり前なのかもしれませんが、こんなものが無料で使えるなんて凄すぎますね。

個人的には2018年のスピルバーグ監督映画の「Ready Player One」のような世界を実現できたら本当に面白そうだなと思っているので、引き続きXR系の技術で色々作っていきたいです。

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

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);

})();

使い方

 .js
GlobalRegistor.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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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);

})();

使い方

 .js
GlobalRegistor.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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む