20211124のvue.jsに関する記事は10件です。

Vue3はどうやってリアクティブシステムを実現しているのか:その2 値の更新とeffect

はじめに その1から順に読んでください ※ だいたいここの内容まんまです(構成は違います) ※ vue3のソースはここです ※ packages/reactivity/src内にあるファイルの内容を簡略化して説明しています。 目次 effectとは trackとtrigger 参考文献 effectとは 前編でも触れましたが、リアクティブの実現には3つの要素が必要です。 * targetに対する操作の監視 * targetが使用されている関数を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) このうち、監視に関してはProxyで実現できました。 まずは一旦effectについて考えましょう。 リアクティブ変数を使用している関数はeffectと呼ばれ、trackで記録され、triggerで実行されます。 (実際のソースコードでは関数ではなく、その関数を内包したクラスで表現されますが、ここでは簡単のため関数とします) 具体例でeffectのイメージを掴みましょう // 重さと個数 const obj = {weight: 100, num: 4}; // 全体の重さ let totalWeight = 0; // 重さを更新するエフェクト const effect1 = () => { totalWeight = obj.weight * obj.num; } // 初期化 effect1(); console.log(totalWeight); // 400 // 値を変更して初期化 obj.num = 8; effect1(); console.log(totalWeight); // 800 この例ではオブジェクトの値を変更してからeffect1を再度実行することにより、totalWeightを再計算しています。 ただ、これではいちいちeffect1を再実行しなければならないので、リアクティブとは言えませんね。 Vueではこれをアプリ開発者が意識しなくても行えるような仕組みが用意されています。 trackとtrigger ※ 以下の内容はbaseHandler.tsおよびeffect.tsに対応します Vue3においてtrackとは、リアクティブ変数が使用されているeffectを記録し追跡できるようにしておくことです。 そして、ここでtrackされたeffectはtriggerで実行されるという流れです。 最終的にはこのような形になります function reactive(target) { return new Proxy(target, { get: (...) => { const result = Reflect.get(...) track(); return result; }, set: (...) => { trigger(); Reflect.set(...); } }) } get内でtrackを呼び、set内でtriggerを呼びます。(triggerはともかくget内でtrackを呼ぶというのが腑に落ちないかもしれませんが、もう少し読み進めてみてください) track内部から現在実行中のeffectを参照するには少し工夫が必要です。それが以下の処理です(effect.ts: effect関数参照) let activeEffect; function effect(fn) { activeEffect = fn; fn(); activeEffect = null; } const effect1 = () => { // 何らかの処理 } effect(effect1); effect関数にeffect1を渡し、activeEffectにeffect1を一時的に格納、間接的にeffect1を呼び出し、最後にactiveEffectからの参照を外すという流れです。 これにより、effect1実行中はactiveEffectからeffect1が参照できる状態になります。 先ほど説明した通り、trackはeffectを記録する処理でしたね。少々ごちゃごちゃしてるように見えるかもしれませんが、以下がtrackのコードです。 const targetMap = new WeakMap(); function track(target, key) { if(activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } dep.add(activeEffect); } } 単にWeakMap,Map,Setの3つのコレクションがネストされているだけです(未登録の場合追加する) コレクションクラスが三重になっている理由は、どのリアクティブオブジェクトの、どのプロパティかを区別する必要があり、そのプロパティに依存するエフェクトが複数あるためです。 ここでWeakMapが使われていますが、ここでは特にMapと区別する必要が無いので、詳しく知りたい方は公式サイトをご覧ください。 参考 WeakMap 簡単に言うと、オブジェクトのみをキーに指定できるMapのようなものです。そして、そのオブジェクトが他所で参照されなくなったら自動的に削除されます(この性質をWeakと言っているのだろう)。制限が多い分パフォーマンス上のメリットと、開放し忘れによるメモリリークを防ぐことができるので、使い方によっては有用なコレクションクラスです。(実はWeakSetもありますが触れません。) イメージ的にはこんな感じで格納されます。(Map,WeakMapを{...}、Setを[...]で表現しています) const targetMap = { // どのリアクティブオブジェクトか dataset1: { // どのプロパティか time: [effect1], // 関連するエフェクトをSetに格納 speed: [effect1, effect3], }, dataset2: { weight: [effect2], num: [effect2], } } trackの雰囲気は掴めたでしょうか。 次はtriggerを考えましょう。ざっくりこんな感じです。 function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) { return; } const dep = depsMap.get(key); if (dep) { dep.forEach(effect => { effect(); }) } } 先ほどtrackで保存したeffectをtarget,keyにより取得し、ループで呼び出しています。 これで先ほど述べた3つの要素 * targetに対する操作の監視 * targetが使用されている関数を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) が揃いました。 最終的なコードは function reactive(target) { return new Proxy(target, { get: (target, key, receiver) => { track(target, key); // targetMapにeffectを記録 return Reflect.get(target, key, receiver); }, set: (target, key, value, receiver) => { trigger(target, key); // trackで記録したeffectを実行 Reflect.set(target, key, value, receiver); } }) } const targetMap = new WeakMap(); function track(target, key) { if(activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } dep.add(activeEffect); } } function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) { return; } const dep = depsMap.get(key); if (dep) { dep.forEach(effect => { effect(); }) } } let activeEffect; function effect(fn) { activeEffect = fn; fn(); activeEffect = null; } const dataset = reactive({time: 10, speed: 4}); let dist = 0; const effect1 = { dist = dataset.time * dataset.speed; } effect(effect1); // ここまで初期化処理 console.log(dist); // 40 dataset.time = 30; console.log(dist); // 120 dataset.speed = 2; console.log(dist); // 60 流れを整理しましょう。 1. reactive()でリアクティブオブジェクトを作成 2. effect()により間接的にeffect1を実行, effect1をactiveEffectに一時保存 3. effect1内部でdataset.time(dataset.speed)を取得、getハンドラー実行 5. track実行、targetMap.dataset.timeにeffect1を追加 6. distにdataset.time * dataset.speed (= 40)を代入, activeEffectをnullに // ここまで初期化処理 7. dataset.timeに30を代入、setハンドラー実行 8. dataset.timeの値を更新 9. 5で追加したeffect1を再度実行, distを再計算(= 120) これで、一度初期化したら、リアクティブオブジェクトのプロパティを変更時、自動的にeffectが再実行されるようになりました。 最後に リアクティブの概要の説明はこれで終わりですが、だいたいイメージは掴めたでしょうか? まだrefやcomputedの説明をしていませんが、それらは次回にしようと思います。 参考文献 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy https://philipdevblog.hashnode.dev/vue-3-reactivity-reading-source-code-with-evan-you https://github.com/vuejs/vue-next
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3はどうやってリアクティブシステムを実現しているのか:その1 オブジェクトの監視

