ふなまる水産業務作業日報

フロントエンドエンジニアをやらせていただいております。日々のつまづきを備忘録として残していきます。

【Nuxt.js】動的ルーティングについて

はじめに

動的ルーティングについてここらで整理をと思い、めも。

動的ルーティングの概要

まず、動的ルーティングとは何か。 以下公式より抜粋。

パラメータを使って動的なルーティングを定義するには .vue ファイル名またはディレクトリ名に アンダースコアのプレフィックス を付ける必要があります。

例えば

pages
    users
        _id.vue

_id.vueが動的ルーティング。

users/1でアクセスすれば、1のユーザーのページが表示される。

つまり動的に、1のユーザーのページを生成している。

各モードと動的ルーティング

Nuxt.jsには、SSR、SPA、静的の3つのモードが用意されている。

それぞれの動的ルーティングの動きをまとめると以下のような感じかと思う。

  • SSR -> ⭕️

  • SPA -> ❌

  • 静的 -> ⭕️

SSR

SSRは、サーバーが動的なルーティングに対しHTMLを作成するため問題なく動く。

SPA

SPAの場合、表示は可能だが、そのページでリロードをすると404となる。 理由としては、そのURLに該当するHTMLファイルがサーバー上になく、参照できないため。 全てブラウザ上で、動的にページを生成しているSPAでは、表示はできても参照はできない。

▽表示はできても
f:id:h-sasaki-worksfront:20200601012956p:plain

▽リロードするとこうなる
f:id:h-sasaki-worksfront:20200601012954p:plain

静的

静的はそのままでは、動的ルーティングができません。 少し設定が必要になります。

公式より以下抜粋

generate コマンド(yarn generate)では 動的なルーティング は無視されます。Nuxt はこれらのルートが何であるのか知らないので、生成することができません。

そこで、Nuxtは以下のように解決策を提示しています

動的なパラメーターを用いたルートを生成させたい場合は、動的なルーティングの配列をセットする必要があります。 nuxt.config.js 内に /users/:id のルーティングを追加します:

export default {
  generate: {
    routes: [
      '/users/1',
      '/users/2',
      '/users/3'
    ]
  }
}

また、 動的なパラメータが必要となった場合は、routesに対して ページのルーティングを返すようにAPIのfetchを行います。 もっとも効率的な方法として、payloadを使用することが勧められています。

上記の例では、サーバーから user.id を利用してルーティングを生成しますが、必要なデータ以外を破棄しています。通常、そのような場合は /users/id.vue の内部から再度データを取得する必要があります。再度取得することは可能ですが、そうした場合は generate.interval オプションに 100 などの値を設定して、サーバーへとコールが溢れないようにする必要があります。このような実装は生成時間の増加へとつながるため、 user オブジェクト自体を、 id.vue のコンテキストに渡すことが望ましいでしょう。上記のコードを、以下のように変更することで、実現することができます :

import axios from 'axios'

export default {
  generate: {
    routes () {
      return axios.get('https://my-api/users')
        .then((res) => {
          return res.data.map((user) => {
            return {
              route: '/users/' + user.id,
              payload: user
            }
          })
        })
    }
  }
}

このように、 /users/_id.vue から payload へとアクセスすることが可能です :

async asyncData ({ params, error, payload }) {
  if (payload) return { user: payload }
  else return { user: await backend.fetchUser(params.id) }
}

( ここら辺は公式を見るのが早い )

まとめ

動的ルーティングの設定・使用可否はモードによって異なるので注意して使用しよう。(雑)

参考

【Nuxt Typescript】vuex-module-decoratorsを使ってみた

はじめに

vuex-module-decoratorsは、class構文でvuexを書くデコレーター。

これまでのNuxt Typescriptはcommitやdispatchを使用することで

型の推論が途切れてしまうという問題があった。

それを解決するツールとして最近スタンダードに使われているのがこのvuex-module-decoratorsらしいので試してみた。

全体像

