Help us understand the problem. What is going on with this article?

JavaScript初心者がriot.jsを使い始めるまで

More than 3 years have passed since last update.

はじめに

Ruby on Railsを通してフロントエンドも何となくわかった気になっていたJavaScript初心者がイマドキのフレームワークを使い始めるまでに辿ったステップをまとめました。

前提となる今のスキルレベル

Ruby on Rilsは1年間やってきて基本的なことはわかるレベルだが、JavaScriptは全くの初心者。
フロントエンドはBootstrapでできる範囲で済ませ、Rails内で使うJavaScriptといえば<form>タグにremote='true'を設定してあれこれするだけでAjaxの処理が余裕でできる〜!で終わっていました。
この状態でJavaScriptを自分のスキルに入れていました。

もちろんコードはイチからは書けず、検索したものをコピペで済ます日々。
JSON,DOM...なんとなくはわかったつもりでいるけど具体的に何、そしてどう書くの?

最新のフレームワークに挑戦したいけど、どうやってRailsの環境で使うの?
山が高すぎて、、どこから登ろうかという状態でした。

そこから一歩ずつ進んでいき、約1か月強でRiot.jsを使い始めるまでの記録です。

さてどこから勉強しようか?

大まかに下記のようなステップで進めていきました

Step1. まずは素のJavaScriptを書いてみる
Step2. jQueryを使ってみる
Step3. 処理の用途別にファイルを分割する & デザインパターンを使ってみる
Step4. Riot.jsを使ってみる

