20210413のvue.jsに関する記事は11件です。

普遍的な atom と molecule の境界を意識したコンポーネント設計

はじめに Dwango でニコニコ生放送のフロント開発を担当している @misuken です。 AtomicDesign でコンポーネントを作る際 atom と molecule をどこで分けるかに悩むことが多いと思うので、今回は普遍的な atom と molecule の境界を意識したコンポーネント設計に関しての話をします。 この記事のスコープ atom と molecule の境界のみにスコープを当てた内容になります molecule と organism の境界には触れません(最後まで読むとその理由がわかります) JSX で説明しますが、 React でも Vue でもこの記事の内容が有効です 悩ましい粒度の境界 AtomicDesign が話題になってから結構時間が経ちました。 しかし、実践してみたもののそのままだとうまくいかないところが出てくるという話を未だよく目にします。 大抵は AtomicDesign をそのまま実践することよりも、開発チーム内で共通認識を持てることを落とし所として、AtomicDesign を参考にチーム独自のルールを追加して運用していることが多いのではないでしょうか。 しかし、開発チームのメンバーごとにも捉え方の違いがあったり、チーム編成が変わったり、新しい案件でチームが変わったり、その都度折り合いを付けていくのは大変です。折り合いを付ける度に主観が衝突し、妥協が発生し、徐々にコンポーネントの秩序が失われていきます。 もっと幅広く共有できる指針があれば、開発はもっと楽になるのではないでしょうか。 ということで、普遍的な atom と molecule の境界を手に入れて、迷うポイントを減らしていきましょう。 本題 このような <button>push</button> HTML を出力するボタンのコンポーネントがあったとします。 これは誰もが atom に分類するでしょう。 しかし、アイコンと組み合わせたボタンの場合はどうでしょう。 <button><svg />push</button> IconButton として molecule にする atom はこれ以上分解できない単位じゃないといけないので、アイコンを内包した時点で molecule になる IconButton として atom にする Button を atom とする以上、他の要素を内包しても atom とし続ける Button に svg を渡せるようにするだけだから atom のままにする Button 自体を拡張するだけで IconButton というコンポーネントは作らない この時点で人によって粒度の解釈が変わり、問題が生じ始めます。 表現パターンと粒度 実際に開発を進めると、ボタンを以下のような表現パターンで使用したいシーンが頻繁に訪れます。 3つ目はアクセシビリティを高めつつ css の content: attr(aria-label); を使用してホバーツールチップボタンにする際に使用されます。 <button>push</button> <button><svg />push</button> <button aria-label="push"><svg /></button> これら表現パターンの違いで粒度を変えるべきなのでしょうか? あなたのプロジェクトのコンポーネントは探しやすいですか? コンポーネントを探しやすいというのは「あのコンポーネントどこにあったっけ?」というときに、すぐ実装されている場所がわかるということです。 例えば IconButton コンポーネントを探そうとしたとき、以下のディレクトリまで来て、あなたは atoms と molecules のどちらに進みますか? /atoms /molecules 「チームの方針による」といったところでしょうか。 では、 CloseButton を探すときはどちらに進みますか? このとき最悪のパターンは以下です。 実装で svg を含んでいなかったら atoms 実装で svg を含んでいたら molecules つまり、実装に引きずられて粒度が変わるパターンを採用したら、コンポーネントは必ず探しにくくなるということを示しています。 実装がわからないと場所を判断できない 場所がわからないと実装を確認できない これはデッドロックに陥った状態と言えます。 CloseIconButton にすればよいのでは? CloseButton と CloseIconButton で分ければ多少マシかもしれません。 しかし、本当にアイコンの有無で atoms と molecules を分けたいでしょうか? 本心はそうではないはずです。 実際の開発中にありがちなシーンをイメージしてみましょう。 閉じるボタンをここに置きたいです atoms/close-button/* 閉じるボタンにアイコンを付けられますか? atoms/close-button/* → molecules/close-icon-button/* やっぱりアイコン無いほうが良いので外してください molecules/close-icon-button/* → atoms/close-button/* 「ええと、今は閉じるボタンってどう実装されてたっけ・・・」 ちょっとした見た目の変更の度にコンポーネント名が変わったり、粒度が変わってディレクトリを行き来してしまっては、筋の良い方法とは言えません。 軽微な変更内容に対して、利用先でパスやコンポーネント名の変更が必要になるなど、破壊的変更のインパクトが大きくなりすぎてしまいます。 表現方法の違い 関心があるのは閉じるボタンであって、アイコンが付いてるかどうかは表現方法の違いでしかありません。表現方法の変更では破壊的変更が生じないようしたほうが明らかに得策です。 では、どのようにすれば変更に強いコンポーネントを作れるのかを考えてみましょう。 CloseButton を例にあげます。 以下の3つは表現は違えど、動作も意味も同じです。 <button>閉じる</button> <button><svg />閉じる</button> <button aria-label="閉じる"><svg /></button> これらの違いは全て CloseButton が良しなに吸収してくれたほうが使いやすいコンポーネントであると言えます。 さらに突き詰めると、 "Close" の部分に依存しているのはアイコンとテキストの内容だけなので、値とレイアウトに分離し Button の標準機能で吸収できることがわかります。 // 実装イメージ const closeButtonProps = { symbolIcon: <svg />, text: "閉じる" }; // 同じ props を渡し、レイアウトの指定だけで表現方法を切り替える <Button layout="textOnly" {...closeButtonProps} /> <Button layout="default" {...closeButtonProps} /> <Button layout="ariaLabel" {...closeButtonProps} /> こうすることで、 Button コンポーネントが表現の違いを吸収する機能を提供するようになり、表現の違いによる破壊的変更の発生を防げます。 その他の表現パターン Button と同様の表現パターンは Anchor や Item(メニューの項目) といったテキストを表現する系のコンポーネントで広く共通する部分です。 シンボルとなるアイコンだけでなく、以下のような表現が必要になる場合もあります。 ショートカットキーの表示 リスト項目の右端に表示される > などの項目選択時のアクションを表すアイコン これらも基礎的なコンポーネントで吸収することで、粒度を安定させることができます。 粒度の境界 ここまでの説明を踏まえ、改めて粒度の境界を探ってみましょう。 表現パターンの違いで内包する要素が現れたとしても、それは粒度に影響を与えないということがわかっているので atom と molecule の境界がどこなのかを表現するとこうなります。 内包する構成要素が無ければ意味を成さないコンポーネントかどうか です。 molecule の例 構成要素を持つことが前提で、内包する構成要素が無ければ意味を成さないコンポーネントは molecule になります。 例えば、 List や Form といったコンポーネントは molecule になります。 出力する HTML が <ul></ul> や <form></form> だけの状態なら atom と言えなくもないかもしれませんが、実際に利用する際は必ず内包する構成要素が存在するので List や Form は molecule です。 Button は常に atom Button の場合はアイコン等の要素を内包する場合がありましたが、それは表現の違いです。内包する構成要素が無くてもボタンとしての意味を成しており、その点で molecule とは明確な違いが存在します。 これはボタンが主体のコンポーネントなら、中に何が入ろうがそれは atom の条件を満たしていることを表し、それらのコンポーネント名の末尾は常に Button で終わるということを意味します。 画像のボタンの例 例えばテキストの代わりに画像 <img src="foo.png" alt="push" /> が表示されたとしましょう。 画像に文字が書いてあるかもしれませんし、画像にイラストが描いてあるかもしれませんが、画像の内容によって粒度が変わるのは不自然です。テキストの文字列で表示しようが、画像の文字列で表示しようが、伝えようとしていることやボタンとしての機能に変化はありません。 やはりこれらは表現の違いでしか無く、本質的にはただのボタンであると言えます。 普遍的な粒度の境界 これらを踏まえると、普遍的な Button = atom の関係が定まっていると言えるでしょう。 他の全てのコンポーネントに対しても同じ方法で整理できるので、コンポーネント名の末尾を見れば、それが atom か molecule かを判断できるようになります。 このようにコンポーネントの末尾に UI の型を表す名前を明示する手法は BCD Design で有効な手段であることがわかっているので、是非参考にすると良いでしょう。 改めて問います 以下のディレクトリ構造で CloseButton はどっちにありますか? これまでの内容を理解していれば明確に atoms にあることを説明できます。 /atoms /molecules これがわかりやすさであり、個人の主観や特定のチームに依存しない普遍的な atom と molecule の境界であると言えるでしょう。 粒度は重要なのか? ここまで、 AtomicDesign の粒度の話をしてきましたが、実はコンポーネントの粒度はそこまで重要ではないとも感じています。 概念軸で分類する BCD Design を使用すると、粒度軸で得られるメリットは依存関係の明確化くらいしかなく、概念軸で得られる関心の凝集のメリットのほうが圧倒的に大きくなるためです。 もちろん、より精度を高めていく上では粒度軸も正確であることに越したことはないのですが、多くの場面では概念軸で整理すると、さらに粒度軸で整理ほどの複雑度が残らないので概念軸だけで済んでしまいます。 AtomicDesign は molecule と organism の分類が曖昧になりやすい問題もあり、 atom と molecule を見極められるようになった先にも困難が待ち受けます。 BCD Design は organism 不要で AtomicDesign で抱えるモヤモがスッキリ解消できるため、より本質的な解決を目指す場合は "概念軸" を学んでみてはいかがでしょうか。 もっと奥の深い粒度の話 TextBox SelectBox ComboBox などでもっと奥深い粒度の話を掘り下げられるのですが、長くなったので今回はここまでにしておきます。 2021-04-14 意外と単純ではない TextBox が atom である理由 を公開しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue CLIでプロジェクトを作成&起動する方法

概要 @vue/cliで新規にプロジェクトを作成し起動する方法を記載する。 手順 プロジェクトの作成 コマンドプロンプトで以下のコマンドを入力する。 $ vue create [プロジェクト名] 例:vue create sample_project その後、以下のようなpresetの選択肢が表示されるため選択する。 DefaultかManually select features(自分で細かく設定する)かが選べるが今回はDefaultを選択 Vue CLI v4.5.12 ? Please pick a preset: (Use arrow keys) > Default ([Vue 2] babel, eslint) Default (Vue 3 Preview) ([Vue 3] babel, eslint) Manually select features 上記選択後、プロジェクトの作成が行われるのでしばらく待つと以下のような表示になる。 これが表示されたらプロジェクトの作成は完了 // 省略 success Saved lockfile. Done in 9.48s. ⚓ Running completion hooks... ? Generating README.md... ? Successfully created project sample_project. ? Get started with the following commands: $ cd sample_project $ yarn serve プロジェクトの起動 cdコマンドでプロジェクト配下に移動し、yarn serveでプロジェクトを起動する。 $ cd sample_project $ yarn serve その後、以下のような表示になったらプロジェクトの起動は成功 $ yarn serve yarn run v1.22.0 $ vue-cli-service serve INFO Starting development server... 98% after emitting CopyPlugin DONE Compiled successfully in 2329ms 23:07:11 App running at: - Local: http://localhost:8080/ - Network: http://192.168.0.9:8080/ Note that the development build is not optimized. To create a production build, run yarn build. ページにアクセス 上記のLocal:またはNetwork:に表示されているURLをブラウザに入力してみると、以下のようなページが表示される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js(@vue/cli)のインストール

概要 WindowsでのVue.js(@vue/cli)のインストール手順をまとめる。 手順 Node.js(npm)のインストール Vue.jsをインストールするためにnpmが必要なため、先にNode.js(npm)のインストールを行う。 1. インストーラーのダウンロード Node.js公式からインストーラーをダウンロードする。 2. インストーラーのダウンロード インストーラーを起動し、画面に従ってインストールを進める。 ※基本的にNextを押して進めて行けば良い。 3. インストール完了確認 インストールが完了したらコマンドプロンプトから以下のコマンドを入力し、Node.jsとnpmがインストールできていることを確認する。 $ node --version v14.16.1 // 正常にインストールできていればバージョンが表示される $ npm --version 6.14.12 // 正常にインストールできていればバージョンが表示される @vue/cliのインストール 1. インストーラーのダウンロード 以下のコマンドで@vue/cliをインストールする。 $ npm install -g @vue/cli 2. インストール完了確認 インストールが完了したらコマンドプロンプトから以下のコマンドを入力し、@vue/cliがインストールできていることを確認する。 $ vue --version @vue/cli 4.5.12 // 正常にインストールできていればバージョンが表示される バージョンが表示されず以下のようなメッセージが表示されたら何らかの問題が発生している可能性がある。 'vue' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。 筆者は上記状態になったためNode.jsとnpmをアンインストールし関連ファイルを手動で完全に削除後、再度Node.jsと@vue/cliとインストールを行ったところ正常にバージョンが表示されるようになった。 ※おそらくnpmが環境変数に追加されていなかったために発生していた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

④Vue.js & Laravel(Create...データベースにデータを保存)

こちらは10本の記事で構成されています ①Vue.js & Laravel(環境構築) ②Vue.js & Laravel(CRUDシステムの準備) ③Vue.js & Laravel(Read...データベースからデータを取得) ④Vue.js & Laravel(Create...データベースにデータを保存) ⑤Vue.js & Laravel(Update…データベースのデータを更新) ⑥Vue.js & Laravel(Delete…データベースのデータを削除) ⑦Vue.js & Laravel(リアルなダミーデータを大量に挿入) ⑧Vue.js & Laravel(検索機能の実装) ⑨Vue.js & Laravel(ページャーの実装) ⑩Vue.js & Laravel(認証機能の実装) 【目標】データベースにAPIでデータを送信し、保存する 【手順】①フロントエンドからAPIにリクエストする②APIからコントローラーにリクエストを送る③コントローラーからモデルにリクエストを送る④モデルからデータベースに保存する ①フロントエンドからAPIにリクエストする /resources/js/components/task/Task.vue <template> <div class="taskComponent"> + <form @submit.prevent="taskCreate"> + 緊急:<input v-model="newtask.emergency" type="checkbox"><br> + 内容:<input v-model="newtask.content" type="text"> + <button>create</button> + </form> + <hr> <ul> <li v-for="(task, index) in tasks" :key="index"> 緊急:<input type="checkbox" v-model="task.emergency"><br> 内容:<span :class="{red:task.emergency}">{{task.content}}</span> </li> </ul> </div> </template> <script> export default { data() { return { + newtask: { + content: "", + emergency: false, + }, tasks:[], }; }, methods: { + taskCreate() { + if (this.newtask != "") { + axios.post("/api/task/create", this.newtask).then((res) => { + this.newtask.emergency = false; + this.newtask.content = ""; + this.taskRead(); + }); + } + }, taskRead() { axios.get("/api/task/read").then((res) => { this.tasks = res.data; }); }, }, mounted() { this.taskRead(); }, }; </script> <style lang="scss" scoped> li{ list-style: none; margin-bottom: 15px; } .red{ color: red; } </style> これで「①フロントエンドからAPIにリクエストする」が達成されました ②APIからコントローラーにリクエストを送る /Applications/MAMP/htdocs/hoge/routes/api.php <?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; + Route::post('/task/create', 'TaskController@create'); Route::get('/task/read', 'TaskController@read'); これで「②APIからコントローラーにリクエストを送る」が達成されました ③コントローラーからモデルにリクエストを送る /app/Http/Controllers/TaskController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Task; class TaskController extends Controller { + public function create(Request $request) + { + Task::create([ + 'emergency' => $request->emergency, + 'content' => $request->content, + ]); + } public function read() { $data = Task::get(); return $data; } } これで「③コントローラーからモデルにリクエストを送る」が達成されました ④モデルからデータベースに保存する /app/Task.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Task extends Model { + protected $fillable = [ + 'emergency', + 'content' + ]; } これで「④モデルからデータベースに保存する」が達成されました 【達成】今回の目標「データベースにAPIでデータを送信し、保存する」が達成されました。 次回↓↓↓↓⑤Vue.js & Laravel(Update…データベースのデータを更新)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VuexをVue Composition APIで実装する!【NoVue】

こんにちは。shundroidです。5年ぶりにQiita書いてます! 大学の授業が始まって早1週間。 最初はガイダンスばかりですが、だんだん大学の授業の面白さが分かってきました。 さて、今回は、vue-composition-apiを使っていきたいと思います。 これが凄いんですよ。 おそらく、最初のモチベーションとしては、肥大化したViewModelを整理する、ということだったと思うんですが、 おなじみの watch や、リアクティブな変数を生成する reactive などをコンポーネントから分離して利用できるようになったことで、後で詳しく述べますが、幅広く使える可能性を備えていると思います。 特に、これはデータバインドを活用したアーキテクチャを設計し、プロトタイプを作るのにも向いていると考えたので、 ここでは、試しにVuexをvue-composition-apiを使って実装してみたいと思います! Composition API の基本的な機能 詳しくは、Vue.jsの公式ドキュメントが分かりやすくて、APIが作られた背景までわかるようになっているので、そちらを見てください。 今回特に使う機能を紹介していきます。 ref リアクティブな変数を生成します。似たものに reactive というのがありますが、こちらは値がオブジェクトの時に使えて、 .value を省略できます。 コードの説明はTypeScriptで行っていきます。 import { ref, Ref } from '@vue/composition-api'; // (予めVueにComposition APIを登録する必要はある:後のstore.ts参照) // どういう型になるか説明するために、型指定を行っておきます。 const num1: Ref<number> = ref(0); const num2 = num1; console.log(num1.value, num2.value); // 0, 0 num2.value++; console.log(num1.value, num2.value); // 1, 1 これだと、ただ参照値を渡しているだけで、例えば、次のようにしても良いのではないかと思うかもしれません。 const num1 = { value: 0 }; const num2 = num1; ですが、 ref の強みは、Vueの便利なリアクティブシステムに値するものがすべて使えるところにあります。 watch、computedなどです。これを次に説明します。 watch Vueでおなじみのものと同じです。 import { ref, watch, Ref } from '@vue/composition-api'; const num = ref(0); watch(num, () => { console.log('changed!'); }); num.value--; // changed! こういった機構を簡単に設計できるのです。 Vueのコンポーネントシステム以上の可能性を感じますよね。 おまけ:computed 今回は使いませんでしたが、 computed もシンプルに使えます。 import { ref, computed, Ref } from '@vue/composition-api'; const num1 = ref(0); const num2 = computed(() => num1.value + 1); console.log(num1.value, num2.value); // 0, 1 num1.value++; console.log(num1.value, num2.value); // 1, 2 すごいなあ。 こういうプログラムが普及すると、プログラムは上から実行されるんだよという説明が難しくなってきそうですよね。 もちろん間違ってはいないですが、このようなcomputed内のコールバックが実行される順番等を踏まえると、もっと別の意味づけのほうが適切なのではないか、と思えてしまいます。 プログラミング言語の命令型→宣言型への移行が垣間見える気がします。 個人的には、データフロープログラミングに興味があるので、この流れは賛成です。 computedが内部的にどのようにコールバックの実行タイミングを見極めているか、については、下の記事が分かりやすかったです。一旦中身を実行してみて、どのリアクティブ変数が取得されたか、を記録しているようですね。 条件分岐などがcomputed内であった場合は、一旦実行するのみでは足りないのではないか、と思いましたが、条件自体がリアクティブだとこれで足りそうですね。よくできてるなあ。 VuexをNo Vueで実装していく ここまでで Composition API の基本的な機能を見てきました。 Vueのコンポーネントに止まらず、もっと汎用的な使い道があるということがわかりました。 というわけで、実際の応用例として、Vuex的アーキテクチャを実装してみましょう。 今回はTodoアプリを作っていきますが、 あまり本質的でない部分は端折ります。 Actionはごめんなさい、さようなら。 そして本当はAction呼び出しは疎結合に行われますが、これはあまりメリットがないので、今回はMutation直呼びで代用します。 store.tsの実装 /store.ts import Vue from 'vue'; import VueCompositionAPI, { DeepReadonly, reactive, readonly, ref, Ref, UnwrapRef } from '@vue/composition-api'; type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>; Vue.use(VueCompositionAPI); interface TodoItem { title: string; completed: boolean; } interface State { todoList: Ref<TodoItem[]>; } const state: State = { todoList: ref([]) }; // mutations export function addTodo(title: string) { state.todoList.value.push({ title, completed: false }) } const readonlyState: { [P in keyof State]: DeepReadonly<UnwrapNestedRefs<State[P]>> } = { todoList: readonly(state.todoList) } export default readonlyState; Composition APIを使うために、仕方なくVueを呼び出していますが、 あくまでVueをアプリケーションで使うわけではないです。 stateの定義に ref を用いています。これは先ほど紹介した通りです。 本当は、Model部分はViewに影響されず、純粋なモデルにするべきですが、リアクティブ化する程度ならばいいかと思ってしまいました。どうですかね。 あと、 reactive([]) では配列はリアクティブになりません。注意してください。 mutationは普通にstateを書き換えています。 そして、ここが肝なのですが、 stateをexportするときに、stateをreadonlyにしています!!!!! 実は、ライブラリのほうのVuexはstateに双方向データバインドがかかっていて、 Mutationを介さずにバインドによってstateが書き換えられてしまうのですが、 これはどう考えてもbadなので、今回はComposition APIの機能をフル活用して、 readonlyにしています。 readonlyStateの型ですが、Composition API内部で使われている型を拝借しました。 [P in keyof Type] というのは、Mapped TypesというTypeScriptの機能です。反復処理の型バージョンみたいなやつです。 DeepReadonlyでは、 T extends U ? A : B という、型バージョンの条件分岐を使っているようです(参考:API内部)。 どうしてここまでして型を組みたいかというと、こうすることで、readonlyStateの値に関して保証ができるからです。 つまり、stateにあるのにreadonlyStateにない!とか、readonlyになっていない!とかいうことが、TypeScriptのエラーとして把握できます。 このように、ただ型がつくと嬉しい!楽しい!というだけでなく、開発上のミスが無いようにするために型を使うべきです。この使い方は never 型などでよくします。 個々のコンポーネントの実装 まずはインターフェース実装。 /components/component.ts export default interface Component { render(): HTMLElement } Todoを追加するやつ。 /components/todoList.ts import Component from './component'; import state from '../store'; import { watch } from '@vue/composition-api'; export default class TodoList implements Component { render() { const ul = document.createElement('ul'); watch(state.todoList, () => { ul.innerHTML = ''; for (let todoItem of state.todoList.value) { const li = document.createElement('li'); li.textContent = `${todoItem.title}` ul.appendChild(li) } }); return ul; } } VuexなのにtextContentとかdocumentとか出てきてて草 今回は、Composition APIの汎用性を説明したかったので、 バニラでも十分役立つんだ!ということを説明したくてこうしています。 もちろん、バリバリVue使って実装してもらってもいいです。 ここでは、state.todoListをwatchしています。 さすがに直DOMに流すのはリアクティブにはできないので仕方ない。 また、ご確認いただけると幸いですが、state.todoListはしっかりreadonlyになっているので、Viewからは改変できません!わーい じゃあViewからstateを変えるにはどうするの?というのが次のコンポーネントになります。 /components/addTodo.ts import Component from './component'; import { addTodo } from '../store'; export default class AddTodo implements Component { render() { const parent = document.createElement('div'); const input = document.createElement('input'); input.type = 'text'; parent.appendChild(input); const button = document.createElement('button'); button.textContent = 'Add a task'; button.addEventListener('click', () => { addTodo(input.value); input.value = ''; }); parent.appendChild(button); return parent; } } mutationをimportして使っています。 エントリーポイント ここはあまり大事ではないですが、コンポーネントを登録(物理)します。 /main.ts import AddTodo from './components/addTodo'; import TodoList from './components/todoList'; const app = document.querySelector('#app'); app.appendChild(new AddTodo().render()); app.appendChild(new TodoList().render()); Vueが完全に隠れていて気持ちがいいですね。 出来上がったものがこちらになります。 冷凍庫で3時間冷凍したものがGithubにあります。 実装にはViteを用いました。 速いですね。今風でいいですね。webpackもそろそろ引退かな。 デモはこちらで試すことができます。 まとめ いかがでしたでしょうか。Composition APIの汎用性が伝われば幸いです。 個人的には、アーキテクチャの実装等に興味があるので、 データバインドを活用したアーキテクチャのプロトタイプ作成などにどんどん使っていこうかと思います。 ※この記事は僕のブログを移植して書いたものです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

③Vue.js & Laravel(データベースからデータを取得)

【目標】データベースからAPIでデータを取得し、画面に表示させる 【手順】①フロントエンドからAPIにリクエストする②APIからコントローラーにリクエストを送る③コントローラーからデータベースにリクエストを送る④レスポンスを画面に表示させる ①フロントエンドからAPIにリクエストする /resources/js/components/task/Task.vue <template> <div class="taskComponent"> タスク画面 </div> </template> <script> export default { data() { return { + tasks:[], }; }, methods: { + taskRead() { + axios.get("/api/task/read").then((res) => { + this.tasks = res.data; + }); + }, }, mounted() { + this.taskRead(); }, }; </script> <style lang="scss" scoped> </style> これで「①フロントエンドからAPIにリクエストする」が達成されました ②APIからコントローラーにリクエストを送る /routes/api.php <?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; - /* - |-------------------------------------------------------------------------- - | API Routes - |-------------------------------------------------------------------------- - | - | Here is where you can register API routes for your application. These - | routes are loaded by the RouteServiceProvider within a group which - | is assigned the "api" middleware group. Enjoy building your API! - | - */ - Route::middleware('auth:api')->get('/user', function (Request $request) { - return $request->user(); - }); + Route::get('/task/read', 'TaskController@read'); これで「②APIからコントローラーにリクエストを送る」が達成されました ③コントローラーからデータベースにリクエストを送る /app/Http/Controllers/TaskController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Task; class TaskController extends Controller { + public function read() + { + $data = Task::get(); + return $data; + } } これで「③コントローラーからデータベースにリクエストを送る」が達成されました ④レスポンスを画面に表示させる /resources/js/components/task/Task.vue <template> <div class="taskComponent"> - タスク画面 + <ul> + <li v-for="(task, index) in tasks" :key="index"> + 緊急:<input type="checkbox" v-model="task.emergency"><br> + 内容:<span v-if="!task.edit" :class="{red:task.emergency}">{{task.content}}</span> + </li> + </ul> </div> </template> <script> export default { data() { return { tasks:[], }; }, methods: { taskRead() { axios.get("/api/task/read").then((res) => { this.tasks = res.data; }); }, }, mounted() { this.taskRead(); }, }; </script> <style lang="scss" scoped> + li{ + list-style: none; + margin-bottom: 15px; + } + .red{ + color: red; + } </style> これで「④レスポンスを画面に表示させる」が達成されました 【達成】今回の目標「データベースからAPIでデータを取得し、画面に表示させる」が達成されました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