axiosの設定部分は // axios としています。

~/store/index.tsと~/utils/store-accessor.tsが storeとコンポーネントをタイプセーフに繋ぐ役割を持っています。 ( これを別々のファイルとして定義している理由があんまりよくわかっていない... )

component
└ comp.vue

store
├ index.ts
└ sampleStore.ts
    
utils
├ api.ts // axios
└ store-accessor.ts
    
plugins
└ axios-accessor.ts  // axios

ノーマルなvuexと比較

ノーマルなvuex

import { MutationTree, ActionTree, GetterTree } from 'vuex'
import { CounterState, RootState } from '@/store/types';

export const state = (): CounterState => ({
  count: 0
})

export const getters: GetterTree<CounterState, RootState> = {
  getCount: state => state.count
}

export const mutations: MutationTree<CounterState> = {
  increment(state: CounterState, delta: number): void {
    state.count += delta
  }
}

export const actions: ActionTree<CounterState, RootState> = {
  incr({ commit }) {
    commit('increment', 5)
  }
}

vuex-module-decorators

vuex-module-decorators

import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'

interface CounterState {
  count: number
}

@Module
export default class Counter extends VuexModule {
    // state
  count: number = 0

  // getters
  getCount() {
    return this.count
    }

  @Mutation
  increment(delta: number) {
    this.count += delta
  }

  // action 'incr' commits mutation 'increment' when done with return value as payload
  @Action({ commit: 'increment' })
  incr() {
    return 5
  }
}

感想

個人的にはvuex-module-decoratorsの方が見た目もスッキリしているし 型もパッとみてわかりやすい気がするのでありかなと思います。

参考文献

【Nuxt.js】各モードについてメモ。

はじめに

Nuxt.jsを日々使ってはいるものの、まだまだ知識不足な部分がある。 そのうちのひとつである、Nuxtのモードについて ふわっとした知識しかなかったため 今回実際に触れて つまづいたところなどをメモしておく。

それぞれのモードについて

モードはまず、3つある。

1. SSR(サーバー サイド レンダリング)

2. SPA(シングル ページ アプリケーション)

3. 静的モード

SSR

NuxtにはNuxtサーバー(node.js)と呼ばれるものがある。 Nuxtサーバー上で、あらかじめhtmlを生成し、ブラウザに表示する方法。

SEO

クローラーが完全にレンダリングされたページが最初に表示されるため、SEO観点で良いとされる。

高速

サーバー側にHTMLを作成する責務があるため、ユーザー側のデバイス・ネットワークに影響を受けない。

注意点

  • サーバーサイドとクライアントサイドそれぞれ固有のコードを使い分けなければいけない。(例:サーバー側では windowやdocumentといったブラウザのAPIは使用できない)

  • 静的ファイルサーバーにデプロイできる完全に静的なSPAとは異なり、サーバーレンダリングアプリには、Node.jsサーバーを実行できる環境が必要

  • Node.jsでのレンダリングは、静的ファイルを提供するよりもCPUに負荷がかかるためサーバーに負荷がかかる。 高トラフィックが予想される場合は、対応するサーバーの負荷に備え、キャッシュなどを工夫し利用する必要がある。

SPA

SPAのビルドは'spa'モードで行い、/distの静的なファイルをサーバーにアップロードするのみ。

SPAは全てクライアントサイドで処理を行う。 必要な資材を全て最初にサーバー側より受け取り、再度リロードを行わない限り ブラウザ上でjavascriptがページのコンテンツの出し分けを行い、実際には遷移をしていないが、しているように見せる挙動をする。

注意点

  • 初期のサーバーへの問い合わせが多いため、初期描画が遅い。

  • クローラが読み込む時点ではコンテンツが何もないためSEOの懸念点がある

静的モード

静的モードのビルドは'universal'モードで行い、事前にSSRでHTMLを作成しておく。

SSR時に、fetchしたAPIの情報を組み込んだ状態でレンダリング、ビルドを行うことで 完全にレンダリングされた静的ファイルが吐き出される。

これによりSEO観点ではSSR同様、安心である。

もしユーザーのステータスに合わせてコンテンツを出し分ける場合は、generate時に 全てのデータを取得、コンテンツ側では、出し分ける処理をあらかじめ書いておく。

あまりにも動的にユーザーに合わせて表示を変更するような処理の場合は 複雑化する可能性が考えられる。

以上。

参考

three.js(version r106)でMMDモデルを動かす

three.js(version r106)でMMDモデルを動かす

概要

公式のexampleはこちら▼
- MMDダンス: https://threejs.org/examples/#webgl_loader_mmd - カメラモーション付き: https://threejs.org/examples/#webgl_loader_mmd_audio

exampleを参考に作ってみました。▼
- https://mmdtest.netlify.com/ (めちゃくちゃ重いので時間かかります。)

github
- https://github.com/kamabokochan/Jasmine

お借りしたもの▼
- ちゃわんむ式善逸モデル(https://www.nicovideo.jp/watch/sm35808552) ちゃわんむし様 - 【MMD戦国BASARA】極楽浄土 モーショントレース(https://www.nicovideo.jp/watch/sm29180863) yurie様 - カメラモーション ゆきねこ様 - ステージ 冴木稲荷神社 シロ様

ソースコード解説

各所ポイントのみ

js

initの中

単純なthree.jsのセッティング部分です(光とかカメラとか)▼

this.scene = new THREE.Scene();
this.clock = new THREE.Clock();
this.setLight();
this.setDisplay();
this.setCamera();
this.bindEvent();
this.loader = new THREE.MMDLoader();

各外部ファイルの読み込みが完了するまでawaitで待機して同期的な処理にしています▼

await this.LoadPMX();
await this.LoadStage();
this.vmd = await this.LoadVMD();

アニメーションの読み込み▼

this.helper = new THREE.MMDAnimationHelper();

    this.helper.add(this.mesh, {
      animation: this.vmd,
      physics: false
    });

アニメーションのループ▼
requestAnimationFrameでループ helper.updateはDelta:numberを受け取ってミキサー時間を進め、ヘルパーに追加されたオブジェクトのアニメーションを更新します

 Render() {
    requestAnimationFrame(() => this.Render());
    this.renderer.clear();
    this.renderer.render(this.scene, this.camera);
    this.helper.update(this.clock.getDelta());
  }

まとめ

動くと感動するね!

JavaScriptの同期処理・非同期処理


※この記事の内容はJavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみるを参考に、自分用にまとめただけなので、こちらのリンクを辿って参考にした方が良いです.


JavaScriptの同期処理・非同期処理

Javascriptはシングルスレッド

  • Javascriptはシングルスレッドで動いている.
  • 同期であろうと非同期であろうと2つ以上の処理を同時に行なうことはできない.
  • JavaScriptでは、キューに登録された関数が順番にひとつずつ実行されていく.
  • でもキューに登録される順番が同期であったり非同期であったりする.

まとめると - JavaScriptは並列処理できない:1度に1つの仕事しかできない。 - JavaScriptは非同期処理できる:誰か(DB等)に仕事を任せている間に自分の他の仕事を進めることができる

同期処理・非同期処理

  • 同期処理
    • 書いた順番に実行されていく.
    • 重たい処理が間にあると、そこで大きな待ち時間が生まれる.

同期処理の例

console.log(1);
console.log(2);
console.log(3);

実行結果

1
2
3
  • 非同期処理
    • あるタスクが実行をしている際に、他のタスクが別の処理を実行できる方式.
    • 例えばサーバーと通信を行った際に、リクエストが返ってくるまでに数秒以上もかかる場合、レスポンスが返るまでに一旦関数から抜けて別の処理を進めて、レスポンスを受け取り次第、呼び出し元に値を返す.

非同期処理の例

console.log(1);
setTimeout(function(){console.log(2)}, 1000);
console.log(3);

キューの登録順

console.log(1);
setTimeout(function(){console.log(2)}, 1000); //★
console.log(3);
// 1000ミリ秒後に★で登録された新たなキュー↓
console.log(2)

実行結果

1
3
2

関数setTimeoutに無名関数function(){console.log(2)}が渡ったように、ある関数Aの引数に別の関数Bを渡し、AからBを呼び出すことをコールバックという。

非同期処理の例2

var xhr = new XMLHttpRequest();
xhr.open('GET','何かのAPI');
xhr.send();
xhr.addEventListener('load', function(result){
  console.log(1);
});
console.log(2);

キューの登録順

xhr.open('GET','何かのAPI');
xhr.send();
xhr.addEventListener('load', function(result){
  console.log(1); // ★
});
console.log(2);
console.log(1); // ★

addEventListener()でloadイベントが発生した時点でlog(1)がキューに登録される. これはその前の一連の関数とは関係ないタイミングでキューに登録されるので非同期.

実行結果

2
1

つまり、JavaScriptXMLHttpRequestを使って外部と通信している間に、次の自分の仕事であるconsole.log(2)を進めている事になる. これにより、JavaScriptのスレッドでやらなくても良い仕事を外部にやらせてる間にJavaScriptのスレッドが自分の仕事を進められる.

同期処理と非同期処理の比較

複数行の処理を行う上で、同期処理は簡潔だが、非同期処理は複雑になる。

複数行の同期処理

関数の例

var syncBuyApple = function(payment){
  if(payment >= 150){
    return {change:payment-150, error:null};
  }else{
    return {change:null, error:150-payment + '円足りません。'};
  }
}

複数行の同期処理

var result1 = syncBuyApple(500);
if(result1.change !== null){
  console.log('1つ目のおつりは' + result1.change + '円です。');
}
if(result1.error !== null){
  console.log('1つ目でエラーが発生しました:' + result1.error);
}
var result2 = syncBuyApple(result1.change);
if(result2.change !== null){
  console.log('2つ目のおつりは' + result2.change + '円です。');
}
if(result2.error !== null){
  console.log('2つ目でエラーが発生しました:' + result2.error);
}
var result3 = syncBuyApple(result2.change);
if(result3.change !== null){
  console.log('3つ目のおつりは' + result3.change + '円です。');
}
if(result3.error !== null){
  console.log('3つ目でエラーが発生しました:' + result3.error);
}
var result4 = syncBuyApple(result3.change);
if(result4.change !== null){
  console.log('4つ目のおつりは' + result4.change + '円です。');
}
if(result4.error !== null){
  console.log('4つ目でエラーが発生しました:' + result4.error);
}

実行結果

1つ目のおつりは350円です。
2つ目のおつりは200円です。
3つ目のおつりは50円です。
4つ目でエラーが発生しました:100円足りません。

複数行の非同期処理

関数の例 今度はreturnではなく1秒後にコールバックでおつりを受け取る

//150円のりんごを1つ買う関数
//第一引数に支払い金額
//第二引数にコールバック関数
//おつりを計算してコールバック関数に渡す
var asyncBuyApple = function(payment, callback){
  setTimeout(function(){
    if(payment >= 150){
      callback(payment-150, null);
    }else{
      callback(null, '金額が足りません。');
    }
  }, 1000);
}

複数行の非同期処理

//りんごをたくさん買う場合(コールバック地獄)
asyncBuyApple(500, function(change, error){
  if(change !== null){
    console.log('1回目のおつりは' + change + '円です。');
    asyncBuyApple(change, function(change, error){
      if(change !== null){
        console.log('2回目のおつりは' + change + '円です。');

        asyncBuyApple(change, function(change, error){
          if(change !== null){
            console.log('3回目のおつりは' + change + '円です。');
          }
          if(error !== null){
            console.log('3回目でエラーが発生しました:' + error);
          }
        });
      }
      if(error !== null){
        console.log('2回目でエラーが発生しました:' + error);
      }
    });
  }
  if(error !== null){
    console.log('1回目でエラーが発生しました:' + error);
  }
});

実行結果

1つ目のおつりは350円です。
2つ目のおつりは200円です。
3つ目のおつりは50円です。

コールバックを続けて行なうとネストがかなり深くなる. これがコールバック地獄と呼ばれている.

Promise

コールバック地獄を回避する方法として登場したのがPromise.

var promiseBuyApple = function(payment){
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            if (payment >= 150) {
                resolve(payment - 150);
            } else {
                reject('金額が足りません。');
            }
        }, 1000);
  });
}