その2 はじめに ※ 主にVue3の解説になります ※ リアクティブシステムとは簡単に言うと、ある値を変更すると、関連する値を自動で更新してくれるやつです。 ※ 基本的にここを参考にしています。 ※ vue3のソースはここですが、クローンしなくても大丈夫です ※ 実際のソースコードそのままだと複雑なので、簡略化して大まかな流れを理解することを目指します ※ 長めの記事書くのは初めてなのと、周囲に詳しい人も居ないので、間違い等多いと思いますが、皆さまの指摘を受けてより良い記事にできたら良いなと思います。 Vueによるフロントエンド開発の案件に入ってちょうど1年ぐらい経ちましたが、ここ半年ぐらいでVueの宣言的UI構築の素晴らしさを体感できたので、いかにしてそれを実現しているのか興味が湧いた。 というのも、業務で(Vueアプリの一部で)ほぼスクラッチでツールを作っていたのですが、DOMの操作を命令的に操作するのが非常にしんどかったのです。モデルの値を更新するたびDOMのプロパティを操作して...みたいなことをやると自分ですら手に負えないコードが出来上がります。 幸い何とか形になりつつありますが、本音を言うと今からでもVueに乗っかった実装に変更したいです。そんな余裕はありませんが。 (開発を始めて暫くして知りましたが、VueでもSVGがHTMLと同様に扱えるので、スクラッチで作る必要は無かったんですよね) 目次 リアクティブとは何か 前提 オブジェクトの監視 補足 参考文献 リアクティブとは何か 直訳すると"反応性" ある値を更新すると、それを使用している別の式も自動的に更新される仕組みのことです。 例えば、 sample.js const dataset = {time: 10, speed: 3}; const dist = obj.time * dataset.speed; console.log(dist); // 30 dataset.time = 20; console.log(dist); // 30 このコードでは途中でdataset.timeの値を変更していますが、変更前に定義したdistの値が更新されることはありません。 手続き的な処理ならこの挙動で問題ありませんが、UIを作る際にはこれが自動的に更新された方が都合が良いんですね。 そこで登場するのがVueのリアクティブシステムです。例えばVueだと <template> <div>{{ count1 + count2 }}</div> <div>{{ 'count2は' + count2 + 'です。' }}</div> </template> と書いてcount1,count2の値を変更すれば(それらがリアクティブな値であれば)、それぞれの変数を使用している箇所全てが更新されますよね。 Vueでは、ここにはどういう値が入れば良いかを一度宣言すれば、あとは自動で更新してくれるようになるのです。 直感的にはわかりやすい挙動ですが、javascriptのプログラムとしては不自然な感じです。 本来は表示する内容を更新したい場合、更新するための処理を明示的に実行しないといけませんが、そうすると実際に画面に表示される内容とコードがかけ離れたものになってしまいます。 よくわからなかったら公式を見てください(丸投げ) 前提 ※ ソースコードが手元にある方は、packages/reactivity/src/以下のファイルを見てください。 まず、リアクティブを実現するために必要なことは(対象の値をtargetとすると) * targetに対する操作の監視 * targetが使用されている箇所を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) (track, trigger, effectはソースコード上での呼び方です) この3つです。 そして、これらを実現するためにVue3ではreactive関数を使用します。具体的には const dataset = {time: 10, speed: 3}; const reactiveDataset = reactive(dataset); これにより、datasetオブジェクトがリアクティブ化されます。 そして、このオブジェクトの変更は使用箇所全てに適用されます。 一見不思議な挙動ですが、中でどういった処理が行われているか詳しく見ていきましょう オブジェクトの監視 ※ 以下はreactive.tsの内容を簡略化して説明しています reactive関数を通すとリアクティブな値を取得できますが、実は返却される値は、元のオブジェクトは別のオブジェクトです。ただのコピーでもありません。 console.logとかで見るとわかると思いますが、実はこれはProxyという組み込みクラスのインスタンスで元のオブジェクト、objectへの操作を傍受するためのものです。 このProxyにトラップ(イベントハンドラー)をセットすると、何か操作をするたび処理を呼び出すことができるわけです。 参考 アプリケーション開発ではほぼ使われないので、知っている人は少ないのではないかと思います。(僕も知らなかった) Proxyは代理人という意味です。第一引数は対象のオブジェクト、第二引数は各操作に対するトラップ(ハンドラー)です。 簡単な使用例を見てみましょう。proxyを介した値の取得時に2倍にして返却するだけです。 const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { // getハンドラーは値の読み取り時に発火 get: (target, propertyKey, receiver) => { // 元の値を2倍にして返却 return Reflect.get(target, propertyKey, receiver) * 2; } }) console.log(dataset.time); // 10 console.log(datasetProxy.time); // 20 また変なやつが出てきましたね。 Reflectというクラスは、簡単にいうとオブジェクトに対する操作をする関数をstaticメソッドとしてまとめたものです。 Reflect.getによって元の値を取得し、それを*2して返しています。 第三引数のreceiverとは、datasetProxy自身のことです。 これが無い場合、datasetProxyではなくdatasetの値を参照してしまいます。 具体的にどう困るかというと const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { get: (target, propertyKey, receiver) => { if (propertyKey === 'dist') { return Reflect.get(target, 'time') * Reflect.get(target, 'speed'); } return Reflect.get(target, propertyKey, receiver) * 2; } }) console.log(datasetProxy.time); // 30 datasetProxyでは各プロパティが2倍になるので、distは(10 * 2) * (3 * 2) = 120になることを期待しますよね。でも、receiverを渡さないと元の値を参照してしまうので、10 * 3 = 30となってしまいます。これがreceiverが必要な理由です。 少々脱線しましたが、今度は値の取得時ではなく変更時に処理を行いたいとします。するとこうなります。 const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { // setハンドラーは値の更新時に発火 set: (target, propertyKey, value, receiver) => { // 入力値を3倍にしてプロパティにセット Reflect.set(target, propertyKey, value * 3, receiver); } }) datasetProxy.time = 4; console.log(datasetProxy.time); // 12 これも特に意味のない処理ですが、timeプロパティにセットした値が実際には3倍になって格納されています。 以上のように、オブジェクトへの操作を監視するのはProxyを利用して実現ができます。 Proxyの機能は他にも色々ありますが、 Vueのコードを読むにはget、setのみ理解すれば大丈夫です。 ここでは値を操作してみましたが、実際にはここで値の更新をするロジックを組み込みます。 具体的にはget時にその実行された処理(effect)を保存、set時に保存したeffectを実行します。 とりあえず今回はここまでです。何かありましたらコメントお願いします。 補足:Vue2との比較 ここまでVue3の説明をしましたが、Vue2ではまた別の方法で変更の監視をしていました。 そもそもProxyはモダンブラウザでは問題なく使えますが、IEでは対応していないらしいです。 この辺の互換性については本題から逸れますので、あまり触れません。 ではどうやっているのかというと、Object.definePropertyによってsetter、getterを定義しているみたいです。 const obj = {a: 10, b; 11}; Object.defineProperty(obj, 'a', { get: () => { // なんか }, set: (val) => { // なんか } }) この方法のメリットは互換性ですが、デメリットは初期化処理の遅さと、後から追加したプロパティを監視できない点です。 次回 次回は、具体的にどういった処理がgetおよびsetに必要なのかを必要なのかを見ていきます。 その2 参考文献 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy https://philipdevblog.hashnode.dev/vue-3-reactivity-reading-source-code-with-evan-you https://github.com/vuejs/vue-next
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vuex】ストアの作成

