- 投稿日:2019-03-24T19:40:18+09:00
[Vue.js/Nuxt.js] v-forで中の要素にcomputedを使いたいときはコンポーネント分割をしよう
概要
Vueの算出プロパティであるcomputedは引数を持つことができず、v-forで回しているArray[Index]のような一部の要素のみを指定することができない。
一応、methodsに書いて呼び出すことで擬似的に動作させることは可能だが、状態が変わったときに再計算が行われないのが結構落とし穴である。
以下のコードは、コメント一覧画面でv-forでArrayのコメント一覧を回し、コメント1つ1つに対して閲覧者であるユーザのコメントがあったらそのコメントのみModifierのclassを付与したいという場合のサンプルコードである。
動作しないコード
computedは引数を持つことができないので、以下のコードはエラーとなる。
Comments.Vue<template> <div v-for="(comment, comments)" class="comments"> <p class="comments__comment" :class="isUserPostClass(comment)"/> </div> </temlate> ~省略 computed: { isUserPostClass(comment) { return comment.isUserPost ? 'comments__comment--user-post' : ''; } } ~省略動作するが良くないコード
methodsに定義すれば引数を取得でき、動きはするが再計算がされないので複数回の描画の際に積む。
(たとえば、クリックしたらそのコメントが強調されるようにしたいなどの要件に対応できない)Comments.Vue<template> <div v-for="(comment, comments)" class="comments"> <p class="comments__comment" :class="isUserPostClass(comment)"/> </div> </temlate> ~省略 methods: { isUserPostClass(comment) { return comment.isUserPost ? 'comments__comment--user-post' : ''; } } ~省略解決策
ではどうすればいいのかと言うと、タイトルにもある通りv-forの中身だけをコンポーネント分割すればOKである。
今回はコメント一覧画面なので、コメント単体のコンポーネントを作成し、propsにv−forで回しているコメントのObject、および変更を検知させて再計算させたいプロパティをを与えてあげれば問題ない。その後、コメントコンポーネントの中でcomputedを定義すれば、v-forの中身でcomputedを使うことができる。
動作するコード
Comments.Vue<template> <div v-for="(comment, comments)" class="comments"> <UserComment :comment="comment" :isUserPost="comment.isUserPost" /> </div> </temlate>UserComment.Vue<template> <div class="user-comment" :class="isUserPostClass"> ~内容~ </div> </temlate> ~省略 props: { comment: { type: Object, default: null }, isUserPost: { type: Boolean, default: false } }, computed: { isUserPostClass() { return this.isUserPost ? 'user-comment--user-post' : ''; } } ~省略まとめ
- v-forの中でcomputedを使いたくなったら、ループ対象であるDOMをコンポーネント分割しよう
- computedでプロパティの内容が変わったときの再計算処理を走らせるためには、明示的にpropsとして渡したほうが安全
- 投稿日:2019-03-24T19:40:18+09:00
[Vue.js/Nuxt.js] v-forで回している要素の中にcomputedを書くのはあまり得策ではない
概要
Vueの算出プロパティであるcomputedは引数を持つことができず、v-forで回しているArray[Index]のような一部の要素のみを指定することができない。
一応、methodsに書いて呼び出すことで擬似的に動作させることは可能だが、状態が変わったときに再計算が行われないのが結構落とし穴である。
以下のコードは、コメント一覧画面でv-forでArrayのコメント一覧を回し、コメント1つ1つに対して閲覧者であるユーザのコメントがあったらそのコメントのみModifierのclassを付与したいという場合のサンプルコードである。
動作しないコード
computedは引数を持つことができないので、以下のコードはエラーとなる。
Comments.Vue<template> <div v-for="(comment, comments)" class="comments"> <p class="comments__comment" :class="isUserPostClass(comment)"/> </div> </temlate> ~省略 computed: { isUserPostClass(comment) { return comment.isUserPost ? 'comments__comment--user-post' : ''; } } ~省略動作するが良くないコード
methodsに定義すれば引数を取得でき、動きはするが再計算がされないので複数回の描画の際に積む。
(たとえば、クリックしたらそのコメントが強調されるようにしたいなどの要件に対応できない)Comments.Vue<template> <div v-for="(comment, comments)" class="comments"> <p class="comments__comment" :class="isUserPostClass(comment)"/> </div> </temlate> ~省略 methods: { isUserPostClass(comment) { return comment.isUserPost ? 'comments__comment--user-post' : ''; } } ~省略解決策
ではどうすればいいのかと言うと、タイトルにもある通りv-forの中身だけをコンポーネント分割すればOKである。
今回はコメント一覧画面なので、コメント単体のコンポーネントを作成し、propsにv−forで回しているコメントのObject、および変更を検知させて再計算させたいプロパティをを与えてあげれば問題ない。その後、コメントコンポーネントの中でcomputedを定義すれば、v-forの中身でcomputedを使うことができる。
動作するコード
Comments.Vue<template> <div v-for="(comment, comments)" class="comments"> <UserComment :comment="comment" :isUserPost="comment.isUserPost" /> </div> </temlate>UserComment.Vue<template> <div class="user-comment" :class="isUserPostClass"> ~内容~ </div> </temlate> ~省略 props: { comment: { type: Object, default: null }, isUserPost: { type: Boolean, default: false } }, computed: { isUserPostClass() { return this.isUserPost ? 'user-comment--user-post' : ''; } } ~省略まとめ
- v-forの中でcomputedを使いたくなったら、ループ対象であるDOMをコンポーネント分割しよう
- computedでプロパティの内容が変わったときの再計算処理を走らせるためには、明示的にpropsとして渡したほうが安全
- 投稿日:2019-03-24T15:17:19+09:00
Vue.jsでDraft.jsのハッシュタグプラグインに似た機能をなんとか作ってみる
背景
コンテンツ投稿型のアプリケーション開発の過程で、テキスト入力時や他の投稿を表示するときなど、FacebookやInstagramの投稿のように文章中にあるハッシュタグをハイライトさせる機能が必要な案件がありました。さらに、入力中のハッシュタグからインクリメントサーチでハッシュタグの予測語をリスト表示させ、予測語と入力中のハッシュタグを置換させるといった要望もあります。リッチテキストエディタを作る訳ではありませんが、その実装に向けてリサーチを行い、その備忘録を記します。
完成品はこんな感じです。
Desktop Mobile Facebookのハッシュタグハイライト
実際のFacebookでは投稿時にどのようにハイライトさせているか調べてみると、
spanタグにcontentstateという属性が付いていて、どうやらその中身がハッシュタグに装飾を行なっている模様でした。
Facebook投稿時のハッシュタグ部分のhtml
<span contentstate="c { "entityMap": [object Object], "blockMap": OrderedMap { "c9rd9": c { "key": "c9rd9", "type": "unstyled", "text": "#aa", "characterList": List [ b { "style": OrderedSet {}, "entity": null }, b { "style": OrderedSet {}, "entity": null }, b { "style": OrderedSet {}, "entity": null } ], "depth": 0, "data": Map {} } }, "selectionBefore": b { "anchorKey": "c9rd9", "anchorOffset": 0, "focusKey": "c9rd9", "focusOffset": 0, "isBackward": false, "hasFocus": true }, "selectionAfter": b { "anchorKey": "c9rd9", "anchorOffset": 3, "focusKey": "c9rd9", "focusOffset": 3, "isBackward": false, "hasFocus": true }}" decoratedtext="#aa" start="0" end="3" blockkey="c9rd9" offsetkey="c9rd9-0-0" data-offset-key="c9rd9-0-0" class="_5zk7" spellcheck="false"> <span data-offset-key="c9rd9-0-0"> <span data-text="true">#aa</span> </span> </span>中身は全く分からなかったですが、spanタグにある
contentstateという見慣れない属性があり、これがヒントだと考えて調べてみるとDraft.jsというライブラリの機能のようでした。残念なことに、このDraft.jsはReact向けのライブラリのようで、今回はVueを使ったプロジェクトなので導入できません。そこで、似たような機能を搭載したVueコンポーネントを作ることにしました。contenteditable
こちらの記事に、Draft.jsのハッシュタグハイライトの構造がまとめられていました。作りとしては入力されたテキストに対して、ハッシュタグを抽出する正規表現を用意し、検出したハッシュタグをcallbackで返し、そのハッシュタグを装飾用の別のReactのコンポーネントで置き換える?といった流れだと思います。ここで重要なのが
contenteditableというブラウザに実装されているhtmlの属性です。最初はtextareaを使った実装にしていましたが、文字数が増えていってコンテナサイズに収まりきらなくなった時に、textareaをリサイズするための処理が複雑になったので断念しました。このcontenteditable属性はDOMを直接編集可能にできるため、コンテナの高さを100%に設定しておけば、自動でリサイズしてくれます。ただし、v-modelが使えないため、watchなどでテキストのインプットをリアルタイムでトラッキングする方法が思いつきませんでした。私はDOMの変更を監視するMutationObserverを使ってテキストの入力を監視させました。// targetはcontenteditable属性を持つ const target = document.getElementById('input-true-text'); const observer = new MutationObserver(this.onObserveElement); const config = { childList: true, characterData: true, characterDataOldValue: true, subtree: true }; observer.observe(target, config);innerHTMLで置換
contenteditableのコンテナの下レイヤーにもう一つコンテナを置き、contenteditableのコンテナで入力された内容をinnerHTMLで全て置き換えます。その際、ハッシュタグの内容だけタグで囲むように変更し、ハッシュタグを装飾できるようにしました。
ここで注意しなければいけなかった点は、innerHTMLで置換される文字列に
<や&などの文字があるとhtmlとして認識されてしまうことです。そのため、一度全ての文字列からエスケープ文字だけ最初に置換してから、ハッシュタグの検出 => タグ文字とともに置換 を行います。また、なぜかSafariブラウザとその他のブラウザでは複数行の改行をした時の改行コードの数が違っていたため、Safariブラウザ以外では改行文字を1つ削除させています。
methodsonObserveElement(mutations) { mutations.forEach((mutation) => { const type = mutation.type switch(type) { // 文字入力に変化があればここ case 'characterData': this.replaceContent() break; // 行に変化があればここ case 'childList': this.replaceContent() break; default: break; } }) }, replaceContent() { const target = document.getElementById('input-true-text'); // NOTE: エスケープ文字を処理する const content = this.escapeHtml(target.innerText) const contentHTML = target.textContent // NOTE: 改行コードを削除(Safariブラウザ以外) const spaceExp = /^\n\n/gm const content2 = content.replace(spaceExp, function(match) { return '\n' }) // NOTE: 新しいテキストを作成 const srcContent = this.isSafariBrowser ? content : content2 const self = this // ハッシュタグ文字を置換する const replaceContent = srcContent.replace(this.regExp, function(match) { const idStr = ' id=' + self.getUniqueStr() const result = '<i ' + self.hashtagStyle + idStr + '>' + match + '</i>' return result }) // NOTE: 表示レイヤーに置換文字を適用 const insertNode = document.getElementById('input-overlay') insertNode.innerHTML = replaceContent },ハッシュタグの選択に対応
プレビュー時にハッシュタグを選択してハッシュタグ関連のコンテンツを表示させる、といった要望もありました。編集時にハッシュタグが選択できるようにさせると、ハッシュタグの選択なのか編集のための選択か判別できないため、編集モードとプレビューモードを分けました。プレビューモードでは単純に表示レイヤーをcontenteditableレイヤーの上に置き、DOMの編集をできないようにさせるだけです。その上で、表示レイヤーの
<i>タグの変更を監視します。mounted() { const overlayElm = document.getElementById('input-overlay') overlayElm.addEventListener("click", this.onSelectHashtag, false); }, methods: { onSelectHashtag(e) { const target = e.target const tagName = target.tagName if (tagName === 'I') { const content = target.textContent this.$emit('onSelectHashtag', target) } }, }その他の機能
編集中のハッシュタグの置換も実装しました。入力中のハッシュタグからインクリメントサーチをして、予測語のハッシュタグを置換させるためです。
コンポーネントをライブラリ化
Vueのコンポーネントを
vue-hashtag-textareaとしてライブラリ化しました。以下、変更できる装飾オプションです。
Options Type Description Default textColor String ordinary text color black font String wave height 14px "Noto Sans Japanese", sans-serif hashtagBackgroundColor String background color under hashtag transparent hashtagColor String hashtag color #ff0000 placeholder String placeholder on empty Sentence for placeholder #place #holder isEditMode Boolean true: enable to edit but cannot select hashtag
false: enable to select hashtag but cannot edittrue npmとGitHubにあります。
なお、このライブラリはカーソルの管理は行なっておりません。そのため、編集中のハッシュタグを置換した後のカーソル位置は文末に置かれるので、少し使い勝手が悪いかもしれません。Conclusion
今回の実装に当たって、ScrapBoxというサービスのハッシュタグ機能も参考にしました。入力された文字に対して1文字ずつ
spanタグを追加するといった実装になっています。ScrapBoxでもcontenteditableを使っていそうな感じで、ここを起点としてcontenteditableの使い方などを調べていっています。contenteditableはなかなかクセがある属性のようで、今回作成したvue-hashtag-texareaもまだ不十分な点が潜んでいると思います。その点をこれから発見して改善していければと思います。
- 投稿日:2019-03-24T15:17:19+09:00
Vue.jsでFacebookのハッシュタグハイライトに似た機能をなんとか作ってみる
背景
コンテンツ投稿型のアプリケーション開発の過程で、テキスト入力時や他の投稿を表示するときなど、FacebookやInstagramの投稿のように文章中にあるハッシュタグをハイライトさせる機能が必要な案件がありました。さらに、入力中のハッシュタグからインクリメントサーチでハッシュタグの予測語をリスト表示させ、予測語と入力中のハッシュタグを置換させるといった要望もあります。リッチテキストエディタを作る訳ではありませんが、その実装に向けてリサーチを行い、その備忘録を記します。
完成品はこんな感じです。
Desktop Mobile Facebookのハッシュタグハイライト
実際のFacebookでは投稿時にどのようにハイライトさせているか調べてみると、
spanタグにcontentstateという属性が付いていて、どうやらその中身がハッシュタグに装飾を行なっている模様でした。
Facebook投稿時のハッシュタグ部分のhtml
<span contentstate="c { "entityMap": [object Object], "blockMap": OrderedMap { "c9rd9": c { "key": "c9rd9", "type": "unstyled", "text": "#aa", "characterList": List [ b { "style": OrderedSet {}, "entity": null }, b { "style": OrderedSet {}, "entity": null }, b { "style": OrderedSet {}, "entity": null } ], "depth": 0, "data": Map {} } }, "selectionBefore": b { "anchorKey": "c9rd9", "anchorOffset": 0, "focusKey": "c9rd9", "focusOffset": 0, "isBackward": false, "hasFocus": true }, "selectionAfter": b { "anchorKey": "c9rd9", "anchorOffset": 3, "focusKey": "c9rd9", "focusOffset": 3, "isBackward": false, "hasFocus": true }}" decoratedtext="#aa" start="0" end="3" blockkey="c9rd9" offsetkey="c9rd9-0-0" data-offset-key="c9rd9-0-0" class="_5zk7" spellcheck="false"> <span data-offset-key="c9rd9-0-0"> <span data-text="true">#aa</span> </span> </span>中身は全く分からなかったですが、spanタグにある
contentstateという見慣れない属性があり、これがヒントだと考えて調べてみるとDraft.jsというライブラリの機能のようでした。残念なことに、このDraft.jsはReact向けのライブラリのようで、今回はVueを使ったプロジェクトなので導入できません。そこで、似たような機能を搭載したVueコンポーネントを作ることにしました。contenteditable
こちらの記事に、Draft.jsのハッシュタグハイライトの構造がまとめられていました。作りとしては入力されたテキストに対して、ハッシュタグを抽出する正規表現を用意し、検出したハッシュタグをcallbackで返し、そのハッシュタグを装飾用の別のReactのコンポーネントで置き換える?といった流れだと思います。ここで重要なのが
contenteditableというブラウザに実装されているhtmlの属性です。最初はtextareaを使った実装にしていましたが、文字数が増えていってコンテナサイズに収まりきらなくなった時に、textareaをリサイズするための処理が複雑になったので断念しました。このcontenteditable属性はDOMを直接編集可能にできるため、コンテナの高さを100%に設定しておけば、自動でリサイズしてくれます。ただし、v-modelが使えないため、watchなどでテキストのインプットをリアルタイムでトラッキングする方法が思いつきませんでした。私はDOMの変更を監視するMutationObserverを使ってテキストの入力を監視させました。// targetはcontenteditable属性を持つ const target = document.getElementById('input-true-text'); const observer = new MutationObserver(this.onObserveElement); const config = { childList: true, characterData: true, characterDataOldValue: true, subtree: true }; observer.observe(target, config);innerHTMLで置換
contenteditableのコンテナの下レイヤーにもう一つコンテナを置き、contenteditableのコンテナで入力された内容をinnerHTMLで全て置き換えます。その際、ハッシュタグの内容だけタグで囲むように変更し、ハッシュタグを装飾できるようにしました。
ここで注意しなければいけなかった点は、innerHTMLで置換される文字列に
<や&などの文字があるとhtmlとして認識されてしまうことです。そのため、一度全ての文字列からエスケープ文字だけ最初に置換してから、ハッシュタグの検出 => タグ文字とともに置換 を行います。また、なぜかSafariブラウザとその他のブラウザでは複数行の改行をした時の改行コードの数が違っていたため、Safariブラウザ以外では改行文字を1つ削除させています。
methodsonObserveElement(mutations) { mutations.forEach((mutation) => { const type = mutation.type switch(type) { // 文字入力に変化があればここ case 'characterData': this.replaceContent() break; // 行に変化があればここ case 'childList': this.replaceContent() break; default: break; } }) }, replaceContent() { const target = document.getElementById('input-true-text'); // NOTE: エスケープ文字を処理する const content = this.escapeHtml(target.innerText) const contentHTML = target.textContent // NOTE: 改行コードを削除(Safariブラウザ以外) const spaceExp = /^\n\n/gm const content2 = content.replace(spaceExp, function(match) { return '\n' }) // NOTE: 新しいテキストを作成 const srcContent = this.isSafariBrowser ? content : content2 const self = this // ハッシュタグ文字を置換する const replaceContent = srcContent.replace(this.regExp, function(match) { const idStr = ' id=' + self.getUniqueStr() const result = '<i ' + self.hashtagStyle + idStr + '>' + match + '</i>' return result }) // NOTE: 表示レイヤーに置換文字を適用 const insertNode = document.getElementById('input-overlay') insertNode.innerHTML = replaceContent },ハッシュタグの選択に対応
プレビュー時にハッシュタグを選択してハッシュタグ関連のコンテンツを表示させる、といった要望もありました。編集時にハッシュタグが選択できるようにさせると、ハッシュタグの選択なのか編集のための選択か判別できないため、編集モードとプレビューモードを分けました。プレビューモードでは単純に表示レイヤーをcontenteditableレイヤーの上に置き、DOMの編集をできないようにさせるだけです。その上で、表示レイヤーの
<i>タグの変更を監視します。mounted() { const overlayElm = document.getElementById('input-overlay') overlayElm.addEventListener("click", this.onSelectHashtag, false); }, methods: { onSelectHashtag(e) { const target = e.target const tagName = target.tagName if (tagName === 'I') { const content = target.textContent this.$emit('onSelectHashtag', target) } }, }その他の機能
編集中のハッシュタグの置換も実装しました。入力中のハッシュタグからインクリメントサーチをして、予測語のハッシュタグを置換させるためです。
コンポーネントをライブラリ化
Vueのコンポーネントを
vue-hashtag-textareaとしてライブラリ化しました。以下、変更できる装飾オプションです。
Options Type Description Default textColor String ordinary text color black font String wave height 14px "Noto Sans Japanese", sans-serif hashtagBackgroundColor String background color under hashtag transparent hashtagColor String hashtag color #ff0000 placeholder String placeholder on empty Sentence for placeholder #place #holder isEditMode Boolean true: enable to edit but cannot select hashtag
false: enable to select hashtag but cannot edittrue npmとGitHubにあります。
なお、このライブラリはカーソルの管理は行なっておりません。そのため、編集中のハッシュタグを置換した後のカーソル位置は文末に置かれるので、少し使い勝手が悪いかもしれません。Conclusion
今回の実装に当たって、ScrapBoxというサービスのハッシュタグ機能も参考にしました。入力された文字に対して1文字ずつ
spanタグを追加するといった実装になっています。ScrapBoxでもcontenteditableを使っていそうな感じで、ここを起点としてcontenteditableの使い方などを調べていっています。contenteditableはなかなかクセがある属性のようで、今回作成したvue-hashtag-texareaもまだ不十分な点が潜んでいると思います。その点をこれから発見して改善していければと思います。
- 投稿日:2019-03-24T12:44:52+09:00
Nuxt.jsで新規プロジェクトのlocalhost:3000にアクセスするまでの手順[pug][sass][fontawesome][googlefonts][normalize]
私がいつもNuxt.jsでプロジェクトを作るときの初期設定の手順がだんだん固定化されてきたので、まとめてみました。
pug,sass,fontawesome,googlefontsに加えて、normalize.scssも入った、個人的全部入りです。
自分でも使っていくつもりなので、更新されることもあるかと思います。環境
- Mojave 10.14.3(18D109)
- node.js v10.11.0
- yarn 1.10.1
手順
1. nuxt.jsでプロジェクト作成
$ yarn create nuxt-app test_nuxt_app
test_nuxt_appの部分には自分のプロジェクトの名前を入れて、適宜読み替えてください。
色々と設定項目出てきますが、とりあえずこんな感じにしておきます。
実はこれでプロジェクト自体は完成です。
コマンド実行したディレクトリにtest_nuxt_appという名前のディレクトリがあるので、移動して、サーバを起動させてみます。$ cd test_nuxt_app $ yarn dev起動するとターミナル上ではこんな感じです。
powerlineとかアイコンフォントとか、色々ツッコミどころはありますが、とにかく当初の目的である、localhost:3000にブラウザからアクセスしてみましょう。
ちなみに、2019年3月24日現在ではnuxt.jsの最新バージョンは2.5.1のようです。簡単ですね。
2. git管理
余談です。
ここまでのプロジェクトをgitでリポジトリを作って管理しましょう。
新しくShoutaWATANABE/nuxt_test_appというリポジトリを作りました。
ここにコミット・プッシュします。$ git add -A $ git commit -m "[add] yarn devまで" $ git remote add origin https://github.com/ShoutaWATANABE/nuxt_test_app.git $ git push -u origin master3. 各種パッケージのインストール
さて、本番はこれからです。
以下のコマンドでまとめてパッケージをインストールします。$ yarn add -D pug@2.0.3 pug-plain-loader node-sass sass-loader @nuxtjs/style-resources $ yarn add nuxt-webfontloader nuxt-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/free-brands-svg-iconsインストールが無事に完了したら、
nuxt.config.jsを以下のように編集します。
※デフォルトコメントアウトは除外してあります。nuxt.config.jsimport pkg from './package' export default { mode: 'universal', head: { title: pkg.name, meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: pkg.description } ], link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }] }, loading: { color: '#fff' }, css: [{ src: '~/assets/scss/_normalize.scss', lang: 'scss' }], //normalize plugins: [{ src: '~/plugins/global-components.js', ssr: true }], //コンポーネントを一括読み込み modules: [ ['nuxt-webfontloader'], //googlefonts 'nuxt-fontawesome', //fontawesome '@nuxtjs/style-resources', //共通スタイルシート読み込み '@nuxtjs/axios', '@nuxtjs/pwa' ], webfontloader: { //googlefonts google: { families: ['Josefin+Sans'] } }, //googlefontsここまで fontawesome: { //fontawesome imports: [ { set: '@fortawesome/free-solid-svg-icons', icons: ['fas'] }, { set: '@fortawesome/free-brands-svg-icons', icons: ['fab'] } ] }, //fontawesomeここまで axios: { }, build: { extend(config, ctx) { if (ctx.isDev && ctx.isClient) { config.module.rules.push({ enforce: 'pre', test: /\.(js|vue)$/, loader: 'eslint-loader', exclude: /(node_modules)/ }) } } } }4. 各種ファイルの作成・編集
インストールしたパッケージが使えるように、それぞれ関係するファイルの作成・編集をします。
pages/index.vue
pages/index.vue<template lang="pug"> section.container div logo h1.title test_nuxt_app h2.subtitle My transcendent Nuxt.js project .links a.button--green( href="https://nuxtjs.org/" target="_blank" ) Documentation a.button--grey( href="https://github.com/nuxt/nuxt.js" target="_blank" ) font-awesome-icon(:icon="['fab', 'github']") |GitHub </template> <style lang="scss" scoped> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .title { font-family: 'Josefin Sans', 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } .links { padding-top: 15px; } </style>plugins/global-components.js
plugins/global-components.jsimport Vue from 'vue' import Logo from '~/components/Logo.vue' Vue.component('Logo', Logo)assets/scss/_normalize.scss
assets/scss/_nomalize.scsshtml { -webkit-text-size-adjust: 100%; overflow: auto; height: 100%; font-family: 'Avenir', 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic', 'メイリオ', Meiryo, 'MS Pゴシック', 'MS PGothic', sans-serif; } body { margin: 0; -webkit-font-smoothing: antialiased; line-height: 1.8; } h1 { font-size: 2em; margin: 0.67em 0; } hr { box-sizing: content-box; height: 0; overflow: visible; } pre { font-family: monospace, monospace; font-size: 1em; } a { background-color: transparent; } abbr[title] { border-bottom: none; text-decoration: underline; text-decoration: underline dotted; } b, strong { font-weight: bolder; } code, kbd, samp { font-family: monospace, monospace; font-size: 1em; } small { font-size: 80%; } sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } img { border-style: none; } button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; } button, input { overflow: visible; } button, select { text-transform: none; } button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } button::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } button:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } fieldset { padding: 0.35em 0.75em 0.625em; } legend { box-sizing: border-box; color: inherit; display: table; max-width: 100%; padding: 0; white-space: normal; } progress { vertical-align: baseline; } textarea { overflow: auto; } [type="checkbox"], [type="radio"] { box-sizing: border-box; padding: 0; } [type="number"]::-webkit-outer-spin-button { height: auto; } [type="search"] { -webkit-appearance: textfield; outline-offset: -2px; } [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; } details { display: block; } summary { display: list-item; } template { display: none; } [hidden] { display: none; } *, *:after, *:before { box-sizing: border-box; }タイトルのフォントが変わって、line-heightも広くなっています。
logoのコンポーネントはindex.vueでは読み込みを明記していませんが、しっかりグローバルコンポーネントとして読み込まれています。
また、githubへのリンクにfontawesomeのアイコンフォントを追記したものも、きちんと表示されていますね。
- 投稿日:2019-03-24T10:50:01+09:00
[Nuxt.js] Mismatching childNodes vs. VNodes
状況
- Consoleに以下エラーが出た。
Mismatching childNodes vs. VNodes: : [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.原因、対策
p の中に div とかネスト構造がHTML規約に準じていない時に発生するようだが、今回の場合、HTMLのコメント表記が悪さしてたようだ。
エラーになる例
<template> : <!-- comment --> xxx : </template>
- エラーにならない例
- pタグで囲っただけ
<template> : <!-- comment --> <p>xxx</p> : </template>
- 投稿日:2019-03-24T09:12:46+09:00
GraphQL APIをVueJSフロントエンドに接続する方法
この記事はMarion Schleiferのこの記事を翻訳したものです
How to connect your GraphQL API to your VueJS Frontend
I recently wrote a blogpost about how to create an API with Hasura’s GraphQL engine. If you are not familiar with…medium.com私は最近、HasuraのGraphQLエンジンを使ってAPIを作成する方法についてのブログ記事を書きました。 GraphQLにまだ慣れていない方は、まずこの記事を読むことをお勧めします。それでは、非常にシンプルなVueアプリを作成し、それをGraphQL APIに接続する方法を学びましょう。
Vueアプリの作成中に何か不明な点がある場合、または完全な解決策を見たい場合は、いつでもこのプロジェクトをGithubで見ることができます。
目次
Vueプロジェクトを作成する
映画リストコンポーネント
映画詳細コンポーネント
ムービーコンポーネントを追加する
GraphQL APIへの接続
GraphQL APIから実際のデータを取得する
映画リスト
映画を追加
この後は?
コミュニティに参加するVueプロジェクトを作成する
vue create harry-potter-appデフォルトのプリセットを選択して、お気に入りのエディタでプロジェクトを開きます。アプリの外観を少しきれいにするために、Milligramライブラリを追加します。 index.html内に
の内側に次の3行を追加します。<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"> <link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css"> <link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">端末に移動し、プロジェクトにcdして、次のようにローカルに入力してアプリケーションを実行します。
yarnの場合は:
yarn serveNpmの場合は
npm run serveVueのロゴが表示されたVueのHello Worldページが表示されます。
前回の記事で作成したHarry Potter APIを見ると、movies、characters、actors、scenesといういくつかのテーブルがあることがわかります。私たちのVueアプリでしたいことは、映画用のコンポーネントを作成することです。最後に、1つのmovieを表す複数のコンポーネントで構成されるmovieのリスト用のコンポーネントが必要です。そしてmovieを追加することを可能にするコンポーネントを作成します。
Movieリストコンポーネント
始めましょう。まず、MovieListコンポーネントをApp.vueファイルに登録します。ファイル内で、コンポーネントをに置き換える必要があります。いまはまだ失敗します。新しいコンポーネントを登録していないからです。コンポーネントリストの同じファイルのもう少し下の部分で、名前を変更したコンポーネントを正しい場所からインポートし、HelloWorldコンポーネントをMoviesListコンポーネントに置き換えます。完了したら、次のようになります。
それでは、HelloWorld.vueファイルの名前をMoviesList.vueに変更する必要があります。ファイル内で、
<template>と<script>を置き換えて、次のようになります。ここで何が起こっているかを説明します。テンプレートの内側に、v-forを使ってすべてのmovieを反復処理する
<div>を追加します。各movieの識別方法をVueに指示する必要があります。各movieの識別方法は、そのmovieのIDで行います。二重の中括弧を使用して、ブラウザに何を表示するかをVueに指示します。これは、この場合はmovieのIDです。<script>タグ内に、表示するデータを定義します。今のところ、定義したIDを持つmovieであるダミーデータを表示します。ブラウザでこのアプリを見ると、VueのロゴとmovieのIDが表示されます。Movie詳細コンポーネント
前述したように、1つのmovieを表示するためのコンポーネントも必要です。これの目的は、ダミーデータを使用する代わりに、実際のMovieItemのコレクションをMoviesListに後で表示できるようにすることです。それでは、componentsフォルダの中を進んで、MovieItem.vueという新しいファイルを作成しましょう。 MoviesListの各MovieItemについて、title、director、composer、およrelease dateを表示します。それでは、先に進み、コンポーネントをMovieItem.vueファイルに追加しましょう。
そのためには、単純な
の代わりにコンポーネントを使用するように3行目を変更する必要があります。また、8行目で、対応するファイルからMovieItemをインポートする必要があります。最後に、11行目で、MovieItemが現在のファイル内でmovieのリストのインスタンスとして使用されるコンポーネントであることを説明する必要があります。ブラウザをリロードすると、次のようになります。
movieコンポーネントの追加
それでは、新しいmovieをアプリケーションに追加する機能を追加しましょう。そのためには、componentsフォルダの中に、AddMovie.vueという新しいファイルを作成し、今のところ空のままにします。今度はApp.vueを開き、
<template>内で、<movies-list>コンポーネントの前に<add-movie />コンポーネントを追加します。また、MoviesListコンポーネントと同様に、正しい場所からコンポーネントをインポートし、AddMoviesコンポーネントをコンポーネントリストに追加する必要があります。完了したら、<template>と<script>は次のようになります。AddMovie.vueファイル内に、新しいムービーを作成するためのフォームを追加します。 APIを見ると、movieテーブルにどのようなフィールドがあるのかがわかります。今のところ、私たちはタイトル、監督、作曲家そしてリリース日を持っている映画を作成することができたいです。ファイルは次のようになります。
これはmovieのフィールドを入力して送信することを可能にする非常に単純なフォームです。まだコンポーネントにメソッドを追加していないので、送信ボタンをクリックしても何も起こりません。
ブラウザをリロードすると、次のようになります。
GraphQL APIへの接続
次のステップは、前回の投稿で作成したGraphQL APIにVueアプリを接続して、ダミーデータの代わりに実際のデータを表示できるようにすることです。それでは始めましょう。
前回の記事では、GraphiQLツールでクエリを実行する方法を説明しました。 Hasuraコンソールに移動してGraphiQLで、すべてのムービーのリストを取得するためのメソッドと、新しいムービーを追加するための機能をテストしましょう。
さて、Vueプロジェクトから、私たちはまさにこれらのメソッドを呼び出したいです。そのためには、Apolloクライアントを使います。 Apolloは、簡単な言語であなたのAPIに問い合わせをすることを可能にするGraphQLプラットフォームです。プロジェクトフォルダのコマンドラインで次のコマンドを実行して、Apolloをインストールしましょう。
npmの場合:
npm install apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql --saveyarnの場合:
yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tagまず、Hasuraプロジェクトへのリンクを確立し、Apolloをインスタンス化する必要があります。 main.jsを次のように変更します。
これを見てみましょう。最初に、インストールしたばかりのライブラリをインポートする必要があります。それらをこのファイルで使用するからです。 HttpLinkを使用して、12〜14行目でGraphQL APIへの接続を確立します。 HasuraプロジェクトのGraphiQLツールに独自のカスタムリンクがあります。
次に、16行目から20行目で、ApolloClientのインスタンスを作成します。引数として、HttpLinkを渡して、データが正しいAPIからポーリングされるようにします。次にキャッシュを渡します。 InMemoryCacheはApolloClientのデフォルトのキャッシュ実装であるため、これを使用します。最後に開発ツールに接続するためのオプションを渡します。そのため、デバッグが必要な場合に備えて、クロムインスペクタにApolloタブが表示されます。
22行目では、VueインスタンスをVueApolloを使用するように設定しています。
Vueアプリからクエリやmutationを作成できるようにするには、ApolloProviderのインスタンスを作成する必要があります(24〜26行目)。 Apolloクライアントに渡して、これらの操作をコミットします。
最後に、28行目から32行目で、Apolloプロバイダーが渡されるVueアプリを起動し、アプリ全体でデータベースのクエリと操作を行えるようにします。
GraphQL APIからデータを取得する
これまでのところ、Vueフロントエンドにダミーデータを表示しています。さて、実際のデータを表示したいです。 MoviesListには、現在データベースに保存されているmovieを表示します。また、映画をデータベースに保存してフロントエンドに表示できるように、movieを追加できるようにしたいと考えています。
Moviesリスト
データベースから映画を表示するには、MoviesList.vueを次のように変更しましょう。
<script>タグの中に、GraphQLをインポートする必要があります。これにより、クエリを実行できるようになります。 11〜21行目では、映画のリストを取得するためのGraphQLクエリを定義しています。デフォルトのexport default関数内で、ダミーデータを削除し、データベースからポーリングした映画のリストを返します。また、ApolloにどのクエリをAPIに送るべきかを伝える必要があります。そして先に定義したGET_MOVIESクエリを渡します。
ブラウザを更新すると、データベースから映画の全リストを見ることができます。
やったできました!
movieの追加
これまでのところ、フォームを送信しても何も起こりません。私たちは今、私たちのフォームの上に映画を追加できるようにしたいです。これには、AddMovie.vueファイルにいくつかの変更を加える必要があります。
繰り返しになりますが、
<script>タグの中に、GraphQLをインポートする必要があります。また、InMemoryCacheをインポートする必要があります。その理由はほんの少しのうちにわかります。17〜39行目で、データベースに映画を追加するためのmutationを定義します。まず、渡されるパラメータを定義します。次に、movieを挿入するためのオブジェクトとして渡します。そして第三にmutationの戻り値が先程作成したmovieのidであることを記述します。
export default関数内の最も重要な変更は52〜67行目に反映されており、送信ボタンがクリックされたときの動作を定義しています。まず、ボタンをクリックしたときに送信されるフォームのデフォルトの動作を防ぐ必要があります。次にmutationに渡すデータを定義します。次に、$ apolloインスタンスへのmutationを呼び出し、オブジェクト作成のために変数を渡します。最後に、movieリストを更新する必要があります。新しく追加されたmovieがリストに表示されるようにブラウザをリロードしたくないためです。これにはキャッシュが必要です。以前にmovieをリストするためにクエリを実行したので、キャッシュからそれを再フェッチすることができます。
この後は?
よくできました!これで小さなVueアプリを作成しそれをGraphQL APIに接続しました。これはとても簡単でしたね。しかし、これはほんの始まりです!我々のアプリに追加できるもっとたくさんの機能があります。たとえば、映画の更新や削除などです。または各映画のキャラクターとそれに対応する俳優を表示します。 Hasuraのドキュメントを調べてください。このアプリを拡張するために必要なものすべてを見つけることができます。
コミュニティに参加しましょう
Hasuraはますます多くの開発者によって使用されています。フレンドリーなコミュニティに参加して、最新情報を入手してください。
Hasuraであなた自身のプロジェクトを構築したい場合は、ここからdiscordのチャンネルに参加することができます。すでにかなり大きなコミュニティがあり、あなたはすぐに助けを得るでしょう。
️⭐️Github
- 投稿日:2019-03-24T08:57:33+09:00
GraphQL APIの初心者向けガイド
GraphQL APIの初心者向けガイド
この記事はMarion Schleiferの以下の記事を翻訳したものです。
Beginner’s Guide to GraphQL API
Today, I want to show you how to build serverless backends with the Hasura GraphQL API. After looking at GraphQL and…medium.comこんにちはHasura GraphQL APIを使用してサーバーレスバックエンドを構築する方法を説明します。 GraphQLとその利点を見た後で、API用に新しいテーブルを作成する方法を学びます。 次に、データベースに実際のデータを入れます。 もちろんテーブル間の関係の作り方も紹介します。 最後に、mutationを使ってデータを操作する方法を学びます。 このチュートリアルを進めるにあたりGraphQLに関する知識は必要ありません。
目次
GraphQLについて
Hasuraについて
プロジェクトを作成する
テーブルを作成する
データを挿入
問い合わせ
関係
オブジェクトの関係
配列の関係
多対多の関係
mutation
コミュニティに参加するGraphQLについて
GraphQLはAPIのための型付けされたクエリ言語です。 Facebook、Twitter、GitHubなど大手企業を含む、ますます多くのテクノロジー企業が、広く使われているREST APIからGraphQLソリューションに移行しています。 RESTのような他のAPIアーキテクチャに対するGraphQLの主な利点は次のとおりです。
わかりやすいクエリで必要なデータを正確に入手
信頼できる結果と役に立つエラーメッセージを入手
データはサーバーを介さずに直接アクセスされるため、高速で軽量なアプリケーションになる
複数のURLを使用せずにリソースを連鎖させることなく、1回のクエリーで複数のリソースを取得
すべてのデータに1つのエンドポイントを使用し、正しいデータアクセスには入力フィールドを使用できる
既存のアプリケーションにGraphQLを簡単に追加できる
これがサンプルクエリです:
これに対応する結果:
ご覧のとおり、クエリは非常に直感的であり結果は予測可能です。 あなたはまさに求めているものを手に入れました。
Webアプリケーションがより複雑になる傾向があるので、速くて簡単に保守できる解決策の必要性が高まっています。 それでGraphQLをチェックするには今がおそらくいい時期です。 しかし、どうやって使うのでしょうか。 自分のサーバーを構築する必要がありますか? フロントエンドにそれをどのように接続できますか? 心配しないで、これからそのあたりを紹介しますね。
Hasuraについて
HasuraはDockerコンテナ内で動作するオープンソースのGraphQLエンジンを提供しています。 Hasuraはプロジェクトで作成されたPostgresデータベースに接続します。 Hasura GraphQLを既存のプロジェクトの上で実行することもできます。 これはGraphQLに移行したい場合に特に便利です。移行を少ないステップで実行できるからです。
GraphQL APIをどこにデプロイできるかについては、Heroku、Docker、Digital Ocean、Azure、AWS、およびGoogle Cloudといういくつかのオプションがあります。
プロジェクトの作成
この記事では、Herokuを始めます。 ハリーポッターのAPIを作成します?
ダッシュボードで、あなたのapiのための(ユニークな)名前を選択して、「Deploy」ボタンをクリックしてください。
簡単でしたね。 今、あなたが一番下までスクロールすると、あなたはこれを見るでしょう:
「View」をクリックすると、HasuraコンソールはGraphiQLツールで開きます。これもGraphQLを使いやすくするための機能の1つです。 Hasuraコンソールでは、テーブルを作成してクエリをテストできます。 コンソールを見てみましょう。
これは、apiと対話するために使用される(唯一の)エンドポイントです。 後で外部サービスからデータをポーリングするとき、これはアクセスする必要があるURLです。 ここで確認できますが、GraphQLへのリクエストは常にPOSTリクエストです。
ここで、リクエストヘッダを追加することができます。 後で(たとえばJWTトークンを使用して)認証を追加したい場合は、ここにヘッダーを追加できます。
これは、クエリをテストできる分野です。
ここに結果が表示されます。
次にそこに行き、最初のテーブルを作成します。
テーブルの作成
まず、映画を保存するためのテーブルを作成します。 それをしましょう!
テーブルの名前
すべてのテーブルにIDを追加することをお勧めします。 整数またはUUIDを使用できます。 UUIDを使います。 オブジェクトを作成するたびにIDを渡す必要はないので、Hasuraは自動的にそれを作成する方法を提供します。
さらにフィールドを追加する
テーブルごとに主キーを定義する必要があります。 これが私たちのIDになります。
次の2つのテーブルを作成してください。
characters (id: UUID, name: Text, hair_color: Text, house: Text, wizard: Boolean, birth_year: Integer, patronus: Text)
actors (id: UUID, name: Text, birth_year: Integer, awards: Integer)
データの追加
データを追加してみましょう:
もう1人のcharacterと、2つのmovieと2つのactorを追加します。
クエリ
データができたので、GraphiQLで最初のクエリを実行できます。
このクエリはcharactersテーブルに挿入した2つのcharacterを返します。
クエリに追加できる制約はたくさんあります。 たとえば、特定の数のオブジェクトだけを取得するようにすることができます。 あるいは、特定の条件が満たされているオブジェクトのみを取得します。 これらすべてはHasuraのサイトに分かりやすく紹介されています:https://docs.hasura.io/1.0/graphql/manuals/query/simple-object-query.html
この説明を読んでクエリを微調整することで異なる結果が返ってきます。そのためには、さらにデータを追加する必要があるかもしれません。
関係
現在、互いに独立した3つのテーブルがあります。 クエリを使用すると、movie、character、actorを取得できます。 しかし、私たちは彼らの性格を持つmovieを、またそれぞれのactorを辿ってmovieを検索することはできません。 これを行うためには、関係を定義する必要があります。
関係には、オブジェクトの関係と配列の関係の2種類があります。 オブジェクト関係は1対1の関係です。 たとえば、characterはactorと呼ばれる単一のネストされたリソースを持ちます。 配列の関係は1対多の関係です。 たとえば、movieにはsceneと呼ばれるネストされたリソースの配列があります。
オブジェクト関係
まず、characterとactorの関係をモデル化しましょう。 最初のステップは、charactersテーブルにactor_idを追加することです。
actor_idカラムを追加したら、それを編集しactor_idが実際にactorsテーブルを指す外部キーであることを確認する必要があります。
characterの“ Relationships”タブに移動すると、提案された関係が表示されます。 そうです、Hasuraは外部キーを自動的に検出し、関係について提案をします。 名前として、私たちは “actor”を使います。
次に、charactrをデータベース内の対応するactorに接続します。 charactersテーブルを見てみると、前に作成したcharacterのactor_idがNULLであることがわかります。 データを編集して、これらのcharacterに対応するactorのidを追加できます。
これでcharacterとactorはリンクされました。 こうして一つのクエリでcharacterとactorフィールド両方にアクセスできます。
配列の関係
配列の関係は1対多の関係です。 つまり、テーブルの1つのオブジェクトに別のテーブルの複数のオブジェクトを含めることができます。 この例では、1つの映画に複数のsceneを含めることができ、各sceneは1つのmovieに属します。 sceneには、ID、name、location、およびmovie_idがあります。 それでは、データベースに”scenes”という新しいテーブルを作成しましょう。
すばらしいです! さて、前と同じように、テーブルを修正してmovie_idを外部キーにする必要があります。
これでmoviesテーブルに移動して”Relationships”をクリックすると、sceneに対して提案される配列の関係がわかります。 この関係を追加して、それを”scene”と呼びましょう。 配列関係を作成するために必要なのはこれだけです。 それをテストするには、scenesテーブルにいくつかの行を挿入します。
多対多の関係
前に説明したように、配列の関係は1対多の関係です。 しかし、movieやcharacterの場合、多対多の関係があります。 1つの映画に複数のcharacterを含めることができ、1つのcharacterを複数の映画に含めることができます。 このシナリオでは、「movie_characters」と呼ぶ結合テーブルを作成する必要があります。このテーブルには、1つの映画と1つのキャラクタの関係を格納できます。 ID、movie_id、およびcharacter_idを持つ”movie_characters”テーブルを作成しましょう。
movie_charactersテーブルの観点からは、moviesテーブルとcharactersテーブルの両方に対するオブジェクトの関係が必要です。 これは、各movie_characterに1つの映画と1つのキャラクタが格納されているためです。
上記のように、movie_characterテーブルのmovie_idとcharacter_idの両方を編集し、それらが外部キーになるようにチェックボックスを選択します。 次に、正しい参照表と参照列を追加してください。 movie_idの場合、参照テーブルは映画であり、character_idの場合、参照テーブルはcharactersです。 両方の参照列の場合、それはIDになります。
“Relationships”タブをクリックすると、オブジェクトの関係について2つの提案が表示されます。 両方を追加して、それらを”movie”と”character”と呼びます。 追加すると、次のようになります。
もちろん、上記のように、データとの関係を作成する必要があります。 IDを使用して、各movieとcharacterの関係についてmovie_charactersテーブルに新しい行を作成します。
やった!テーブルはすべて正しい関係でモデル化されています。 クエリでテストしましょう。
これは素敵ではないですか? たった1回のクエリで、欲しいフィールドを使っていくつかのリソースにアクセスすることができます。 通常オブジェクト全体が返されるRESTと比較して、クエリを基本要素に減らすことができます。 これにより、APIはより効率的になり、軽量になり、扱いやすくなります。
Mutations
これまでは、APIからデータを取得する方法を学びました。 しかし、新しいデータを追加するのはどうですか? movieやcharacterを追加したいとときもありますね。 そのためにはmutationが必要です。 mutationはGraphQL APIで使いやすく、クエリと同じように、Hasuraコンソールで試すことができます。
ここで何が起こっているのか説明しましょう。mutationの中では、データベースに格納されているリソースに対して、insert、update、deleteなどのさまざまなメソッドを呼び出すことができます。今回の場合は、新しいmovieを追加します。movieをオブジェクトとして渡す必要があり、1つのmutationに複数のオブジェクトを追加することが可能です。最後に、何かを返す必要があります。この例では新しく作成されたオブジェクトのIDです。
繰り返しになりますが、HasuraのWebサイトにmuatationに関する完璧な説明があります。https://docs.hasura.io/1.0/graphql/manuals/mutations/index.html
例を見て、movieを削除するなど、他のmutationを試してみてください。
今日はここまでです。GraphQLについて楽しく学び、Harry Potter APIをたくさんの新しいテーブルとデータで拡張したいと願っています。何かがはっきりしない場合は、いつでもmarion.schleifer@gmail.comに電子メールを送ったり、Twitterでメッセージを送ったりできます。https://twitter.com/rubydwarf。
アップデートをお楽しみに。 HasuraバックエンドをVueJSフロントエンドに接続する方法についてのブログポストをもう1つ公開します?
コミュニティーに参加しよう
Hasuraは、ますます多くの開発者によって使用されています。 フレンドリーなコミュニティに参加して、最新情報を入手してください。
あなたがHasuraであなた自身のプロジェクトを作り始めたいなら、このリンクからdiscordのチャンネルに参加することができます:https://discordapp.com/invite/hasura
すでにかなり大きなコミュニティがあり、あなたはすぐに助けを得るでしょう。
?Twitter:https://twitter.com/hasurahq
- 投稿日:2019-03-24T00:55:25+09:00
バックエンド:railsAPI(heroku),フロントエンド:Vue(netlify)で連携
概要
バックエンドでrails、フロントではVueを使いたい。
連携させるにはrailsでAPIサーバを作成してVueで呼ぶのがいいかと考えました。
しかしまとまったやり方が調べても出てこなかったので自分なりのやり方をまとめました。
rails,vue-cliでプロジェクト作成可能な状態であり
heroku,netlifyのアカウントは登録済みとします。環境
Rails: 5.1.6.2
vue-cli: 3.3.0
heroku: 7.22.7 darwin-x64 node-v11.10.1手順
railsプロジェクトの準備
railsアプリを作成する
まずはAPI用のrailsプロジェクトを作成$ rails new ***-api --api $ cd ***-apigemファイルの以下を変更し bundle install します。
生成時からの修正点は以下
- コメントアウト部分を削除
- gemファイルの本番環境にpostgreを追加
- sqliteを開発環境に限定
- herokuはsqliteでなくpostgreを使用するため
- rack-corsを追加
- クロスドメイン対策(後述)で使用
Gemfilesource 'https://rubygems.org' git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "https://github.com/#{repo_name}.git" end gem 'rails', '~> 5.1.6', '>= 5.1.6.2' gem 'puma', '~> 3.7' gem 'rack-cors' group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do gem 'sqlite3' gem 'listen', '>= 3.0.5', '< 3.2' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end group :production do gem 'pg' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]config/initializers/cors.rbのコメントアウトを解除し
originsを'*'に修正します。
先ほどのrack-corsの追加と本設定をしないとvueからAPIを呼び出した際にエラーが発生します。
(参考)【Ruby on Rails】「No 'Access-Control-Allow-Origin' header is present on the requested resource」を回避する.config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' #コメントアウトを解除し、ここを'*'に修正 resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end endjsonで渡す用のデータを作成 今回はサンプルとしてUserテーブルを作ります
$ rails generate scaffold user name:string email:string password_digest:string $ rails db:migrate適当なデータを入れておきましょう
$ rails console >user1 = User.new(name:"hoge_name",email:"hoge_email",password_digest:"hoge_pass") >user1.save >user2 = User.new(name:"fuga_name",email:"fuga_email",password_digest:"fuga_pass") >user2.save >User.all >exit取得できるか確認します。
$ curl -X GET -H 'Content-Type:application/json' http://0.0.0.0:3000/users #結果表示以上ができたらherokuにアップロードしておきましょう。
#commit,herokuログイン後 $ git push heroku master $ heroku run rake db:migrate #herokuのdb準備herokuにアップロードされたアドレス https://***.herokuapp.com/users
にアクセスしjsonデータが表示されればOKです。Vueプロジェクトの準備.
Vueプロジェクトを作成します。作成後はとりあえず動くか確認しましょう。
プリセット設定はなんでも大丈夫だと思います。$ vue create *** ~~プリセット設定~~ $ cd *** $ yarn serve #動作確認今回はサンプルとして初期表示ページでAPIからjsonを取得して表示したいと思います。
json取得のためaxiosをインストールします$ npm install axios --save次にsrc/components/HelloWorld.vueを以下のように編集します。
src/components/HelloWorld.vue<template> <div class="hello"> <h1>{{ msg }}</h1> <h1>{{ info }}</h1> </div> </template> <script> import axios from 'axios' export default { name: 'HelloWorld', props: { msg: String, }, data() { return { info: "" } }, mounted () { axios.get("http://localhost:8080/users").then(response => (this.info = response)) } } </script>上記を保存し yarn serve すると、ページ上にuserのデータが表示されます。
ここまでできたらvueプロジェクトもgithub等にpushしnetlifyで公開してしまいましょう。
(参考)vue-cliでwebアプリケーションを作って、Netlifyを使って無料で爆速でリリースした話(リリースまででOK)本番環境の連携
さて、ここまででローカルでの連携は終わりました。
しかしnetlifyで公開したページを見るとjsonデータは表示されずエラーを吐いています。
axios.getでlocalhostを参照しているので当然ですね。
ここからは本番環境(heroku-netlify間)でも連携するように修正していきます。vueプロジェクトのフォルダに環境変数を設定するフォルダを作成します。
$ touch .env $ touch .env.development内容に以下を追記します。
.envVUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/.env.developmentVUE_APP_BASE_API=http://localhost:3000/HelloWorld.vueのaxios.getについて、参照先を環境変数から取得するよう修正します
src/components/HelloWorld.vuemounted () { axios.get(process.env.VUE_APP_BASE_API + "users").then(response => (this.info = response)) }process.env.VUE_APP_*** は先ほど作成したenvファイルから値を取得します
yarn serveで起動した時は.env.development、
yarn buildで作成したファイルは.envから値を取得します。
これによりローカルではlocalhost,netlify上ではheroku-appを参照するようになります。
(参考)vue-cli 3.0 で作成したプロジェクトの環境変数(.env)の設定後述しますが、環境変数には他の人に見られたくないものを書いたりするので
gitignoeに忘れずに登録しておきましょう。gitignore.env .env.*API認証の設定
現在の状況ではURLを知っていれば誰でもuserの中身がわかってしまいます。
なので認証をつけてVueのプロジェクトからしか見られないようにします。application_controllers.rbに認証機能を追記します。
これによりいずれのアクションが起動する際も認証が必要となります。app/controllers/application_controllers.rbclass ApplicationController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods before_action :authenticate def authenticate #環境変数API_TOKENがrailsとvueで一致しないと認証されない authenticate_or_request_with_http_token do |token,options| token == ENV.fetch('API_TOKEN') end end endAPI_TOKENは環境変数で設定します。
こんかいは先ほどと違い、自身の端末に設定します。$ export API_TOKEN=xxxxx(任意の文字列 予測できないランダムなものが望ましい)本番環境(heroku)でも同様の文字列を環境変数に設定します。
$ heroku config:set API_TOKEN=xxxxx次にvueプロジェクトで同様のトークンキーを設定します。
.envVUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/ VUE_APP_API_TOKEN=xxxxx.env.developmentVUE_APP_BASE_API=http://localhost:3000/ VUE_APP_API_TOKEN=xxxxx最後にHelloWorld.vueのmountedを編集します
headersにトークンを追加します。src/components/HelloWorld.vuemounted () { axios .get(process.env.VUE_APP_BASE_API + "users",{ headers : { "Authorization": "Token " + process.env.VUE_APP_API_TOKEN } }).then(response => (this.info = response)) }上記で本番環境でもAPI連携が可能になりました。
おわりに
APIサーバ作るのも初めてだったのでなにか不備があればご指摘お願いします。
テスト環境とかも作らなければ…そのたお世話になった記事
- 投稿日:2019-03-24T00:55:25+09:00
railsAPI+Vue.jsの開発環境構築(heroku,netlify使用)
概要
バックエンドでrails、フロントではVueを使いたい。
連携させるにはrailsでAPIサーバを作成してVueで呼ぶのがいいかと考えました。
しかしまとまったやり方が調べても出てこなかったので自分なりのやり方をまとめました。
rails,vue-cliでプロジェクト作成可能な状態であり
heroku,netlifyのアカウントは登録済みとします。環境
Rails: 5.1.6.2
vue-cli: 3.3.0
heroku: 7.22.7 darwin-x64 node-v11.10.1手順
railsプロジェクトの準備
railsアプリを作成する
まずはAPI用のrailsプロジェクトを作成$ rails new ***-api --api $ cd ***-apigemファイルの以下を変更し bundle install します。
生成時からの修正点は以下
- コメントアウト部分を削除
- gemファイルの本番環境にpostgreを追加
- sqliteを開発環境に限定
- herokuはsqliteでなくpostgreを使用するため
- rack-corsを追加
- クロスドメイン対策(後述)で使用
Gemfilesource 'https://rubygems.org' git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "https://github.com/#{repo_name}.git" end gem 'rails', '~> 5.1.6', '>= 5.1.6.2' gem 'puma', '~> 3.7' gem 'rack-cors' group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do gem 'sqlite3' gem 'listen', '>= 3.0.5', '< 3.2' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end group :production do gem 'pg' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]config/initializers/cors.rbのコメントアウトを解除し
originsを'*'に修正します。
先ほどのrack-corsの追加と本設定をしないとvueからAPIを呼び出した際にエラーが発生します。
(参考)【Ruby on Rails】「No 'Access-Control-Allow-Origin' header is present on the requested resource」を回避する.config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' #コメントアウトを解除し、ここを'*'に修正 resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end endjsonで渡す用のデータを作成 今回はサンプルとしてUserテーブルを作ります
$ rails generate scaffold user name:string email:string password_digest:string $ rails db:migrate適当なデータを入れておきましょう
$ rails console >user1 = User.new(name:"hoge_name",email:"hoge_email",password_digest:"hoge_pass") >user1.save >user2 = User.new(name:"fuga_name",email:"fuga_email",password_digest:"fuga_pass") >user2.save >User.all >exit取得できるか確認します。
$ curl -X GET -H 'Content-Type:application/json' http://0.0.0.0:3000/users #結果表示以上ができたらherokuにアップロードしておきましょう。
#commit,herokuログイン後 $ git push heroku master $ heroku run rake db:migrate #herokuのdb準備herokuにアップロードされたアドレス https://***.herokuapp.com/users
にアクセスしjsonデータが表示されればOKです。Vueプロジェクトの準備.
Vueプロジェクトを作成します。作成後はとりあえず動くか確認しましょう。
プリセット設定はなんでも大丈夫だと思います。$ vue create *** ~~プリセット設定~~ $ cd *** $ yarn serve #動作確認今回はサンプルとして初期表示ページでAPIからjsonを取得して表示したいと思います。
json取得のためaxiosをインストールします$ npm install axios --save次にsrc/components/HelloWorld.vueを以下のように編集します。
src/components/HelloWorld.vue<template> <div class="hello"> <h1>{{ msg }}</h1> <h1>{{ info }}</h1> </div> </template> <script> import axios from 'axios' export default { name: 'HelloWorld', props: { msg: String, }, data() { return { info: "" } }, mounted () { axios.get("http://localhost:8080/users").then(response => (this.info = response)) } } </script>上記を保存し yarn serve すると、ページ上にuserのデータが表示されます。
ここまでできたらvueプロジェクトもgithub等にpushしnetlifyで公開してしまいましょう。
(参考)vue-cliでwebアプリケーションを作って、Netlifyを使って無料で爆速でリリースした話(リリースまででOK)本番環境の連携
さて、ここまででローカルでの連携は終わりました。
しかしnetlifyで公開したページを見るとjsonデータは表示されずエラーを吐いています。
axios.getでlocalhostを参照しているので当然ですね。
ここからは本番環境(heroku-netlify間)でも連携するように修正していきます。vueプロジェクトのフォルダに環境変数を設定するフォルダを作成します。
$ touch .env $ touch .env.development内容に以下を追記します。
.envVUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/.env.developmentVUE_APP_BASE_API=http://localhost:3000/HelloWorld.vueのaxios.getについて、参照先を環境変数から取得するよう修正します
src/components/HelloWorld.vuemounted () { axios.get(process.env.VUE_APP_BASE_API + "users").then(response => (this.info = response)) }process.env.VUE_APP_*** は先ほど作成したenvファイルから値を取得します
yarn serveで起動した時は.env.development、
yarn buildで作成したファイルは.envから値を取得します。
これによりローカルではlocalhost,netlify上ではheroku-appを参照するようになります。
(参考)vue-cli 3.0 で作成したプロジェクトの環境変数(.env)の設定後述しますが、環境変数には他の人に見られたくないものを書いたりするので
gitignoeに忘れずに登録しておきましょう。gitignore.env .env.*API認証の設定
現在の状況ではURLを知っていれば誰でもuserの中身がわかってしまいます。
なので認証をつけてVueのプロジェクトからしか見られないようにします。application_controllers.rbに認証機能を追記します。
これによりいずれのアクションが起動する際も認証が必要となります。app/controllers/application_controllers.rbclass ApplicationController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods before_action :authenticate def authenticate #環境変数API_TOKENがrailsとvueで一致しないと認証されない authenticate_or_request_with_http_token do |token,options| token == ENV.fetch('API_TOKEN') end end endAPI_TOKENは環境変数で設定します。
こんかいは先ほどと違い、自身の端末に設定します。$ export API_TOKEN=xxxxx(任意の文字列 予測できないランダムなものが望ましい)本番環境(heroku)でも同様の文字列を環境変数に設定します。
$ heroku config:set API_TOKEN=xxxxx次にvueプロジェクトで同様のトークンキーを設定します。
.envVUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/ VUE_APP_API_TOKEN=xxxxx.env.developmentVUE_APP_BASE_API=http://localhost:3000/ VUE_APP_API_TOKEN=xxxxx最後にHelloWorld.vueのmountedを編集します
headersにトークンを追加します。src/components/HelloWorld.vuemounted () { axios .get(process.env.VUE_APP_BASE_API + "users",{ headers : { "Authorization": "Token " + process.env.VUE_APP_API_TOKEN } }).then(response => (this.info = response)) }上記で本番環境でもAPI連携が可能になりました。
おわりに
APIサーバ作るのも初めてだったのでなにか不備があればご指摘お願いします。
テスト環境とかも作らなければ…そのたお世話になった記事