この関数はPromiseオブジェクトを返す. Promiseオブジェクトのコンストラクタに渡す関数には、成功した場合に実行する関数と、失敗した場合に実行する関数を渡す. この例の場合は、成功した場合にはresolve、失敗した場合にはrejectが実行される.

Promise.prototype.then() メソッドと Promise.prototype.catch() メソッドもまた Promise を返すので、これらをチェーン (連鎖) させることができる.

Promiseを使い簡潔になった例

promiseBuyApple(500).then(function(change){
    console.log('おつりは' + change + '円です');
    // 成功なら次のthenへ
    // 失敗ならcatchへ
    return promiseBuyApple(change);
}).then(function(change){
    console.log('おつりは' + change + '円です');
    // 成功なら次のthenへ
    // 失敗ならcatchへ
    return promiseBuyApple(change);
}).then(function(change){
    console.log('おつりは' + change + '円です');
    // 成功なら次のthenへ
    // 失敗ならcatchへ
    return promiseBuyApple(change);
}).catch(function(error){
  console.log('エラーが発生しました:' + error);
})

このように同期処理のように記述できる.

async / await

Promiseよりさらに非同期処理を簡潔に書ける方法.

async / awaitで書き換えた例

async function asyncCall() {
  try {
    var change = await promiseBuyApple(500);
    console.log('おつりは' + await promiseBuyApple(500) + '円です');
    change = await promiseBuyApple(change);
    console.log('おつりは' + change + '円です');
    change = await promiseBuyApple(change);
    console.log('おつりは' + change + '円です');
    change = await promiseBuyApple(change);
    console.log('おつりは' + change + '円です'); // error: catchへ
  } catch (error) {
    console.log(error)
  }
}

