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

意気揚々とAtomic Designを導入したら、v-modelとmethodsに躓いたお話

Atomic Designとは パーツ・コンポーネント単位で定義していく、UIデザイン手法です。 最小の単位「原子」(Lv1)からデザインし、「分子」(Lv2)、「生体」(Lv3)、「テンプレート」(Lv4)、「ページ」(Lv5)の順にデザイン作業を進めていきます。 今回はフォームを作成しました。 Atoms Template FormInput.vue Form.vue FormButton.vue Atomsにフォームの入力欄のFormInput、フォームの送信ボタンのFormButtonの作成をします。 Templateに、Atomsを使って作成したフォームをFormとして作成します。 以下の記事にもあるように、完璧な線引きは難しいので私は適当にAtomsとTemplateを使います。 FormInput.vueを作成する FormInput.vueでは、v-modelでの値の渡し方に躓きました。 ありがちなミスですが、親から受け取ったvalueをv-modelにしました。 ただこれではうまくいきません。 Atoms/FormInput.vue <template> <input v-model="value" /> </template> <script lang="ts"> import { defineComponent, ref, onMounted, PropType } from '@nuxtjs/composition-api'; export default defineComponent({ props: { value: { type: String, required: true, }, }, }); 何故なら、valueは親から渡された値なので、読み取り専門になるからです。 子で値を上書きしても、その値が親まで伝わりません。 v-modelを使った値の渡し方 答えはこの記事にありました。 要約すると、v-modelは:valueと@inputの糖衣構文だから分解しなさいという内容です。 ということで実際にコードを書いてみましょう。 まずは親からです。 Template/Form.vue <template> <div class="form-container"> <FormInput :type="'text'" :placeholder="'姓'" :value="firstName" @input="firstName = $event" /> <FormInput :type="'text'" :placeholder="'名'" :value="lastName" @input="lastName = $event" /> <FormInput :type="'email'" :placeholder="'メールアドレス'" :value="email" @input="email = $event" /> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted } from '@nuxtjs/composition-api'; // components import FormInput from '@/components/Atoms/FormInput.vue'; export default defineComponent({ components: { FormInput, }, setup() { const firstName = ref<string>(''); const lastName = ref<string>(''); const email = ref<string>(''); return { firstName, lastName, email, }; }, }); </script> 次に子です。 Atoms/FormInput.vue <template> <div> <input class="form-input" :type="type" :placeholder="placeholder" :value="value" @input="$emit('input', $event.target.value)" /> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted, PropType } from '@nuxtjs/composition-api'; // types import { FormInputType } from './types/FormInput.type'; export default defineComponent({ props: { type: { type: String as PropType<FormInputType>, default: 'text', validator: function(value: string) { return ['text', 'email', 'password'].indexOf(value) !== -1; }, }, placeholder: { type: String, required: true, }, value: { type: String, required: true, }, }, }); </script> Atoms/Types/FormInput.type.ts export type FormInputType = 'text' | 'email' | 'password'; これで無事に動きました! FormButton.vueを作成する FormButton.vueでは、methodsの使い方に躓きました。 方法としては以下の2つです。 ①Vuexを使う ②$emitやpropsを使う VuexとTypeScriptの相性が悪いので、①はなし。 なので②の方法を考えました。 $emitを使わずに作る 以下の記事を参考にしました。 TypeScriptを使うのでpropsの成約をつけられるというメリットでこちらを採用しました。 ということで実際にコードを書いてみましょう。 まずは親からです。 Template/Form.vue <template> <div class="form-container"> <FormButton :click="submit" @submit="submit" /> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted } from '@nuxtjs/composition-api'; // components import FormButton from '@/components/Atoms/FormButton.vue'; export default defineComponent({ components: { FormButton, }, setup() { const submit = async (): Promise<void> => { try { await window.liff.sendMessages([ { type: 'text', text: firstName.value, }, { type: 'text', text: lastName.value, }, { type: 'text', text: email.value, }, ]); await window.liff.closeWindow(); } catch (err) { window.alert(err); await window.liff.closeWindow(); } }; return { submit, }; }, }); </script> 次に子です。 Atoms/FormButton.vue <template> <div class="form-button"> <button class="button" @click="childClick">送信</button> </div> </template> <script lang="ts"> import { defineComponent, ref, onMounted, PropType } from '@nuxtjs/composition-api'; // type import { ClickType, Props } from './types/FormButton.type'; export default defineComponent({ props: { click: { type: Function as ClickType, required: true, }, }, setup(props: Props) { // methods const childClick = () => { props.click(); }; return { childClick, }; }, }); </script> Atoms/Types/FormButton.type.ts export type ClickType = { (): void; }; export type Props = { click: Function; }; これで完成です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのCLIプロジェクトを新規に導入&Bootstrapを利用可能とするときの手順メモ

