- 投稿日:2019-06-25T23:49:06+09:00
Vue.js の vm.$scopedSlots により slot 内部要素から値を受け取る
Vue.js の .$scopedSlots により slot 内部要素から値を受け取る
vm.$scopedSlots を使用することで SSR の結果より値を受け取る際に使えるかも・・・?しれません。
使用する slot 持ちコンポーネント
slot を持ち親コンポーネントに
vm.$scopedSlot
の実行結果を.$emit()
するコンポーネントです。
vm.$scopedSlot
の結果は VNode インターフェース の型で返ります。slot-data.vue<template> <div class="slot-data"> <slot/> </div> </template> <script> export default { mounted() { this.$scopedSlots['default']() .forEach(vn => { if (vn.data) { this.$emit('slot-data', vn.data) } }); }, }; </script>親コンポーネントにおいて下記のように
@slot-data
として受け取ります。App.vue<template> <div id="app"> <img width="10%" src="./assets/logo.png"> <SlotData @slot-data="slotData"> <input name="abc" value="1"> <input name="def" value="taro"> </SlotData> </div> </template> <script> import SlotData from "./components/SlotData"; export default { name: "App", components: { SlotData }, methods: { slotData(data) { console.log(data); } } }; </script>親コンポーネントは
slotData
により 受け取ったdata
より
取得したいデータをそれぞれ処理するような形です。もっといい方法はありそうですが・・・
- 投稿日:2019-06-25T23:33:17+09:00
Nuxtのsentryモジュールでエラーを検知するときの注意点
こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。
Nuxtのsentryモジュールでエラーを検知するときの注意点
以下のようにtryの中で例外が発生する場合は注意が必要です。このとき、sentryで通知するには
this.$sentry.captureException(e)
が必要なことに注意してください。デフォルトでは、Uncaught Exceptionにしかsentryは機能しないからです。
try { const a = 1 a() } catch (e) { this.$sentry.captureException(e) // これが必要! }参考 公式 https://github.com/nuxt-community/sentry-module
はてなブックマーク・Pocketはこちらから
- 投稿日:2019-06-25T21:27:50+09:00
[初心者向け]VueとExpressを使ってパラメーターの受け渡しをしてみよう
はじめまして、PMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います.はじめに
Vue.jsでaxiosを使ってパラメーターをpostしexpressで作った、apiで受取るというところまでやりたいと思います。
必要な環境
Vue.jsの実装
- まずこちらの記事を参考にしてサーバーを立ち上げてください。
- http://localhost:8080にアクセスして、このような画面が表示されれば成功です。
![]()
- 次にaxiosをインストール
npm install axios -s
画面を作る
- 次に
src/components/HelloWorld.vue
をいかのように書き換えてくださいHelloWorld.vue<template> <div class="hello"> <form action> <input type="text" placeholder="text" v-model="text"> <input type="submit" value="decide" @click="submitClick"> </form> </div> </template>HelloWorld.vueの
</template>
以下に<script>
を追加HelloWorld.vue<script> import Axios from "axios"; export default { name: "HelloWorld", data() { return { text: null }; }, methods: { submitClick() { alert(this.text) } } }; </script>このようにinputに文字を入力してその文字がalertに表示されれば成功です
<script></script>
を書き換え次に
<script></script>
を以下のように書き換えてくださいHelloWorld.vue<script> import Axios from "axios"; export default { name: "HelloWorld", data() { return { text: null }; }, methods: { submitClick() { const body = { text: this.text }; Axios.get(`http://localhost:3000/hoge/${this.text}`) .then(res => { console.log(res.data.name) }) .catch(err => { console.log(err); }); } } }; </script>Expressの実装
- まずこちらの記事を参考にサーバーを立ち上げてください
- http://localhost:3000/にアクセスして以下のようになれば成功です
![]()
簡単なapiを作ってみる
アプリ名/app.js
に以下のことを追記しましょうapp.jsapp.get('/hoge', (req, res) => { res.json({ name: "hoge" }) });
- そしてhttp://localhost:3000/hogeにアクセスして以下のように表示されれば成功です。
apiを書き換える
app.jsapp.get('/hoge/:name', (req, res) => { let data = req.params console.log(data.name); res.json({ name: data.name }) });これでコードを書く部分はすべて終了です。
動かしてみる
http://localhost:8080にアクセスし任意の文字を入力し送信してください。
最後に
初心者向けにVue.jsからExpressのapiへのパラメーターの受け渡しを行いました。
これから始める方の手助けになれば幸いです。
なにか間違いがあればご連絡ください。
- 投稿日:2019-06-25T21:21:07+09:00
[Vue]テキストをクリックすると入力欄に変わる、といった実装には$nextTickが便利な件
とりあえず成功したサンプル
See the Pen [Vue]テキストを押したら入力欄が現れて、かつフォーカスする(1) by riotam (@riotam4) on CodePen.
使い方
「ここをクリックしてね」自体をクリックしたら、入力欄が現れて、そのまま編集できる。
という想定で作ってみました。失敗したコード
See the Pen [Vue]テキストを押したら入力欄が現れて、かつフォーカスする(2) by riotam (@riotam4) on CodePen.
問題点
入力欄は出せたけど、フォーカスができていません。
失敗コードの解説
HTML側<div id="app"> <p v-if="inputField"><input ref="focusThis" value="ここをクリックしてね"></p> <p @click="show" v-else>ここをクリックしてね</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script>JS側new Vue({ el: "#app", data: {inputField: false}, methods: { show() { this.inputField = true; this.$refs.focusThis.focus(); }, }, })まず、「ここをクリックしてね」テキストをクリックすると、v-on:clickディレクティブによって、showメソッドが発火されます。
showメソッドでdataのsがtrueに変更され、すぐにthis.$refs.focusThis.focus();
が発火し、ref="focusThis"のついた要素を探し、そこにフォーカスします。一方、その頃HTML側では、inputFieldがtrueになったことで、HTML側のv-ifもtrueとなり、DOM操作によりinputタグが表示されます。
それと同時に、「ここをクリックしてね」テキストはv-elseなので消え。テキストと入力欄が入れ替わります。原因
参考サイト等を調べた結果、上の動きに間違いは無いのですが、DOM操作のタイミングがやや遅いことが原因と考えられます。
つまり、inputタグが表示されるより先に、this.$refs.focusThis.focus();
が実行されてしまうことで、フォーカスできないという問題が発生します。成功したコードの解説
JS側new Vue({ el: "#app", data: {inputField: false}, methods: { show() { this.inputField = true this.$nextTick(() => this.$refs.focusThis.focus()) }, }, })基本的には失敗したコードと同じですが、ここでは$nextTickというものを使っています。
以下に説明していきます。$nextTickとは
$nextTickは、Vue公式インスタンスメソッド(ライフサイクル)です。公式には小難しく書かれていますが、とても簡単です。
要は、this.$nextTick()
の引数の中に、関数の形で実行したいコードを入れているだけです。
(ちなみに、() => this.$refs.r.focus()
これはアロー関数の書き方です。その記事についてはこちら)
これによって、this.$refs.focusThis.focus()
はDOM操作が終わってから実行されるようになります。さいごに
今回は非同期処理的な実装によく使うであろう、$nextTickを中心に説明させて頂きつつ、サンプルコードの共有を致しました。
最後までありがとうございました。参考にしたもの
- 投稿日:2019-06-25T19:29:01+09:00
Array.mapが分からないからArray.mapもどきを作ってみた
業務中ワイ
ワイ「なあ、ハスケル子ちゃん」
ハスケル子「はい」
ワイ「今、ハスケル子ちゃんの書いたJSのコードを見てたんやけど」
ワイ「これどういう意味?」const nodes = document.querySelectorAll(".list > li"); const nodesArray = Array.from(nodes); const nodesHeightArray = nodesArray.map(getHeight);ハスケル子「えっと、うーん・・・」
ハスケル子「(見ての通りなんだけど、逆にどこが分からないんだろう・・・)」
ハスケル子「どこが分かりにくかったですか?」ワイ「この
nodesArray.map
いうメソッドは何なん?」ハスケル子「なんでそんなことが分からないんだろう・・・」
ワイ「お、おい・・・心の声が出てもうてるで・・・」
ハスケル子「(す、すいません!)」
ワイ「なんで謝罪は心の声になっとんねん」
ワイ「どんな忖度や」
ワイ「判断基準どうなってんねん」ハスケル子「ごめんなさい」
ハスケル子「お詫びにmap
メソッドについて説明しますね」
map
メソッドとはハスケル子「
map
メソッドは、配列が持ってるメソッド1です」
ハスケル子「ある配列を元に、新しく別の配列を作ることができるメソッドですね」
ハスケル子「さっきのコードでいうと───」const nodes = document.querySelectorAll(".list > li");ハスケル子「↑まず
querySelectorAll
で、いくつかのli
要素を取得して」
ハスケル子「それをnodes
とします」const nodesArray = Array.from(nodes);ハスケル子「↑次に
nodes
をArray.from
メソッドで配列に変換します」
ハスケル子「nodes
は配列みたいだけど配列じゃない2ですからね」
ハスケル子「そしてnodes
から作った配列nodesArray
の───」const nodesHeightArray = nodesArray.map(getHeight);ハスケル子「───
map
メソッドを実行します」
ハスケル子「nodesArray
の各要素に対して何らかの処理を施して」
ハスケル子「新しくnodesHeightArray
という配列を作る、って感じです」
ハスケル子「その何らかの処理というのを、関数として渡します」ワイ「なるほどな」
ワイ「getHeight
が関数なんやね」ハスケル子「はい」
const getHeight = element => element.clientHeight;ハスケル子「
getHeight
関数の中身は↑これです」ワイ「
element
という引数を1つ受け取ってelement.clientHeight
を返すということは」
ワイ「要素の高さを返す関数やな」ハスケル子「はい」
ハスケル子「さっきのコードを実行すると、最終的にnodesHeightArray
には」[24, 48, 72, 120, 24, 72, 48]ハスケル子「↑という配列が入ることになります」
ワイ「ほえ〜、DOM要素のリストを元に、各要素の高さを集めた配列を作れるんか」
ワイ「たまに便利そうやな」ワイの気になったこと
ワイ「でもハスケル子ちゃん」
const getHeight = element => element.clientHeight;ワイ「↑この
getHeight
の引数であるelement
はどこで渡してんの?」ハスケル子「どこで渡すっていうか、勝手に入ってくるんです」
ワイ「え・・・勝手に入ってくる・・・?」
ワイ「空き巣みたいに・・・?」ハスケル子「やめ太郎さんも、自分で
map
メソッドを実装してみたら分かりますよ」
ハスケル子「やってみましょう」Object.defineProperty(Array.prototype, "myMap", { value: function(callback) { /* ここに処理内容を書く */ } });ハスケル子「↑こうやって」
ハスケル子「Object.defineProperty
メソッドを使うことで」
ハスケル子「配列にmyMap
というメソッドを追加できるんです3」ワイ「おお、そんな機能があるんか」
ワイ「ほなちょっと挑戦してみるわ」
ワイ「でも・・・このcallback
いう引数はどこから入ってくんの」ハスケル子「だから・・・」
ハスケル子「その辺を理解するために今からコードを書いてもらうんです」
ハスケル子「再帰的な質問をしないでください」
ハスケル子「もうとりあえず空き巣が勝手に入ってくるとでも思っておいてください」ワイ「お、おう・・・」
ハスケル子「配列の
map
メソッドには、関数を1つ渡して実行しましたよね?」ワイ「ああ、さっきしてたな」
ハスケル子「そのとき
map
メソッドに渡した関数が」
ハスケル子「今から書く関数のcallback
という引数として入ってくる・・・」
ハスケル子「そんなイメージでmyMap
メソッドも実装してみてください」ワイ「なるほど、やってみるで」
Object.defineProperty(Array.prototype, "myMap", { value: function(callback) { const resultArray = []; // まず空の配列を作成 /* ここで配列に色々する */ return resultArray; // その配列を戻り値として返す } });ワイ「まずは↑こんな感じやな」
ワイ「空の配列を作って、なんか色々して、最終的にその配列を返す、と」ハスケル子「いいですね」
ワイ「ほんで、元々の配列の各要素に対して何らかの処理をしていけばいいんやな」
ワイ「そうか」
ワイ「その何らかの処理が、callback
として受け取った関数に入ってるというテイで考えればいいんやな」ハスケル子「そうです」
ハスケル子「ちなみに今書いてる関数の中ではthis
って書けばその配列自身にアクセスできますよ」ワイ「なるほど、
this
が配列自身やな」
ワイ「ほな、配列の分だけfor文
で回して───」for (let i = 0; i < this.length; i++) { const newElement = callback(this[i]); // i番目の要素にcallbackを適用。 resultArray.push(newElement); // それを「戻り値として返す配列」に追加。 } return resultArray;ワイ「↑こうやな!」
ワイ「
this[i]
、つまり配列のi
番目の要素に対してcallback
関数を実行して」
ワイ「それをnewElement
として配列にpush
する」
ワイ「そして出来上がった配列resultArray
を戻り値として返す」ハスケル子「いいですね!」
ワイ「よっしゃ、じゃあワイの
myMap
メソッド試してみるで!」const nodesHeightArray = nodesArray.myMap(getHeight); console.log(nodesHeightArray); // コンソール結果: [24, 48, 72, 120, 24, 72, 48]ワイ「おお、さっきの
map
メソッドと同じ結果や!」ハスケル子「いい感じです」
ハスケル子「実際のmap
メソッドはもっと複雑なアルゴリズムですけど」
ハスケル子「イメージ的はこんな感じです」ワイ「なるほどな〜」
ワイ「map
メソッドに対して渡したgetHeight
いう関数を」
ワイ「map
メソッドの中で、配列の各要素に対して実行してくれてたんやな」
ワイ「そのときにthis[i]
的な感じで」
ワイ「callback
関数の引数として、元の配列の各要素を渡してくれてたんやな」
ワイ「せやから引数が勝手に入って来てたんか〜」
ワイ「謎が解けましたわ〜」
ワイ「ありがとう、ハスケル子ちゃん」ハスケル子「実際の
map
メソッドは、callback
関数を実行するときにthis[i]
だけじゃなく」
ハスケル子「i
とthis
も渡してくれるので、さらに色々できますよ」ワイ「なるほどな」
ワイ「第二引数に今何番目の要素かを表すインデックス」
ワイ「第三引数には配列自身が入ってくると」
ワイ「工夫次第で色々できそうやな」
ワイ「array[i - 1]
で一つ前の要素を見たりとかな」ハスケル子「そうですそうです」
コールバック関数について
ワイ「今みたいな感じで、関数を渡して使うタイプの関数を」
ワイ「自分で作ってみてもオモロそうやな〜」
ワイ「例えば共通化できそうな処理があったとして」
ワイ「ほとんどの処理を関数として共通化できるけど」
ワイ「最後にちょっとだけ挙動を変えたい」
ワイ「しかも何パターンかの挙動があるから、引数で何かパラメータを受け取ってやる方法やと」
ワイ「if文が増えすぎて見通しが悪くなる・・・そんな時には」
ワイ「関数の最後にこの処理をやってくれい!」
ワイ「っていう処理の内容を、コールバック関数として受け取って」
ワイ「最後に実行してやればええんやな〜」
ワイ「そうすれば」
ワイ「同じ関数を使うけど、一部だけ自由に挙動を変えることが出来んねやな」ハスケル子「だんだん中級者っぽくなってきましたね」
ワイ「(いや、君よりだいぶJS歴長いねんけどな・・・)」
Object.defineProperty
についてワイ「あと、
Object.defineProperty
すごいな」ハスケル子「Vue.jsなんかも、この
Object.defineProperty
で」
ハスケル子「Array
のpush
メソッド等を上書きすることで」
ハスケル子「配列に要素を追加しただけでDOMが更新されるようにしてるんですよ」ワイ「せやろな〜(まじか!)」
ワイ「よっしゃ、ワイも明日から配列にメソッド追加しまくるで〜!」
ワイ「あ、メソッドの上書きもできるんやったな!」ハスケル子「レッツ、プロトタイプ汚染っ!」
社長「(ハスケル子ちゃん、そこはちゃんと止めてや・・・!)」
そうこうしてるうちに18時
ワイ「やば、ワイがコード読めへんせいでもう定時やないかい」
ワイ「コーディング全然進んでへん・・・」Riot兄さん「俺に任しとけ」
ワイ「あ、兄貴・・・!」
Riot兄さん「俺が界王拳2倍でコーディングして、なんとかしたるわ」
Riot兄さん「よっしゃ、今日から毎日7時間残業するで〜!」社長「おっ、兄貴の物理界王拳やな!」
ハスケル子「物理界王拳・・・?(兄貴、すごい・・・///)」
社長「せや」
社長「ただし物理界王拳は3倍までしか使えへん」
社長「24時間超えてしまうからな」ワイ「物理界王拳て」
ワイ「ただのブラック会社4やないかい!」〜おしまい〜
追記
ワイ「っていうかホンマの界王拳も物理やろ!」
- 投稿日:2019-06-25T18:46:39+09:00
[Vue]<li>をループで回してつくった箇条書きに、十字キーでフォーカスを合わせてる風の挙動をさせる(+enterキーでその要素を取得する)
前回の記事
前回の記事からの続きになります。
まだ読まれていない方は、こちらからどうぞ。今日のサンプルコード
See the Pen [Vue]ループ<li>のフォーカス移動+選択 by riotam (@riotam4) on CodePen.
サンプルコードのみですが、こちらです。
入力欄にフォーカスした状態で、下キーを押すと、その下の箇条書きのところを、フォーカスしているような挙動をします。
enterキーを押すと、ダイアログ画面でその要素を受け取り、出力します。
- 投稿日:2019-06-25T17:23:41+09:00
Netlify Functions を使って CORS エラーを回避する
はじめに
現在 Web アプリを Nuxt で開発していて、
nuxt generate
で静的コンテンツを作成し、Netlify にホスティングしている
そのアプリでは、別ドメインの API にリクエストを投げてデータを取得しているので、CORS ポリシーでブロックされないようにproxy-moduleを使って API と同一ドメインになるようにプロキシしているnuxt.config.jsmodules: ['@nuxtjs/axios', '@nuxtjs/proxy'], proxy: { '/atnd': { target: 'http://api.atnd.org/events', pathRewrite: { '^/atnd': '/' } }, '/connpass': { target: 'https://connpass.com/api/v1/event', pathRewrite: { '^/connpass': '/' } } }ただし、上記の proxy モジュールによる書き換えはサーバーが必要で、generate 機能を使って静的コンテンツをホスティングしている場合は機能しない
ちなみにローカルでは webpack-dev-server などを使って、Node サーバー上で動かしていれば、ちゃんとプロキシされるこの辺のことは以下に詳しく書いてある
https://github.com/nuxt-community/proxy-module/issues/1Nuxt の generate 機能を使っていても、ちゃんと別ドメインの API が叩けるようにするというのが今回の開発の目的です
作業リポジトリはこちら
https://github.com/kurosame/event-searchNetlify Functions を使う
ブラウザには別ドメインへの通信を拒否する仕組みが標準で備わっている
Nuxt のプロキシ機能はサーバーが無いと使えないし、API 側にオリジンを許可させることもできないこれらのことを踏まえて、今回は Netlify Functions を使って Lambda 関数内で API リクエストを実行することで CORS エラーを回避することにした
Netlify Functions の実行環境は AWS Lambda だが、AWS 側の設定は一切やらなくて良い
てか AWS アカウントも不要Netlify のアカウントがあれば使えるが、フリープランだと以下の制限がある
- 125000 リクエスト/月
- 100 時間/月
言語は JS と Go をサポートしている
(本記事では JS で実装している)ローカルで動作確認
以下のモジュールをインストール
yarn add -D netlify-lambda設定ファイルを用意する
netlify.toml[build] functions = "functions/dist"Lambda にやらせたい処理を書いて、handler 関数を export
functions/src/sample.tsexport async function handler(event, context) { return { statusCode: 200, body: 'Hello, World' } }package.json"scripts": { "lambda": "netlify-lambda serve functions/src" },yarn lambda以下のようにリクエストする
http://localhost:9000/sample
Functions から connpassAPI を叩いてみる
以下のコードが Functions 関数の実装
API の仕様上、100 件以上レスポンスが存在していても、MAX100 件しかデータが取れないので再帰関数にしているが、やっていることは API から取ってきたデータを body に JSON.stringify にして渡しているだけです
IConnpassEventResponse と IConnpassResponse の型インターフェースは後述してますyarn add -D @types/aws-lambdafunctions/src/connpass.tsimport { IConnpassEventResponse, IConnpassResponse } from '@/store/connpass' import axios, { AxiosResponse } from 'axios' import { APIGatewayProxyEvent } from 'aws-lambda' export async function handler(event: APIGatewayProxyEvent) { const ymd = (event.queryStringParameters || { period: '' }).period const count = 100 const getEvents = ( events: IConnpassEventResponse[] = [], start: number = 1 ): Promise<IConnpassEventResponse[]> => axios .get('https://connpass.com/api/v1/event', { params: { ymd, start, count } }) .then((res: AxiosResponse<IConnpassResponse>) => res.data.results_returned === count ? getEvents([...events, ...res.data.events], start + count) : [...events, ...res.data.events] ) const events: IConnpassEventResponse[] = await getEvents() return { statusCode: 200, body: JSON.stringify(events) } }TS で書いた場合は、以下を functions ディレクトリに追加
yarn add -D @babel/preset-typescriptfunctions/.babelrc{ "presets": ["@babel/preset-typescript", "@babel/preset-env"], "plugins": [["@babel/plugin-transform-runtime", { "regenerator": true }]] }基本的に presets だけでコンパイルエラーは回避できると思うが、plugins はエラーになったらそのエラー名で検索するとたぶん必要なプラグインが分かると思うので、必要に応じて設定する
作った後に気づいたけど、ここに書いてたyarn lambda正常に起動したら、以下にアクセス
http://localhost:9000/connpass
たぶん Lambda 側でタイムアウトする(デフォルト 10 秒)ので、日付で絞るかタイムアウト時間を伸ばすかしてみてください
http://localhost:9000/connpass?period=20190624
or
yarn lambda -t 20
Nuxt から Functions にリクエストする
Vuex のアクションで axios を使って Functions にリクエストしている
?period=${period}
の書き方について
⇒axios.get
の param オプションを使うと、Functions 側の event 引数に入らなかったので、直接 URL にパラメータを書いているstore/connpass.tsimport { IEventState } from '@/store/events' export interface IConnpassEventResponse { title: string catch: string description: string event_url: string started_at: string ended_at: string limit: number address: string place: string } export interface IConnpassResponse { results_returned: number events: IConnpassEventResponse[] } export const actions = { async getConnpassEvents({ commit }, period: string) { const events: IConnpassEventResponse[] = await (this as any).$axios.$get( `/connpass?period=${period}` ) // Functionsのレスポンスを色々加工してStoreに入れている // (ここから以下は本記事の目的とは関係ない部分) commit( 'events/setEvents', events .filter((e: IConnpassEventResponse) => e.limit >= 30) .map( (e: IConnpassEventResponse) => ({ title: e.title, catch: e.catch, description: e.description, eventUrl: e.event_url, startedAt: (this as any) .$moment(e.started_at) .format('YYYY-MM-DD HH:mm:ss (ddd)'), endedAt: (this as any) .$moment(e.ended_at) .format('YYYY-MM-DD HH:mm:ss (ddd)'), address: `${e.address} ${e.place}` } as IEventState) ), { root: true } ) } }ローカルで netlify-lambda を使って起動するとポート 9000 で実行される
Nuxt はポート 3000 で起動しているので、以下のようにプロキシしないと CORS に引っかかるNetlify にホスティングして動かす場合は、Nuxt と Functions は同じドメイン上で動くので、プロキシは不要
(そうじゃないと今回 Functions を使った意味がないので、、)nuxt.config.jsproxy: { '/connpass': { target: 'http://localhost:9000' } }最後にアクションを Dispatch する
components/Summary.vuethis.$store.dispatch('connpass/getConnpassEvents', '20190624,20190625')動作確認
ローカルで Lambda を起動して、Nuxt でアクションを Dispatch するyarn lambda -t 100ホスティングして動作確認
netlify.toml がルートディレクトリに存在する場合、Netlify の管理画面上の設定は無視される
その場合、netlify.toml にデプロイ設定を書く必要があるnetlify.toml[build] command = "yarn generate" functions = "functions/dist" publish = "dist"Netlify にホスティングすると、Functions は
https://[Site name].netlify.com/.netlify/functions
というパスになるので、axios の baseURL を Functions の URL にするnuxt.config.jsaxios: { baseURL: '/.netlify/functions' }, proxy: { '/.netlify/functions/connpass': { target: 'http://localhost:9000' } }ちなみに netlify-lambda を使ってローカルでビルドした場合は
http://localhost:9000
とhttp://localhost:9000/.netlify/functions
のどちらでも良いので、上記の proxy 設定でアクセスできるただし、axios の baseURL を Functions に固定するのは後々不便になるかもしれない
その場合は、環境変数を使って対応した方がいいかも後は Netlify 上で GitHub リポジトリを紐づけて、その GitHub リポジトリのブランチに push すればホスティングされて動作するはずです
- 投稿日:2019-06-25T16:48:05+09:00
Nuxtでビルド時にAPIを静的化して、完全にサーバーへのリクエストをなくすト
ビルド時にAPIを静的化するジェネレータを自作しましたので、ご紹介です。(コード有りです。)
Nuxtのジェネレータを自作中...。
— 新田聡一郎 (@soichiro_nitta) 2019年2月11日
・Airtableでマークダウン
・Nuxtビルド時にJSON生成
・link rel="prefetch"にjson追加
・ルート生成
・コンポーネントからは/_nuxt/xxx.jsonにリクエスト
って感じで出来たんだけど、爆速なんだが! pic.twitter.com/DvJL5hucGq最近はやりのヘッドレスCMSをやっていこうと思ったのですが、APIのリクエスト制限きびしいんですよね...。
現状だとNuxtは静的generateモードでも、ページ遷移時にリクエストが発生してしまうので、完全に静的化してしまう必要がありました。
以下のような流れになります。
- Nuxtビルド時にJSONを生成
<link rel="prefetch">
にJSONを追加- ルート生成
- コンポーネントからは
/_nuxt/articles/xxx.json
にリクエスト(
<link rel="prefetch">
の指定で、生成したJSONは前もって準備するようにしています。)ジェネレータの中身は以下。
modules/generator.jsimport axios from 'axios' module.exports = function generateModule(moduleOptions) { this.nuxt.hook('build:before', async ({ app }) => { // 全データを取得 const { data } = await axios.get('/hogehoge') const posts = data // JSONを生成 this.options.build.plugins.push({ apply(compiler) { compiler.plugin('emit', (compilation, cb) => { posts.forEach(post => { compilation.assets[`articles/${post.slug}.json`] = { source: () => JSON.stringify(post), size: () => {} } }) cb() }) } }) // link rel="prefetch"にJSONを追加 const url = this.options.dev ? '' : 'https://xxx' this.options.head.link = [ ...this.options.head.link, ...posts.map(post => ({ rel: 'prefetch', href: `${url}/_nuxt/articles/${post.slug}.json` })) ] if (this.options.dev) return // ルート生成 this.options.generate.routes = posts.map(post => `/${post.slug}`) }) }こんな感じにモジュールを書いて、
nuxt.config.js
で設定します。nuxt.config.jsmodule.exports = { // ジェネレータ内で開発orプロダクションの書き分けに使用しています dev: process.env.NODE_ENV !== 'production', // モジュールを指定 modules: ['~/modules/generator'], ... }コンポーネントからは以下のようにデータを取得します。
pages/_id.vue<script> export default { async asyncData({ app, params }) { const url = process.env.NODE_ENV === 'development' ? '' : 'https://xxx' const { data } = await app.$axios.get( `${url}/_nuxt/articles/${params.id}.json` ) return { article: data } } } </script>これで表示・遷移時ともにサーバーへデータ取得のリクエストを送らずにすむようになりました?
こちらの情報を参考にさせていただきました。
Firebase、Flamelink、Nuxt、Netlify、PWAを使ってJAMstackなブログを作る
- 投稿日:2019-06-25T16:24:23+09:00
Vue.jsとExpressを使って画像アップロードをしてみる
はじめまして、PMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思いますはじめに
Vue.jsからリクエストを投げてExpressのpublicディレクトリに画像をアップロードしてみたいとおもいます。
今回画像アップロードにはmulterというライブラリを使って行きます。実装
使用するライブラリのインストール
- axios
npm install axios -s
Vueプロジェクトの中でinstallしてください- multer
npm install multer -s
Expressプロジェクトの中でinstallしてくださいVueの実装
まず一つvueファイルを作ってください
Uplord.vue<template> <div> <form> <input type="file" id="file" v-on:change="onFileChange"> <input type="submit" value="decide" @click="submitClick"> </form> </div> </template> <script> import Axios from "axios"; export default { data() { return { imageFile: null }; }, methods: { //画像が選択されたとき呼ばれる onFileChange(e) { this.imageFile = e.target.files || e.dataTransfer.files; }, //submitされたときに呼ばれる async submitClick() { try { const formData = new FormData(); formData.append("file", this.imageFile[0]); const config = { headers: { "content-type": "multipart/form-data", } }; let res = await Axios.post("/image", formData, config); console.log(res); if (res.data.status === "error") { alert(res.data.error); } else { alert("登録完了") } } catch (error) { alert("画像の送信に失敗しました"); } } } } </script>
- ここではformDataを使って画像をpostしています。
formData.append("key",value)
を使うことでkey:valueを対応させる形で任意のデータを乗せることが出来ます。注意点
- valueにはオブジェクトなどは乗せることが出来ません。基本的にkeyとvalueを一つずつ載せてください。
- formDataに載せたvalueはStingになってしまいます。
Expressの実装
src/index.jsに以下を書いていきましょう
index.jsconst express = require('express') const multer = require('multer'); const app = express() const storage = multer.diskStorage({ // ファイルの保存先を指定(今回はsrc/public/image以下に画像を保存) destination: function (req, file, cb) { cb(null, 'src/public/image') }, // ファイル名を指定(オリジナルのファイル名を指定) filename: function (req, file, cb) { // Math.random().toString(36).slice(-9)で乱数を生成 const imageName = `${Math.random().toString(36).slice(-9)}_${Date.now()}.png` cb(null, imageName) } }) const upload = multer({ storage: storage }).single('file') app.post('/image', (req, res) => { upload(req, res, (err) => { if (err) { //アップロード失敗した場合 res.json({ status: "error", error: "fail to uplord image" }) } else { //アップロード成功した場合 res.json({ status: "sucess", // ファイル名を返す path: res.req.file.filename }) } }) });
const storage
で画像の保存先をファイル名を指定- ここではファイル名を乱数と時間を組み合わせていますがこれではユニークとは言えません。ユニークにしたい方はuserIdと時間を組み合わせるなどしてください。
/image
のエンドポイントでuplordを呼び出しています。ただアップロードするだけでしたらもっと簡単に出来ます。(詳しくはこちら)しかしここではアップロード出来たかどうかのエラーハンドリングを行いたいのでこのような形になっています。- アップロードされた画像のファイル名は
res.req.file.filename
で取得する事ができます。- 無事フロントで
res.data.status
がsucsess
になっていれば登録完了です。最後に
multerとaxiosでお手軽にVueからExpressに画像をアップロードすることが出来ました。
機会があれば是非試してみてください
なにか間違いなどありましたら教えていただけると幸いです。
- 投稿日:2019-06-25T15:51:45+09:00
Vue.jsでオブジェクトのネストしたプロパティーにアクセスできないとき
以下のようなオブジェクトがありまして、この中のmembersの値を取得したかったのですが、、、
var data = { [50]: { members: { 0: { id: "4", name: "谷裏" }, 1: { is: "5", name: "眉村" }, length: 2 }, facilities: "プール" }, }通常であれば以下のようなコードで取得できるはずなのですが
console.log(data[50].members.length);一向に取れず、、、
自分の書き方がおかしいのか?っと調べてみてもあっている様子、、、
いちいちビルドし直すと時間がかかるので、ブラウザ上で簡単にコードを試したりすることができるでコードがあっているか試してみたところ、間違っていない様子、、、
っと思って改めて調べたところ
以下の記事と同じ現象であることが確認できました
https://codeday.me/jp/qa/20190403/539522.html[ネストしたプロパテイーにアクセスする方法]
この記事に書いてあることは、データのロードが完了するのを待ちなさいとのことでした。
データを取得している場所にフラグのようなものを立てて、ロード完了後表示してデータを取得しなさいということでした!
- 投稿日:2019-06-25T11:30:10+09:00
Vueで複数条件(and,or,フリーワード)の絞り込みを実装する
まずはサンプル
https://kackie.github.io/sort/vue.html
こちらの記事で作ったお店のリストを改良しましたw
今回は管理するデータがより複雑になったので、データはjson形式でまとめてaxiosで読み込んでいます。絞り込みの条件
グループ化されたカテゴリがあり、同グループ内を複数選択した場合はor(和食||洋食)、グループを跨いで選択した場合はand(和食||洋食&&肉)での絞り込みになります。
またフリーワード検索を作り、これもフリーワード&&選択したカテゴリというふうにandで絞り込まれます。jQuery版との比較
データバインディングの面でVueが圧倒的に優れています。さすが。
そのおかげでjsのソースもシンプルに書くことができました。
必然的にオブジェクト指向に則って書いていくので、ソースが整理されて見やすくわかりやすいです。
またVueのtransitionクラスを利用して切り替えのインタラクションを簡単に作ることができました。今後実装したいもの
- URLにパラメータ付与
jQuery版にあったやつ。管理する情報が増えたので書き方を新しく考えないと..。- 時間による絞り込み
開店時間で絞り込んだり、早い順でソートしたりできればよいかなと。
インターフェースはプルダウン?- json更新のシステム
手動でもいいけど、PHP等の勉強用に作ってみたい。- 複数キーワードでの検索
今は単一キーワードでの絞り込みしかできないので、スペース区切りで複数入力された場合の処理が必要。
- その場合はand検索のほうが良いか?
- 非表示時のインタラクション
これはどうしようもないかもしれないけど、非表示のものが左上に集約されていくのがちょっと気持ち悪いw
おそらくflex配下の要素にposition:absolute;
を指定すると親要素基準で左上に移動してしまうせい(初めて知った)。
flexをやめると高さ揃えをjsでやることになるしうーん。感想
jsonを手動で作成し管理するなんてことを初めてやりましたが、このインターフェースを組み合わせれば色々できそうですね。
何かの実績を整理してみたり、読んだ本や見た映画をまとめてみたり、料理のレシピを材料やジャンル別でカテゴライズしてみたり...。
思いついたら自分用にでも使ってみようと思います。
- 投稿日:2019-06-25T07:37:30+09:00
JestでVueコンポーネントとVuexストアの単体テストを書いてみよう!
概要
この記事は、Ginza.js#2にて登壇した、
「Jestを使って VueコンポーネントとVuexストアのテストコードを書いてみよう!」
のLTのスライド内容を元に記事を作成しました。ソースコードは以下で公開しています。
https://github.com/karamage/vue_jest_test動作環境
・Mac OS 10.14.4
・Nuxt v2.8.1
・Vue v2.6.10
・Node v10.15.3この記事で説明すること
- Jestとは?
- Jestのインストール
- Jestの基本的なテストの書き方
- Vueコンポーネントの単体テスト
- Vuexストアの単体テスト
Jestとは?
- Facebook製のJavaScriptテストプラットフォーム
- JavaScriptテスト界のシェア率ナンバー1
Jestの特徴
- ブラウザの起動がないぶん軽快に動く(DOMのエミュ)
- スナップショットテスト(仮想DOMをJSONでダンプして差分比較)ができる
- テストに必要な機能全部入りで楽(テストランナー、アサーション、モック、カバレッジ)
- 設定一つでカバレッジを簡単に取得できる
- ウォッチでファイル変更時に依存関係のあるテストだけ走る。賢い。
便利すぎて使わない理由がない!
vue-test-utils
- vueのテスト時に便利なUtil
- vueコンポーネントのmount処理してくれる
Jest と vue-test-utils インストール
$ yarn add --dev jest vue-jest babel-jest @vue/test- utils babel-preset-vue-appまずはyarnかnpmでパッケージをインストールしましょう
Jest 設定ファイル記述
package.json"scripts": { ..., "test": "jest" },.babelrc{ "env": { "test": { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] } } }jest.config.jsmodule.exports = { moduleNameMapper: { '^@/(.*)$': '<rootDir>/$1', '^~/(.*)$': '<rootDir>/$1', '^vue$': 'vue/dist/vue.common.js' }, moduleFileExtensions: ['js', 'vue', 'json'], transform: { '^.+\\.js$': 'babel-jest', '.*\\.(vue)$': 'vue-jest', }, "collectCoverage": true, "collectCoverageFrom": [ "<rootDir>/components/**/*.vue", "<rootDir>/pages/**/*.vue" ] }Jest 実行!
$ yarn testしかし、Bebelのエラーがでて動かない。。。
Cannot find module 'babel-core' が出てしまう。泣きそうになる
babel-coreのバージョンを調整したらイケた!
$ yarn add --dev babel-jest babel-core@^7.0.0-0 @babel/core以下のisuueを参考にした
https://github.com/vuejs/vue-jest/issues/160
https://github.com/facebook/jest/issues/5525
Jestでテストコードを書いてみよう
足し算の関数があるとして
logic/sum.jsexport function sum(x, y) { return x + y }テストコードは以下のように書く
test/sum.test.jsimport { sum } from "@/logic/sum" test("1 + 2 = 3", () => { expect(sum(1, 2)).toBe(3) })Jestの基本
- test(name, fn)で単一のテストを表す。
- it(name, fn)でも同じ意味。rspec的
- expect(value) でテスト対象の値を入れる
- toBe(value) で結果の値の検証を行う
Vueコンポーネントのテストを書いてみよう(Vuex使わない場合)
テスト対象のVueコンポーネント
components/Counter.vue<template> <div> <span class="count">{{ count }}</span> <button @click="increment">Increment</button> </div> </template> <script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } } </script>
カウンターコンポーネント(ボタンを押すとカウントアップする)Vueコンポーネントのテストコード
test/Counter.spec.jsdescribe('Counter', () => { // コンポーネントがマウントされ、ラッパが作成されます。 const wrapper = mount(Counter) it('renders the correct markup', () => { expect(wrapper.html()).toContain('<span class="count">0</span>') }) // 要素の存在を確認することも簡単です it('has a button', () => { expect(wrapper.contains('.count')).toBe(true) }) // ボタンを押してカウントアップするテスト it('button click should increment the count', () => { expect(wrapper.vm.count).toBe(0) const button = wrapper.find('button') button.trigger('click') expect(wrapper.vm.count).toBe(1) }) })
- mount()でテスト対象のVueコンポーネントをマウントしてラッパーを作成する
- wrapper.html()でhtmlの中身を文字列としてチェックできる
- wrapper.contains()でCSSセレクタを使って要素の存在を確認できる。しかし、CSSに依存したテストを書くのはよくない。data-test属性を使ったほうが良い
- Vueコンポーネントのdataは、wrapper.vm.countで確認できる。ボタンをクリックした際にcountが1アップしているのを確認する
VueコンポーネントとVuexストアの単体テストを書こう
テスト対象
Vuexストア(テスト対象)
store/count.jsexport const state = () => ({ count: 0 }) export const mutations = { setCount: (state, { count }) => state.count = count } export const getters = { count: state => state.count, } export const actions = { async increment({ commit, state }, {}) { commit("setCount", { count: state.count + 1 }) }, }Vueコンポーネント(テスト対象)
components/CounterVuex.vue<template> <div> <span class="count">{{ count }}</span> <button @click="increment">Increment</button> </div> </template> <script> import { mapGetters, mapActions } from "vuex" export default { computed: { ...mapGetters("count", ["count"]) }, methods: { ...mapActions("count", ["increment"]) } } </script>テストコード
Vuexストアの単体テスト
test/store/count.spec.jsimport Vuex from 'vuex' import * as count from '@/store/count' import { createLocalVue } from '@vue/test-utils' const localVue = createLocalVue() localVue.use(Vuex) let action const testedAction = (context = {}, payload = {}) => { return count.actions[action](context, payload) } describe('store/count.js', () => { let store beforeEach(() => { store = new Vuex.Store(count) }) describe('getters', () => { test('countの値を取得', () => { store.replaceState({ count: 3 }) expect(store.getters['count']).toBe(3) }) }) describe('actions', () => { let commit let state beforeEach(() => { commit = store.commit state = store.state }) test('increment', async done => { action = "increment" await testedAction({ commit, state }) expect(store.getters['count']).toBe(1) await testedAction({ commit, state }) expect(store.getters['count']).toBe(2) done() }) }) })
- 他のテストに影響を与えないようにcreateLocalVue()を使う
- replaceStateでstateを置き換えたときにgetterで取得できているか確認する
- vuexのactionは非同期になるので、非同期のテストは async done { で書き始めて、最後にdone()を呼ぶのを忘れないようにする
Vueコンポーネントの単体テスト
test/CounterVuex.spec.jsimport { shallowMount, createLocalVue } from '@vue/test-utils' import Vuex from 'vuex' import CounterVuex from '@/components/CounterVuex' const localVue = createLocalVue() localVue.use(Vuex) describe('CounterVuex.vue', () => { let store let countStoreMock let wrapper beforeEach(() => { //Vuexストアのモックを作成する countStoreMock = { namespaced: true, actions : { increment: jest.fn(), }, getters : { count: () => 0, }, } store = new Vuex.Store({ modules: { count:countStoreMock } }) // shallowMountだと子コンポーネントをスタブによって描画しなくなる(高速化) wrapper = shallowMount(CounterVuex, { store, localVue }) }) it('renders the correct markup', () => { expect(wrapper.html()).toContain('<span class="count">0</span>') }) // 要素の存在を確認することも簡単です it('has a count label', () => { expect(wrapper.contains('.count')).toBe(true) }) // ボタンを押してinclementが呼び出されているかテスト it('button click should increment call', () => { expect(countStoreMock.actions.increment).not.toBeCalled() const button = wrapper.find('button') button.trigger('click') expect(countStoreMock.actions.increment).toBeCalled() }) })
- コンポーネントの単体テストなので、VuexのストアはMock化する
- ボタンをタップしたときactionが呼び出されているかを確認する(実際にカウントアップしているかどうかはストア側の責務のためここでは確認しない)
まとめ
- Jest はいいぞ
- ストアとコンポーネントは責務を切り分けて 単体テストしよう
- 投稿日:2019-06-25T03:41:56+09:00
キョリカンというサービスを作った話。
こんにちは、かけるです。本業はデザイナーなのですが、いろいろサービスのアイデアを考えたりするのが好きで自分で作れるようになりたいと思いプログラミングを始めました。もっとスタートアップ界隈のかたとつながりたいのでtwitterフォローお願いします!仕事ください。。。。
と、余談は後にして。本題です。今回は、プログラミングを始めて作ったサービスの話をしてもいいですか。Vue.jsとfirebaseで作りました。cloud functionの使い方がいまいちつかめていないのとvuexなど全然使いこなせていないですが、何とか作ることができました。
作るなら、誰かのためになるものを
やはり何か作るなら使ってもらいたいので、誰かのためになるものを創ろうと思いました。でも、これがまた難しいんですよね、どこに課題があるのか見つけるのが難しい。それこそ、課題なんかないじゃないかと思うくらい僕には何も見えていなかったんです。その課題探しに没頭すること数日、、、
ついに見つけることができたんです。それは、僕が体験していたけど自ら「しょうがないこと」と封印していたことでした。その問題というのは、企業と採用応募者との距離感です。就活をしていて、どうも変な感じがしていたんです。もっとお互いのこと知るべきなのに形式ばったり、企業という顔が前面に出すぎていて本心が言いずらいと思っていました。緊張もしましたし。もっと面接までのやり取りをラフな感じにすれば、このような緊張感などは起きないのかなと思いたわけです。人材業界の市場規模と問題
人材業界の市場規模は、約9兆円で電子部品・デバイスや医薬品市場よりも多いです。ということは、人材業界より低い市場は人手不足という事ではないでしょうか。専門性が高まるにつれて、人材不足数が大きくなっていきます。そうすると専門職の給料は高騰し儲かるというわけですが、そんな大金到底払えないので、インターンやアルバイト、派遣社員雇って安く済ませているという事だと思います。そして、それが嫌な人はフリーランスとして働き始めて、企業ではどんどん人手が少なくなっていくのかなと考えています。間違っているかもしれないですが、、、
そして、人手不足を抑える方法の一つに退職率を減らすことがあげられます。それは、新卒や転職者と企業とのミスマッチを減らすという事です。こんかい、私が問題だと思った事とつながってきます。本心や求めているものを引き出し、ミスマッチを減らすにはやはり距離感が必要になってくると思うのです。企業と採用応募者との距離感
これは、企業と採用応募者の距離が遠いほど採用者のリラックス度や密なコミュニケーションをすることができる可能性が減っていく図です。リクナビやその他の就活サービスは「企業」という顔が前面に出ていてどうしても、応募者側に緊張感を与えてしまいます。一方Wantedlyというサービスは、企業という面を持ちながらも、その内部の人とチャットができるという面でよりリラックスさせることができると考えられます。
採用応募者のリラックス度が高ければ高いほどより密なコミュニケーションが取れるので本心や真の実力を知ることができると考えられます。
これは、私の予測ですが「Wantedly」を使うベンチャー企業が多いのはこの密なコミュニケーションによってより良い人材をとることができると感じているからではないでしょうか。後日アンケートを取ってみたいと思います。より密なコミュニケーションを実現するために
より高い数値を出すにはどのようなサービスが良いのか。そこで考えたのは、プラットフォームに内包したサービスです。私が、「これだ!」と思ったのは「というサービスでした。ツイッターやFacebookでイベントや人材の募集ができるサービスで、僕は最初このサービスがバズったのは、はやりのOGPを利用したサービスだったからだと思っていました。でもそれだけじゃないと気付いたんです。「bosyu」でイベントや人材を募集する人たちは個人なんです。そう、個人が募集を共有するところはツイッター、Facebookという開けたソーシャルネットワークなため、よりラフに応募することができそのやり取りもそこで進んでいきます。企業という顔が前面に出ず個人が前面に出る事が密なコミュニケーションをとることには重要だと思っていたので、「これなら密なコミュニケーションが実現できる。」と思った瞬間でした。また、最近「ツイッター採用」という言葉を耳にする事や、そこで仕事を受注する人を良く見るようになったのでイケるかもと思い今回「キョリカン」というサービス作成に至りました。
キョリカンというサービス
https://kyorikan.tk
これがトップページです。キャッチフレーズに関してですが、サービス名である「キョリカン」と「距離感」をかけて、創り出してほしいものは信頼だという事を伝えたくこのようなキャッチフレーズにしました。何かいいキャッチフレーズある人募集します。。。ツイッターに内包されたサービスですので、サインインはツイッターアカウントのみです。使い方
これが一連の流れです。募集要項を作ってそして確認し、ツイッターに共有するだけです。そしてツイッターできになった人が共有されたリンクに飛び、詳細ページで募集要項確認後、興味があればツイッターで興味を知らせるボタンを押します。すると作成者に共有された通知がツイッターで届くのでそこからやり取りが始まります。このメカニズムは、募集要項作成時にツイッターIDを記入する欄があり、そこに記入することによって通知が来るようになっています。ツイッターAPIでIDを自動取得しようと思ったのですが、よくわからなかったので、今の形で作りました。今後改善していくつもりです。OGPで作成される画像と詳細で見る画像が違うのは、少しでも会社のことを詳細画面で表せたらなと思ったためです。
使い方はこれくらいです。最後に
誰かの役に立つものを創ろうとしたと書きましたが、本当は自分のためで、僕が就職活動をしていていやだなと思ったことを解決したくて作りました。もし、この問題を分かち合える採用側、採用者側がいれば使っていただきたいです。大きな問題より小さな問題を解決するのが案外難しかったりするんです。また、見つけにくいものなんです。
- 投稿日:2019-06-25T03:11:49+09:00
[Docker] Vueでaxiosを使うときにbaseurlを設定する方法
はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思いますはじめに
フロントエンドとapiをdockerで別々のサーバーとして立ち上げているときに、フロントからいちいちエンドポイントを指定するのが面倒だったので、いろいろ調べたmemo
何をするのか
vueからaxiosを用いてapiリクエストを行う際、いちいちURLを指定するのは面倒。
sample.jsimport Axios from 'axios' async getAmount() { try { const res = await Axios.get(`http://localhost:8081/hoge`); console.log(res.data); } catch (error) { console.log(error); } }このように毎回
http://localhost:8081/hoge
というようにurlを指定するのは面倒
できれば/hoge
というように簡潔にエンドポイントを指定したい解決策
まず
docker-compose.yml
でvueプロジェクトのdockerイメージ以下のenvironment
にVUE_APP_API_ENDPOINT: http://http://localhost:任意のport番号
を追加するdocker-compose.ymlservices: web: ... environment: VUE_APP_API_ENDPOINT: http://localhost:8081 //任意のport番号を指定 ...vueプロジェクト内の
main.js
に以下を追記main.jsimport Axios from 'axios' Axios.defaults.baseURL = process.env.VUE_APP_API_ENDPOINT;これで以下のように簡潔に書けるようになります
sample.jsimport Axios from 'axios' async getAmount() { try { const res = await Axios.get(`/hoge`); console.log(res.data); } catch (error) { console.log(error); } }最後に
エンドポイントへのリクエストが増えれば増えるほどURLの指定は面倒になってくるので、よければ試してみてください。
なにか間違いなどありましたら指摘していただけると幸いです。
- 投稿日:2019-06-25T02:04:13+09:00
v8nを使ってvue.jsでバリデーションをかけてみた
はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います何をするのか
v8nというnode moduleを使用してvue.jsの入力フォームにバリデーションをかけていきたいと思います。
vue.jsの一般的なバリデーションライブラリとしてはvee-validateなどがありますが、
v8nは簡単にバリデーションをかけたいときやバリデーションのカスタマイズを行いたいときに便利です。使い方
install(vue project)
npm install v8n -s
Sample code
sample.vue<template> <form action> <input type="text" v-model="email"> <input type="password" v-model="password"> <input type="text" v-model="email"> <input type="submit" value="decide" @click="onClickSubmitButton"> </form> </template> <script> import v8n from "v8n"; export default { data() { return { email: null, password: null }; }, methods: { onClickSubmitButton() { const message = { null: "中身が空です", minLength: "文字数は4文字以上です", pattern: "emailを入力してください", maxLength: "文字数が多すぎます" }; try { v8n() .not.null() // 値がnullじゃないか .string() // 文字列 .minLength(4) // a@b.c を想定して最低5文字 .pattern(/[^\s@]+@[^\s@]+\.[^\s@]+/) .check(this.email); } catch (ex) { let rule = ex.rule.name; alert(`Emailに関して${message[rule]}`); return; } try { v8n() .not.null() .string() .minLength(4) .check(this.password); } catch (error) { let rule = error.rule.name; alert(`passwordに関して${message[rule]}`); return; } } } }; </script>説明
- まずv8nをimport
import v8n from "v8n";
v8n()
に様々な設定を加えていくことで様々なバリデーションを作ることが出来ます。
code rule null() nullかどうかを判定する not.null() null出ないかどうかを判定する string() stringかどうかを判定する minLength(4) 4文字以上かどうかを判定する pattern(/[^\s@]+@[^\s@]+.[^\s@]+/) patternに一致しているかどうかを判定する(今回はemailのpattern maching)
- 他にも様々なruleをつけることが出来ます(詳しくはこちらにまとまっていました)
- 加えた条件に対して
.check(変数名)
を行うことで任意の変数が自分の設定したruleに一致しているか判定することができます- あとはpromiseなりtry catchなりでerrorを受け取り、エラーハンドリングを行ってください
- errorの場合どのruleによってerrorになったかは
error.rule.name;
などで受け取ることが出来ます。参考にしたもの
最後に
ざっくりとv8nの使い方について説明しました。
初めての投稿なので間違いなどあれば指摘していただけると幸いです。
- 投稿日:2019-06-25T02:04:13+09:00
v8nを使ってVueでバリデーションをかけてみた
はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います何をするのか
v8nというnode moduleを使用してvue.jsの入力フォームにバリデーションをかけていきたいと思います。
vue.jsの一般的なバリデーションライブラリとしてはvee-validateなどがありますが、
v8nは簡単にバリデーションをかけたいときやバリデーションのカスタマイズを行いたいときに便利です。使い方
install(vue project)
npm install v8n -s
Sample code
sample.vue<template> <form action> <input type="text" v-model="email"> <input type="password" v-model="password"> <input type="text" v-model="email"> <input type="submit" value="decide" @click="onClickSubmitButton"> </form> </template> <script> import v8n from "v8n"; export default { data() { return { email: null, password: null }; }, methods: { onClickSubmitButton() { const message = { null: "中身が空です", minLength: "文字数は4文字以上です", pattern: "emailを入力してください", maxLength: "文字数が多すぎます" }; try { v8n() .not.null() // 値がnullじゃないか .string() // 文字列 .minLength(4) // a@b.c を想定して最低5文字 .pattern(/[^\s@]+@[^\s@]+\.[^\s@]+/) .check(this.email); } catch (ex) { let rule = ex.rule.name; alert(`Emailに関して${message[rule]}`); return; } try { v8n() .not.null() .string() .minLength(4) .check(this.password); } catch (error) { let rule = error.rule.name; alert(`passwordに関して${message[rule]}`); return; } } } }; </script>説明
- まずv8nをimport
import v8n from "v8n";
v8n()
に様々な設定を加えていくことで様々なバリデーションを作ることが出来ます。
code rule null() nullかどうかを判定する not.null() null出ないかどうかを判定する string() stringかどうかを判定する minLength(4) 4文字以上かどうかを判定する pattern(/[^\s@]+@[^\s@]+.[^\s@]+/) patternに一致しているかどうかを判定する(今回はemailのpattern maching)
- 他にも様々なruleをつけることが出来ます(詳しくはこちらにまとまっていました)
- 加えた条件に対して
.check(変数名)
を行うことで任意の変数が自分の設定したruleに一致しているか判定することができます- あとはpromiseなりtry catchなりでerrorを受け取り、エラーハンドリングを行ってください
- errorの場合どのruleによってerrorになったかは
error.rule.name;
などで受け取ることが出来ます。参考にしたもの
最後に
ざっくりとv8nの使い方について説明しました。
初めての投稿なので間違いなどあれば指摘していただけると幸いです。
- 投稿日:2019-06-25T02:04:13+09:00
v8nを使ってVue.jsでバリデーションをかけてみた
はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います何をするのか
v8nというnode moduleを使用してvue.jsの入力フォームにバリデーションをかけていきたいと思います。
vue.jsの一般的なバリデーションライブラリとしてはvee-validateなどがありますが、
v8nは簡単にバリデーションをかけたいときやバリデーションのカスタマイズを行いたいときに便利です。使い方
install(vue project)
npm install v8n -s
Sample code
sample.vue<template> <form action> <input type="text" v-model="email"> <input type="password" v-model="password"> <input type="text" v-model="email"> <input type="submit" value="decide" @click="onClickSubmitButton"> </form> </template> <script> import v8n from "v8n"; export default { data() { return { email: null, password: null }; }, methods: { onClickSubmitButton() { const message = { null: "中身が空です", minLength: "文字数は4文字以上です", pattern: "emailを入力してください", maxLength: "文字数が多すぎます" }; try { v8n() .not.null() // 値がnullじゃないか .string() // 文字列 .minLength(4) // a@b.c を想定して最低5文字 .pattern(/[^\s@]+@[^\s@]+\.[^\s@]+/) .check(this.email); } catch (ex) { let rule = ex.rule.name; alert(`Emailに関して${message[rule]}`); return; } try { v8n() .not.null() .string() .minLength(4) .check(this.password); } catch (error) { let rule = error.rule.name; alert(`passwordに関して${message[rule]}`); return; } } } }; </script>説明
- まずv8nをimport
import v8n from "v8n";
v8n()
に様々な設定を加えていくことで様々なバリデーションを作ることが出来ます。
code rule null() nullかどうかを判定する not.null() null出ないかどうかを判定する string() stringかどうかを判定する minLength(4) 4文字以上かどうかを判定する pattern(/[^\s@]+@[^\s@]+.[^\s@]+/) patternに一致しているかどうかを判定する(今回はemailのpattern maching)
- 他にも様々なruleをつけることが出来ます(詳しくはこちらにまとまっていました)
- 加えた条件に対して
.check(変数名)
を行うことで任意の変数が自分の設定したruleに一致しているか判定することができます- あとはpromiseなりtry catchなりでerrorを受け取り、エラーハンドリングを行ってください
- errorの場合どのruleによってerrorになったかは
error.rule.name;
などで受け取ることが出来ます。参考にしたもの
最後に
ざっくりとv8nの使い方について説明しました。
初めての投稿なので間違いなどあれば指摘していただけると幸いです。
- 投稿日:2019-06-25T00:32:01+09:00
Vue.jsでinput type="number"としてもvalueは常に文字列を返すので注意!number装飾子を追加しよう
はじめに
Vue.jsで投稿機能を実装していたときにハマってしまったことをメモしておきます。
ハマってしまったこと
価格入力をするために
input type="number"
として、情報を送信したのですが、うまくデータベースに保存できませんでした。<div class="form-group"> <label>価格</label> <input type="number" v-model="price" name="price"> </div>modelprice: DataTypes.INTEGERpriceはinteger型なので、
input type="number"
でいけるかなと思ったのですが、そうではなかったようです。原因
type=number と書いたときでさえ、 HTMLのinput要素のvalue は常に文字列を返すようです。
なのでvue上で文字列を数値に変換します。解決策
v-modelに管理された入力にnumber修飾子を追加するだけです。
これで数値として値を保存することができました!!<div class="form-group"> <label>価格</label> <input type="number" v-model.number="price" name="price"> </div>
- 投稿日:2019-06-25T00:12:36+09:00
JavaScriptだけで麻雀アプリを作れないかやってみた(できた)
Python+Flask+Herokuで麻雀アプリを作りたい(作った)の記事に
上げたように、Pythonを使って麻雀(もどき)アプリを作成することは出来ました。ですが、やはり牌を捨てるたびにサーバーにアクセスしに行くのは無駄ですね。
電車の中とかですと、全然通信出来ないタイミングもありますし…。なので、JavaScriptだけで出来ないかやってみました!
JavaScriptだけって言いながらVue.jsも使いましたが(笑)
まぁJavaScriptフレームワークなのでセーフってことで。
【完成品】
https://lonlymahjong.herokuapp.com/vue
Pythonで作ったのと背景色が違います!
見た目の違いはそれだけです…。はじめに
Pythonを使って麻雀アプリを作ることは出来ているので、
そのロジックをほぼそのままJavaScriptで書くだけですね!
簡単そうです!・・・どうやればいいんだろう?
3日ほど悩みましたが、妖精さんが夜中にコードを書いてくれることはなかったので
取り敢えず作業を以下に分けて実施して行こうと思います。①麻雀牌のクラスを作る
②山牌を作る
③初期配牌する
④牌を捨てて積もる
⑤あがり判定こうやってタスクを切り分けると
なんだかできそうな気がしきますね!①麻雀牌のクラスを作る
まずはじめに牌のクラスを作っていきたいと思います。
・・・でもJavaScriptでクラスってどう作ればいいんだ?
プログラミングの先生もJavaとJavaScriptは違うって言ってたし…。
と3日ほど悩みましたが、オブジェクト指向が役に立った話を読んでみると
どうやらほとんどJavaと同じような感じに書けるらしい。まじか。きっと自分の知らない新技術をこっそり使ってるんだろうなーと思ってたけど
どうやら最近のJavaScriptはES6って言うバージョンで何もしなくても
ブラウザが対応してれば使えるらしい。まじでか。.js//麻雀牌クラス class Hai{ //コンストラクタ constructor(kind, value){ this.kind = kind; //麻雀牌の種類(萬子・筒子・索子・四風牌・三元牌) this.value = value; //麻雀牌の値(1~9 東南西北白発中) this.pic = this.kind+'_'+String(this.value)+'.png'; //画像ファイル名 } }すごく簡単に出来た!
ES6すげぇ…。技術の進化バンザイ!②山牌を作る
麻雀牌クラスが作れたので、後は136牌インスタンスを作って
それをシャッフルすれば山牌が出来ますね。
Pythonではリスト内包表記で簡単に
インスタンスのListができたんですけど
ES6にはないみたいですね…。
せっかくなので、ジェネレーターを使って書いてみることにします。.jsconst KINDS = { SUUPAI : ['manzu', 'pinzu', 'souzu'], JIHAI : ['sufonpai', 'sangenpai'] } const SUUPAI_VALUE = [1,2,3,4,5,6,7,8,9]; const SUFONPAI_VALUE = [1,2,3,4]; const SANGENPAI_VALUE = [1,2,3]; //山牌作成 function createYamahai(){ // 配列ランダムソート(シャッフル)関数 let shuffleArray = (arr) => { let n = arr.length; let temp = 0, i = 0; while (n) { i = Math.floor(Math.random() * n--); temp = arr[n]; arr[n] = arr[i]; arr[i] = temp; } return arr; } //山牌作成ジェネレーター let yamahaiGenerator = function* (kinds, values){ for (let kind of kinds) { for (let value of values){ for (let i = 0; i<4; i++ ) { yield new Hai(kind,value); } } } }; //山牌返却 return shuffleArray([...yamahaiGenerator(KINDS.SUUPAI,SUUPAI_VALUE) ,...yamahaiGenerator([KINDS.JIHAI[0]],SUFONPAI_VALUE) ,...yamahaiGenerator([KINDS.JIHAI[1]],SANGENPAI_VALUE)]); }JavaScript - 配列をランダムソート(シャッフル)するを参考に、シャッフルする関数も自作。
createYamahai関数でしか山牌作成ジェネレーターもシャッフル関数も利用しないので
createYamahai関数内にオブジェクトとして作成しました。
このほうがスコープが絞れたりしてメリットになったりするのかな?③初期配牌する
山牌が出来たので、後はそこから14牌取ってきて画面に表示できれば
なんかそれっぽいものが一旦出来ます。
ここからはJavaScriptフレームワークとしてVue.jsを利用しました。
AngularとReact、Vue.jsどれを使うか迷ったのですが、
比較的スモールスタートできるとのことだったので。とはいえ、vue-cliとかNode.jsとかNuxt.jsとか利用しないと使えないんだろなー
と思ってたんですが、HTMLファイルの中で普通にJsファイルを読み込むだけで使えるみたい。<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>まじでか。Vue.jsパネェ。
ということで早速実装♪.jsvar app = new Vue({ el:'#app', data:{ yamahai : [], tehai : [] }, created: function() { //山牌作成 this.yamahai = createYamahai(); //配牌作成(山牌から14牌取得する) for (let i = 0; i<14; i++ ) { this.tehai.push(this.yamahai.shift()); } } }).html<div id="app"> <div> <img v-for="hai in tehai" v-bind:src="`/static/pic/${hai.pic}`"> </div> </div>双方向データバインディングを使うと簡単にブラウザ上の表示を変えられて便利ですね!
④牌を捨てて積もる
これもVue.jsを使えば簡単に出来ます。
.jsvar app = new Vue({ el:'#app', data:{ yamahai : [], tehai : [] }, created: function() { //山牌作成 this.yamahai = createYamahai(); //配牌作成(山牌から14牌取得する) for (let i = 0; i<14; i++ ) { this.tehai.push(this.yamahai.shift()); } }, methods: { //牌の交換 change: function(index) { //捨牌 this.tehai.splice(index, 1); //自摸 let tsumo = this.yamahai.shift(); this.tehai.push(tsumo); } } }).html<div id="app"> <div> <img v-for="(hai, index) in tehai" v-bind:src="`/static/pic/${hai.pic}`" v-on:click="change(index)"> </div> </div>v-forディレクティブの要素にIndexを取れるので、
それを利用してクリックした牌を捨てて新しく自摸ってきます。⑤あがり判定
あがり判定ロジックはPythonで作り込んだので、
それをJavaScriptとに書き直すだけなのでこれも簡単。
・・・と思ってたんですが、雀頭の候補取得が思いのほか難しかったです。
もっと形式化すると、「配列内の重複項目の取得」です。Pythonでは以下のように書いていたのですが、
.py[x for x in set(tehai) if tehai.count(x) >= 2]JavaScriptだとオブジェクトの配列に対してSetが使えないんですよね…。
おそらく、「===演算子が「同一のインスタンスかどうか」を判断するため」だと思うんですが…。
JabaScriptのClassオブジェクトもJavaのesualsメソッドや、Pythonのeqメソッドが
あればできるんだと思うんですが…。なので、JavaScriptでは以下メソッドを自作して対応しました。
.js//麻雀牌クラス class Hai{ //コンストラクタ constructor(kind, value){ this.kind = kind; //麻雀牌の種類(萬子・筒子・索子・四風牌・三元牌) this.value = value; //麻雀牌の値(1~9 東南西北白発中) this.pic = this.kind+'_'+String(this.value)+'.png'; //画像ファイル名 } //イコールメソッド equals(hai){ return hai.kind == this.kind && hai.value == this.value } } //重複要素取得 //duplicateCount:重複数 function findDuplicate(array,duplicateCount){ let setArray = array.filter((val, index, self) => self.findIndex(n => val.equals(n)) === index); let result = setArray.filter(val => (array.filter(n => val.equals(n)).length >= duplicateCount)); return result; }setArray(重複のない配列)=
findIndexにて配列内で要素が最初に一致する位置が取得できるので、
それをループのindexとぶつけて一致するものだけを取得。
この処理で利用しているequalsメソッドは自作したものになります。result=
setArray(重複のない配列)の一要素が、
もとの配列に何件あるか取得し、その件数がduplicateCountを上回る値だけを取得。…プログラム処理を日本語にするのは難しいですね。
完成!
上記の処理に加え、理牌や捨て牌の表示、あたりの際の画像表示を行えばとりあえず完成です。
思ったより長かった…。
でもJavaScriptだけですべての処理を行うことができるようになりました!
牌の画像さえキャッシュに残っていれば低レイテンシどころか0レイテンシです!Pythonで書いたコードをJavaScriptで書き直すって言うのは結構発見があってよかったです。
一度書いたコードを別の言語で書き直すって言うのは学習の一貫としてありかもしれないですね。
それにしてもES6すごいな…。もう大抵のことはJavaScriptでできちゃいますね。
新人の研修とかもJavaScriptでいいんじゃないかな〜。今後の改修案としては
①あがり役の表示と点数計算機能を実装する。
②サーバーサイドでもなにかやる。
(あがりまでの打牌数や点数をDBに記録してなんか出す。
グラフ化して上達具合を可視化するとか?)
③Vue.jsをもっと使いこなす。
(なんかソシャゲっぽくする。ソシャゲっぽいってなんだ…?)
④Firebaseを使って見る。
(Python(Flask)はルーティングしか使ってないですし、
Firebaseを使っても行けるんでないかなーと。
使ったことないけど。)
なんかを考えてます。
また時間を作ってやっていこうっと。おまけ
今回書いたコード。GitHubにも上げてます!
mahjong.jsconst KINDS = { SUUPAI : ['manzu', 'pinzu', 'souzu'], JIHAI : ['sufonpai', 'sangenpai'] } const SUUPAI_VALUE = [1,2,3,4,5,6,7,8,9]; const SUFONPAI_VALUE = [1,2,3,4]; const SANGENPAI_VALUE = [1,2,3]; // const SUFONPAI_VALUE = { // 1:'東', // 2:'南', // 3:'西', // 4:'北' // }; // const SANGENPAI_VALUE = { // 1:'白', // 2:'發', // 3:'中' // }; const MENTSU_KINDS = ['順子','刻子'] //麻雀牌クラス class Hai{ //コンストラクタ constructor(kind, value){ this.kind = kind; //麻雀牌の種類(萬子・筒子・索子・四風牌・三元牌) this.value = value; //麻雀牌の値(1~9 東南西北白発中) this.pic = this.kind+'_'+String(this.value)+'.png'; //画像ファイル名 } //ソートキーを返却 getSortKey(){ switch( this.kind ) { case KINDS.SUUPAI[0]: return Number('1'+String(this.value)); case KINDS.SUUPAI[1]: return Number('2'+String(this.value)); case KINDS.SUUPAI[2]: return Number('3'+String(this.value)); case KINDS.JIHAI[0]: return Number('4'+String(this.value)); case KINDS.JIHAI[1]: return Number('5'+String(this.value)); default: throw new TypeError('Hai.kind is not undefined'); } } //イコールメソッド equals(hai){ return hai.kind == this.kind && hai.value == this.value } } //あがり牌クラス class Agari{ //コンストラクタ constructor(yaku,arrayHai,janto,mentsu1,mentsu2,mentsu3,mentsu4){ this.yaku = yaku; this.arrayHai = arrayHai; this.janto = janto; this.mentsu1 = mentsu1; this.mentsu2 = mentsu2; this.mentsu3 = mentsu3; this.mentsu4 = mentsu4; } } //雀頭 class Janto{ //コンストラクタ constructor(arrayHai){ this.arrayHai = arrayHai; } } //面子 class Mentsu{ //コンストラクタ constructor(kind, arrayHai){ this.kind = kind; this.arrayHai = arrayHai; } } //麻雀牌オブジェとのソート用関数 function sortHai(a,b){ return a.getSortKey()-b.getSortKey(); } //山牌作成 function createYamahai(){ // 配列ランダムソート(シャッフル)関数 let shuffleArray = (arr) => { let n = arr.length; let temp = 0, i = 0; while (n) { i = Math.floor(Math.random() * n--); temp = arr[n]; arr[n] = arr[i]; arr[i] = temp; } return arr; } //山牌作成ジェネレーター let yamahaiGenerator = function* (kinds, values){ for (let kind of kinds) { for (let value of values){ for (let i = 0; i<4; i++ ) { yield new Hai(kind,value); } } } }; //山牌返却 return shuffleArray([...yamahaiGenerator(KINDS.SUUPAI,SUUPAI_VALUE) ,...yamahaiGenerator([KINDS.JIHAI[0]],SUFONPAI_VALUE) ,...yamahaiGenerator([KINDS.JIHAI[1]],SANGENPAI_VALUE)]); } //あがり判定 function judge(tehai){ agari=[]; //雀頭 jantoArray = findDuplicate(tehai,2); if(jantoArray.length === 0){ return false; } //国士無双 if(checkKokushimusou(tehai, jantoArray)){ //return new Agari('国士無双',tehai,null,null,null,null,null); return true; } //七対子 if(jantoArray.length === 7){ agari= agari.concat(new Agari('七対子',tehai,null,null,null,null,null)); } //通常役 for(let janto of jantoArray){ let copy = Object.assign([], tehai); removeElement(copy,janto); removeElement(copy,janto); copy.sort(sortHai); //刻子の種類 koutsuArray = findDuplicate(copy,3); //刻子が0個のパターン agari= agari.concat(agariKoutsu0(copy, janto)) //刻子が1個のパターン agari= agari.concat(agariKoutsu1(copy, janto, koutsuArray)) //刻子が2個のパターン agari= agari.concat(agariKoutsu2(copy, janto, koutsuArray)) //刻子が3個のパターン agari= agari.concat(agariKoutsu3(copy, janto, koutsuArray)) //刻子が4個のパターン agari= agari.concat(agariKoutsu4(janto, koutsuArray)) } return agari.length > 0 } //重複要素取得 //duplicateCount:重複数 function findDuplicate(array,duplicateCount){ let setArray = array.filter((val, index, self) => self.findIndex(n => val.equals(n)) === index); let result = setArray.filter(val => (array.filter(n => val.equals(n)).length >= duplicateCount)); return result.sort(sortHai); } //配列に特定の要素があるか確認 //存在する場合:true function checkAvailability(array, val) { return array.some(arrVal => val.equals(arrVal)); } //配列から特定の要素を削除 function removeElement(array, val){ let index = array.findIndex(arrVal => val.equals(arrVal)); array.splice(index, 1); } //順子をひとつ見つける function findOneSyuntsu(arrayHai){ arrayHai.sort(sortHai); for(let hai of arrayHai){ let syuntsu = createSyuntsu(hai); if(syuntsu == null){ continue; } if(checkAvailability(arrayHai,syuntsu.arrayHai[0]) && checkAvailability(arrayHai,syuntsu.arrayHai[1]) && checkAvailability(arrayHai,syuntsu.arrayHai[2])){ return syuntsu; } } throw new RangeError('No Mentsu'); } //自身を一番最初とした順子を返却 function createSyuntsu(hai){ if(KINDS.SUUPAI.includes(hai.kind) && hai.value <= 7){ return new Mentsu(MENTSU_KINDS[0], [new Hai(hai.kind,hai.value) ,new Hai(hai.kind,hai.value+1) ,new Hai(hai.kind,hai.value+2)]); } return null; } //刻子が0個のあがりパターン function agariKoutsu0(arrayHai,janto){ try { let copy = Object.assign([], arrayHai); let first = findOneSyuntsu(copy); removeElement(copy,first.arrayHai[0]); removeElement(copy,first.arrayHai[1]); removeElement(copy,first.arrayHai[2]); let second = findOneSyuntsu(copy); removeElement(copy,second.arrayHai[0]); removeElement(copy,second.arrayHai[1]); removeElement(copy,second.arrayHai[2]); let third = findOneSyuntsu(copy); removeElement(copy,third.arrayHai[0]); removeElement(copy,third.arrayHai[1]); removeElement(copy,third.arrayHai[2]); let fourth = findOneSyuntsu(copy); return [new Agari(new Janto([janto,janto]), first, second, third, fourth)]; }catch (e) { return []; } } //刻子が1個のあがりパターン function agariKoutsu1(arrayHai,janto,koutsuArray){ if(koutsuArray.length < 1){ return []; } let result = []; for(let koutsu of koutsuArray){ try{ let copy = Object.assign([], arrayHai); let first = new Mentsu(MENTSU_KINDS[1],[koutsu,koutsu,koutsu]); removeElement(copy,first.arrayHai[0]); removeElement(copy,first.arrayHai[1]); removeElement(copy,first.arrayHai[2]); let second = findOneSyuntsu(copy); removeElement(copy,second.arrayHai[0]); removeElement(copy,second.arrayHai[1]); removeElement(copy,second.arrayHai[2]); let third = findOneSyuntsu(copy); removeElement(copy,third.arrayHai[0]); removeElement(copy,third.arrayHai[1]); removeElement(copy,third.arrayHai[2]); let fourth = findOneSyuntsu(copy); result.push([new Agari(new Janto([janto,janto]), first, second, third, fourth)]); }catch (e) { continue; } } return result; } //刻子が2個のあがりパターン function agariKoutsu2(arrayHai,janto,koutsuArray){ if(koutsuArray.length < 2){ return []; } let result = []; for(let i = 0; i < koutsuArray.length - 1; i++){ for(let j = i + 1; j < koutsuArray.length; j++){ try{ let copy = Object.assign([], arrayHai); let first = new Mentsu(MENTSU_KINDS[1],[koutsuArray[i],koutsuArray[i],koutsuArray[i]]); removeElement(copy,first.arrayHai[0]); removeElement(copy,first.arrayHai[1]); removeElement(copy,first.arrayHai[2]); let second = new Mentsu(MENTSU_KINDS[1],[koutsuArray[j],koutsuArray[j],koutsuArray[j]]); removeElement(copy,second.arrayHai[0]); removeElement(copy,second.arrayHai[1]); removeElement(copy,second.arrayHai[2]); let third = findOneSyuntsu(copy); removeElement(copy,third.arrayHai[0]); removeElement(copy,third.arrayHai[1]); removeElement(copy,third.arrayHai[2]); let fourth = findOneSyuntsu(copy); result.push([new Agari(new Janto([janto,janto]), first, second, third, fourth)]); }catch (e) { continue; } } } return result; } //刻子が3個のあがりパターン function agariKoutsu3(arrayHai,janto,koutsuArray){ if(koutsuArray.length != 3){ return []; } try { let copy = Object.assign([], arrayHai); let first = new Mentsu(MENTSU_KINDS[1],[koutsuArray[0],koutsuArray[0],koutsuArray[0]]); removeElement(copy,first.arrayHai[0]); removeElement(copy,first.arrayHai[1]); removeElement(copy,first.arrayHai[2]); let second = new Mentsu(MENTSU_KINDS[1],[koutsuArray[1],koutsuArray[1],koutsuArray[1]]); removeElement(copy,second.arrayHai[0]); removeElement(copy,second.arrayHai[1]); removeElement(copy,second.arrayHai[2]); let third = new Mentsu(MENTSU_KINDS[1],[koutsuArray[2],koutsuArray[2],koutsuArray[2]]); removeElement(copy,third.arrayHai[0]); removeElement(copy,third.arrayHai[1]); removeElement(copy,third.arrayHai[2]); let fourth = findOneSyuntsu(copy); return [new Agari(new Janto([janto,janto]), first, second, third, fourth)]; }catch (e) { return []; } } //刻子が4個のあがりパターン function agariKoutsu4(janto,koutsuArray){ if(koutsuArray.length != 4){ return []; } return [new Agari(new Janto([janto,janto]), new Mentsu(MENTSU_KINDS[1],[koutsuArray[0],koutsuArray[0],koutsuArray[0]]), new Mentsu(MENTSU_KINDS[1],[koutsuArray[1],koutsuArray[1],koutsuArray[1]]), new Mentsu(MENTSU_KINDS[1],[koutsuArray[2],koutsuArray[2],koutsuArray[2]]), new Mentsu(MENTSU_KINDS[1],[koutsuArray[3],koutsuArray[3],koutsuArray[3]]))]; } //国士無双のチェック(前提として雀頭があること) function checkKokushimusou(tehai){ if( checkAvailability(tehai,new Hai(KINDS.SUUPAI[0],SUUPAI_VALUE[0])) && checkAvailability(tehai,new Hai(KINDS.SUUPAI[0],SUUPAI_VALUE[8])) && checkAvailability(tehai,new Hai(KINDS.SUUPAI[1],SUUPAI_VALUE[0])) && checkAvailability(tehai,new Hai(KINDS.SUUPAI[1],SUUPAI_VALUE[8])) && checkAvailability(tehai,new Hai(KINDS.SUUPAI[2],SUUPAI_VALUE[0])) && checkAvailability(tehai,new Hai(KINDS.SUUPAI[2],SUUPAI_VALUE[8])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[0],SUFONPAI_VALUE[0])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[0],SUFONPAI_VALUE[1])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[0],SUFONPAI_VALUE[2])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[0],SUFONPAI_VALUE[3])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[1],SANGENPAI_VALUE[0])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[1],SANGENPAI_VALUE[1])) && checkAvailability(tehai,new Hai(KINDS.JIHAI[1],SANGENPAI_VALUE[2])) ){ return true; }else{ return false; } } const TEST_TEHAI = ['23333444556688', '22333456667788', '22344445556677', '11123334445577','22223344455677', '11555677788899']; function createTestTehai(index){ return Array.from(TEST_TEHAI[index]).map(n => new Hai(KINDS.SUUPAI[0],Number(n))); } function checkTestTehai(){ for(let i =0;i<TEST_TEHAI.length;i++){ let tehai = createTestTehai(i); // console.log(TEST_TEHAI[i]); // console.log(judge(tehai)); if(!judge(tehai)){ console.log('失敗') } } }main.jsvar app = new Vue({ el:'#app', data:{ yamahai : [], kawa : [], tehai : [], agari : false }, created: function() { //山牌作成 this.yamahai = createYamahai(); //配牌作成(山牌から14牌取得する) for (let i = 0; i<14; i++ ) { this.tehai.push(this.yamahai.shift()); } //理牌 this.tehai.sort(sortHai) //あがり判定 this.agari = judge(this.tehai); }, methods: { //牌の交換 change: function(index) { //捨牌 sutehai = this.tehai[index]; this.tehai.splice(index, 1); //河 this.kawa.push(sutehai); //理牌 this.tehai.sort(sortHai); //自摸 let tsumo = this.yamahai.shift(); this.tehai.push(tsumo); //あがり判定 this.agari = judge(this.tehai); } } })vueJong.html<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <link rel="stylesheet"href="/static/css/main.css"> <title>ひとりまーじゃん</title> </head> <body class="vue"> <div id="app"> <div class="win" v-if="agari" > <a href="/vue"><img src="/static/pic/win.png"></a> </div> <div class="sutehai" v-if="!agari" > <template v-for="(hai, index) in kawa"><br v-if="(index % 9) == 0" ><img v-bind:src="`/static/pic/${hai.pic}`"></template> </div> <div class="tehai"> <img v-for="(hai, index) in tehai" v-bind:src="`/static/pic/${hai.pic}`" v-on:click="change(index)"> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="/static/js/mahjong.js"></script> <script src="/static/js/main.js"></script> </body> </html>