はじめに こんにちは! 今回は【Nuxt.js】ストアの作成についてアウトプットしていきます! Vuexとは Vuexとは、状態管理を行うためのライブラリです。アプリケーションの状態を1つの場所に置いて管理する。 (イメージ) サーバーという夢想に広がるマンションの1室を借りて物置部屋にしている感覚。 インターネット版物置。 【メリット】 ・データの流れを見えやすくすることによってバグの少ない ・デバックの少ない状態管理を実現すること。 【デメリット】 ・コードが冗長になる。 【状態管理の例】 □ECサイトのショッピングカート機能  ・最初カート内は商品が入っていない空の状態  ・ユーザーがカートに商品を追加すると、カートに商品が入った状態になる。  ・ユーザーが決済を行なって購入完了するとカート内は商品が入ってない状態。 【vuexの利用タイミング】 中規模から大規模のSPAを構築する場合。 ストア(store)作成 【storeとは】 各コンポーネントで共通の保管場所であるストア(store)を実装します。 storeファイルの中にindex.jsを作成します。(index.jsは決まり事) index.js import Vuex from 'vuex' // ⏫vuexのインポート // ⏬vuex.storeオブジェクトを作成して利用 const createStore = () => { return new Vuex.Store({ //⏬stateに値を保管する処理 state: function() { return { message: 'Hello Vuex!' } } }) } // ⏬作成したcreateStoreをexport defaultでexportし、使用できるようにする。 export default createStore ・vuexのインポート ・storeを作成し、stateに値を補完する処理を記述。 ・エクスポート といったことをindex.jsではやっていきます。 次にpages⏩index.vueを実装していきます。 index.vue <template> <section class="container"> <div> <!--⏬storeフォルダのstateの値messageを利用することができる--> <p>{{ $store.state.message }}</p> </div> </section> </template> <script> export default { } </script> <p>{{ $store.state.message }}</p>とすることでstoreフォルダのstateの値messageを利用することができるという風になります。 最後に 今回はVuex.ストアの作成についてアウトプットしました。 今回の記事を読んで質問、間違い等あればコメント等頂けると幸いです。 今後ともQiitaにてアウトプットしていきます! 最後までご愛読ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-draggableでドラッグ中に位置がずれる問題の解消方法

問題 vue-draggableでドラッグ中に、カーソルの脇に移動しているオブジェクトが表示されず、カーソルとズレた位置にオブジェクトが表示されてしまっていた。 追記:画面サイズがある程度大きくなるとずれる。おそらくレスポンシブ関連が影響している。この点は未解決(調査中) 解決方法 draggable属性を使用する <draggable v-model="items" handle=".item-handle" @end="updateItemSort" draggable=".item" //drag対象を見つけるクラスを指定 > <v-card v-for="item in items" :key="item.id" flat rounded="lg" outlined class="mb-5 item" //drag対象であることの明示としてクラス指定 > <ChildComponent :item="item" /> </v-card> </draggable>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactのことをざっくりメモ

きっかけ Reactを書いていて、〇〇とは何か。 など根本的なことを理解していないのではと不安になったのでメモ程度に書いてみました。 ここ違います!などあればコメントお願い致します。 Reactの特徴 まず、javascriptのライブラリである。 仮想DOMを利用していること 仮想DOMとは、情報が書き変わってもすぐにブラウザ憑依するのでなく、一旦仮想のDOMにその変化を加えて、差分があった部分のみ変更するこの仕組みのことです。また、仮想DOMから実際のDOMに反映することで処理スピードが速く、比較的高速に切り替えが可能となっています。(ここではあくまでReactにおける仮想DOMについてです。) JSXを使用できる JSXとは、javascript XML。スクリプトとDOMを一緒に記述できるもの。(Vueでも使われています。)   コンポーネントベースのUI 他のライブラリでも採用されているものではあります。 コンポーネントはザックリといえば、構成要素のことで、UIを部品に見立てその部品ごとに作成することで管理と再利用性を高めることができます。大規模なシステムであれば恩恵が大きいと感じるかと思います。  Class component と Functional component この2つの使い分けですが、基本何か理由がなければFunctional componentを利用することをお勧めします。前まではstateを使用するにはclassのみしか選択肢がなかったですが今はどちらでも使えるので問題ありません。理由はいくつかあります。 まずReactのお偉いさん方がこちらを使用するように推奨しています。ですが、Class componentがなくなることはないと言っていますので、無理に変更する必要はありません。  Functionalの方が記述量が減りやすい classではconstructorやrender、ライフサイクルメソッド(componentDidMount)を書く必要がなくなります。3つ目のことが大きくこれは可読性にも関わってくると思ってます。classではライフサイクルメソッドがそれぞれあるので、中身の処理が同一でもまとめて書くことができません。ですが、FunctionalではuseEffectの中でまとめることができる優位性があるかと思います。 // class componentDidMount() { // マウント時処理 } componentDidUpdated() { // 更新時処理 } componentWillUnmount() { // アンマウント時処理 } // functional useEffect(() => { // マウント時処理 // 更新時処理 return () => { // アンマウント処理 }; }) カスタムフックを利用してstateとロジックを一緒に切り出すことができる 再利用も可能となり、管理が容易になり便利なツールだと思っています。ここではザックリ概要なので、詳しくは別途お調べください。 Functionalの方が動作が早いと言われている。 useCallback useMemo React.memo こちらの3つの機能を利用するとレンダリングの最適化が容易にできるかと思います。 useCallbackとuseMemoは似ていて、useCallbackは関数に対して利用できるものでuseMemoはそれ以外です。この2つを利用することで無用な関数の実行や変数の計算をスキップできます。これは処理が重い関数や変数に有効です。結果が同じであれば以前の値を使用してくれるので、動作の改善がみられるとはお思います。大規模なものでないと時間が湧かないかもしれませんが。また、React.memoと一緒に利用することで無用再レンダリングの抑止もできます。 処理の重くないものに関しては無理にuseCallbackuseMemoを利用することはしないで良いかと思います。 それぞれ単独で使いことはなく、組み合わせて使うことが多いかと思います。 最後に 閲覧いただきありがとうございました。 これからはもっと細かい部分や、なぜそれを使うのかなど疑問を潰していてければなと思いました。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel, VueJSの連携・ルーティング(Web, VueRouter, API, VueRouterとAPIの連携)