3あたりがお腹いっぱいになります(*´3`)-з

Step1. まずは素のJavaScriptを書いてみる

常識なのだろうけど全くわからなかったのが JavaScriptって非同期処理なんだよという話

Railsの場合、一般的にソースコードの上から下へ順に処理を実行するののだが、JavaScriptの場合ユーザーからのアクション(イベント)に応じて処理を実行するため、どうもそうではないらしい。
その場合、1つの処理ならば問題ないが、複数の処理を実行させる場合、前の処理が終わった後に実行という(いわゆるコールバック)という記述の仕方になるので、工夫しないと入れ子が半端ないコールバック地獄というものに陥るらしい。はにゃー。
ということで幾つかのコールバック処理を書いてみて、非同期処理の流れをつかむことに。

Step1-1. コールバックの基本的な関数定義、呼び出しのサンプル

Railsの環境でviewを作って、、というはまだ仰々しかったので、使いこなしている人にアドバイスをもらいNode.jsの環境をnodebrewを使って構築し、
$ node app/assets/javascripts/sample.js(<= ファイルを指定)コマンドで実行

app/assets/javascripts/sample.js
/**
 * Sample1 (参照 http://qiita.com/nekoneko-wanwan/items/f6979f687246ba089a35)
 */
// 1. 関数の定義
var sayYellow = function(){
  console.log('黄色');
};

var sayName = function(myName){
  console.log(myName + 'の好きな果物です');
}

// 2. 呼び出しの定義
var sayBanana = function(aCallback){
  console.log('バナナは');
  aCallback();
};

// 3. 実行
sayBanana(sayYellow); // => バナナは黄色

// 引数に関数を渡して実行
sayBanana(function(){
  sayName('Hana'); // => バナナはHanaの好きな果物です
});

Step1-2. setTimeoutメソッドを使ったコールバック処理のサンプル

app/assets/javascripts/sample_timeout.js
// Sample_1: setTimeout基本の使い方
var example = function(){
  console.log('5秒後に実行!');
};
var executeConsolelog = function(_callback, _time){
  setTimeout(_callback, _time);
};
// 実行
executeConsolelog(example, 5000); // => 5秒後にexampleが表示される


/** Sample_2: setTimeoutの第1引数に無名関数のコールバックを渡す
 * 参照:http://easyramble.com/javascript-settimeout-callback-args.html
 */

var greet = 'Hello!',
    name = 'JavaScript';
// 実行
setTimeout( function() {
  showLog(greet, name)
}, 3000);

function showLog(a, b) { // 関数宣言なので後に定義してても巻き上げ実行される
  console.log(a + b);
};

/** Sample_3: setTimeoutとclearTimeoutの使い方
  * 参照 https://techacademy.jp/magazine/5541
  */

var cnt = 0;
var countUp = function(){
  console.log(cnt++);
};
// 実行
setTimeout(countUp, 1000); // => 0, 1秒後に1が表示

var i = 0;
var countUpTill5 = function(){
  console.log(i++);
  var id = setTimeout(countUpTill5, 1000);
  if(i > 5) {
    clearTimeout(id);
  }
};
// 実行
countUpTill5(); // => 0, 1秒ごとに1-5まで表示

文末のセミコロンが抜けてるとか発生するので、、

ここでJavaScriptの基本的な文法チェックをしてくれるのエディタのお便利機能を導入。
AtomユーザーなのでESLintを入れることに。
もっと細かい指摘もカスタマイズできるらしい。

とにかくデバッグしてみる

Google Chromeのデベロッパーツール(検証)でのデバッグを利用し始めました。
ソースファイルにブレイクポイント打ってステップ実行したり、console.logで変数を表示してみたり無限大です!

Step2. jQueryを使ってみる

jQueryを導入して、下記のステップでサンプルを作ってみた。
ポイントは一気に書きあげようとしないこと。少し書いては、実行して、また少し書いての繰り返しを行った方がはまりづらいのでベターということをハマった末実感しました。

Step2-1. 静的なJSONデータを使って、Ajaxを実行

GitHubのgistで静的なJSONファイルを用意し、読み込んだ内容をHTML側に表示するというサンプルを作成。
下記ステップで少しずつバージョンアップしていった。

  1. ページが読み込まれたらJSONのデータを取得してconsole.logで内容を表示してみる
  2. ページ読み込み時でなく、ユーザーからのイベント操作で1の処理を実行してみる
  3. 1の表示処理をconsole.logからidセレクターを設定した要素内に表示してみる
  4. 処理が終わった後に別の処置を呼び出してみる
userProfile.json(gist上で作成したJSONファイル)
[{
    "id": 1,
    "firstname": "Taro",
    "lastname": "Suzuki",
    "fruitsId": 2
}, {
    "id": 2,
    "firstname": "Hanako",
    "lastname": "Tanaka",
    "fruitsId": 1
}]
app/assets/javascripts/sample_jq.js
$(function(){
  var jsonFilePath = 'https://gist.githubusercontent.com/xxx/userProfile.json'; // gistJSONファイルへのpath

  $("#showConsoleLog").on("click", function () {
    $.ajax({
      url: jsonFilePath, // 通信するurl(必須)
      type: "GET", // POST or GET(デフォルトGET
      dataType: "json", // 受信時に期待するデータのタイプ
      success: function (items) { // 成功時に呼ばれるコールバック関数を指定(itemsはサーバから受信したJSONデータ)
        var i, max;
        for (i = 0, max = items.length; i < max; i++){
          console.log("こんにちは、" + items[i].firstname + " " + items[i].lastname + "さん");
        }
      }
    });
  });

Step2-2. DBから取得したJSONデータを使って、Ajaxを実行

  1. Railsでmodelからデータを取得し、JSON形式でview表示する(APIを自作する)
  2. 1のJSONデータをJavaScriptで表示

具体的なサンプルとして
「都道府県一覧のjsonデータを返すAPIを自作する」をやってみます

やったこと
scaffoldでapi/以下にアプリ名Prefecturecontroller, model, migrationを作成し、修正。
ルーティングも追加。

ポイント

  • modelに静的なデータを保存する場合、SQLのcreate文を走らせなくても、rubyの機能を使い、modelファイルへのallメソッド作成で対応した。active_hashなども使えそうだけど極力Gemだけに頼らない方法で実装したかったので。

  • viewをJSON形式で表示したい場合、jbuilderを使うと便利だった。

備考
一覧を表示するだけであればindexアクションのみで良いが、次のステップで詳細表示が必要な場合を加味してshowも記載してみました。
modelfindメソッド、controllershowアクション、ルーティング定義、show用のjbuilderファイルなど。

db/migrate/2017xxxx_create_prefectures.rb
class CreatePrefectures < ActiveRecord::Migration
  def change
    create_table :prefectures do |t|
      t.string :name
      t.string :furigana
      t.timestamps null: false
    end
  end
end
app/models/prefecture.rb
class Prefecture < ActiveRecord::Base
  class << self
    def all
      [
        {id: 1, name: '北海道', furigana: 'ほっかいどう'},
        {id: 2, name: '青森県', furigana: 'あおもりけん'},
        {id: 3, name: '岩手県', furigana: 'いわてけん' },
        {id: 4, name: '宮城県', furigana: 'みやぎけん' },
        {id: 5, name: '秋田県', furigana: 'あきたけん' }
      ] //とりあえず5件のデータを格納
    end

    def find(id)
      self.all.find{ |item| item[:id] == id.to_i }
    end

  end
end
app/controllers/api/prefectures_controller.rb
class Api::PrefecturesController < Api::ApplicationController
  def index
    @prefectures = Prefecture.all
  end

 def show
    @prefecture = Prefecture.find_by_id(params[:id])
  end

end
config/routes.rb
Rails.application.routes.draw do
  (省略)
  namespace :api, { format: 'json' } do
    resources :prefectures, only: [:index, :show]
  end
end
app/views/api/prefectures/index.json.jbuilder
json.prefectures do
  json.array! @prefectures do |prefecture|
    json.id prefecture[:id]
    json.name prefecture[:name]
    json.furigana prefecture[:furigana]
    json.towns prefecture[:towns] do |town|
      json.id town[:id]
      json.name town[:name]
    end
  end
end
app/views/api/prefectures/show.json.jbuilder
json.prefecture do
  json.id @prefecture[:id]
  json.name @prefecture[:name]
  json.furigana @prefecture[:furigana]
end

Step3. 処理の用途別にファイルを分割する & デザインパターンを使ってみる

ここで急にグイッとレベルが上がります。ちょっと詰め込みすぎ感ありますが、進みます(・ェ・)/

jQueryを使ってだいぶスッキリしてきたが、処理が増えることを考えて、デザインパターンを使った書き方に書き換える。
これを使うことで、Aの処理が終わったらBを実行するというコールバックの実行が複数件あってもスマートにかける。処理と処理の関係が密結合でなく疎結合なため、処理の内容に変更が生じた場合の修正範囲の最小化や、再利用性が高いらしい。
今回は、Observerパターンを基にしたPublishSubscribeパターンを導入

Rails & JavaScriptで実装する場合のイメージはこちらが図解されていてわかりやすい!

作成したサンプルファイルと用途はこちらです。

ファイル名 用途
prefectureView.js ユーザーのボタンクリックイベントでModelを呼び出す
prefectureModel.js DBと通信し(Ajax)、JSONデータを取得してくる
prefectureList.js JSONデータを受け取った後にDOMへ描画する
app.js prefectureView, PrefectureListをページロード時に呼び出し

その他のファイルたち

ファイル名 用途
jquery-pubsub.js jQueryを使ってPublishSubscribeパターンを実装できるファイル
application.js ファイルの読み込み順をケアする必要がある場合指定する

jquery-pubsub.jsはjquery-tiny-pubsub からダウンロードしてきて利用

app/assets/javascripts/admin/prefecture/app.js
$(document).ready(function() {
  var prefectureView = new PrefectureView();
  var prefectureList = new PrefectureList();
});
app/assets/javascripts/admin/prefecture/prefectureView.js
var PrefectureView = (function(){
  function PrefectureView(){
    var indexBtn = $("#displayPrefectureBtn"),
        _this = this;

    _this.model = new PrefectureModel();

    indexBtn.on('click', function(){
      _this.index();
    });
  }

  PrefectureView.prototype.index = function(){
    this.model.index();
  };

  return PrefectureView;
}());

app/assets/javascripts/admin/prefecture/prefectureModel.js
var PrefectureModel = (function() {
  function PrefectureModel(){
    var hostname = window.location.hostname,
        protocol = window.location.protocol,
        port = window.location.port;
    this.baseURL = [protocol, '//', hostname, ':', port, '/api'].join('');
  }

  PrefectureModel.prototype.index = function(){
    var params = {
        url: this.baseURL + '/prefectures.json',
        type: "GET",
        action: "index"
    };
    return this._request(params);
  };

  PrefectureModel.prototype._request = function(params){
    var deferred = $.ajax(params);
    deferred.done(function(prefectureData){
      prefectureData = deferred.responseJSON.prefectures;

      $.publish('prefecture.loaded', [ prefectureData ]); // 通知
    });
  };
  return PrefectureModel;
}());
app/assets/javascripts/admin/prefecture/prefectureList.js
var PrefectureList = (function(){
  function PrefectureList(){
    var _this = this;

    $.subscribe('prefecture.loaded', function(e, prefectureData){ // あらかじめ購読
      _this.draw(prefectureData);
    });
  }

  PrefectureList.prototype.draw = function(prefectureData){
    var i,
        len = prefectureData.length,
        prefectureElements = [],
        resultArea = $("#resultArea");

    for(i = 0; i < len; i++) {
      prefectureElements.push(
        '<li>' + prefectureData[i].id + ',' + prefectureData[i].name + '</li>'
      );
    }
    resultArea.html('<ul id="prefectureList">' + prefectureElements.join('') + '</ul>');
  };

  return PrefectureList;
}());

app/assets/javascripts/admin/jquery-pubsub.js
/* jQuery Tiny Pub/Sub - v0.7 - 10/27/2011
 * http://benalman.com/
 * Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */

(function($) {

  var o = $({});

  $.subscribe = function() {
    o.on.apply(o, arguments);
  };

  $.unsubscribe = function() {
    o.off.apply(o, arguments);
  };

  $.publish = function() {
    o.trigger.apply(o, arguments);
  };

}(jQuery));

ファイルの読み込み順を調整

app/assets/javascripts/admin/application.js
//= require jquery
//= require jquery_ujs
//= require twitter/bootstrap
//= require admin/jquery-pubsub
//= require admin/prefecture/prefectureView
//= require admin/prefecture/prefectureModel
//= require admin/prefecture/app
//= require_tree .

viewファイルには下記のみ記載しておく

app/views/admin/index.html.erb
<button id="displayPrefectureBtn">Display PrefectureSelect</button>
<div id="resultArea"></div>

Step4. Riot.jsを使ってみる

流行りのフレームワークはいろいろあるのだけど、初心者にも分かりやすいということで、Riot.jsを使うことにする。
公式ガイドにサンプルがあるので、まずこれを写経しながら少しずつ理解していっている状態です。
jQueryを使って作成した今までの処理をRiotを使って書き換えるイメージです。通信部分はjQueryのコードを利用する予定です。

まとめ

まだRiot.jsを使い始めたばかりですが、楽しいです。それも変に飛び級せずに基礎的なとこから一歩ずつ学んでいるからかなと思っています。
もやっとしていた部分がステップを進んで手を動かしていくうちに少しずつ腹落ちしてきている感があります。

ここまで来る際に何度もググったワード

  • 関数の種類
    関数宣言、無名関数、即時関数、、などの意味、書き方、その違い

  • 変数の種類
    ローカル、グローバル変数、インスタンス(メンバ)変数。。
    やっと最近インスタンス変数の使いどころがわかってきたところ

  • クラス

  • コンストラクタ

  • thisの指すもの、どこで別の変数に入れる必要があるか
    今もconsoleで検証しながら逐一確認している状態です。

皆さんもHappyなJavaScript Lifeを!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした