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

DOM操作を使った簡易的なtodoリストアプリを作ってみた

現在つよぽんさんの元でフロントエンド、バックエンドについて教わっています。今回はその中の課題の1つ「【エクササイズ】DOM操作をしてTodoリストを作成する【JavaScript】」に取り組んだ際の流れ、コードの説明をしていきたいと思います。

つよぽんさんのTwitterアカウント
学習サイト【Web白熱教室】
MENTA

制作物の完成サンプル

github


必要な機能

今回は各挙動を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();
  }
});

addEventListeneraddButtonをクリックした際の挙動を設定します。

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を足します。

Node.textContent【MDN】

Document.createElement【MDN】

デリートボタン、それとデリートボタンがクリックされた際の挙動もここで作成します。(入力したタスクに結び付けないといけないため)
button要素をcreateElementで作成して、デリートボタンがクリックされた際、deleteTodoを実行するようにします。

最後に、liElementとdeteleButtonをtodoListElementにappendChildで追加します。これで追加したタスクがページ上に表示されるようになります。

Node.appendChild【MDN】

todoの削除
const deleteTodo = index => {
  todos.splice(index, 1);
  showTodo();
};

最後にdeleteButtonをクリックした際に宣言されるdeleteTodoの作成です。
spliceを使い、インデックス値から1つ目の値、つまりdeleteButtonと結び付けられているタスク(liElement)を削除し、配列の中身を変更します。そして再度showTodoを宣言してタスク一覧を表示させます。今回は課題として関数を別で作るようにしましたが、直接addEventListenerに記述しても問題なく動作します。

Array.prototype.splice()【MDN】


つまづき

タスクを入力すると重複して表示される。

これはshowTodoで最初のwhile文を記述してなかったために起きた症状です。todoListElement内にどんどん溜まっていく一方ですから当たり前ですね。

while文の中身は、firstchildでtodoListElement内にタスクが入ってる(true)ことを判別した場合、removechildでtodoListElement内のfirstChildを削除し続けるループ処理を実行しています

Node.firstChild【MDN】

Node.removeChild【MDN】

削除ボタンを押したら必ず1番目のタスクが削除される。

単純にindex値の渡し忘れでした。

liElementとdeleteButtonを別々にtodoListElementに追加した場合改行される

todoListElement.appendChild(liElement);
todoListElement.appendChild(deleteButton);

とすると、タスクとボタンの間で改行が発生しました、この追加方法だとHTMLに直すと

<ul>
  <li>...</li>
  <button>...</button>
</ul>

このように、ulの中にlibuttonが別々に追加されるので、改行が発生します。なので

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' にマッチします。

とのことです。

正規表現【MDN】


今後してみたいこと

CSSを使った装飾

現状の見た目だとかなり味気ないので、webサイトとして見れる形にしたい。

ユーザー登録機能を使って個々にtodoを作れるようにする

上記に加え、Webアプリとして機能するようにしてみたい。


みていただきありがとうございました

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

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

【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("対象の行","対象の列","書き換える値")

コード例では、メーカー名が変わった場合に、車種の値を空白にする処理を入れている。
これが無いと、メーカー名、車種名を入力した後にメーカー名を変更した場合、意図しない組み合わせになってしまう。
(無い場合の例)
image.pngimage.png

setCellMeta

https://handsontable.com/docs/6.2.2/Core.html#setCellMeta

セルのメタデータを書き換えるためのメソッド。

setCellMeta("対象の行","対象の列","対象のプロパティ名","変更後の値")

コード例では、メーカー名の値に応じて車種のselectOptionsを書き換える処理を行っている。

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

Qiitaのページング仕様が変わった

タグ単位の最近の記事のページングが、これまでのRESTでitems?page=2のようにページ遷移する方式から、GraphQLを使って動的に書き換える方式に変更になった。

01.png

そのこと自体はまあいいのだが、それに伴い多大な不都合が発生した。

・適当にタグで検索
・次ページへの遷移ボタンを10連打。
・適当な記事を表示
・ブラウザバック

1ページ目に戻る。

さっきまで11ページ目を見てたんですけど?
続きを見るには10回クリックしなおさないといけないんですけど!!

何も考えずPWA化したときの典型的な弊害じゃん。
history.pushStateって突っ込むだけで回避できるのに。
新技術の導入によってユーザエクスペリエンスが劣化するのって、ほんと技術者の自己満足でしかないよね。

さらに、これまで使用されていたhttps://qiita.com/tags/javascript/items?page=2のようなURLは早々に使用できなくなっている。
つまり、2ページ目以降を直接URL指定で表示する方法がない。

スラドにもずっと前から言ってるんだけどさ、どうして普通のページングを用意してくれないんですかね1
そんなに古い記事は見せたくないんですかね。

あと記事タイトル長によって『次ページ』ボタンの位置がずれるから、次ページに行くつもりでうっかり記事を押して死ぬ可能性もある。
1記事の縦の長さを固定するか、ボタンの位置が一定になるよう調節するか、一覧の上にもボタンを付けるなどしたほうがいいだろう。