目的 LaravelとVueJSの開発において、 LaravelではWebとAPIのルーティング、 VueJSではVueRouter(Component)を使用してルーティングを行い、 LaravelとVueJSの機能を組み合わせて使用するときがあると思います。 それらのルーティングの設定が複雑なので、備忘録用にまとめました。 前提 で構築した環境を基にしています。 Laravel Web Routing routes/web.php Route::get('/', function () { return view('welcome'); }); Laravelの初期設定のroutes/web.phpです。 まず、Route::getでHTTPのメソッドを指定しています。 今回はGetメソッドを指定しています。 '/'で、アクセスしてきたURLとの一致の条件を指定しています。 今回であれば、http://localhost:8000/ が一致します。 return view('welcome')で welcome.blade.phpを返します。 return view('[ビュー名]')は, resources/views/内の[ビュー名]に一致する[ビュー名].blade.phpファイルを返します。 流れとしては、Getメソッドでhttp://localhost:8000/にリクエストがきたら、 ルーティングの条件と一致したwelcome.blade.phpを返す、のような感じになると思います。 本記事用の設定 routes/web.php Route::get('/{any}', function() { return view('app'); })->where('any', '.*'); routes/web.phpを本記事のテスト用に上記の様に変更します。 この書き方では、'/{any}'と条件を指定し、 また、)->where('any', '.*');の句がついています。 {any}は、ルートパラメータと呼び、ルーティングの条件一致の際に使える変数の様なものと理解しています。 ->where内で'any', '.*'と記述することで、アクセスしてきたURLに対して正規表現による条件一致が使用できます。 この記述では'.*'と正規表現の指定があるので、どのようなURLでアクセスしても、app.blade.phpを返します。 http://localhost:8000/.*とも書けると思います。 もし、条件が一致しない場合、例えば正規表現に'a-Z'が設定されており、http://localhost:8000/1でアクセスした場合、 条件が一致しないのでreturn view('app')で app.blade.phpを返しません。 その後、他のルーティングの条件のいずれにも一致しなければ何も返さず、ブラウザ上で404が表示されます。 resources/views/app.blade.php <!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Vue Laravel Routing') }}</title> <link href="{{ mix('/css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> </div> <script src="{{ mix('/js/app.js') }}" defer></script> </body> </html> resources/views/app.blade.phpを上記の内容で新たに作成します。 Vue Component Setting recources/js/components/TestComponent.vue <template> <div class="container"> TEST </div> </template> <script> export default {} </script> まず読み込むコンポーネントの作成をします。 resources/js/app.js import TestComponent from "./components/TestComponent.vue"; require('./bootstrap'); window.Vue = require('vue'); Vue.component('test-component', TestComponent); const app = new Vue({ el: '#app', }); 次に、resources/js/app.jsを上記の様に記述します。 import TestComponent from "./components/TestComponent.vue";で、作成したコンポーネントを読み込みます。 require('./bootstrap');とwindow.Vue = require('vue');でVueなどのJavascriptの依存関係を読み込んでいます。 Vue.component('test-component', TestComponent);でVueのコンポーネントを定義し、Laravel(php)から使えるようにします。 const app = new Vue({});で新しいVueのインスタンスを生成します。 el: '#app',でインスタンスのAPIのid(名前)を設定します。 ここで設定した名前(今回であればapp)が、Laravel(php)からコンポーネントを使用する際に紐づきます。 resources/views/app.blade.php <div id="app"> <test-component></test-component> </div> <script src="{{ mix('/js/app.js') }}" defer></script> resources/views/app.blade.php内の記述を上記の様に変更します。 <script src="{{ mix('/js/app.js') }}" defer></script>で/js/app.jsを読み込みます。 <div id="app"></div>で/js/app.jsで作成したVueインスタンスを設定します。 今回はel:プロパティに#appを設定しているので、<div id="app">と指定します。 el:プロパティが#hogeであれば、<div id="hoge">と指定します <div id="app"></div>内の、 <test-component></test-component>で/js/app.jsで設定したコンポーネントを使用します。 Vue Router Setting recources/js/components/TestComponent.vue <template> <div class="container"> TEST </div> </template> <script> export default {} </script> まず読み込むコンポーネントの作成をします。(前項で作成したものをそのまま使っています。) resources/js/app.js import VueRouter from 'vue-router'; import TestComponent from "./components/TestComponent.vue"; require('./bootstrap'); window.Vue = require('vue'); Vue.use(VueRouter); const router = new VueRouter({ mode: 'history', routes: [ { path: '/test', name: 'test', component: TestComponent }, ] }); const app = new Vue({ el: '#app', router }); 次に、resources/js/app.js内で、 import VueRouter from 'vue-router'; でVue-Routerを読み込み、 import TestComponent from "./components/TestComponent.vue";で作成したコンポーネントを読み込みます。 Vue.use(VueRouter);で、Routerを使用する準備をします。 const router = new VueRouter({});で新しいVueRouterのインスタンスを生成します。 mode: 'history',でページ遷移時のURLの処理を設定します。三種類の設定値があります。 値 動作 hash ページ遷移時のURLにhttp://localhost:8000/#/hogeのような#が入る。 history ページ遷移時のURLがhttp://localhost:8000/hogeのようになる(#が入らない。)。 abstarct URLが変わらない。ページを遷移してもURLはhttp://localhost:8000/のまま。 path: '/test',で、VueRouter内に設定したコンポーネントを呼び出すURLを設定します。 今回であれば,http://localhost:8000/testです。 name: 'test',で、VueRouter内に設定したコンポーネントを、 ほかのページから呼び出す際の名前を設定します。(名前付きルート) 今回であれば、<router-link :to="{ name: 'test' }">test</router-link>の記載で呼び出す(ページ遷移)ができます。 component: TestComponentで、作成したコンポーネントを読み込んでいます。 el: '#app',でインスタンスのAPIのid(名前)を設定します。 routerで、VueからRouterを使用する設定をしています。 resources/views/app.blade.php <div id="app"> <router-view></router-view> <router-link v-bind:to="{name: 'test'}">Jump to Test</router-link> </div> <script src="{{ mix('/js/app.js') }}" defer></script> <router-view></router-view>の記述よって、 http://localhost:8000/testにアクセスした際、testにルーティングされたコンポーネントを読み込みます。 http://localhost:8000/testにアクセスすると、resources/js/app.jsのrouterのpathに/testが設定されたコンポーネントを表示します。 <router-link v-bind:to="{name: 'test'}">Jump to Test</router-link>の記述によって、リンクをクリックした際に、testの名前がつけられたコンポーネントを表示します。 http://localhost:8000/testにアクセスするか、 http://localhost:8000/にアクセスし、Jump to Testをクリックすると、 TESTの文字列の表示が確認できると思います。 Laravel API Routing powershell php artisan make:controller TestController Dockerに構築したローカルサーバー上でphp artisan make:controller TestControllerを実行します。 \app\Http\Controllers\TestController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\View; class TestController extends Controller { public function index() { $data = [ 'test'=>'Test data from controller', ]; return view('app', $data); } } \app\Http\Controllers\にTestController.phpが作成されます。 上記の様に記述を変更します。 namespace App\Http\Controllers;で名前空間を指定しています。 名前空間は、クラス名などが重複しないために、このクラスはこの名前空間のもの、といった定義をするためのものです。 use ~~で指定した名前空間にあるクラスなどを読み込んでいます。 今回は、Illuminate下にある\Http\Requestと\Support\Facades\Viewを読み込んでいます。 class TestController extends Controller{~}でTestControllerクラスを定義し、かつextendsでControllerクラスを継承しています。 ControllerクラスはLaravelに最初からあるもので、ミドルウェアの登録などをやってくれています。 return view('app', $data);にてapp.blade.phpに戻り、かつ$dataの配列を渡します。 \routes\api.php <?php Route::get('/test', 'TestController@index'); \routes\のapi.phpを上記の様に変更します。 このファイルで、LaravelのAPIのルーティングの設定をします。 Route::get('/test',の記述は、routes/web.phpと同じです。 また、api.phpで設定したルーティングはhttp://localhost:8000/api/(設定したパス)として設定されます。 今回は'/test'を設定していますので、実行する際はhttp://localhost:8000/api/testにアクセスします。 'TestController@index'のTestControllerで呼び出したいコントローラークラスの名前を指定し、 indexで呼び出し時に実行したい関数名を指定します。 resources/views/app.blade.php <div id="app"> </div> <p> Received:{{ $test }} </p> app.blade.phpに{{ $test }}と記述すると、 TestController.phpから送られてきた配列($data)の中の、 キー(test)が一致する値(Test data from controller)を表示します。 この変数はVueJSとは関係ないので、<div id="app"></div>内に記述する必要はありません。 http://localhost:8000/api/testにアクセスし、動作が確認できると思います。 Laravel(API)からデータを受け取りVueで描画 Laravelは、MVCモデルに基づいて構成されたフレームワークだと思いますので、 そちらにのっとったような感じでやっていきたいと思います。 テストデータの作成・設定 powershell php artisan make:migration create_tests_table Dockerに構築したローカルサーバー上でphp artisan make:migration create_tests_tableを実行します。 Laravelでは、モデルクラス内で明示的にテーブル名を指定しない限り、 モデルのクラス名にsを合わせた名前が指定されるテーブル名になります。 今回はTestの名前でモデルを作りますので、テーブル名は自動的にtestsが指定されます。 ゆえにそれに合わせる形でtestsの名前でテーブルを作成しています。 \database\migrations\YYYY_MM_DD_[0-9]{6,6}_create_tests_table.php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTestTable extends Migration { public function up() { Schema::create('tests', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title', 100); $table->string('content', 100); $table->timestamps(); }); } public function down() { Schema::dropIfExists('tests'); } } database\migrations\にYYYY_MM_DD_[0-9]{6,6}_create_test_table.phpが作成されますので、上記の様に内容を変更します。 Schema::create('test',の'test'で、作成するテーブル名を指定します。 $table->でテーブル内に作成するカラムを指定します。 bigIncrements('id');は、idというカラムを、Bigint型で自動で増分(1->2->3...)していくように設定しています。 string('title', 100);は、String型で100(文字?バイト?)と指定しています。 timestamps();は、データが作成、更新された年月日時分秒(Datetime)を指定しています。 powershell php artisan make:model Test Dockerに構築したローカルサーバー上でphp artisan make:model Testを実行します。 app/Test.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Test extends Model { protected $fillable = [ 'title', 'content', ]; } app/にTest.phpが作成されますので、上記の様に内容を変更します。 Laravelでは、Eloquentというものでデータベースの操作ができます。 データの操作をする前に、モデルに対してfillableかguardedという属性をつけないといけない様です。 今回は、protected $fillableでfillableという属性をつけ、 titleとcontentのカラムを編集していいよ、といった設定をしています。 guardedをつけると、逆にtitleとcontentのカラムが編集できないようになる様です。 powershell php artisan make:seeder TestTableSeeder Dockerに構築したローカルサーバー上でphp artisan make:seeder TestTableSeederを実行します。 database\seeds\TestTableSeeder.php <?php use App\Test; use Illuminate\Database\Seeder; class TestTableSeeder extends Seeder { public function run() { for ($i = 1; $i <= 3; $i++) { Test::create([ 'title' => 'title' . $i, 'content' => 'content' . $i, ]); } } } database\seeds\にTestTableSeeder.phpが作成されますので、上記の様に内容を変更します。 For文を回し、Task::create([で生成したテストデータをデータベース上のtestテーブルに格納します。 database/seeds/DatabaseSeeder.php public function run() { $this->call(TestTableSeeder::class); } database/seeds/DatabaseSeeder.phpを上記の様に変更します。 $this->call(TestTableSeeder::class);で、先ほど作成したTestTableSeeder.phpを呼ぶようにします。 powershell php artisan migrate --seed migrateコマンドを実行します。 powershell php artisan tinker Test::all(); php artisan tinkerで、Laravel搭載のデバッグ用の対話シェルであるtinkerを起動します。 tinker内でTest::all();を実行することで、migrationによって作成されたテーブルに挿入されたデータが確認できると思います。 LaravelとVueの連携 \app\Http\Controllers\TestController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\View; use App\Test; class TestController extends Controller { public function index() { $data = [ 'test'=>'Test data from controller', ]; return view('app', $data); } public function getAllRecords() { return Test::all(); } } \app\Http\Controllers\TestController.phpにgetAllRecords関数を追加します。 Test::all();で、取得したtestsテーブル内の全レコードを、returnで返します。 \routes\api.php <?php Route::get('/test', 'TestController@getAllRecords'); \routes\api.phpを上記の様に変更して、 http://localhost:8000/api/testで作動する関数をgetAllRecordsに変更します。 \resources\js\components\TestComponent.vue <template> <div class="container"> <table> <tbody> <tr v-for="test in tests"> <th scope="row">{{ test.id }}</th> <td>{{ test.title }}</td> <td>{{ test.content }}</td> </tr> </tbody> </table> </div> </template> <script> export default { data: function () { return { tests: [] } }, methods: { getAllRecords() { axios.get('/api/test') .then((res) => { this.tests= res.data; }); } }, mounted() { this.getAllRecords(); } } </script> \resources\js\components\TestComponent.vueを上記の様に変更します。 書き方上、<template></template>から書いていますが、実際に先に動くのは<script></script>です。 data: functionでtestsの配列を定義しています。 このtestsがtemplate内で読み込むときの名前になります。 methods:にgetAllRecords()という関数を定義します。 axios.get('/api/test')で、/api/testのパスに対してGetメソッドでリクエストを投げています。 .then((res) => {~});に`axios.getが成功した際の処理を書いています。 this.tests= res.data;でres(HTTPレスポンス)の中のdataをtestsに代入しています。 流れでいうと、/api/testのパスに対してGetメソッドでリクエストを投げ、 routes/api.phpのtestに指定されたクラスの関数を実行し、 成功した場合、実行の結果(testsテーブルの全レコード)がresに格納され、 testの中に実行の結果(testsテーブルの全レコード)が代入される、という感じです。 mounted() {~}で、DOMが作成された直後の処理を定義しています。 this.getAllRecords();を記述しているので、このクラスのgetAllRecords()を実行します。 resources/views/app.blade.php <div id="app"> <router-view></router-view> </div> VueRouterの動作確認用に、 resources/views/app.blade.phpの<div id="app"></div>に<router-view></router-view>を記述してください。 動作確認 ソースをビルドnpm run devしてから、http://localhost:8000/testにアクセスします。 http://localhost:8000/api/test ではありません。 http://localhost:8000/api/testにアクセスすると、Json形式のレコードデータが見れると思います。 こんな感じで動作が確認できると思います。  おわりに 複雑だな~、と思って軽くまとめようとおもったら激重な内容になりました。 複雑すぎる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3.2 で追加された expose のメモ