③Vue.js & Laravel(Read...データベースからデータを取得)

こちらは10本の記事で構成されています ①Vue.js & Laravel(環境構築) ②Vue.js & Laravel(CRUDシステムの準備) ③Vue.js & Laravel(Read...データベースからデータを取得) ④Vue.js & Laravel(Create...データベースにデータを保存) ⑤Vue.js & Laravel(Update…データベースのデータを更新) ⑥Vue.js & Laravel(Delete…データベースのデータを削除) ⑦Vue.js & Laravel(リアルなダミーデータを大量に挿入) ⑧Vue.js & Laravel(検索機能の実装) ⑨Vue.js & Laravel(ページャーの実装) ⑩Vue.js & Laravel(認証機能の実装) 【目標】データベースからAPIでデータを取得し、画面に表示させる 【手順】①フロントエンドからAPIにリクエストする②APIからコントローラーにリクエストを送る③コントローラーからデータベースにリクエストを送る④レスポンスを画面に表示させる ①フロントエンドからAPIにリクエストする /resources/js/components/task/Task.vue <template> <div class="taskComponent"> タスク画面 </div> </template> <script> export default { data() { return { + tasks:[], }; }, methods: { + taskRead() { + axios.get("/api/task/read").then((res) => { + this.tasks = res.data; + }); + }, }, mounted() { + this.taskRead(); }, }; </script> <style lang="scss" scoped> </style> これで「①フロントエンドからAPIにリクエストする」が達成されました ②APIからコントローラーにリクエストを送る /routes/api.php <?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; - /* - |-------------------------------------------------------------------------- - | API Routes - |-------------------------------------------------------------------------- - | - | Here is where you can register API routes for your application. These - | routes are loaded by the RouteServiceProvider within a group which - | is assigned the "api" middleware group. Enjoy building your API! - | - */ - Route::middleware('auth:api')->get('/user', function (Request $request) { - return $request->user(); - }); + Route::get('/task/read', 'TaskController@read'); これで「②APIからコントローラーにリクエストを送る」が達成されました ③コントローラーからデータベースにリクエストを送る /app/Http/Controllers/TaskController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Task; class TaskController extends Controller { + public function read() + { + $data = Task::get(); + return $data; + } } これで「③コントローラーからデータベースにリクエストを送る」が達成されました ④レスポンスを画面に表示させる /resources/js/components/task/Task.vue <template> <div class="taskComponent"> - タスク画面 + <ul> + <li v-for="(task, index) in tasks" :key="index"> + 緊急:<input type="checkbox" v-model="task.emergency"><br> + 内容:<span :class="{red:task.emergency}">{{task.content}}</span> + </li> + </ul> </div> </template> <script> export default { data() { return { tasks:[], }; }, methods: { taskRead() { axios.get("/api/task/read").then((res) => { this.tasks = res.data; }); }, }, mounted() { this.taskRead(); }, }; </script> <style lang="scss" scoped> + li{ + list-style: none; + margin-bottom: 15px; + } + .red{ + color: red; + } </style> これで「④レスポンスを画面に表示させる」が達成されました 【達成】今回の目標「データベースからAPIでデータを取得し、画面に表示させる」が達成されました。 次回↓↓↓↓④Vue.js & Laravel(Create…データベースにデータを保存)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsを用いたクラス付与

