こまぶろ

技術のこととか仕事のこととか。

Vue.jsで作る初めてのSPA〜Udemyの"The Ultimate Vue JS 2 Developers Course"のProject 2を受講した

初めてのSPAをVue.jsで作る

前々回の記事の続きです。

ky-yk-d.hatenablog.com

UdemyのThe Ultimate Vue JS 2 Developers CourseのProject 2を終えました。今回は、そちらの感想記事です。

www.udemy.com

3つのサンプルアプリケーションを作りながらVue.jsを学ぶこちらのコースの2番目のプロジェクトで作るのは、"Vue.js Cinema"。映画のポスターと詳細情報と上映時刻をAPIで取得し、一覧・フィルター・詳細表示するSPAです。講師の方が公開しているデモサイトは下のリンクにあります。

vuejs-cinema.vuejsdevelopers.com

f:id:ky_yk_d:20180616213744p:plain

1番目のプロジェクトで作成した"Vue.js Poster Store"との大きな違いは、今回のアプリケーションがVue routerを利用したSPAであることです。これまでSPAを作った経験がないので、初めてのSPAをVue.jsで作るということになります。

内容について

CSS(scss)はあらかじめ用意されているものを適用するだけですが、JavaScriptやHTMLはほぼ全て自分で1から記述します(API接続やユーティリティ関数は一部用意されているものを使います)。Vue.jsを学習するコースなので、ビューに関わる箇所は省略せずに全てのコードを映像の中で講師の方と一緒に記述していきます。

今回のプロジェクトでは、下記のようなこと(抜粋)を学び/使います。

  • DOM操作
  • イベント処理
  • Webpack
  • Vue.js devtools
  • SFC
  • Vue router
  • Vueインスタンスによるイベントバス

詳細な内容については、コースの内容一覧のページに記載してあります。細かく映像が区切られており、それぞれにその映像で学ぶ/実施する内容がわかるタイトルがついているので、どんな技術を使っているのかは受講しなくてもわかります。また、作成するソースコードGitHubに初期状態から完成形までが公開されています。区切り区切りでブランチが切られているので、復習するのにも便利です。ソースコードは受講していなくても見られるので、ご興味ある方は見てみてください。

github.com

受講して良いと思った点

今回もとてもわかりやすかったです。コンポーネントにせよSFCにせよVue routerにせよ、文法・使い方はドキュメントを読んでわかっていても、実際にどのように使うのか、何が嬉しいのかはよくわからず、自分でこれらを使ってアプリを作ってみようという気になれていませんでした。それが、今回のプロジェクトで実際に一からコードを書いていくなかで、「なるほど、こうやって使うんだな」というのがわかってきた気がします。受講していて良かったと感じたポイントをいくつか書き留めておきます。

巨大VueインスタンスからSFCへのリファクタリングを実践できる

今回のプロジェクトではWebpackを利用しているのですが、最初はエントリポイントであるsrc/main.jsファイル内のVueインスタンス(ルートインスタンス)に全てのVueのコードを記載していきます。ある程度まではそのまま続けていくのですが、途中で「これだとファイルが巨大になっちゃうからからリファクタリングしていこう」と子コンポーネントSFCに切り出していきます。モジュール化するといいよねというのは頭ではわかるのですが、実際に巨大なインスタンスを切り分けていく体験をするとリファクタリングする動機も方法もより実感できます。

相変わらず間違えるAnthonyさんにツッコミを入れたくなる

上の点に関連して、ファイル作成→コピー/カット&ペーストを繰り返して書き換えていくなかで、モジュールの相対パスを書き換えたり、モジュールのインポートを追加したりする必要が出てきます。その過程でも、前回書いたようにAnthonyさんは間違えます。わかっている人からすれば「何回同じ間違いしてんだよ」となるのでしょうが、やはり教材としては長所になっているのではと感じます。

これが最初からすんなり適宜修正を加えてくれてしまうと、映像を見ながら写経している側の印象に残らないのですが、ご丁寧に何度も何度も間違えるので、次第に写経しているこちらが先回りしてエラーが出ないようなコードを書くようになります。「またAnthonyさん同じ間違いしてるじゃん(笑)」となったらこれは教育としては最高の結果ですよね。

Vue.js以外のフロントエンド技術も学べる

これも個人的にはありがたい点です。上述のように、このプロジェクトではJavaScriptはほぼ自分で書きます。したがって、アプリケーションを作成していく中で用いられるVue.js以外のJavaScriptのライブラリや関数についても一緒に勉強することができます。もちろん、Vue.jsのコースなので深い解説まではされないのですが、「こういうものを使うよ」という断りはちゃんとされますし、参考資料として外部のドキュメントのURLなどを提示してくれるので、わからないところは自分で調べられるようになっています。一例としては以下のようなものを学びました。

  • ES6
  • JavaScriptの組み込みオブジェクト(StringやArrayなど)
  • 基本的なライブラリ(Moment.jsなど)
  • Webpackで用いるローダー(Style loaderなど)

「隅から隅まで知っているサンプルコード」を獲得できる

新しい言語やツールを学ぶときは、ドキュメントやサンプルコードを参考にしてどうにかこうにか作らざるを得ません。しかし、一度学んだ/使ったことのあるものを思い出す時には、自分が書き、理解しているコードを見るのが最前だと思っています。どうやって使うんだっけ?と思ったときに、公式のドキュメントだけでは応用方法がわからず、かといって他人のコードではその設計をまず理解しないとどのように使っているかはわからない、という経験をされたことがある方は多いのではないでしょうか。そういうときに、自分の書いたコードがあると、「ああ、そうだったそうだった」と思い出せます。Anthonyさんと一緒にコードを書いていくことで、この最強のサンプルコードを手に入れることができる。これがこのコースの大きな長所なのではないかと感じました。

おわりに

全部で14時間、3プロジェクトのコースも2/3まできました。だいぶ、Vue.jsの世界にも馴染めてきたような気がしています。Project 3ではGoogle Calendarのクローンを作りながら、サーバーサイドレンダリングなどを学べるようなので、楽しみながら学んでいきます!

フロントエンド弱者が腹を括ってWebpackに触ってみた

前回UdemyのVue.jsのコースについての記事を書きましたが、続きがなかなか視聴できていないので、今週はそれとは違う内容になります。少々お待ちください・・・。

さて、ブログを始めたくらいの時期からこれまで、フロントエンドの勉強を細々と続けてきました。

  • GitHub Pagesで静的Webサイトを公開したり

ky-yk-d.hatenablog.com

  • twitter:cardやog:imageを設定してみたり