概要 自分用の( ..)φメモメモ。 Vue.jsのCLIプロジェクトを新規に導入し、 且つBootstrapを利用可能とするときの手順。 必要最小限。testはMocha+Chaiとする。 Vue.jsはVue2.xとする(そろそろVue3.xへ移行すべきかもだが、調査時間が今は無いなので)。 @vue/cliはローカル導入とする。 backage.json "dependencies": { "bootstrap-vue": "^2.21.2", "core-js": "^3.6.5", "vue": "^2.6.11" } 手順のメモ Vue/cliの(一時的)導入 以下のようにして、Vue-ClIをローカルに導入する。 本手順を最後まで終えたら、Vue-ClIはアンインストールしてもよい。 ここでは、package.jsonは作らない。 npm install @vue/cli npx vue create cli-vue Vue Cliのプロジェクトを作成 最初の選択肢は「Manually select features」を選択する。 これは、UnitTestを導入したいため。 続く設問は、Unit Testing を選択する。 Vue CLI v4.5.13 ? Please pick a preset: Manually select features ? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection) (*) Choose Vue version (*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support ( ) Router ( ) Vuex ( ) CSS Pre-processors (*) Linter / Formatter >(*) Unit Testing ( ) E2E Testing 以降の設問は、デフォルトのまま進む。 Choose a version of Vue.js that you want to start the project with (Use arrow keys) > 2.x 3.x Pick a linter / formatter config: (Use arrow keys) > ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection) >(*) Lint on save ( ) Lint and fix on commit Pick a unit testing solution: (Use arrow keys) > Mocha + Chai Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) > In dedicated config files In package.json ここまででVue CLIの導入は完了。 BootStrap-Vueの導入 Vue CLIのプロジェクトのルートフォルダにて、以下のコマンドでインストールする。 cd vue-cli npm install bootstrap-vue --save 続いて、ルートにあるmain.jsを開いて、次の2行(本サンプルではコメント含めて4行)を追加する。 main.js import Vue from 'vue' import App from './App.vue' // +++ add for bootstrap +++ import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap-vue/dist/bootstrap-vue.css' // ------------------------- Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app') 以上ー。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

propsと$emitによるデータのやりとり