Vue.jsを使って、スタイルの適用の仕方を学んだのでまとめておきます。 <div id="app"> <p v-bind:class="{large: fontLarge, org: color}"> Hello Vue.js </p> <button v-on:click="click"> クリック </button> <button v-on:click="large"> ラージ </button> <h2 v-bind:class="color? aqua : ''">ようこそ</h2> </div> var app = new Vue({ el: '#app', data: { fontLarge: false, color: false, aqua: { 'aqua': true, 'bg-black': true, }, hidden: false }, methods: { click: function() { this.color = true; this.hidden = true; }, large: function() { this.fontLarge = true; } } }) v-bind:class="{large: fontLarge, org: color}" 上記の記述を用いて、trueの時クラスを付与する記述としています。 その後のbuttonのクリックによるイベント発火で、クラスを付与できる仕様にしています。 v-bind:class="hidden? 'aqua' : 'hidden'">ようこそ</h2> こちらの記述でhiddenがtrueか否かでクラスを変える三項演算子です。 ボタンクリックでクラスを切り替えます。 スタイルの適用を切り替える方法について記述しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-gtag使用時、GA4にpage_viewが2回計測されてしまう

はじめに gtag.jsやga4の動作に詳しくなかったので、備忘録のために残しています。 記載が簡潔すぎることをご容赦ください。 前置き Vue.js(2.x)製のSPAサイトのアクセスを、既存のGA3に加えて、GA4でも計測することになりました。 トラッキング周りはvue-analyticsを使っていましたが、GA4にはgtag.jsが必要だったため、vue-gtagを導入しました。 vue-gtagのAuto trackingを設定すると、router-linkクリック時にGA4にpage_viewイベントが送信されているのが確認できました。 が、GA4コンソールのdebug viewでよくよく見てみると、同じページでpage_viewが2回送信されているのに気がつきました。 結論 GA4のデータストリームの設定画面からブラウザの履歴イベントに基づくページの変更というチェックを外したら、vue-gtagによるpage_viewのみが送られるようになりました。 ※ 2021/04/13時点のUIです。 補足等 どこでpage_viewが送られているのかをGoogle Analytics Debuggerで探していると、ページ遷移したタイミングで、gtm.historyChange-v2というイベントが送られていることに気が付きました。 その中のパラメータで、page_viewに関するものがあったので、イベント名でググってみると、同じような内容で投稿されたフォーラムを見つけました。 この回答に従って、GA4コンソールのデータストリーム設定を見てみると、今回の設定を発見しました。 この設定自体は、SPAじゃないサイトで、ブラウザバックしたときでもpage_viewを送れるようにする的な機能なのかな? 誰かの役に立てば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ページの最下部に行ったらコンテンツが追加されるような処理を実装してみた