ちなみに一番見せたくないんじゃないかと思っている全記事一覧は、いまだ直接ページ指定に対応している。


  1. スラドは改善される予定。 

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

2019年の今 Mithril.js を使うためのTips

最近めっきり話を聞かなくなったJSフレームワーク、 Mithril.js に関する Tips をつらつらと書いていきます。
決して下火になったわけじゃあないんです。

Mithril.js とは

https://mithril.js.org/

いわゆる仮想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"),
}

これに加えて map filter reduce などのメソッドや、三項演算子を組み合わせることでよりコンポーネントをスマートに書けます。楽しくかっこよくコーディングしましょう。

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

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

自社サービスやってる会社に転職したいのでエンジャパンでプログラマ派遣会社を除外する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);         }     }); }); 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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変数自体を変えたいのであれば、成功例のように書いて頂くと良いかと思います。

さいごに

走り書きで恐縮ですが、読んでいただいてありがとうございました。
間違いや、分かりにくい部分があれば、お気軽に編集リクエスト出していただければ幸いです。

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

配列に配列を追加したら、勝手に二次元配列になる件

はじめに

配列に配列を追加しようと、定番の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変数自体を変えたいのであれば、成功例のように書いて頂くと良いかと思います。

さいごに

走り書きで恐縮ですが、読んでいただいてありがとうございました。
間違いや、分かりにくい部分があれば、お気軽に編集リクエスト出していただければ幸いです。

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

ジェネレータで再帰を制す

社内LT発表用資料
JavaScriptで説明していきますが、最後に見るようにPython, Rubyでもジェネレータは使えます。

ジェネレータとは?

例:自然数を無限に生成するジェネレータ

natural-number-generator.js
function* 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.js
function* 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.js
class 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.js
function 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重再帰

image.png

  • パスカルの三角形を素直にアルゴリズム化したのがこの実装
  • 自分自身を1回しか呼び出さない実装もあり、そっちの方が効率はいいけど、こっちの方が発想は素直かと

  • 問題点

    • 入力が大きくなると結果の配列が大きくなり、メモリを圧迫する
    • 50C10 == 10272278170 (102億!)
    • 試しにさっきのプログラムでやってみると返ってこないです

ジェネレータなら

  • さっきの関数とアルゴリズムは同じで、ジェネレータで書き換えたもの
combinations-generator.js
function* 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 source

PythonやRubyにもジェネレータはある

Python

natural-number-generator.py
def generate_natural_numbers():
    n = 1
    while True:
        yield n
        n += 1

for i in generate_natural_numbers():
    print(i)

Ruby

natural-number-generator.rb
generator = Enumerator.new {|yielder|
  n = 1
  loop do
    yielder.yield n
    n += 1
  end
}

generator.each do |n|
  puts n
end

まとめ

  • ジェネレータを使うと、巨大な組み合わせや探索をする再帰関数が効率的に書ける。楽に書ける
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

計算知能WolframAlphaからアメーバーのような幾何学生物を作成

計算機科学専用の検索エンジンWolframAlphaをご存知だろうか。私はこのWolframAlphaという存在を、2017年のSXSWでどこかのセッションで登壇者の方が説明しているときに知ったが、その時は何に使うんだろーと思っていた。それ以来忘れていたが、つい最近日本語版が公開されたという情報を偶然目にして、少し興味を持って調べてみた。。。のはこちらに書きました。

面白そうな図形

walframAlphaで面白そうな図形を発見したので、それをJavaScriptでうまく生成できないか試行錯誤してみました。こんな図形です。

スクリーンショット 2019-03-01 16.05.24.png

r = 4 + 0.5 * sin( 8 theta)

半径Rを割り出す式ですが、Θの角度に応じて円形を歪めています。
この式の構造としては以下の感じかと思います。

半径 = 半径の大きさ + 振幅 * sin( 周波数 * theta)

この式を元にp5.jsを使って描画してみます。

p5.js
    const 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);
    }

スクリーンショット 2019-03-01 16.17.29.png

周波数12に設定したので、12個の山が出来ています。周波数や振幅ドットの数や半径、Θの角度の間隔など、パラメータを変更するだけで、実に様々な形状を作ることができます。

これを基本原理として、アメーバージェネレーターを作りました。アメーバーに見えないかもしれないけど。。。
GitHubに置いておきます。
また、OpenProcessing上でも試すことができます。

Gellery

sample01.png sample02.png sample03.png sample04.png
sample05.png sample06.png sample07.png sample08.png
sample09.png sample10.png sample11.png sample12.png
sample13.png sample14.png sample15.png sample16.png
sample17.png sample18.png sample19.png sample20.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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.rb
def 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 複数の画像をコード三行で一つのカラムに保存する

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

全角数字・漢数字を一括して半角数字に変える

引き続き、スプレッドシートでフォーマットの統一されていない住所一覧を渡されてキレてる私の覚書です。

