20190502のJavaScriptに関する記事は22件です。

webpack入門の入門

JavaScriptについてあまり詳しくないけれどどうやらwebpackが必要になるみたいだという初心者の方向けの記事です。わかりやすいように冗長な記述が多いのでReactとかVueで開発したことがあるような方は他の記事を参考にした方がいいと思います。

前置き

  • Webpack4についての記事です。よってそれ以前のwebpackについてはこの記事はあまり参考になりません。
  • webpack.config.jsを使わない方法で書いてますのでbabel通したいとか色々やりたい場合はこの記事では解決できません。
  • 特に事情がない限りはWebpackは使わずにCDNで配信されているものやダウンロードしたものをscriptタグで読み込むのが良いでしょう。まずは他の知識をつけてから必要になった時にwebpackに挑戦するのがよいです。

  • CDNからライブラリを読み込むにはhtmlの適切な場所に(headタグの中か、bodyの閉じタグの直前が多い)にscriptタグで指定します。
    https://cdnjs.com/ から使いたいライブラリを検索するとよいです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script src="https://aaa.bbb.com/foobar.js"></script> <!-- ここ -->
  </head>
  <body>
    <!-- イカしたコンテンツがいろいろ -->
    <script src="https://aaa.bbb.com/foobar.js"></script> <!-- またはここ -->
  </body>
</html>

  • でもどうしても私はwebpackがやりたいんだという場合はこの記事を参考にしてみてください。

本当にwebpackを使う必要があるのでしょうか?

個人的には初心者がwebpackをいきなり使うのはあまりおすすめしません。JavaScriptの読み込み順を意識しないといけないとかそういうのを理解していればよいですが、webpackを使わずに解決できる程度のことであれば使わない方が楽ですし余計なことを考えたり学ばなくて良いです。必要になってからで十分ですし、最近は便利なツールがいっぱいあるのでVueやReact触ってる人も全員がwebpackを理解しているわけではないと思います。

webpackを使う理由は基本的に一つでファイルの依存関係を解決したいということに尽きます。依存関係の解決って何かっていうと要はライブラリや他のファイルを読み込んでいるコードを動くようにするってことです。他の便利な機能を使いたい場合もありますが、それはあくまで副次的なものです。いろんなライブラリを読み込み順などを気にせず使いたいとか、開発時のJavaScriptのファイルを小さくモジュールとして分割したいとかそういうのでなければ他の方法で検討した方がいいです、何度もいいますけど。

webpackがやってくれることは複数のJavaScriptのファイルを、起点とするファイル(エントリーポイントと呼びます)から辿り、それらで読み込まれているファイルを一つのファイルとしてまとめた新しいファイルを作成することです。そうすることで依存関係を解決します。その間にいろいろな処理を挟むこともできますが、前述したとおりこれはあくまで副次的なもので、基本的には一つにまとめたファイルを作成するためのツールです。webpackのようなツールをモジュールバンドラーと呼びます。

一つにまとまると何がいいかというとscriptタグでのファイルの読み込み順を気にしなくてよくなったり、開発時のファイルを複数のファイルに分割して管理しやすくなったり、リクエストの回数を減らすことができたりします。

できるだけ最小構成でやってみましょう

それでもやるっていうならまぁ止めはしないんですが、ある程度わかってる前提で書かれてる記事ばかりなのでwebpackを動かすだけでも一苦労です。本来のやりたいことやる前に時間が無限に溶けていきます。なのでいろんなことせずにできるだけ最小構成でやっていきましょう。

Node.jsの準備

  • webpackを使うならまずはこれがなくっちゃ話になりません。インストールするにはいろいろ方法がありますけど・・https://nodejs.org/ からダウンロードするか、またはndenvというものをインストールしてそれをつかってインストールするのがいいと思います。インストールするのは最新版と安定版についてはどっちでもいいですがお仕事で使うなら安定版を入れるのが無難です(初心者にそういうことを任せる職場がないことを祈ります)。インストールできたら以下のようなコマンドを入力してみましょう($マークは入力しないでねcommand not found と表示されなければ問題ないです。
$ node -v

webpackとwebpack-cliのインストール

Node.jsをインストールするとnpmというコマンドが使えるようになります。Rubyで言うとgemコマンドとbundler、PHPでいうcomposer,Rustでいうと・・cargo, Goでいうと go getとglide、Pythonでいうpipとかだいたいそのへんです。(RustとかGo使うレベルの人がこの記事を見ることはないと思うけど・・)

ではインストールする前に・・。現在のディレクトリを確認してみてください。仮に今回作成するアプリケーションのディレクトリをmy-appとしましょうか、my-appのディレクトリにいますか?その状態でインストールしましょう。

おっとその前に、my-appの中にはpackage.jsonはありますか?なければ npm init とコマンドを入力して作成しましょう。package.jsonを作成するためにいろいろ聞かれますが、あとで変えられるので全部Enter押しといて大丈夫です。

package.jsonが作成できたら⬇️のコマンドで早速インストールしましょう。

$ npm install --save-dev webpack webpack-cli

コマンドの意味ですがだいたいこんな意味です。

# npm で インストール 開発時用 に webpack と webpack-cli を
$ npm install --save-dev webpack webpack-cli

ネットワークが悪くなかったりとか特別問題なければスムーズにインストールできるはずです。で、インストールは完了なんですが、webpackとコマンドを打ってもwebpack: command not foundといわれるでしょう。使うためにはいくつか方法があります。

webpackコマンドを使う方法

まず一つはwebpackの実行ファイルを直接使う方法です。カレントディレクトリ(現在のディレクトリ)がmy-appの状態で以下のコマンドを入力してみてください。webpackのバージョンが表示されます。

# webpack -v と同じ
$ ./node_modules/webpack/bin/webpack.js -v 

webpack initのような設定ファイルの生成のために一度しか使わないようなコマンドはこういう方法で十分です。

# webpack init と同じ (今回は使いません)
$ ./node_modules/webpack/bin/webpack.js init

もう一つはnpm scriptを使う方法ですね。基本はこのnpm scriptを使うのがいいと思います。先ほど作ったpackage.jsonですが、npm initで作成してwebpackとwebpack-cliをインストールしているとだいたい以下のようになっていると思います。

package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.1"
  }
}

このpackage.jsonのscriptsの部分はnpm run コマンド名の形式で使用できるコマンドを定義できます。⬆️の場合はnpm run testが使えます(エラーメッセージが出るだけですが)

この部分をwebpackを使うためにpackage.jsonを以下のように書き換えます。JSONの形式を守らないと動かないので「"」とか「,」とかが抜けないように気をつけましょう。

package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "build": "webpack --mode production",
+    "dev": "webpack --watch --mode development"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.1"
  }
}

これでnpm run devコマンドとnpm run buildコマンドが使えるようになります。

他にもwebpackコマンドを使うためにPATHを通す方法を紹介した記事をみたことがありますが・・オススメしないです。

webpackを使ってみる

さぁでは早速webpackのコマンドをを使ってみましょう。package.jsonがあるディレクトリで(⬅️これは必ず意識しましょう)npm run devと入力してみてください。・・・多分エラーメッセージがでると思います。
webpackは設定ファイルなしでも使えますが、そのためにはルールに従ってファイルを配置する必要があります。

具体的にはmy-appの中にsrcという名前のディレクトリを作成し、その中にindex.jsというファイルが必要です。my-app/src/index.jsといった具合です。ディレクトリ構成が以下のようになっていれば問題ないです。

my-app/
├── node_modules/
│   └── 省略
├── package-lock.json
├── package.json
└── src/
    └── index.js # これが必要

今回はみんな大好き(大嫌い)jQueryを使ってみますか。令和になってまでjQueryかという感じがしますが有名だし初心者はみんな知ってるでしょう(たぶん)。以下のコマンドでインストールしてみます。

$ npm install --save jquery

そしてsrc/index.jsを以下のような内容にしてみます。

my-app/src/index.js
import $ from 'jquery' // クライアントサイドでは require ではなく importで書きましょうね

$('#app').text('Hello webpack!')

src/index.jsが存在している状態で、npm run devとコマンドが成功するといくつかのメッセージがでてきて、そのあとはコマンドを打てなくなります。これはwebpackがファイルの変更を検知するために監視している状態です。package.jsonに書いたwebpack --watch --mode development--watchオプションでそういう指定をしています。コンソールを操作したければ別のコンソールを起動するなどするか、Ctrl+Cで終了すればよいです。

Ctrl+Cで止めたあと、my-appの下にdistというディレクトリが作成されています。これが(デフォルトの設定で)webpackが作成するディレクトリです。その中のmain.jsが一つにまとまったファイルです。このdist/main.jsをhtmlで読み込めばライブラリのコードも一つにまとまっているのでうまく動作するはずです。

my-app/index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
 <div id="app"></div>
  <script src="./dist/main.js"></script>
</body>
</html>

ブラウザでindex.htmlを開いてみてHello webpack!の文字が表示されていれば成功です。

開発時はnpm run devを使って本番のサーバーなどにあげる時はnpm run buildで生成したものをデプロイしましょう。JavaScriptを圧縮してくれます。

以上です。

なんでこんな記事を書いたかっていうとTwitterでうっかり初心者の人に「browserifyやるくらいならwebpackやったら?」と言ってしまったのですが、苦戦していらっしゃるようなので言った手前責任をもって説明しないとなぁという気持ちになったからです。Qiitaじゃなくてもいいんですけど、まぁ他の誰かの役に多少たてばいいかなぁという気持ちです。
勢いでガーッと書いたんで間違いがあったらコメントで教えてください。

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

JavaScriptカウンター

学習履歴

■はじめに

JavaScript で、カウンターを勉強した。

■Code

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>怒りカウンター</title>
</head>
<body>
    <!-- top section -->
    <div class="intro py-3 text-center">
        <div class="container">
            <h2 class="display-3 my-4">
                穏やか
            </h2>
        </div>
    </div>

    <!-- result section -->
    <div class="result py-4 d-none bg-light text-center">
        <div class="container lead">
            <p>ぶち切れ度<span class="display-4 p-3">0%</span></p>
        </div>
    </div>


    <div class="text-center">
        <button type="button" class="btn btn-light" id="start">START</button>
        <button type="button" class="btn btn-light" id="stop">STOP</button>
    </div>

    <script src="counter.js"></script>
</body>
</html>
counter.js
const startBtn = document.querySelector('#start');
const stopBtn = document.querySelector('#stop');
const result = document.querySelector('.result');
const h2tag = document.querySelector('h2');

let timer;

const manageCss = function(removeColor, addColor) {
    result.querySelector('span').classList.remove(removeColor);
    result.querySelector('span').classList.add(addColor);
    h2tag.classList.remove(removeColor);
    h2tag.classList.add(addColor);
}

startBtn.addEventListener('click', e => {
    e.preventDefault();


    manageCss(result.querySelector('span').classList[2], 'text-info');

    result.classList.remove('d-none');

    const score = 1000
    let output = 0;

    timer = setInterval(() => {
        result.querySelector('span').textContent = `${output}%`;
        if(output === score) {
            clearInterval(timer);
        } else if (output < 100) {
            manageCss(result.querySelector('span').classList[2], 'text-info');
            h2tag.textContent = `あ、...`;
            output++
        } else if(output >= 100 && output < 300) {
            h2tag.textContent = `ちょっと、やばい。。`;
            manageCss(result.querySelector('span').classList[2], 'text-primary');
            output++;
        } else if(output >= 30 && output < 600) {
            h2tag.textContent = `やばい、やばい!`;
            manageCss(result.querySelector('span').classList[2], 'text-warning');
            output++;
        } else if(output >= 600 && output < 900) {
            h2tag.textContent = `逃げてェええええ!!!`;
            manageCss(result.querySelector('span').classList[2], 'text-danger');
            output++;
        } else if(output < 1000) {
            h2tag.textContent = `一周回って、落ち着いた`;
            manageCss(result.querySelector('span').classList[2], 'text-info');
            output++;
        } 
    }, 10);
});

stopBtn.addEventListener('click', e => {
    clearInterval(timer);
});

■画面

スクリーンショット 2019-05-02 22.31.38.png

挙動については、実際にコードをコピペして、動かして欲しい。
(大したことないんだけど)

■まとめ

JavaScript は、面白い。

サーバーサイドよりフロントエンドの方が好きかもしれない。

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

【WebGL】texImage2DでArrayBufferViewを使う

WebGLでテクスチャを作成するときに、texImage2Dpixels引数にArrayBufferViewを渡せるようなので試してみました。具体的に使用できるArrayBufferViewの型やtype引数の組み合わせはMDNのドキュメントを参考にしてください。

