20220111のvue.jsに関する記事は8件です。

Vue3.2 で追加された effectScope の使用例のメモ

Vue 3.2 より Effect Scope API が追加されました。 こちらについての個人用メモです。 公式ドキュメントはこちら エフェクトスコープ API(日本語) RFC 一度これらのドキュメントに目を通すことをおすすめします。(特にRFC) 実際このメモもRFCの内容に近いです Effect Scope の使用例 具体例を交えて説明していきます。 以下のような useWindowSize 関数を考えます。 useWindowSize.js import { ref, readonly, onUnmounted } from "vue"; /** * windowのリサイズを監視し、ウィンドウサイズを返す */ function useWindowSize() { const width = ref(0); const height = ref(0); const onResize = (ev) => { const t = ev.target; width.value = t.innerWidth; height.value = t.innerHeight; }; // 初期値 width.value = window.innerWidth; height.value = window.innerHeight; // リスナー登録 window.addEventListener("resize", onResize); onUnmounted(() => { window.removeEventListener("resize", onResize); }); return { width: readonly(width), height: readonly(height), }; } export { useWindowSize } この関数は 実行時に ウィンドウの resize イベント監視を開始し、コンポーネント廃棄時に監視を終了するようにしています。 利用する側は以下のように 戻り値の width, height を使います。 QiitaSandbox.vue <script> import { useWindowSize } from "./useWindowSize"; export default { name: "QiitaSandbox", setup() { const size = useWindowSize(); // size.height, size.width を使う // 以下省略 }, }; </script> 問題点 上記のコードでも特に問題なく動きますが、気になる点があります。 useWindowSize 関数ですが 使用箇所それぞれで window にリスナー登録が行われます。 極端な話、とあるコンポーネントで useWindowSizeが使われており、そのコンポーネントが100個使われてると、100個のリスナーが登録されることになります。 これを回避する方法の例としては ルートに近いコンポーネントで useWindowSize を使い、サイズを Vuex などのストアで管理する方法も考えられます。 ただし、利用するコンポーネントが存在しない状況でもリッスンされ続けるという問題があります。 これらの問題を独自で解決する方法もありますが、Effect Scope API を使うと解決することができるようになります。 書き換えてみる まず useWindowSize 関数を書き換えます useWindowSize.js import { ref, readonly, onScopeDispose } from "vue"; /** * windowのリサイズを監視し、ウィンドウサイズを返すuse */ function useWindowSize() { const width = ref(0); const height = ref(0); const onResize = (ev) => { const t = ev.target; width.value = t.innerWidth; height.value = t.innerHeight; console.log(`resize(${width.value}, ${height.value})`); }; // 初期値 width.value = window.innerWidth; height.value = window.innerHeight; // リスナー登録 window.addEventListener("resize", onResize); // 解除はonScopeDispose で行う onScopeDispose(() => { window.removeEventListener("resize", onResize); }); return { width: readonly(width), height: readonly(height), }; } export { useWindowSize } onUnmounted が onScopeDispose に変わっています。 これは スコープが廃棄されるときに呼ばれるコールバックです。 次にスコープを作るための関数wrapScopeと、それを利用した新しいウィンドウサイズ用の関数useWindowSizeExを定義します。 wrapScope.js import { effectScope, onScopeDispose } from "vue"; function wrapScope(fn) { let count = 0; let state = null; let scope = null; const dispose = () => { count--; if (count <= 0) { // count が0 // つまり、使用中のものがなくなった時に実体を廃棄する。 state = null; scope.stop(); scope = null; } }; return (...args) => { count++; if (state == null) { //trueにするとスコープの親子関係が切り離される scope = effectScope(true); state = fn(...args); } //おもな呼び出しタイミングはコンポーネント廃棄の時 onScopeDispose(dispose); return state; }; } export { wrapScope } useWindowSizeEx.js import { wrapScope } from "./wrapScope"; import { useWindowSize } from "./useWindowSize"; // wrapScope でラップすることで、 // 関数呼び出し毎にグローバルリスナーが登録されるのではなく // グローバルリスナーは一つのみ // 参照がなくなればグローバルのリスナーも削除される // ということができるようになる。 const useWindowSizeEx = wrapScope(useWindowSize); export { useWindowSizeEx }; ポイントとなるのは wrapScope 関数でこれは関数を返す関数です。 これが生成した関数は 実行されるたびに count が+1されていきます。また0→1の時に effectScope でスコープが 作成されます。 effectScope の引数は true にする事で 現在のスコープとは切り離された独立したスコープになるようです。また0→1のタイミングでfn が実行されることで1回だけ リスナー登録がされるようになります。 onScopeDispose はそれを利用しているスコープが廃棄されたとき(典型的な例ではコンポーネント廃棄時)に実行されます。 この時に dispose が実行され countが-1されていきます。全ての利用箇所がなくなった時に scope.stop()が実行されスコープを廃棄します。 この関数が実行されることで useWindowSize の onScopeDispose が実行され、リスナーが解除されます。 利用するvue コンポーネントは useWindowSize のかわりに useWindowSizeEx を使うだけで済みます。 こうすることで、リスナー登録は1回にしつつ、利用するコンポーネントがある時だけリスナーが存在し続けるという事ができるようになります。 おわりに ここで紹介した利用例はほんの一例です。 Effect Scope の利用場面はこれ以外にもたくさんあると思います。 Pinia VueUse これらのライブラリでも Effect Scope は使われているので ソースを見ると使い方の参考になると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue3 で day.js

day.js を vue3 で読み込む方法。 app.js import dayjs from 'dayjs'; import 'dayjs/locale/ja'; dayjs.locale('ja'); const app = createApp({ data () { return { dayjs:'', } }, created(){ this.dayjs = dayjs; hoge.vue {{this.$root.dayjs().format('YYYY年M月DD日')}} こんな感じ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

css だけで ヘッダを固定

ヘッド要素の自動追随。 くっついて来るメニュー。 vue2 のときはvue.js の headroom を使っていたが見つからない。 ということで結論 hoge.vue <header style="background-color:yellow;position: sticky;top: 0;height:42px;z-index: 9999;"> これがヘッダーになるよ固定されるよ </header> たったこれだけok。 要素が後ろになったりする場合は z-index を多くしよう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

propsと$emitを使って親子間のデータを更新する

やりたかったこと ハンバーガーボタンをクリックしてメニューを開閉、 この時ハンバーガーボタンは三本線からバツマークに変わる。 開いたメニューの後ろをクリックしてもメニューが閉じる(ハンバーガーボタンも三本線に戻る) 子コンポーネント:ハンバーガーボタン 親コンポーネント:ハンバーガーメニューが置いてあるヘッダー (別コンポーネントに開閉後のメニュー) 試したこと 子コンポーネントでは、下記の記述をしていました。 Child.vue(ハンバーガーボタン) <template> <div class="hamburger_btn" @click="activeBtn = !activeBtn"> <span class="line line_01" :class="{btn_line01: activeBtn}"></span> <span class="line line_02" :class="{btn_line02: activeBtn}"></span> <span class="line line_03" :class="{btn_line03: activeBtn}"></span> </div> </template> <script> export default { data() { return { activeBtn: false, }; }, }; </script> // styleは割愛 クリックイベントでactiveBtnを発火させて、v-bindでクラスを付与する形にしていました。 (btn_line01〜03はバツボタンになった時のスタイルを当てています) ですが、これだと子コンポーネント内でしか値が更新されないため この子コンポーネントを他の場所でも使いやすくするため、propsと$emitを使って 親から子へ値を更新したいと考えました。 1. まずemitで子から親へ値を渡す MenuButton.vue(子コンポーネント) <template> <div class="hamburger_btn" @click="$emit('is-active')"> <span class="line line_01" :class="{btn_line01: activeBtn}"></span> <span class="line line_02" :class="{btn_line02: activeBtn}"></span> <span class="line line_03" :class="{btn_line03: activeBtn}"></span> </div> </template> <script> export default { data() { return { activeBtn: false, }; }, }; </script> // styleは割愛 Header.vue(親コンポーネント) <template>  <HamburgerMenuButton @is-active="menuToggle()" /> <!-- v-onでイベント名を受け取る -->  <transition>  <div v-if="activeButton">  <div class="bg" /> <!-- メニューが開いている時の後ろの黒背景 -->  <MenuBar /> <!-- メニュー(別コンポーネント) -->  </div>  </transition> </template> <script> import MenuButton '@/components/MenuButton'; import MenuBar from '@/components/MenuBar'; export default { components: { MenuButton, MenuBar, }, data() { return { activeButton: false, }; }, methods: { menuToggle() { this.activeButton = !this.activeButton; }, }, }; </script> 子コンポーネントの@clickで、$emit('is-active')で設定したイベント名を 親コンポーネントの使いたい場所で@is-active="メソッド名"という形で設定して受け取ります。 今回はハンバーガーメニューをクリックでメニューを開閉させる かつ メニューボタンのアニメーション(三本線からバツボタン)を実行させたいので、 menuToggle()というメソッドを用意して、その中にactiveButtonのtrue/falseトグル処理を書きました。 ただこれだけだと、親コンポーネント内でのデータの書き換えになるので 子コンポーネントにはactiveButtonがfalseやtrueになったことが渡っていません。 2. propsで親から子へデータを渡す Vueでは、propsは子のコンポーネントに記述します。 MenuButton.vue(子コンポーネント) <template> <div class="hamburger_btn" @click="$emit('is-active')"> <span class="line line_01" :class="{btn_line01: activeBtn}"></span> <span class="line line_02" :class="{btn_line02: activeBtn}"></span> <span class="line line_03" :class="{btn_line03: activeBtn}"></span> </div> </template> <script> export default { props: { activeBtn: { type: Boolean, default: false, }, }, }; </script> 親から値が渡ってきて更新されればバツボタンの表示になってくれるので dataからpropsに置き換えました。 親コンポーネントでは下記のように記述します。 Header.vue(親コンポーネント) <template>  <HamburgerMenuButton @is-active="menuToggle()" :active-button="this.activeButton" <!-- 子に渡すデータを指定 --> />  <transition>  <div v-if="activeButton" @click="menuClose()"> <!-- メニューを閉じるクリックイベントを追加 -->  <div class="bg" />  <MenuBar />  </div>  </transition> </template> <script> import MenuButton '@/components/MenuButton'; import MenuBar from '@/components/MenuBar'; export default { components: { MenuButton, MenuBar, }, data() { return { activeButton: false, }; }, methods: { menuToggle() { this.activeButton = !this.activeButton; }, menuClose() { this.activeButton = false; }, }, }; </script> propsでもらったデータは動的に子に渡したいので:active-button="this.activeButton"と記述しましいた。 注意点としては、子コンポーネントのpropsで定義した変数はキャメルケース(activeButton)ですが template内ではケバブケース(active-button)となります。名前が一致していないのでここがややこしいです…。 propsでもらったデータはそのまま書き換えるのではなく、dataに入れて書き換えて使います。 参考記事 Vue.jsをシンプルに理解しよう その4 -propsとemitについて-
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue3-lazyload の使い方

vue3 で遅延ロードしたい。 をインスコしたが、うまく動かない。 <img v-lazy="'/img/man.jpg'" /> 上記のように " ' の両方を指定すれば動く。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue3 オブジェクトの統合

オブジェクトを入れる場合は、以下 this.user = Object.assign({}, this.user,data) これでオブジェクトを入れられる。 そのまま突っ込んでもバグるので注意。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【django/vue.js初学者向け】DjangoRestFrameworkで作成したAPIをVue.jsから叩いてみる

目的 django_restframeworkでAPIを作成してみたが、Vue.jsでフロント画面も作成し、フロントからaxiosを利用してAPIを叩くところまでしてみたいと思った 実施環境 ハードウェア環境 項目 情報 OS macOS Catalina(10.15.7) ハードウェア MacBook Air (11-inch, Early 2015) プロセッサ 1.6 GHz デュアルコアIntel Core i5 メモリ 4 GB 1600 MHz DDR3 グラフィックス intel HD Graphics 6000 1536 MB ソフトウェア環境 項目 情報 homebrew 3.3.8 mysql Ver 8.0.27 for macos10.15 on x86_64 python 3.8.12 node 17.2.0 npm 8.1.4 @vue/cli 4.5.15 vue 2.6.14 前提 vueとdjango_restframeworkを使ったことがあるが、どのようにしてvueからdjango_restframworkのAPIを叩けばいいかわからない人。 npmとnode.jsがインストールされていること。 確認方法は $ npm -v $ node -v 項目 バージョン   node    17.2.0      npm    8.1.4   環境構築 vueCLIをインストール VueCLIとはvue.jsで開発の準備を支援してくれるツール VueCLIがあると プロジェクトのテンプレートの作成 複数のjsファイルを一つにまとめる .vueファイルを.jsに変換する トランスパイル JavaScriptの構文チェック テストツールの導入など これら全てを行ってくれる。非常に優秀。 インストールする。 $ sudo npm i -g @vue/cli Vue CLI v4.5.15のインストールが完了 VueCLIを使ってvueプロジェクトを作る $ vue create プロジェクト名 この後に対話式でいくつかの質問に答えるとプロジェクトが作成される こちら参考にしていく。 webpack-bundle-trackerをインストール npm i webpack-bundle-tracker@0.4.3 画面確認画面確認 npm run serve axiosインストール npm install --save axios vue-axios  axiosはPromiseベースのHTTP ClientライブラリでGETやPOSTのHTTPリクエストを使ってサーバからデータの取得、データへのデータ送信を行うことができます。 vue.jsを使ってaxiosを学ぶより vueプロジェクトにAPIを叩く処理を実装 main.jsでaxiosをimportする main.js import Vue from 'vue' import App from './App.vue' import router from './router' import axios from 'axios' //追記 import VueAxios from 'vue-axios' //追記 Vue.config.productionTip = false Vue.use(VueAxios, axios) //追記 new Vue({ router, render: h => h(App) }).$mount('#app') index.jsでルーティングを実装する ここではルーティングを実装し、どのURLでどのコンポーネントを表示するかを定義する。 index.js import Vue from "vue"; import VueRouter from "vue-router"; import Home from "../views/Home.vue"; import GetMembers from '@/components/GetMembers' Vue.use(VueRouter); const routes = [ { path: "/", name: "Home", component: Home, }, // 追記 // http://localhost:8080/get_membersでGetMembersコンポーネントが呼ばれる { path: "/get_members", name: "GetMembers", component: GetMembers }, ]; const router = new VueRouter({ mode: "history", base: process.env.BASE_URL, routes, }); export default router; 実際に表示される画面を実装 上記のindex.jsでルーティングされたgetMember.vueでaxiosでdjango_restframeworkのAPIを叩く処理を実装していく。 下記のaxiosの処理によってページが更新される時にhttp://127.0.0.1:8000/api/v1/member/のURLに対してGETメソッドでHTTP通信を行う。 そのレスポンスをMembersプロパティに格納し、ループで各memberのusernameを表示することができる。 getMember.vue <template> <div> <div v-for="(member, key) in Members" :key="key"> <hr> <p>{{ member.username }}</p> <hr> </div> </div> </template> <script> export default { data() { return { Members: [] }; }, // ページを読み込んでマウントを行っている状態 mounted() { this.axios // APIのURL .get("http://127.0.0.1:8000/api/v1/member/") // レスポンスをdata()内のMembersプロパティに格納 .then(response => (this.Members = response.data)); } }; </script> 実はこれだけでAPIを叩くことはできない。 サーバサイド側でCORSの設定をすることでようやくAPIを叩くことを許可される。 CORSとは、同一生成元でないところへの要求を安全に許可する仕組みのことだが、詳しくはこの神記事を確認してほしい drfでjwt認証APIの作成方法 と言うことで、djangorestframework側でCORSの設定を行う。 pip install django-cors-headersでcorsheadersをインストール そしてsettings.pyに下記のように記述する settings.py INSTALLED_APPS = [ # CORS対策用のlib 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', ] # VueからのXMLHttpRequestのアクセスがDRF側でブロックされないようにCORS設定をする CORS_ALLOWED_ORIGINS = [ 'http://localhost:8080', ] CORS_ALLOWED_ORIGINSにはこのdjangorestframeworkのAPIを叩くことを許可するurlを記載する。 これでとりあえずはフロント(vue)からAPIを叩くことができた。 認証周りなどは次回また詳細に記述していく。 参考文献 https://qiita.com/taki_21/items/d2905e093d326f72a286 https://stackoverflow.com/questions/35760943/how-can-i-enable-cors-on-django-rest-framework
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue-infinite-loading で no-resultsなどの設定を共通化する方法

<div slot="spinner">読込中</div> <div slot="no-more">全件取得しました</div> <div slot="no-results">データがありません</div> <div slot="spinner"> </div> これ毎回コンポーネントにかくのだるくね? だるいだるいだるい!!!!! やったこと 公式ドキュメントを読んだ。 https://peachscript.github.io/vue-infinite-loading/ 英語と中国語 ? 解決策 import Vue from 'vue' import InfiniteLoading from 'vue-infinite-loading' Vue.use(InfiniteLoading, { slots: { spinner: "データないです", noMore: 'もうないです。', noResults: "データがない", }, }); おそらくInfinitive loadを入れる際にプラグイン?として任意のフォルダにjsファイルを入れているかと思います。(忘れている人は思いだして。) そこに共通の設定が書けるようになっていました。なんて便利なんだ! 注意点として変数?として"-"が使えないようなので?キャメルケースでの記述に変換するようです。 no-more -> noMore no-results -> noResults <div slot="no-more">全件取得しました</div> ↓こんな感じかな。 noMore: '全件取得しました。', 結論 開発がんばろう。!!!!!!!!!!!!!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む