こんな感じの挙動です まずスクロール値を監視 data : function(){ return{ scrollY : 0 //スクロール値が保存される } }, mouted : function(){ window.addEventListener('scroll',this.handleScroll); }, methods : { handleScroll : function(){ this.scrollY = window.scrollY; } } スクロールを監視して最下部に来た時に処理を実行 watch : { scrollY : function(){ let bottom = document.body.scrollHeight - document.body.clientHeight; if(bottom <= this.scrollY){ //最下部に来たときに実行される this.getNextContent(); } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

②Vue.js & Laravel(CRUDシステムの準備)

こちらは10本の記事で構成されています ①Vue.js & Laravel(環境構築) ②Vue.js & Laravel(CRUDシステムの準備) ③Vue.js & Laravel(Read...データベースからデータを取得) ④Vue.js & Laravel(Create...データベースにデータを保存) ⑤Vue.js & Laravel(Update…データベースのデータを更新) ⑥Vue.js & Laravel(Delete…データベースのデータを削除) ⑦Vue.js & Laravel(リアルなダミーデータを大量に挿入) ⑧Vue.js & Laravel(検索機能の実装) ⑨Vue.js & Laravel(ページャーの実装) ⑩Vue.js & Laravel(認証機能の実装) 【目標】CRUDシステムの準備を完了させる 【手順】①テーブルの作成②モデルの作成③コントローラーの作成④ダミーデータの挿入 ①テーブルの作成 $ php artisan make:migration create_tasks_table これで/database/migrations/[現在日時]_create_tasks_table.phpが作成されました /database/migrations/[現在日時]_create_tasks_table.php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTasksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('tasks', function (Blueprint $table) { $table->id(); + $table->string('content'); + $table->boolean('emergency'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('tasks'); } } 上記のようにファイルを編集し以下のコマンドを叩きます。 $ php artisan migrate:refresh --seed これで「①テーブルの作成」が達成されました。 ②モデルの作成 $ php artisan make:model Task /app/Task.phpが作成されました。 これで「②モデルの作成」が達成されました。 ③コントローラーの作成 $ php artisan make:controller TaskController /app/Http/Controllers/TaskController.phpが作成されました。 /app/Http/Controllers/TaskController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; + use App\Task; class TaskController extends Controller { // } 上記のようにファイルを編集します。 「use App\Task;」でモデルと接続します。 これで「③コントローラーの作成」が達成されました。 ④ダミーデータの挿入 $ php artisan make:seeder TaskSeeder これで/database/seeds/TaskSeeder.phpが作成されました /database/seeds/TaskSeeder.php <?php use Illuminate\Database\Seeder; + use App\Task; class TaskSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { + for ($i = 1; $i <= 10; $i++) { + Task::create([ + 'emergency' => $i%2, + 'content' => 'タスクの内容' . $i, + ]); + } } } 「use App\Task;」でモデルと接続します。 /database/seeds/DatabaseSeeder.php <?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { - // $this->call(UserSeeder::class); + $this->call(TaskSeeder::class); } } 上記の部分を編集し、下記のコマンドを叩いてください。 $ php artisan migrate:refresh --seed これで「④ダミーデータの挿入」が達成されました。 【達成】今回の目標「CRUDシステムの準備を完了させる」が達成されました。 次回↓↓↓↓③Vue.js & Laravel(データベースからデータを取得)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む