以下のサンプルコードでは、512x512のテクスチャを作成しています。一つのピクセルにRGBAの4要素を格納するので、Uint8Arrayの大きさはtexWidth*texHeight*4になっています。x要素が大きいほど赤色に、y要素が大きいほど緑色が強くなるようにしています。Uint8Arrayを使用しているのでtype引数にはgl.UNSIGNED_BYTEを使用しています。

const texWidth = 512;
const texHeight = 512;
const pixels = new Uint8Array(texWidth * texHeight * 4);
for (let y = 0; y < texHeight; y++) {
  for (let x = 0; x < texWidth; x++) {
    const offset = 4 * (x + texWidth * y);
    pixels[offset + 0] = 255 * (x / texWidth);
    pixels[offset + 1] = 255 * (y / texHeight);
    pixels[offset + 2] = 255;
    pixels[offset + 3] = 255;
  }
}

const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texWidth, texHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.bindTexture(gl.TEXTURE_2D, null);

サンプルのソースコード全体は以下に置いておきました。
https://gist.github.com/aadebdeb/e65b88d6efe421a734038dbc654d5208

作成したテクスチャを四角形のメッシュに張り付けると以下のようになり、意図通りの色になっています。
arraybufferview.png
ArrayBufferViewを使うと任意の値を書き込んだテクスチャをCPU側で作成することができます。メッシュの表面に張るテクスチャを作成する以外に、シェーダーには定義できる定数に限りがあるので大量の定数がシェーダー側で必要な場合にも使用できるのではないでしょうか。

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

ツイッターで自動的に一括いいねする JavaScript

$('.HeartAnimation').each(function(i,el) {
    $(this).closest('.js-tooltip').trigger('click');
})

開発ツールを開き、これをコンソールに貼り付けて実行すれば一括でいいねできます。

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

Promiseとはなんぞや?!できるだけわかりやすく説明してみた(ついでにasync/awaitも)

PromiseやAsync/awaitという言葉をよく耳にするようになってきましたが、どういう仕組みか理解せず苦しんでいる方も多いのではないでしょうか?

実は自分もこの記事書くまではかなり苦戦していました…!

そこで、できるだけ、本当にできるだけわかりやすく説明するために、いろんなケースを交えて説明していこうと思います!

そもそもどういうときにPromiseって必要になるんや?

まず以下のコードをご覧ください

function getData () {
  return axios.get('http://example.com')
  .then((res) => {
    return res;
  })
}

let data = {};
if (data) {
  data = getData ();
}
console.log(data);


上記のコードを実行すると、コンソールに帰ってくるのはexample.comのデータ…ではなくて、promiseが帰ります。
ここで、なぜpromiseにしているのか?と疑問に思った人も何人かいると思います。

もともと、javascriptは上から順番に実行されるとは限らない言語仕様になっています。
そのため、axiosのレスポンスよりも先に下のコードが実行されてしまう可能性があり、結果としてデータが何も入っていないまま帰ってしまうことがあるのです。

この現象を防ぐための役割を果たしているのがPromiseです

Promiseを使うことで、順番を指定することができます。

それでは、一体どのようにPromiseを使うのでしょうか?

実際にPromiseを使ってみる。

定義編

それでは、Promiseをつかってどのように順番を指定していくのかを見てみましょう。
基本的な使い方は、以下のコードのとおりになります。

function getData () {
  return new Promise(function(resolve, reject) {
    try {
      resolve('success!') // 成功したときの処理をここに書きます。
    } catch (e) {
      reject('error...') // 失敗したときの処理をここに書きます。

    }
  })
}
console.log(getData());

話をシンプルにするため、先程よりもコードをシンプルにしました。
関数の中でPromiseクラスをリターンしています。先程のaxiosの戻り地も、元はこのpromiseクラスが帰ってきています。

もしオブジェクト指向を理解していたら、このオブジェクトのインスタンス化する際の引数に、コールバック関数を定義してあげることで使えるようになります。

Promiseに必要な引数は第一引数のみです。コールバック関数の中で定義できる関数は2種類あります。

  • resolve:成功したときにreturnさせる値を引数に入れます。今回はsuccessという文字列を返しました。
  • reject:失敗したときにreturnさせる値を引数に入れます。今回はerror...という文字列を返しました。

上記のプログラムを実行すると

Promise { 'success!' }

上記のようなpromiseが帰ります。
promiseが帰ったときは、promiseを展開してあげることで中身のデータを取り出せるようになります

呼び出し編

先程のPromiseを呼び出すには、axiosを使っているときと同じように、thenとcatchを使ってあげます。

function getData () {
  return new Promise(function(resolve, reject) {
    try {
      resolve('success!') // 成功したときの処理をここに書きます。
    } catch (e) {
      reject('error...') // 失敗したときの処理をここに書きます。
    }
  })
}

// 実際にgetDataの中身を取り出す
getData()
.then(function (resolve) {
  console.log(resolve)
})
.catch(function (reject) {
  console.log(reject)
})
console.log('finish?')

引数とメソッド名を見るとなんとなく察した方がいると思います。
getDataメソッドを呼び出した際、成功した処理をthenに、失敗したときの処理をcatchに書きます。
また、それぞれの関数の引数は、成功したときはresolve、失敗したときはrejectが入ります。このコードから、getDataメソッドを定義している側と関連づいていることがわかりますね。

実際に上記のコードを実行をしてみると、一番最初に表示されたのはfinish?で、その後にsuccess!が表示されました。

なぜこの順番になるかというと、promiseの引数に渡されたコールバック関数内で、thenだったらresolve、catchだったらreject関数が呼ばれるまでは次の処理に行かないという特性があります。

ただし、getDataメソッドの下にあるconsole.log('finish?')は、promiseのスコープにないため、promise内の処理が完了し切る前に先にconsole.log('finish?')が実行されていまいます。

finish?を一番最後に表示させるにはどうしたらいいか?promiseを使えばこの問題も簡単に解決できます。


function getData () {
  return new Promise(function(resolve, reject) {
    try {
      resolve('success!') // 成功したときの処理をここに書きます。
    } cat ch (e) {
      reject('error...') // 失敗したときの処理をここに書きます。
    }
  })
}

function show() {
  return new Promise(function(resolve, reject) {
    getData()
    .then(function (resolve) {
      console.log(resolve)
    })
    .catch(function (reject) {
      console.log(reject)
    })
    resolve(true)
  })
}

show()
.then(function (checkBool) {
  if (checkBool) {
    console.log('finish!')
  }
})

show関数のresolveが呼び出された後にif (checkBool)が走ることを保証されているため、これで順番の秩序を保つことができました!

えーでもpromise定義するたびにコード肥大化しちゃうじゃーん

安心してください。ちゃんとスマートに書くためのアプローチがすでに用意されています。
ただ、ここから先の内容を理解するには、上記のPromiseのルールを頭に叩き込んでおく必要がありますので、まだ理解していない方はもう一度読み返すか、別のPromise関連の記事を参考にしてみてください。

async/awaitを使ってみる

上記のコードをasync/awaitを使ってシンプルにさせてみましょう。
async/awaitに置き換えるときのコツは以下です。

  • return new Promiseを外し、代わりにfunctionの左にasyncをつける(例:async function getData() {}
  • resolveやrejectにはreturnを使う。(1つめのawaitはresolve、2つめのawaitはreject)

これらを踏まえた上で書き換えてみると以下のとおりになります。

async function getData () {
  try {
    return 'success!' // 成功したときの処理をここに書きます。
  } catch (e) {
    return 'error...' // 失敗したときの処理をここに書きます。
  }
}

async function show() {
    getData()
    .then(function (resolve) {
      console.log(resolve)
    })
    .catch(function (reject) {
      console.log(reject)
    })
    return true
}

show()
.then(function (checkBool) {
  if (checkBool) {
    console.log('finish!')
  }
})

少しだけスッキリしました。もう少しスッキリさせてみましょう。

  • asyncを使っている関数内でpromise呼び出しているところがあれば、then,catchをなくし、代わりに変数を一つ用意してawaitを使って代入する
  • 通常定義でasync関数の中にない場合は、awaitを使えないので普通にthen,catchを使ったまま

少しイメージつきづらいと思うので、これに関してはソースコードを見たほうが早いかもしれません。

async function getData () {
  try {
    return 'success!' // 成功したときの処理をここに書きます。
  } catch (e) {
    return 'error...' // 失敗したときの処理をここに書きます。
  }
}

async function show() {
    let data = await getData()
    console.log(data)
    return true
}

show()
.then(function (checkBool) {
  if (checkBool) {
    console.log('finish!')
  }
})

だいぶスッキリ書くことができましたね!

まとめ

この記事を書きはじめる数分前に理解したばかりで、思考の整理をしながら記事に起こしてみたのですが、この記事が誰かの役に立つといいなと思っています。

※文章書くの苦手すぎる為読みづらくてごめんなさい?編集したほうがいい箇所ありましたら遠慮なくお申し付けくださいm(_ _)m

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

Google Geocordingは優秀だが高価なので、YOLPのサービスに利口に乗り換える方法

はじめに

 Google提供の地名からその緯度経度を返してくれるサービス、Geocordingはとても優秀でまず期待に裏切ることのない結果を返してくれます。ところが、便利さの裏返しか、高価なのも事実。5ドル/1000回つまり1回あたり0.55円くらい徴収されます。1ヶ月あたり200ドルまでは無料とありますが、Geocordingだけ利用しているということはまず無く、Google MapsやDirectionを兼用しているとあっという間に無料枠は使い切り、知らない間にカードから利用料がバンバン落とされるという自体にもなりかねません。Google Cloud Platformというところで、各サービス毎に日ごとの使用上限を事前に設定できるので、それによって貧乏になることは防げますが、上限を超えるとエラーが返ってくるので、せっかく使ってくれた人に不快な思いをさせてしまうことになってしまいます。

 そこでYOLP(Yahoo! Open Local Platform)のAPIを使ってGeocordingの代わりをさせようと考えるのは当然です。だって、YOLPは無料なのです。(ただし1日50,000回まで。そこまでは使いません)

Yahoo!ジオコーダAPIを使ってみる

 ご他聞にもれず、YOLPでも登録によるアプリケーションIDの取得は必須です。

YOLP登録ページ

 さて、GoogleのGeocordingに似ているサービスというと、Yahoo!ジオコーダAPIになります。

https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/geocoder.html

 名前が似ているので、まさにぴったりと言いたいところですが、本サービスには欠点があって、例えば
東京都新宿区新宿1-1
とか
渋谷区原宿1
みたいに町名+丁目くらいまでの指定をしてあげればその近くの緯度経度を返してくれるのですが、
東京タワー
とか
大阪城
は受け付けてくれません(泣)
(ちなみにGoogleのGeocordingは正しく返してくれます)

つまり住所からの検索サービスなので、実は使い勝手が悪いのです・・・。

ではということで、Yahoo!ローカルサーチAPIを使ってみる

これは逆に住所からというのではなく、地域・拠点情報の名称からその拠点の情報(緯度経度も)を返してくれるサービスです。
例えば、
東京タワー
で検索すると、東京タワーや東京タワーの名称を使った店(例えば●●商事東京タワー支店とか)が出てきます。東京タワーならまだしも、東京駅とか大阪城とかですと、希望の東京「駅」や大阪「城」でなく、その近くのお店とかがまずヒットするので、うまくいくと希望通り出ますが、うーん近いけどここじゃないなぁーというデメリットも生じます。
検索結果は複数(指定可)なので、何個か検索してその中から、文字数の一番少ないものを選ぶとかいろいろ頭を使えば上手く絞り込めるかもしれませんが・・・(どなたか私のコードにそれを付け加えていただくこと希望w)

結局、どうすればいいのか

つまり、コストを度外視すれば、正確性等から言うと
①Google Geocording
②Yahoo!ジオコーダAPI
③Yahoo!ローカルサーチAPI
の順番になります。

そこで、予めGoogle Cloud Platformで、Geocordingの日ごとの使用上限を事前に設定した上で、それを超えてエラーが出たら、②Yahoo!ジオコーダAPIを使って検索、それでもエラーが出たら③Yahoo!ローカルサーチAPIで検索したら、出費もコントロールできるし、検索結果がゼロという最悪の事態も回避できるのではないかと考えたわけです。

作ったコード

function showAddress(address) {
    var  geocoder = new google.maps.Geocoder();
    if (geocoder) {
    geocoder.geocode( { 'address': address}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            map.setCenter(results[0].geometry.location);
            } else {
        var geocoder = new Y.GeoCoder();
        geocoder.execute({query:address}, function(result){
        if (result.features.length > 0){
            map.setCenter(new google.maps.LatLng(result.features[0].latlng.lat(), result.features[0].latlng.lng() ));
            }else{
        var local = new Y.LocalSearch();
            var cid = '個々のヤフーIDを入れてください';
        local.search(address, cid, {}, function (ydf) {
                if (ydf.features.length > 0) {
                map.setCenter(new google.maps.LatLng(ydf.features[0].latlng.lat(), ydf.features[0].latlng.lng() ));
                } else {
                alert('Not Found');
                }
            });
            }
            });
        }
    });
    }
}

