- 投稿日:2019-03-01T23:50:24+09:00
DOM操作を使った簡易的なtodoリストアプリを作ってみた
現在つよぽんさんの元でフロントエンド、バックエンドについて教わっています。今回はその中の課題の1つ「【エクササイズ】DOM操作をしてTodoリストを作成する【JavaScript】」に取り組んだ際の流れ、コードの説明をしていきたいと思います。
つよぽんさんのTwitterアカウント
学習サイト【Web白熱教室】
MENTA制作物の完成サンプル
必要な機能
今回は各挙動をDOM操作を用いて実行することとする
- タスク入力欄
- todoに追加したいタスク(文字)を入力する
- 追加ボタン
- クリックすると、入力したタスクがtodoリストに追加される
- todoリスト一覧
- 入力されたタスクを表示する。
- 「
表示される順の番号:入力したタスク」と表示する。- 削除ボタン
- クリックするとタスクを一覧から削除することができる。
- 残ったタスクの
表示される順の番号はタスクが削除された後の順番に変更されなければならない。今回はあくまで純粋なtodoリストを作る課題なので、CSS等の装飾機能はつけない
コード
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>【エクササイズ】Todoリストの作成</title> </head> <body> <h1>Todoアプリ</h1> <div> <label for="input-todo-box">Todoタスクの入力 : </label> <input id="input-todo-box" type="text"> <button id="add-button">追加</button> </div> <h2>現在のTodoリスト</h2> <ul id="todo-list"></ul> <script src="main.js"></script> </body> </html>
Javascript
タスクを保存するための配列const todos = [];入力したタスクをこちらに配列として保存します。
DOM要素を用いてID値を会得するconst textBox = document.getElementById('input-todo-box'); const addButton = document.getElementById('add-button'); const todoListElement = document.getElementById('todo-list');DOM操作に用いるために必要な要素を
document.getElementByIdで取得します。
今回必要なのは
- タスクの入力 → inputタグのidinput-todo-box
- タスクの追加 → buttonタグのidadd-button
- タスクのリスト表示 → ulタグのidtodo-listなので、この3つを取得しました。
Document.getElementById()【MDN】
追加ボタンをクリックした際の挙動(DOM操作)addButton.addEventListener("click", event => { const todo = textbox.value; textbox.value = ""; if(todo && todo.match(/\S/g)){ todos.push(todo); showTodo(); } });
addEventListenerでaddButtonをクリックした際の挙動を設定します。EventTarget.addEventListener()【MDN】
まず変数
todoにテキストボックスに入力した文字をvalueプロパティを用いて代入します。
その後、入力した後のテキストボックスは空にしなければなりませんので、""を代入します。また、テキストボックス内が空のままだったり、スペース入力でそのまま空のタスクがリストに追加されるのはよくないですので、
ifを使い、todoに文字が入っている場合にのみリストに追加されるようにします。
ifの中で、先に作った定数todosの配列にtodoを追加します。しかしこれではHTML上にタスクが表示されることがありません。なので、その後HTML上にタスクを表示する関数
showTodoを宣言し、タスクが追加されるたびに実行されるようにしますtodosの中身等をHTML上に表示するための関数const showTodo = () => { // HTML上の表示を一旦リセットする while(todoListElement.firstChild) { todoListElement.removeChild(todoListElement.firstChild); } todos.forEach((todo, index) => { // ulに追加するためにliタグの作成、todos内の配列を代入 const liElement = document.createElement("li"); liElement.textContent = `${index + 1} : ${todo}$`; // デリートボタンの作成 const deleteButton = document.createElement("button"); deleteButton.textContent = "削除"; //deleteButtonをクリックした際の挙動 deleteButton.addEventListener("click", event => { deleteTodo(index); }); //作成した要素をHTML上に表示させる liElement.appendChild(deleteButton); todoListElement.appendChild(liElement); }); };上のDOM操作で宣言した
showTodoを作成します。まず最初に一旦HTML上に表示されているタスクを全て削除。なぜ消すか理由は後述します。
次に
forEachでtodosをイテレーション処理させ、HTML上にタスクが表示されるようにします。
まず、todoListElementに渡されているIDはtodo-list、IDが入っているタグ要素はulですね。ulに入る要素はli、なのでliをdocument.createElementで作成、変数に代入し、textContentプロパティでtodos内の値を代入します。index値そのままを代入すると0からのスタートになるので、1を足します。デリートボタン、それとデリートボタンがクリックされた際の挙動もここで作成します。(入力したタスクに結び付けないといけないため)
button要素をcreateElementで作成して、デリートボタンがクリックされた際、deleteTodoを実行するようにします。最後に、liElementとdeteleButtonをtodoListElementに
appendChildで追加します。これで追加したタスクがページ上に表示されるようになります。todoの削除const deleteTodo = index => { todos.splice(index, 1); showTodo(); };最後にdeleteButtonをクリックした際に宣言される
deleteTodoの作成です。
spliceを使い、インデックス値から1つ目の値、つまりdeleteButtonと結び付けられているタスク(liElement)を削除し、配列の中身を変更します。そして再度showTodoを宣言してタスク一覧を表示させます。今回は課題として関数を別で作るようにしましたが、直接addEventListenerに記述しても問題なく動作します。
つまづき
タスクを入力すると重複して表示される。
これは
showTodoで最初のwhile文を記述してなかったために起きた症状です。todoListElement内にどんどん溜まっていく一方ですから当たり前ですね。while文の中身は、
firstchildでtodoListElement内にタスクが入ってる(true)ことを判別した場合、removechildでtodoListElement内のfirstChildを削除し続けるループ処理を実行しています削除ボタンを押したら必ず1番目のタスクが削除される。
単純にindex値の渡し忘れでした。
liElementとdeleteButtonを別々にtodoListElementに追加した場合改行される
todoListElement.appendChild(liElement); todoListElement.appendChild(deleteButton);とすると、タスクとボタンの間で改行が発生しました、この追加方法だとHTMLに直すと
<ul> <li>...</li> <button>...</button> </ul>このように、
ulの中にliとbuttonが別々に追加されるので、改行が発生します。なのでliElement.appendChild(deleteButton); todoListElement.appendChild(liElement);こうすると
<ul> <li>...<button>...</button></li> </ul>
liの中にbuttonを追加した後、ulに追加しているので、改行されずに表示されます。
@tsuyopon_xyz さんありがとうございます!テキストボックスにスペース入力した後追加をすると空のタスクがリストに追加される
todo.match(/\S/g)で解決しました、意味はよくわかりませんが、
\S
スペース以外の文字にマッチします。 [^ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] に相当します。
例えば /\S\w*/ は "foo bar" の 'foo' にマッチします。とのことです。
今後してみたいこと
CSSを使った装飾
現状の見た目だとかなり味気ないので、webサイトとして見れる形にしたい。
ユーザー登録機能を使って個々にtodoを作れるようにする
上記に加え、Webアプリとして機能するようにしてみたい。
みていただきありがとうございました
- 投稿日:2019-03-01T22:29:30+09:00
jQuery $.Defferdのthen,done,fail,alwaysの使い分けメモ
$.Defferdは非同期処理を行うための便利なモジュールですが、
その使い方についてはYahooデベロッパーネットワークの 爆速でわかるjQuery.Deferred超入門がとても分かりやすくお世話になりました。非同期処理を時系列で記述できるのでとても便利ですが、主に使う4つのメソッドの使い所についての自分メモ書きです。
resolved,rejectd時の動作を同時に定義できるthen()
promiseオブジェクトが、resolveにもrejectにもなる場合、
then()でそれぞれの時のコールバックを定義します。function hoge() { let deffer = $.Defferd(); if (isFuga()) { deffer.resolve(); return true; } else { deffer.reject(); } return deffer.promise(); } hoge().then( /** * resolve時の動作 */ fucntion() { return $.Defferd().resolve().promise(); }, /** * reject時の動作 */ function() { return $.Defferd().resolve().promise(); } ).then( /** * resolve時の動作 */ fucntion() { return $.Defferd().resolve().promise(); }, /** * reject時の動作 */ function() { return $.Defferd().resolve().promise(); } );例の関数が非同期処理ではないのですが・・
例えばAjaxを使って外部リソースを取得しその結果次第で処理を変えたい場合があるときに使うと便利です。第2引数のreject時のコールバックは省略もできます。
resolvedで実行されるdone()
then()でもresolve時の動作を定義できますが、then()と違うのは、done()を連結してもそれは順次実行されないことでしょうか。// hoge()が返すpromiseがresolveだとする hoge().then( /** * resolve時の動作 */ fucntion() { // 実行順1 return $.Defferd().resolve().promise(); }, /** * reject時の動作 */ function() { return $.Defferd().reject().promise(); } ).then( /** * resolve時の動作 */ fucntion() { // 実行順2 return $.Defferd().resolve().promise(); }, /** * reject時の動作 */ function() { return $.Defferd().resolve().promise(); } ).done( function() { // 実行順3 // このdoneと下のdoneは同時に実行されます } ).done( function() { // 実行3 // このdoneと上のdoneは同時に実行されます } );爆速でわかるjQuery.Deferred超入門にも書かれていましたが、
done()を複数書いても、それはdone()のコールバックを2つ定義しただけで、順次実行にはなりません。rejectedで実行されるfail()
fail()は コールバックの中で新たにresolvedなpromiseをreturnしない限り、rejectedが継続されるためdone()は実行されません。try-catchの catchのイメージ。// hoge()が返すpromiseがrejectedだとする hoge().fail( function() { // 実行1 // 失敗時の処理 } ).done( function() { // 実行されない } );必ず実行されるalways()
最後の後始末処理に使える
always()。
rejectedでもresolvedでも実行されるので、成功・失敗に関わらず実行したい処理があるときに使います。
try-catch でいえば、finally。// hoge()が返すpromiseがrejectedだとする hoge().fail( function() { // 実行1 // 失敗時の処理 } ).done( function() { // 実行されない } ).always( function() { // 実行2 // 後始末の処理 } );
- 投稿日:2019-03-01T20:53:50+09:00
【Handsontable】別セルの値に応じてドロップダウンリストを連携させる
概要
県名→市町村名等、前のセルの入力値に応じて後ろのセルのドロップダウンリストを変えたい場合の対応方法を記述する。
以下のコード例で、1列名のメーカー名の値に応じて、2列目の車種名の選択肢が変わる例を紹介する。コード例
See the Pen OqMQrw by HK0n (@hk0n) on CodePen.
コード説明
以下3つの要素を利用している。
- afterChange
- setDataAtCell
- setCellMeta
afterChange
https://handsontable.com/docs/6.2.2/Hooks.html#event:afterChange
セルのデータが変更された場合のイベント。
"changes"には[["変更された行","変更された列","変更前の値","変更後の値"]]という形でデータが格納されている。
今回は、変更された列が車種(1列目(changes[0][1]=0))の場合に、後述のsetDataAtCellとsetCellMetaを実行する。setDataAtCell
https://handsontable.com/docs/6.2.2/Core.html#setDataAtCell
セルのデータが書き換えるためのメソッド。setDataAtCell("対象の行","対象の列","書き換える値")
コード例では、メーカー名が変わった場合に、車種の値を空白にする処理を入れている。
これが無いと、メーカー名、車種名を入力した後にメーカー名を変更した場合、意図しない組み合わせになってしまう。
(無い場合の例)
setCellMeta
https://handsontable.com/docs/6.2.2/Core.html#setCellMeta
セルのメタデータを書き換えるためのメソッド。
setCellMeta("対象の行","対象の列","対象のプロパティ名","変更後の値")
コード例では、メーカー名の値に応じて車種のselectOptionsを書き換える処理を行っている。
- 投稿日:2019-03-01T20:22:11+09:00
Qiitaのページング仕様が変わった
タグ単位の最近の記事のページングが、これまでのRESTで
items?page=2のようにページ遷移する方式から、GraphQLを使って動的に書き換える方式に変更になった。そのこと自体はまあいいのだが、それに伴い多大な不都合が発生した。
・適当にタグで検索。
・次ページへの遷移ボタンを10連打。
・適当な記事を表示
・ブラウザバック1ページ目に戻る。
さっきまで11ページ目を見てたんですけど?
続きを見るには10回クリックしなおさないといけないんですけど!!何も考えずPWA化したときの典型的な弊害じゃん。
history.pushStateって突っ込むだけで回避できるのに。
新技術の導入によってユーザエクスペリエンスが劣化するのって、ほんと技術者の自己満足でしかないよね。さらに、これまで使用されていた
https://qiita.com/tags/javascript/items?page=2のようなURLは早々に使用できなくなっている。
つまり、2ページ目以降を直接URL指定で表示する方法がない。スラドにもずっと前から言ってるんだけどさ、どうして普通のページングを用意してくれないんですかね1。
そんなに古い記事は見せたくないんですかね。あと記事タイトル長によって『次ページ』ボタンの位置がずれるから、次ページに行くつもりでうっかり記事を押して死ぬ可能性もある。
1記事の縦の長さを固定するか、ボタンの位置が一定になるよう調節するか、一覧の上にもボタンを付けるなどしたほうがいいだろう。
- 投稿日:2019-03-01T19:16:15+09:00
2019年の今 Mithril.js を使うためのTips
最近めっきり話を聞かなくなったJSフレームワーク、 Mithril.js に関する Tips をつらつらと書いていきます。
決して下火になったわけじゃあないんです。Mithril.js とは
いわゆる仮想DOMフレームワークの一つ。メジャーな React や Angular と比べ、軽量・シンプル・高速なことを謳う。
フレームワークの機能としては、React, Vue と比べても決して不足していないうえ、実際のプロダクトにも採用例があり十分ポテンシャルはある。はずです。
前述の通りバズってはいないですが、現在も活発に開発が続けられており、Ver.2のリリースも予定されています。
Mithril 情報を集めるために
重要な点として、Mithril は v0.2 系と v1.x 系で大きくAPIが異なっています。 v1.xの方が正式となり、v2でも大きな変更は入らないようなので、この点だけは覚えておいてください。
早い話が、 Controller あるいは m.props が話題に出る記事は 古いです。これからは参考にしてはいけません。
じゃあどうなったかと言うと、現在はよりシンプルかつ賢くなって、m.props を使わずとも変更が検知されるし、controller を使わずともライフサイクルに応じたフック処理を実行できます。より簡単かつ高速になった純粋な上位互換と言えるものなので、この点でも今から v0.2 系の知識を仕入れる意味はありません。
ここまで分かればあとは公式サイトなり、新し目の記事なりを漁って導入は出来るかと思うので、そのフェーズは他の記事に譲ります
手抜きともいう。以下は使っていく上で気づいたポイントを書き連ねたものです。
vdom.state は使わない
基本的に view に state をもたせるのは99%アンチパターンです。状態に類するものは model のオブジェクトとして分離し、 view 側ではそれを読みに行くだけにしましょう。
例: テーブルの各列コンポーネントに状態をもたせるのではなく、元データにその状態をもたせて、コンポーネントは filter をかけるなどして出し分ける。
arrow function 最強説
arrow function はどんどん活用しましょう。
// 普通に書いてみる const Component = { view: function(vdom) { // ... } } // arrow function で書いてみる const Component = { view: vdom => { // ... } } // 値を返すだけのfunctionはもっと短くできる const Component = { view: v => m("button", { onclick: model.incCount }, "+1"), }これに加えて
mapfilterreduceなどのメソッドや、三項演算子を組み合わせることでよりコンポーネントをスマートに書けます。楽しくかっこよくコーディングしましょう。form の取扱い
結論から言うと、 formは使わずformっぽいスタイルを使って実現します 。 (bootstrap なら
.form-groupが使えます)const FormLikeComponent = { view: v => [ m(".form-group", [ m("input[type=text]", { value: model.textData, oninput: e => model.textData = e.target.value }), m("button", { onclick: model.submit }, "送信"), ]), ], } let model = { submit: e => { m.request({ url: someUrl, data: {text: model.textData}, }) .then( /* some handling */ ); }, }form が使えないとはいえ十分シンプルに書けます。ポイントは、
oninputイベントなどを用いて入力値を補足します。入力値を得るには、イベントオブジェクトのtarget.valueを参照します。ここで得た値は例によって state ではなく model のどこかに持たせるべきです。- form 内の button なりなんなりで、submit 相当のイベントを発火します。そこでは Mithril らしく
m.requestを使います。要は普通のリクエストですね。フォームだからと特別な処理は必要ありません。input 要素を見てもらえれば分かる通り、こんな感じで双方向バインディングな処理が簡単に書けるのが Mithril の素敵ポイントですね。formっぽいスタイルがないときは頑張ってください。
request まわり
request について書きたかったけど力尽きたので一旦ここまでで。
TypeScript まわり
TypeScript について (ry
- 投稿日:2019-03-01T18:38:51+09:00
自社サービスやってる会社に転職したいのでエンジャパンでプログラマ派遣会社を除外するjavascriptを書いた
現在転職活動を始めたばかりなのですが、エンジャパンなどで求人を検索するとそのほとんどが特定労働者派遣事業者でした。
私の転職希望先は自社サービスを行なっている会社なので、検索結果から特定労働者派遣の会社を除外するjavascriptを書きました。同じような不便を感じている方はぜひご利用ください。
chromeでエンジャパンの検索結果一覧ページを開いて開発ツールのコンソールに以下をコピペすると派遣会社が削除されます。var cnt = 0; $('.toDesc').each(function (key, obj) { var regex = /労働者派遣|職業紹介|派遣番号|勤務先により異なります|派遣事業|プロジェクトによって異なり|人材支援|プロジェクト先|クライアント先/; link = 'https://employment.en-japan.com' + obj.getAttribute('href'); $.get(link, function (res) { if (regex.exec(res)) { var box = $(obj).parents('.jobSearchListUnit'); var jobName = box.find('.jobNameText')[0].textContent; var company = box.find('.company')[0].textContent; box.remove(); cnt++; console.log('[除外' + cnt + '] 会社名:' + company + '概要:' + jobName); } }); });以下のページとかでおためしください。
https://employment.en-japan.com/s_webprogrammer/リクナビとかも必要に応じて作ろうと思ってますが、書いた人いれば共有してもらえると嬉しいです。
ブックマークレットはこちらです。javascript: var cnt = 0; $('.toDesc').each(function (key, obj) { var regex = /労働者派遣|職業紹介|派遣番号|勤務先により異なります|派遣事業|プロジェクトによって異なり|人材支援|プロジェクト先|クライアント先/; link = 'https://employment.en-japan.com' + obj.getAttribute('href'); $.get(link, function (res) { if (regex.exec(res)) { var box = $(obj).parents('.jobSearchListUnit'); var jobName = box.find('.jobNameText')[0].textContent; var company = box.find('.company')[0].textContent; box.remove(); cnt++; console.log('[除外' + cnt + '] 会社名:' + company + '概要:' + jobName); } }); });
- 投稿日:2019-03-01T18:12:00+09:00
Javascriptで配列に配列を追加したら二次元配列になる件
はじめに
多次元配列をpushしようとしたら、
失敗例①let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base.push(add1); base.push(add2); console.log(base);実行結果:失敗例①[ [ 1, 2, 3 ], [ 4, 5, 6 ] ]・・・こうなりました。
間違ってはないけど、もうちょっと綺麗に並べたい・・・
ってなったので、調べた結果を共有します。今回のコード
成功例let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base = base.concat(add1); base = base.concat(add2); console.log(base);実行結果:成功例[ 1, 2, 3, 4, 5, 6 ]きれいに並んだ!
失敗例
その1 pushをフツーに使ったパターン
失敗例①let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base.push(add1); base.push(add2); console.log(base);実行結果:失敗例①[ [ 1, 2, 3 ], [ 4, 5, 6 ] ]はじめに書いた通り、普通にpushしたらこうなる。
.lengthで個数を調べたりしたら、
「中身2個ですよ」とか返ってくる始末です。その2 concatをというメソッドを知り、希望を託すも惨敗したパターン
失敗例②let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base.concat(add1); base.concat(add2); console.log(base);実行結果:失敗例②[]もう、なんも入ってませんやん。
一周回って、笑えるやつ。ざっくり解説
pushは、元の配列に値を追加するときによく使われるメソッドです。
しかし、追加する値が配列だった場合、失敗例①のように、二次元配列になって追加されてしまいます。
どうも、かゆいところに手が届かない。そこで、他のメソッドを調べたら、出てくるのがconcatメソッド。
これは、追加するというより、結合するというメソッドだそうです。しかし油断するなかれ。
そのまま、失敗例①のpushメソッドをconcatメソッドに書き直しても、失敗例②のようになります。一周回って笑うしかありません。
僕は、笑いました。これは、pushが破壊的メソッドと表現されるのに対して、concatは非破壊的メソッドと表現されるものだからだそうです。
つまり、pushメソッドは、base変数に付くとともに、元の空配列であったbase変数を破壊し、add1変数やadd2変数が追加された変数へと姿形を変えてしまいます。これに対して、concatメソッドは、元の空配列であるbase変数を破壊しません。そのままです。
なので、失敗例②は元の空配列であるbase変数が出力されて、こうなってしまったんですね。
base変数自体を変えたいのであれば、成功例のように書いて頂くと良いかと思います。さいごに
走り書きで恐縮ですが、読んでいただいてありがとうございました。
間違いや、分かりにくい部分があれば、お気軽に編集リクエスト出していただければ幸いです。
- 投稿日:2019-03-01T18:12:00+09:00
配列に配列を追加したら、勝手に二次元配列になる件
はじめに
配列に配列を追加しようと、定番のpushメソッドを使ってみたら、
失敗例①let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base.push(add1); base.push(add2); console.log(base);実行結果:失敗例①[ [ 1, 2, 3 ], [ 4, 5, 6 ] ]・・・こうなりました。
間違ってはないけど、もうちょっと綺麗に並べたい・・・
ってなったので、調べた結果を共有します。今回のコード
成功例let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base = base.concat(add1); base = base.concat(add2); console.log(base);実行結果:成功例[ 1, 2, 3, 4, 5, 6 ]きれいに並んだ!
失敗例
その1 pushをフツーに使ったパターン
失敗例①let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base.push(add1); base.push(add2); console.log(base);実行結果:失敗例①[ [ 1, 2, 3 ], [ 4, 5, 6 ] ]はじめに書いた通り、普通にpushしたらこうなる。
.lengthで個数を調べたりしたら、
「中身2個ですよ」とか返ってくる始末です。その2 concatをというメソッドを知り、希望を託すも惨敗したパターン
失敗例②let base = []; let add1 = [ 1, 2, 3 ]; let add2 = [ 4, 5, 6 ]; base.concat(add1); base.concat(add2); console.log(base);実行結果:失敗例②[]もう、なんも入ってませんやん。
一周回って、笑えるやつ。ざっくり解説
pushは、元の配列に値を追加するときによく使われるメソッドです。
しかし、追加する値が配列だった場合、失敗例①のように、二次元配列になって追加されてしまいます。
どうも、かゆいところに手が届かない。そこで、他のメソッドを調べたら、出てくるのがconcatメソッド。
これは、追加するというより、結合するというメソッドだそうです。しかし油断するなかれ。
そのまま、失敗例①のpushメソッドをconcatメソッドに書き直しても、失敗例②のようになります。一周回って笑うしかありません。
僕は、笑いました。これは、pushが破壊的メソッドと表現されるのに対して、concatは非破壊的メソッドと表現されるものだからだそうです。
つまり、pushメソッドは、base変数に付くとともに、元の空配列であったbase変数を破壊し、add1変数やadd2変数が追加された変数へと姿形を変えてしまいます。これに対して、concatメソッドは、元の空配列であるbase変数を破壊しません。そのままです。
なので、失敗例②は元の空配列であるbase変数が出力されて、こうなってしまったんですね。
base変数自体を変えたいのであれば、成功例のように書いて頂くと良いかと思います。さいごに
走り書きで恐縮ですが、読んでいただいてありがとうございました。
間違いや、分かりにくい部分があれば、お気軽に編集リクエスト出していただければ幸いです。
- 投稿日:2019-03-01T17:48:52+09:00
ジェネレータで再帰を制す
社内LT発表用資料
JavaScriptで説明していきますが、最後に見るようにPython, Rubyでもジェネレータは使えます。ジェネレータとは?
例:自然数を無限に生成するジェネレータ
natural-number-generator.jsfunction* createNaturalNumberGenerator() { let n = 1; while (true) { yield n; n += 1; } } const generator = createNaturalNumberGenerator(); console.log(generator.next().value); console.log(generator.next().value); console.log(generator.next().value); console.log(generator.next().value); console.log(generator.next().value);1 2 3 4 5
function*で定義される関数のことをジェネレータ関数といい、- ジェネレータ関数が返す値、つまり変数
generatorのことをジェネレータと言います
for ... ofを使って呼び出すと、ジェネレータから値がなくなるまでループします。natural-number-generator2.jsfunction* createNaturalNumberGenerator() { let i = 1; while (true) { yield i; i += 1; } } const generator = createNaturalNumberGenerator(); for (let n of generator) { console.log(n); }と言っても自然数の場合は無限にあるので、無限に続きます。
1 2 3 4 5 ...(無限につづく)[ポイント]
createNaturalNumberGeneratorの中で無限ループしているけど、この関数が永久に戻らないわけではなく、next()を呼ぶごと、あるいはfor ...ofで1回ループを回るごとに1回yieldが呼ばれて呼び出し元に戻っていることに注意してください。そんなのクラスやクロージャでもできるよ
はい、確かにできます。
クラスを使う版
class.jsclass NaturalNumberGenerator { constructor() { this.n = 0; } get() { this.n += 1; return this.n; } } const g = new NaturalNumberGenerator(); console.log(g.get()); console.log(g.get()); console.log(g.get()); console.log(g.get());しかし、この場合
this.nという今どこまで進んだかという状態を自分で管理しなければならないことになります。
ジェネレータ関数を使うと、状態を自分で管理しなくて済むようになります。
まだ何のことか分からないと思いますので、次に具体的にメリットが分かる例を出します。組み合わせ(nCr)を出力する関数(非ジェネレータ)
combinations.jsfunction getCombinations(xs, r) { if (xs.length < r) { return []; } if (r == 1) { const result = []; for (let x of xs) { result.push([x]); } return result; } const x = xs[0]; const xs_ = xs.slice(1); return getCombinations(xs_, r - 1).map(comb => [x, ...comb]).concat(getCombinations(xs_, r)); } console.log(getCombinations([1, 2, 3, 4], 2));[ [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 2, 3 ], [ 2, 4 ], [ 3, 4 ] ]
- 中で自分自身を2回呼び出している2重再帰
- パスカルの三角形を素直にアルゴリズム化したのがこの実装
自分自身を1回しか呼び出さない実装もあり、そっちの方が効率はいいけど、こっちの方が発想は素直かと
問題点
- 入力が大きくなると結果の配列が大きくなり、メモリを圧迫する
50C10 == 10272278170(102億!)- 試しにさっきのプログラムでやってみると返ってこないです
ジェネレータなら
- さっきの関数とアルゴリズムは同じで、ジェネレータで書き換えたもの
combinations-generator.jsfunction* createCombinationGenerator(xs, r) { if (xs.length < r) { return; } if (r == 1) { for (let x of xs) { yield [x]; } return; } const x = xs[0]; const xs_ = xs.slice(1); for (let comb of createCombinationGenerator(xs_, r - 1)) { yield [x, ...comb]; } for (let comb of createCombinationGenerator(xs_, r)) { yield comb; } } const xs = []; for (let i = 0; i < 50; i++) { xs.push(i + 1); } const generator = createCombinationGenerator(xs, 10); for (let comb of generator) { console.log(comb); }[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 11 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 12 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 13 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 14 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 15 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 16 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 17 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 18 ] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 ] ...(相当な時間がかかるが、順次結果を出力する)
- 結果を保存しておく配列を考えなくてもよくなるので、コーディングも楽になります(この手の処理はしばしば配列の配列の配列とか出てきてややこしくなるので)
ジェネレータはどう実装されているのか?
不思議に思いませんでしたか?どう実装されているのか。
Node.jsの場合
How does yield actually pause/resume the flow of a generator? : javascript
- Node.jsではC++内でスタックフレームを保存・復元しているらしい
- (C言語レベルのスタックフレームではなく、JavaScriptのスタックフレーム)
BabelでES5に変換する場合
BabelでES5に変換できるのだから、C++で書かれたインタープリタ自体がサポートしていなくても実装できるはず。
Babalは有限状態機械に変換するらしい。
(変換されたコードを読もうとしてもメッチャ分かりづらい)Babelで変換したnatural-number-generator.js"use strict"; var _marked = /*#__PURE__*/ regeneratorRuntime.mark(createNaturalNumberGenerator); function createNaturalNumberGenerator() { var i; return regeneratorRuntime.wrap(function createNaturalNumberGenerator$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: i = 1; case 1: if (!true) { _context.next = 7; break; } _context.next = 4; return i; case 4: i += 1; _context.next = 1; break; case 7: case "end": return _context.stop(); } } }, _marked); } var generator = createNaturalNumberGenerator(); console.log(generator.next().value); console.log(generator.next().value); console.log(generator.next().value); console.log(generator.next().value); console.log(generator.next().value);async/awaitも実はジェネレータで実装されている
…らしい。
自分でジェネレータを使ってasync/awaitを実装することもできます。
ES6 Generatorを使ってasync/awaitを実装するメモ - maru sourcePythonやRubyにもジェネレータはある
Python
natural-number-generator.pydef generate_natural_numbers(): n = 1 while True: yield n n += 1 for i in generate_natural_numbers(): print(i)Ruby
natural-number-generator.rbgenerator = Enumerator.new {|yielder| n = 1 loop do yielder.yield n n += 1 end } generator.each do |n| puts n endまとめ
- ジェネレータを使うと、巨大な組み合わせや探索をする再帰関数が効率的に書ける。楽に書ける
- 投稿日:2019-03-01T16:45:47+09:00
計算知能WolframAlphaからアメーバーのような幾何学生物を作成
計算機科学専用の検索エンジンWolframAlphaをご存知だろうか。私はこのWolframAlphaという存在を、2017年のSXSWでどこかのセッションで登壇者の方が説明しているときに知ったが、その時は何に使うんだろーと思っていた。それ以来忘れていたが、つい最近日本語版が公開されたという情報を偶然目にして、少し興味を持って調べてみた。。。のはこちらに書きました。
面白そうな図形
walframAlphaで面白そうな図形を発見したので、それをJavaScriptでうまく生成できないか試行錯誤してみました。こんな図形です。
式r = 4 + 0.5 * sin( 8 theta)半径Rを割り出す式ですが、Θの角度に応じて円形を歪めています。
この式の構造としては以下の感じかと思います。式半径 = 半径の大きさ + 振幅 * sin( 周波数 * theta)この式を元にp5.jsを使って描画してみます。
p5.jsconst radius = 150; const amplitude = 12; const frequency = 12; translate(width / 2, height / 2) for (let i = 0; i < 360; i++){ const r = radius + amplitude * sin( radians(frequency * i )) const x = cos(radians(i)) * r const y = sin(radians(i)) * r point(x, y); }周波数12に設定したので、12個の山が出来ています。周波数や振幅ドットの数や半径、Θの角度の間隔など、パラメータを変更するだけで、実に様々な形状を作ることができます。
これを基本原理として、アメーバージェネレーターを作りました。アメーバーに見えないかもしれないけど。。。
GitHubに置いておきます。
また、OpenProcessing上でも試すことができます。Gellery
- 投稿日:2019-03-01T14:58:11+09:00
[Ajax+Rails]プレビューで個別削除可能な画像アップローダの作成
RailsでAjaxを使って、画像アップローダーを作成しました。想定しているのは、メルカリのようなユーザーが商品を出品し、取引を行うサイトの出品画面です。できたことは以下です。
- ドラッグ&ドロップで画像をアップロードし、結果を送信する前にプレビューをする。
- そのほかのフォームの結果と一緒に画像をコントローラに送信する。
- 画像のプレビュー時に、削除ボタンで個別にアップロードする画像を変更する。
環境
Rails 5.0.7.1
ruby 2.3.1注意点
- Rails初学者のため、間違っている部分が多々ある可能性がありますので、参考程度にご覧ください。
- 実際に動いているコードから編集を加えているため、動作保証ないです。すいません。。。
- モデル側については、解説ありません。コントローラ、ビュー、javascript部分になります。モデル側は、itemに対して複数のimagesテーブルのレコードを登録できるような中間テーブルを作成しています。
1.ビュー
new.html.haml/ form_forを利用して、itemのモデルオブジェクトにデータを送信する。 = form_for @item, html: {id:'new_item'} do |f| / 中略 .item__images__container .item__images__container__preview / 以下のulタグの下に、Jsからli要素を挿入することでプレビューを実現します。 %ul .item__images__container__guide / ドラッグ&ドロップエリアをプレビューによって変動させたい場合以下のようなclassを作成し、プレビュー画像の数によってclassを変える。 .have__image--0 %h5 ドラッグ&ドロップ<br>でファイルをアップロード / 中略(そのほか様々なフォームがある想定) .item__form__sellcontent__submit__done = f.submit class: 'btn-default item__done', disable_with: "Save", value:"出品する"ドラッグ&ドロップで、画像アップローダを作成するため、ビュー側には、inputタグは必要ありません。(もし、inputタグを利用して実装可能ならば、ご教示いただきたいです。。。)
ビューについては、
- プレビューの画像と削除ボタンのHTMLを挿入するためのulを設置する。
- ドラッグ&ドロップのためのエリアを設定する。(今回の場合は、.item_imagescontainer_guide)のみでOKです。
2.コントローラー
items_controller.rbdef new @item = Item.new end def create # itemが保存できたかどうかで、画像の保存を分岐させたいために、newです。 @item = Item.new(create_params) if @item.save image_params[:images].each do |image| #buildのタイミングは、newアクションでも可能かもしれません。buildすることで、saveした際にアソシエーション先のテーブルにも値を反映できるようになります。 @item.images.build item_image = @item.images.new(image: image) item_image.save end #今回は、Ajaxのみの通信で実装するためHTMLへrespondする必要がないため、jsonのみです。 respond_to do |format| format.json end end end private def create_params # images以外の値についてのストロングパラメータの設定 item_params = params.require(:item).permit(:name, :description,:category_id, :size, :brand_id, :condition, :select_shipping_fee, :shipping_method, :area, :shipping_date, :price) return item_params end def image_params #imageのストロングパラメータの設定.js側でimagesをrequireすれば画像のみを引き出せるように設定する。 params.require(:images).permit({:images => []}) endコントローラーの特徴としては、itemsとimagesの二つのテーブルを更新するためにストロングパラメータを二つ設定することです。その際に、imagesのアップロードデータのparamsのkeyを:itemにしていると、itemを保存する際に、imagesの値が許可されいないのにも関わらず、値を持っているため、unpermitted parameterが出てしまいます。他にも対処があるかもしれませんが、僕は、keyを:imageに設定することでunpermitted parameterのエラーが出ないようにしています。
(通常、form_forでモデルオブジェクトを設定すると、name属性が"item[description]"となりますが、このitem部分をimageに変更してjsで送ることでparamsのkeyを変更できます。)3.Javascript部分
かなり長くなりますが、ご了承ください。
3.1 ドラッグアンドドロップおよび画像読み込み
item_new.js// プレビューに挿入するHTMLの作成 function buildImage(loadedImageUri){ var html = `<li> <img src=${loadedImageUri}> <div class="item__images__container__preview__box"> <div class="item__images__container__preview__box__edit" > 編集 </div> <div> <a class="item__images__container__preview__box__delete">削除</a> </div> </div> </li>` return html }; // 画像を管理するための配列を定義する。 var files_array = []; // 通常のドラッグオーバイベントを止める。 $('.item__images__container__guide').on('dragover',function(e){ e.preventDefault(); }); // ドロップ時のイベントの作成 $('.item__images__container__guide').on('drop',function(event){ event.preventDefault(); // 何故か、dataTransferがうまくいかなかったので、originalEventから読み込んでいます。 // ここで、イベントによって得たファイルを配列で取り込んでいます。 files = event.originalEvent.dataTransfer.files; // 画像のファイルを一つづつ、先ほどの画像管理用の配列に追加する。 for (var i=0; i<files.length; i++) { files_array.push(files[i]); var fileReader = new FileReader(); // ファイルが読み込まれた際に、行う動作を定義する。 fileReader.onload = function( event ) { // 画像のurlを取得します。 var loadedImageUri = event.target.result; // 取得したURLを利用して、ビューにHTMLを挿入する。 $(buildImage(loadedImageUri,)).appendTo(".item__images__container__preview ul").trigger("create"); }; // ファイルの読み込みを行う。 fileReader.readAsDataURL(files[i]); } });読み込み部分は、「Javascript ドラッグ&ドロップ」を検索すると、比較的たくさん出てきます。他のものと異なるのは、読み込み動作時に、files_arrayに画像ファイルを追加していることです。最終的に、このfile_arrayに存在している画像を送信します。配列に代入する目的は二つあります。
- 画面遷移せずに、再度画像がドラッグ&ドロップされた際に、前回の画像ファイルを保持する。
- プレビューで画像が削除された際に、該当画像を特定し、送信する画像を削除する
簡単に言うと、JS側に画像を削除・追加可能なDBを配列で作成して、コントローラーへの送信が行われるまで管理するために利用します。
3.2 プレビューからの画像削除
item_new.js// div配下のaタグがクリックされた際に、イベントを発生させる。 $(document).on('click','.item__images__container__preview a', function(){ // index関数を利用して、クリックされたaタグが、div内で何番目のものか特定する。 var index = $(".item__images__container__preview a").index(this); // クリックされたaタグの順番から、削除すべき画像を特定し、配列から削除する。 files_array.splice(index - 1, 1); // クリックされたaタグが含まれるli要素をHTMLから削除する。 $(this).parent().parent().parent().remove(); });先ほど、プレビューとして追加されたli要素は、個別にそれぞれの画像を特定する要素が含まれいません。(例えば、liのクラスにナンバーをカスタムクラスで通し番号をふるなどが行われていない。)そのため、何番目のli要素が削除のイベントを受けたのかをindex()で何番目のaタグがクリックされたかを特定することで特定しています。これによって、何番目に挿入した画像かを判別し、file_arrayから削除しています。
3.3 出品フォームのコントローラへの送信
item_new.js// submitボタンが押された際のイベント $('#new_item').on('submit', function(e){ e.preventDefault(); // そのほかのform情報を以下の記述でformDataに追加 var formData = new FormData($(this).get(0)); // ドラッグアンドドロップで、取得したファイルをformDataに入れる。 files_array.forEach(function(file){ formData.append("image[images][]" , file) }); $.ajax({ url: '/items', type: "POST", data: formData, contentType: false, processData: false, dataType: 'json', }) .done(function(data){ alert('出品に成功しました!'); }) .fail(function(XMLHttpRequest, textStatus, errorThrown){ alert('出品に失敗しました!'); }); });items_controllerへの送信は、ajaxのみで行うため、画像以外の値を先に、formDataに追加します。その後、files_arrayに入っている画像をimage[images][]と言うkeyでformDataに追加します。この際、controllerの部分で説明したようにimage[]と言う形式で送ることで、コントローラ側で受け取った際に、paramsのkeyをimageとしてデータを送ることができます。
以上、js部分になります。
まとめ・ポイント
- fromData.append()を利用することで、jsで取得した値をajaxで送信することができる。
- js側に配列などを作成することで、ビューで削除された画像を削除できるようにする。
- 本当は、Ajaxではなく通常のHTMLで行いたかったのですが、どうしてもできなかったのでAjaxで実装しました。他の方法もあるかと思います。(どうしてできなかったのかは、おまけに書きます。)
- 今回は、出品画面のみですが、更新画面は大幅にjsを変更することが必要です。(DBに保存された画像と新規登録画像を判別する必要があるため)
おまけ
なぜ、inputタグで作成できなかったのか。
理由1:inputタグのtype=fileのvalueにjs側から値を挿入できない。
ドラッグ&ドロップでの画像アップローダーは、jsでしか実装できないため、inputタグのtype=fileに取得した値を挿入しようとしたのですが、反映されない。調べてみると、下記のようにセキュリティ上値を入れることができなそうなことが分かり、断念した。
INPUT TYPE="FILE"の初期値をセットする方法
NPUT TYPE=FILEタグでの入力値保持について理由2.ajaxとhtmlで分けてテーブルに登録できるかわからなかった
試していないのですが、ajaxで画像をそれ以外をhtmlを送信することを考えましたが、itemが保存できなかった時にimageの処理を止めることがどうしたらいいかわからなかった(逆も同じ)。
なんとなく、buildを利用すればなんとかなるような気がしたが、一緒にデータを送った方が安全だと判断した。理由3.input multiple= trueは、過去の値を保持できない
input multiple=trueを利用すれば簡単に複数画像をinputタグで渡すことができるのですが、画面遷移せずに再度inputタグの値を更新すると前回の値はなくなってしまうため、HTMLのみでは複数回のファイルアップロードにどのように対応していいかわからなかった。
以上になります。他のやり方がありましたらご教示いただきたいです。(CarrierWaveの機能をフルに使えばできそうな気もするのですが、、、公式のgitを読み解くことができませんでした。。。)
参考リンク
ドラッグ&ドロップで複数画像をアップロードする
CarrierWave Upload Multiple Images [2018 Update]
CarrierWave 複数の画像をコード三行で一つのカラムに保存する
- 投稿日:2019-03-01T14:41:05+09:00
全角数字・漢数字を一括して半角数字に変える
引き続き、スプレッドシートでフォーマットの統一されていない住所一覧を渡されてキレてる私の覚書です。
前回は、住所に含まれる色んな横棒をハイフンに変えました。→こちら
ただ、この横棒処理は、番地を表す数字があらかじめ半角になっていることが前提となっています。というわけで、別途こんな感じの関数が必要かと思います。
code.jsfunction num2num(str){ // 全角数字を半角数字に var reg; var twoBtNum = ['1','2','3','4','5', '6','7','8','9','0']; var num = ['1','2','3','4','5', '6','7','8','9','0']; for(var i=0;i<num.length;i++){ reg = new RegExp(twoBtNum[i],'g'); // ex) reg = /3/g str = str.replace(reg,num[i]); } return str; } function kanji2num(str){ // 漢数字を半角数字に var reg; var kanjiNum = ['一','二','三','四','五', '六','七','八','九','〇']; var num = ['1','2','3','4','5', '6','7','8','9','0']; for(var i=0;i<num.length;i++){ reg = new RegExp(kanjiNum[i],'g'); // ex) reg = /三/g str = str.replace(reg,num[i]); } return str; } // 使用例 var add = '宮城県仙台市横浜区二丁目3-4'; var add2 = num2num(add); // add2 = '宮城県仙台市横浜区二丁目3ー4' var add3 = kanji2num(add2); // add3 = '宮城県仙台市横浜区2丁目3ー4';住所一覧に限らず色んな場面でけっこう使っております。
単純に、replace(/数字/g,"半角数字")を10通りやってるだけですが。
gオプションは、あてはまるもの全部ということになり、「三丁目三ー三」→「3丁目3ー3」と全部変わります。正規表現を使わない場合や、オプション無しの場合は、「3丁目三ー三」になる気がします。住所に関してひとつ気を付けなければならないことがあって、この関数をやみくもに使うと、八王子→8王子 とか 三鷹→3鷹 という具合になります。
なので、こちらは単体で使うよりは、他の色々な処理と組み合わせて使っていくことが多くなると思います。他の色々な処理について、またあとで書きます。
【その他の、住所の表記ゆれ対策シリーズ】
・色んな横棒をハイフンにする
- 投稿日:2019-03-01T11:24:36+09:00
React開発ノウハウメモ(随時更新)
少し前までAngularを使って開発してきましたが、年明けごろからReactを使い始めたので、自分なりのノウハウをまとめておきます。随時更新予定です。
使っている言語・ライブラリはReact, TypeScript, RxJSです。
RxJSはAngularで使っており慣れているのでReactでも引き続き使っていこうと試行錯誤しています。型定義の無いライブラリが多く面倒なので、なるべく生のReactで解決しようとしていますが、今後は状態管理に別のライブラリを使うかもしれません。
型付け
stateやpropsのメンバーはComponent内では書き換えてはならないので、すべてのメンバーにreadonlyを付ける。全メンバーにreadonlyを付けるのは面倒なので以下のように定義している。interface IProps extends Readonly<{ member1: number, member2: string, member3: number[], }>{}これで
interface IProps { readonly member1: number; readonly member2: string; readonly member3: number[]; }と同じ意味になる(はず)。
- スタイリングはエラー発見しやすいCSSinJSを使っている。CSSオブジェクトの型定義を以下のように作成した。
import { CSSProperties } from 'react'; export interface CSSobj { readonly [key: string]: CSSProperties; }使い方
const css: ICSSobj = { tableWrapper: { overflowX: 'auto', }, spacer: { flexGrow: 1, }, footer: { display: "flex", flexDirection: "row", alignItems: "center", }, resetButtonWrapper: { padding: "7px", }, };関数コンポーネント(FC)
ステートレスコンポーネントを関数で簡単に書けるのがReactを使い始めて一番良いと思ったところ。
自分は基本的に、state管理を行う親コンポーネント(class)と、ロジックをほぼ持たずCSSスタイリング等を行うview専用の子コンポーネント(FC)の親子組を作るようにした。
class InputWithReset extends React.Component<Readonly<{ placeholder: string; valueChange: (v: string) => void; }>, Readonly<{ value: string }>> { state = { value: '', }; valueChange = (value: string) => { this.setState({ value: value }); this.props.valueChange(value); } resetClick = () => { this.setState({ value: '' }); this.props.valueChange(''); } render = () => ( <InputWithResetView value={this.state.value} placeholder={this.props.placeholder} valueChange={this.valueChange} resetClick={this.resetClick} /> ) }const css: ICSSobj = { input: { minWidth: "120px", }, }; const InputWithResetView = (props: Readonly<{ placeholder: string; value: string; valueChange: (value: string) => void; resetClick: () => void; }>) => { const onInput = (ev: React.ChangeEvent<HTMLInputElement>) => props.valueChange( ev.target.value || '' ); return ( <FormControl> <InputLabel shrink htmlFor="input">{props.placeholder}</InputLabel> <Input style={css.input} id="input" type='text' value={props.value} onChange={onInput} endAdornment={ <InputAdornment position="end"> <IconButton aria-label="Reset Input" onClick={props.resetClick} > <ClearIcon /> </IconButton> </InputAdornment> } /> </FormControl> ); };
event.target.valueを取り出すようなhtml要素に近い部分の整形はviewの方で行うようにしている。
import文も整理されるので親子は別ファイルに書くことにした。Props変更時の処理
class componentにおいてpropsの値が変わったとき、renderは呼ばれるがconstructor等に記述したpropsに依存する変数は再計算されない。
propsが変わったときにrender以外の処理を行いたい場合については、公式ページの getDerivedStateFromProps の説明に書かれている。stateをpropsに依存するようにするのはバグの元なので
getDerivedStateFromPropsメソッド以外の方法を推奨しているようだ。
対応方法が箇条書きで3つ書かれているが、propsの値を使った変数の値を再計算したい場合、2つ目と3つ目が該当するように思われる。どれが適しているかはケースによるが、自分はconstructor内のロジックも含めて全体的に再計算したかったため3つ目を採用した。親コンポーネントにおいてこのコンポーネントを
key={Date.now()}を付けて呼ぶことで、keyの変更によりコンポーネントが再生成されることを利用するという方法だ。interface IProps extends Readonly<{ data: any }>{} const ParentFC = (props: IProps) => ( <div> {!!props.data && <Child key={Date.now()} // recreate this component every time when props change data={props.data} /> } </div> ); const eq = (prev: IProps, curr: IProps) => ( (prev.data === curr.data) ); export const Parent = React.memo(ParentFC, eq);class Child extends React.Component<IProps, IState> { constructor(props: IProps) { super(props); // propsの値を使った様々な計算 } ... }また、コンポーネント全体を再生成するため高コストであることも考慮し、メモ化も行っている。
2つ目の use a memoization helper をやりたくなかった理由は、本来表示に関するメソッドであるはずの
render内にロジックを書くのが気持ち悪かったので。
props変更時にrenderは呼ばれるので、内部変数の再計算をここで行い、一部のみ変更したい場合に対応するためにメモ化するという方法のようで、必要な再計算が一部のみであることが分かっていればこちらを採用した方がパフォーマンスは向上するのかもしれない。RxJS
複雑な状態管理にはRxJSを使っている。Angularのようにasyncパイプなどはなさそうなので、subscribeをちゃんと書く必要があるが、どこに書くのか分からなかったのでメモ。
Angularでやっていた時と同じく、
takeWhileとalive変数を使って自動unsubscribeさせるパターンを使っている。export class A extends React.Component<IProps, IState> { state = { data: [], }; private alive = true; componentWillUnmount() { this.alive = false; } componentDidMount() { this.data$.takeWhile( () => this.alive ) .subscribe( v => { this.setState({ data: v }); }); } ... }
- 投稿日:2019-03-01T11:13:30+09:00
Reactによる開発ノウハウメモ(随時更新)
少し前までAngularを使って開発してきましたが、年明けごろからReactを使い始めたので、自分なりのノウハウをまとめていきたいと思います。
使っている言語・ライブラリはReact, TypeScript, RxJSです。RxJSはAngularで使っており慣れているのでReactでも引き続き使っていこうと試行錯誤しています。
型付け
stateやpropsのメンバーはComponent内では書き換えてはならないので、すべてのメンバーにreadonlyを付ける。全メンバーにreadonlyを付けるのは面倒なので以下のように定義している。interface IProps extends Readonly<{ member1: number, member2: string, member3: number[], }>{}これで
interface IProps { readonly member1: number; readonly member2: string; readonly member3: number[]; }と同じ意味になる(はず)。
- スタイリングはエラー発見しやすいCSSinJSを使っている。CSSオブジェクトの型定義を以下のように作成した。
import { CSSProperties } from 'react'; export interface CSSobj { readonly [key: string]: CSSProperties; }使い方
const css: ICSSobj = { tableWrapper: { overflowX: 'auto', }, spacer: { flexGrow: 1, }, footer: { display: "flex", flexDirection: "row", alignItems: "center", }, resetButtonWrapper: { padding: "7px", }, };関数コンポーネント(FC)
ステートレスコンポーネントを関数で簡単に書けるのがReactを使い始めて一番良いと思ったところ。
自分は基本的に、state管理を行う親コンポーネント(class)と、ロジックをほぼ持たずCSSスタイリング等を行うview専用の子コンポーネント(FC)の親子組を作るようにした。
class InputWithReset extends React.Component<Readonly<{ placeholder: string; valueChange: (v: string) => void; }>, Readonly<{ value: string }>> { state = { value: '', }; valueChange = (value: string) => { this.setState({ value: value }); this.props.valueChange(value); } resetClick = () => { this.setState({ value: '' }); this.props.valueChange(''); } render = () => ( <InputWithResetView value={this.state.value} placeholder={this.props.placeholder} valueChange={this.valueChange} resetClick={this.resetClick} /> ) }const css: ICSSobj = { input: { minWidth: "120px", }, }; const InputWithResetView = (props: Readonly<{ placeholder: string; value: string; valueChange: (value: string) => void; resetClick: () => void; }>) => { const onInput = (ev: React.ChangeEvent<HTMLInputElement>) => props.valueChange( ev.target.value || '' ); return ( <FormControl> <InputLabel shrink htmlFor="input">{props.placeholder}</InputLabel> <Input style={css.input} id="input" type='text' value={props.value} onChange={onInput} endAdornment={ <InputAdornment position="end"> <IconButton aria-label="Reset Input" onClick={props.resetClick} > <ClearIcon /> </IconButton> </InputAdornment> } /> </FormControl> ); };
event.target.valueを取り出すようなhtml要素に近い部分の整形はviewの方で行うようにしている。
import文も整理されるので親子は別ファイルに書くことにした。Props変更時の処理
class componentにおいてpropsの値が変わったとき、renderは呼ばれるがconstructor等に記述したpropsに依存する変数は再計算されない。
propsが変わったときにrender以外の処理を行いたい場合については、公式ページの getDerivedStateFromProps の説明に書かれている。stateをpropsに依存するようにするのはバグの元なので
getDerivedStateFromPropsメソッド以外の方法を推奨しているようだ。
対応方法が箇条書きで3つ書かれているが、propsの値を使った変数の値を再計算したい場合、2つ目と3つ目が該当するように思われる。どれが適しているかはケースによるが、自分はconstructor内のロジックも含めて全体的に再計算したかったため3つ目を採用した。親コンポーネントにおいてこのコンポーネントを
key={Date.now()}を付けて呼ぶことで、keyの変更によりコンポーネントが再生成されることを利用するという方法だ。interface IProps extends Readonly<{ data: any }>{} const ParentFC = (props: IProps) => ( <div> {!!props.data && <Child key={Date.now()} // recreate this component every time when props change data={props.data} /> } </div> ); const eq = (prev: IProps, curr: IProps) => ( (prev.data === curr.data) ); export const Parent = React.memo(ParentFC, eq);class Child extends React.Component<IProps, IState> { constructor(props: IProps) { super(props); // propsの値を使った様々な計算 } ... }また、コンポーネント全体を再生成するため高コストであることも考慮し、メモ化も行っている。
2つ目の use a memoization helper をやりたくなかった理由は、本来表示に関するメソッドであるはずの
render内にロジックを書くのが気持ち悪かったので。
props変更時にrenderは呼ばれるので、内部変数の再計算をここで行い、一部のみ変更したい場合に対応するためにメモ化するという方法のようで、必要な再計算が一部のみであることが分かっていればこちらを採用した方がパフォーマンスは向上するのかもしれない。
随時更新予定だが、とりあえず今日はここまで。
- 投稿日:2019-03-01T08:27:21+09:00
【今日から携わる】Javascript入門(2)上級
Javascript入門(1)初級-中級
Javascript入門(2)上級←いまここ
Javascript入門(3)応用Javascript入門のため、Progateをやってみました
学習コース JavaScript Ⅳ
最後の実演(コンストラクタのオーバーライド)では、Animalクラスを継承したDogクラスで、コンストラクタに犬の種類を表すbreedプロパティを追加することができるようになります。
オブジェクトの復習
▼動物の情報を表すオブジェクトを作成してみましょう
実演// 定数animalを定義してください const animal = { name: "レオ", age: 3, greet: () => { console.log("こんにちは"); } }; // animalのnameプロパティの値を出力してください console.log(animal.name); // animalのgreetプロパティの関数を実行してください animal.greet();クラスとは
オブジェクトを量産する
Webサービスなどでは、先ほど作成したようなオブジェクトをいくつも扱っています。例えばProgateのようなログインが必要なサービスでは、ユーザー(利用者)に関するデータ(オブジェクト)を用いています。
これらのデータは毎回ゼロから作成していたら大変です。ここからは、似たようなデータを効率よく生成する方法を学習していきましょう。オブジェクトの設計図
ユーザーのデータをいくつも作成する場合、最初に「ユーザーを生成するための設計図」を用意し、その設計図をもとにユーザーのデータを生成していく、といったことができます。
クラス
「設計図」のことをJavaScriptでは「クラス」と呼びます。
図のように「class クラス名」とすることで新しくクラスを用意できます。なお、クラス名は基本的に大文字から始めるようにしましょう。▼Animalクラスを定義してください
実演class Animal { }インスタンスの生成
オブジェクトを生成するための設計図を用意できたので、その設計図から実際にオブジェクトを生成してみましょう。クラスからオブジェクトを生成するには、図のように「new クラス名()」とします。
クラスから生成したオブジェクトは特別にインスタンスと呼びます。また、AnimalクラスのインスタンスをAnimalインスタンスと呼びます。▼インスタンスを生成して出力する
実演class Animal { } // Animalクラスのインスタンスを定数animalに代入してください const animal = new Animal(); // 定数animalの値を出力してください console.log(animal);コンストラクタ(1)
設計図の中身を追加する
設計図(クラス)を用意し、それをもとにインスタンスを生成する方法を学習してきました。しかし、今はまだクラスに何も処理を追加していないため、白紙の設計図のような状態です。
次のスライドから、設計図に設定を追加する方法を学習していきましょう。コンストラクタとは
クラスにはコンストラクタと呼ばれる機能が用意されています。コンストラクタはインスタンスを生成するときに実行したい処理や設定を追加するための機能です。
まず、図のように、クラスの中括弧 { } 内に「constructor() { }」と記述します。コンストラクタの処理
図のように、コンストラクタの中には処理を記述することができます。
ここに書いた処理はインスタンスが生成された直後に実行されます。
大切なのは、インスタンスごとに毎回実行されるということです。以下の図では2回「new Animal()」としているので、その度にコンストラクタ内の処理が実行されます。▼コンストラクタを追加してみましょう。
実演class Animal { // コンストラクタを追加してください constructor(){ console.log("インスタンスを生成しました"); } } const animal = new Animal();コンストラクタ(2)
プロパティと値を追加する
コンストラクタの中で、生成したインスタンスに関する情報を追加してみましょう。
コンストラクタの中で「this.プロパティ = 値」とすることで、生成されたインスタンスにプロパティと値を追加することができます。▼まずはコンストラクタ内で値を追加してみましょう。
実演class Animal { constructor() { // nameの値に文字列「レオ」を代入してください this.name = "レオ"; // ageの値に数値の「3」を代入してください this.age = 3; } } const animal = new Animal(); // 「名前: 〇〇」となるように出力してください console.log("名前: " + animal.name); // 「年齢: 〇〇」となるように出力してください console.log("年齢: " + animal.age);コンストラクタ(3)
コンストラクタの引数に値を入れる
コンストラクタに引数として値を渡すには、「new クラス名()」の括弧「( )」内に値を追加します。
実演class Animal { // 引数に「name」と「age」を追加してください constructor(name,age) { // 「"レオ"」の代わりに引数nameの値を代入してください this.name = name; // 「3」の代わりに引数ageの値を代入してください this.age = age; } } // 引数に「"モカ"」と「8」を渡してください const animal = new Animal("モカ",8); console.log(`名前: ${animal.name}`); console.log(`年齢: ${animal.age}`);メソッド(1)
メソッドとはそのインスタンスの「動作」のようなものです。「名前」や「年齢」などの情報はプロパティで追加したのに対して、メソッドは「挨拶をする」「値を計算する」などの処理のまとまりを表します。
メソッドの使い方
メソッドは、そのクラスから生成したインスタンスに対して呼び出します。
具体的には、以下の図のように「インスタンス.メソッド名()」とすることでそのメソッドを呼び出し、処理を実行することができます。▼Animalクラス内にメソッドを追加してみましょう。
実演class Animal { constructor(name, age) { this.name = name; this.age = age; } // greetメソッドを追加してください greet(){ console.log("こんにちは"); } } const animal = new Animal("レオ", 3); console.log(`名前: ${animal.name}`); console.log(`年齢: ${animal.age}`); // animalに対してgreetメソッドを呼び出してください animal.greet();メソッド(2)
メソッド内で値を使う
では次に、「name」の値を用いて「名前は〇〇です」と出力するメソッドを作成してみましょう。
メソッド内でインスタンスの値を使用するには、「this」という特殊な値を用いて、「this.プロパティ名」とします。実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } // infoメソッドを追加してください info() { console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } const animal = new Animal("レオ", 3); animal.greet(); // animalに対してinfoメソッドを呼び出してください animal.info();メソッド内でのメソッド呼び出し
メソッド内で他のメソッドを呼び出すことも可能です。
以下の図のように、メソッド内で「this.メソッド名()」とすることで、同じクラスの他のメソッドを使うことができます。実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { // greetメソッドを呼び出してください this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } const animal = new Animal("レオ", 3); // 以下の行を消してください // animal.greet(); animal.info();継承
Animalクラスを使って、Dogクラスを作ろう
ここまで動物に関するデータを扱う「Animalクラス」を作成してきました。ここからは、犬のデータに特化した「Dogクラス」を作成していきましょう。
新しく作成するクラスが既存のクラスの一種である場合、「継承」という方法を用いることで非常に効率よく作業を進めることができます。継承とは
「継承」とは、すでにあるクラスをもとに、新しくクラスを作成する方法のことです。
例えば「Animalクラス」から「Dogクラス」を継承すると、「Animalクラス」の全ての機能を引き継いで、「Dogクラス」を作成することができます。継承の書き方
継承を用いてクラスを作成するには「extends」を用います。
「Animalクラス」を継承して「Dogクラス」を作成するには、図のように「class Dog extends Animal」と書きます。
また、継承では元となるクラスを親クラス(今回はAnimalクラス)、新しく作成するクラスを子クラス(今回はDogクラス)と呼びます。▼継承を覚えるため、新しくクラスを定義してください。
実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } // Animalクラスを継承してDogクラスを定義してください class Dog extends Animal { } const animal = new Animal("レオ", 3); animal.info();継承したクラスを使う
使えるメソッド
「Dogクラス」は「Animalクラス」のすべての機能を引き継いでいます。そのため、「Dogクラス」内にはまだ何もメソッドは定義されていませんが、「Animalクラス」に定義されている「infoメソッド」などを使用することができます。
▼継承したクラス(Dogクラス)を使ってみましょう
実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { } // 定数dogにDogクラスのインスタンスを代入してください const dog = new Dog("レオ",4); // dogに対してinfoメソッドを呼び出してください dog.info();メソッドの追加
継承して作成したクラスにも、これまでと同じようにメソッドを追加することができます。今回は犬の年齢を人間の年齢に換算する「getHumanAge」メソッドを用意してみましょう。
メソッドでは、関数と同じように戻り値を用いることができます。
以下の図では、「getHumanAge」メソッドの戻り値を、「humanAge」という定数に代入しています。子クラスで定義した独自のメソッドは、親クラスから呼び出すことはできません。以下のように、AnimalクラスのインスタンスからgetHumanAgeメソッドを呼び出すとエラーが発生してしまいます。
▼実演
・Dogクラスにメソッドを追加しましょう。
・定数humanAgeを定義し、定数dogに対してgetHumanAgeメソッドを呼び出した値を代入してください
・「人間年齢で〇〇歳です」と出力してください実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { // getHumanAgeメソッドを追加してください getHumanAge() { return this.age * 7; } } const dog = new Dog("レオ", 4); dog.info(); // 定数humanAgeを定義し、定数dogに対してgetHumanAgeメソッドを呼び出した値を代入してください const humanAge = dog.getHumanAge(); // 「人間年齢で〇〇歳です」と出力してください console.log(`人間年齢で${humanAge}歳です`);オーバーライド(1)
同名のメソッド
継承したクラスは、親クラスのメソッドと子クラスのメソッドの両方が使用できることがわかったかと思います。
では以下の図のように、Animalクラス(親クラス)にすでにあるメソッドと同じ名前のメソッドをDogクラス(子クラス)に定義すると、どちらのメソッドが呼び出されるでしょうか?親クラスと同じ名前のメソッドを子クラスに定義すると、子クラスのメソッドが優先して使用されます。
これは、子クラスのメソッドが親クラスのメソッドを上書きしていることから、オーバーライドと呼ばれます。▼演習
・infoメソッドを追加してください
・Dogクラスにもinfoメソッドを定義してオーバーライドし、人間年齢を表示しましょう。演習class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { // infoメソッドを追加してください info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } const dog = new Dog("レオ", 4); dog.info();オーバーライド(2)
コンストラクタのオーバーライド(1)
メソッドと同じように、コンストラクタもオーバーライドすることができます。例えば、子クラスにプロパティを追加したい場合などに用います。
ただし、コンストラクタをオーバーライドする際は1行目に「super()」と記述する必要があります。▼演習
Dogクラスでは、コンストラクタに犬の種類を表すbreedプロパティも追加してみましょう。演習class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { // constructorを追加してください constructor(name, age, breed) { super(name, age); this.breed = breed; } info() { this.greet(); console.log(`名前は${this.name}です`); // 「犬種は〇〇です」と出力してください console.log(`犬種は${this.breed}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } // 3つ目の引数に「"チワワ"」を渡してください const dog = new Dog("レオ", 4, "チワワ"); dog.info();Javascript入門(1)初級-中級
Javascript入門(2)上級←いまここ
Javascript入門(3)応用
- 投稿日:2019-03-01T08:27:21+09:00
【今日から携わる】Javascript入門のため、Progateをやってみた(2)
Javascript入門のため、Progateをやってみた(1)
Javascript入門のため、Progateをやってみた(2)←いまここ
Javascript入門のため、Progateをやってみた(3)学習コース JavaScript Ⅳ
最後の実演(コンストラクタのオーバーライド)では、Animalクラスを継承したDogクラスで、コンストラクタに犬の種類を表すbreedプロパティを追加することができるようになります。
オブジェクトの復習
▼動物の情報を表すオブジェクトを作成してみましょう
実演// 定数animalを定義してください const animal = { name: "レオ", age: 3, greet: () => { console.log("こんにちは"); } }; // animalのnameプロパティの値を出力してください console.log(animal.name); // animalのgreetプロパティの関数を実行してください animal.greet();クラスとは
オブジェクトを量産する
Webサービスなどでは、先ほど作成したようなオブジェクトをいくつも扱っています。例えばProgateのようなログインが必要なサービスでは、ユーザー(利用者)に関するデータ(オブジェクト)を用いています。
これらのデータは毎回ゼロから作成していたら大変です。ここからは、似たようなデータを効率よく生成する方法を学習していきましょう。オブジェクトの設計図
ユーザーのデータをいくつも作成する場合、最初に「ユーザーを生成するための設計図」を用意し、その設計図をもとにユーザーのデータを生成していく、といったことができます。
クラス
「設計図」のことをJavaScriptでは「クラス」と呼びます。
図のように「class クラス名」とすることで新しくクラスを用意できます。なお、クラス名は基本的に大文字から始めるようにしましょう。▼Animalクラスを定義してください
実演class Animal { }インスタンスの生成
オブジェクトを生成するための設計図を用意できたので、その設計図から実際にオブジェクトを生成してみましょう。クラスからオブジェクトを生成するには、図のように「new クラス名()」とします。
クラスから生成したオブジェクトは特別にインスタンスと呼びます。また、AnimalクラスのインスタンスをAnimalインスタンスと呼びます。▼インスタンスを生成して出力する
実演class Animal { } // Animalクラスのインスタンスを定数animalに代入してください const animal = new Animal(); // 定数animalの値を出力してください console.log(animal);コンストラクタ(1)
設計図の中身を追加する
設計図(クラス)を用意し、それをもとにインスタンスを生成する方法を学習してきました。しかし、今はまだクラスに何も処理を追加していないため、白紙の設計図のような状態です。
次のスライドから、設計図に設定を追加する方法を学習していきましょう。コンストラクタとは
クラスにはコンストラクタと呼ばれる機能が用意されています。コンストラクタはインスタンスを生成するときに実行したい処理や設定を追加するための機能です。
まず、図のように、クラスの中括弧 { } 内に「constructor() { }」と記述します。コンストラクタの処理
図のように、コンストラクタの中には処理を記述することができます。
ここに書いた処理はインスタンスが生成された直後に実行されます。
大切なのは、インスタンスごとに毎回実行されるということです。以下の図では2回「new Animal()」としているので、その度にコンストラクタ内の処理が実行されます。▼コンストラクタを追加してみましょう。
実演class Animal { // コンストラクタを追加してください constructor(){ console.log("インスタンスを生成しました"); } } const animal = new Animal();コンストラクタ(2)
プロパティと値を追加する
コンストラクタの中で、生成したインスタンスに関する情報を追加してみましょう。
コンストラクタの中で「this.プロパティ = 値」とすることで、生成されたインスタンスにプロパティと値を追加することができます。▼まずはコンストラクタ内で値を追加してみましょう。
実演class Animal { constructor() { // nameの値に文字列「レオ」を代入してください this.name = "レオ"; // ageの値に数値の「3」を代入してください this.age = 3; } } const animal = new Animal(); // 「名前: 〇〇」となるように出力してください console.log("名前: " + animal.name); // 「年齢: 〇〇」となるように出力してください console.log("年齢: " + animal.age);コンストラクタ(3)
コンストラクタの引数に値を入れる
コンストラクタに引数として値を渡すには、「new クラス名()」の括弧「( )」内に値を追加します。
実演class Animal { // 引数に「name」と「age」を追加してください constructor(name,age) { // 「"レオ"」の代わりに引数nameの値を代入してください this.name = name; // 「3」の代わりに引数ageの値を代入してください this.age = age; } } // 引数に「"モカ"」と「8」を渡してください const animal = new Animal("モカ",8); console.log(`名前: ${animal.name}`); console.log(`年齢: ${animal.age}`);メソッド(1)
メソッドとはそのインスタンスの「動作」のようなものです。「名前」や「年齢」などの情報はプロパティで追加したのに対して、メソッドは「挨拶をする」「値を計算する」などの処理のまとまりを表します。
メソッドの使い方
メソッドは、そのクラスから生成したインスタンスに対して呼び出します。
具体的には、以下の図のように「インスタンス.メソッド名()」とすることでそのメソッドを呼び出し、処理を実行することができます。▼Animalクラス内にメソッドを追加してみましょう。
実演class Animal { constructor(name, age) { this.name = name; this.age = age; } // greetメソッドを追加してください greet(){ console.log("こんにちは"); } } const animal = new Animal("レオ", 3); console.log(`名前: ${animal.name}`); console.log(`年齢: ${animal.age}`); // animalに対してgreetメソッドを呼び出してください animal.greet();メソッド(2)
メソッド内で値を使う
では次に、「name」の値を用いて「名前は〇〇です」と出力するメソッドを作成してみましょう。
メソッド内でインスタンスの値を使用するには、「this」という特殊な値を用いて、「this.プロパティ名」とします。実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } // infoメソッドを追加してください info() { console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } const animal = new Animal("レオ", 3); animal.greet(); // animalに対してinfoメソッドを呼び出してください animal.info();メソッド内でのメソッド呼び出し
メソッド内で他のメソッドを呼び出すことも可能です。
以下の図のように、メソッド内で「this.メソッド名()」とすることで、同じクラスの他のメソッドを使うことができます。実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { // greetメソッドを呼び出してください this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } const animal = new Animal("レオ", 3); // 以下の行を消してください // animal.greet(); animal.info();継承
Animalクラスを使って、Dogクラスを作ろう
ここまで動物に関するデータを扱う「Animalクラス」を作成してきました。ここからは、犬のデータに特化した「Dogクラス」を作成していきましょう。
新しく作成するクラスが既存のクラスの一種である場合、「継承」という方法を用いることで非常に効率よく作業を進めることができます。継承とは
「継承」とは、すでにあるクラスをもとに、新しくクラスを作成する方法のことです。
例えば「Animalクラス」から「Dogクラス」を継承すると、「Animalクラス」の全ての機能を引き継いで、「Dogクラス」を作成することができます。継承の書き方
継承を用いてクラスを作成するには「extends」を用います。
「Animalクラス」を継承して「Dogクラス」を作成するには、図のように「class Dog extends Animal」と書きます。
また、継承では元となるクラスを親クラス(今回はAnimalクラス)、新しく作成するクラスを子クラス(今回はDogクラス)と呼びます。▼継承を覚えるため、新しくクラスを定義してください。
実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } // Animalクラスを継承してDogクラスを定義してください class Dog extends Animal { } const animal = new Animal("レオ", 3); animal.info();継承したクラスを使う
使えるメソッド
「Dogクラス」は「Animalクラス」のすべての機能を引き継いでいます。そのため、「Dogクラス」内にはまだ何もメソッドは定義されていませんが、「Animalクラス」に定義されている「infoメソッド」などを使用することができます。
▼継承したクラス(Dogクラス)を使ってみましょう
実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { } // 定数dogにDogクラスのインスタンスを代入してください const dog = new Dog("レオ",4); // dogに対してinfoメソッドを呼び出してください dog.info();メソッドの追加
継承して作成したクラスにも、これまでと同じようにメソッドを追加することができます。今回は犬の年齢を人間の年齢に換算する「getHumanAge」メソッドを用意してみましょう。
メソッドでは、関数と同じように戻り値を用いることができます。
以下の図では、「getHumanAge」メソッドの戻り値を、「humanAge」という定数に代入しています。子クラスで定義した独自のメソッドは、親クラスから呼び出すことはできません。以下のように、AnimalクラスのインスタンスからgetHumanAgeメソッドを呼び出すとエラーが発生してしまいます。
▼実演
・Dogクラスにメソッドを追加しましょう。
・定数humanAgeを定義し、定数dogに対してgetHumanAgeメソッドを呼び出した値を代入してください
・「人間年齢で〇〇歳です」と出力してください実演class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { // getHumanAgeメソッドを追加してください getHumanAge() { return this.age * 7; } } const dog = new Dog("レオ", 4); dog.info(); // 定数humanAgeを定義し、定数dogに対してgetHumanAgeメソッドを呼び出した値を代入してください const humanAge = dog.getHumanAge(); // 「人間年齢で〇〇歳です」と出力してください console.log(`人間年齢で${humanAge}歳です`);オーバーライド(1)
同名のメソッド
継承したクラスは、親クラスのメソッドと子クラスのメソッドの両方が使用できることがわかったかと思います。
では以下の図のように、Animalクラス(親クラス)にすでにあるメソッドと同じ名前のメソッドをDogクラス(子クラス)に定義すると、どちらのメソッドが呼び出されるでしょうか?親クラスと同じ名前のメソッドを子クラスに定義すると、子クラスのメソッドが優先して使用されます。
これは、子クラスのメソッドが親クラスのメソッドを上書きしていることから、オーバーライドと呼ばれます。▼演習
・infoメソッドを追加してください
・Dogクラスにもinfoメソッドを定義してオーバーライドし、人間年齢を表示しましょう。演習class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { // infoメソッドを追加してください info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } const dog = new Dog("レオ", 4); dog.info();オーバーライド(2)
コンストラクタのオーバーライド(1)
メソッドと同じように、コンストラクタもオーバーライドすることができます。例えば、子クラスにプロパティを追加したい場合などに用います。
ただし、コンストラクタをオーバーライドする際は1行目に「super()」と記述する必要があります。▼演習
Dogクラスでは、コンストラクタに犬の種類を表すbreedプロパティも追加してみましょう。演習class Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } class Dog extends Animal { // constructorを追加してください constructor(name, age, breed) { super(name, age); this.breed = breed; } info() { this.greet(); console.log(`名前は${this.name}です`); // 「犬種は〇〇です」と出力してください console.log(`犬種は${this.breed}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } // 3つ目の引数に「"チワワ"」を渡してください const dog = new Dog("レオ", 4, "チワワ"); dog.info();Javascript入門のため、Progateをやってみた(1)
Javascript入門のため、Progateをやってみた(2)←いまここ
Javascript入門のため、Progateをやってみた(3)
- 投稿日:2019-03-01T08:24:27+09:00
【今日から携わる】Javascript入門(1)初級-中級
Javascript入門(1)初級-中級←いまここ
Javascript入門(2)上級
Javascript入門(3)応用Javascript入門のため、Progateをやってみました
学習コース JavaScript Ⅰ
JavaScriptを学ぼう
JavaScript(以下、JSと呼びます)はWeb開発において必須の存在です。現在、「ライブラリ」という便利な機能を集めたものが多く開発され、世界中で利用されています。
例えば、有名なJSのライブラリやプラットフォームには「jQuery」や「React」、「Node.js」などがあります。ここで学べること
・変数・定数(let・const)
・計算(+-*/)
・条件分岐(if・else・switch)変数・定数
変数とは
変数は、データ(値)の入れ物(箱)です。箱についている名前が「変数名」であり、箱の中に実際の値(文字列や数値など)が入っています。
変数は「let 変数名 = 値」として定義します。プログラミングの「=」は「等しい」という意味ではなく、「右辺を左辺に代入する」という意味です。「let」は「これから変数を定義します」という宣言で、その後ろに変数名を書き、値を代入します。
演習// 変数nameを定義し、「にんじゃわんこ」を代入してください let name = "にんじゃわんこ"; // 変数nameの値を出力してください console.log(name);定数とは
変数とよく似たものに、定数があります。
定数はletの代わりにconstを用いて定義します。変数は1度代入した値を更新することができましたが、定数は1度代入した値を変更することができません。
演習// 定数languageを定義してください const language = "フランス語"; // 定数languageの値を出力してください console.log(language); // 定数languageを用いて、「〇〇を話せます」と出力してください console.log(language + "を話せます");テンプレートリテラル
これまで文字列や定数の連結には、「+」記号を用いてきました。
ES6では、それ以外の方法として「テンプレートリテラル」という連結方法があります。テンプレートリテラルを用いると、下の図のように文字列の中に定数(変数)を埋め込むことができます。const name = "にんじゃわんこ"; console.log(`こんにちは、${name}さん`);文字列の中で「${定数}」とすることで、文字列の中に定数や変数を含めることができます。
この時、文字列全体をバッククォート(`)で囲む必要があります。計算
計算console.log(12 + 3); // 15 console.log(12 - 3); // 9 console.log(12 * 3); // 36 console.log(12 / 3); // 4 console.log(12 % 3); // 0 console.log(13 % 3); // 1 console.log("12" + "3"); // 123 console.log("やき" + "にく"); // やきにく省略基本 → 省略 X = X + 10 → X += 10 X = X - 10 → X -= 10 X = X * 10 → X *= 10 X = X / 10 → X /= 10 X = X % 10 → X %= 10条件分岐 if文
プログラミングにおいて重要な条件分岐について学びましょう。
プログラミングを学んでいると「ある条件が成り立つときだけある処理を行う」という場面が出てきます。このようなプログラムを条件分岐と言います。if文
条件分岐
例題if(条件式){ 処理 }実演const level = 12; // 条件式を「level > 10」とするif文を作ってください if (level > 10) { console.log("レベルが10より大きいです"); }真偽値と比較演算子(1)
条件分岐
const number = 12; if (number > 10) { console.log("numberが10より大きいです"); } ↓↓↓ const number = 12; // ここから変更 if (true > 10) { // ここまで変更 console.log("numberが10より大きいです"); }条件式は真偽値で置き代えられる
比較演算子の種類
a < b … aはbより小さい
a <= b … aはbより小さい、または等しい
a > b … aはbより大きい
a >= b … aはbより大きい、または等しい実演(真偽値と比較演算子(1))const age = 24; // 「age >= 20」を出力してください console.log(age >= 20); // 「age < 20」を出力してください console.log(age < 20); // ageの値が20以上の場合に、「私は20歳以上です」と出力してください if (age >= 20) { console.log("私は20歳以上です"); }真偽値と比較演算子(2)
比較演算子
等しいかを比べる
a === b … aとbが等しい
a !== b … aとbが異なる実演(真偽値と比較演算子(2))const password = "ninjawanko"; // passwordの値が"ninjawanko"の場合、「ログインに成功しました」と出力してください if (password === "ninjawanko") { console.log("ログインに成功しました"); } // passwordの値が"ninjawanko"でない場合、「パスワードが間違っています」と出力してください if (password !== "ninjawanko") { console.log("パスワードが間違っています"); }else
条件が成り立たない場合の処理
実演const age = 17; // 条件式が成り立たない場合に「私は20歳未満です」と出力してください if (age >= 20) { console.log("私は20歳以上です"); } else { console.log("私は20歳未満です"); }else if
条件を追加する
実演const age = 17; // ageの値が10以上20未満のとき、「私は20歳未満ですが、10歳以上です」と出力してください if (age >= 20) { console.log("私は20歳以上です"); } else if (age >= 10) { console.log("私は20歳未満ですが、10歳以上です"); } else { console.log("私は10歳未満です"); }switch文
if、else ifによる分岐が多く複雑になってしまった場合、switch文で書き換えるとシンプルで読みやすいコードにできます。
switch文の括弧内の式または変数がcaseの値と一致した時、そのブロックが実行されます。caseのどれにも一致しなかった時、defaultのブロックが実行されます。break
caseブロックの最後にはbreak命令を指定します。break命令は現在のブロックから脱出するための命令です。break命令がないと、後ろに続くcaseブロックが続けて実行されてしまいます。
```javascript:演習 switch文
const rank = 5;switch (rank) {
case 1:
console.log("金メダルです!");
break;
case 2:
console.log("銀メダルです!");
break;
case 3:
console.log("銅メダルです!");
break;
// defaultの処理を追加してください
default:
console.log("メダルはありません");
break;
}
```学習コース JavaScript Ⅱ
ここで学べること
・繰り返し処理(while・for)
・配列
・オブジェクト
・undefined
・総合演習繰り返し処理
while文
繰り返し処理を行うためにはwhile文というものを用います。
whileとは「~の間」という意味の英語です。
while文は下の図のように書き、「条件式がtrueの間、{ }内の処理を繰り返す」ことができます。 {}の後にセミコロンは不要です。定義while (条件式) { 処理 }//セミコロンは不要▼演習
繰り返し処理を用いて、1から100までの数字をコンソールに出力してみましょう。```javascript:演習 while
// 変数numberを定義してください
let number = 1;// while文を作成してください
while(number <= 100) {
console.log(number);
number += 1;
}### for文 繰り返し処理を行う方法として、while文以外にもfor文というものがあります。できることはwhile文と同じですが、while文に比べてシンプルに書くことができるのが特徴です。 ```js:for文の定義 for (変数の定義;条件式;変数の更新) { 処理 }//セミコロンは不要▼演習
for文を用いて、1から100までの数字を出力してください演習for (let number = 1; number <= 100; number++) { console.log(number); }for・if・else
繰り返し処理の応用
▼演習
for文を用いて1から100を順番に出力しましょう。
ただし、3の倍数のときは「3の倍数です」と出力してください。演習// for文を完成させてください for (let number = 1; number <= 100; number++) { // if文を用いて、numberが3の倍数の時に「3の倍数です」、そうでないときは数字を出力してください if (number % 3 === 0) { console.log("3の倍数です"); } else { console.log(number); } }配列
複数の値をまとめて管理するには、配列というものを用います。
果物の名前についての値がいくつもあるとき、それぞれを定数(変数)として定義するより、「果物の名前一覧」といったように関連する値をまとめて管理すると便利です。
配列は、[値1, 値2, 値3] のように作ります。配列に入っているそれぞれの値のことを要素と呼びます。
配列の使い方[要素1,要素2,要素3]//角かっこの中に、要素をコンマで区切る配列を定数に代入するconst fruits = ["apple","banana","orange"]; console.log(fruits);//["apple","banana","orange"]インデックス番号
配列は0から数える
const fruits = ["apple","banana","orange"]; console.log(fruits[0]);//apple console.log(fruits[2]);//orange配列と繰り返し
▼演習
for文を用いて、配列の要素をすべて出力してみましょう。演習const animals = ["dog", "cat", "sheep"]; // for文を用いて、配列の値を順にコンソールに出力してください for (let i = 0; i < 3; i++) { console.log(animals[i]); }length
配列.lengthとすることで、配列の要素数を取得できます。
演習const animals = ["dog", "cat", "sheep", "rabbit", "monkey", "tiger", "bear", "elephant"]; // lengthを用いて配列の要素の数を出力してください console.log(animals.length); // lengthを用いて条件式を書き換えてください for (let i = 0; i < animals.length; i++) { console.log(animals[i]); } ▼コンソール 8 dog cat sheep rabbit monkey tiger bear elephantオブジェクト
オブジェクトは配列と同じく複数のデータをまとめて管理するのに用いられます。
配列は複数の値を並べて管理するのに対して、オブジェクトはそれぞれの値にプロパティと呼ばれる名前をつけて管理します。//配列 [値1,値2,値3] //オブジェクト(要素はプロパティと値のセットのこと) {プロパティ1:値1,プロパティ2:値2}演習// 定数characterを定義し、指定されたオブジェクトを代入してください const character = {name: "にんじゃわんこ", age: 14}; // characterの値を出力してください console.log(character);▼演習
・定数characterに代入されているオブジェクトの、nameに対応する値を出力してください。
・定数characterに代入されているオブジェクトの、ageに対応する値を20に更新してください。
・定数characterの値をコンソールに出力してください。演習const character = {name: "にんじゃわんこ", age: 14}; // characterのnameの値を出力してください console.log(character.name); // characterのageの値を「20」に更新してください character.age = 20; // characterをコンソールに出力してください console.log(character);オブジェクトを要素に持つ配列(1)
配列の中のオブジェクトのプロパティの値を取り出すには、
「配列[インデックス番号].プロパティ名」と書きます。▼演習
・charactersに代入されている配列の、1つ目の要素をコンソールに出力してください。
・charactersの2つ目の要素の、nameプロパティに対応する値をコンソールに出力してください。演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 1000} ]; // charactersの1つ目の要素をコンソールに出力してください console.log(characters[0]); // charactersの2つ目の要素の「name」に対応する値をコンソールに出力してください console.log(characters[1].name);オブジェクトを要素に持つ配列(2)
配列と繰り返し処理
▼演習
演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 100}, {name: "ベイビーわんこ", age: 5}, ]; // for文を完成させてください for (let i=0; i < characters.length ; i++) { console.log("--------------------"); // 定数characterを定義してください const character = characters[i]; // 「名前は〇〇です」を出力してください console.log("名前は" + character.name + "です"); // 「〇〇歳です」を出力してください console.log(character.age + "歳です"); }undefined
存在しない要素を取得する
配列の存在しないインデックス番号の要素や、オブジェクトの存在しないプロパティの要素を取得しようとすると、undefined と出力されます。
undefined は特別な値で、値が定義されていないことを意味します。演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 100}, {name: "ベイビーわんこ", age: 5}, // 要素を追加してください {name: "とりずきん"} ]; for (let i = 0; i < characters.length; i++) { console.log("--------------------"); const character = characters[i]; console.log(`名前は${character.name}です`); console.log(`${character.age}歳です`); }演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 100}, {name: "ベイビーわんこ", age: 5}, {name: "とりずきん"} ]; for (let i = 0; i < characters.length; i++) { console.log("--------------------"); const character = characters[i]; console.log(`名前は${character.name}です`); // if文を追加してください if (character.age === undefined) { console.log("年齢は秘密です"); } else { console.log(`${character.age}歳です`); } }総合演習1
・定数
cafeの中のbusinessHoursの値に、2つの要素を代入してください。
・店名:〇〇となるように出力してください。
ただし、〇〇の部分は定数cafe内のnameに対応する値にしてください。
・営業時間: 〇〇から△△となるように出力してください。
ただし、〇〇の部分は定数cafe内のopeningに対応する値に、△△の部分は定数cafe内のclosingに対応する値にしてください。▼演習
演習const cafe = { name: "Progateカフェ", businessHours: { // businessHoursの値に指定されたオブジェクトを代入してください opening: "10:00(AM)", closing: "8:00(PM)" }, }; // 「店名:〇〇」を出力してください console.log(`店名:${cafe.name}`); // 「営業時間:〇〇から△△」を出力してください console.log(`営業時間:${cafe.businessHours.opening}から${cafe.businessHours.closing}`); ▼コンソール 店名:progateカフェ 営業時間:10:00(AM)から8:00(PM)総合演習2
最後に、おすすめメニューの情報を追加しましょう。
演習const cafe = { name: "Progateカフェ", businessHours: { opening: "10:00(AM)", closing: "8:00(PM)" }, // menusプロパティに配列を代入してください menus: ["コーヒー","紅茶","チョコレートケーキ"] }; console.log(`店名: ${cafe.name}`); console.log(`営業時間:${cafe.businessHours.opening}から${cafe.businessHours.closing}`); console.log(`----------------------------`); console.log("おすすめメニューはこちら"); // for文を用いて配列menusの中身を表示させてください for (let i = 0; i < cafe.menus.length; i++) { console.log(cafe.menus[i]); }学習コース JavaScript Ⅲ
このレッスンでは関数について学んでいきます。
関数とは
「いくつかの処理をまとめたもの」です。
「function()」と書き、その後ろの中括弧「{ }」の中にまとめたい処理を書くことで関数を用意することができます。
また、このように関数を用意することを「関数を定義する」と呼びますので覚えておきましょう。関数の呼び出し
数を定義しただけでは、その中の処理は実行されません。
図のように、関数を定義した際に使用した定数名を用いて、「定数名()」と書くことで関数の中の処理を実行できます。このことを「関数を呼び出す」と言います。実演const greet = function() { console.log("こんにちは!"); console.log("関数を学習していきましょう!"); }; // 関数を呼び出してください greet();関数の代入
▼定数helloを定義し、関数を代入してください。
実演// 定数helloに関数を代入してください const hello = function() { console.log("こんにちは!"); console.log("私の名前はabenoです"); }; // 定数helloに代入された関数を呼び出してください hello();アロー関数(1)
() => {
function() {実演// 定数greetにアロー関数を代入してください const great = () => { console.log("こんにちは!"); } // 定数greetを呼び出してください great();引数とは
引数(ひきすう)とは関数に与える追加情報のようなものです。
関数を呼び出すときに一緒に値を渡すことで、関数の中でその値を利用することができます。定義const 定数 = (引数名) => { //処理 };実演// 関数の引数にnameを追加してください const greet = (name) => { // 「こんにちは、〇〇さん」となるように出力してください console.log(`こんにちは、${name}さん`); }; // greetの引数に「ひつじ仙人」を渡して呼び出してください greet("ひつじ仙人");複数の引数を受け取る関数
関数は引数を複数受け取ることもできます。()の中に受け取る引数をコンマ(,)で区切って並べることで、複数の引数を指定することができます。
引数は、左から順番に「第1引数、第2引数、...」と呼びます。▼関数に複数の引数を渡してみましょう。
実演// 関数の引数にnumber1とnumber2を追加してください const add = (number1,number2) => { // number1とnumber2を足した値をコンソールに出力してください console.log(number1 + number2); }; // 引数に5と7を渡して関数を呼び出してください add(5,7);戻り値とは
ここからは、関数の処理結果を呼び出し元で受け取る方法を学びます。
呼び出し元で受け取る処理結果を戻り値(もどりち)と呼び、このことを「関数が戻り値を返す」と言います。実演const half = (number) => { // numberを2で割った値を戻り値として返してください return number / 2; }; // 定数resultを定義してください const result = half(130); // 「130の半分は〇〇です」となるように出力してください console.log(`130の半分は${result}です`);様々な戻り値
実演const check = (number) => { // numberが3の倍数かどうかを戻り値として返してください return number % 3 === 0; }; // if文の条件式で、checkを呼び出してください if (check(123)) { console.log("3の倍数です"); } else { console.log("3の倍数ではありません"); }関数の中の定数
関数の引数や、関数内で定義した定数は、その関数の中でしか使うことができません。
実演// 定数nameを定義してください const name = "にんじゃわんこ"; const introduce = (name) => { // 「わたしは〇〇です」を出力してください console.log(`わたしは${name}です`); }; // 関数introduceを呼び出してください introduce("ひつじ仙人"); // 定数nameの値を出力してください console.log(name);総合演習
関数を用いて、アメリカドルの商品を日本円に直す計算をしましょう。
実演// 定数dollarYenRateに110を代入してください const dollarYenRate = 110; // アメリカドルを日本円に換算する関数convertToYenを作成してください const convertToYen = (priceDollar) => { return priceDollar * dollarYenRate; }; const information = (name, price) => { console.log(`アメリカドルで${name}は${price}ドルです`); // 定数priceYenを用意し、関数convertToYenを呼び出したものを代入してください const priceYen = convertToYen(price); // 「日本円で〇〇は△△円です」と出力してください console.log(`日本円で${name}は${priceYen}円です`); // 消さないでください console.log('--------------'); }; information("香水", 48); information("お菓子", 6);Javascript入門(1)初級-中級←いまここ
Javascript入門(2)上級
Javascript入門(3)応用
- 投稿日:2019-03-01T08:24:27+09:00
【今日から携わる】Javascript入門のため、Progateをやってみた(1)
Javascript入門のため、Progateをやってみた(1)←いまここ
Javascript入門のため、Progateをやってみた(2)
Javascript入門のため、Progateをやってみた(3)学習コース JavaScript Ⅰ
JavaScriptを学ぼう
JavaScript(以下、JSと呼びます)はWeb開発において必須の存在です。現在、「ライブラリ」という便利な機能を集めたものが多く開発され、世界中で利用されています。
例えば、有名なJSのライブラリやプラットフォームには「jQuery」や「React」、「Node.js」などがあります。ここで学べること
・変数・定数(let・const)
・計算(+-*/)
・条件分岐(if・else・switch)変数・定数
変数とは
変数は、データ(値)の入れ物(箱)です。箱についている名前が「変数名」であり、箱の中に実際の値(文字列や数値など)が入っています。
変数は「let 変数名 = 値」として定義します。プログラミングの「=」は「等しい」という意味ではなく、「右辺を左辺に代入する」という意味です。「let」は「これから変数を定義します」という宣言で、その後ろに変数名を書き、値を代入します。
演習// 変数nameを定義し、「にんじゃわんこ」を代入してください let name = "にんじゃわんこ"; // 変数nameの値を出力してください console.log(name);定数とは
変数とよく似たものに、定数があります。
定数はletの代わりにconstを用いて定義します。変数は1度代入した値を更新することができましたが、定数は1度代入した値を変更することができません。
演習// 定数languageを定義してください const language = "フランス語"; // 定数languageの値を出力してください console.log(language); // 定数languageを用いて、「〇〇を話せます」と出力してください console.log(language + "を話せます");テンプレートリテラル
これまで文字列や定数の連結には、「+」記号を用いてきました。
ES6では、それ以外の方法として「テンプレートリテラル」という連結方法があります。テンプレートリテラルを用いると、下の図のように文字列の中に定数(変数)を埋め込むことができます。const name = "にんじゃわんこ"; console.log(`こんにちは、${name}さん`);文字列の中で「${定数}」とすることで、文字列の中に定数や変数を含めることができます。
この時、文字列全体をバッククォート(`)で囲む必要があります。計算
計算console.log(12 + 3); // 15 console.log(12 - 3); // 9 console.log(12 * 3); // 36 console.log(12 / 3); // 4 console.log(12 % 3); // 0 console.log(13 % 3); // 1 console.log("12" + "3"); // 123 console.log("やき" + "にく"); // やきにく省略基本 → 省略 X = X + 10 → X += 10 X = X - 10 → X -= 10 X = X * 10 → X *= 10 X = X / 10 → X /= 10 X = X % 10 → X %= 10条件分岐 if文
プログラミングにおいて重要な条件分岐について学びましょう。
プログラミングを学んでいると「ある条件が成り立つときだけある処理を行う」という場面が出てきます。このようなプログラムを条件分岐と言います。if文
条件分岐
例題if(条件式){ 処理 }実演const level = 12; // 条件式を「level > 10」とするif文を作ってください if (level > 10) { console.log("レベルが10より大きいです"); }真偽値と比較演算子(1)
条件分岐
const number = 12; if (number > 10) { console.log("numberが10より大きいです"); } ↓↓↓ const number = 12; // ここから変更 if (true > 10) { // ここまで変更 console.log("numberが10より大きいです"); }条件式は真偽値で置き代えられる
比較演算子の種類
a < b … aはbより小さい
a <= b … aはbより小さい、または等しい
a > b … aはbより大きい
a >= b … aはbより大きい、または等しい実演(真偽値と比較演算子(1))const age = 24; // 「age >= 20」を出力してください console.log(age >= 20); // 「age < 20」を出力してください console.log(age < 20); // ageの値が20以上の場合に、「私は20歳以上です」と出力してください if (age >= 20) { console.log("私は20歳以上です"); }真偽値と比較演算子(2)
比較演算子
等しいかを比べる
a === b … aとbが等しい
a !== b … aとbが異なる実演(真偽値と比較演算子(2))const password = "ninjawanko"; // passwordの値が"ninjawanko"の場合、「ログインに成功しました」と出力してください if (password === "ninjawanko") { console.log("ログインに成功しました"); } // passwordの値が"ninjawanko"でない場合、「パスワードが間違っています」と出力してください if (password !== "ninjawanko") { console.log("パスワードが間違っています"); }else
条件が成り立たない場合の処理
実演const age = 17; // 条件式が成り立たない場合に「私は20歳未満です」と出力してください if (age >= 20) { console.log("私は20歳以上です"); } else { console.log("私は20歳未満です"); }else if
条件を追加する
実演const age = 17; // ageの値が10以上20未満のとき、「私は20歳未満ですが、10歳以上です」と出力してください if (age >= 20) { console.log("私は20歳以上です"); } else if (age >= 10) { console.log("私は20歳未満ですが、10歳以上です"); } else { console.log("私は10歳未満です"); }switch文
if、else ifによる分岐が多く複雑になってしまった場合、switch文で書き換えるとシンプルで読みやすいコードにできます。
switch文の括弧内の式または変数がcaseの値と一致した時、そのブロックが実行されます。caseのどれにも一致しなかった時、defaultのブロックが実行されます。break
caseブロックの最後にはbreak命令を指定します。break命令は現在のブロックから脱出するための命令です。break命令がないと、後ろに続くcaseブロックが続けて実行されてしまいます。
```javascript:演習 switch文
const rank = 5;switch (rank) {
case 1:
console.log("金メダルです!");
break;
case 2:
console.log("銀メダルです!");
break;
case 3:
console.log("銅メダルです!");
break;
// defaultの処理を追加してください
default:
console.log("メダルはありません");
break;
}
```学習コース JavaScript Ⅱ
ここで学べること
・繰り返し処理(while・for)
・配列
・オブジェクト
・undefined
・総合演習繰り返し処理
while文
繰り返し処理を行うためにはwhile文というものを用います。
whileとは「~の間」という意味の英語です。
while文は下の図のように書き、「条件式がtrueの間、{ }内の処理を繰り返す」ことができます。 {}の後にセミコロンは不要です。定義while (条件式) { 処理 }//セミコロンは不要▼演習
繰り返し処理を用いて、1から100までの数字をコンソールに出力してみましょう。```javascript:演習 while
// 変数numberを定義してください
let number = 1;// while文を作成してください
while(number <= 100) {
console.log(number);
number += 1;
}### for文 繰り返し処理を行う方法として、while文以外にもfor文というものがあります。できることはwhile文と同じですが、while文に比べてシンプルに書くことができるのが特徴です。 ```js:for文の定義 for (変数の定義;条件式;変数の更新) { 処理 }//セミコロンは不要▼演習
for文を用いて、1から100までの数字を出力してください演習for (let number = 1; number <= 100; number++) { console.log(number); }for・if・else
繰り返し処理の応用
▼演習
for文を用いて1から100を順番に出力しましょう。
ただし、3の倍数のときは「3の倍数です」と出力してください。演習// for文を完成させてください for (let number = 1; number <= 100; number++) { // if文を用いて、numberが3の倍数の時に「3の倍数です」、そうでないときは数字を出力してください if (number % 3 === 0) { console.log("3の倍数です"); } else { console.log(number); } }配列
複数の値をまとめて管理するには、配列というものを用います。
果物の名前についての値がいくつもあるとき、それぞれを定数(変数)として定義するより、「果物の名前一覧」といったように関連する値をまとめて管理すると便利です。
配列は、[値1, 値2, 値3] のように作ります。配列に入っているそれぞれの値のことを要素と呼びます。
配列の使い方[要素1,要素2,要素3]//角かっこの中に、要素をコンマで区切る配列を定数に代入するconst fruits = ["apple","banana","orange"]; console.log(fruits);//["apple","banana","orange"]インデックス番号
配列は0から数える
const fruits = ["apple","banana","orange"]; console.log(fruits[0]);//apple console.log(fruits[2]);//orange配列と繰り返し
▼演習
for文を用いて、配列の要素をすべて出力してみましょう。演習const animals = ["dog", "cat", "sheep"]; // for文を用いて、配列の値を順にコンソールに出力してください for (let i = 0; i < 3; i++) { console.log(animals[i]); }length
配列.lengthとすることで、配列の要素数を取得できます。
演習const animals = ["dog", "cat", "sheep", "rabbit", "monkey", "tiger", "bear", "elephant"]; // lengthを用いて配列の要素の数を出力してください console.log(animals.length); // lengthを用いて条件式を書き換えてください for (let i = 0; i < animals.length; i++) { console.log(animals[i]); } ▼コンソール 8 dog cat sheep rabbit monkey tiger bear elephantオブジェクト
オブジェクトは配列と同じく複数のデータをまとめて管理するのに用いられます。
配列は複数の値を並べて管理するのに対して、オブジェクトはそれぞれの値にプロパティと呼ばれる名前をつけて管理します。//配列 [値1,値2,値3] //オブジェクト(要素はプロパティと値のセットのこと) {プロパティ1:値1,プロパティ2:値2}演習// 定数characterを定義し、指定されたオブジェクトを代入してください const character = {name: "にんじゃわんこ", age: 14}; // characterの値を出力してください console.log(character);▼演習
・定数characterに代入されているオブジェクトの、nameに対応する値を出力してください。
・定数characterに代入されているオブジェクトの、ageに対応する値を20に更新してください。
・定数characterの値をコンソールに出力してください。演習const character = {name: "にんじゃわんこ", age: 14}; // characterのnameの値を出力してください console.log(character.name); // characterのageの値を「20」に更新してください character.age = 20; // characterをコンソールに出力してください console.log(character);オブジェクトを要素に持つ配列(1)
配列の中のオブジェクトのプロパティの値を取り出すには、
「配列[インデックス番号].プロパティ名」と書きます。▼演習
・charactersに代入されている配列の、1つ目の要素をコンソールに出力してください。
・charactersの2つ目の要素の、nameプロパティに対応する値をコンソールに出力してください。演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 1000} ]; // charactersの1つ目の要素をコンソールに出力してください console.log(characters[0]); // charactersの2つ目の要素の「name」に対応する値をコンソールに出力してください console.log(characters[1].name);オブジェクトを要素に持つ配列(2)
配列と繰り返し処理
▼演習
演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 100}, {name: "ベイビーわんこ", age: 5}, ]; // for文を完成させてください for (let i=0; i < characters.length ; i++) { console.log("--------------------"); // 定数characterを定義してください const character = characters[i]; // 「名前は〇〇です」を出力してください console.log("名前は" + character.name + "です"); // 「〇〇歳です」を出力してください console.log(character.age + "歳です"); }undefined
存在しない要素を取得する
配列の存在しないインデックス番号の要素や、オブジェクトの存在しないプロパティの要素を取得しようとすると、undefined と出力されます。
undefined は特別な値で、値が定義されていないことを意味します。演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 100}, {name: "ベイビーわんこ", age: 5}, // 要素を追加してください {name: "とりずきん"} ]; for (let i = 0; i < characters.length; i++) { console.log("--------------------"); const character = characters[i]; console.log(`名前は${character.name}です`); console.log(`${character.age}歳です`); }演習const characters = [ {name: "にんじゃわんこ", age: 14}, {name: "ひつじ仙人", age: 100}, {name: "ベイビーわんこ", age: 5}, {name: "とりずきん"} ]; for (let i = 0; i < characters.length; i++) { console.log("--------------------"); const character = characters[i]; console.log(`名前は${character.name}です`); // if文を追加してください if (character.age === undefined) { console.log("年齢は秘密です"); } else { console.log(`${character.age}歳です`); } }総合演習1
・定数
cafeの中のbusinessHoursの値に、2つの要素を代入してください。
・店名:〇〇となるように出力してください。
ただし、〇〇の部分は定数cafe内のnameに対応する値にしてください。
・営業時間: 〇〇から△△となるように出力してください。
ただし、〇〇の部分は定数cafe内のopeningに対応する値に、△△の部分は定数cafe内のclosingに対応する値にしてください。▼演習
演習const cafe = { name: "Progateカフェ", businessHours: { // businessHoursの値に指定されたオブジェクトを代入してください opening: "10:00(AM)", closing: "8:00(PM)" }, }; // 「店名:〇〇」を出力してください console.log(`店名:${cafe.name}`); // 「営業時間:〇〇から△△」を出力してください console.log(`営業時間:${cafe.businessHours.opening}から${cafe.businessHours.closing}`); ▼コンソール 店名:progateカフェ 営業時間:10:00(AM)から8:00(PM)総合演習2
最後に、おすすめメニューの情報を追加しましょう。
演習const cafe = { name: "Progateカフェ", businessHours: { opening: "10:00(AM)", closing: "8:00(PM)" }, // menusプロパティに配列を代入してください menus: ["コーヒー","紅茶","チョコレートケーキ"] }; console.log(`店名: ${cafe.name}`); console.log(`営業時間:${cafe.businessHours.opening}から${cafe.businessHours.closing}`); console.log(`----------------------------`); console.log("おすすめメニューはこちら"); // for文を用いて配列menusの中身を表示させてください for (let i = 0; i < cafe.menus.length; i++) { console.log(cafe.menus[i]); }学習コース JavaScript Ⅲ
このレッスンでは関数について学んでいきます。
関数とは
「いくつかの処理をまとめたもの」です。
「function()」と書き、その後ろの中括弧「{ }」の中にまとめたい処理を書くことで関数を用意することができます。
また、このように関数を用意することを「関数を定義する」と呼びますので覚えておきましょう。関数の呼び出し
数を定義しただけでは、その中の処理は実行されません。
図のように、関数を定義した際に使用した定数名を用いて、「定数名()」と書くことで関数の中の処理を実行できます。このことを「関数を呼び出す」と言います。実演const greet = function() { console.log("こんにちは!"); console.log("関数を学習していきましょう!"); }; // 関数を呼び出してください greet();関数の代入
▼定数helloを定義し、関数を代入してください。
実演// 定数helloに関数を代入してください const hello = function() { console.log("こんにちは!"); console.log("私の名前はabenoです"); }; // 定数helloに代入された関数を呼び出してください hello();アロー関数(1)
() => {
function() {実演// 定数greetにアロー関数を代入してください const great = () => { console.log("こんにちは!"); } // 定数greetを呼び出してください great();引数とは
引数(ひきすう)とは関数に与える追加情報のようなものです。
関数を呼び出すときに一緒に値を渡すことで、関数の中でその値を利用することができます。定義const 定数 = (引数名) => { //処理 };実演// 関数の引数にnameを追加してください const greet = (name) => { // 「こんにちは、〇〇さん」となるように出力してください console.log(`こんにちは、${name}さん`); }; // greetの引数に「ひつじ仙人」を渡して呼び出してください greet("ひつじ仙人");複数の引数を受け取る関数
関数は引数を複数受け取ることもできます。()の中に受け取る引数をコンマ(,)で区切って並べることで、複数の引数を指定することができます。
引数は、左から順番に「第1引数、第2引数、...」と呼びます。▼関数に複数の引数を渡してみましょう。
実演// 関数の引数にnumber1とnumber2を追加してください const add = (number1,number2) => { // number1とnumber2を足した値をコンソールに出力してください console.log(number1 + number2); }; // 引数に5と7を渡して関数を呼び出してください add(5,7);戻り値とは
ここからは、関数の処理結果を呼び出し元で受け取る方法を学びます。
呼び出し元で受け取る処理結果を戻り値(もどりち)と呼び、このことを「関数が戻り値を返す」と言います。実演const half = (number) => { // numberを2で割った値を戻り値として返してください return number / 2; }; // 定数resultを定義してください const result = half(130); // 「130の半分は〇〇です」となるように出力してください console.log(`130の半分は${result}です`);様々な戻り値
実演const check = (number) => { // numberが3の倍数かどうかを戻り値として返してください return number % 3 === 0; }; // if文の条件式で、checkを呼び出してください if (check(123)) { console.log("3の倍数です"); } else { console.log("3の倍数ではありません"); }関数の中の定数
関数の引数や、関数内で定義した定数は、その関数の中でしか使うことができません。
実演// 定数nameを定義してください const name = "にんじゃわんこ"; const introduce = (name) => { // 「わたしは〇〇です」を出力してください console.log(`わたしは${name}です`); }; // 関数introduceを呼び出してください introduce("ひつじ仙人"); // 定数nameの値を出力してください console.log(name);総合演習
関数を用いて、アメリカドルの商品を日本円に直す計算をしましょう。
実演// 定数dollarYenRateに110を代入してください const dollarYenRate = 110; // アメリカドルを日本円に換算する関数convertToYenを作成してください const convertToYen = (priceDollar) => { return priceDollar * dollarYenRate; }; const information = (name, price) => { console.log(`アメリカドルで${name}は${price}ドルです`); // 定数priceYenを用意し、関数convertToYenを呼び出したものを代入してください const priceYen = convertToYen(price); // 「日本円で〇〇は△△円です」と出力してください console.log(`日本円で${name}は${priceYen}円です`); // 消さないでください console.log('--------------'); }; information("香水", 48); information("お菓子", 6);Javascript入門のため、Progateをやってみた(1)←いまここ
Javascript入門のため、Progateをやってみた(2)
Javascript入門のため、Progateをやってみた(3)
- 投稿日:2019-03-01T04:00:27+09:00
メルマガ風メールにデータを添付して送る
今回作成したのは、前回作成した記事「Googleスプレッドシート – メルマガ風一括送信」の応用です!
メールを一括配信したいけどメールにデータも添付する形で送りたいなぁ。。。って場合に活用ください!以下様々なGASの記事を紹介してます!
https://bzbot.work/紹介記事
今回紹介している記事は以下です!
https://bzbot.work/2019/02/14/spreadsheet-email/1.シート名:「メール詳細」を作成
前回同様送信する為のテンプレを作成します。
以下を参考に作成してみてください!『メール送信フォーマット』では、プログラムで値が変わる部分と固定で変わらない部分があります。
『③cc』と『④subject』は完全な固定、『⑤body』は『⑥本文』で作った文章と『①担当者名』の名前を関数でくっつけている為、ハイブリット形式。それ以外はGASで都度可変です!ちなみにハイブリット形式のところに入っている関数は以下
2.シート名:「メーリングリスト」を作成
メーリングリストはA列に担当者、B列にアドレスを入れて一覧を作ります。
※アドレスに空欄があるとエラーになるので、注意(今度気が向いたらエラー処理ちゃんと入れます..)GASvar sheet = SpreadsheetApp.getActiveSpreadsheet(); //「メーリングリスト」のシートをアクティブにして、シート情報をmListに取得 var mList = sheet.setActiveSheet(sheet.getSheetByName("メーリングリスト")); //「メール詳細」のシートをアクティブにして、シート情報をbTempに取得 var mTemp = sheet.setActiveSheet(sheet.getSheetByName("メール詳細")); function goGoM(){ //取得したmListの最終行を取得する var lastR = mList.getLastRow(); Logger.log("【lastR】:" + lastR); var cnt = lastR - 1; //lastRは最初から最後の行までをカウントしているので、対象となるデータが入っている個数をカウントしてしまってます。 var popUp = Browser.msgBox("送信確認!","送信対象数"+cnt+"件です。実行しますか?", Browser.Buttons.OK_CANCEL); if (popUp == 'ok') { //取得した最終行までの情報を配列で取得する var mlvalue = mList.getRange(1, 1, lastR, 2).getValues(); Logger.log("【mlvalue】:" + mlvalue); var folder = DriveApp.getFolderById('**************'); var Files = folder.getFilesByName('関数一覧.pdf'); var attachmentFiles = Files.next(); //lastRは6だが、1行目はタイトルなのでX回繰り返して欲しいため、iがより大きい場合は実行終了とする for(var i = 1; i < lastR; i++) { Logger.log("【mlvalue[" + i + "][0]】:" + mlvalue[i][0]); //mlvalueで取得した値を「内容テンプレ」の担当者名にセット mTemp.getRange(2, 2).setValue(mlvalue[i][0]); Logger.log("【mlvalue[" + i + "][0]】:" + mlvalue[i][1]); //mlvalueで取得した値を「内容テンプレ」のアドレスにセット mTemp.getRange(3, 2).setValue(mlvalue[i][1]); //宛先と担当者名をセットし終わったら送信情報を取得 var add = mTemp.getRange(3,2).getValue(); var cc = mTemp.getRange(4,2).getValue(); var mailSubject = mTemp.getRange(5,2).getValue(); var mailBody =mTemp.getRange(6,2).getValue(); //送信情報をセットして送信! GmailApp.sendEmail(add, mailSubject, mailBody,{attachments:attachmentFiles}); } Browser.msgBox("送信しました"); }else { Browser.msgBox("キャンセルしました"); } }ログ結果は以下です。
[19-02-14 02:46:01:657 JST] 【lastR】:6 [19-02-14 02:46:03:320 JST] 【mlvalue】:担当者名,アドレス,担当1,bzbot@bzbot.work,担当2,bzbot@bzbot.work,担当3,bzbot@bzbot.work,担当4,bzbot@bzbot.work,担当5,bzbot@bzbot.work [19-02-14 02:46:03:680 JST] 【mlvalue[1][0]】:担当1 [19-02-14 02:46:03:682 JST] 【mlvalue[1][0]】:bzbot@bzbot.work [19-02-14 02:46:05:599 JST] 【mlvalue[2][0]】:担当2 [19-02-14 02:46:05:601 JST] 【mlvalue[2][0]】:bzbot@bzbot.work [19-02-14 02:46:07:679 JST] 【mlvalue[3][0]】:担当3 [19-02-14 02:46:07:681 JST] 【mlvalue[3][0]】:bzbot@bzbot.work [19-02-14 02:46:09:614 JST] 【mlvalue[4][0]】:担当4 [19-02-14 02:46:09:616 JST] 【mlvalue[4][0]】:bzbot@bzbot.work [19-02-14 02:46:10:988 JST] 【mlvalue[5][0]】:担当5 [19-02-14 02:46:10:991 JST] 【mlvalue[5][0]】:bzbot@bzbot.workコードの解説です。
GASfunction goGoM(){ //取得したmListの最終行を取得する var lastR = mList.getLastRow(); Logger.log("【lastR】:" + lastR); var cnt = lastR - 1; //lastRは最初から最後の行までをカウントしているので、対象となるデータが入っている個数をカウントしてしまってます。 var popUp = Browser.msgBox("送信確認!","送信対象数"+cnt+"件です。実行しますか?", Browser.Buttons.OK_CANCEL); if (popUp == 'ok') { //TRUE(YES)を押されたときの挙動を記載 }else { //FALSE(NO)を押されたときの挙動を記載 Browser.msgBox("キャンセルしました"); } }配信する情報を取得した最終行までのリスト情報を変数 mlvalue へ取得します。
メールに添付するファイルを探します。DriveApp.getFolderById('**************');でGoogleDrive内にあるデータのフォルダIDを指定します。
※フォルダIDは以下場所の箇所に記載されているIDをセットします取得したIDにあるファイル名(関数一覧.pdf)に一致するデータを探します。
GAS//取得した最終行までの情報を配列で取得する var mlvalue = mList.getRange(1, 1, lastR, 2).getValues(); Logger.log("【mlvalue】:" + mlvalue); var folder = DriveApp.getFolderById('**************'); var Files = folder.getFilesByName('関数一覧.pdf'); var attachmentFiles = Files.next();最後に送信処理の記述です。
for文で最終行で取得した値まで処理を繰り返しながら、取得した値を一つずつ取り出してスプレッドシートにセットします。スプレッドシートにセットしたらその情報でGmailApp.sendEmail()関数に引数を渡します。この渡すタイミングでデータファイルの指定を『{attachments:attachmentFiles}』このような形で指定する必要があります。『{attachments:こっちは取得したファイル名がセットされた変数名}』
GAS//lastRは6だが、1行目はタイトルなのでX回繰り返して欲しいため、iがより大きい場合は実行終了とする for(var i = 1; i < lastR; i++) { Logger.log("【mlvalue[" + i + "][0]】:" + mlvalue[i][0]); //mlvalueで取得した値を「内容テンプレ」の担当者名にセット mTemp.getRange(2, 2).setValue(mlvalue[i][0]); Logger.log("【mlvalue[" + i + "][0]】:" + mlvalue[i][1]); //mlvalueで取得した値を「内容テンプレ」のアドレスにセット mTemp.getRange(3, 2).setValue(mlvalue[i][1]); //宛先と担当者名をセットし終わったら送信情報を取得 var add = mTemp.getRange(3,2).getValue(); var cc = mTemp.getRange(4,2).getValue(); var mailSubject = mTemp.getRange(5,2).getValue(); var mailBody =mTemp.getRange(6,2).getValue(); //送信情報をセットして送信! GmailApp.sendEmail(add, mailSubject, mailBody,{attachments:attachmentFiles}); } Browser.msgBox("送信しました");このような形で指定した件名、宛先、本文、添付ファイルと、実行が完了しました!
添付したファイルはしっかりと開いて閲覧することも問題なかったので完了です!
- 投稿日:2019-03-01T00:37:21+09:00
Angularの公式チュートリアルを実践してみた。第1回目
Angularの公式チュートリアルを実践してみた。第1回目
昨日に引き続き、Angularの勉強をしていきます。
この記事は公式ページのチュートリアルの内容を実践したログです。
昨日は導入編で、今回から実際にチュートリアルアプリケーションを作成していきます。この記事は数回に分けて進めていきたいと思います。
前提
前回の記事を実行済みであること。
参考:angular.jsよりもAngularを推奨してもらったので、さっそく勉強してみた。
https://qiita.com/ryuutamaehara/items/fa93f5c25f36167124a7何をつくるのか
ツアー・オブ・ヒーローズ チュートリアルという名前のアプリケーションです。
どんな機能があるかは公式ページの説明をご参照ください。参考:https://angular.jp/tutorial
公式サンプル:https://angular.jp/generated/live-examples/toh-pt6/stackblitz.htmlこのチュートリアルをやり切ると次のことが出来るようようです。
・要素を表示・隠蔽する。
・データのリストを表示するための組み込みAngularディレクティブを使う。
・リストの詳細やリストを表示するためのAngularコンポーネントを作成する。
・読み取り専用データのための単方向データバインディングを使用する。
・双方向データバインディングを用いて、モデルを更新するための編集可能なフィールドを設置する。
・キー入力やクリックといったユーザーのイベントに対しコンポーネントがもつメソッドをバインドする。
・ユーザーがマスターリストから要素を選択し、詳細画面でその要素を編集できるようにする。
・パイプによりデータを整形する。
・リストを組み立てるための共有サービスを作成する。
・さまざまなビューとそれらのコンポーネント間を遷移可能にするためにルーティングを使用する。コンポーネントを作って様々な処理ができるって事ですね。きっと。(適当)
最初は昨日やった内容のおさらいです。
新しいワークスペースと初期アプリケーションリンクを作成する。
さて、まずはワークスペースと初期アプリを作ります。
作成が完了したらアプリケーションをそのままサーブします。ng new angular-tour-of-heroes cd angular-tour-of-heroes ng serve --open正常に完了するとブラウザにページが表示されます。
この表示されているページはアプリケーションシェルで、AppComponentというAngularコンポーネントから
操作されるとのことです。コンポーネントはAngularの基礎的な構成要素で、画面にデータを表示してユーザからの入力を待ち受け、入力に対して
リアクションを返すことができるらしいです。なるほど。よくわからん。
ひとまず進めてみます。アプリケーションを変更する
AppComponentシェルとやらはどうやら3つのファイルに分割されているみたいです。
ファイルはsrc/appに格納されています。・app.component.ts
TypeScriptで書かれたコンポーネントクラスのコードです。・app.component.html
HTMLで書かれたコンポーネントのテンプレートです。・app.component.css
このコンポーネント専用のCSSです。まずはapp.component.tsから編集します。
タイトルを書き替えます。app.component.tsimport { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'Tour of Heroes'; }正直TypeScriptってよくわかってないけど、おそらくAppComponentっていうクラスの
titleっていう属性名前をセットしたんだなーってことはわかりました。(´・ω・`)次にapp.component.htmlを編集します。
h1タグに囲まれた部分を編集します。app.component.html<!--The content below is only a placeholder and can be replaced.--> <div style="text-align:center"> <h1> <h1>{{title}}</h1> </h1> <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="> </div> <h2>Here are some links to help you start: </h2> <ul> <li> <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2> </li> <li> <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2> </li> <li> <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2> </li> </ul> <router-outlet></router-outlet>h1タグが2重の波括弧で囲まれています。
これは補間バインディングの構文らしいです。これにより先ほど編集したapp.component.tsのtitleにセットした値が渡されるようです。
次はcssの設定をします。
src/style.css/* Application-wide Styles */ h1 { color: #369; font-family: Arial, Helvetica, sans-serif; font-size: 250%; } h2, h3 { color: #444; font-family: Arial, Helvetica, sans-serif; font-weight: lighter; } body { margin: 2em; } body, input[type="text"], button { color: #888; font-family: Cambria, Georgia; } /* everywhere else */ * { font-family: Arial, Helvetica, sans-serif; }文字色が青になったらOKです。
ここまでは昨日やった内容と大差ないので驚きませんね。ではここからが今日の勉強の本番です。
新しくコンポーネントを作る。
今日からの内容を学習するために新しいコンポーネントを作成します。
ng generate component heroesこのコマンドを実行すると新しくsrc/app/配下にheroesコンポーネントが作成されます。
この中のheroes.component.tsについて内容を確認してみます。
heroes.component.tsimport { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { constructor() { } ngOnInit() { } }まず、コアライブラリからComponentをインポートします。
さらに@Componentでコンポーネントクラスに注釈をつけています。
これにより、メタデータを定義することができるようです。CLIによって自動で3つのプロパティが作成されていますので確認していきます。
・selector
コンポーネントのCSS要素セレクター。
つまりapp-rootがCSS要素のセレクターになりますね。・templateUrl
コンポーネントのテンプレートファイルの場所。
カレントディレクトリのapp.component.htmlが指定されています。・styleUrls
コンポーネントのプライベートCSSスタイルの場所
カレントディレクトリのapp.component.cssが指定されています。ngOnInit()はライフサイクルフックと呼ばれるもので、Angularがコンポーネントを作成した直後に
呼び出されるため、初期化ロジックを設置するのに適しているとのことです。では次にプロパティを追加してみます。
windstormeっていうヒーローのために…ってありますが、好きな名前を付けましょう。僕の幼少期のヒーローはティーンエイジ・ミュータント・ニンジャ・タートルズのドナテロでした。(TEMNT Donatello)でした。
なので、このヒーローのためにプロパティを作ります。heroes.component.tsimport { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { hero = 'Donatello'; constructor() { } ngOnInit() { } }プロパティを追加したら、次にhtmlにheroプロパティへのデータバインディングを追加します。
heroes.component.html<p> {{hero}} </p>ここまでできたら、次は作成したHeroComponentをAppComponentのテンプレートに追加します。
app-heroesはHeroesComponentの要素セレクタらしいです。AppComponentのテンプレートファイルのタイトルの直下に要素を追加してみましょう。
再度サーブします。今日はここまで!途中経過ですが、作成したコンポーネントはGitHubにpushしておきます。
参考:https://github.com/ryuutamaehara/Angular-tutorial
明日以降は「HeroesComponent ビューを表示する」の項目からスタート予定です。
眠い(:3 」∠)
- 投稿日:2019-03-01T00:21:14+09:00
Node.jsを使って、特定のツイートをしている人をフォローする
お馴染みのtwitterモジュールを使います。
フォローのAPIの公式ドキュメントはこちら(POST friendships/create)
使うメソッド
.stream('statuses/filter')で特定ツイートを検索.post('friendships/create')でフォローします。フォローする際はユーザーのIDかスクリーンネームを渡してあげる模様です。
フォロー関連はなかなかサンプルが見つからないですね。
筋トレのハッシュタグでツイートしている人をフォローするコードapp.js'use strict'; const twitter = require('twitter'); const client = new twitter({ consumer_key: '', // consumer keyを記入 consumer_secret : '', // consumer secretを記入 access_token_key : '', // access tokenを記入 access_token_secret : '' // access token secretを記入 }); // async/awaitで表現 const main = async () => { const stream = await client.stream('statuses/filter', {'track':'#筋トレ'}); stream.on('data', async data => { console.log(data.id_str); try { await client.post('friendships/create', {user_id:data.user.id}); console.log(`${data.user.name}さんをフォローしました。`); } catch (error) { console.log(error); } }); } main();最初ミスったエラー
こんな感じで
Cannot find specified user.というエラーメッセージがちょくちょく出てました。[ { code: 108, message: 'Cannot find specified user.' } ]こちらの記事を見て気づきましたが、
client.post('friendships/create', {user_id:data.id});という形で ユーザーのIDじゃなくてツイートのIDを指定してしまっていたのが原因だったみたいです。正しいのは、
client.post('friendships/create', {user_id:data.user.id});という指定ですね。参考































