20220110のvue.jsに関する記事は3件です。

Webアプリ間でWebSocket使ってデータをリアルタイムにやりとりするの作ってみたのでメモ

概要 ちょっとしたデモを作る時など、PCやスマホ、IoTデバイス等の間でリアルタイムにデータをやりとりしたいときって結構あります。 そんなとき Firestore 使うと便利なんですけど、Internet につながらない環境という場合がけっこうありまして、そんなのときのためにローカル回線の中で簡易 Firestore 的なこと(データを更新したら各WebApp上のデータも自動でリアルタイムに更新されるというもの)を実現してみました。 作ったもの 同期するデータ(サーバ/クライアントで共有) synchroData.ts interface AllData { aaa: number bbb: string ccc: string[] } const allData: AllData = { aaa: 123, bbb: 'abc', ccc: [], } export default {} export { allData, AllData } WebSocketサーバ(node-ts) package.json { "name": "synchro-data", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "ts-node src/index.ts" }, "license": "MIT", "devDependencies": { "typescript": "^4.5.4" }, "dependencies": { "@types/lodash": "^4.14.178", "@types/node": "^17.0.8", "@types/ws": "^8.2.2", "lodash": "^4.17.21", "ts-node": "^10.4.0", "ws": "^8.4.0" } } index.ts import { Server } from 'ws' import { set } from 'lodash' import { allData, AllData } from './synchroData' const ws = new Server({ port: 5001 }); ws.on('connection', socket => { console.log('connected!') // 初期データ受け取り socket.send(JSON.stringify({ allData, })) socket.on('message', msg => { console.log('receive: ' + msg); // 自身のデータ更新 const received = JSON.parse(`${msg}`) if (received.method === 'set') { set(allData, received.path, received.data) console.log('allData', allData) } // クライアントにリレー ws.clients.forEach(client => { // console.log(`${msg}`); client.send(`${msg}`) }) }) socket.on('close', () => { console.log('good bye.'); }) }) console.log('Start!') tsconfig.json { "compilerOptions": { "module": "commonjs", "target": "ES2021", "sourceMap": true, "types": [ "node" ] }, "include": [ "./src/**/*" ], "exclude": [ "node_modules" ] } クライアント(nuxt3) package.json { "private": true, "scripts": { "dev": "nuxi dev", "build": "nuxi build", "start": "node .output/server/index.mjs" }, "devDependencies": { "nuxt3": "latest" }, "dependencies": { "@types/lodash": "^4.14.178", "lodash": "^4.17.21" } } nuxt.config.ts import { defineNuxtConfig } from 'nuxt3' // https://v3.nuxtjs.org/docs/directory-structure/nuxt.config export default defineNuxtConfig({ ssr: false, }) plugins/SynchroDataPlugin.ts import { reactive, ref } from 'vue' import { defineNuxtPlugin } from '#app' import { set as lodashSet } from 'lodash' import { allData, AllData } from '~/plugins/synchroData' interface SynchroDataJournal { method: 'set' | 'append' path: string data: any } const initSynchroData = <SynchroData extends object> (defaultData: SynchroData) => { type ResOfAllData = { allData: SynchroData } const isResOfAllData = <SynchroData> (res: any): res is ResOfAllData => { return !!res['allData'] } const synchroData = reactive<SynchroData>(defaultData) const synchroDataStatus = ref('') const mutateSynchroData = (journal: SynchroDataJournal) => { if (journal.method === 'set') { lodashSet(synchroData, journal.path, journal.data) // console.log('allData', allData) } } const ws = new WebSocket('ws://localhost:5001') ws.addEventListener('open', (e) => { synchroDataStatus.value = 'Socket Success' }) ws.addEventListener('message', (e) => { synchroDataStatus.value = `received: ${e.data}` const received = JSON.parse(`${e.data}`) as ResOfAllData | SynchroDataJournal if (isResOfAllData(received)) { Object.assign(synchroData, received.allData) } else { mutateSynchroData(received) } }) const synchroDataEmit = (journal: SynchroDataJournal) => { ws.send(JSON.stringify(journal)) } return { provide: { synchroData, synchroDataStatus, synchroDataEmit, } } } export default defineNuxtPlugin(nuxtApp => initSynchroData<AllData>(allData)) pages/index.vue <template> <div> <div>{{ $synchroDataStatus }}</div> <div>{{ $synchroData }}</div> <button @click="dataChange"> dataChange </button> </div> </template> <script setup lang="ts"> const { $synchroData, $synchroDataStatus, $synchroDataEmit } = useNuxtApp() const dataChange = () => { $synchroDataEmit({ method: 'set', path: 'aaa', data: $synchroData.aaa + 1, }) } </script> 結果 これで $synchroData には常に最新のデータがリアクティブに入っている状態になる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

覚えておくと便利なv-bindの機能をまとめてみた

はじめに Vue.jsのv-bindは個人的によく使う機能なので、基本に立ち返ってみました。 色々機能があるv-bindですが、特に意識すべき基本構文や覚えておくと便利な機能をまとめました。 この記事で紹介している事はv-bindの機能について書いているので、Vue.jsのバージョンについては2系と3系、基本的にどちらも共通なもので、特に気にしなくてもいいと思います。 基本 v-bindとは v-bindを使うと状態の変化によってHTMLの属性を変えることができます。 属性というのは下のようなclass属性、text属性の事を指します。 <input class="content" type="password"/> v-bindを使うと状態に合わせて動的に属性を変える事ができる為、動的なwebアプリケーションを表現する事ができます。 v-bindでは他にもstyle属性もとい、すべてのHTML属性に対して使用する事ができます。 基本の書き方 <input v-bind:class="値とか評価する式" /> 属性の前に"v-bind"をつけます。上ではclass属性にv-bindをつけています。 v-bindを付与する事を「バインドする」と言ったりします。 覚えておきたいこと 1. v-bindは省略できる <input :class="content" type="password" /> v-bindはコロン『:』に置き換えることができます。(省略記法) 開発の現場ではv-bindではなくコロンで記述されている事もあるので覚えておかないと混乱します。 2. 値には変数を当てて使用する事が多い v-bindは状態が変わる事が前提なので、:class="color_blue"のように決めうちで書く事はあまりないです。 実際は、下記の様にdata属性と紐づけて使用する場面が多いです。 example.vue <template> <input :class=isColor /> </template> <script> export default { data() { return{ isColor: "blue" // isColorの中身が反映される。 } } } </script> ただ、上記の例だとisColor決め打ちしている場所が変わっているだけなので、methodsのところに変数を変える関数を実行させている事が多いです。 例えば下記のコードだったら、isColorで検索してみてどこでどの様にisColor変わっているのか?と思いながらコードを読む事が多いです。 example.vue <template> <input :class=isColor />   <button @click="isClick">ボタン</button> </template> <script> export default { data() { return{ isColor: "blue" } }, methods: { isClick() { this.isColor = "red" // isClickメソッドを発火させて、isColorの中身を書き換える。 }, } } </script> 3. 一部の機能だけバージョンにより振る舞いが違う かなり限定的であまり気にしなくていいのですが、一応注意点です。 vue.jsのバージョン2系と3系とでは、id属性を指定している際に若干の違いがあります。 下記の様にバージョン2系ではv-bindより通常の属性が優先されてしまいます。 この仕様は順番が変わっても、通常の属性の方が優先されます。 ver2_x.vue <!-- テンプレート --> <div id="red" v-bind="{ id: 'blue' }"></div> <!-- 結果 --> <div id="red"></div> <!-- テンプレート --> <div v-bind="{ id: 'blue' }" id="red"></div> <!-- 結果 --> <div id="red"></div> 一方でバージョン3では、後に書かれている方が優先されます。 ver3_x.vue <!-- テンプレート --> <div id="red" v-bind="{ id: 'blue' }"></div> <!-- 結果 --> <div id="blue"></div> <!-- テンプレート --> <div v-bind="{ id: 'blue' }" id="red"></div> <!-- 結果 --> <div id="red"></div> ちなみにclass属性だと、どちらか優先されるとかはなく両方とも出力されます。 v-bimdはVue.jsバージョン2と3での振る舞いに違いがある。 参考のドキュメントはこちら。 使うと便利な書き方 ここまで書いてきた事を前提に、実際に意識しておくと便利、よく使う書き方について紹介します。 1. dataの値によってclass属性を書き換える class属性の変更はv-bindを使うのに便利で使い所が多いです。(一番使っているかもしれないです。) 例えば、ログインしている時だけにclass属性をバインドして色を変えたり、display:noneを指定して表示を消したり、マウスオーバーしてアクティブの時だけ表示変えたり...色々できます。状態に合わせて表示部分もリアクティブに変えられるので結構汎用性が高いです。 一応style属性でもできるのですが、HTML上にCSSを書いてしまうことになり、かなり見通しが悪くなるのでclass属性の方が使う事が多いです。 class属性の使い方は以下の構文に沿う必要があります。 example.vue <div :class="{ クラス名: 値または変数、プロパティなど }"> <!-- 下記の様に書く --> <div :class="{ isOpenClass: isOpen }"> 以下は、isOpenがtrueの時だけ適用するときの書き方です。 example.vue <template>  <div> <div :class="{ isOpenClass: isOpen }">    <button @click="isClick">ボタン</button> </div> </template> <script> export default { data() { return{ isOpen: true } }, methods: { isClick() { this.isOpen != this.isOpen // クリックすることにbooleanが切り替わる。 }, } } </script> <style scoped> .isOpenClass { // isOpenがtrueの時だけ適用させたいCSSを記述する。 } </style> isOpenがtrueの時だけ適用させたい部分(isOpenClass)にCSSを記述するだけなので、直感的に書けるのもメリットですね。 2. disabledをバインドして入力フォームやボタンを制御する disabled属性をバインドすると、非活性化しボタンを押させないなど制御する事ができます。 isDisabledにはtrueまたはfalse(boolean)を指定します。 参考: :disabled | MDN <div> <input :disabled="isDisabled" /> <button :disabled="isDisabled">ボタン</button> </div> 3. type属性にバインドして入力方法を工夫する type属性をバインドすることで、入力方法を工夫することができます。 例えば、ユーザーの入力画面でパスワードを入力する際に最初はtype="password"としておき、何度か間違えた時にtype="text"に変更することで、黒丸の入力から通常の入力に変えて、ユーザーにとって分かりやすく入力を促すことができます。 <div> <input :type="showNotice ? 'password' : 'text'" /> </div> 4. isバインドを使ってコンポーネントを動的に切り替える 今までと少し趣旨が違いますが、v-bindはコンポーネントを切り替えることも可能です。 利用するにはtemplateの部分に<component :is="data名"></compenent>を指定する。 example.vue <template> <div> <component v-bind:is="changeComponent"></component> <div @click="clickChange">コンポーネント変える</div> </div> </template> script側では下記の様にして、クリックしたらコンポーネントを変えるという処理にしています。 example.vue <script> import HogeComponent from "~/components/molecules/hoge.vue"; import HugaComponent from "~/components/molecules/huga.vue"; export default { data() { return { changeComponent: this.$options.components.HogeComponent }; }, components: { HogeComponent, HugaComponent }, methods: { clickChange: function() { this.changeComponent = this.$options.components.HugaComponent); } } } </script> 最後に v-bindについては他にも機能が沢山ありますが、特に意識すべき所だと思った所をまとめました。 紹介した4つの機能は個人的に使用頻度が高いため、今後も意識してより良く使いこなしていきたいです。 参考 v-bindの機能を全てまとめている方の記事 基本構文など分かりやすい記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuexはもう古い!?これからの状態管理はPiniaを使おう

はじめに 煽り気味のタイトルですみません。Vuexは使わない方がいいという話ではなく、別の有力な選択肢が出てきたというレベルの話であると最初に断っておきます。 みなさん、Piniaという状態管理ライブラリをご存知ですか?僕は昨日知りました。 調べてみるとどうやら先月Vueの公式リポジトリ入りをしており、今後さらに注目が集まるライブラリになりそうです。 * ちなみにPiniaはVuexを置き換えるものではなく、Vuexは今後も開発を継続していくようです(現在はVuex5を開発中)。 Vueの状態管理といえばVuexというイメージがありますが、TypeScriptとの相性が良くないだとか、そもそも冗長だとかで長らくVue3時代の状態管理のベストプラクティスがよくわからない状況が続いていました(?) 僕の知っている限りではVue状態管理の方法は 1. Vuex 2. provide/inject 3. 単にグローバルでリアクティブ変数を宣言する 4. Pinia の4つですが、1の問題は前述した通りで、2、3はシンプルですがプロダクトやチームの規模によっては無秩序になりやすいという問題があります。 その点Piniaはシンプルで型安全かつパフォーマンスも良いというので、かなり有力な候補になり得るのではないでしょうか。 目次 基本的な使い方 補足 参考文献 基本的な使い方 導入は簡単 公式より yarn add pinia main.ts import { createPinia } from 'pinia' // ... app.use(createPinia()) これだけ ストアはdefineStoreで定義 ぱっと見そんなにVuexと変わらないので、Vuex利用者は特に説明がなくても理解できると思います。 第一引数はアプリケーション内でユニークなキー 第二引数はストアの定義となるオブジェクトで、基本はstate,getters,actionsの3つのプロパティを指定 vuexと違いmutationsが存在しないのでよりシンプルですね stores/counter.ts import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => { return { count: 0, } }, getters: { double: (state) => state.count * 2, }, actions: { increment() { this.count++ }, } }) 使用する際は <script lang="ts"> import { defineComponent } from 'vue' import { useCounterStore } from '@/stores/counter' import { storeToRefs } from 'pinia' export default defineComponent({ setup() { const counterStore = useCounterStore() // toRefsだとreactivityが壊れるので注意 const { count } = storeToRefs(counterStore) return { counterStore, count } }, }) </script> <template> <div>{{ count }}</div> <div>{{ counterStore.double }}</div> <button @click="counterStore.increment()" > increment! </button> </template> <script setup>記法で書くと ※ この記法を知らない方はここを参照 <script setup lang="ts"> import { useCounterStore } from '@/stores/counter' import { storeToRefs } from 'pinia' const counterStore = useCounterStore() const { count } = storeToRefs(counterStore) </script> <template> <div>{{ count }}</div> <div>{{ counterStore.double }}</div> <button @click="counterStore.increment()" > increment! </button> </template> 基本的な使用方法は以上です。 storeToRefsでリアクティブを維持したまま分割代入可能です。 gettersプロパティはcomputedと同様にリアクティブです。 おまじないが少なくて作業中に頭が混乱するのを防げそうですね。 補完もバッチリ効きます。 ちなみに、store.count++ とかstore.count = 10とかも可能です 実態はreactive()で作成したリアクティブオブジェクトのラッパーにすぎないのでこういうこともできちゃいます。 実際の運用ではstateを直接変更するのは禁止すべきかもしれませんね。 僕の場合、個人開発ではpiniaっぽいことをわざわざライブラリ無しでやっていたので、まさに自分が求めていたものが既に存在していたと知り感動しました。 補足 他のstoreにアクセスしたい場合 この場合もシンプルで、単に使用箇所でストアを取得しプロパティにアクセスするだけです。 stores/counter.ts import { useOtherStore } from './otherStore' export const useCounterStore = defineStore('counter', { state: () => { return { count: 0, } }, getters: { other: (state) => { const other = useOtherStore() return other.num * state.count } }, }) 参考文献 - 公式サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む