はじめに Vue.jsは1つのコンポーネントで実装するよりも、粒度に応じて複数のコンポーネントに分解して実装する方がよいとされている。そのようが、ソースコードの可読性やメンテナンス性、コンポーネントの再利用性も活かすことができて便利なためだ。だが、複数のコンポーネント間でデータのやり取りも必要不可欠になり、そこがVue.jsの扱いで最も難しいとされる箇所の1つなのではないかと思う。 そこで今回は自分が忘れてしまってからも当記事を確認するだけですぐに思い出せるように、簡単に実装例も交えつつ、「props」「emit」について取り上げます。 emit, propsとは コンポーネント間には親と子という概念がある。片方のコンポーネントがもう片方のコンポーネントを取り込み(import)するとき、importする側を「親」,importされる側を「子」と呼ぶ。そして、親から子へデータを伝達する手段を「props」。子から親へデータを伝達する手段を「emit」としている。 propsの使い方(親から子へデータを送る) V-bind(:)を経由して親から子へデータを送信して、子でpropsを使ってプロパティという形で親からのデータを受け取って利用できる。 親からv-bindでデータを子へ送る Parents.vue <template> //親で扱ってるデータをバインドしてそのプロパティとして子へデータ送る <Child :param0=“item” :param1=“value” /> <template> <script> import Child from './components/Child.vue' components: { Child }, </script> 子でprops内のプロパティをという形で親が飛ばしたデータを受け取ってdataのように利用できる Child.vue //親から送られてきたデータを受け取りdataのように使う props: [“item”, “value”] emitの使い方(子から親へデータ送る) emitを使ったカスタムイベントで子から親へデータが渡される。 カスタムイベントとは、子コンポーネント内「@click」のイベントがクリックされると同時に発火する$emitを使用したイベントのこと。子コンポーネント内で親に送りたいデータを引数にした「$emit」が発火すると、親でそのイベントが違う名前のmethodsに渡されて、そのmethodsが発動する。つまり、子で発火してその引数がデータとして親に渡されて親のmethods内で関数として使われる。 子から親へデータを渡すには、@clickや@changeなどのなにかしらのイベントが必要になってくる。 Child.vue <template> <div> <p>child_num: {{ child_num }}</p>         //@clickイベントでmethodsのsendが発動 <button @click='send'>親に値を渡す</button> </div> </template> <script> export default { data: function() { return { child_num: 0 }; }, methods: {         //関数内のカスタムメソッド$emitが発火し、引数として親に送りたいデータを渡すことができる send() { this.$emit("my-click", this.child_num); } } }; </script> </script> Parents.vue <template> <div> <p>parent_num: {{ parent_num }}</p>         //子のカスタムメソッドを親の関数として使う <Child @my-click='reflectNum'/> </div> </template> <script> import Child from './components/Child.vue' export default { data: function() { return { parent_num: 100 } }, components: { Child }, methods: {         //子のカスタムイベントで引数にしたデータはこちらで受け取る(子から親へデータ渡す) reflectNum(value) { this.parent_num = value } } } </script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuexで初期値をセットする

