20200627のJavaScriptに関する記事は18件です。

【Vue】mixin / directives / filters / Vue Routerについてまとめてみたよ

Vueまとめ3(上級編)

こちらの記事は、Adnan Babakan 氏によりDev.to上で公開された『 Vue cheat sheet 3 』の邦訳版です(原著者から許可を得た上での公開です)

原著をベースに説明の足りない部分は適宜、追記しています。
(追記・改変の許可は得ています。)


Cover image for Vue cheat sheet 3 (advanced)

DEV.toコミュニティの皆さん、こんにちは!

Vueまとめシリーズは多くの注目を集めたので、シリーズの公開を続けることにしました。これは、ほとんどすべての主要な初心者向けの事柄が以前のものでカバーされているので、これは少し高度になっています。

いくつかのコードサンプルは公式ウェブサイトから引用しています。Vue.jsで確認できます。1

mixins - ミックスイン

簡単に言うと、ミックスインは別々のファイルに保存されるコンポーネントの一部であり、他のコンポーネントで再使用可能なもの。

ミックスインは次のよう書くことができる。

// define a mixin object
export const myMixin = {
  created: function () {
    this.hello()
  },

  methods: {
    hello: function () {
      console.log('Hello from mixin!')
    }
  }
}

ご覧のとおり、これにはcreatedフックとhelloというメソッドがあるので、このmixinを次のようにコンポーネントで使うことができる。

    <script>
        import { myMixin } from './myMixin.js'

        export default {
            mixins: [myMixin]
        }
    </script>

したがって、このコンポーネントでこれらのフックとメソッドを記述した場合と同様に機能する。

ミックスインが競合した場合

コンポーネント自身のプロパティまたはメソッドとミックスインの間に競合がある場合は、ミックスインではなくコンポーネントの方が優先される。

    let mixin = {
      data: function () {
        return {
          message: 'hello',
          foo: 'abc'
        }
      }
    }

    new Vue({
      mixins: [mixin],

      data: function () {
        return {
          message: 'goodbye',
          bar: 'def'
        }
      },

      created: function () {
        console.log(this.$data)
        // ミックスインではなくコンポーネントのdataになっている
        // => { message: "goodbye", foo: "abc", bar: "def" }
      }
    })

Vueによる上書きの処理方法は変更できるが、それについては別の記事で説明する。

Vue.mixin() - グローバルミックスイン

グローバルミックスインは、作成されたすべてのVueインスタンスをスコープに含むという以外は通常のミックスインと同様の機能を提供する。

import Vue from 'vue'

Vue.mixin({
    methods: {
        sayHi() {
            alert('Salam!')
        }
    }
});

const app = new Vue({
    el: '#app'
})

このメソッドsayHiは、上記のコードのすべてのコンポーネントとVueインスタンスで使用できる。

directives - カスタムディレクティブ

ご存じのとおり、ディレクティブはVueがDOMを処理する方法だ。たとえば、v-modelやv-showはディレクティブだ。

ディレクティブを定義するには、次のようにする。

<script>
export default {
    directives: {
        focus: {
            inserted: function (el) {
                el.focus()
            }
        }
    }
}
</script>

focusディレクティブは、v-focusとして次のように使うことができるようになる。

// コンポーネントがレンダリングされるとすぐにinput要素がフォーカスされる
<input v-focus />

Vue.directives() - カスタムグローバルディレクティブ

カスタムディレクティブをVueインスタンス全体でグローバルに使用できるようにするには、次のように定義する。

Vue.directive('focus', {
  inserted: function (el) {
    el.focus()
  }
})

filters - フィルター

フィルターは、単に値を変更してそれを返すために使用される。
それらは、ムスタッシュ構文とv-bindディレクティブの両方で使うことができる。

コンポーネントでフィルターを定義するには、次のように定義する。

<script>
export default {
    filters: {
        capitalize: function(value) {
            if (!value) return ''
            value = value.toString()
            return value.charAt(0).toUpperCase() + value.slice(1)
        }
    }
}
</script>

定義したcapitalizeフィルターは次のように使うことができる。

<span>{{ msg | capitalize }}</span>

または、以下のようにv-bindでも使うことができる。

<a v-bind:href="url | capitalize">My capitalized link!</a>    

Vue.filter() - グローバルフィルター

グローバルフィルターは通常のフィルターと同じだが、一度定義すると、すべてのVueインスタンスまたはコンポーネントで使用可能。

import Vue from 'vue'

Vue.filter('focus', {
    capitalize: function(value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
    }
})

Vue Router

Vue Routerは、Vueを使用してクライアント側でルーティングシステムを設計するために使われる。

Vue Routerを使い始める

ルーターの使用を開始するには、ルーターをインストールする必要がある。
手順を紹介する。

Vue Routerのインストール

npmを使用してインストールする(推奨)。

npm i vue-router --save

次のファイルを含めることでもインストールできる。

<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>

インストール後、VueにVueRouterを使用するよう指示する。
次の通り。

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

ルートにバインドされているコンポーネントは、ページのどこかにレンダリングする必要があります。これは次のようにrouter-viewタグを使って宣言される。

<div id="app">
    <div>
        Hello World!
    </div>
    <router-view></router-view>
</div>

ルートの定義

各ルートには、独自の固有のコンポーネントがバインドされる。以下のようにルートを定義できる。

const routes = [
  { path: '/foo', component: require('./path/to/foo/component') },
  { path: '/bar', component: require('./path/to/bar/component') }
]

const router = new VueRouter({
  routes  // `routes: routes`の省略記法
})

ユーザーのブラウザの履歴へルートを残したい場合は、以下のように履歴モードをアクティブにする必要がある。

const router = new VueRouter({
  mode: 'history',
  routes  // `routes: routes`の省略記法
})

次に、ルートをVueインスタンスにアタッチする。

const app = new Vue({
  router
}).$mount('#app')

履歴モードを使用している場合、すべてのリクエストをindex.htmlファイルにリダイレクトしてVueが残りの処理を行えるようにするには、Webサーバーの設定をする必要がある。そうしないと、ページ上でブラウザを更新することにより、そのページが実際にはサーバー上に存在しないため、404が返されてしまうことに注意。