asyncで定義した関数は、promise(Promiseのインスタンス)を返す. awaitをPromiseに付けると、Promiseがresolve()するのを待ってその値を取得する (ように見える) ようになる.awaitはasync関数内でないと使えない.

参考

【MMD】 3Dモデリング 初心者備忘録

今回の使用ソフト

  • metasequoiaLE 3.0

    • モデリングソフト。windowsでしか動きません。macで作りたかったのですが、macで動作するバージョンのものはモデルファイルの出力形式が限られています。。とりあえずフリーでも一通り作れる用になりたかったため今回はこれ。
  • PmxEditor

    • ボーン(骨)とウェイト(肉と骨の繋ぎ役)をモデルにはめるために使いました。今回は、配布モデルのボーンをお借りしてウェイト付けをしました。実は配布されているものをそのまま解すると、色々とエラーメッセージが出てきます。これは、各ファイルに権限が制限されているためのようで、対象ファイルの詳細表示を行い、権限の承認を行わなければいけません。因みに私は面倒くさかったので、ファイルをZIP化->解凍を行いました。(これでも権限による問題は解決されます。詳しくはググってね。)
  • MMD

    • MikuMikuDance。モデルとモーションを合わせて実際に動かすことが出来ます。

各所ポイント

  • metasequoiaLE

    • 鏡像を利用すること。目とか足・腕とか片方を作るともう片方も鏡として自動生成してくれる機能がある。非常に便利。最後にフリーズ?させて鏡じゃなく、実体として保存する。
  • PmxEditor

    • ボーンとモデルの頂点をそろえるため、モデルの頂点を選択し、オブジェクト操作で重心をとり、スケール(0,0,0)を押す。ボーンとモデルを合わせるのはこれが一番簡単そう。
    • ウェイト付けなども鏡像への適用が出来るので、便利。
  • MMD 今のところ特になしかな。