ky-yk-d.hatenablog.com

  • Dynamo DBに繋いでみたり

ky-yk-d.hatenablog.com

と、少しずつ知識は広がってきていますが、その中で、チラチラと目に入りながらも避けてきたものがあります。

それは、Webpackです。

公式のホームページかっこいいですね。

webpack.js.org

単にWebサイトの基本やVue.jsの文法を学ぶだけなら、Webpackなどのモジュールバンドラーを使う必要はないのですが、

  • Vue.jsの勉強をしていると途中からvue-cliが導入されたり
  • 使おうとしているライブラリのサンプルがWebpack前提だったり

と、ここから先に進むにはブラウザでそのまま動くファイルだけでは限界があるなと感じ、Webpackの勉強を始めることにしました。

例によって、山田祥寛さんの書籍を見ながらぽちぽちやりまして、ソースコードGitHubに上げていっています(サンプルコードそのままではありません)。

速習webpack 速習シリーズ

速習webpack 速習シリーズ

github.com

まだ、書籍も途中なのですが、基本的なファイルのローダーを扱うところまでできましたのでまとめてみます。まだ理解が不十分なので、間違ったことを書いているかもしれません。お気づきの方はコメントくださると幸いです。

Webpackを導入する

Webpackとは何か

「モジュールバンドラー」です。JavaScriptスタイルシート、画像ファイルなどをまとめるツールとのことです。モジュールバンドラーを利用するメリットとしては、以下のようなものが挙げられています。

  • 依存関係の解決
  • リクエスト数の抑制
  • ソースの可読性の向上・グローバル汚染、名前の競合の回避

個人的な経験としても、htmlファイルの中に<script>タグでJSファイルを埋め込んでいくときに、記述の順番を間違えて動作しない(先に読み込んだJSから後に読み込むJSを利用していた)という経験をしており、これが増えてくるとヤバそうだと感じていました。かといって、ひとつのファイルに全部書き込むのは嫌なので、モジュールシステム、モジュールバンドラーの出番というわけですね。

モジュールバンドラーのなかでも、Webpackは広く採用されているものです。『速習Webpack』では、Webpackの長所として下記のものを挙げています。

3つ目の理由に関連したものかもしれませんが、採用実績が豊富であるため、試したいライブラリのサンプルが見つけやすいのもメリットではないかと感じています。

Webpackをインストールする

それでは、Webpackをインストールしていきます。npmを利用します。

ターミナル

> cd プロジェクトのパス
> npm
> npm init -y # -y は規定の値で設定ファイルを作成
# package.jsonが生成される
> npm install --save-dev webpack webpack-cli
# --save-dev はpackage.jsonにインストールするパッケージの情報を記録するオプション
# webpack-cli はwebpackコマンドを実行するために必要

package,jsonはNode.jsの設定ファイルで、依存関係などを記載しておくと別の環境で必要なライブラリを用意したいときに便利(npm installだけで構築できる)らしいです。

qiita.com

Webpackがインストールできたら、下記のような構成を作ります。

.
├── node_modules   ←npmでインストールしたライブラリが置かれる
│
├── dist ← バンドルしたファイルが出力される
│   └── index.html ←生成したJSファイルを呼び出すhtml
│
├── src ← Webpackによってバンドルされる対象のファイル
│   └── index.js ←エントリーポイント
│
├── package.json
│
└── webpack.config.js

このような構成を作って、npx webpackを実行すると、dist配下にmain.jsが生成されます。これがバンドルしたファイルです。npx [パッケージ名]で、ローカルのnode_modules内のパッケージを実行するコマンドです。

qiita.com

ちなみに、エントリーポイントと出力場所はデフォルトでそれぞれ、/src/index.js、/dist/main.jsになっているようですが、下記のような設定ファイルを作成することで変更することが可能のようです。このwebpack.config.jsは、ローダー等を追加する際にも必要になってきます。

webpack.config.js