` - ルーターリンク

ルーターリンクは、ページを更新せず、必要なコンポーネントのみを取得するため(そして履歴モードで履歴にURLをプッシュするため)特別なもので、新しいページに切り替わったように見える。

ハイパーリンク(aタグ)の代わりに、router-linkを次のように使用する必要がある。

<router-link to="/foo">Go to foo</router-link>

高度なルーティング

Vue Routerはには単純なルーティング機能以外に、ルーティングを処理する優れた方法を他にも用意している。

動的ルーティング

動的ルートは、取得するいくつかのパラメータを持つ一連のルートを照合するために使用される。

動的ルートは、通常のルートと同様に定義されるが、動的セグメントの先頭にコロンが付いている。

const routes = [
  { path: '/user/:username', component: require('./path/to/user/component') },
]

これで、/user/adnanbabakan、/user/dev/または/user/vueなどのルートは全て有効になる。

以下のようにルート内のusername対応するコンポーネントには次のようにしてアクセスすることができる。

<div>This is {{ $route.params.username }}'s profile!</a>
動的ルートのパラメーター変更への対応

上記の例を考えてみよう。ユーザが/user/adnanbabakanから/user/devに移動する場合、同じコンポーネントをレンダリングしようとするので、Vueは以前のインスタンスを破棄しない。$routeオブジェクトで見ることのできるどんなparamsの変化にも対応するためにライフサイクルフックが呼び出されることはない。

<script>
  export default {
    watch: {
      $route(to, from) {

      }
    }
  }
</script>

404ルーティング

すべてのWebサイトには、優れた404ルートが必要だ。Vue Routerでは、最後にアスタリスクルート*を定義して、定義されていないすべてのものをキャッチすることができる。

アスタリスクルートは、他のルートの最後とその後に定義する必要がある。そうしないと、このアスタリスクルートは他のすべてと一致し、ルーティングシステムを破壊してしまうことに注意だ。

そのためには、以下のコードをご覧ください。

const routes = [
     // 他のルートを書いた後に404となるルートを書く
     {
       path: '*',
       component: require('./path/to/404/component'
     }
]

アスタリスクルート

アスタリスクを使用すると、別の種類の動的ルートを照合可能だ。動的ルートは2つのスラッシュ間の動的セグメントだけを照合可能だが、アスタリスクはそれとは別のことができる。

次のルートを見てみよう。

const routes = [
    {
      path: '/user-*',
      component: require('./path/to/user/component')
    }
]

このように書くことで、/user-adnan、および/user-devのようなルートでページはレンダリング可能だ。

$route.paramsを使うことによってと呼ばれるプロパティがpathMatchと呼ばれるプロパティをもつことができる。

このプロパティはアスタリスクと一致した部分が含んでいる。

たとえば/user-adnanページの$route.params.pathMatchははが返されadnanを返す。

名前付きルート

名前付きルートは、短い名前で長いルートパターンにアクセスするために使用される。

次のようなパターンがあるとする。

const routes = [
  {
    path: '/user/profile/setting/',
    component: require('./path/to/user/component')
  }
]

次のように名前を定義できる。

const routes = [
  {
    path: '/user/profile/setting/',
    component: require('./path/to/user/component'),
    name: 'settings'
  }
]

これで、リンクは次のように書ける。

<router-link :to='{name: "settings"}'>
    Profile settings
</router-link>

代わりにこう書くこともできる。

<router-link to="/user/profile/setting/">
    Profile settings
</router-link>

オブジェクトをto属性に渡すときは、バインドする必要があることに注意だ。

パラメータ付きの名前付きルート

先述の例のように、一部のルートにはパラメータがある場合があるがそれらの名前も定義できる。

const routes = [
  {
    // usernameというパラメータを持っているが名前を付けることが可能
    path: '/posts/from/:username',
    component: require('./path/to/posts/component'),
    name: 'posts'
  }
]

そして、これのリンクを作成する時は、次のように書くことができる。

<router-link :to='{ name: "posts", params: {username: "adnanbabakan"} }'>Profile settings</router-link>

プログラムによるナビゲーション

先に書いたように、<router-link></router-link>を使用してルーターリンクを作成することができるが、プログラムでユーザーをリダイレクトする必要がある場合はどうだろうか。さて、このコードでそれを行うことができる

router.push('/foo');

このコードはユーザーを/fooにリダイレクトする。これは特に、ログイン後のリダイレクトなどの状況で使用される。

プログラムによる名前付きナビゲーション

You can also use router.push() for named routes like this:

このような名前付きルートにもrouter.push()を使うことができる。

router.push({name: 'myRoute'});

パラメータのある場合のプログラムによるナビゲーション

同様にrouter.push()は次のようにparamsと一緒にリダイレクトするために使用できる。

router.push({name: 'myRoute', params: {paramOne: 'Hello', paramTwo: 'Salam'}})

ルートリダイレクト

ルートは、次のように相互にリダイレクトされるように定義できる。

const routes = [
  {
    path: '/foo',
    redirect: '/bar'
  }
]

名前付きリダイレクトのルート

次のように、ルートを名前付きルートにリダイレクトすることもできる。

const routes = [
  {
    path: '/foo',
    redirect: { name: 'myRoute' }
  }
]

動的ルートリダイレクト

A route can be redirected using a function to evaluate the destination, like this:

次のように、宛先を評価する関数を使用してルートをリダイレクトできます。

const routes = [
  {
    path: '/foo',
    redirect: to => {
      // the function receives the target route as the argument
      // return redirect path/location here.
    }
  }
];

パラメータを使用したルートリダイレクト

ターゲットルートにパラメータがある場合は、リダイレクト先にも渡すことができる。

const routes = [
  {
    path: '/profile/:username',
    redirect: '/user/:username'
  }
]

ルートエイリアス

ルートエイリアスとは、1つのルートがアクセスされるための複数のアドレスをもつことができるとい機能だ。

次が例。

const routes = [
  {
    path: '/foo',
    alias: '/bar'
  }
];

これで/fooというルートは/bar/でもアクセスすることができる。

1つのルートは複数のエイリアスを持つこともできる。

const routes = [
  {
    path: '/foo',
    // 配列に複数のエイリアスを指定できる
    alias: ['/bar', '/baz']
  }
];

  1. 日本語版はこちらを参照 ↩

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

Javascript: めっちゃ簡単なオープン・クローズドの原則(初級者向け)

最近、勉強会をする機会があったり、部下を育てるような話が出てきたのでSOLID原則についてまとめようと思いました。
これ、Javascriptばかり書いている人に伝えようと思っても、そもそもクラスつかってなかったり、インターフェースなんてないし、なんて人も多いので結構説明に困るんですよね。。。

なので、Javascriptでの簡単な事例を通してSOLID原則を語るシリーズを書くことにしました。

今回は SOLID原則の 「O」 オープン・クローズドの原則(Open/closed principle)です。


こんなオブジェクトでデータがまとめられています。

data = [
  { type: "header", data: "必要なデータ" },
  { type: "modal", data: "必要なデータ" },
  { type: "slider", data: "必要なデータ" },
]

データには type が設定されていてそれぞれに応じた処理を実行する必要があります。

const createDOM = (data) {
  data.forEach(item => {
    item.type === 'header' && createHeader(item)
    item.type === 'modal' && createModal(item)
    item.type === 'slider' && createSlider(item)
  })
}

あるときデータの type に footer を追加しました。

data = [
  { type: "header", data: "必要なデータ" },
  { type: "modal", data: "必要なデータ" },
  { type: "slider", data: "必要なデータ" },
  { type: "footer", data: "必要なデータ" },
]

そして気がつきます。

「createDOM直さないといけないじゃん。めんどくさ...」

これはデータの type を拡張しようとすると createDOM をいちいち直さなくてはいけないというところに問題がありそうです。言い換えると

dataの変更に対してcreateDOMが開いている

となります。ちょっととっつきにくい表現ですが我慢しましょう。この問題が起きている理由は

createDOMがdataに設定されているtypeを知っていないといけない

と言うことです。なので data が変更されたら createDOM も追従しないといけないんですね。

めんどくさいので、いちいち追従しなくてもうまいこと動くようにするにはどうすればいいかと言うと、この場合は次の3つのことをしなくてはいけません。

  1. データ側にメソッドを持たせる
  2. メソッドは create の名前で統一する
  3. createDOM側では type の判定はせず、create を実行するだけにする
data = [
  { type: "header", data: "必要なデータ", create: () => { /* headerつくる処理 */ } },
  { type: "modal", data: "必要なデータ", create: () => { /* modalつくる処理 */ } },
  { type: "slider", data: "必要なデータ", create: () => { /* sliderつくる処理 */ } },
  { type: "footer", data: "必要なデータ", create: () => { /* footerつくる処理 */ } },
]
const createDOM = (data) {
  data.forEach(item => {
    item.create()
  })
}

こんな感じでしょう。こうすることで data の type が拡張されても createDOM を変更する必要がなくなりますね。data に新しいタイプを追加するにしても、そっちに処理を追加してあげれば問題ありません。これを

dataは拡張に対して開いている

createDOMは変更に対して閉じている

と表現します。やはりとっつきにくいですね。でも我慢しましょう。

追記
JSONデータの場合どうすんの?

[
  { "type": "header", "data": "必要なデータ" },
  { "type": "modal", "data": "必要なデータ" },
  { "type": "slider", "data": "必要なデータ" },
  { "type": "footer", "data": "必要なデータ" },
]

確かにJSONには関数を設定できないので、取得した後でメソッドを埋め込む必要があります。

  const data = await getJSON('./data.json')
  data.forEach(item => {
    if (item.type === 'header') item.create = () => { /* headerつくる処理 */ }
    if (item.type === 'modal') item.create = () => { /* modalつくる処理 */ }
    if (item.type === 'slider') item.create = () => { /* sliderつくる処理 */ }
    // item.typeには 'footer' もある
  })
}

問題は全ての type に create を実装しないとロジック部分でエラーになってしまうことです。

  const createDOM = (data) {
    data.forEach(item => {
      item.create() // データにcreateが実装されてないとエラー
    })
  }

なので、create が実装されているデータだけ処理を実行するようにしてみましょう

  const createDOM = (data) {
    data.filter(item => !!item.create).forEach(item => {
      item.create()
    })
  }

こうすることで、create を実装していないデータは無視されるようになります。言い換えるとデータ側では create を実装する処理を追記するだけで新しい type のデータが来ても対応できるわけですね。

整理が追いつかない人は「データを作る部分」と「ロジックを実行する部分」を分けて考えてみましょう。ロジック部分のコードを修正する場合、今まで正しく動いていた処理にバグを生み出す可能性が高いです。
なので極力ロジック部分は変更せずに済むような作り方をしたほうがいいよね、という考え方なんですね。

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

Slackを喋らせてリモートワークを賑やかにしよう:smiley:

皆さん、リモートワーク捗っていますか?この記事では、現在アクティブなSlackチャンネルの内容を発話させて物理オフィスの賑やかなイメージを取り戻すことができます。

使う技術の紹介

Web Speech API

ブラウザ上で文字から音声を再生することができるAPIです。意外と多くのブラウザがサポートしていてびっくり。

Mutation Observer

DOM が変わったときに通知を受ける事ができる機能です。Slackのアクティブなチャンネルに新たな投稿があったのをフックするために使います。

ちょっと手こずったのは、子孫の要素の変更を受け取るためには子の要素の変更を受け取らないといけないこと。つまり、subtree:true だけではダメで、childList:trueも指定する必要があります。

発話させよう

この2つが揃ったあとは

  • DOMの変更を受け取って
  • テキストを発話させる

と良いです。というわけで、サクッと下記を Chrome の開発者ツールのコンソールに突っ込むと喋りだします。

const elem = document.querySelectorAll(".c-virtual_list__scroll_container")[1];
const observer = new MutationObserver(records => {
  const record = records[records.length - 1];
  if (record.nextSibling) return;
  record.addedNodes.forEach(node => {
    if (node.ariaExpanded !== "false") return;
    const matchResult = /:\d\d(.+)/gs.exec(node.textContent);
    if (matchResult) {
      const text = matchResult[1];
      speechSynthesis.speak(new SpeechSynthesisUtterance(text));
    }
  });
});
const config = {subtree: true, childList: true, characterData: true};
observer.observe(elem, config);
//observer.disconnect(); // 発話を終了する

普段からコピペするのも大変なので、ブックマークレットにすると便利でしょう。下記ブックマークレットでは、再度実行すると「現在の発話を中断し」「Observerを切断し」ます。何かおかしくなったら再度実行してください。

https://gist.github.com/kijuky/053c7988684f55bf7f0e7c3ddb50cf9b

やってみた感想

  • Botがいるチャンネルとかだと微妙かも。
  • チャンネルを変更すると、そのチャンネルの最後の発言が発話される。
  • [追記] 過去の発言を表示するとそれを延々と喋り続けてしまう。 -> 修正した
  • 3日くらいは楽しめそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

青空一行文庫ブックマークレット

青空文庫の作品を「一行にする」ブックマークレットです。岩下 智氏の作られた一行文庫に触発されて作りました。人や環境によっては複数行より読みやすくなると思います。
20200627.png

javascript: (() => { let text = ''; document.querySelectorAll('body > h1, body > h2, body > h3, .metadata, .main_text').forEach(node => { text += node.innerHTML.replace(/<(\/?ruby|\/?rb|\/?rp|\/?rt)>/g, '___$1___').replace(/<[^>]+>/g, '').replace(/___([^_]+)___/g, '<$1>') + ' '; }); text = text.trim().replace(/(\r\n|\r)+/g, '\n').replace(/\n{2,}/g, '\n').replace(/\n/g, ' ').replace(/ {2,}/g, ' '); document.querySelectorAll('body > *').forEach(node => { node.style.display = 'none'; }); document.body.innerHTML += `<div style="letter-spacing: 0.25em; white-space: nowrap; writing-mode: vertical-rl;">${text}</div>`; document.body.style.cssText = 'background-color: #001020; color: #f0f0f0; display: flex; justify-content: center; font-family: "游明朝","Yu Mincho",YuMincho,"Hiragino Mincho Pro",serif; font-feature-settings: "pkna"; font-size: calc(2.5vh + 10px); -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; margin: 2em;'; })(); void 0;

ご存知の通り、青空文庫の HTML フォーマットは完全に統一されているわけではないため、作品によってはレイアウトが崩壊したり、外字が非表示になったりする可能性があります(外字についてはルビが振られているはずなので、可読性は損なわれないかと)。
また、スマートフォンでの使用を想定したフォントサイズにしているため、環境によってはフォントサイズの調整が必要になる可能性もあります。 PC では font-size: calc(2.5vh + 10px); の 2.5vh を 2vh くらいにした方が良いかもしれません。

【その他の青空文庫用ブックマークレット】
* 青空文庫の闇堕ちブックマークレット
* 青空文庫スピーカー

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

Javascript: めっちゃ簡単な依存性逆転の原則(初級者向け)

最近、勉強会をする機会があったり、部下を育てるような話が出てきたのでSOLID原則についてまとめようと思いました。
これ、Javascriptばかり書いている人に伝えようと思っても、そもそもクラスつかってなかったり、インターフェースなんてないし、なんて人も多いので結構説明に困るんですよね。。。

なので、javascriptでの簡単な事例を通してSOLID原則を語るシリーズを書くことにしました。

今回は SOLID原則の 「D」 依存性逆転の原則(dependency inversion principle)です。


なんかDOMつくるメソッドがありました。

createHoge() {
  // データ取得する
    // ... いろんな処理

  // DOM作る
    // ... いろんな処理

  // DOMをDocumentに加える
    // ... いろんな処理
}

なんか長いので分割しました。
分割した処理は再利用できるようにオブジェクトにまとめました。

createHoge() {
  const data = hogeStore.getData()  
  const DOM = hogeDOMCreator.hoge(data)  
  hogeDOMCreator.render(data)
}

hogeStore = { // データの扱いはここにまとめる
  getData() {
    // ... いろんな処理
  },
  ...
}

hogeDOMCreator = { // DOM生成処理はここにまとめる
  hoge() {
    // ... いろんな処理
  },
  render() {
    // ... いろんな処理
  }
  ...
}

レビュアーがいいます。

「hogeって名詞じゃん。メソッドなんだから動詞にしてよ」

しぶしぶ直そうとして気がつきます。

「これhogeDOMCreator直したら、使ってる場所も直さないといけないじゃん。めんどくさ...」

まだhogeDOMCreatorを使っている箇所が1つだったから良かったですが、すでに多くの実装で使いまわされていたらと思うとゾッとしますね。

この問題を一言で言うと

createHoge が hogeDOMCreator に依存している

つまり、hogeDOMCreatorに互換性のない変更が起きたら、使ってる側も直さないといけないという問題です。こんなめんどくさい自体にならないようにするにはどうすればいいでしょう?無理そうですね。名前が変わってしまうので当然です。

じゃあどうすればいいかと言うと、

事前にルールを作ります
 ↓

「xxxDOMCreator系はcreate()というメソッドでDOMを作るようにする」

このルールに則って作れば、使う側も使われる側も名前がずれる心配がないですよね。これ、言い換えると次のようになります。

createHoge と hogeDOMCreator は 上記のルールに依存している

このルールを守って作っている限り、名前がおかしいなんて指摘自体がなくなります。

「ただの規約なのでは?」と思うかもしれません。実際Javascriptではこれをプログラムのレベルで強制させるのは困難です。

実はJavascriptでは無理でもその他多くのプログラム言語ではインターフェースや抽象クラスというものがあり、上記のルールを強制させることができます。

なのでこういう考え方があるんですね。

ちなみに、最近のAltJSのデファクトスタンダードである TypeScript を使えばインターフェースや抽象クラスを使えます。興味ある人は調べてみましょう。

※おもしろい試みをしている記事があったのでリンク貼っておきます。
JavaScriptでinterfaceを使ったポリモーフィズムを表現する

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

nuxt.js vue-form-wizardフォームにsimple-vue-validatorバリデーションを実装してみました。

はじめに

以前作ったvue-form-wizard ステップフォームにsimple-vue-validatorを追加してみました。
以前作成したステップフォームはこちらです。

普通inputに <input type='text' required= 'required'> だけでもフロントのバリデーションは走らせることができますがやりたい実装としては下記のイメージのようにerrorMsgをinputの下に表示させようとしています。
image.png
Simple Vue Validator一番カンタンだったのでSimple Vue Validatorを使って実装します。

目次

  1. NPMパッケージをインストール
  2. プラグイン登録
  3. Simple Vue Validator呼び出し
  4. バリデーションチェック項目登録
  5. errorMsg追加
  6. html上に記載
  7. 終わり

1. NPMパッケージをインストール

$ npm i --save simple-vue-validator
$ npm i --D simple-vue-validator
$ npm i simple-vue-validator

どちらでも大丈夫です

2. プラグイン登録

最初componet直に呼び出しても使えなかったので調べてみましたがなかなかNuxtでの使い方の参考があまりなくてで調べるのに黒しました。結論プラグインに登録しないと行けませんでした。

登録をするためにはファイルを作成が必要です。plugins/Vuelidate.jsを作成します。

plugins/Vuelidate.js
import Vue from 'vue'
import SimpleVueValidation from 'simple-vue-validator'
Vue.use(SimpleVueValidation)
nuxt.config.js
plugins: [
    '~/plugins/Vuelidate',
],

pluginsに登録後nuxtconfigに再度呼び出しをするとグローバルでつかえるようになります。

3. Simple Vue Validator呼び出し

呼び出したconponentのscriptタグにsimple-vue-validatorを呼び出しが必要です。

import SimpleVueValidation from 'simple-vue-validator'
const Validator = SimpleVueValidation.Validator

simple-vue-validator を呼び込んでValidatorに割当をします。

4. バリデーションチェック項目登録

<script>
methods: {
   validateStepOne: function(){//stepOneに移動した場合チェックfunction
   //OneStepCheckField
       return this.$validate(
         'stepForm.oneStep.name',//stepOneチェックしたいフォームmodel。
   ),
   validateStepTow: function(){//stepTwoに移動した場合チェックfunction
   //TwoStepCheckField
       return this.$validate(
         'stepForm.stepTwo.mail',//stepTwoチェックしたいフォームmodel。
   )
}
</script>

チェックしたいフォームを定義していきます。
oneSteVvalidationとtwoSteVvalidationのfunctionを分けた理由はvue-form-wizardは一枚にindexをタブ形式で画面を表示したり非表示にする仕組みなのでバリデーションを分けないとステップ全部バリデーションが走ってしまうため1ステップから2ステップに移動した場合既にエラーメッセージが出てしいる状態になってしまいます。他のやり方があるかもしれないですが自分はこのやり方でやってみました。

もしフォームが追加した場合は[配列]で囲んでチェックしたいmodelを追加することもできます。

//追加したい場合の例
validateStepOne: function(){
   //OneStepCheckFields
       return this.$validate([
         'stepForm.oneStep.name',
         'stepForm.oneStep.tel'
   ]),

5. errorMsg追加

validators: {
  //oneStepErrorMsgs
    'stepForm.oneStep.name': function(value)  {
        return Validator.value(value).required('未選択です')//ここにエラーメッセージ
    },
    //twoStepErrorMsgs
    'stepForm.stepTwo.mail': function(value)  {
        return Validator.value(value).required('未選択です').email('メールアドレスが正しくありません')//ここにエラーメッセージ
    },
}

errorメッセージのルールもカンタンに追加することができます。

//例
email('メールアドレスが正しくありません')
digit('数字以外は入力できません')

バリデーションルールを詳しは公式を参考してください。

6. html上に記載

上記に validateStepOneのfunction()を form-wizard の tab-content追加をします。
エラ〜メッセージのボックスも必要です。

:before-change="oneSteVvalidation" //funtion
.message {{ validation.firstError('modelName') }}
index.vue
<template lang="pug">
   .container
      h2 {{ pageName }}
        form-wizard
          tab-content(
            title:'ONESTEP',
            :before-change="oneSteVvalidation" //<-ここfunction
          )
            .inputBox
              input(
                type='text',
                v-model='stepForm.oneStep.name'
              )
            .message {{ validation.firstError('stepForm.oneStep.name') }}//<-エラーメッセージ
          tab-content(
            title:'TWOSTEP',
            :before-change="oneSteVvalidation" //<-ここfunction

          )
            .inputBox
              input(
                type='email', 
                v-model='stepForm.stepTwo.mail'
              )
            .message {{ validation.firstError('stepForm.stepTwo.name') }}//<-エラーメッセージ
          tab-content(
            title:'確認'
          )
            .confirm
              name : {{ stepForm.oneStep.name }}
              email : {{ stepForm.oneStep.name }}
          button( 
              slot="prev"
              ) 前のステップへ
          button( 
              slot="next"
              ) 次のステップへ
          button(
              type="button" 
              slot="finish") 送信
</template>

<script>
import SimpleVueValidation from 'simple-vue-validator'//SimpleVueValidation呼び出し
const Validator = SimpleVueValidation.Validator//割当
import {FormWizard, TabContent} from 'vue-form-wizard'
export default {
  components: {
        FormWizard,
        TabContent,
  }
  data(){
    return{
       pageName: 'contact',
       stepForm : {
          stepOne : {
            name : ''
          },
          stepTwo : {
            mail : ''
          },
        }
      },
      methods: {
         validateStepOne: function(){//stepOneに移動した場合チェックfunction
         //OneStepCheckField
            return this.$validate(
              'stepForm.oneStep.name',//stepOneチェックしたいフォームmodel。
         ),
         validateStepTow: function(){//stepTwoに移動した場合チェックfunction
         //TwoStepCheckField
         return this.$validate(
            'stepForm.stepTwo.mail',//stepTwoチェックしたいフォームmodel。
         )
       },
       validators: {
      //oneStepErrorMsgs
          'stepForm.oneStep.name': function(value)  {
              return Validator.value(value).required('未選択です')//ここにエラーメッセージ
       },
       //twoStepErrorMsgs
          'stepForm.stepTwo.mail': function(value)  {
             return Validator.value(value).requhired('未選択です').email('メールアドレスが正しくありません')//ここにエラーメッセージ
       }
     },
  }
</script> 

7. 終わり

vue-form-wizardにsimple-vue-validator使ってバリデーションを走らせて完成度が少し上がっていきました。

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

nuxt.js vue-form-wizardステップフォームにsimple-vue-validatorバリデーションを実装してみました。

はじめに

以前作ったvue-form-wizard ステップフォームにsimple-vue-validatorを追加してみました。
以前作成したステップフォームはこちらです。

普通inputに <input type='text' required= 'required'> だけでもフロントのバリデーションは走らせることができますがやりたい実装としては下記のイメージのようにerrorMsgをinputの下に表示させようとしています。
image.png
Simple Vue Validator一番カンタンだったのでSimple Vue Validatorを使って実装します。

目次

  1. NPMパッケージをインストール
  2. プラグイン登録
  3. Simple Vue Validator呼び出し
  4. バリデーションチェック項目登録
  5. errorMsg追加
  6. html上に記載
  7. 終わり

1. NPMパッケージをインストール

$ npm i --save simple-vue-validator
$ npm i --D simple-vue-validator
$ npm i simple-vue-validator

どちらでも大丈夫です

2. プラグイン登録

最初componet直に呼び出しても使えなかったので調べてみましたがなかなかNuxtでの使い方の参考があまりなく調べるのに黒しました。結論プラグインに登録しないと行けませんでした。

登録をするためにはファイルを作成が必要です。 plugins/Vuelidate.jsを作成します。

plugins/Vuelidate.js
import Vue from 'vue'
import SimpleVueValidation from 'simple-vue-validator'
Vue.use(SimpleVueValidation)
nuxt.config.js
plugins: [
    '~/plugins/Vuelidate',
],

pluginsにファイル作成したらnuxt.config.jsに登録をするとグローバルでつかえるようになります。

3. Simple Vue Validator呼び出し

使用したいcomponentのscriptタグにsimple-vue-validatorを呼び出しが必要です。

import SimpleVueValidation from 'simple-vue-validator'
const Validator = SimpleVueValidation.Validator

simple-vue-validator を呼び込んでValidatorに割当をします。

4. バリデーションチェック項目登録

<script>
methods: {
   validateStepOne: function(){//stepOneに移動した場合チェックfunction
   //OneStepCheckField
       return this.$validate(
         'stepForm.oneStep.name',//stepOneチェックしたいフォームmodel。
   ),
   validateStepTow: function(){//stepTwoに移動した場合チェックfunction
   //TwoStepCheckField
       return this.$validate(
         'stepForm.stepTwo.mail',//stepTwoチェックしたいフォームmodel。
   )
}
</script>

チェックしたいフォームを定義していきます。
oneStepValidationとtwoStepValidationのfunctionを分けた理由はvue-form-wizardで作ったフォームが一枚にindexをタブ形式で入力画面を表示したり非表示にする仕組みなのでバリデーションを分けないとステップ全部バリデーションが走ってしまうため1ステップから2ステップに移動した場合既にエラーメッセージが既に出てしいる状態になってしまいます。他のやり方があるかもしれないですが自分は分けてfunctionを走らせるようにしました。

もしフォームが追加した場合は[配列]で囲んでチェックしたいmodelを追加することもできます。

//追加したい場合の例
validateStepOne: function(){
   //OneStepCheckFields
       return this.$validate([
         'stepForm.oneStep.name',
         'stepForm.oneStep.tel'
   ]),

5. errorMsg追加

validators: {
  //oneStepErrorMsgs
    'stepForm.oneStep.name': function(value)  {
        return Validator.value(value).required('未選択です')//ここにエラーメッセージ
    },
    //twoStepErrorMsgs
    'stepForm.stepTwo.mail': function(value)  {
        return Validator.value(value).required('未選択です').email('メールアドレスが正しくありません')//ここにエラーメッセージ
    },
}

errorメッセージのルールもカンタンに追加することができます。

//例
email('メールアドレスが正しくありません')
digit('数字以外は入力できません')

バリデーションルールを詳しは公式を参考してください。

6. html上に記載

上記に validateStepOneのfunction()を form-wizard の tab-content追加をします。
エラ〜メッセージのボックスも必要です。

:before-change="oneSteVvalidation" //funtion
.message {{ validation.firstError('modelName') }}
index.vue
<template lang="pug">
   .container
      h2 {{ pageName }}
        form-wizard
          tab-content(
            title:'ONESTEP',
            :before-change="oneSteVvalidation" //<-ここfunction
          )
            .inputBox
              input(
                type='text',
                v-model='stepForm.oneStep.name'
              )
            .message {{ validation.firstError('stepForm.oneStep.name') }}//<-エラーメッセージ
          tab-content(
            title:'TWOSTEP',
            :before-change="oneSteVvalidation" //<-ここfunction

          )
            .inputBox
              input(
                type='email', 
                v-model='stepForm.stepTwo.mail'
              )
            .message {{ validation.firstError('stepForm.stepTwo.name') }}//<-エラーメッセージ
          tab-content(
            title:'確認'
          )
            .confirm
              name : {{ stepForm.oneStep.name }}
              email : {{ stepForm.oneStep.name }}
          button( 
              slot="prev"
              ) 前のステップへ
          button( 
              slot="next"
              ) 次のステップへ
          button(
              type="button" 
              slot="finish") 送信
</template>

<script>
import SimpleVueValidation from 'simple-vue-validator'//SimpleVueValidation呼び出し
const Validator = SimpleVueValidation.Validator//割当
import {FormWizard, TabContent} from 'vue-form-wizard'
export default {
  components: {
        FormWizard,
        TabContent,
  }
  data(){
    return{
       pageName: 'contact',
       stepForm : {
          stepOne : {
            name : ''
          },
          stepTwo : {
            mail : ''
          },
        }
      },
      methods: {
         validateStepOne: function(){//stepOneに移動した場合チェックfunction
         //OneStepCheckField
            return this.$validate(
              'stepForm.oneStep.name',//stepOneチェックしたいフォームmodel。
         ),
         validateStepTow: function(){//stepTwoに移動した場合チェックfunction
         //TwoStepCheckField
         return this.$validate(
            'stepForm.stepTwo.mail',//stepTwoチェックしたいフォームmodel。
         )
       },
       validators: {
      //oneStepErrorMsgs
          'stepForm.oneStep.name': function(value)  {
              return Validator.value(value).required('未選択です')//ここにエラーメッセージ
       },
       //twoStepErrorMsgs
          'stepForm.stepTwo.mail': function(value)  {
             return Validator.value(value).requhired('未選択です').email('メールアドレスが正しくありません')//ここにエラーメッセージ
       }
     },
  }
</script> 

7. 終わり

vue-form-wizardにsimple-vue-validator使ってバリデーションを走らせて完成度が少し上がっていきました。

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

DBから取り出したデータを元に、星の数で評価する機能を実装する

はじめに

AmazonなどのECサイトで、商品に対して星をつけて商品の評価をしているのを、
見たことありませんか?

「この商品は5つ星!これは3つ星!」のような。
アラビア数字で「これは何点」と表記されているよりも、すごくすごくわかりやすいですよね。

image.png

背景

筆者はWebフレームワークの勉強の課題として、
「読んだ技術書を評価してリストアップする」Webアプリケーションを製作中なのですが、
データベースに登録した評価カラムの数字を取り出して、その内容を星の個数として反映することを目指しました。

image.png

注意

  • 問題解決からしばらく経ってからの投稿なので、データベースの中身などに矛盾が発生しています。
    (例えば、対応する技術書の評価の数がスクリーンショットごとに異なるとか。)

  • 上の例のような、星3.5個というような小数は想定していません。
     あくまで星1, 2, 3, 4, 5個としての実装です。

開発環境

  • Node.js 12.16.3
  • Express 4.17.1
  • MySQL 15.1

実装したいもの

  • 星の数は常に5つ表示する
  • 評価数に応じて星の色をオレンジにすることで、評価点を確認できるようにする
  • 評価数に満たない部分は灰色の星として表示する
  • これらによって、「評価点の最大値がいくらか」「この本の評価点がいくつか」の可読性を一気に向上させる

解決方法

まずヴァニラなHTML・CSSを用意する

まず、星を表示するソースコードを用意する必要があります。

こちらを参考にしました。

改変したところ:uncheckedというクラスも用意した(文字色をgreyに変更する)

ソースコード

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<span class="fa fa-star checked"></span>
<span class="fa fa-star checked"></span>
<span class="fa fa-star checked"></span>
<span class="fa fa-star"></span>
<span class="fa fa-star"></span>


.checked {
    color: orange;
}
.unchecked {
    color: grey;
}

表示結果

image.png

実装してみる

データベースの中身

このテーブルの"good"というカラムが評価(星の数)に対応します。

image.png

実装前のソースコードとスクリーンショット

app.js
app.get("/", (req, res) => {
  const sql = "select * from book";
  connection.query(sql, function (err, result, fields) {
    if (err) throw err;
    res.render("index", { book: result });
  });
});

inex.ejs
      <table>
        <tr>
          <th>タイトル</th>
          <th>著者</th>
          <th>出版社</th>
          <th>評価</th>
          <th>更新</th>
          <th>削除</th>
        </tr>
        <% book.forEach(function (value) { %>
        <tr>
          <td class="title"><%= value.title %></td>
          <td><%= value.author %></td>
          <td><%= value.publisher %></td>
          <td><%= value.good %></td>
          <td><a href="/edit/<%= value.title %>">更新</a></td>
          <td>
            <a
              href="/delete/<%= value.title %>"
              onClick="disp('<%= value.title %>'); return false;"
              >削除</a
            >
          </td>
        </tr>
        <% }); %>
      </table>

image.png

実装後のソースコードとスクリーンショット(そして補足コメント)

app.js
const maxStar = 5;

app.get("/", (req, res) => {
  const sql = "select * from book";
  connection.query(sql, function (err, result, fields) {
    if (err) throw err;
    books = [];
    for (book of result) {
      book.colored = book.good;  // 評価の数(オレンジ色の星の数)
      book.uncolored = maxStar - book.good;  // 灰色の星の数
      books.push(book);
    }
    res.render("index", { book: books }); 
  });
});
index.ejs
      <table>
        <tr>
          <th>タイトル</th>
          <th>著者</th>
          <th>出版社</th>
          <th>評価</th>
          <th>更新</th>
          <th>削除</th>
        </tr>
        <% book.forEach(function (value) { %>
        <tr>
          <td class="title"><%= value.title %></td>
          <td><%= value.author %></td>
          <td><%= value.publisher %></td>
          <td>
            <% for (let i = 0; i < value.colored; i++){ %>
            <span class="fa fa-star checked"></span>
            <% } %> <% for (let i = 0; i < value.uncolored; i++){ %>
            <span class="fa fa-star unchecked"></span>
            <% } %>
          </td>
          <td><a href="/edit/<%= value.title %>">更新</a></td>
          <td>
            <a
              href="/delete/<%= value.title %>"
              onClick="disp('<%= value.title %>'); return false;"
              >削除</a
            >
          </td>
        </tr>
        <% }); %>
      </table>

image.png

app.jsについて

実装前では「評価の数」だけあればよかったのですが、
今回は「オレンジ色に染まった星の数」「色がついていない(ように見える灰色の)星の数」という2つの変数が必要になります。
データベースからレコードを取り出したあと、
評価数の最大値(5)から、goodカラムの数を引くことで、灰色の星の数を取得し、
それらをまとめて初期化した配列 books に挿入、index.ejsへレンダリングすることで解決を図っています。

index.ejsについて

ejsファイルは通常HTMLで用いるJavaScriptだけでなく、
app.jsから渡されたデータを利用したJavaScriptを埋め込むことができます。

星の数について使った部分は以下です。
colored変数に格納された数だけforループしオレンジ色の星を表示、
uncolored変数に格納された数だけforループし、灰色の星を表示させます。

            <% for (let i = 0; i < value.colored; i++){ %>
            <span class="fa fa-star checked"></span>
            <% } %> <% for (let i = 0; i < value.uncolored; i++){ %>
            <span class="fa fa-star unchecked"></span>
            <% } %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IndexedDB + Vue CLI, markdown対応のtodoを作る

概要

前と同様、IndexedDB + Vue CLIで
Dexie.js ライブラリを使用した構成となります。

・RDB, APIサービス等を使用しない。
ブラウザ側の IndexedDBにデータ保存する。シンプル構成となります。

構成

Chrome 83
Vue CLI
dexie : 3.0.1
vue: 2.6.11
vue-router
marked: 1.1.0

package.json

https://github.com/kuc-arc-f/vue_spa3b_2todo/blob/master/package.json


画面

・詳細
ss-todo-0627p1.png

・リスト
ss-todo-index-0627p2.png


実装など

・create
https://github.com/kuc-arc-f/vue_spa3b_2todo/blob/master/src/components/DexieTodos/new.vue

・index
https://github.com/kuc-arc-f/vue_spa3b_2todo/blob/master/src/components/DexieTodos/Index.vue

・show
https://github.com/kuc-arc-f/vue_spa3b_2todo/blob/master/src/components/DexieTodos/show.vue

・edit
https://github.com/kuc-arc-f/vue_spa3b_2todo/blob/master/src/components/DexieTodos/edit.vue


参考ページ

IndexedDB + Dexie.js で CRUDの作成、Vue CLI版
https://qiita.com/knakaqi/items/765a1fb37a53a26278e9


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

Yahoo!コンテンツジオコーダAPIを使って施設名から緯度経度を取得する

何がしたいか

駅名や施設などのランドマークの名前から緯度と経度を取得したい。

なぜ使うか

ランドマークから緯度や経度を取得できるAPIにはGoogle GeoCoding API があり、無料で利用することができますが、クレジットカードの登録が必要となり簡単に使用することが難しくなりました。
Yahoo の コンテンツジオコーダ API は会員登録が必要なもののクレカの登録は不必要で簡単に利用できます。

使い方

  1. Yahoo! JAPAN ID を取得する。
  2. アプリケーションを登録する。 利用用途に合わせて登録してください。クライアント ID(アプリケーション ID)が発行されます。

主な入力パラメータ

パラメータ 値 説明
appid(必須) string 先程取得したアプリケーション ID
query(必須) string 検索する文字列
category string(address, landmark, world) ランドマークを検索するときは'landmark'に設定
output string(xml, json) 出力形式

その他のパラメータは公式サイトを参照

curl を利用する場合

curl -v -G -d appid=<YOUR_APP_ID> -d query=東京タワー -d category=landmark https://map.yahooapis.jp/geocode/cont/V1/contentsGeoCoder

axios を利用する場合

axios
  .get("https://map.yahooapis.jp/geocode/cont/V1/contentsGeoCoder", {
    params: {
      appid: YOUR_APP_ID,
      query: "東京タワー",
      category: "landmark",
      output: "json",
    },
  })
  .then((results) => {
    console.log(results);
  });

この方法で緯度経度を取得することができました。
情報の過不足があればコメントお願いします。

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

コンセプト | webpack

https://webpack.js.org/concepts/ の日本語訳。

コンセプト

webpack は、最新の JavaScript アプリケーションの為の静的モジュールバンドラーです。
webpack がアプリケーションを処理するとき、プロジェクトが必要とするすべてのモジュールを整理して、 1 つ以上のバンドルを生成する 依存グラフ を内部的に構築します。

JavaScript モジュールと Webpack モジュールの詳細については、 こちら をご覧ください。

バージョン4.0.0以降、 webpack はプロジェクトをバンドルするための構成ファイルを必要としません。
しかし、開発者のニーズに合わせて設定することができ、 驚くべきカスタマイズ性 を有しています。
よりよく合うように柔軟に設定することができます。
webpack の利用を開始するには、中核となるコンセプトを理解するだけで十分です。

このドキュメントは、個々のコンセプトの使用例のリンクを提供すると共に、コンセプトの概要を説明することを目的としています。
モジュールバンドラーの背後にある思想と、内部でどのように機能するかについての理解を深めるには、次のリソースを参照してください。

エントリー

エントリポイントは、内部 依存関係グラフ の構築を開始するために Webpack が使用する必要があるモジュールを示します。
webpack は、そのエントリポイントが(直接的および間接的に)依存している他のモジュールとライブラリを特定します。
デフォルト値は ./src/index.js です。
しかし webpack の設定で entry プロパティを指定することで、異なる(または複数のエントリーポイント)を指定できます。例えば:

webpack.config.js
module.exports = {
  entry: './path/to/my/entry/file.js'
};

詳細は エントリポイント のセクションをご覧ください。

出力

output プロパティは、バンドルファイルを出力する場所とファイルの命名規則を webpack に指示します。
デフォルトでは、メインの出力ファイルは ./dist/main.js 、他の出力ファイルは ./dist フォルダーになります。
設定で output フィールドを指定することにより、ファイルの出力先と命名規則を設定することができます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  }
};

上記の例では、 output.filename プロパティと output.path プロパティを使用して、バンドルの名前と出力先を webpack に指示しています。
一番上にインポートされている path モジュールは、ファイルパスの操作に使用される Node.js のコアモジュール です。

output プロパティは、 もっと多くの設定機能 を有しています。
その背後にあるコンセプトについて学びたい場合は、 output セクションをご覧ください。

ローダー

デフォルトでは webpack は JavaScript ファイルと JSON ファイルのみを認識します。
ローダーを使用すると、 webpack は他のタイプのファイルを処理し、それらを有効な モジュール に変換してアプリケーションにおいて使用し、依存関係グラフに追加することができます。

どのタイプのモジュールでもインポートできることに注意してください。
.css ファイルは webpack に固有の機能であり、他のバンドラーやタスクランナーではサポートされていない場合があります。
開発者がより正確な依存関係グラフを作成できるようにするため、 CSS 言語の拡張は正当化されると私たちは考えています。

webpack のローダー設定には大まかに 2 つのプロパティがあります。

test プロパティは変換するファイルを識別します。
use プロパティは変換を行うために使用するローダーを示します。

webpack.config.js
const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};

上記の設定では、 test と use の 2 つの必須プロパティを持つ単一モジュールの rules プロパティを定義しています。
これは webpack のコンパイラに次のように指示します。

「やあ、 webpack コンパイラー。 require()/import ステートメント内の .txt ファイルに解決されるパスに遭遇したとき、バンドルに追加する前に raw ローダーを使用して変換してくれ」

webpack の設定でルールを定義するときは、 rules ではなく module.rules でルールを定義していることを覚えておくことが重要です。
開発者の利益のために、設定が誤っている場合、 webpack は警告を出します。

正規表現を使用してファイルを照合する場合は、引用符で囲まない場合があることに注意してください。
つまり、 /\.txt$/ は '/\.txt$/' または "/\.txt$/" と同じではありません。
前者は .txt で終わるすべてのファイルに一致するように webpack に指示し、後者は絶対パス '.txt' を持つ単一のファイルに一致するように webpack に指示します。
これはおそらくあなたの意図するところではないでしょう。

ローダーについての詳細は ローダー セクションをご覧ください。

プラグイン

ローダーは特定のタイプのモジュールの変換に使用されますが、プラグインを利用して、バンドルの最適化、アセットの管理、環境変数の注入などの幅広いタスクを実行することができます。

プラグインを使用して Webpack の機能を拡張する方法については プラグインインターフェイス をご覧ください。

プラグインを使用するには、プラグインを require() してプラグイン配列に追加する必要があります。
ほとんどのプラグインはオプションを通じてカスタマイズ可能です。プラグインはさまざまな用途で複数回使用できるため、 new 演算子を使用して呼び出し、プラグインのインスタンスを作成する必要があります。

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // npm を介してインストール
const webpack = require('webpack'); // 組込みプラグインを利用する

module.exports = {
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

上記の例では、 html-webpack-plugin は、生成されたすべてのバンドルを自動的に挿入し、アプリケーションの HTML ファイルを生成します。

すぐに使える webpack のプラグインはたくさんあります!
プラグインの一覧 をご覧ください。

Webpack の設定でプラグインを使用するのは簡単です。
しかし、さらに知っておく価値のある多くのユースケースがあります。
詳しくは こちら をご覧ください。

モード

mode パラメーターを development 、 production 、または none のいずれかに設定することにより、各環境に対応する Webpack の組み込み最適化を有効にすることができます。
デフォルト値は production です。

module.exports = {
  mode: 'production'
};

mode の詳細と、どのような最適化が行われるかについては こちら をご覧ください。

ブラウザの互換性

webpack は、 ES5 準拠 のすべてのブラウザーをサポートしています(IE8 以下はサポートされていません)。
webpackは import() と require.ensure() のための Promise を必要とします。
古いブラウザをサポートする場合は、これらの式を使用する前に ポリフィルをロードする 必要があります。

動作環境

webpack は Node.js バージョン 8.x 以降で実行されます。

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

kintone縛りでURL監視システムを構築する

kintoneにできるだけ処理をよせてURL監視(http/https)システムが作れるか試してみました。kintoneのお試しで無理やり作ってみたので本当にURL監視したい場合はpingdomやsite24x7などを使うのが良いかと思います。

ソースコード
https://github.com/isotai/url_monitoring_by_kintone

機能

  • 15分に1回ポーリング
  • URLごとに監視するか、中止するか設定できる
  • URLごとにしきい値をつけられる(連続n回失敗したらアラートする)
  • URLごとにしきい値を超えた場合にアラートするか決められる

処理フロー

XL9TIy9G6BxVhpZigWAXwox27z1Lq5Lu8RJfo6Rel0h1dPEr24iYflI99f9QIeJIf_wOr-FMl-WzcyeKmLrixvxdUS_xlCyMJXlHb94vI4ZI8HQQeILrufusUJiFj0li1rWHx-H88cHbRPM4Gfj4vzO9x-T7ZKS41I3lQxgHr1MWLM0bO4qmMwAc7Q3VG3zbrEb9Go4fBHOto0O1LWR6W9q1Uq4Uc0si0RGEj2T6qglXxy2v.png

kintoneアプリ

アプリは4つ作成しています。
image.png

  • 「url_monitoring_resorce」アプリがユーザーが監視対象のURLやしきい値を入力するアプリです。

  • 「url_monitoring_alarm」はURL監視の失敗回数を記録するアプリです。「url_monitoring_resorce」でレコードを登録した際に自動的に「url_monitoring_alarm」のレコードが作成されます。

  • 「url_monitoring_result」は監視結果を登録するアプリです。

  • 「[開くの禁止]monitoring_定期実行」にレコードはなく、本アプリを開いた時にURLへの監視がキックされるようにしています。監視機能をkintoneのJavaScriptのカスタマイズ機能で実装しているので処理を実行するためにはアプリにアクセスしてJavaScriptを実行する必要があります、そのため監視キック用のアプリを作成しています。監視は別サーバーで実行して、結果をkintoneに登録するのが楽そうですがkintone縛りでやっているので、監視もkintoneで実装します。。

bP7FJW8n4CRl-nGD9ptu6bwyU2UaQQ1YmzAIjdiWGCA21nCrNWGY60b_97eG8J6UZ2_JR1VUmh9qoO9YufpwpNmp_NLQGja2ocEGAX0g6SNmZF5Y2Y019E9vBA4e2ucBeE80WAaaiWnI7dTXaL4Y6ISq3paMpEDP77N58cfO1Sc1zEb9nghWxMin7vW1Lgl6K3LVLFDJDI_ZbDzH_etoBuBw-61P7uKVtM0yrwrEq7lKhTcX.png

  • kintoneにプライマリーキー、外部キーを設定することはできないので、上記図の[PK][FK]はそうしたい気持ちです。

image.png

image.png

ポイント

定期実行

kintone側に実装した監視処理を定期実行したかったのですが、kintone側にはcronのような定期実行を設定できる機能がない、作成した関数をAPIとして公開するようなこともできないようでした。そのためkintoneのevent処理 (JavaScriptのイベントリスナーの様な機能)を利用して監視処理を実行する必要があります。今回は苦肉の策として、「[開くの禁止]monitoring_定期実行」アプリの一覧画面表示イベントで監視処理を実行するように設定。別サーバーでヘッドレスブラウザーを使用して定期的に「[開くの禁止]monitoring_定期実行」へアクセスして処理をキックするように設計しました。kintone縛りしたかったのですが、定期実行をキックする部分のみkintoneだけでは実現できず...

具体的にはpuppeteerというHeadless Chrome使うのを便利にするNode ライブラを使用して処理を書き、自宅のラズベリーパイでcronを設定して15分間隔で定期実行するようにしています。

  1 const puppeteer = require('puppeteer');
  2 const user = 'xxx'
  3 const pass = 'xxx'
  4 const kintone_app_url = 'xxx'
  5 const kintone_login_url = 'xxx'
  6 
  7 (async () => {
  8   const browser = await puppeteer.launch({
  9     executablePath: '/usr/bin/chromium-browser',
 10     args: ['--no-sandbox']
 11   });
 12   const page = await browser.newPage();
 13   await page.goto(kintone_login_url)
 14   await page.waitFor(1000);
 15   await page.$eval('input[name="username"]', (el) => {
 16     el.value = user
 17   });
 18   await page.waitFor(1000);
 19   await page.$eval('input[name="password"]', (el) => {
 20     el.value = pass
 21   });
 22   await page.waitFor(1000);
 23   await page.click('input[type="submit"]');
 24   await page.waitFor(1000);
 25   await page.goto(kintone_app_url);
 26   await page.waitFor(2000);
 27   await browser.close();
 28 })();

JavaScriptからの別ドメインへの疎通確認

クロスドメイン制約で別ドメインへのリクエストが送れないのでkintoneに用意されているkintone.proxy(url, method, headers, data, callback, errback)メソッドで回避します。

所感

今回kintone側に無理やり処理を寄せましたが、本来であればkintoneはフロント側のデータ入力と表示、データベースの役割で使用し、監視などのロジックはlambdaなど使って構築するのが良いのかなと思います。

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

ツクールMVで小数ライブラリを使ってみた(Numeral.js) && メソッドの追加

ツクールMVの開発で小数を計算したかったのですが、Javascriptでは小数を扱う計算で誤差が生まれてしまうんですね。

そこで、小数を誤差なく扱えるライブラリを導入しました。

今回はnumeral-jsを使ってみました。
導入方法としては、GitHubのnumeral.jsにアクセス => Raw => 名前を付けて保存 をして、ツクールMVにプラグインとして追加します。

numeral.js
0.1 + 0.2 // => 0.30000000000000004
numeral(0.1).add(0.2).value()// => 0.3

元々の計算式より少々書き方が煩雑にはなりますが、誤差なく計算してくれるようになりました。

メソッドを足してみる

numeral.jsには四則計算のためのメソッドとして、add, subtract, multiply, divideがそれぞれ用意されています。
足し算のadd以外は長いので、エイリアス(別名)としてメソッドを足してみました。
加えて、複数の引数を入れて一気に計算できるメソッドも追加してみました。

numeral_hook.js
Object.assign(numeral.fn, {
    plus: function(value){ return this.add(value) },
    adds: function(...values){
        for(const value of values){
            this.add(value)
        }
        return this;
    },
    sub: function(value){ return this.subtract(value) },
    minus: function(value){ return this.subtract(value) },
    subs: function(...values){
        for(const value of values){
            this.subtract(value)
        }
        return this;
    },
    mult: function(value){ return this.multiply(value) },
    mults: function(...values){
        for(const value of values){
            this.multiply(value)
        }
        return this;
    },
    div: function(value){ return this.divide(value) },
    divs: function(...values){
        for(const value of values){
            this.divide(value)
        }
        return this;
    }
})
numeral_hook.js-test
numeral(0.3).sub(0.1).value()// => 0.3
//== numeral(0.3).subtract(0.1).value()// => 0.3
numeral(0.1).adds(0.2, 0.3, 0.4, 0.5).value()// => 1.5

GitHubにて配布しましたので、numeral.jsを使われる際はご自由にどうぞ

numeral_hook.js

Object.assign()で関数同士を結合してみましたが、Object.defineproperties()で新たにメソッドを書くのとどちらがいいんでしょう?

他のライブラリ

小数を扱えるようにするライブラリとしては、bignumber.jsやdecimal.jsのほうが有名そうですが、RPGツクールMV1.6.2ではプラグインとして読み込んでも、うまく動作しませんでした。

decimal.js
    new Decimal(0.1).plus(0.2)// => {constructor: Decimal(v), d:[3000000], e:-1, s:1}
    //(値が返ってくるはずが、オブジェクトが返ってくる)

理由は謎です。

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

Javascriptの非同期処理を制御する方法

はじめに

今回はJavascriptの非同期処理について解説していきます。

最初に非同期処理とはという解説をした後に、Promiseを使って制御する方法とasync/awaitを使って制御する方法を解説します。

頑張っていきましょう。

非同期処理とは

そもそも非同期処理とは一体なんでしょうか。

以下のサイトに分かりやすい図があったので拝借しました。

I-26-2. 非同期処理と同期処理の実装パターンと特徴

image.png

非同期処理を端的に言うなら、あるタスクが実行しているときに、他のタスクが別の処理を実行できる方式ということができます。

プログラムを例にして、詳しく解説していきます。

普通、プログラムを実行すると、コードを上から順に一行ずつ実行していきます。

これは普段から当たり前のように使っている同期処理と呼ばれるものです。

しかし、サーバーと通信を行った時などは、この同期処理とは違った挙動をします。

具体的には、サーバーと通信を行う(fetchなど)行を実行したあと、その終了を待たずに次の行が実行されることになります。

例えば、fetchを使ってサーバーからデータを取ってきた後に、そのデータを出力するというプログラムを書いたとします。

その時、非同期処理によりfetchが完了する前に次のコードが実行されるため、データが出力できない(undefainedになる)です。

以下のコードで、非同期処理を制御せずにfetchメソッドを用いてgithubからユーザーIDを取得して、出力してみましょう。ちなみに、外部からメソッドをimportするためESModuleであることをpackage.json内に示しておきましょう。

package.json
{"type": "module"}
import fetch from 'node-fetch';

const getGitUsername = () => {
    const url = 'https://api.github.com/users/hangi4343'

    fetch(url).then(res => res.json())
    .then(json => {
        console.log('非同期処理成功')
        return json.login
    }).catch(error => {
        console.error('非同期処理失敗', error)
        return null
    })
}

const message = 'GitのユーザIDは'
const username = getGitUsername()
console.log(message + username)

GitのユーザIDはundefined
非同期処理成功

const username = getGitUsername()の行でgetGitUsername関数の完了を待たずに、その下のconsole.log(message + username)が実行されるためusernameがundefainedになってしまいます。

fetchの使い方についてはこちらの記事を参考にして下さい。

簡単に解説すると、fetchが返すResponseオブジェクトを最初のthenで受け取り、その中でres.json()をreturnしています。その後、それをまたthen受け取り、メッセージを出力した後にjson.loginがreturnされます。また、この処理内でエラーが起きると、そのエラーをcatchで受け取り、その後の処理を実行します。

このコードが上手く実行されるために、const username = getGitUsername()の非同期処理を行う処理を、完了まで待ってから次のコードを実行することが考えられます。

この非同期処理の完了を待つために、Promiseやasync/awaitが用いられます。

Promiseで非同期処理を制御

それでは、非同期処理を制御していきましょう。

以下の記事を参考にしました。

Promiseについて0から勉強してみた

僕なりのPromise入門

Promiseはresolveとrejectの2つの関数を引数に取ります。

  • resolve : 処理が成功したときのメッセージを表示する関数
  • reject : 処理が失敗したときのメッセージを表示する関数
import fetch from 'node-fetch';

const getGitUsername = () => {
    return new Promise((resolve, reject) => {
        const url = 'https://api.github.com/users/hangi4343'

        fetch(url).then(res => res.json())
            .then(json => {
                console.log('非同期処理成功')
                return resolve(json.login)
            }).catch(error => {
                console.error('非同期処理失敗', error)
                return reject("reject")
            })
    })
}

const message = 'GitのユーザIDは'
getGitUsername().then(username => {
    console.log(message + username)
})

非同期処理成功
GitのユーザIDはhangi4343

非同期処理を制御することができました。

コードについて解説します。

Promiseは、resolveとrejectの2つの引数を持ちます。

getGitUsernameを実行すると、Promiseオブジェクトがreturnされます。

このPromiseオブジェクトに対してthenを用いると、そのthenの引数にPromiseオブジェクトのresolveでreturnされた値が代入されます。

もう少し詳しく解説します。Promiseのコンストラクタの中でresolveが呼ばれると、Promiseはresolveの状態になります。thenはPromiseの状態がresolveになったときに呼ばれるので、結果的にthenの引数にresolveでreturnされた値が代入されることになります。

この場合では、getGitUsername().then(username)=>の部分で、returnで帰ってきたPromiseオブジェクトに対してthenを実行し、resolve(json.login)のjson.loginがthenの引数であるusernameに代入されます。

今回のコードでは示していませんが、getGitUsername.then.catch(reject => console.log(reject))のようにすると、Promiseのコンストラクタの中でrejectが呼ばれたときに、Promiseがrejectの状態になり、catch内の処理が実行されます(catchはPromiseがrejectの状態のときに呼ばれるため)。

Promiseはresoveとrejectとpendingの三つの状態を持ちます。returnしたPromiseオブジェクトに対してthenを実行すると、Promiseオブジェクトがresolveの状態になるまで待ってからthen以降の処理が実行されます。

また、catchを実行するとPromiseオブジェクトがrejectの状態になるまで待ってからcatch以降の処理が実行されます。

また、このthenやcatchの処理は非同期処理になるため、resolveやrejectが返される前にその次のコードが実行され、resolveやrejectが返ってきて初めて実行されます。

async/awaitを使って非同期処理を制御

今度は、async/awaitを使って非同期処理を制御してみましょう。

こちらの記事を参考にしました。

Promiseが分かれば簡単!async, await

以下のコードです。

import fetch from 'node-fetch';

const getGitUsername = async () => {
    const message = 'GitのユーザーIDは';
    const url = 'https://api.github.com/users/hangi4343'

    const json = await fetch(url)
        .then(res => {

            console.log('非同期処理成功')
            return res.json()
        }).catch(error => {
            console.error('非同期処理失敗', error)
            return null
        })
    console.log(message + json.login)
}
getGitUsername()
console.log('end')

end
非同期処理成功
GitのユーザーIDはhangi4343

それではコードの解説です。

asyncは、関数の前に書きます。このasyncにより、その関数の中でawaitが使えるようになります。

awaitは端的にいうと、非同期処理を同期処理のように書けるようにしてくれるものという風に説明されます。

今回はconst json = await fetch(url)の部分でawaitを使っています。awaitにより、fetch(url)の部分で値がreturnするまで待つことができます。

そのため、値が返ってきたあとにconsole.log(message + json.login)の部分のコードが実行されます。

しかし、awaitはあくまでPromiseを簡単に扱えるようにしたものであり、内部的にはawaitの右側の式を同期的に実行した後、resolveしたpromiseを生成し、awaitの左側および下のコード全てをthenの引数の関数として実行しています。

つまり、この場合においてはconsole.log(message + json.login)の部分がPromiseにおけるthenの後に実行されるものであり、console.log('end')の部分はthenの後の処理ではありません。

つまり、getGitUsername()の実行の完了を待たずに、console.log('end')は実行されます。

終わりに

今回の記事はここまでになります。

お疲れさまでした。

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

【1ヶ月で学ぶ!】Back-end Developperへの道 #02日目 : Basic Frontend Kowledgeとは

下記の企画の1日目です。
【1ヶ月で学ぶ!】Back-end Developperへの道 #0:導入

下記を参考に項目を作成しました。
Roadmap to becoming a web developer in 2020

HTMLとは

  • Hyper Text Markup Languageの略で、Webページを作るための最も基本的なマークアップ言語のひとつ
  • ハイパーリンクや画像等のマルチメディアを埋め込むハイパーテキストとしての機能、見出しや段落といったドキュメントの抽象構造などの機能を持たせる
  • メールにも使用することができ、Webページのように文字のフォントや色や大きさを変えたり画像や動画を入れたり、また、メールの開封数を計測することもできる

CSSとは

  • Cascading Style Sheets” の略のスタイルシート言語で、デザインを施し、見栄えを整える
  • HTMLなどの構造化文書とは別に用意することで、見栄えと構造を分離させる使い方を推奨されている
  • 最近はメンテナンス性・再利用などの観点から、Ruby製のCSSメタ言語であるSassが選ばれるケースも増えている

JavaScriptとは

  • Webサイト・Webアプリ・バックエンド・デスクトップアプリ・モバイルアプリなど、ブラウザからサーバー、デスクトップからスマートフォンなど幅広く利用されるオブジェクト指向言語
  • 主にWebブラウザ上での動的な挙動を制御したり、動的にコンテンツを更新したり、マルチメディアを管理したりする際に利用される
  • JavaScriptライブラリのjQueryやReact、サーバーサイドのNode.js、フロントエンドフレームワークのAngularJSやVue.jsなどなど、今なお進化を続けている

所感

  • Sassは名前は聞いていたが触ったことはないので、ぜひ触ってポートフォリオに取り入れたい。
  • jsってほんま凄い子。jsになりたい。
  • Reactやりたい。
  • 本日の所要時間 0時間45分

参照

https://ja.wikipedia.org/wiki/HyperText_Markup_Language
https://ja.wikipedia.org/wiki/Cascading_Style_Sheets
https://ja.wikipedia.org/wiki/JavaScript
https://webdesignday.jp/inspiration/technique/css/5819/
https://eng-entrance.com/javascript-framework
https://www.sejuku.net/blog/45745

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

[handlebars.js]Smartyからhandlebars.jsを急に使うようになって困ったこと

Handlebarsとは

単純なテンプレート言語です。
テンプレートと入力オブジェクトを使用して、HTMLまたはその他のテキスト形式を生成します。
https://handlebarsjs.com/examples/simple-expressions.html
詳しいことは、公式ドキュメントをご覧ください。

Smartyからhandlebars.jsを急に使うことになった

プロジェクトが変わったらテンプレートエンジンも変わっていて、急にhandlebars.jsを使うことになりました。
基本的な使い方などは、Smartyを使ったことあるのでそんなに困ることはなかったです。
おそらくテンプレートエンジンをなにかしら使ったことあれば困らないと思います。

ifヘルパーはelseのみ

ifヘルパーを使いたいときに困りました。
ifヘルパーはelseのみ用意されています。
要は、else ifが使えないのです。
https://handlebarsjs.com/guide/builtin-helpers.html#if

なので、テンプレート側で複雑な条件式を書くことができないので、見通しはよくなります。
ただ、この出力に使っている処理の方を修正する必要があるので、Smartyに慣れてしまうとここが一番ネックになってくるかなと思います。

なかなか、else ifが使えないことってないですよね。

まとめ

シェアも少なく、あまり採用されているのも見たことないので、
調べものするときは公式ドキュメントを読む方が早いぐらいでした。
ちょっとした修正でドキュメントを隅から隅まで読むことはまずないので、メモとして書き留めておきました。

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

firebase firestoreで非同期でデータを取得する(Javascript,reactnative)

背景

ReactnativeでAndroidアプリ開発をしています。
本当に初学者なので間違えている点あると思いますがご了承ください、、、

firebase & Reactnativeでの開発をしていてタイムラインを実装しようとしたときに
1.まずPostをcollectiongroupとして取得
2.そのPostそれぞれが持つuidを元にPostを投稿したUserを取得し、User情報と一緒にListにぶち込む
3.そのListをsetListとしてstateを変更してFlatListで表示する

という実装をしたかったのですが、FlatListをScrollViewの中に入れてしまったりするとどうも表示されず、setListもうまく機能していなかったので色々調べました

ソースコード

うだうだ話していてもあれなので実際に実装したコードをとりあえず貼っておきます

TLScreen.js
useEffect(()=>{
  firestore()
  .collectionGroup('posts')
  .onSnapshot(async(querySnapshot) => {
    let tempList = [];
    await Promise.all(querySnapshot.docs.map(async doc => {
      let documentSnapshot=await firestore().collection('users').doc(doc.get('uid')).get();
      tempList.push({
        ...doc.data(),
        uid:doc.get('uid'),
        uname:documentSnapshot.get('nickname'),
        uimg:documentSnapshot.get('img'),
        id: doc.id,
      });
    }));
    setList(tempList);
  });
},[]);

私はタイムラインをつくる、という目的だったのでuseEffect内での関数ですが、

上のような実装をすると無事postにデータが入った状態でScreenがRenderされるのでいい感じに表示できました!

詰まった所について解説しておきます。

async awaitについて

初心者向けです。
まだ自分も初心者なので理解が浅く、非同期関数でつまずいてしまったので
同じような人の助けになればと思い書かせて頂きます。

asyncは何があってもPromiseを返す関数
例えば、

//これは1を返す
function number(){
  return 1
}

//これは1をラップしたPromiseが返される
async function number(){
  return 1
}

awaitはPromiseを返す関数の前につければ、その関数の処理が終わるまでその行で関数の実行を待っていてくれるやつ(ただしasync関数内でしか使えない)
例えば、

async function a(){
  //firestoreからなんか取ってくるとかの重い処理をするとこの行↓で実行が止まってくれる!
  let users = await firestore().collection('users').get()
  //上のawaitの関数の結果が返ってきてからconsole.logが動く!
  console.log(users)
}

といった感じで考えてもらえるとよいかと思います。(間違っていたらすみません!)

じゃあPromiseとは?てなる人がいるかもしれません

これも私の理解が浅いのですが、Promiseは.thenとか.catchとかを関数の後に続けることができて、前の結果を受けて次にどんな処理をするのか、というのを決めることができるという返り値です。

firestore().collection('users').get()
.then(()=>{})
.catch(()=>{})

的な感じです多分、、、

Promiseがあることで何かの処理を終えた後で何かの処理を行うという非同期処理を行うことができるのです。

このPromiseを使いやすくしたのがasync awaitで、awaitを使えばPromiseのときのように.thenなどを使わずにどの処理を待てばよいかわかる、というようなものだと思います、多分、、、

流れ

実際に流れを見ていくと

TLScreen.js
useEffect(()=>{
  firestore()
  .collectionGroup('posts')
  .onSnapshot(async(querySnapshot) => {
    let tempList = [];

まずここまではuseEffect内でpostsのcollectionGroupを取ってきて.onSnapshotでその結果であるquerySnapshotを用いて関数を実行しています。

ここでasyncを用いているのはこの関数内でawaitを使いたい(処理を待たせたい)関数があるため、awaitを使うためにasync関数にしています。
let tempList=[]はpostsの情報を一時的に入れておくための箱を用意してるだけです。

TLScreen.js
  .onSnapshot(async(querySnapshot) => {
    let tempList = [];
    await Promise.all(querySnapshot.docs.map(async doc => {

その後ですが、async関数内でawaitを使いたいものとは、Promise.allというものです。

この関数は複数のPromiseを返す関数を平行して処理してPromiseを返す、というものです。もちろんPromiseを返してくれるのでawaitをつけることができます。

次にPromise.allが処理する関数はなにかといわれると、引数のquerySnapshot.docs.map(async doc =>{...となるのですが、

ここではquerySnapshotが持つdocumentひとつひとつに対してそれらを引数としたasync関数を実行している、という感じです。

また先ほどとは違い、ここでasyncを使っているのはawaitを使いたいだけでなく、
async関数は必ずPromiseを返すので、Promise.allで実行することができる、ということも考えています。

TLScreen.js
  .onSnapshot(async(querySnapshot) => {
    let tempList = [];
    await Promise.all(querySnapshot.docs.map(async doc => {
      let documentSnapshot=await firestore().collection('users').doc(doc.get('uid')).get();
      tempList.push({
        ...doc.data(),
        uid:doc.get('uid'),
        uname:documentSnapshot.get('nickname'),
        uimg:documentSnapshot.get('img'),
        id: doc.id,
      });
    }));
    setList(tempList);
  });
},[]);

そしてそのあとですが、ここで2回目のfirestoreの呼び出しが起こります。
ここで注意してほしいのが、firestore().collection().doc().onSnapshotは使えない、ということです。

さっきは.onSnapshotを使っていたのに今回はなぜだめなのか、となるかと思います(なってほしい笑)

いま、このasync関数の中では、
1. 処理の重いfirestoreへの呼び出し
2. tempListへのデータのpush
の2つのことが行われようとしています。

このときfirestoreへの呼び出しができてからtempListにデータをpushしたいのですが、そのためには1の段階で処理を止めないといけません。

そのためasyncを使いたいのですが、asyncはPromiseを返す関数にしか使えないので、.onSnapshotとしてしまうと間違いなのです。

そこで、get()メソッドを用います。get()はPromiseを返してくれるのでawaitを用いることができ、非同期でDocumentSnapshotを取得することができるのです!

*ちなみに.onSnapshotとするとawaitがつけられないので処理をしただけで結果が返ってこないまま進むので結果的に空のtempListをsetListすることになり表示されません

かくしてtempListにデータが無事入り、setListでstateにセットすることができたのでFlatlist等で表示できるかと思います!

参考にさせていただいたサイト

Firestoreのデータをasync/await, mapで取り出したい
Async/await - 現代の JavaScript チュートリアル
firebase公式サイト様様

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

学習日記7日目(2020/6/26)

学習内容

  • JavaScript基礎学習:Progateで学習コースIVまで終了

 以前はしっかりJavaScriptの学習をしておらず、Laravelの学習に入ってからいきなりクラスやインスタンスが出てきてあまり理解できていなかったのですが、JSの学習時にそれらの詳しい説明があって理解が深まりました。嬉しい誤算です。

その他

  • タイピング練習

明日の予定

  • JavaScript基礎学習:できればProgate終了まで
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む