addressに検索ワードを入れてfunction showAddress(address)を呼び出せばそこを中心とした場所にGoogleMapが移動します。
(利用にはGoogleのID等は別途取る必要があります)

結び

地図を使ったサービスを提供していると、意外にもGeocordingのサービスを使う人が多いことを、奇しくもGoogleのサービスが有料化されてよく分かりました。今回はGeocording系の移管でしたが、標高API等は簡単に移行できるので、Google貧乏で悩んでいる方は他社のサービスを検討されるといいと思います。

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

3歳娘「いつから論理式が真偽値のみを返すと錯覚していた?」

ある日の某Web制作会社

ワイ「おはようございます〜」

社長「ん?やめ太郎」
社長「今日は有休とるんやなかったっけ?」

ワイ「そうなんでっけど、娘ちゃんがJavaScriptを教えてほしい言うもんでっから」
ワイ「よめ太郎と娘ちゃんと一緒に来たんですわ」
ワイ「会社のPC、勉強に使ってもええでっしゃろか?」

社長「(ええでっしゃろか?っていうか)」
社長「(もう、来てもうてるやん・・・)」
社長「も、もちろんええで・・・」

お勉強開始

ワイ「さあ、JavaScriptのお勉強開始や」

よめ太郎「ちゃんと本も買うてあるで」

ワイ「おう、気が利くやないけ〜」

ドサドサッ・・・

オライリー
詳解 Javaプログラミング
【全巻】

ワイ「Oh... Java...」

よめ太郎「2冊で10,000円近くしたわー」
よめ太郎「でもまあ、娘ちゃんの将来のためや!」

ワイ「あの、よめ太郎はん・・・?」
ワイ「JavaJavaScriptは違う言語やっていうたやん・・・?」

よめ太郎「どっちもプログラミング言語やろ?」

ワイ「いやいや、全然ちゃうねん」
ワイ「例えるなら大切な人排泄の音くらい違うねん」

ハスケル子「(インドインドネシアとかじゃないんだ・・・)」

よめ太郎「なんやそれ」
よめ太郎「ほな、どっちが排泄の音なん」