module.exports = {
    mode: 'development', // productionを指定すると実行効率の良いコードを生成する
    entry: './src/index.js',
    output: {
        path: `${__dirname}/dist`,
        filename: 'main.js'
    },
/* 後略 */

開発サーバーの導入

静的Webサイトを作るだけであれば、上記のnpx webpackコマンドを実行してから/dist/index.htmlを開けばよいのですが、いちいちビルドするのはだるいので、開発サーバーを導入します。ホットリロードってやつですね。

ターミナル

npm install --save-dev webpack-dev-server
# 開発サーバーがインストールされる

webpack.config.js

module.exports = {
  /* 中略 */
  devServer: {
    contentBase: './dist'
  },
};

[プロジェクトルート]/dist配下のファイルがコンテンツとして公開されるということですね。

次に、開発サーバーを起動するためのコマンドにショートカットを設定しておきます。package.jsonを編集します。ここではビルド用のショートカットも設定しています。

package.json

"scripts": {
    "start": "webpack-dev-server  --open",
    "build": "webpack --config webpack.config.js"
},

こうしておくと、npm startコマンドで開発サーバが起動します。package.jsonscriptsに記載した内容はnpm run hogehogeで呼び出せるようになりますが、startは特殊らしく、単にnpm startで起動できるようになります。

ローダー

モジュールバンドラーとしてのWebpackのコアな機能は、JavaScriptのモジュールをバンドルすることなのですが、ローダーを用いるとCSSや画像ファイルをモジュール化して、Webpackのバンドルの対象とすることができます。

山田さん曰く、

webpackを学ぶということは、ローダーを学ぶことである

とのことです。書籍では様々なファイルに対応したローダー(スタイルシート、画像、Webフォント、CSVJSON、HTMLなど)を紹介していますが、スタイルシートに関わるものだけご紹介します。

スタイルシートのバンドル

css-loaderによってスタイルシートを読み込み、style-loaderによって<style>要素として埋め込みます。いずれも、npm installでインストールします。インストールしたローダーは、webpack.config.jsに記載します。

webpack.config.js

module.exports = {
/* 中略 */
    module: {
        rules: [
            // スタイルシートを処理するローダー
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
};

use要素で指定したローダーは、逆順に処理されるので、上記の設定によって

  • 拡張子が.cssのファイルに対して、
  • css-loaderでモジュール化して、
  • style-loaderでページに埋め込む

という処理が施されることになります。エントリーポイントである/src/index.jsから、import './hogehoge.css;という形でモジュールとして読み込むことができます。

外部ファイルとして出力する場合について

スタイルを<style>タグで埋め込むのではなく外部ファイルとして出力して利用したい場合には、style-loaderは使えません。『速習Webpack』では、この用途のために、extract-text-webpack-pluginを用いています。

github.com

しかし、これはまさにこの記事を執筆していて気づいたのですが、Webpack 4 以降ではこのプラグインは非推奨となっているようです。

docs(readme) WP4 deprecation for css. Link to MiniCSSExtractPlugin (#… · webpack-contrib/extract-text-webpack-plugin@0b69b72 · GitHub

現在では、mini-css-extract-pluginがWebpack 4 で利用されるべきプラグインとされています。こちらについては使ってみていないのですが、余裕があれば別途使用してみて記事にするかもしれません。

github.com

その他のファイルのバンドル

バンドル対象としたいファイル形式と、紹介されているローダーだけ記載しておきます。

  • 画像ファイルのバンドル
    url-loaderを用います
  • フォントファイルのバンドル
    file-loaderを用います
  • JSONファイルのバンドル
    気味の悪い拡張子のファイルですが、Webpackは標準対応しているのでローダー不要です
  • CSV/TSVファイルのバンドル
    csv-loaderを用います
  • HTMLファイルのバンドル
    html-loaderを用います
  • XMLファイルのバンドル
    xml-loaderを用います

おわりに

今回、WebpackをWebpackとして勉強してみて、使い方はなんとなくわかりました。ホットリロードが便利なので、これからは積極的にWebpackを用いて開発していこう!と思いました。

  • Node.js
  • モジュール
  • ES2015
  • Sass

などについても、Webpackを使いながら勉強していこうと思います。UdemyのコースでもWebpack使うみたいなので、楽しみですね。

目指せ!脱Vue.js初心者〜Udemyの"The Ultimate Vue JS 2 Developers Course"を始めた〜

脱初心者を目指して

Vue.jsを毎日粛々と書き続けております。基本文法や局所局所の機能は少しずつ理解し始めていますが、まだまだ初心者の域を出られていません。せめて、何かを作ろうと思ったときにVueで一通りのことはできるようになりたいと思っています。

この域に達するのには、やはり自分で苦労しながら作ってみることだと常々思っていますが、「自分で作ってみよう!」という気持ちにまだなれていません。作りたいものがないということもありますが、そもそもどんなものが作れるものなのかをわかっていないというのも大きいと思います。Webアプリを一から作った経験がほぼないので、いくつかサンプルを見ながら自分の手で作っていくなかで「こういうの作ってみようかな」となってくるのではと考えています。

そこで、

  • ある程度の複雑さがあるアプリを
  • 使用されている機能の解説を見ながら
  • 一行一行コードを書いていく

ようなチュートリアルをやってみよう!ということで、Udemyの下記のコース*1を受講し始めました(もちろんセール期間中に購入)。

https://www.udemy.com/vuejs-2-essentials/www.udemy.com

f:id:ky_yk_d:20180603220802p:plain

コースの概要と本記事で扱う範囲

このコースでは、3つのアプリケーションを作成しながらVueを学んでいきます。内容については、無料サンプルビデオのCourse introductionで見ることができるほか、教材のコードがGitHubに上がっています。作成する3つのアプリケーションとそこで利用する技術は以下の通りです。

全編で14時間のコースとなっています。内容的にはProject 2以降が最も欲している部分ではありますが、せっかくなのでProject 1から取り組んでいこうと思います。というわけで、今回はProject 1を終えた段階での感想記事となります。

The Ultimate Vue JS 2 Developers CourseのProject1で新しく学んだ内容

1つ目のアプリケーションではVue.jsの基本文法を学ぶということで、あまり新しく学ぶことはないかなと思っていましたが、下記については初めて使ったり存在を知ったりという経験ができました。最初からやった甲斐があった!

filters

v-forで算出プロパティを呼ぶやつしか使ったことありませんでした。『速習Vue.js』でも言及はありましたが、プラグインの章で補足的に言及されていただけだったので、自分で実際に定義して使ったのは初めてでした。

jp.vuejs.org

mounted

存在は知っていたけど使ったことありませんでした。ライフサイクルについては、いちど一通り使ってみながら整理した方がいいかなーと感じました。

jp.vuejs.org

scrollMonitorライブラリ

Twitterで馴染み深い、ページ末尾までスクロールすると追加でロードが走るやつを実装するのに使います。意外と簡単に書けてびっくり。

github.com

Chrome開発者ツールのNetworkパネル

3G回線の場合の通信の状態のエミュレーション機能なんてあったんですね・・・ごく一部の機能しか使っていない自覚はあったので、便利な使い方を身につけていきたいですね。

developers.google.com

The Ultimate Vue JS 2 Developers Courseの良いところ

少しずつ段階を踏んで実装が進んでいく

このコースでは、GitHubリポジトリに上がっている初期状態のコードを、動画で説明しながら少しずつ修正していきます。初期状態として与えられているのは、ほぼ設定ファイルとライブラリ、それにCSSのみで、htmlとjsはほぼ自分で書いていきます。最初の画面にはロゴとレイアウト設定しかありません。

この状態から、「まずはこれを表示するようにしてみよう」、「次はこの機能を実装しよう」、「これだとUI的に好ましくないからこうなるようにしよう」と一歩一歩進んでいきます。「先を見据えてこういう風にしておく」ということがなく、「ある機能を実装する」→「足りない機能を考える」→「その機能を実装する」→……というサイクルを回していくので、「なんでそうするの?」と躓くことがありません。

 やりそうな間違いを講師が実際にしてみせてくれる

動画の強みを生かし、追加するソースコードは全て動画の中で実際に講師のアンソニーさんが書いてみせてくれます。そしてその中では、答えに一直線でたどり着くのではなく、結構な頻度で間違えます(たぶんわざと)。間違えると、当然動作確認したときに動かないわけですが、そのときにChromeの検証モードでエラーメッセージをみて、「あ、こういうエラーが出ているということは、ここはこうしなくちゃいけなかったね」とエディタに戻って修正するところも動画に含まれています。

アンソニーさんが動画の中でする間違いにはタイプミスのような些細な間違いも含まれているのですが、なかには変数のスコープや処理の順番などの仕様の理解に関わる「間違い」もあり、それを修正していく過程を見ることから学ぶことは多いです。

印象に残っているのは、thisのスコープの問題に関わる「間違い」です。少し前にコードを書いている時に、まさにその問題で躓き、(あとから典型的な間違いだということもわかったのですが)かなり悩んでしまった経験があったので、「あるある間違い集」のようなものは本当にありがたいです。

英語の発音が聴き取りやすい&字幕が読みやすい

このコース、音声が全編英語で、字幕も英語のみです。日本語でVue.jsをアプリを作りながら学べる教材はあまり見つけられなかったので、大学受験以来錆び付いていく一方の英語をたまには使おうという意味も込めて英語の教材にチャレンジしてみました。

英語でもよいとなると、教材の選択肢はかなり広がります。Udemyだけに絞っても、英語のコースは36件あります。その中でも、下記のコースが、同サイト内のVueのコースで受講者が圧倒的トップで、評判が良かったので、当初はこちらにしようかと思っていました。

https://www.udemy.com/vuejs-2-the-complete-guide/www.udemy.com

しかし、上記のコースのサンプルの動画を見てみると、日本語字幕は自動生成であるため読めたものではなく、英語字幕もちょいちょい間違っています。特に、技術的な用語については英語の段階で誤った字幕になっていることが多いので、肝心なところで字幕が頼りにならないという不安を覚えました。また、音声の英語についても、講師の方の英語には少し癖があり、聴き取りづらく感じました。

以上のような理由で、「英語が聴き取りやすいやつがいいな・・・」と思い、今回の"The Ultimate Vue JS 2 Developers Course"のサンプルを見てみたところ、「めっちゃ聴き取りやすい!!!」となりました。僕にとって聴き馴染みのある英語というと、ほぼ受験英語ということになるのですが、講師のアンソニーさんの英語はとても聴き取りやすく感じますので、おそらく日本での英語教育を受けてきた方の多くが同じように感じるのではないでしょうか。

また、コースの冒頭で、アンソニーさんは「このコースには英語ネイティブじゃない受講生もいるだろうからゆっくり喋るよ!速くしたい人は画面の再生速度で調整してね!」と宣言していて、本当にコースの中でもとてもゆっくりと、はっきりとした発音で喋っています。

The Ultimate Vue JS 2 Developers Courseの「もうちょっと」なところ

まだ一部しか視聴していないので、欠点を述べ立てるには早いのですが、褒めてばかりなのもアレなので一点だけ。

ソースコードに工夫の余地がありそう

これは無い物ねだりというか、コースの目的に照らして不可欠なものだとも思わないですが、敢えて欠点を挙げるとすれば、ソースコードがあまり美しくないように感じます。変数の作り方(命名)やロジックの実装の仕方には、コースの中では頓着せず、「こうすればいいねー」と進んでいってしまいます。2以降ではまた違ってくるのかもしれませんが、コードが美しいとプログラマとして信頼が増すので、講師としてはもっと気を遣ってもよかったのでは?と思います。

おわりに

今のところ、楽しく視聴を続けられています。細かく動画が切られていますし、章立てもあるので、毎日少しずつ進めていくのにはちょうどいい教材だと思います。6月中にはコース全て終えられることを目標に、がんばります!乞うご期待。

*1:カカカカックさん(@kakakakakku)に教えていただきました。ありがとうございます。

Vue.js+axiosでDynamo DBにAjax通信する

はじめに

前回のブログの末尾でこんなことを書いていました。

技術的には、次はVue.jsとDynamoDBでも繋げてみようかと思っています。乞うご期待。

WebページからDynamoDBにアクセスしてみる〜はじめてのAjax通信とDOM操作〜 - こまどブログ

これまで散々予告を破ってきていますが、今回は予告通りの記事です。Vue.jsでフロントを書いて、そこからAjaxAPI Gagewayを叩き、Lambda経由でDynamo DBに接続します。

なお、今回の実装においては前回の記事で紹介した内容をそのまま用いている部分があり(API GatewayのCORS有効化など)、それらについては特に説明を加えていません。「手順通りやっているのに動かない!」などあれば前回の記事もご参照ください。

成果物

f:id:ky_yk_d:20180527221827p:plain

ベースは以前の記事でもご紹介したこちらのタスク管理アプリとなっています。毎度お世話になります。

re-engines.com

ky-yk-d.hatenablog.com

使用している技術・ツール

  • Vue.js
  • Vee-Validate:バリデーション機能を提供するライブラリ
  • Axios:非同期通信用のライブラリ
  • Amazon API Gateway
  • AWS Lambda
  • Amazon Dynamo DB

Vue.jsでAjax通信する:Axiosを利用する

Vue.jsの「公式Ajaxライブラリ」?

フロントにVue.jsで書いてみようということで、単純にmethodsに前回利用したxmlHttpRequestを用いた実装をコピペしてみたのですが、どうも動きません。何かしら別の手段を用いる必要があるようです。

というわけで、Vue.jsからAjaxを行うためのツールを調べてみました。すると、かつてはvue-resourceなるものが「公式ライブラリとして」利用されていたようですが、現在ではAxiosが推奨されているようです。今回は、こちらを用いてAjaxを行っていこうと思います。

ちなみに、vue-resourceが廃止されたというわけでも、Axiosが新たに公式ライブラリになったというわけでもありません。そうではなく、「公式ライブラリ」というものが廃止され、外部ライブラリで人気のあるAxiosが紹介されたということです。この事情についての、Evan You氏(Vue.jsの生みの親)の記事の引用を下記に示します。

しかし、時が経つにつれ私たちは Vue 用の「公式 ajax ライブラリ」は実は必要ではないとの結論に至りました。なぜなら:

  1. ルーティングや 状態管理とは異なり、ajax は Vue のコアとの緊密な統合を必要とする問題領域ではありません。 ほとんどの場合純粋な 3rd パーティのソリューションが同様にうまく問題を解決できます。
  2. 同じ問題を解決するための優れた 3rd パーティの ajax ライブラリがあり、より積極的に改良/保守されていて、かつuniversal/isomorphic(Node とブラウザの両方で動作し、そのことはサーバーサイドレンダリング用途での Vue 2.0 にとって重要)になるよう設計されています。
  3. (1) と (2) なので、vue-resource の現状を維持することは二度手間かつ不要なメンテナンスの負担をもたらしていることが明らかです。 私たちが vue-resource の問題の解決に費やしていた時間を他のスタック(訳注:課題リスト)の改善により費やすことが可能となります。

jp.vuejs.org

「公式ライブラリ」というものが廃止されるべきである理由がシンプルに述べられていますね。今回の記事の内容には直接関係ありませんが、Vueというものの性格に照らしてこのような結論が出る、という理路が面白かったのでご紹介しました。

リクエストボディを送る:PUTメソッドを例に

能書きが長くなってしまいました。Axiosを使ってみましょう。今回は、下記の3つの処理を実装しました。

  • 全件取得(GETメソッド)
  • 新規作成(PUTメソッド)※これは本当はPOSTメソッドで実装されるべき?
  • 削除(DELETEメソッド)

このうち、全件取得は単純に「よこせ!」と送るだけなので説明を省略し、新規作成と削除について順に記述します。まずは、新規作成についてです。PUTメソッドでは、リクエストボディに登録内容を載せて送信します。

var tasks = new Vue({
    el: '#tasks',
    data: {
        tasks:[],
        newTask: '',
    },
//〜〜中略〜〜
    methods: {
//〜〜中略〜〜
        createTask: function(){
            var new_id;
            if (this.tasks.length === 0){
                new_id = 1;
            } else {
                new_id = this.tasks[this.tasks.length - 1].id + 1;
            }
            axios.put(this.endpoint(),{
                Item: {
                    id: new_id,
                    taskname: this.newTask
                }
            }).then(response => {
                this.getAll();
            }).catch(function(err){

            });
            this.newTask = '';
        },
// 〜〜中略〜〜
        endpoint: function(){
            return 'https://[API GatewayのエンドポイントURL]/[ステージ名]/[リソース名]';
        }
    }
});

肝は下記の部分です。axios.put()の第二引数に、登録内容をオブジェクトで渡しています。こちらが、リクエストボディ(本文)となります。API Gatewayの本文マッピングテンプレート、Lambda関数の処理は前回と共通です。

axios.put(this.endpoint(),{
    Item: {
        id: new_id,
        taskname: this.newTask
    }
})

クエリ文字列を送る:DELETEメソッドを例に

前回は実装していなかった削除処理です。DELETEメソッドでは、リクエストボディではなくクエリ文字列パラメータでキーを指定します。axiosでクエリ文字列パラメータを送る場合、axios.delete()の第二引数にconfigとしてオブジェクトを渡します(当初、この第二引数にリクエストボディを書いていて、「渡らない!!おかしい!」と苦しみました)。

var tasks = new Vue({
// 〜〜中略〜〜
    methods: {
// 〜〜中略〜〜
        deleteTask: function(taskId){
            var id = taskId;
            console.log(id, 'を削除する');
            axios.delete(this.endpoint(),{
                params: {
                    id: id
                } 
            }).then(response => {
                this.getAll();
            }).catch(function(err){
                console.log(err);
            });
        },
// 〜〜中略〜〜
    }
});

散々苦しみ、「第二引数に渡せばいいらしい」と実装して歓喜したのち、落ち着いてAxiosのGitHubをみていたら、下記のような記載を見つけました。axios.delete(this.endpoint() + '?id='+ id)とも書けたようですね。言われてみればそれはそうなのですが、最初からこちらを採用していたら第二引数の使い方がわからないままだったので結果オーライです。

https://github.com/axios/axios#example

API Gateway・Lambdaでリクエストを処理する

API Gatewayでクエリ文字列パラメータをLambdaに送る

ここまでで、Axiosを用いたリクエストの送信は完了です。次に、API GatewayでそのリクエストをLambdaに送信するところについてです。

先述の通り、PUTメソッドで送信したリクエストボディについては、前回の記事と同じように「本文マッピングテンプレート」を記載しておけばLambda側に送ってくれます。それでは、DELETEメソッドで渡したクエリ文字列パラメータはどのようにすればよいのでしょうか。

クエリ文字列パラメータをLambda側に送信するために必要なAPI Gatewayの設定は以下の2つです。

それぞれ、下記の画面で上段に記載されているものです。順に、見ていきます。

f:id:ky_yk_d:20180527212715p:plain

まず、メソッドリクエスト側です。こちらでは、下記の画像のように、「URLクエリ文字列パラメータ」を設定します。「クエリ文字列の追加」ボタンを押して、params: {Foo: Bar}(=?Foo=Bar)のFooを記入してください。「必須」は設定しなくても大丈夫だと思いますが、念のため設定しています。メソッドリクエスト側の設定はこれで終わりです。

f:id:ky_yk_d:20180527195443p:plain

続いて、統合リクエスト側です。こちらでは、「URL クエリ文字列パラメータ」ではなく、「本文マッピングテンプレート」を利用しています(前者も色々と試してみたのですが、結局よくわかりませんでした・・・)。

f:id:ky_yk_d:20180527195449p:plain

基本的には前回と同様ですが、今回はリクエストボディではなくクエリ文字列を取得したいので、マッピングテンプレートの中身は下記のようになっています。$input.params('id'で、クエリ文字列パラメータで渡したidを数値で取得しています(前回は、 Dynamo DBのプライマリーキーは文字列でしたが、今回は数値にしているからです)。

{
    "method": "$context.httpMethod",
    "id": $input.params('id')
}

前回も貼りましたが、こちらのリファレンスを見ると色々なデータが取得できることがわかります。

docs.aws.amazon.com

LambdaでDeleteする

以上で処理されたデータが、Lambda関数に渡されます。本文マッピングテンプレートで、削除したいidをidマッピングして渡しているので、Lambdaではevent.idとして利用できます。こちらをキー情報として、Dynamo DBに削除処理をかければ終了です。

// 〜〜前略〜〜
        case 'DELETE':
            console.log('event:',event);
            console.log('context:', context);
            params = {
                TableName: 'tasks',
                Key: {
                    "id": event.id
                }
            };
            dynamo.delete(params, callback);
            break;
// 〜〜後略〜〜

お試しVue.js:算出プロパティでソートした結果を表示する

以上、axios→API Gateway→Lambda→Dynamo DBの処理の流れを記載してきました。最後に、画面側のVue.jsでについても1点だけ、どうしてもやっておかなければならない処理があったので、記載します。それは、scanで取得したデータのソートです。

GETメソッドでDynamo DBをscanして取得したデータは、ソートされていない状態でJavaScriptの配列に入ります。したがって、単純にv-forで表示してしまうと、よくわからない順序になってしまいます。それを防ぐために、算出プロパティでソート処理をかけてビュー側では利用することにします。

var tasks = new Vue({
    el: '#tasks',
    data: {
        tasks:[],
        newTask: '',
    },
    computed: {
        sortedTasks: function(){
            return this.tasks.sort(function(a, b){
                let comparison = 0;
                if (a.id > b.id){
                    comparison = 1;
                } else if (a.id < b.id){
                    comparison = -1;
                }
                return comparison;
            });
        }
    },
// 〜〜後略〜〜

取得したデータは、tasksに格納されています。これをhtml側で直接扱うのではなく、算出プロパティsortedTasksを利用することで、ソートした結果を表示することができます。メソッドを使っても同じ目的な実現できますが、算出プロパティの利用が推奨されています。

リストレンダリング#フィルタ/ソートされた結果の表示

算出プロパティとメソッドの違いは、下記のような点があるようです。今回のような場合にはメソッドでもよかったかもしれません。

算出プロパティの代わりに、同じような関数をメソッドとして定義することも可能です。最終的には、2つのアプローチは完全に同じ結果になります。しかしながら、算出プロパティは依存関係にもとづきキャッシュされるという違いがあります。算出プロパティは、それが依存するものが更新されたときにだけ再評価されます。

算出プロパティとウォッチャ#算出プロパティ vs メソッド

ちなみに、HTML側は下記のようになっています。

<div id="tasks">
                <table class="table table-striped">
                    <thead class="thead-dark">
                        <tr>
                            <th>Id</th>
                            <th>Name</th>
                            <th>Delete</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="task in sortedTasks">
                            <td width="3px">{{ task.id }}</td>
                            <td>{{ task.taskname }}</td>
                            <td>
                                <div class="btn btn-secondary" v-on:click="deleteTask(task.id)">Delete Task</div>
                            </td>
                        </tr>
                        <tr>
                            <td>New:</td>
                            <td><input v-validate="'required'" v-model="newTask" class="form-control" name="newtaskname" placeholder="新しいタスク名を入力してください"></td>
                            <td v-if="newTask === ''">
                                <div class="btn btn-disabled" >Create Task</div> 
                            </td>
                            <td v-else>
                                <div class="btn btn-primary" v-on:click="createTask">Create Task</div>
                            </td>
                        </tr>
                    </tbody>
                </table>
                <div v-if="errors.has('newtaskname')" style="color: red; font-size: 24px">
                    {{errors.first('newtaskname')}}
                </div>
            </div>

おわりに

以上で、「Vue.jsとDynamo DBを繋ぐ」という予告は果たされたと思います。今回作成したコードはすべてGitHubに公開していますので、お気付きの点あればコメントなりプルリクなりいただけると幸いです。

github.com

本当は、Vee-Validateも利用して見ているのですが、使い方が中途半端(エラーメッセージを表示するだけで、処理の制御に使っていない)なので記事にはしていません。ちゃんとした使い方ができたら、ブログでもご紹介したいと思います。乞うご期待。

(おまけ) こんな本が出ていました。『速習Vue.js』よりも踏み込んだ記載をしている印象です。Vue.jsは書籍が少ないですが、入門から中級への橋渡しとなるような本が出てくれるといいなと期待しています。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

WebページからDynamoDBにアクセスしてみる〜はじめてのAjax通信とDOM操作〜

前回のLINE Bot作成に引き続き、AWS関連です。Webアプリ側に寄せています。

ky-yk-d.hatenablog.com

前回やったこと

今回やること

  • Lambdaの裏側にDynamoDBを置く(前回はLambda側に全て記載されていた)
  • WebページからJavaScriptAPI GatewayAPIを叩く
  • 新たなデータを作成する/取得した結果を表示する

LINEボットを作成して、 API GatewayからLambda関数を呼び出す流れはわかりました。 前回は、API Gatewayの呼び出しはLINE側で用意されていましたが、今回はWebページからJavaScriptで呼び出してみます。また、 フレームワークを使ったWebアプリを作成した(編集した)経験はあるのですが、クライアントサイドJavaScriptを書いた経験はほぼゼロです。自分でWebアプリを作れるようになるには、クライアントサイドJSは避けて通れませんので、今回はその初歩をやってみました。

図示するほどのものではないのですが、憧れがあったので構成図を。素材は以下から。 ちなみに、PowerPointでペタペタ貼り付けてスクショ撮ったんですけど、色々間違っている気がします。

f:id:ky_yk_d:20180520201851p:plain
アーキテクチャ構成図(書いてみたかった)

API Gateway〜DynamoDBの設定は下記の書籍(198頁以下)を参照しました。

Amazon Dynamo DB の設定

テーブルを作成します。以上!

・・・

というわけには流石にいきませんが、リレーショナルデータベースと異なりスキーマを設定する必要がないので、確実に設定しなければならないのはテーブル名とプライマリーキーくらいです。

テーブル名はSAMPLE_TASKS、プライマリーキーはIDとしました。普段(リレーショナルDBでの)テーブル名やカラム名は大文字表記の場合が多いので、書籍の指示を無視して大文字にしたのですが、画面との連携を考えると小文字の方が良かったと思います(そして見返して思いましたがtasknameは小文字にしてますね・・・)

f:id:ky_yk_d:20180520233151p:plain
DynamoDB(データを入れた後の状態)

AWS Lambda の設定

Lambdaで設定する内容は以下の2点です。

  • DynamoDBFullAccessを付与したロールを割り当てる
  • 関数を記載する

今回は、LambdaからDynamoDBにアクセスするので、DynamoDBに対する読み書きの権限を有するロールをLambda関数に割り当てる必要があります。そこで、Lambda関数の作成の前にIAMロールを作成します。ロール作成画面で、「信頼されたエンティティの種類」でLambdaを、次の画面の「アクセス権限ポリシー」でDynamoDBFullAccessを選択しましょう。

f:id:ky_yk_d:20180520201842p:plainf:id:ky_yk_d:20180520201846p:plain

上記の手順でIAMロールを作成できたら、Lambda関数の作成です。「一から作成」を選択し、先程のロールを割り当てて作成します。今回は、ランタイムにはNode.js 8.10を選択しました。

Lambda関数の中身は今回はこのファイルひとつです。GETとPUTの両方をこの関数で受け付けます。

index.js

const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

exports.handler = (event, context, callback) => {
    const operation = event.method;  // API Gatewayのマッピングテンプレートで指定した"method"要素
    let params;
    switch (operation) {  // いずれのリクエストかによって分岐させる
        case 'GET':
            params = {
                TableName: "SAMPLE_TASKS", 
            };
            dynamo.scan(params, callback);
            break;
        case 'PUT':
            params = {
                TableName: "SAMPLE_TASKS",
                Item: {
                    "ID": event.body.Item.id,  //  "ID"はDynamoDBのカラム(?)名、"Item.id"は送信するJSON内の要素名
                    "taskname": event.body.Item.taskname
                }
            };
            dynamo.put(params, callback);
            break;
        default:
            callback('Unknown operation: ${operation}');
    }
};

Amazon API Gateway の設定

API Gatewayでは以下の手順で作業を行います。

  • APIの作成(sample_crud_tasks
  • リソースの作成(sample_tasks
  • GETメソッドとPUTメソッドを作成し、Lambda関数と紐づける
  • GETメソッドとPUTメソッドの双方の「統合リクエスト」に本文マッピングテンプレートを指定
  • ステージにデプロイ

f:id:ky_yk_d:20180520205451p:plainf:id:ky_yk_d:20180520220121p:plain
作成したAPI(GETメソッド) ※OPTIONはのちの過程で自動的に作成される

本文マッピングテンプレートを設定するところについては説明が必要と思われます。「統合リクエスト」をクリックして開く画面の最下部に「本文マッピングテンプレート」という項目があるので、Content-Typeの欄にはapplication/jsonと記載して追加します。上記の2番目のような画面になります。

本文マッピング・テンプレート(GET, PUT共通)

{
    "method": "$context.httpMethod",
    "body": $input.json('$')
}

送られてきたHTTPリクエストの内容を、受け手側のデータにマッピングして渡すもののようです。 $context.httpMethod は一見してわかるように使用されたHTTPメソッド名で、画面側のJavaScriptxhr.open()の第一引数で指定しているものです。Lambda側ではevent.methodとして参照されています。どのメソッドを使用したかによってLambda側で処理を変えるためのものですね。

$input.json('$')は入力されたxhr.send()の引数で渡した内容(=リクエスト本文)をJSON形式で読み取っています。こちらも、Lambda側でevent.bodyとして参照され、DynamoDBに登録するデータの取り出しに使われています。

API Gatewayマッピングテンプレートのリファレンスはこちら

API Gateway のマッピングテンプレートリファレンス - Amazon API Gateway

ここまでで、DynamoDBのためのAPIは完成です。

Webページ(HTML & JavaScript)の作成

さて、ここまでは専らAWSのお話(『実践AWS Lambda』に記載の内容)でした。ここからは作成したAPIをWebページから呼び出します。呼び出しただけではしょうがないので、以下の2つを実装します。

  • 簡単な入力フォームを用いたデータの追加(PUTなので同じIDなら更新になる)
  • DynamoDBのデータのリスト表示

使用する技術としては、以下のようなものです。

僕は「DOMってなに?モビルスーツ?」というレベルなので、極めて初歩的な内容となっています。また、jQueryなどを用いればAjaxはもっと簡潔に実装できるのだと思うのですが、色々なところから「jQueryはもう・・・」という声が聞こえてきています(例:You-Dont-Need-jQuery)。研修のときにjQueryがモダンなのだという話を聴いた気がしたのですが。

今後の開発のなかでjQueryを使いたくなるようなことがあれば学ぶことはやぶさかではないのですが、今回はJavaScriptの勉強をするという観点からも、ピュアJSで記述しました。また、HTMLファイルとJSファイルを別に作成すべきところなのでしょうが、今回はHTMLファイルに<script>タグで埋め込んでしまいました。どこかのタイミングで切り離します。ソースコードに重複も多いですし、リファクタをちゃんと考えてみたいです。

f:id:ky_yk_d:20180520233428p:plainf:id:ky_yk_d:20180520233431p:plainf:id:ky_yk_d:20180520233434p:plainf:id:ky_yk_d:20180520233436p:plain
こんな感じになる(大きさバラバラ・・・)

なお、今回のコードを実装するにあたっては、下記を参照しました。

tomosoft.jp

それでは実際のコードです。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <form>
        <input id="ID" type="text" placeholder="IDを入力">
        <input id="taskname" type="text" placeholder="タスク名を入力">
        <input id="submit" type="button" value="送信">
    </form>
    <hr/>
    <div id="status"></div>
    <form>
        <input id="btn" type="button" value="取得">
    </form>
    <hr/>
    <div id="result"></div>
    <script>
        document.addEventListener('DOMContentLoaded', function(){
            document.getElementById('submit').addEventListener('click',function(){
                var status = document.getElementById('status');
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function(){
                    if (xhr.readyState === 4){ // 通信完了
                        if (xhr.status === 200) { // 通信成功
                            status.textContent = '送信しました';
                        } else {
                            status.textContent = 'サーバーエラーが発生しました';
                        }
                    } else {
                        status.textContent = '送信中';
                    }
                };
                var obj = {
                    Item : {
                        id: document.getElementById('ID').value,
                        taskname: document.getElementById('taskname').value
                    }
                };
                var json = JSON.stringify(obj);
                xhr.open('PUT', 'https://l3uk6hufcf.execute-api.ap-northeast-1.amazonaws.com/prod/sample-tasks', true);
                xhr.setRequestHeader('Content-Type', 'application/json');
                console.log(json);
                xhr.send(json);
            })
        })
    </script>

    <script>
        document.addEventListener('DOMContentLoaded', function(){
            // 取得ボタンクリック時に実行される
            document.getElementById('btn').addEventListener('click', function(){
                var result = document.getElementById('result');
                var xhr = new XMLHttpRequest();
                // 非同期通信時の処理
                xhr.onreadystatechange = function(){
                    if (xhr.readyState === 4){ // 通信完了
                        if (xhr.status === 200) { // 通信成功
                            //var data = JSON.parse(xhr.responseText);
                            var data = xhr.response;
                            // 結果からキーにアクセス
                            if (data === null) {
                                // ない場合はメッセージ
                                result.textContent = 'データが存在しません';
                            } else {
                                // 取得できた場合
                                console.log(typeof(data));
                                console.log(data)
                                var items = data.Items;
                                var ul = document.createElement('ul');
                                for (var i = 0; i < items.length; i++){
                                    var li = document.createElement('li');
                                    var text = document.createTextNode(items[i].ID + ' ' + items[i].taskname);
                                    // 組み立て
                                    li.appendChild(text);
                                    ul.appendChild(li);
                                }
                                // <div id="result">の配下を置き換える
                                result.replaceChild(ul, result.firstChild);
                            }
                        } else {
                            result.textContent = 'サーバエラーが発生';
                        }
                    } else { // 通信中
                        result.textContent = '通信中'
                    }
                };
                // 非同期通信を開始
                xhr.responseType = 'json';
                xhr.open('GET', 'https://l3uk6hufcf.execute-api.ap-northeast-1.amazonaws.com/prod/sample-tasks', true);
                xhr.send(null);
            }, false );
        } ,false);
    </script>
</body>
</html>

HTMLの部分はまぁいいとして、JSの部分を丁寧にみてみようと思います。

送信ボタンの処理

document.addEventListener('DOMContentLoaded', function(){
        / * 中略 */
        })

ページロード時に実行されるイベントリスナーを登録しています。/* 中略 */の部分に記載した内容が初期化処理になります。次は中身をみてみます。

document.getElementById('submit').addEventListener('click',function(){
        /* 中略 */
            })

submitボタンにイベントリスナーを登録しています。さきほどDOMContentLoadedを渡していた第一引数は、今回はclickとなっています。イベントリスナーの種類を指定しているのですね。クリックしたときに、第二引数で渡している関数オブジェクトが実行されるわけです。さらに括弧の中をみていきます。

var status = document.getElementById('status');     // HTMLのid="status"の要素を取得
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){                       // 通信の状態が変化したときに呼び出されるイベントハンドラ
    if (xhr.readyState === 4){ // 通信完了
        if (xhr.status === 200) { // 通信成功
            status.textContent = '送信しました';
        } else {
            status.textContent = 'サーバーエラーが発生しました';
        }
    } else {
        status.textContent = '送信中';
    }
};
var obj = {
    Item : {
        id: document.getElementById('ID').value,
        taskname: document.getElementById('taskname').value
    }
};
var json = JSON.stringify(obj);
xhr.open('PUT', 'https://l3uk6hufcf.execute-api.ap-northeast-1.amazonaws.com/prod/sample-tasks', true);
xhr.setRequestHeader('Content-Type', 'application/json');
console.log(json);
xhr.send(json);

非同期通信を管理するXMLHttpRequestが登場しています。onreadystatechangeプロパティには、通信の状態が変化したときに呼び出されるイベントハンドラを格納します。この例の場合、HTTP通信の状態(readyState)とHTTPステータスコードstatus)によって通信の状態を判断し、status.textContentにメッセージを代入することで通信状態を画面に表示しています。

後半では、PUTリクエストでJSONを送付する処理を記述しています。まず、送信したいJSONデータの元となるJavaScriptのオブジェクトをobjに代入し、それをJSON.stringify()で文字列形式に変換しています。次に、XMLHttpRequestオブジェクトのopen()メソッドで、HTTPリクエストの初期化を行います。第一引数がHTTPメソッド、第二引数がアクセス先URLです。第三引数以降はオプションで、ここでは第三引数にtrueを渡すことで非同期通信であることを明示しています(第三引数はデフォルトがtrueなので書いても書かなくても変わらないはずですが)。

重要なのは、xhr.setRequestHeader('Content-Type', 'application/json');の部分です。リクエストヘッダーを指定しています。API Gatewayで本文マッピングテンプレートを設定した際、「Content-Typeの欄にはapplication/jsonと記載して〜〜」と書いたことに対応しています。この処理の記述を省いてしまうと、リクエストは拒否されます(ステータスコード415)。この点については下記の記事が参考になるかと思います。

dev.classmethod.jp

リクエストヘッダーを指定したら、最後にJSON文字列をリクエストの本文としてsend()メソッドに渡して送信します。ここまでの処理が、すべて`「送信」ボタンをクリックした際に実行されます。

取得ボタンの処理

リクエスト送信時のJSON関係の処理がないこと以外、基本は送信ボタンの処理と同じです。ただ、こちらでは取得したデータをHTML側に埋め込むための処理が加わっています。

var data = xhr.response;
// 結果からキーにアクセス
if (data === null) {
    // ない場合はメッセージ
    result.textContent = 'データが存在しません';
} else {
    // 取得できた場合
    console.log(typeof(data));
    console.log(data)
    var items = data.Items;
    var ul = document.createElement('ul');
    for (var i = 0; i < items.length; i++){
        var li = document.createElement('li');
        var text = document.createTextNode(items[i].ID + ' ' + items[i].taskname);
        // 組み立て
        li.appendChild(text);
        ul.appendChild(li);
    }
// <div id="result">の配下を置き換える
result.replaceChild(ul, result.firstChild);

API Gateway(の背後にいるLambda)からはオブジェクト形式でデータが送られてきます。responseプロパティがレスポンスの本体です。それは下記のような形のデータです(ブラウザでAPIのエンドポイントに直接アクセスすると見られます)。

{
    "Items":[
        {"taskname":"test2","ID":"secondData"},
        {"taskname":"test","ID":"firstData"},
        {"taskname":"変更後!!!","ID":"aaa"}
    ],
    "Count":3,
    "ScannedCount":3
}

(参考)AWS-SDKのscanの仕様(英語)

取得されたデータの個数(Count)やスキャン対象となったデータの個数(ScannedCount※今回はフィルター適用していないのでCountと等しい)も一緒に送られてきていることがわかります。今回欲しいのはそれぞれのデータなので、Itemsを順に扱えば足ります。

ローカル変数itemsItemsの内容を代入したあとは、forループを使いながらid="result"の箇所に挿入する要素を作成しています。古典的なfor文で記載していますが、たぶんもっと近代的な書き方があるのだと思います。

仕上げ(CORSの有効化)

liul要素を順に作成したあと、最後にresult.replaceChild()で置き換えて、この無事表示ができました。めでたしめでたし。

と思いきや、この状態でindex.htmlにアクセスして、送信ボタンや取得ボタンを押すとエラーになります。クロスオリジン通信を可能にする必要があります。ブラウザのコンソールに、Access-Control-Allow-Originがなんちゃらかんちゃらと出るはずなので、これをコピーしてAPI GatewayAPIのアクションで「CORSの有効化」を選択し、Access-Control-Allow-Headersのところに上記のAccess-Control-Allow-Originを加えます。この設定をすることで、ようやくAPIをWebページから叩くことができます。

f:id:ky_yk_d:20180521001112p:plain

API Gateway リソースの CORS を有効にする - Amazon API Gateway

※XDR(Cross-Domain Request)は、元来セキュリティ上の必要性から制限されたもので、対応手段はCORS(Cross-Origin Resource Sharing)以外にも存在するようです。どの手段が適切であるかは、実践レベルでは要検討でしょう。

dev.classmethod.jp

JavaScript のクロス ドメイン (Cross-Domain) 問題の回避と諸注意tsmatz.wordpress.com

終わりに

はい。短くまとめるはずだったのに、今回も長くなってしまいました。個々の技術要素については入門的な記事や書籍がいくらでも存在しているので、あえて記事にするとすれば「実現したことベース」にして繋ぎ方を意識したものがいいかなと思ってるのですが、それゆえにこそ色々な要素を詰め込んでしまい、長くなってしまう傾向がありますね。これだけ長くなるとどこに何書いたかも忘れますし、構成や文章もグダグダになりがちです。なんとかしたいですね。技術的には、次はVue.jsとDynamoDBでも繋げてみようかと思っています。乞うご期待。

ソースコードについてはツッコミどころ満載であることは自覚しており、発展させながら直していこうと思っていますが、幾分初心者であるがために気づかないミスや改善点などあると思いますので、pull request・Issue等なげていただけるととても嬉しいです。

github.com