Vuexで初期値をセットする方法 Vuexで初期値をサーバーから取得してセットする方法を記述します。 環境 Vue 2.5.17 Vuex 3.6.2 参考サイト 実装例 Vuexのインスタンスに初期化用のメソッドを作成します。 例ではinitと命名していますが名前は任意です。Ajaxでデータを取得しセットします。 const store = new Vuex.Store({ state: { userId: "", userName: "" }, // ... actions: { init() { // ajaxで初期値を取得し、stateにセットします。 axios.post("/api/vuexInit").then(response => { this.state.userId = response.data.user_id; this.state.userName = response.data.user_name; }).catch(error => { console.error(error); }); }, } }); 上記で定義したメソッドをVueコンポーネントのcreatedなどで呼び出すことで初期値をセットできます。 created: function () { this.$store.dispatch("init"); },
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

plunkerでvue その48

概要 plunkerでvueやってみた。 練習問題、やってみた。 練習問題 chatを実装せよ。 参考にしたページ バックエンド enebular サンプルコード const MSG_START = "start"; new Vue({ el: "#app", components: {}, data: () => ({ name: "名無し", message: "おはようございます。", connection: null, messages: [], ws_key: 0, avatar_color: "", }), created: function() { this.connection = new WebSocket("wss://ohiapp0.herokuapp.com/pub"); let random_color = "#"; for (var i = 0; i < 6; i++) { random_color += "0123456789abcdef"[(16 * Math.random()) | 0]; } this.avatar_color = random_color; const vm = this; this.connection.onopen = function() { vm.sendMessageData(vm.avatar_color, MSG_START, vm.name); }; this.connection.onmessage = function(event) { const msg_json = JSON.parse(event.data); vm.messages.push({ ws_key: 1, avatar_color: msg_json.avatar_color, message: msg_json.message, name: msg_json.name, }); }; }, updated: function() { this.scrollToEnd(); }, methods: { send_onClick: function() { if (this.message == "") return; this.sendMessageData(this.avatar_color, this.message, this.name); this.message = ""; }, sendMessageData: function(avatar_color, message, name) { const msg_json = { avatar_color, message, name, }; this.connection.send(JSON.stringify(msg_json)); }, scrollToEnd() { this.$nextTick(() => { const chatLog = this.$refs.scrollTarget; if (!chatLog) return; chatLog.scrollTop = chatLog.scrollHeight; }); }, }, }) 成果物 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue × Firebase】非同期処理について

自身の備忘録として残します。 postItem()が押下されたら「この内容で投稿しますか?」とアラートを表示させてif文で以下のように分岐してます。 「OK」の場合・・・Firestoreのpostsというコレクションを参照して保存。その後、「投稿しました」というアラートを表示し、         投稿が画面上でも確認できるようにrouter.goを使ってリロード実行。   「キャンセル」の場合・・・何も実行せず「キャンセルしました」というアラートを表示。 if文の後は.catch()でエラーを吐き出しております。 問題点 router.goを使わずに手動でリロードをしたら画面上に新しく追加した投稿が表示されるが、 router.goを使うとリロードは実行されるが、Firestoreにすら保存されず、画面表示もしない。 修正前のコード postItem() { const currentUser = firebase.auth().currentUser; this.uid = currentUser.uid; const id = firebase .firestore() .collection("posts") .doc().id; this.$swal({ title: "内容確認", text: "この内容で投稿しますか?", icon: "info", buttons: true, dangerMode: true, }) .then((willDelete) => { if (willDelete) { firebase .firestore() .collection("posts") .add({ title: this.title, description: this.description, genre: this.genre, time: firebase.firestore.FieldValue.serverTimestamp(), //サーバ側で値設定 id: id, //dataにデータを作ってないので、thisは付けなくてOK! uid: this.$route.params.uid, }); this.$swal("投稿しました。", { icon: "success", }); this.$router.go({ path: `/board/${this.uid}`, force: true }); } else { this.$swal("キャンセルしました。"); } }) .catch(() => { this.$swal("投稿出来ませんでした。", { icon: "error", }); }); }, 原因 router.goが先に実行されてFiresutoreに保存がされていなかった為、 .then()内にアラートとrouter.goの処理をいれることで改善されました。 修正後のコード postItem() { const currentUser = firebase.auth().currentUser; this.uid = currentUser.uid; const id = firebase .firestore() .collection("posts") .doc().id; this.$swal({ title: "内容確認", text: "この内容で投稿しますか?", icon: "info", buttons: true, dangerMode: true }) .then(willDelete => { if (willDelete) { firebase .firestore() .collection("posts") .add({ title: this.title, description: this.description, genre: this.genre, time: firebase.firestore.FieldValue.serverTimestamp(), id: id, uid: this.$route.params.uid }) .then(() => { this.$swal("投稿しました。", { icon: "success" }); this.$router.go({ path: `/board/${this.uid}`, force: true }); }) .catch(() => { console.log("err"); }); } else { this.$swal("キャンセルしました。"); } }) .catch(() => { this.$swal("投稿出来ませんでした。", { icon: "error" }); }); }, おまけ 以下の場合、コンソール内の「a」と「b」どちらが先に実行されるか。 答えは、「b」 Firebaseは外部から通信しているので、通信に多少たりとも時間を要する。 非同期では、アラート等の表示は先にするが、通信が必要な部分は後回しで 例の場合、先に「b」が実行されてから「a」が実行される為 したがって、修正前では先にリロードが実行されFirestoreに保存されなかった。 test() { this.$swal({ title: "内容確認", text: "この内容で投稿しますか?", icon: "info", buttons: true, dangerMode: true }) .then(() => { if () { firebase .firestore() .collection("test") .add({ a:a b:b c:c }) .then(() => { /// console.log("a") }); .catch(() => {}); } else { this.$swal("キャンセルしました。"); } }) /// console.log("b") .catch(() => {}); }); },
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsの動的引数が機能しない

Vue.js 2.6.0以降で利用できる動的引数について。 公式ドキュメントのチュートリアルを進めていたらエラーが出ました。 https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments vue.js:634 [Vue warn]: Property or method "attributename" >is not defined on the instance but referenced during >render. Make sure that this property is reactive, either >in the data option, or for class-based components, by >initializing the property vue.js:634 [Vue warn]: Invalid value for dynamic >directive argument (expected string or null): undefined ソース <div id="app"> <a v-bind:[attributeName]="url">hoge</a> </div> <script> var vm = new Vue({ el: '#app', data: { url: 'http://hoge.com', attributeName: 'href' }, }) </script> 原因はv-bindに渡している変数名attributeName。 DOM内テンプレート(直接HTML書いたテンプレート)では、ブラウザが属性名を 小文字として認識するので、attributenameがない!ってなってるみたいです。 なのでDOM内テンプレートを使う場合、変数名を全部小文字attributenameにすればOKです。 webpackを利用している場合は事前にコンパイルされるので問題ないようです。(※試してません) <div id="app"> <a v-bind:[attributename]="url">hoge</a> </div> <script> var vm = new Vue({ el: '#app', data: { url: 'http://hoge.com', attributename: 'href' }, }) </script> ドキュメントをしっかり読むと、ちゃんと記載されてます。 https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments in-DOM テンプレート (すなわち、HTML ファイルに直接書かれるテンプレート) を使う場合、ブラウザが強制的に属>性名を小文字にするため、キー名を大文字にするのは避けるべきです: つまり、DOM内テンプレート使うときはちゃんとブラウザの属性名として有効な文字にしなさいよ。ってことですかね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vueのelectronでzoomとかのURLを開けるようにしたい

最近vueいじってねぇ なんかelectronでも使って作ってみるかー vueって最初の初期設定がめんどくさいイメージ 自分の備忘録なので文章とか気にせずいきましょ やっていきましょ vueのcreateから vue create test \\ターミナルで実行 Vue3を選択 なんか、前は色々とmanualyで選んでたんだけど、 めんどくさそうなので、vue3をとりあえず選択 ? Please pick a preset: Default (Vue 3 Preview) ([Vue 3] babel, eslint) electronで実行させたいので、electron-builderを追加 cd test/ vue add electron-builder Choose Electron Version ^12.0.0 さぁ、createが完了したのでとりあえず動かしてみましょう yarn electron:serve よし、とりあえずelectronアプリは立ち上がったね プルダウンリストを作っていくよ App.vueを変えて、そこに書いていこう それ以外は消しましょー App.vue <template> <div class="home"> <select v-model="selectedFruits"> <option disabled value="">果物一覧</option> <option v-for="fruit in optionFruits" v-bind:value="fruit.name" v-bind:key="fruit.id" > {{ fruit.name }} </option> </select> </div> </template> <script> // @ is an alias to /src export default { data() { return { selectedFruits: "", optionFruits: [ { id: 1, name: "バナナ" }, { id: 2, name: "りんご" }, { id: 3, name: "みかん" }, ], }; }, }; </script> とりあえず、プルダウンメニューだけ作れたね URLを入れてzoomを開けるようにしよう じゃあ、ここにURLを入れて、選択された名前のURLをボタンで開けるようにしていきましょう ついでにclassroomも開けるようにしておこうか <template> <div class="app"> <select v-model="subjectedSub"> <option disabled value="">教科一覧</option> <option v-for="subject in optionSubjects" v-bind:value="subject.id" v-bind:key="subject.id" > {{ subject.name }} </option> </select> <div> <button v-on:click="openZoom(subjectedSub)">zoom</button><br /> <button v-on:click="openClassroom(subjectedSub)">classroom</button ><br /><br /><br /> </div> </div> </template> <script> // @ is an alias to /src export default { data() { return { subjectedSub: "", optionSubjects: [ { id: 1, name: "教科名", zoom: "url", \\ここにURLを入れる classroom: "url", }, { id: 2, name: "教科名", zoom: "url", classroom: "url", }, { id: 3, name: "教科名", zoom: "url", classroom: "", }, ], nowSubject: "", }; }, methods: { openZoom(id) { window.location.href = this.optionSubjects[id - 1].zoom; }, openClassroom(id) { window.location.href = this.optionSubjects[id - 1].classroom; }, }, }; </script> とりあえず、こんな感じで3教科から選べるようにできたよ 参考にした記事 Vue.js で プルダウンメニューの作り方 (基礎編) Nuxt.jsで外部サイト(URL)へ遷移する方法 [ElectronでWebviewの簡易ブラウザをつくってみたメモ]https://qiita.com/mamosan/items/084039c3e6d703b7b45f background.jsでelectronアプリのサイズを変更しよう widthとheightで変更できるね const win = new BrowserWindow({ width: 300, height: 150, webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, }, show: true, "always-on-top": true, \\常に上に開いてくれるやつらしい }); buildしてみよう ついにbuildするよ アイコンとか、特に設定していないからtestでそのままだけど yarn electron:build そうすると同じフォルダの中にdist_electronができたね この中のtest.dmgを解凍すればアプリが開けるよ 結果 うんとりあえず、できてるねぇ ウィンドウちっさいかな笑 あ、でもclassroomを開くのが、electron上になっちゃうので、そこはまた改良します まぁ、まだ改良の余地はたくさんあるね でわ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

plunkerでvue その47

概要 plunkerでvueやってみた。 練習問題、やってみた。 練習問題 ipアドレスを表示せよ。 サンプルコード Vue.createApp({ el: '#app', data() { return { ip: 'ここにIPが表示されます' } }, methods: { getIp() { this.ip = 'IPを取得しています'; axios.get('https://httpbin.org/get').then((response) => { this.ip = response.data.origin; }).catch((reason) => { this.ip = 'IPの取得に失敗しました'; }); } } }).mount('#app') 成果物 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Netlify Formsで簡易的お問い合わせフォーム機能 in Veu.js

簡単にお問い合わせフォームをVueで実装したい!  (プログラミングはまだまだ勉強中の身でして、内容に誤りがあるかもしれません。コメントでご教授頂けるとありがたいです...) Vue.jsでWebサイトを作成してる中で、ポートフォリオ用などに、 サーバーレスで簡易的にフォーム機能を実装したいと思い、今回は、NetlifyのNetlifyFormsとういう機能を使用しました。 Netlifyとは? 静的ウェブサイトのためのホスティングサービスとサーバーレスのバックエンドサービスを提供。 GitHubとの連携で、自動デプロイも行ってくれる便利なWebサービスです。 前提 今回は、送信ボタンを押しても、バリデーションチェックのみ走り、実際にはどこにもデータが送信されない状態のcontact-formを使用しています。 Vue.jsでサイト(Form)を作成済み contact-formにバリデーション機能を実装済み Vue Routerはインストール済み(Vue CLI使用) Netlifyでデプロイ設定済み  Vue.js+Netlifyで自動デプロイの方法 手順 1, HTMLのフォームを作成 (NetlifyFormsはHTMLのフォームを認識し、作動する為) public/index.html <body ontouchstart=""> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript>    //追記 <form name="contact" data-netlify="true" data-netlify-honeypot="bot-field" hidden> <input type="text" name="name" /> <input type="email" name="email" /> <textarea name="message"></textarea> </form>   //ここまで <div id="app"></div> <!-- built files will be auto injected --> </body> 2,Vue.jsのフォームに追加設定 ! formの属性にname, method, data-netlify, data-netlify-honeypot, そして hidden input field を設定 data-netlify-honeypotとhidden inputはスパム防止の設定です。 hidden inputタグのnameは必ず”form-name”にして、そのvalue = formタグのname (ここでは”contact”)になるようにします。 @submit.preventはデフォルトの更新機能をなくします。参考 Contact.vue <form class="contact-form" @submit.prevent="handleSubmit" novalidate="true" name="contact" method="POST" data-netlify="true" data-netlify-honeypot="bot-field" > <input type="hidden" name="form-name" value="contact" /> <div class="errors" v-if="errors.length"> <ul> <li v-for="error in errors" v-bind:key="error">{{ error }}</li> </ul> </div> <div class="flex-row"> <input type="text" name="name" placeholder="お名前" v-model="form.name" /> </div> <div class="flex-row"> <input type="email" name="email" placeholder="メールアドレス" v-model="form.email" /> </div> <div class="flex-row"> <textarea type="text" name="message" placeholder="本文" v-model="form.message" ></textarea> </div> <button class="btn">送信</button> </form> 3, ローカルdataの設定 後にformをスプレッド構文で展開した配列をフォームの内容として渡す。 data() { return { errors: [], form: { name: null, email: null, message: null, }, }; }, 4, メソッドの設定 javascriptオブジェクトであるformをencod( )で、HTTPリクエストに沿った形式に変換してからPOSTリクエストで送信 encode(data) { return Object.keys(data) .map( (key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}` ) .join("&"); }, handleSubmit() { this.checkForm(); //別にバリデーション関数を用意 fetch("/", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: this.encode({ "form-name": "contact", ...this.form, //name, email, messageが入っている }), }) .then(() => { this.$router.push("thanks");//成功時 }) .catch(() => { this.$router.push("404");//失敗時 }); }, 5, Routerと成功時、失敗時のそれぞれの遷移先のページを用意 コードは省略します。 6, Netlifyのマイページより、送信されたお問い合わせフォームを確認 サイト内の設定のForm notificationsから管理者宛の通知(emailやslackなど)を設定できます。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで親子間の双方向バインディングをいろいろ試してみた

これはなに Vue.jsを使う上で、親で定義した要素を子コンポーネントに渡し、フォームに反映させるのはよくあること。 で、その要素を書き換える方法がいろいろあるなと思ったので少しまとめてみました。 badな例もあります。 環境 Vue3 typescript 4.3.2 Composition APIを使用します。 検証するソース 親コンポーネント <template> <div class="child-components"> <!-- obj1.name 1階層だとどうなる? --> <obj1-vue :obj1="obj1" /> <br />---------------------------------- <!-- obj2.name.firstName 2階層だとどうなる? --> <obj2-vue :obj2="obj2" /> <br />---------------------------------- <!-- obj3.gender.value リアクティブな要素を持つオブジェクト --> <obj3-vue :obj3="obj3" /> <br />---------------------------------- <!-- obj4.gender.value emitで書き換えよう --> <obj4-vue :obj4="obj4" @update:text="updateText" /> <br />---------------------------------- <!-- obj5.description.value Vue3からのv-model --> <obj5-vue v-model:description="obj5.description.value" /> </div> <div class="parent-content"> <h3>親側の値</h3> <p>{{ obj1.name }}</p> <p>{{ obj2.name.firstName }}</p> <p>{{ obj3.gender.value }}</p> <p>{{ obj4.text.value }}</p> <p>{{ obj5.description.value }}</p> </div> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; import Obj1Vue from "./components/Obj1.vue"; import Obj2Vue from "./components/Obj2.vue"; import Obj3Vue from "./components/Obj3.vue"; import Obj4Vue from "./components/Obj4.vue"; import Obj5Vue from "./components/Obj5.vue"; export default defineComponent({ name: "App", components: { Obj1Vue, Obj2Vue, Obj3Vue, Obj4Vue, Obj5Vue, }, setup() { const obj1 = { name: "obj1.nameの初期値" }; const obj2 = { name: { firstName: "obj2.name.firstNameの初期値", }, }; const obj3 = { gender: ref("obj3.gender.valueの初期値"), }; const obj4 = { text: ref("obj4.textの初期値"), }; const updateText = (val: string) => { obj4.text.value = val; }; const obj5 = { description: ref("obj5.descriptionの初期値"), }; return { obj1, obj2, obj3, obj4, updateText, obj5, }; }, }); </script> といった感じで5つのオブジェクトの中身を書き換えてみます。 画面にするとこんな感じ。 左が子供、右が親です。 子供側でボタン押したり、フォーム入力するなどのアクションをとると親側がどう変化するかをそれぞれ見ていきます。 第一章 1階層のオブジェクト 子コンポーネント <template> <div class="obj1"> <button @click="submit">空objectを代入</button> <button @click="submit1">文字列を代入</button> <input v-model="obj1.name" @input="input" /> </div> </template> <script lang="ts"> import { defineComponent, PropType } from "vue"; type Obj1 = { name: String; }; export default defineComponent({ name: "HelloWorld", props: { obj1: { type: Object as PropType<Obj1>, }, }, setup(props) { const submit = () => { props.obj1 = {}; console.log(props.obj1); }; const submit1 = () => { props.obj1.name = "中身変更!"; console.log(props.obj1); }; const input = () => { console.log(props.obj1); }; return { submit, submit1, input, }; }, }); </script> それぞれボタンやフォーム入力した際にはコンソールで中身を出すようにしました。 結果は オブジェクトに直接オブジェクトを代入 Set operation on key "obj1" failed: target is readonly. と怒られました。propsの値を直接書き換えるのはルール違反です。中身も変わりませんでした。 オブジェクトの中身に代入 書き換わった文字列がコンソールに表示されました! またオブジェクト自体ではなく中身の変更の場合エラーはでません。 しかしながら親側の文字列は初期値のままです。 そもそもobj1.nameはリアクティブでもなく、双方向のバインディングもなされていないので親は微動だにしません。 ですがエラーは出ずにに子供側の中身は変わりました。危ないですね〜。 第二章 2階層のオブジェクト 1階層とやってることは同じで、対象のオブジェクトを1階層深くしてみました。 const obj2 = { name: { firstName: "obj2.name.firstNameの初期値", }, }; 結果は1階層となにも変わらず。(そりゃそうか) 第三章 リアクティブな要素を持つオブジェクト 第一章ではそもそもリアクティブなオブジェクトを渡さなかったので、今度はリアクティブにしてみます。 const obj3 = { gender: ref("obj3.gender.valueの初期値"), }; 子コンポーネント <template> <div class="obj3"> <button @click="submit">文字列を代入</button> <input v-model="obj3.gender.value" @input="input" /> </div> </template> <script lang="ts"> import { defineComponent, PropType, Ref } from "vue"; type Obj3 = { gender: Ref<string>; }; export default defineComponent({ name: "Obj3", props: { obj3: { type: Object as PropType<Obj3>, }, }, setup(props) { const submit = () => { props.obj3.gender.value = "中身変更!"; console.log(props.obj3); }; const input = () => { console.log(props.obj3); }; return { submit, input, }; }, }); </script> さてどうなるのか 親も書き換わった! エラーなし、そして親側の中身も書き換わりました! しかし正直「え、これでいいの?」という感じ。 なんとなくルール違反をしている気がするのです。 子コンポーネントでpropsの中身をv-modelするのはいささか不安が残ります。 第四章 computedを使う 第三章の処理に不安が残るのでcomputedでgetter/setterを用意し、それをv-modelします。 set時はemitを使い親コンポーネントで中身を書き換えます。 親コンポーネント const obj4 = { text: ref("obj4.textの初期値"), }; const updateText = (val: string) => { obj4.text.value = val; }; 子コンポーネント(Obj4.vue) <template> <div class="obj4"> <button @click="submit">文字列を代入</button> <input v-model="text" @input="input" /> </div> </template> <script lang="ts"> import { computed, defineComponent, PropType, Ref } from "vue"; type Obj4 = { text: Ref<string>; }; export default defineComponent({ name: "Obj4", props: { obj4: { type: Object as PropType<Obj4>, }, }, setup(props, { emit }) { const text = computed({ get: () => props.obj4.text.value, set: (val: string) => emit("update:text", val), }); const submit = () => { text.value = "中身変更!"; console.log(props.obj4); }; const input = () => { console.log(props.obj4); }; return { submit, input, text, }; }, }); </script> 問題なし 挙動的には問題がなく、子供で直接書き換わるような記述もないので安心♨︎ 第五章 Vue3ではv-modelが使える そもそもVue2までは.syncを利用して親子間の双方向バインディングを実現することができました。 しかしVue3で.syncが廃止され、そのかわりにv-modelが使えるようになりました。 その部分だけ抜粋した親コンポーネント <template> <div class="child-components"> <!-- obj5.description.value Vue3からのv-model --> <obj5-vue v-model:description="obj5.description.value" /> </div> </template> <script lang="ts"> export default defineComponent({ name: "App", components: { Obj5Vue, }, setup() { const obj5 = { description: ref("obj5.descriptionの初期値"), }; return { obj5, }; }, }); </script> 子コンポーネント(Obj5.vue) <template> <div class="obj5"> <button @click="submit">文字列を代入</button> <input :value="description" @input="$emit('update:description', $event.target.value)" /> </div> </template> <script lang="ts"> import { defineComponent } from "vue"; export default defineComponent({ name: "Obj5", props: { description: { type: String, }, }, setup(props, { emit }) { const submit = () => { emit("update:description", "中身変更!"); console.log(props.description); }; return { submit, }; }, }); </script> 親側で本来定義する@update:description="obj5.description.value = $event"の役割をv-modelが担ってくれているようです。 総じて Vue触りたての時とかはこの辺ハマりポイントだったりしますし、 Vue3、Composition APIが出てきて書き方のバリエーションが増えたように思います。 v-modelもいいけど、暗黙的な感じもあるのでcomputed使う方が好きだなぁと思う今日この頃です。 慣れですかね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む