前回は、住所に含まれる色んな横棒をハイフンに変えました。→こちら
ただ、この横棒処理は、番地を表す数字があらかじめ半角になっていることが前提となっています。

というわけで、別途こんな感じの関数が必要かと思います。

code.js
function 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鷹 という具合になります。
なので、こちらは単体で使うよりは、他の色々な処理と組み合わせて使っていくことが多くなると思います。

他の色々な処理について、またあとで書きます。

【その他の、住所の表記ゆれ対策シリーズ】
色んな横棒をハイフンにする

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

React開発ノウハウメモ(随時更新)

少し前までAngularを使って開発してきましたが、年明けごろからReactを使い始めたので、自分なりのノウハウをまとめておきます。随時更新予定です。

使っている言語・ライブラリはReact, TypeScript, RxJSです。
RxJSはAngularで使っており慣れているのでReactでも引き続き使っていこうと試行錯誤しています。型定義の無いライブラリが多く面倒なので、なるべく生のReactで解決しようとしていますが、今後は状態管理に別のライブラリを使うかもしれません。


型付け

  • statepropsのメンバーは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でやっていた時と同じく、takeWhilealive変数を使って自動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 });
    });
  }

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

Reactによる開発ノウハウメモ(随時更新)

少し前までAngularを使って開発してきましたが、年明けごろからReactを使い始めたので、自分なりのノウハウをまとめていきたいと思います。

使っている言語・ライブラリはReact, TypeScript, RxJSです。RxJSはAngularで使っており慣れているのでReactでも引き続き使っていこうと試行錯誤しています。

型付け

  • statepropsのメンバーは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は呼ばれるので、内部変数の再計算をここで行い、一部のみ変更したい場合に対応するためにメモ化するという方法のようで、必要な再計算が一部のみであることが分かっていればこちらを採用した方がパフォーマンスは向上するのかもしれない。


随時更新予定だが、とりあえず今日はここまで。

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

【今日から携わる】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)応用

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

【今日から携わる】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)

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

【今日から携わる】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)応用

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

【今日から携わる】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)

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

メルマガ風メールにデータを添付して送る

今回作成したのは、前回作成した記事「Googleスプレッドシート – メルマガ風一括送信」の応用です!
メールを一括配信したいけどメールにデータも添付する形で送りたいなぁ。。。って場合に活用ください!

以下様々なGASの記事を紹介してます!
https://bzbot.work/

紹介記事
今回紹介している記事は以下です!
https://bzbot.work/2019/02/14/spreadsheet-email/

1.シート名:「メール詳細」を作成

前回同様送信する為のテンプレを作成します。
以下を参考に作成してみてください!

メールフォーマット - bzbot(ビジボット)

『メール送信フォーマット』では、プログラムで値が変わる部分と固定で変わらない部分があります。
『③cc』と『④subject』は完全な固定、『⑤body』は『⑥本文』で作った文章と『①担当者名』の名前を関数でくっつけている為、ハイブリット形式。それ以外はGASで都度可変です!

ちなみにハイブリット形式のところに入っている関数は以下

image.png

2.シート名:「メーリングリスト」を作成

メーリングリストはA列に担当者、B列にアドレスを入れて一覧を作ります。
※アドレスに空欄があるとエラーになるので、注意(今度気が向いたらエラー処理ちゃんと入れます..)

image.png

GAS
  var 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

コードの解説です。

GAS
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') {
//TRUE(YES)を押されたときの挙動を記載

  }else {
//FALSE(NO)を押されたときの挙動を記載
    Browser.msgBox("キャンセルしました");
  }
}

配信する情報を取得した最終行までのリスト情報を変数 mlvalue へ取得します。
メールに添付するファイルを探します。DriveApp.getFolderById('**************');でGoogleDrive内にあるデータのフォルダIDを指定します。
※フォルダIDは以下場所の箇所に記載されているIDをセットします

image.png

取得した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("送信しました");  

このような形で指定した件名、宛先、本文、添付ファイルと、実行が完了しました!
添付したファイルはしっかりと開いて閲覧することも問題なかったので完了です!

image.png

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

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.ts
import { 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.ts
import { 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.ts
import { 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 」∠)

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

Node.jsを使って、特定のツイートをしている人をフォローする

お馴染みのtwitterモジュールを使います。

フォローのAPIの公式ドキュメントはこちら(POST friendships/create)

使うメソッド

  • .stream('statuses/filter')で特定ツイートを検索
  • .post('friendships/create')でフォローします。

フォローする際はユーザーのIDかスクリーンネームを渡してあげる模様です。

フォロー関連はなかなかサンプルが見つからないですね。

https://www.npmjs.com/package/twitter

筋トレのハッシュタグでツイートしている人をフォローするコード

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を指定してしまっていたのが原因だったみたいです。

tweepyでツイート検索キーワードでフォローしたいがエラーで出る

正しいのは、client.post('friendships/create', {user_id:data.user.id});という指定ですね。

参考

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