完成品

参考

【express + swagger + vue】swaggerで立てたモックをGUI操作で簡単に変更する

概要

前回【Swagger】モックサーバーの立て方について書きました。

ただ、あくまで定義ファイルという立ち位置だとすると、ワンパターンのAPIでは少し不便です。

FE観点として実際に利用するとなると、例えば複数のコンテンツのUI、もしくは、様々なパターンによってUIを出し分ける実装の検証などには、データの個数・値の変更を行えればなお良いかと思います。

そこで、簡易的ではありますがプロトタイプを作成したのでご紹介します。

仕様

今回用いるフレームワークは以下です。

  • vue.js

  • express

手順としては、

  • expressで、swaggerのモックサーバーにHTTPリクエストを行う
  • 受け取ったAPIをGETリクエス
  • client側のvue.jsで取得と加工を行い、適宜POST
  • expressでPOSTを受け取り、GETリクエス

簡易的ですが、ざっくりと構造は以下となります。

nodejs-server
└ ...api
client
└ ...vue.js
server
└ index.js

server / index.js

const express = require('express')
const app = express()
const port = 3000
const rp = require('request-promise')

// body parser
app.use(express.json())
app.use(express.urlencoded({ extended: true }));

app.use('/', express.static('./client/dist'))

// CORSを許可
app.use(function (req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

// request options
const options = {
  url: 'http://localhost:8080/v2/pet/1',
  method: 'GET'
};

let Pet = {};

(async () => {
  try {
    Pet = await rp(options);
    app.get('/v2/pet/1', function (req, res) {
      res.send(Pet)
    })
  } catch (error) {
    console.log(error);
  }
})();

app.post('/test', function (req, res) {
  Pet = req.body
})

app.listen(process.env.PORT || port);
  1. swaggerのエンドポイントhttp://localhost:8080/v2/pet/1へリクエストを飛ばす
  2. 結果を非同期で取得、http://localhost:3000/v2/pet/1エンドポイントで配信
  3. /testに対し、リクエストが来たら変数Petへ格納、連動しhttp://localhost:3000/v2/pet/1が更新される。

というのがざっくりとした流れです。

client / src / App.vue

<template>
  <div id="app">
    <p>status: {{Response.status}}</p>
    <p>
      status change =>
      <input type="checkbox" v-model="isCheck" />
      <button @click="postData">submit</button>
    </p>
  </div>
</template>

<script>
import axios from "axios";
export default {
  name: "app",
  components: {},
  data() {
    return {
      Response: {},
      isCheck: false
    };
  },
  methods: {
    async postData() {
      const sendData = {
        ...this.Response,
        status: this.isCheck ? "available" : "unavailable"
      };
      const res = await axios.post("http://localhost:3000/test", sendData);
      console.log(res.data);
    }
  },
  async mounted() {
    try {
      const res = await axios.get("http://localhost:3000/v2/pet/1");
      this.Response = res.data;
      this.isCheck = this.Response.status === "available";
    } catch (err) {
      console.log(err);
    }
  }
};
</script>

vueでは大したことはやっていないのですが

  1. mountedでAPI取得
  2. v-modelでcheckboxと連動して変数の値を変更
  3. 取得したAPIの雛形をもとにcheckboxによって値を更新し、postする

といった流れとなっております。

まとめ

簡単さとシンプルさを追求した分、無理矢理感はありますがAPI定義を元に自由に値を変更できる実装の紹介でした。 一方で、自由すぎる部分や、API定義の変更に弱い部分がまだあると思うのでもう少し良い方法やツールがあればぜひ教えて欲しいです。

リポジトリ

github.com