Vue 3.2 より setup 関数の第2引数の context に expose という関数が追加されました。 expose を使うことにより、そのコンポーネントが公開するプロパティを明示的に示すことができるようになります。 これについてのメモです。 公式ドキュメントはこちら setup(日本語) setup(英語) 使い方 対象のコンポーネント script 部分のみ抜粋 ChildComponent.vue <script> export default { name: "ChildComponent", setup(props, context) { const func = () => { console.log("child func"); }; // funcを公開 context.expose({ func }) return { } }, }; </script> 利用する側のコンポーネント Parent.vue <template> <div> <ChildComponent ref="child" /> <button @click="handleClick">click me!</button> </div> </template> <script> import { ref } from "vue"; import ChildComponent from "./ChildComponent.vue"; export default { name: "Parent", components: { ChildComponent, }, setup() { const child = ref(null); const handleClick = () => { // 公開されている関数を呼び出す child.value.func(); }; return { child, handleClick, }; }, }; </script> expose で公開された関数はchild.value.func() のような形で利用することができます。 また expose を使ったコンポーネントは 下で挙げるような return に含めるものとは異なり child.value はまた別の形のインスタンスになっているようです。(console.log(child.value) をしてみて差異を比較するとわかります。) おわりに これよりも前のバージョンでは setup(props, context) { const func = () => { console.log("child func"); }; return { func } }, このように setup 関数の中でreturn に含めて返すことで 同じことができましたが テンプレートで利用しないものまで 含めるのは違和感がありました。 expose 関数を使うことで 「外部に公開するもの」「テンプレートで使うもの」を 明示的に示すことができるので このあたりの問題を解決することができるかなと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js & Flask & Bolt for Python でSlackアプリを作るチュートリアル

2021年の7月あたりから、フロントエンドをVue.js バックエンドをFlaskで書いたアプリケーションの開発を行い、公開してきました。 その顛末はこちら 今回は、誰でも簡単にSlackアプリを作ることができるように、チュートリアル形式でテンプレートをビルドしていきたいと思います。 開発環境はMacを想定して書いてあります。 コードはこちら https://github.com/geeorgey/vue_flask_template 準備する Github右上のForkボタンからコードをforkしてください。 リモートリポジトリの設定と、ローカルへのコードのダウンロード $ git init $ git remote add origin Forkしたgitのアドレス $ git pull $ git checkout main pythonの仮想環境を作る $ python3 -m venv .venv $ source .venv/bin/activate これで仮想環境で作業できるようになります ローカル環境にPostgreSQLをインストールする PostgreSQLのクライアントはPostico使えば良いと思います。無料版もあります。 https://eggerapps.at/postico/ 利用するDBを作成する $ psql psql $ create database slack; pssql $ \q slack部分はデータベース名なので、適宜変更してください。 pipenvのインストール pythonのパッケージ管理ツールをインストールします https://original-game.com/how-to-build-a-python-development-environment-on-mac-using-pipenv/ yarnのインストール こちらはフロントエンド開発に使います。 https://zenn.dev/oimo_revolution/articles/c61c034f7af41e ローカル環境を立ち上げる python環境を作る $ pipenv install -r requirements.txt これでOKです。 DBを初期化する $ flask db init $ flask db upgrade こちらでDBが初期化されます。 こんな風になります。 Slackアプリを立ち上げる アプリの立ち上げ方は @seratch さんのこちらの記事を参照。 YAMLはこんな感じです appmanifest.yml _metadata: major_version: 1 minor_version: 1 display_information: name: vue_flask_template description: Bolt for python sample bot background_color: "#070d1f" long_description: "Bolt for Pythonを使って、どのような実装ができるかをテストするためのアプリケーション。\r An application to test what kind of implementation can be done with Bolt for Python.\r https://qiita.com/geeorgey/items/85905a4772903b180fad" features: bot_user: display_name: vue_flask_template always_online: true oauth_config: redirect_urls: - https://vueflasktestgeeorgey.herokuapp.com/slack/install scopes: bot: - chat:write - channels:history - groups:history - im:history - mpim:history settings: event_subscriptions: request_url: https://vueflasktestgeeorgey.herokuapp.com/slack/events bot_events: - app_home_opened - app_uninstalled - message.channels - message.groups - message.im - message.mpim - team_access_revoked - tokens_revoked interactivity: is_enabled: true request_url: https://vueflasktestgeeorgey.herokuapp.com/slack/events org_deploy_enabled: false socket_mode_enabled: false is_hosted: false token_rotation_enabled: false URL部分はngrokのURLに適宜置き換えて下さい。 環境変数を登録します。ターミナルで以下を設定します。 export SLACK_CLIENT_ID= export SLACK_CLIENT_SECRET= export SLACK_SIGNING_SECRET= export SLACK_SCOPES=chat:write,users:read,users:read.email,channels:join,groups:write,im:write,mpim:write,channels:read,app_mentions:read,im:read,mpim:read,channels:history,groups:history,im:history,mpim:history,commands botを立ち上げる $ flask run これでbotが動き始めます。簡単! インストールしてみる https://ngrokのURL/slack/install にアクセスすると、インストールボタンが現れますので、そこからインストールしてみましょう。 Boltの本体について 本体はbackend/bolt.py にあります。 インストール時にDMでインストールされたことを伝える機能 全ての公開チャンネルにjoinする機能 ホーム画面からユーザーにDMを送る機能 等が組み込まれています。 Vue.jsを使うには こちらはオプションです。 フロントエンドはこんな形になっています。 https://vueflasktestgeeorgey.herokuapp.com/ 詳細はこの辺をみてみてください。 こちらは、もともとはGoogleのOAuthを紐付けるために作成した機能です。 Slackのアプリ上で出来ないようなことをやる必要がある場合はWebのフロントエンドが必要になりますので、必要になった場合に使ってみて下さい。不要な場合は特に使う必要はありません。 本番環境を構築しよう こちらのアプリはHerokuで動かせるようになっています。 やり方はこちらを参照して下さい。 これを元にして5つのアプリを作りました! Slackアプリの開発の共通テンプレートとしてつかっているのですが、これを元にして5つのアプリを作りました。 よろしければインストールして使ってみて下さい! OYASUMI bot https://lne.st/oyasumilink TIPS https://lne.st/tips TimeLine for Slack https://lne.st/wr21 YOKOKU for Slack https://lne.st/4cdy TASUKARU-TaskALL- https://lne.st/3x1u OYASUMI bot Googleカレンダーのスケジュールに「休み」等の、休暇判定キーワードが入っていた場合に、自動的にSlackをスヌーズ状態に変更します。 メンションがあった場合に、メンションした相手にその日は休暇ですというレスを付けます。 スタッフの休暇を気持ちよく過ごしてもらう為のアプリです。 TIPS Slack用のリマインダーアプリです。 デフォルト機能のリマインダーは、一つのスケジュールに一つのメッセージの設定しか出来ません。 このアプリでは、一つのスケジュールに複数のメッセージを登録し、ランダムで指定された時間にpostします。 チャンネル特有のノウハウ等をスケジュール登録しておくことで、自然と情報が浸透する状態を作ります。 TimeLine for Slack 全ての公開チャンネルのpostを一つのチャンネルにまとめるタイムラインチャンネルを生成するアプリです。 一部のチャンネルのみを集めたミックスチャンネルを作成すると、自分が必要なチャンネルだけのタイムラインを作り出すことが出来ます。 オプション機能として、メッセージ転送時にDeepL翻訳を挟むことが出来ます。 YOKOKU for Slack OYASUMI botの応用です。 例えば「営業」というキーワードが入った予定だけを抽出したいと思ったことはありませんか? カレンダーをいちいち検索するのは手間がかかります。 このアプリを用いれば、特定のキーワードが入った予定を、特定のチャンネルに流すことが出来ます。 TASUKARU Slackで色々な人からメンションをもらって混乱したことはありませんか? 何か頼まれていたはずだけど思い出せない… そんな状態からあなたを救います。 TASUKARUはSlack専用のタスクマネージャーです。 デフォルトのスレッドやメンション画面では、処理が終わったpostをアーカイブすることが出来ませんが、TASUKARUならそれが出来ます。 今アクションが必要なものだけに集中することができる、それがTASUKARUです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

独自コンポーネントの作成とその使い方

はじめに ベースとなるコンポーネントを作成しておくと、他コンポーネントで流用することが可能。 今回はベースとなるボタンを作成し、流用する形でローディングボタンを作成する。 ベースとなるコンポーネントの作成 ベースとなるボタンを作成 nameを指定すると他コンポーネントでその名前で流用できる <template> <button @click.prevent="isClick"> {{ status }}</button> </template> <script> export default { name: "base-button", methods: { isClick() { this.isDisabled = true; this.$emit( 'isClick' ); } } } </script> 作成したコンポーネントを流用する ベースボタンを基にローディングボタンを作成する base-buttonという独自タグで簡単に差し込むことが可能 <template> <!-- 今回作成したコンポーネント --> <base-button :label="isLoading ? 'Loading...' : status" > </base-button> </template> <script> import BaseButton from './base-button'; export default { name: "loading-button", // コンポーネントの登録 components: { BaseButton }, props: { isLoading: Boolean, } } </script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最近の流行りを調べてみた

本記事では、触ってみた、やってみた系ではないので、コードの書き方に関しては記述しません。 どういった技術なのか簡単に知りたい方向けです。 Vite 一年前くらいから流行っているViteに関して調べた Viteとは Vue.jsのビルドツールといえば、vue-cliでした。これに変わる可能性が高い存在としてViteが現れました。 Viteとは、Vue.jsの作者のEvan You氏が開発中のノーバンドルなビルドツールです。 大きなメリットとしては以下の2点になります 開発時はバンドル不要で動作するので、大規模プロジェクトでも初回起動が非常に早い HMR(画面の再描画無しにファイル変更をブラウザに適用してくれる機能)が、モジュールの総数と切り離されているため一貫して高速に動作する 字面でなんとなく開発が速くなるのは理解できると思います。  そのほかの特徴としては、 viteは、Vue.js限定ではなく、React、Preactにも対応 Rollup.jsをベースとしたプロダクションビルド機能 開発ビルドは、 esbuildを使っている。Goでかかている為JavaScriptベースのバンドルラーよりも10-100倍速い。 さて、バンドル不要とはどういうことでしょうか?、モジュールの総数と切り離されているとはどういうことでしょうか? この2点を理解するのに必要になるのがNative ESM(ES Module)です。 Native ESMとは 本記事ではあまり深く解説しません。詳しく理解したい方はNative ESM時代とはなにかの記事がわかりやすくまとめられており、これからどうなっていくのかもわかり、とても勉強になります。 Native ESMとは、ES Modulesのことです。2015年にECMAScriptの標準仕様になり、モダンなフロントエンド開発において広く用いられている、import宣言でインポートしexport宣言でエクスポートするのがES Modulesです。 vue-cliでは、ビルド時にバンドラによってimport・exportで繋がった複数のJavaScriptファイルたちを一つのJavaScriptにまとめる処理が行われます。つまり、ビルド前にES Modulesを利用していたとしても、ビルド後のJavaScriptではもはやES Modulesが使われいないことになります。 この一つにまとめるという処理(バンドル)は、単純に全てのファイルを繋げるだけではなく、辻褄を合わせるためにランタイムのコードを生成したりしています。この作業は、プロジェクトが大きくなればなるほど時間がかかるものになります。 このバンドルは、開発環境においても同様に行われておりファイルを編集するたびに裏で動いているため、開発環境を快適にする、効率をあげるという意味でもバンドルの高速化が重要になっていきます。 バンドルのフローは以下の図のようになっています。 そこで登場するのがNative ESMです。ビルド後の成果物にもES Modulesの利用が含まれる(言い換えれば、ビルド後の成果物が複数ファイル(モジュール)から成る)ようにしようというのがNative ESMです。 現在のNative ESMの開発環境では、そもそもバンドルせずにブラウザにESMを読み込ませる(複数ファイルを読み込ませる)。これによりビルドにかかる時間がなくなり、開発の効率が上がることになります。 Native ESMでのフローは以下の図のようになっています。 HMR さらに、HMRを行う際も、部分バンドルの作成のような面倒な処理をする必要が無く効率的です。 HMRを有効にするには、HMR境界を定義する必要があります。HMR境界とは(viteだけの用語?)、「このモジュール(およびその依存モジュール)が書き換えられたときはこのモジュールだけ再読み込みすればよくて、このモジュールに依存するファイルたちは再読み込みをする必要がない」という宣言です。HMR境界があれば、その内側のモジュールが更新されたときは境界の内側のみ再読み込みし、外側はそのままということが可能になり、効率がよくなります。これを設定するのは、かなり労力が必要になるが、Viteの場合、Reactプロジェクトに対しては@vite/plugin-react-refreshがこの設定をしてくれます。これにより、ReactコンポーネントのみをエクスポートするモジュールがHMR境界となり、ビルド・再読み込みのパフォーマンスが最適化されます。 これにより、モジュールの総数と切り離されているため、HMRが高速化されるということになります。 これらによりViteでは、開発時のビルドの高速化に繋がります。 その他参考資料 Viteを使ったvueプロジェクトの作成に関して 【Vite】 Vue3.0もReactも!ノーバンドルなビルドツール「Vite」を試してみる どれくらい速くなったのか検証 Vite は本当に早いのか ~ Vue CLI と比較 ~ Vite公式ページ tailwindcss 次は半年前くらいから話題になっているtailwindcssです。 tailwindcssに関しては、個人的に懐疑派でとりあえずプロジェクトで1回使ってみたいなって感じです。 tailwindcssとは ユーティリティクラスが大量に詰め込まれたライブラリーがcssです。 ユーティリティクラスとは、Bootstrapでよくあるmt-3とかがそれにあたります。 tailwindcssに関しては、新しい技術というよりは、css設計に関する話になっています なぜ今更ユーティリティクラスなのか cssは長い歴史があり、現在の主流のcss設計はBEMです。 BEMが主流となったのは、 cssがプロジェクト全体で共有されてしまうため、命名によって意図していない箇所に適用されないようにするため 単純なHTMLとCSSだけのプロジェクトだと、構造がわかりずらいので命名で分かりやすくするため という2点が大きいです(多分)。 プロジェクトが大きくなるとclassの命名には5分くらい掛けることもあり、コードを描いてる時間より名前を考えてる時間の方が長くなったりしていました。 しかし、Vue.jsとReactなどのコンポーネント思想によりこの2点の影響が少なくなってきました。 コードの全体像に関しては、小分けにされているため構造がわかりやすくなり、cssに関してもコンポーネント内でしか影響を与えないようになりました。 これにより、命名に頑張らなくても良くなり、ユーティリティクラスが導入できるようになりました。 cssをまとめる tailwindcssには、@applyを使うことによってcssをまとめることができます。書き方に関しては、調べればたくさん出てくるのでそちらを参考にしてください。 bootstrapなどには、btnなどのclassが存在していますが、tailwindcssには存在しません。そのため、毎回classに大量のクラス名を記述するのは大変になります。これを解決するのが@applyになります。これにより、頻度の高い構造体に関しては、まとめられ可読性も向上し、再利用性も高くなります。 繰り広げられる討論 さて、設計の話になってくると通常宗教戦争が勃発するように、tailwindcssについても賛否両論となってます。 tailwindcssは、確かに命名に掛ける時間が短縮されてコードに専念できるようになった一方、classが多くついてしまう、初期の学習時間がかかるなどの声が多くあります。また、かなり凝ったUIを実現しようとすると実装しにくいなどのデメリットもあるらしいです。  個人的な意見 既存のプロジェクトにbootstrapが入っていない、素のcssを使っているのであれば導入してもいいのかなといった感じです。また、bootstrapを導入しているが、足りずに自分でもcssを書いたりすることが多いプロジェクトに関しても導入を検討してみるのもいいではと考えてます。 その他参考資料 なぜ Tailwindcssなのか? CSSのパラダイムを振り返ってみた TailwindCSSって何?なんで流行ってるの? それでも私がTailwind CSSではなく、CSS Modulesを推す理由 ユーティリティーファーストとTailwind CSSのススメ swrv swrvは、SWRというデータ取得時の非同期処理の管理とキャッシュ管理を行うための、React Hooks向けライブラリを、vue hooks向けライブラリにしたものです。 swr(stale-while-revalidate )とは stale-while-revalidateは、RFC5861で策定された効率的なHTTPCache-Controlを実現するための戦略で、2つのキャッシュ戦略が示されています。 stale-while-revalidate 指定された期間に行われるキャッシュの再検証中は古いキャッシュを返す stale-if-error 指定された期間にネットワークエラーがあった場合古いキャッシュを返す 指定された期間は、キャッシュの生存期間とは別に指定します。例えば max-age=600、stale-while-revalidate=600 とすると20分間保持したキャッシュを返却します。ただし、stale-while-revalidate が有効なのは再検証が終わっていない間で、キャッシュが破棄された場合は次のフェッチでサーバーへ問い合わせを行います。 RFC2616では、「慎重に検討された状況」では期限の切れたキャッシュを返却しても良い、ということになっています。古いキャッシュというのはキャッシュの生存期間を過ぎてもstale-while-revalidate、または stale-if-error の値に基づいて返しても良いキャッシュ、つまり「慎重に検討された状況」で返しても良いキャッシュのことを指します。 簡単に言い換えると、まずは既存のキャッシュを返して(stale)、そのすぐ後にバックグラウンドで通信を行って、既存のキャッシュと取得したデータに違いがないかを検証します(revalidate)。キャッシュを更新している間に古いキャッシュを返すことで、ユーザーに遅延を感じさせないようにキャッシュコントロールできます。 SWRはこの2つをうまく落とし込んだライブラリです。stale-while-revalidateを自動で行い、さらにユーザコードから非同期処理を隠蔽することで stale-if-errorを楽に実装できます。そのため、React界隈では頻繁に利用されているライブラリーとなっている。 swrvのその他機能 重複削除機能 同じページ内で、同じAPIが2回以上呼ばれている時、1つになるように調整してくれる機能がある。 例えば、ダッシュボードページにheaderコンポーネント、ユーザ情報コンポーネントの2つのコンポーネントがあり、両方でuser/:idを叩いていた場合、片方だけが呼ばれるようになります。(めっちゃ便利) インターバルポーリング データが更新されているかどうかを確認するとき、すべてキャッシュからサービスを提供。また、ユーザーがオフラインまたはウィンドウがアクティブでない場合はポーリングを停止する。 その他参考資料 swrv公式 Data Fetching using Vue Hooks Vue に stale-while-revalidate がやってくる そうです。わたしがReactをシンプルにするSWRです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む