ワイ「・・・それは・・・Javaや」
ワイ「ジャバーーー!いうてな(震え声

よめ太郎「どんだけお腹ユルいねん、そいつ」

ワイ「まあええわ、本なしで勉強してみよか」

娘ちゃんの学びたいもの

ワイ「ほな、JSの何を勉強しよか」

娘(3歳)「演算子なぞなぞしたい!」

ワイ「お、ええな〜!(何やそれ・・・)」
ワイ「(近ごろの幼稚園児はひらがなより先に演算子おぼえるんか・・・?)」

娘「じゃあ行くよ〜」
娘「まずは比較演算子ね!」

const a = 1 !== 0;

console.log(a);

娘「これを実行するとコンソールには何て表示される?

ワイ「ええと、イコールの右側の1 !== 0は・・・」
ワイ「10違いまっせって意味やから・・・やな」
ワイ「せやから、trueaに代入されるな」
ワイ「コンソールに出力されるのはtrueや!」

娘「正解〜!
娘「パパ、すごーい!!」

ワイ「でへへ」
ワイ「(演算子なぞなぞ、なかなか楽しいやないかい・・・)」

娘「次は論理演算子!

if (0 || 1) {
    console.log("aaa");
} else {
    console.log("bbb");
}

娘「これは、どっちがコンソールに出力されるでしょう?」

ワイ「ええと」
ワイ「||OR演算子やから」
ワイ「左の値右の値、どちらかがtruthy1であれば」
ワイ「OR演算の結果はtrueになるはずや」
ワイ「せやから、コンソールに出力されるのは"aaa"や!」

娘「うーん、まあ正解でいいや!

ワイ「ワーイ!
ワイ「(ん?なんか引っかかるけどまあええか・・・)」

娘「じゃあ、もう一つ論理演算子!

const a = 1 && 2 && 3 && 4 && 5 && 0;

console.log(a);

娘「これはどうでしょう?」

ワイ「ぬぬぬ・・・?」
ワイ「&&AND演算子やから」
ワイ「この論理式は、ぜんぶの値がtruthyやったらtrueを返すし」
ワイ「1つでもfalsyな値があればfalseを返すはずや・・・!」
ワイ「一番右に0があるから・・・」
ワイ「コンソールに表示されるのはfalseや!!!」

娘「false・・・?」
娘「・・・・・・」
娘「いつから論理式が真偽値のみを返すと錯覚していた?

ワイ「ヒィッ!!

ハスケル子「(急に何・・・?)」

ワイ「ふ、不正解いうこと・・・?」

娘「そうです」
娘「ハスケル子さん、説明して差し上げなさい

ハスケル子「(わ、私?)」
ハスケル子「か、かしこまりました・・・」

論理演算子の挙動

ハスケル子「JavaScriptの論理演算子の挙動についてなんですけど」
ハスケル子「たとえばOR演算子は」

左辺または右辺のどちらかがtruthyなら、trueを返す。

ハスケル子「ではなくて

左辺がtruthyな場合は左辺の値を返す。
逆に、左辺がfalsyな場合は右辺の値を返す。

ハスケル子「なんです」

ワイ「え?trueかfalseを返すんちゃうの?」

ハスケル子「はい」
ハスケル子「左辺か右辺の値そのものを返すんです」
ハスケル子「なので、例えば」

const a = 0 || "こんにちは";

ハスケル子「この場合、aにはtrueとかじゃなくて」
ハスケル子「"こんにちは"が代入されるんです」

ワイ「ほえ〜」

ハスケル子「結果として、if文等のカッコの中では"こんにちは"が評価されて、trueの扱いになります」

ワイ「なるほどな〜」
ワイ「論理演算子を使うと自動的にtruefalseに変身するのかと思うてたわ」

ハスケル子「そう思いがちなんですが、実際には論理演算によって真偽値に変換されるのではなくて
ハスケル子「論理演算によって返された値が、if文等の()の中に入ると真偽値に型変換されるんです」

ワイ「なるほどなぁ」

ハスケル子「左辺がtruthyだった場合は、左辺の値を返す」
ハスケル子「その場合、右辺の内容はチェックすらされません

ワイ「え、そうなん?」

ハスケル子「はい」
ハスケル子「この、右辺の評価を行わないことを短絡評価またはショートサーキット評価と言うんです」

ワイ「ほうほう」

ハスケル子「短絡評価を利用すると、パフォーマンスのいいコードを書けたりもするんですよ?」
ハスケル子「例えば・・・」

const a = lightFunction() || heavyFunction();

ワイ「なるほど」
ワイ「まず軽い処理を実行して、その結果がfalsyな場合のみ、右辺の重い処理を実行する、いうことやな?」

ハスケル子「そういうことです」

ワイ「でも、両方の値をちゃんとチェックせんでも大丈夫なん?」
ワイ「if文とかの条件として正しく機能するん・・・?」

ハスケル子「左辺がtruthyな時点で」
ハスケル子「左 OR 右のどちらかがtruthy、という条件は満たされるので」
ハスケル子「右辺をチェックしなくても条件として問題ありません」

ワイ「ああ・・・言われてみれば、せやな」

ハスケル子「ほかにも、こんな書き方もよく見かけますよね」

const name = user.name || "名無しさん";

ワイ「ああ、なんかワイも見たことあるわ」
ワイ「ユーザーの名前が存在すれば、その名前を代入するし」
ワイ「存在しなければ取り敢えず"名無しさん"を代入しとく、いうやつやな」
ワイ「if文とか使わずスッキリ書けるな」

ハスケル子「はい」
ハスケル子「そして、肝心のAND演算子の挙動ですが・・・」
ハスケル子「OR演算子です」

ワイ「逆と言いますと・・・」

ハスケル子「つまり」

左辺がfalsyな場合は左辺の値を返す。
逆に、左辺がtruthyな場合は右辺の値を返す。

ハスケル子「っていうことなんです」

ワイ「ほえ〜」
ワイ「よう分からんけど、具体的にはどんな感じ?」

ハスケル子「例えば」

const a = 1 && 0;

ハスケル子「この場合はaには0が代入されます」

ワイ「ええと」
ワイ「左の値が1で、truthyやから」
ワイ「右辺の0aに代入される、と」

ハスケル子「そういうことです」
ハスケル子「ちなみAND演算子の場合は、左辺がfalsyな場合に右辺の評価が省略されます

ワイ「さっきの短絡評価やね」
ワイ「そこもOR演算子いうことか」

ハスケル子「はい」
ハスケル子「左辺の値がfalsyであれば、その時点で」
ハスケル子「左 AND 右が両方truthyという条件は成り立たないので」
ハスケル子「右辺の値を評価するまでもなく左辺のfalsyな値を返します」

ワイ「その場合、if文的にはfalse扱いか」

ハスケル子「はい」
ハスケル子「そして左辺の値がtruthyな場合には、右辺の値を返します」

ワイ「そうすると、左も右もtruthyであれば」
ワイ「右辺のtruthyな値が返されて、if文的にはtrue扱い
ワイ「左がtruthy右がfalsyの場合にも右の値が返されて」
ワイ「その場合、右の値はfalsyやから」
ワイ「if文的にはfalse扱いいうことか」

ハスケル子「はい」
ハスケル子「ルール的には凄くシンプルなんですけど」
ハスケル子「最初はちょっとややこしく感じますよね」
ハスケル子「そしてようやく、さっきの娘ちゃんの問題に戻ります」

const a = 1 && 2 && 3 && 4 && 5 && 0;

console.log(a);

ハスケル子「まず一番左端の1 && 2が評価されます」
ハスケル子「左右ともにtruthyなので右辺の2が返されます」
ハスケル子「なので、式全体は↓こうなるイメージですね」

const a = 2 && 3 && 4 && 5 && 0;

ワイ「左端にあった1 && 2が、2に変身したんやな」

ハスケル子「そういうことです」
ハスケル子「そして今度は」
ハスケル子「次に左端になった2 && 3が評価されて3になります」

const a = 3 && 4 && 5 && 0;

ワイ「左右ともtruthyの場合は、右辺の値になるんやもんな」
ワイ「なるほどな〜」

ハスケル子「次は3 && 4が評価されて4になるので」

const a = 4 && 5 && 0;

ハスケル子「↑こうです」
ハスケル子「最終的には」

const a = 5 && 0;

ハスケル子「5 && 0が評価されて・・・どうなりますか?」

ワイ「ええと、左辺の5truthyやから、右辺の0aに代入されることになるわ!」

ハスケル子「ですね」
ハスケル子「結果的に、全ての値がtruthyなら、一番右のtruthyな値が返されて」
ハスケル子「if文的にはtrue扱いになりますし」

ワイ「ほうほう」

ハスケル子「逆に、1つでもfalsyな値があれば、そのfalsyな値が返されるので」
ハスケル子「if文的にはfalse扱いになるんです」

ワイ「ほぉ〜」
ワイ「そんな仕組みやったんやね」
ワイ「娘ちゃんとケル子ちゃんのおかげで今日も勉強なったわ・・・!」

社長「(結局、娘ちゃんやなくてやめ太郎が勉強してるやん・・・)」

そんなこんなで帰る時間

ワイ「ところで・・・」
ワイ「このオライリーの分厚いJava本(×2)どないしよ・・・」
ワイ「10,000円近くも払うてもうて・・・」

よめ太郎「メ、メルカリで8,000円で売ったらええやろ!」

ワイ「メルカリ勢がオライリーのJava本買うか?」

よめ太郎「誰か1人くらいJAVA TEAと勘違いして買うやろ」

ワイ「どこに8,000円のJAVA TEAがあんねん」

よめ太郎「120本入りいうことにしけば、誰か勘違いしてくれるんちゃうか?」

ワイ「もうそれ勘違いというか」
ワイ「騙しに行ってるやん

よめ太郎「なんや、文句ばかり言うて!」

娘「ママ、また怒っちゃったの〜?

ワイ「(娘ちゃん、アカン・・・)」

よめ太郎「何やて!?

ワイ「いやいや、声を荒げたらアカンて」
ワイ「まだ会社内なんやから」
ワイ「一応空気を読んで・・・」

娘「空気をよめ太郎!
娘「キャハハハ!」

よめ太郎「」

何かが切れる音がした

よめ太郎「やめさしてもらうわ!」

ワイ「ああ・・・」
ワイ「1人で帰ってもうた・・・」

残された娘ちゃんとワイ

ワイ「はあ・・・またよめ太郎とモメてもうた・・・」

娘「それでいいんだよ」
娘「たまにはケンカしたっていい」
娘「何度もぶつかり合って、少しずつ」
娘「互いに合う形に変わって行くんだよ」

ワイ「娘ちゃん・・・!」
ワイ「ほんまその通りやな・・・」
ワイ「でもな・・・」
ワイ「今日のは君のせいやないかい!

〜おしまい〜


  1. truthy: 真偽値に変換された場合にtrueとみなされる値のこと。falseとみなされる値はfalsy。 

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

【最新サービス試用⑥】簡単にドキュメントやポートフォリオを作成できるサイト作成ツールの「Docusaurusu」を試用

  • 日々輩出される素晴らしき最新サービスを素早く試して、不鮮明な先見性を堂々と誇示する記事第六弾。
  • 幾度となく、「最先端」「自動生成」というサービスに一目散に飛びついてきた生活は、元号変化の新時代も継続。
  • 今回は、マークダウン形式で簡単にドキュメントやブログを作成できるジェネレータの「Docusaurus」を試用することにしよう。

目的とゴール

  • Docusaurusの概要や特徴の把握
  • 類似サービスの把握
  • Docusaurusの導入方法の把握
  • プロジェクトの作成方法の把握
  • サーバーの起動方法の把握
  • 新規ページの作成方法の把握
  • 様々なマークダウン形式の表示確認
  • ※今回はデプロイやCIツールとの連携は行わない

概要

  • Docusaurusは、Facebook開発のドキュメント作成ジェネレーター。
  • マークダウンを用いて、簡単にドキュメントを作成できるため、主にオープンソースプロジェクトのドキュメント生成用として使われている。
  • 公開やCIツールとの連携が容易であるため、ポートフォリオサイト作成としても使われている。
  • 公式サイト

特徴

マークダウン形式でのドキュメント記述

  • ドキュメントやコンテンツをマークダウンで記述することができ、自動でページやHTMLファイルを出力してくれる。
  • シンタックスハイライトやテーブル等、様々なマークダウン形式に対応している。

Reactを用いた記述

  • レイアウトの拡張やカスタマイズを、Reactで記述することができる。

翻訳機能対応

  • Crowdinという翻訳サービスとの連携で、簡単にドキュメントの多言語対応が実装できる。

検索機能対応

  • DocSearchという検索サービスとの連携で、簡単にドキュメントの文章検索機能を実装できる。

バージョニング

  • ドキュメントが古くなっても、最新の内容に基づいて、最適なバージョン管理を行うことができる。

サイト公開とCI連携の容易さ

  • ホスティングサービスであるNetlifyGitHub Pagesを利用して、簡単にサイトを公開することができる。
  • また、CircleCITravisCI等の、CIツールとの連携にも対応

類似サービス

手順

環境

  • Mac OS X 10.13.4Mac OS X 10.13.4
  • NodeJs v11.6.0

導入

  • ターミナル(コマンドプロンプト)を開き、下記のコマンドをうち、Docusaurusを導入する。
    • Nodejsが入っていない場合、インストールする。
$ npm install --global docusaurus-init

プロジェクト作成

  • 下記のコマンドをうち、Docusaurus新規プロジェクトを作成する。
# プロジェクトフォルダの作成
$ mkdir ~/docsaurus_sample
# プロジェクトフォルダへ移動
$ cd ~/docsaurus_sample
# Docusaurus新規プロジェクトの作成
$ docusaurus-init

構成

  • docusaurus-initコマンドで作成されたプロジェクトは、下記のような構成になっている。
root
├── docs
│   ├── doc1.md
│   ├── doc2.md
│   ├── doc3.md
│   ├── exampledoc4.md
│   └── exampledoc5.md
└── website
    ├── blog
    │   ├── 2016-03-11-blog-post.md
    │   ├── 2017-04-10-blog-post-two.md
    │   ├── 2017-09-25-testing-rss.md
    │   ├── 2017-09-26-adding-rss.md
    │   └── 2017-10-24-new-version-1.0.0.md
    ├── core
    │   └── Footer.js
    ├── package.json
    ├── pages
    ├── sidebars.json
    ├── siteConfig.js
    └── static
  • 主に下記の内容で、適切なファイルを配置していく
    • docs/・・・ドキュメントのMarkdownファイルを配置していく。
    • website/blog・・・ブログ用記事の配置していく。
    • website/pages・・・通常ページファイルを配置していく。
    • website/static・・・サイトの画像やスタイルを配置していく。

チュートリアルページ確認

  • 下記のコマンドをうち、ローカルサーバーを起動する。
# websiteフォルダへ移動
$ cd ~/docsaurus_sample/website
# ローカルサーバー起動
$ npm start

image

新規ページ作成

1. ドキュメントページ作成

  • docs/配下に、.md形式でファイルを新規作成して、配置する。
    • 今回は、サンプルとして、intro.mdとして作成する。
  • intro.mdの中身を下記にする。
    • シンタックスハイライト部分の「\」は不要。
intro.md
---
id: intro
title: t_o_d on intro
---

# タイトルテスト

## サブタイトルテスト

### 本文テスト

- 本文テスト

### ネストテスト

- 本文テスト
    - ネストテスト

### 書式等テスト

- **太字テスト**
- ~~打消し線テスト~~
- <details><summary>折りたたみテスト</summary>折りたたまれているようだ</details>
- > 引用テスト

### 水平線テスト

- 水平線テスト
---

### 画像テスト

![penguin](https://user-images.githubusercontent.com/44114228/57018373-3eedac80-6c5e-11e9-91e8-2d5ce7d418ac.png)

### シンタックスハイライトテスト

\```ruby
# テスト
puts "Hello t_o_d"
\```

### テーブルテスト

|タイトル|内容|
|:---:|:---:|
|イントロ|テスト|

### リンクテスト

- [リンク](https://www.google.co.jp/)
  • 中身を記述後、website\sidebars.jsonに作成したファイルを、下記のように追加する。
sidebar.json
{
  "docs": {
    "Docusaurus": ["doc1"],
    "First Category": ["doc2"],
    "Second Category": ["doc3"],
    "Intro": ["intro"]
  },
  "docs-other": {
    "First Category": ["doc4", "doc5"]
  }
}
  • 中身を記述後、http://localhost:3000/docs/intro にアクセスして、下記の画面が表示されることを確認する。
    • アクセスできない場合、ローカルサーバーをCtrl + Cで終了して、再度npm startで起動する。

image.png
image.png

2. 通常ページ作成

  • website\pages\en\配下に、.js形式でファイルを新規作成して、配置する。
    • 今回は、サンプルとして、test.jsとして作成する。
  • test.jsの中身を、既存のindex.jsを少し変えて、下記のようにする。
    • .jsの中身はReact記法で書くことができる。
test.js
const React = require('react');

const CompLibrary = require('../../core/CompLibrary.js');

const Container = CompLibrary.Container;

function Test(props) {
    return (
        <div className="docMainWrapper wrapper">
            <Container className="mainContainer documentContainer postContainer">
                <h1>Hello t_o_d</h1>
                <p>I am t_o_d</p>
            </Container>
        </div>
    );
}

module.exports = Test;
  • 記述して保存後、http://localhost:3000/test にアクセスして、下記の画面が表示されることを確認する。

image.png

まとめ

  • 今回は、サイト生成ジェネレーターの試用ということで、日々増殖を続ける自動生成ツールの恩恵を際限なくいただきながら、記事を書く。
  • 「OSSプロジェクトのドキュメント用だが、技術記事やポートフォリオとしても利用できそう」という、主目的の「OSS」の高権威専用用語から、全力で逃避を図ろうとする。
  • 「試用ということで使ってみたが、検索機能や翻訳機能、CI連携等も確かめてみよう」という、今後の目標を確定した直後に、次なる豊かな最新サービスを探す迷走生活。

参考

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

【最新サービス試用⑥】簡単にドキュメントやポートフォリオを作成できるサイト作成ツールの「Docusaurus」を試用

  • 日々輩出される素晴らしき最新サービスを素早く試して、不鮮明な先見性を堂々と誇示する記事第六弾。
  • 幾度となく、「最先端」「自動生成」というサービスに一目散に飛びついてきた生活は、元号変化の新時代も継続。
  • 今回は、マークダウン形式で簡単にドキュメントやブログを作成できるジェネレータの「Docusaurus」を試用することにしよう。

目的とゴール

  • Docusaurusの概要や特徴の把握
  • 類似サービスの把握
  • Docusaurusの導入方法の把握
  • プロジェクトの作成方法の把握
  • サーバーの起動方法の把握
  • 新規ページの作成方法の把握
  • 様々なマークダウン形式の表示確認
  • ※今回はデプロイやCIツールとの連携は行わない

概要

  • Docusaurusは、Facebook開発のドキュメント作成ジェネレーター。
  • マークダウンを用いて、簡単にドキュメントを作成できるため、主にオープンソースプロジェクトのドキュメント生成用として使われている。
  • 公開やCIツールとの連携が容易であるため、ポートフォリオサイト作成としても使われている。
  • 公式サイト

特徴

マークダウン形式でのドキュメント記述

  • ドキュメントやコンテンツをマークダウンで記述することができ、自動でページやHTMLファイルを出力してくれる。
  • シンタックスハイライトやテーブル等、様々なマークダウン形式に対応している。

Reactを用いた記述

  • レイアウトの拡張やカスタマイズを、Reactで記述することができる。

翻訳機能対応

  • Crowdinという翻訳サービスとの連携で、簡単にドキュメントの多言語対応が実装できる。

検索機能対応

  • DocSearchという検索サービスとの連携で、簡単にドキュメントの文章検索機能を実装できる。

バージョニング

  • ドキュメントが古くなっても、最新の内容に基づいて、最適なバージョン管理を行うことができる。

サイト公開とCI連携の容易さ

  • ホスティングサービスであるNetlifyGitHub Pagesを利用して、簡単にサイトを公開することができる。
  • また、CircleCITravisCI等の、CIツールとの連携にも対応

類似サービス

手順

環境

  • Mac OS X 10.13.4Mac OS X 10.13.4
  • NodeJs v11.6.0

導入

  • ターミナル(コマンドプロンプト)を開き、下記のコマンドをうち、Docusaurusを導入する。
    • Nodejsが入っていない場合、インストールする。
$ npm install --global docusaurus-init

プロジェクト作成

  • 下記のコマンドをうち、Docusaurus新規プロジェクトを作成する。
# プロジェクトフォルダの作成
$ mkdir ~/docsaurus_sample
# プロジェクトフォルダへ移動
$ cd ~/docsaurus_sample
# Docusaurus新規プロジェクトの作成
$ docusaurus-init

構成

  • docusaurus-initコマンドで作成されたプロジェクトは、下記のような構成になっている。
root
├── docs
│   ├── doc1.md
│   ├── doc2.md
│   ├── doc3.md
│   ├── exampledoc4.md
│   └── exampledoc5.md
└── website
    ├── blog
    │   ├── 2016-03-11-blog-post.md
    │   ├── 2017-04-10-blog-post-two.md
    │   ├── 2017-09-25-testing-rss.md
    │   ├── 2017-09-26-adding-rss.md
    │   └── 2017-10-24-new-version-1.0.0.md
    ├── core
    │   └── Footer.js
    ├── package.json
    ├── pages
    ├── sidebars.json
    ├── siteConfig.js
    └── static
  • 主に下記の内容で、適切なファイルを配置していく
    • docs/・・・ドキュメントのMarkdownファイルを配置していく。
    • website/blog・・・ブログ用記事の配置していく。
    • website/pages・・・通常ページファイルを配置していく。
    • website/static・・・サイトの画像やスタイルを配置していく。

チュートリアルページ確認

  • 下記のコマンドをうち、ローカルサーバーを起動する。
# websiteフォルダへ移動
$ cd ~/docsaurus_sample/website
# ローカルサーバー起動
$ npm start

image

新規ページ作成

1. ドキュメントページ作成

  • docs/配下に、.md形式でファイルを新規作成して、配置する。
    • 今回は、サンプルとして、intro.mdとして作成する。
  • intro.mdの中身を下記にする。
    • シンタックスハイライト部分の「\」は不要。
intro.md
---
id: intro
title: t_o_d on intro
---

# タイトルテスト

## サブタイトルテスト

### 本文テスト

- 本文テスト

### ネストテスト

- 本文テスト
    - ネストテスト

### 書式等テスト

- **太字テスト**
- ~~打消し線テスト~~
- <details><summary>折りたたみテスト</summary>折りたたまれているようだ</details>
- > 引用テスト

### 水平線テスト

- 水平線テスト
---

### 画像テスト

![penguin](https://user-images.githubusercontent.com/44114228/57018373-3eedac80-6c5e-11e9-91e8-2d5ce7d418ac.png)

### シンタックスハイライトテスト

\```ruby
# テスト
puts "Hello t_o_d"
\```

### テーブルテスト

|タイトル|内容|
|:---:|:---:|
|イントロ|テスト|

### リンクテスト

- [リンク](https://www.google.co.jp/)
  • 中身を記述後、website\sidebars.jsonに作成したファイルを、下記のように追加する。
sidebar.json
{
  "docs": {
    "Docusaurus": ["doc1"],
    "First Category": ["doc2"],
    "Second Category": ["doc3"],
    "Intro": ["intro"]
  },
  "docs-other": {
    "First Category": ["doc4", "doc5"]
  }
}
  • 中身を記述後、http://localhost:3000/docs/intro にアクセスして、下記の画面が表示されることを確認する。
    • アクセスできない場合、ローカルサーバーをCtrl + Cで終了して、再度npm startで起動する。

image.png
image.png

2. 通常ページ作成

  • website\pages\en\配下に、.js形式でファイルを新規作成して、配置する。
    • 今回は、サンプルとして、test.jsとして作成する。
  • test.jsの中身を、既存のindex.jsを少し変えて、下記のようにする。
    • .jsの中身はReact記法で書くことができる。
test.js
const React = require('react');

const CompLibrary = require('../../core/CompLibrary.js');

const Container = CompLibrary.Container;

function Test(props) {
    return (
        <div className="docMainWrapper wrapper">
            <Container className="mainContainer documentContainer postContainer">
                <h1>Hello t_o_d</h1>
                <p>I am t_o_d</p>
            </Container>
        </div>
    );
}

module.exports = Test;
  • 記述して保存後、http://localhost:3000/test にアクセスして、下記の画面が表示されることを確認する。

image.png

まとめ

  • 今回は、サイト生成ジェネレーターの試用ということで、日々増殖を続ける自動生成ツールの恩恵を際限なくいただきながら、記事を書く。
  • 「OSSプロジェクトのドキュメント用だが、技術記事やポートフォリオとしても利用できそう」という、主目的の「OSS」の高権威専用用語から、全力で逃避を図ろうとする。
  • 「試用ということで使ってみたが、検索機能や翻訳機能、CI連携等も確かめてみよう」という、今後の目標を確定した直後に、次なる豊かな最新サービスを探す迷走生活。

参考

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

Vue CLIに関するすること(メモ)

Vue-cliをするまで

https://qiita.com/567000/items/dde495d6a8ad1c25fa43

こちらのページがわかりやすかった。

https://techacademy.jp/magazine/16105

npmについても調べたページをメモ。

手順画像

1.VS Codeを開く
VS Code.jpg

2.ターミナルを開く(※コマンドプロンプトでもいいけどね)
Terminal.jpg

3.「cd (ファイルを保存するディレクトリ)」
cd.jpg

4.「vue create (プロジェクト名)」
vue create.jpg

※versionが古いと怒られる・・・

vue create is a Vue CLI 3 only command and you are using Vue CLI 2.9.6.
You may want to run the following to upgrade to Vue CLI 3:
npm uninstall -g vue-cli
npm install -g @vue/cli

(意訳)
「『vue create』は3からのコマンドなのにお前の古いよ」
「下のコマンド打って上げろ」

ver.jpg

5.保存したディレクトリを開く
created.jpg

6.できてる!
directory.jpg

つづく

つづく

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

[JavaScript]タッチイベントからoffsetXとoffsetXを取得する

スマホのタッチイベントからoffsetX, offsetYを取得する手法。
忘れがちなのでメモ。

        const rect = event.target.getBoundingClientRect()
        const offsetX = (event.touches[0].clientX - window.pageXOffset - rect.left) 
        const offsetY = (event.touches[0].clientY - window.pageYOffset - rect.top)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeError: $(…).validate is not a functionではまった件

ライブラリ内の関数が参照できない。。。

  • 開発経験無しの初心者が自作Webアプリケーション作成に挑戦してみてはまった点を備忘録がてらに記述しています。
  • お気軽にコメント頂けますと幸いです:bow_tone1:
    • 同様の現象ではまり、より詳細な情報が欲しい方
    • 記述内容に関して、より詳細なお持ちの方
    • 記述内容に関して、疑問点がある方

対象レベル

  • 初学者
    • Webアプリケーション開発経験なし。
    • JavaScriptを用いた開発初体験。

発生環境

現象

  • 以下のコマンドでバリデーション実行。
    • $('セレクタ').validate(ルール);
  • "TypeError: $(…).validate is not a function"のエラーで関数が実行できない。
  • validate()関数は、ライブラリ内で定義されている。
  • ライブラリ(jQuery.validate.jsファイル)自体は読み込まれている。
  • なぜ、、、?

原因

  • JavaScriptの読み込みタイミングで、はまっていた模様。
  • 記述箇所・順序、asyncやdeferを組み合わせ読み込みタイミングを制御することで正常に動作するようになった。 ## 注意点
  • JavaScriptでは、読み込む順番・タイミングによってエラーが発生する。
  • コピペではなく、どこで何を読み込みどこで動作させているかを認識する必要がある。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 初心者まとめ②

Reactアプリケーションはelement(要素)という構成ブロックにより構成されています。
コンポーネントは要素によって構成されたものです。
コンポーネントを使うことにより、UIを部品に分割し分離させることができます。

コンポーネントはpropsと呼ばれる任意の値を受け取り、画面上に表示すべきものを返すReaxt要素を返します。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

これはpropsで受け取ったnameを表示させる関数コンポーネントです。

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

これは等価なコードです、classコンポーネントを使う際はextends React.Componentを記載して下さい。
またファイル冒頭にimport React, { Component } from 'react';を書くことも必要です。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

このコードではelementと定義された要素を引数としてReactDOM.renderが呼び出されます。
そのあとelementに代入されたwelcomeコンポーネントを呼び出し、その時propsとしてname=’Sara’が渡されます。
その後ReactDOMが

Hello, Sara

になるようDOMを更新します。

Reactでは

<welcome />はDOMタグ
<Welcome />はコンポーネント
と認識されることを覚えておきましょう。

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello World</h1>
      </div>
    );
  }
}

これはclassコンポーネントですが、特徴として内部にプライベート状態(state)をもたせたり、
生成状況によって呼び出されるライフサイクルメソッドを持っています。
主に親コンポーネントとして使用し関数コンポーネントで作った子コンポーネントに、保持している値を伝える親玉のような存在と言えるでしょう。

const App = (props) => {
  return (
    <div>
      <h1>Hello World</h1>
    </div>
    <h2>Hello React</h2>
  );
};

これは同レベルのReact Elementがあるためエラーになります。

const App = (props) => {
  return (
    <div>
      <h1>Hello World</h1>
      <h2>Hello React</h2>
    </div>
  );
};

とdivタグで囲ってあげましょう。

// 親コンポーネント
class Parent extends React.Component {
  //superは必須です
  constructor(props) {
    super(props);
    this.state = {
      value1: 'foo',
      value2: [ 'bar', 'baz' ],
    };
  }
  render() {
    return (
      <div>
        <Child1 data={this.state.value1} />
        <Child2 data={this.state.value2} />
      </div>
    );
  }
}

// Functionalコンポーネントで受け取る場合
const Child1 = (props) => (
  <div>
    {props.data}
  </div>
);

// Classコンポーネントで受け取る場合
class Child2 extends React.Component {
  render() {
    return (
      <div>
        {this.props.data}
      </div>
    );
  }
}

親で独自の名前を付けて子に流していく流れですね。
クラスコンポーネント間でも受け取ることができます。

viewで発生したイベントハンドラはクラスコンポーネントのメソッドで設定して下さい。
その際はキャメルケースで書く必要があります。
またコールバック関数として実行する際にthisが機能するようconstructor()内でthisをbindしておく必要があります。
これはjavascriptにまつわる話題です。

ReactDom.render(
  <App />,
  document.querySelector('.content')
);

これは冒頭紹介したコードです、深掘りしていきましょう。
親コンポーネント(App.js)のrender()メソッドで返されたレンダリングの実態は仮想DOMと呼ばれるものです。仮想DOMではブラウザで表示できないので生のDOMに変換する必要があるのですが、そこでこのコードが活躍します。
第一引数に親コンポーネント、第二引数にhtmlで表示・挿入する部分を渡しています。

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

JSでブラウザの履歴を操作する

historyについて

JSではhistoryオブジェクトを使用して、ブラウザの履歴を操作することができる
ブラウザの戻るボタンを押した時に戻る場合など、この履歴に応じてページの遷移が行われている
ブラウザの履歴を操作する

historyは削除できない

追加や変更はできるようだが、historyの削除はできないよう

代替案

ケースバイケースだけど、もし消したいと思った場合に使えるかもしれないことがいくつかあったのでまとめ
A→B→C の遷移でCから戻る時にAに戻したいというケースを考えてみる(Bを消したい)

履歴を残さないで遷移させる

画面遷移時にlocation.replaceを使うと履歴が残らないようになる

BページからCページへ遷移する際に使用をすると、Bページの履歴が残らなくなる
そのため、Cページで戻るボタンを押すとその前にいたAページに戻ることができる

画面遷移:A→B→C
履歴:A→C

// page B → page C
location.replace('/C/')

参考
http://note.onichannn.net/archives/1443

履歴書き換えることはできる

前にいたーページをJSを使って書き換えることで、戻るボタンを押したおきにそのページに戻るようにする

A→B→Cの順番でページの遷移をしている場合、Cページで1つ前の履歴をAページとするとAページに戻れるが、A→A→Cという履歴になってしまうので注意が必要

画面遷移:A→B→C
履歴:A→A→C

// Cページで1つ前の履歴をAページとする
history.replaceState(null,null,"/A/");

戻るボタンクリック時に2つ戻す

戻るボタンをクリックすると、JSでC→B→Aと遷移をさせる

画面遷移:A→B→C
履歴:A→B→C

// 戻るボタンを押された時に何も処理を行わない
history.pushState(null, null, null);

// 戻るボタンを押した時の処理を追加
window.addEventListener("pushState", function() {
  // -2で2つ戻る(-1なら1つ戻る)
  history.go(-2);
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebase (Hosting & Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅 その2

はじめに

令和の時代がやって参りました!

一昨日、Firebase (Hosting & Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅という記事を投稿させていただいたのですが、「Service Workerを利用した構成の方がオススメ!」という有難い助言を頂戴しましたので、早速実践してみました。
大枠は前記事の通りですが、 nuxt 標準の pwa-module を使い、 Service Worker を利用してセッションを保持する実装にしています。
初学者としてつまづいたポイントと一緒に、構成を残しておきます。

目標

前回と同じ

  • NuxtはUniversalモードで起動する
  • Firebase HostingとFirebase FunctionsでサーバレスSSRを実現する
  • /account/login にレンダリングしたFirebaseUIからFirebase Authenticationでログインする
    • ログイン後は index にリダイレクトする
  • ログインユーザ情報をコンテンツ中で取得できるようにする
  • ユーザがログインしていない場合、どのページにアクセスしても /account/login にリダイレクトする
    • リダイレクト前に元ページのレンダリングはしない

辿り着いた答え

ポイント

  • Service Workerをnuxtに導入するにあたり yarn create nuxt-appChoose features to install と聞かれた時に Progressive Web App (PWA) Support にチェックを入れていれば、追加パッケージ等は必要ありませんでした。
  • Firebase公式を参考に導入しましたが、nuxtのPWA Supportがお膳立てしてくれている部分が結構あります。firebase-auth用のservice worker を新しく作成し、importScriptsで読み込むという方針であれば、Service Workerを追加する処理は不要でした。
  • クライアント側はService Workerさえ導入してしまえば、その他変更なくただリクエスト投げるだけというびっくり簡単っぷりでした。
  • ルーティング時の認証チェックは様々な実装方法があると思いますが、前回同様サーバ限定middlewareですんなり実装できました。
  • 多分初学者限定ですが・・・service-workerはブラウザ上で動くので、importとかrequireができず苦労しました。
  • これも初学者限定ですが・・・service-workerはFirebase Hostingでホストさせる必要があるというのがわからず苦戦しました。

ディレクトリ構成

ディレクトリ構成
.
├── firebase.json
├── functions
│   ├── index.js
│   ├── nuxt
│   └── package.json
├── public
│   ├── sw-firebase-auth.js
│   └── sw.js
└── src
    ├── assets
    ├── components
    ├── layouts
    ├── middleware
    │   └── authenticated.js
    ├── nuxt.config.js
    ├── package.json
    ├── pages
    ├── plugins
    │   ├── firebase-admin.js
    │   └── firebase.js
    ├── server
    ├── static
    │   ├── _sw-firebase-auth.js
    │   ├── sw-firebase-auth.js
    │   └── sw.js
    └── store

例によって無駄なのは省いてます。
新しく追加したこととして、static配下とpublicに sw-firebas-auth.js が追加されています。src/static 配下にアンスコ有無の2ファイルがある理由は後述。

導入の流れ

公式ドキュメント に沿って、どのように導入していったか書いておきます。

Service Worker に対する変更

ユーザーがログインしたときに現在の ID トークンを取得できるように、Service Worker に Auth ライブラリを組み込む必要があります。

ここで「Service Worker?え、どこにそんなファイルが??」でだいぶ詰みました。

色々文献を漁り、どうやらPWAサポートが追加されていればnuxtがいい感じにservice workerを追加してくれるらしいことがわかってきました。
yarn create nuxt-appのダイアログで聞かれるコレ↓ですね。後から追加するやり方もあるみたいです。

? Choose features to install (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Progressive Web App (PWA) Support
 ◯ Linter / Formatter
 ◯ Prettier
 ◯ Axios

で、 yarn run build すると勝手に /src/static/sw.js が生成されてました。簡単ですね。

「じゃあ編集対象ってどのファイルなの??」で結構悩みましたが、pwa-module公式ドキュメントを読んだところ、スクリプトが外出しでき、nuxt.conf.jsでインポートする設定で行けることが分かりました。

/src/nuxt.conf.js
  workbox: {
    importScripts: [
      'sw-firebase-auth.js'
    ]
  }

コレだけでService Workerを追加することができます。簡単・・・。
で、/src/static/sw-firebase-auth.js を追加してこいつを編集していけばOKでした。

余談ですが、pwa-module公式ドキュメントの記載だと、importScriptsのjsファイルは/assetsに配置するだけでオッケー!と書いてますが、/staticの間違いじゃないだろうか・・・。

Authライブラリの組み込み

次に詰まったのがここです。

/src/static/sw-firebase-auth.js
// Initialize the Firebase app in the service worker script.
firebase.initializeApp(config);

ん? firebaseって未定義だからいきなり呼んだってダメでしょと思いながら動かしてみるとやっぱりfirebase is undefinedでChromeに怒られます。

src/static/sw-firebase-auth.js
var firebase = require('firebase')

// Initialize the Firebase app in the service worker script.
firebase.initializeApp(config);

じゃあコレでいけるっしょ!と思いきや、requireって何やねんとChromeに怒られます。

多分初学者あるある(だと思いたい)ですが、require でライブラリが読み込めるのはNode上だけだとよく分かっておりませんでした。(だってvueだと普通にクライアントで動いてるっぽい感じで書けるから・・・恥)
service workerはブラウザ上で動くため、requireしようとしてもできないわけですね。
となると外部ライブラリをどうやって読み込むんじゃいと色々調べた結果、HTML側で <script src=""> を実行する以外に方法はなく、javascriptにライブラリインポートの概念自体存在しないことがわかってきました。
jsファイル内に <script src=""> を書いてみるという恥ずかしい悪あがきもして撃沈した後、Browserify を使うという解決策にたどり着きました。

私と同じく初学者向けにBrowserifyを簡潔にご説明すると、jsだと外部ライブラリのインポートできないけど、外部ライブラリ全部ファイルの中に埋め込んじゃえば万事オッケーじゃん☆☆ という発想のシロモノです。

$ yarn global add browserify

で導入して

$ mv static/sw-firebase-auth.js static/_sw-firebase-auth.js
$ browserify static/_sw-firebase-auth.js -o static/sw-firebase-auth.js

で変換完了です。
当然、firebaseがまるっとファイルに含まれるので、正味78行のファイルが50,000行くらいになりました。
もっといいやり方ないだろうか・・・。
毎回やるのは面倒なので、package.jsonのscripts.buildに入れておくと良いかもしれませんね。

あとは9割コピペですが、一応Service Workerの全文を残しておきます。

/src/static/_sw-firebase-auth.js
var firebase = require('firebase')

firebase.initializeApp({
  apiKey: '*****************',
  authDomain: '*****************',
  databaseURL: '*****************',
  projectId: '*****************',
  storageBucket: '*****************',
  messagingSenderId: '*****************'
})

const getIdToken = () => {
  return new Promise((resolve) => {
    const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
      unsubscribe();
      if (user) {
        user.getIdToken().then((idToken) => {
          resolve(idToken)
        }, () => {
          resolve(null)
        });
      } else {
        resolve(null)
      }
    })
  })
}

const getOriginFromUrl = (url) => {
  const pathArray = url.split('/');
  const protocol = pathArray[0];
  const host = pathArray[2];
  return protocol + '//' + host;
};

self.addEventListener('fetch', (event) => {
  const requestProcessor = (idToken) => {
    let req = event.request;
    if (self.location.origin == getOriginFromUrl(event.request.url) &&
        (self.location.protocol == 'https:' ||
         self.location.hostname == 'localhost') &&
        idToken) {
      const headers = new Headers();
      for (let entry of req.headers.entries()) {
        headers.append(entry[0], entry[1]);
      }
      headers.append('Authorization', 'Bearer ' + idToken);
      try {
        req = new Request(req.url, {
          method: req.method,
          headers: headers,
          mode: 'same-origin',
          credentials: req.credentials,
          cache: req.cache,
          redirect: req.redirect,
          referrer: req.referrer,
          body: req.body,
          bodyUsed: req.bodyUsed,
          context: req.context
        });
      } catch (e) {
        console.log(e)
      }
    }
    return fetch(req);
  };
  event.respondWith(getIdToken().then(requestProcessor, requestProcessor));
});

self.addEventListener('activate', event => {
  event.waitUntil(clients.claim());
})

サーバー側に対する変更

あとはほとんどコピペでサーバ側の変更をmiddlewareとして実装すれば動きました。
これも9割型コピペなので、スクリプトを見ていただくのが早いと思います。

/src/middleware/authenticated.js
function getIdToken(req) {
  const authorizationHeader = req.headers.authorization || ''
  const components = authorizationHeader.split(' ')
  return components.length > 1 ? components[1] : ''
}

export default (({ req, redirect }) => {
  if (process.server) {

    var admin = require('firebase-admin')
    const idToken = getIdToken(req)

    admin.auth().verifyIdToken(idToken).then(() => {
      redirect('/')
    }).catch((error) => {
      console.log(error)
      redirect('/account/login')
    })
  }
})

現状だとリロードすると毎回 / に戻っちゃうので、routerでうまく制御しないといけないですが、それは今後。
ログインしていない状態だとエラー吐いて気持ち悪いですが、それも今後。。

この状態で yarn run start するとちゃんと動いてくれました。クライアント側は何も一切気にせずただリクエストを投げればいいという、実にデベロッパフレンドリー。

Firebaseへのデプロイ

これが最大の詰まりポイントで、令和元年初日をまるっと奪い去っていきました。

ローカルで問題なく動いたので firebase serve でも全然いけるっしょ、と思いきや、以下Chromeに怒られます。

A bad HTTP response code (404) was received when fetching the script.
Failed to load resource: net::ERR_INVALID_RESPONSE
dcbac67bb39a765db27a.js:1 Service worker registration failed: TypeError: Failed to register a ServiceWorker: A bad HTTP response code (404) was received when fetching the script.

NetworkやSourceタブをみる限り、どうやら sw.js が見つからない御様子。
dist を見ても、そもそもそんなファイルがデプロイされてない。でも yarn run start だと普通に動いている。
Firebaseを悪者にして「service-workerのファイル名に制約があるんでしょ?」とファイル名を公式サンプルの通りに変えてみてもダメ。
「じゃあsw.jsってやつ dist/client に手動で置いてやれ」と思って置いてみてもダメ。
「pwa-moduleとFirebaseの相性でうまく動かないんだ!手動でサービスワーカーを配置しよう!」これもダメ。

堂々巡りに陥っていましたが、ようやく解決策と自分の勘違いに気づきました。
「Service Workerはブラウザで実行されるので、Firebase Hostingでホストされる必要がある」 これでした。
いくらdistを弄っても、Firebase Functions側の変更なので意味がなかったんですね。。。
それと同時に、/src/static 配下のファイルはFirebase Hostingでホスティングされる必要があるということに今更気づきました。今回の構成では /public 配下に置いてやる必要があります。
そういえば firebase serve だとfaviconなかったよなあ。。。今更。

├── public
│   ├── sw-firebase-auth.js
│   └── sw.js

これで firebase serve firebase deploy 共にしっかり動きました。

追記

firebase deploy を実行した際、deployされる前に実行するスクリプトをあらかじめ定義するという超絶便利機能があるとコメント頂戴しましたので、今回の場合の設定方法を記載しておきます!
deploy前に /public 配下を /static でまるっと更新しています。
【参考】デプロイ前フックおよびデプロイ後フック

firebase.json
 {
   "hosting":{
     "postdeploy":"rm -rf public/* && cp src/static/* public/",
   }
 }

これで firebase deploy するだけで /public 配下が /src/static のファイルで更新されます。便利!

終わりに

前回の記事に有難いコメントを頂いたおかげで、Service Workerを用いた、より簡潔でパフォーマンスも高い実装にできたと思います。感謝感激雨嵐。
Firebase、Nuxtいずれも詰まるポイントは多いですが、大体自分のそもそものjavascript経験の浅さから来るものなので、解決してしまえば数ステップでかなりすんなりデプロイできてしまいます。面倒なことは全部これに任せて、サービスの市場投入を加速するにはもってこいですね。

今後はFirestore関連を掘り下げて学んでいきたいと思います。

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

Firebase (Hosting × Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅 その2

はじめに

令和の時代がやって参りました!

一昨日、Firebase (Hosting & Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅という記事を投稿させていただいたのですが、「Service Workerを利用した構成の方がオススメ!」という有難い助言を頂戴しましたので、早速実践してみました。
大枠は前記事の通りですが、 nuxt 標準の pwa-module を使い、 Service Worker を利用してセッションを保持する実装にしています。
初学者としてつまづいたポイントと一緒に、構成を残しておきます。

目標

前回と同じ

  • NuxtはUniversalモードで起動する
  • Firebase HostingとFirebase FunctionsでサーバレスSSRを実現する
  • /account/login にレンダリングしたFirebaseUIからFirebase Authenticationでログインする
    • ログイン後は index にリダイレクトする
  • ログインユーザ情報をコンテンツ中で取得できるようにする
  • ユーザがログインしていない場合、どのページにアクセスしても /account/login にリダイレクトする
    • リダイレクト前に元ページのレンダリングはしない

辿り着いた答え

ポイント

  • Service Workerをnuxtに導入するにあたり yarn create nuxt-appChoose features to install と聞かれた時に Progressive Web App (PWA) Support にチェックを入れていれば、追加パッケージ等は必要ありませんでした。
  • Firebase公式を参考に導入しましたが、nuxtのPWA Supportがお膳立てしてくれている部分が結構あります。firebase-auth用のservice worker を新しく作成し、importScriptsで読み込むという方針であれば、Service Workerを追加する処理は不要でした。
  • クライアント側はService Workerさえ導入してしまえば、その他変更なくただリクエスト投げるだけというびっくり簡単っぷりでした。
  • ルーティング時の認証チェックは様々な実装方法があると思いますが、前回同様サーバ限定middlewareですんなり実装できました。
  • 多分初学者限定ですが・・・service-workerはブラウザ上で動くので、importとかrequireができず苦労しました。
  • これも初学者限定ですが・・・service-workerはFirebase Hostingでホストさせる必要があるというのがわからず苦戦しました。

ディレクトリ構成

ディレクトリ構成
.
├── firebase.json
├── functions
│   ├── index.js
│   ├── nuxt
│   └── package.json
├── public
│   ├── sw-firebase-auth.js
│   └── sw.js
└── src
    ├── assets
    ├── components
    ├── layouts
    ├── middleware
    │   └── authenticated.js
    ├── nuxt.config.js
    ├── package.json
    ├── pages
    ├── plugins
    │   ├── firebase-admin.js
    │   └── firebase.js
    ├── server
    ├── static
    │   ├── _sw-firebase-auth.js
    │   ├── sw-firebase-auth.js
    │   └── sw.js
    └── store

例によって無駄なのは省いてます。
新しく追加したこととして、static配下とpublicに sw-firebas-auth.js が追加されています。src/static 配下にアンスコ有無の2ファイルがある理由は後述。

導入の流れ

公式ドキュメント に沿って、どのように導入していったか書いておきます。

Service Worker に対する変更

ユーザーがログインしたときに現在の ID トークンを取得できるように、Service Worker に Auth ライブラリを組み込む必要があります。

ここで「Service Worker?え、どこにそんなファイルが??」でだいぶ詰みました。

色々文献を漁り、どうやらPWAサポートが追加されていればnuxtがいい感じにservice workerを追加してくれるらしいことがわかってきました。
yarn create nuxt-appのダイアログで聞かれるコレ↓ですね。後から追加するやり方もあるみたいです。

? Choose features to install (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Progressive Web App (PWA) Support
 ◯ Linter / Formatter
 ◯ Prettier
 ◯ Axios

で、 yarn run build すると勝手に /src/static/sw.js が生成されてました。簡単ですね。

「じゃあ編集対象ってどのファイルなの??」で結構悩みましたが、pwa-module公式ドキュメントを読んだところ、スクリプトが外出しでき、nuxt.conf.jsでインポートする設定で行けることが分かりました。

/src/nuxt.conf.js
  workbox: {
    importScripts: [
      'sw-firebase-auth.js'
    ]
  }

コレだけでService Workerを追加することができます。簡単・・・。
で、/src/static/sw-firebase-auth.js を追加してこいつを編集していけばOKでした。

余談ですが、pwa-module公式ドキュメントの記載だと、importScriptsのjsファイルは/assetsに配置するだけでオッケー!と書いてますが、/staticの間違いじゃないだろうか・・・。

Authライブラリの組み込み

次に詰まったのがここです。

/src/static/sw-firebase-auth.js
// Initialize the Firebase app in the service worker script.
firebase.initializeApp(config);

ん? firebaseって未定義だからいきなり呼んだってダメでしょと思いながら動かしてみるとやっぱりfirebase is undefinedでChromeに怒られます。

src/static/sw-firebase-auth.js
var firebase = require('firebase')

// Initialize the Firebase app in the service worker script.
firebase.initializeApp(config);

じゃあコレでいけるっしょ!と思いきや、requireって何やねんとChromeに怒られます。

多分初学者あるある(だと思いたい)ですが、require でライブラリが読み込めるのはNode上だけだとよく分かっておりませんでした。(だってvueだと普通にクライアントで動いてるっぽい感じで書けるから・・・恥)
service workerはブラウザ上で動くため、requireしようとしてもできないわけですね。
となると外部ライブラリをどうやって読み込むんじゃいと色々調べた結果、HTML側で <script src=""> を実行する以外に方法はなく、javascriptにライブラリインポートの概念自体存在しないことがわかってきました。
jsファイル内に <script src=""> を書いてみるという恥ずかしい悪あがきもして撃沈した後、Browserify を使うという解決策にたどり着きました。

私と同じく初学者向けにBrowserifyを簡潔にご説明すると、jsだと外部ライブラリのインポートできないけど、外部ライブラリ全部ファイルの中に埋め込んじゃえば万事オッケーじゃん☆☆ という発想のシロモノです。

$ yarn global add browserify

で導入して

$ mv static/sw-firebase-auth.js static/_sw-firebase-auth.js
$ browserify static/_sw-firebase-auth.js -o static/sw-firebase-auth.js

で変換完了です。
当然、firebaseがまるっとファイルに含まれるので、正味78行のファイルが50,000行くらいになりました。
もっといいやり方ないだろうか・・・。
毎回やるのは面倒なので、package.jsonのscripts.buildに入れておくと良いかもしれませんね。

あとは9割コピペですが、一応Service Workerの全文を残しておきます。

/src/static/_sw-firebase-auth.js
var firebase = require('firebase')

firebase.initializeApp({
  apiKey: '*****************',
  authDomain: '*****************',
  databaseURL: '*****************',
  projectId: '*****************',
  storageBucket: '*****************',
  messagingSenderId: '*****************'
})

const getIdToken = () => {
  return new Promise((resolve) => {
    const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
      unsubscribe();
      if (user) {
        user.getIdToken().then((idToken) => {
          resolve(idToken)
        }, () => {
          resolve(null)
        });
      } else {
        resolve(null)
      }
    })
  })
}

const getOriginFromUrl = (url) => {
  const pathArray = url.split('/');
  const protocol = pathArray[0];
  const host = pathArray[2];
  return protocol + '//' + host;
};

self.addEventListener('fetch', (event) => {
  const requestProcessor = (idToken) => {
    let req = event.request;
    if (self.location.origin == getOriginFromUrl(event.request.url) &&
        (self.location.protocol == 'https:' ||
         self.location.hostname == 'localhost') &&
        idToken) {
      const headers = new Headers();
      for (let entry of req.headers.entries()) {
        headers.append(entry[0], entry[1]);
      }
      headers.append('Authorization', 'Bearer ' + idToken);
      try {
        req = new Request(req.url, {
          method: req.method,
          headers: headers,
          mode: 'same-origin',
          credentials: req.credentials,
          cache: req.cache,
          redirect: req.redirect,
          referrer: req.referrer,
          body: req.body,
          bodyUsed: req.bodyUsed,
          context: req.context
        });
      } catch (e) {
        console.log(e)
      }
    }
    return fetch(req);
  };
  event.respondWith(getIdToken().then(requestProcessor, requestProcessor));
});

self.addEventListener('activate', event => {
  event.waitUntil(clients.claim());
})

サーバー側に対する変更

あとはほとんどコピペでサーバ側の変更をmiddlewareとして実装すれば動きました。
これも9割型コピペなので、スクリプトを見ていただくのが早いと思います。

/src/middleware/authenticated.js
function getIdToken(req) {
  const authorizationHeader = req.headers.authorization || ''
  const components = authorizationHeader.split(' ')
  return components.length > 1 ? components[1] : ''
}

export default (({ req, redirect }) => {
  if (process.server && process.env.NODE_ENV === 'production') {
    var admin = require('firebase-admin')
    const idToken = getIdToken(req)
    admin.auth().verifyIdToken(idToken).then(() => {
      redirect('/')
    }).catch((error) => {
      console.log(error)
      redirect('/account/login')
    })
  }
})

※ 2019/5/3追記: Service WorkerはProduction環境でないと生成されないので、process.envの分岐を加えました。yarn run devだとmiddlewareはスルーされますのでご留意下さい。

現状だとリロードすると毎回 / に戻っちゃうので、routerでうまく制御しないといけないですが、それは今後。
ログインしていない状態だとエラー吐いて気持ち悪いですが、それも今後。。

この状態で yarn run start するとちゃんと動いてくれました。クライアント側は何も一切気にせずただリクエストを投げればいいという、実にデベロッパフレンドリー。

Firebaseへのデプロイ

これが最大の詰まりポイントで、令和元年初日をまるっと奪い去っていきました。

ローカルで問題なく動いたので firebase serve でも全然いけるっしょ、と思いきや、以下Chromeに怒られます。

A bad HTTP response code (404) was received when fetching the script.
Failed to load resource: net::ERR_INVALID_RESPONSE
dcbac67bb39a765db27a.js:1 Service worker registration failed: TypeError: Failed to register a ServiceWorker: A bad HTTP response code (404) was received when fetching the script.

NetworkやSourceタブをみる限り、どうやら sw.js が見つからない御様子。
dist を見ても、そもそもそんなファイルがデプロイされてない。でも yarn run start だと普通に動いている。
Firebaseを悪者にして「service-workerのファイル名に制約があるんでしょ?」とファイル名を公式サンプルの通りに変えてみてもダメ。
「じゃあsw.jsってやつ dist/client に手動で置いてやれ」と思って置いてみてもダメ。
「pwa-moduleとFirebaseの相性でうまく動かないんだ!手動でサービスワーカーを配置しよう!」これもダメ。

堂々巡りに陥っていましたが、ようやく解決策と自分の勘違いに気づきました。
「Service Workerはブラウザで実行されるので、Firebase Hostingでホストされる必要がある」 これでした。
いくらdistを弄っても、Firebase Functions側の変更なので意味がなかったんですね。。。
それと同時に、/src/static 配下のファイルはFirebase Hostingでホスティングされる必要があるということに今更気づきました。今回の構成では /public 配下に置いてやる必要があります。
そういえば firebase serve だとfaviconなかったよなあ。。。今更。

├── public
│   ├── sw-firebase-auth.js
│   └── sw.js

これで firebase serve firebase deploy 共にしっかり動きました。

追記

firebase deploy を実行した際、deployされる前に実行するスクリプトをあらかじめ定義するという超絶便利機能があるとコメント頂戴しましたので、今回の場合の設定方法を記載しておきます!
deploy前に /public 配下を /static でまるっと更新しています。
【参考】デプロイ前フックおよびデプロイ後フック

firebase.json
 {
   "hosting":{
     "postdeploy":"rm -rf public/* && cp src/static/* public/",
   }
 }

これで firebase deploy するだけで /public 配下が /src/static のファイルで更新されます。便利!

終わりに

前回の記事に有難いコメントを頂いたおかげで、Service Workerを用いた、より簡潔でパフォーマンスも高い実装にできたと思います。感謝感激雨嵐。
Firebase、Nuxtいずれも詰まるポイントは多いですが、大体自分のそもそものjavascript経験の浅さから来るものなので、解決してしまえば数ステップでかなりすんなりデプロイできてしまいます。面倒なことは全部これに任せて、サービスの市場投入を加速するにはもってこいですね。

今後はFirestore関連を掘り下げて学んでいきたいと思います。

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

javascriptのメソッドとコンストラクタ

メソッドとは

プログラム中は、意味や内容がまとまっている処理や、繰り返し現れる処理を、ひとつのグループとしてまとめます。

コンストラクタとは

 あらかじめ定義されたクラスをインスタンスに転化する仕組みです。

JavaScriptのメソッドとコンストラクタ

function foo() {
    return 1
}
var v = foo(); // fooをメソッドとして扱う
var o = new foo(); // fooをコンストラクタとして扱う

JavaScript の 「function」キーワード はメソッド、コンストラクタ両方を宣言することが可能です。

メソッドオブジェクトとコンストラクタオブジェクト

メソッドオブジェクトとは:a object with [[call]] private field
コンストラクタオブジェクトとは:a object with [[construct]] private field

つまり、[[call]] private fieldをもつオブジェクは、メソッドとして呼び出すことができます。
[[construct]] private fieldをもつオブジェクは、コンストラクタとして呼び出すことができます。

「function」キーワードで生成されたメソッド(実はオブジェクト)は必ず[[call]] private field、[[construct]] private field両方を持ち、メソッドまたはコンストラクタとして扱われます。

まだ例に戻りましょう

var o = new foo(); // 何が発生したのか

[[construct]]を呼びされた場合、発生したことは以下になります:

  • Object.protoypeをprotoypeとして新しいオブジェクトを生成する
  • step1で生成したオブジェクトの[[call]]を実行する
  • [[call]]の戻り値がオブジェクト->この値を返す
  • [[call]]の戻り値がオブジェクト以外->step1で生成したオブジェクトを返す

ちなみに、[[construct]]を呼ぶことで新しいオブジェクトを返された場合は(step3)、このオブジェクトがクロージャになります。(外部からこのオブジェクトのプロパティをアクセスできないため)、例えば:

function foo(){
    this.a = 100;
    return {
        getA: function() {
            return this.a;
        }
    }
}
var f = new foo;
f.getA(); //100
//f.a 外からアクセスできない

クラスの定義と関数の定義の切り分け

クラスの定義と関数の定義とも「function」キーワードを使っているのは何となく気持ち悪いのでなんかいい方法があります?

「class」キーワード

es6から「class」キーワードがサポートしているため、new class()でコンストラクタ処理を行えるようになります。

アロー関数

アロー関数とは、より短く記述できる、通常の function 式の代替構文です(javaのlambda関数みたいなやつ)
アロー関数で生成したのは関数だけ、コンストラクタとして扱わないです。

new (v => 0) // error

まとめ

「function」キーワードで生成されたメソッドはメソッドまたはコンストラクタとして扱われます。
「function」キーワードを使わず、関数宣言の場合はアロー関数、クラス宣言の場合は「class」キーワードの方が個人的良いのではないかと思います。

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

Reactで入れ子のstateをsetStateするときにuncontrolledになる

表題の件、地味にハマったのでメモしておきます。

こんなWarningが出るやつです。

Warning: A component is changing a controlled input of type text
to be uncontrolled. Input elements should not switch from controlled
to uncontrolled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.
More info: https://fb.me/react-controlled-components
import React  from 'react';
import update from 'immutability-helper';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text1: 'aaa',
      group: {
        text2: 'bbb',
        text3: 'ccc',
        text4: 'ddd',
      },
    }
  }

  handleText1 = (event) => {
    // [通常]これは入れ子でないので普通にsetStateしてOK
    this.setState({ text1: event.target.value });
  }

  handleText2 = (event) => {
   // [問題]これはWarningになる
    // (Warning: A component is changing a controlled input of type text to be uncontrolled.)
    // uncontrolledになるのは、フォームのvalueがnullやundefinedになる場合に発生する。
    // ここでは、this.state.group.text3やtext4がセットされずundefinedになるために警告がでる。
    this.setState({ 
      group: { text2: event.target.value }
    });
  }

  handleText3 = (event) => {
    // [解1]上記エラーを回避するためには、一度this.state.groupをコピーし、
    // コピーしたオブジェクトを更新後、それをsetStateする。
    const newGroupState = Object.assign({}, this.state.group);
    newGroupState.text3 = event.target.value;
    this.setState({ group: newGroupState });
  }

  handleText4 = (event) => {
    // [解2]immutability-helperを使う。
    // https://github.com/kolodny/immutability-helper
    this.setState({ group: update(this.state.group, { text4: {$set: event.target.value} }) });
  }


  render() {
    return (
      <React.Fragment>
        text2を変更するとコンソールにwarningが出る。
        <table>
          <tbody>
            <tr><td>text1</td><td><input type="text" value={this.state.text1}       onChange={this.handleText1.bind(this)} /></td></tr>
            <tr><td>text2</td><td><input type="text" value={this.state.group.text2} onChange={this.handleText2.bind(this)} /></td></tr>
            <tr><td>text3</td><td><input type="text" value={this.state.group.text3} onChange={this.handleText3.bind(this)} /></td></tr>
            <tr><td>text4</td><td><input type="text" value={this.state.group.text4} onChange={this.handleText4.bind(this)} /></td></tr>
            <tr><td>state</td><td>{JSON.stringify(this.state)}</td></tr>
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default App;

ソース:
https://github.com/xojan0120/uncontrolled_warning_test

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

WebGLで整数頂点バッファーを使う

WebGLではWebGLRendering​Context​.vertex​Attrib​Pointer()でバッファーオブジェクトの使用方法を指定します。vertexAttribPointertype引数にはgl.SHORTなど整数のデータ型を指定できますが、シェーダー側では浮動小数点数としてしか受け取れないので、使用する際には以下のようにシェーダー側で型変換する必要があります。

gl.bufferData(gl.ARRAY_BUFFER, new Int16Array([0, 1, 2]), gl.STATIC_DRAW);
...
gl.vertexAttribPointer(0, 1, gl.SHORT, false, 0, 0);
頂点シェーダー
#version 300 es

layout (location = 0) in float i_index;

vec3[3] POSITIONS = vec3[](
  vec3(-0.5, -0.5, 0.0),
  vec3(0.5, -0.5, 0.0),
  vec3(0.0, 0.5, 0.0)
);

void main(void) {
  gl_Position = vec4(POSITIONS[int(i_index)], 1.0);
}

WebGL2からはWebGL2Rendering​Context​.vertex​Attrib​IPointer()を使用すれば以下のようにシェーダー側で整数として受け取れるようになるので、型変換の必要がなくなります。(わかりづらいですがAttribの後にIがあります)

layout (location = 0) in int i_index;

型付き配列の精度に応じてvertexAttribIPointertypeを決定します。例えば、Int16Arrayの場合はgl.SHORTを、Int32Arrayの場合はgl.INTを使用することになります。

gl.bufferData(gl.ARRAY_BUFFER, new Int16Array([0, 1, 2]), gl.STATIC_DRAW);
...
gl.vertexAttribIPointer(0, 1, gl.SHORT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Int32Array([0, 1, 2]), gl.STATIC_DRAW);
...
gl.vertexAttribIPointer(0, 1, gl.INT, false, 0, 0);

符号なし整数を使用する場合は16bit、32bitそれぞれ以下のようになります。

gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array([0, 1, 2]), gl.STATIC_DRAW);
...
gl.vertexAttribIPointer(0, 1, gl.UNSIGNED_SHORT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Uint32Array([0, 1, 2]), gl.STATIC_DRAW);
...
gl.vertexAttribIPointer(0, 1, gl.UNSIGNED_INT, false, 0, 0);

シェーダー側では次のようにuintで受け取ることができます。

layout (location = 0) in uint i_index;

サンプルのソースコードをgistに置いておきました。
https://gist.github.com/aadebdeb/5f2e70095762e24ab5f132925f67e3cd

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

React + ReduxでTodoアプリを作ってみよう!『Add Todo編』

概要

前回までの記事では、ReactとReduxの基本をまとめさせて頂きました。今回からは、さらにもう一歩踏み込んでReactとReduxを用いたTodoアプリを作ってみたいと思います。(参考:Redux Basic Tutrial

前回の記事
『Redux』を用いて状態管理をしてみよう!

今回は、TodoをTodoListに追加する『Add Todo』の機能を実装したいと思います。

Action & Action Creator

まずは、ActionとAction Creatorの定義から始めていきましょう!
ActionのtypeはADD_TODOと定義し、textは入力されたTodo、idは追加されていくTodoの番号となります。

src/actions/index.js
export ADD_TODO = 'ADD_TODO';

let nextTodoId = 0;
export const addTodo = text => {
 return {
  type: ADD_TODO,
  id: nextTodoId++,
  text, //text: textを省略
 };
};

Reducer

次にReducerを定義していきましょう!
まずは全てのReducerを結合するファイルであるsrc/reducers/index.jsを作成しましょう!combineReducers()などに関して知りたい方は前回の記事をご覧ください

src/reducers/index.js
import {combineReducers} from 'redux';
import todos from './todo';

const todoApp = combineReducers({todos});

export default todoApp;

続いて、実際にActionから発行されるtypeを受け取って状態遷移を行うsrc/reducers/todo.jsを書いていきましょう!
...stateによって今まで追加されてきたTodoが、{id: action.id, text: action.text}によって新しく追加されたTodoが配列に格納され返ってきます。

src/reducers/todo.js
import {ADD_TODO} from '../actions';

const todos = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, {id: action.id, text: action.text}];
    default:
      return state;
  }
};

export default todos;

store

storeはアプリケーション内のstate(状態)を管理しているところです。詳しくは先ほどと同様に前回の記事を参照してください。

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {createStore} from 'redux';

import App from './components/App';
import {addTodo} from './actions';
import * as serviceWorker from './serviceWorker';
import reducers from './reducers';

let store = createStore(reducers);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Presentational Components

『Presentational Components』はpropsとして受け取ったデータを表示することに専念するコンポーネントになります。

src/components/Todo.jsではpropsとして受け取ったデータを表示します。

src/components/Todo.js
import React from 'react';
import PropTypes from 'prop-types';

const Todo = ({text}) => {
  return <li>{text}</li>;
};

Todo.propTypes = {
  text: PropTypes.string.isRequired,
};

export default Todo;

一方src/components/TodoList.jsでは、mapを用いてpropsとして受け取ったtodosをTodo Componentに渡しています。{...todo}はtodoの全ての要素を表しており、id={todo.id}text=todo.textと同じ意味を持っています。

src/components/TodoList.js
import React from 'react';
import propTypes from 'prop-types';
import Todo from './Todo';

const TodoList = ({todos}) => {
  return (
    <ul>
      {todos.map(todo => (
        <Todo key={todo.id} {...todo} />
      ))}
    </ul>
  );

  TodoList.propTypes = {
    todos: propTypes.arrayOF(
      propTypes.shape({
        id: propTypes.number.isRequired,
        text: propTypes.string.isRequired,
      }).isRequired
    ).isRequired,
  };
};

export default TodoList;

Container Components

Container componentはPresentational componentに具体的なデータやコールバック関数を与えるコンポーネントです。

src/containers/VisibleTodoList.jsでは、まずはmapStateToPoropsを用いてstateから必要な情報をpropsとしてマッピングさせます。そしてconnect関数を用いてTodoList Componentとstateを結びつけて、VisibleTodoListとしてexportします。

src/containers/VisibleTodoList.js
import {connect} from 'react-redux';
import TodoList from '../components/TodoList';

const mapStateToPorops = state => {
  return {todos: state.todos};
};

const VisibleTodoList = connect(mapStateToPorops)(TodoList);

export default VisibleTodoList;

これで、ブラウザにTodoを表示させることが出来るようになりましたが、まだTodoを入力するFormがないので作成していきます。

src/containers/AddTodo.js
import React from 'react';
import {connect} from 'react-redux';
import {addTodo} from '../actions';

let AddTodo = ({dispatch}) => {
  let input;

  return (
    <div>
      <input
        ref={node => {
          input = node;
        }}
      />
      <button
        onClick={() => {
          dispatch(addTodo(input.value));
          input.value = '';
        }}>
        Add Todo
      </button>
    </div>
  );
};

AddTodo = connect()(AddTodo);

export default AddTodo;

src/containers/AddTodo.jsではdispatch(addTodo(input.value));と書くことで、buttonを押した際にinput要素に入力された内容をTodoに追加出来るようになっています。

またinput要素内でrefを用いておりますが、refを用いることで、あらかじめ宣言していたinputに入力された内容を格納することができます。今回は以下に示す3つの方法の中でrefコールバック属性を用いる方法を選択しています。

Refsを扱うには3つ方法があります。

  • React.createRefメソッドを用いる(v16.3.0以上で利用可能)
  • refコールバック属性を用いる
  • ref文字列属性を用いる(非推奨 v17で削除)

参考:ReactのRefsを正しく取り扱う

ブラウザに表示させてみよう!

では、src/components/App.js<AddTodo /><VisibleTodoList />を追加してブラウザに表示させてみましょう!

src/components/App.js
import React from 'react';
import VisibleTodoList from '../containers/VisibleTodoList';
import AddTodo from '../containers/AddTodo';

const App = () => {
  return (
    <div className="App">
      <AddTodo />
      <VisibleTodoList />
    </div>
  );
};

export default App;

これで『Add Todo』の機能は実装できたと思います。次回は、Todoの完了・未完了を切り替える『Toggle Todo』の機能を実装していきましょう!

ezgif.com-optimize (1).gif

リファレンス

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

React 初心者まとめ

Reactを自分なりにまとめてみた①

初めての投稿です。
PHPとLaravelを使ったアプリ作成の経験はあったのですが、Reactを学んで興味深い点などあったので自分用のメモとして残します。
Javascriptのコーディング経験もなく、ES6以前にJavascriptって何状態だったので同じような初学者の方へ参考にもなればと思います。

まず前提としてReactはJavascriptのライブラリなので、Javascriptの最低限の理解が必要というのは言うまでもないことなのですが、他の言語をやったことがある方でしたら、入りやすいのかなと感じました。

Javascriptを完璧にやってからReactをやるというのも良いと思いますが、ES6の記法をさらってReactを触り始めてもいいんじゃないかなって思います。

ここで重要なのは(僕自身意識してます)、分かんないのはJavascriptなのかReactなのかを明確にする必要があるということです。

最初ということでJSXについての記述がほとんどになりますのでご存知のかたは②にお進み下さい。

const element = <h1>Hello, world!</h1>;

この構文はJSXと呼ばれるJavascriptの拡張構文です。
ReactではこのJSXを多用します。

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);

constでnameを宣言して、中括弧に囲んでJSX内で使用しています。
PHPで言うところの
$name = “Josh Perez”;
のようなものですが、Javascriptにはlet,const,varが存在しているので差異は理解する必要があります。

const element = <div tabIndex="0"></div>;

文字列リテラルを属性として指定するために引用符を使用できます。
JSXではキャメルケースのプロパティ命名規則を使用します。
キャメルケースとは、アルファベットで複合語やフレーズを表記する際、各単語や要素語の先頭の文字を大文字で表記する手法のことです。
例えばtabindexはtabIndexとなります。

const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;

RectDOMはJSXに埋め込まれた値をレンダリングされる前にエスケープするので、
ユーザーの入力したあらゆるコードが注入できないことが保証されています。
PHPで言うところのhtmlspecialchars()のようなものが、JSXでは担保されているということでしょうか。

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

本番環境(AWS)でjavascriptを読み込む方法

はじめに

某プログラミングスクールの課題で、Railsを使ってECサイトを作成。

開発環境

Ruby 2.3.1
Rails 5.2.2.1

発生時の状況

トップページに掲載している画像を、javascriptを使ってスライドさせる機能を開発環境で実装。
本番環境にデプロイすると、画像がスライドされず、また大きくレイアウトも崩れました。

エラーの原因

原因は、application.jsに
=require rails-ujs
=require jquery-ujs

の2つの記述があり、Rails側とjQuery側でファイルの呼び出しを行いエラーが発生しているようでした。

解決策

調べた結果、上記のコードのいずれかを削除すると正常に動作する模様。
今回は、上の方のコードを削除したところ正常に動作しました。恐らく下のコードを削除しても正常に動作する。

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