- 投稿日:2020-10-18T22:24:42+09:00
アラートダイアログのOKが押されたタイミングで処理を実行する
やりたいこと
このようなアラートダイアログのOKを押されたタイミングで処理を実行したいとします。
確認ダイアログを使って、以下のようにOKボタンが押された場合とキャンセルボタンが押された場合の処理を書くのが一般的です。
if (confirm('確認ダイアログの例')) { console.log('OKボタンが押されました。') } else { console.log('キャンセルボタンが押されました。') }キャンセルボタンの押せない、OKボタンのみの確認ダイアログを作りたい
ただ、時には諸々の事情でキャンセルボタンを押してもらいたくない場合もあります。
ということで、キャンセルボタンの押せない、OKボタンのみの確認ダイアログを作っていきます。
alert()
の仕様確認ダイアログでのOKボタンとキャンセルボタンを押された場合の処理の書き分けでは、JavaScriptの
confirm()
が
- OKボタンをクリックした場合の戻り値:true
- キャンセルボタンをクリックした場合の戻り値:false
であることを使って、if文の条件式の中で
confirm()
を使うことで実現していました。一方、
alert()
では、アラートダイアログのOKボタンを押された際の戻り値はundefinedです。この特性を使って、
if (!alert('OKを押してください。')) { // OKが押された際に実行する処理 }上記のような条件式を書くことによって、OKが押された際にundefinedの否定、つまりtrueとなり、処理が実行されます。
以下、サンプルです。
サンプル
See the Pen Event After Alert by Kamiyama (@MtDeity) on CodePen.
- 投稿日:2020-10-18T20:30:48+09:00
Nuxt.js × Rails APIモードでアプリケーションを作る
やりたいこと
Nuxt.jsでフロントを、RailsでAPIを作る
作成したAPIに対してPostmanで投稿をする
※ 次の記事でNuxt.js側からRailsのAPIを叩いてDBにデータを保存する方法を掲載します開発環境
ruby 2.6.5
Rails 6.0.3.4
node v14.7.0
yarn 1.22.4フロントをNuxt.jsで作る
- post-appというディレクトリの中に
app
という名前でフロント用のアプリケーションを作る- Axiosはプロジェクト作成時にインストールするように選択する
// post-appというディレクトリを作る $ mkdir post-app $ cd post-app // nuxtでのアプリケーションを作る $ npx create-nuxt-app app // 各種設定を選択する ? Project name: (app) └ そのままEnterボタン押す ? Programming language: (Use arrow keys) ❯ JavaScript TypeScript └ JavaScriptを選択 ? Package manager: ❯ Yarn Npm └ Yarnを選択 ? UI framework: (Use arrow keys) ❯ None Ant Design Vue Bootstrap Vue Buefy Bulma Chakra UI Element Framevuerk iView Tachyons Tailwind CSS Vuesax Vuetify.js └ Noneを選択 ? Nuxt.js modules: ❯◉ Axios ◯ Progressive Web App (PWA) ◯ Content └ Axiosを選択(スペースキーを押す) ? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection) ❯◯ ESLint ◯ Prettier ◯ Lint staged files ◯ StyleLint └ そのままEnterボタン押す ? Testing framework: (Use arrow keys) ❯ None Jest AVA WebdriverIO └ そのままEnterボタン押す ? Rendering mode: Universal (SSR / SSG) ❯ Single Page App └ Single Page Appを選択 ? Deployment target: (Use arrow keys) ❯ Server (Node.js hosting) Static (Static/JAMStack hosting) └ Serverを選択 ? Development tools: ❯◉ jsconfig.json (Recommended for VS Code if you're not using typescript) ◯ Semantic Pull Requests └ jsconfig.jsonを選択(スペースキーを押す)nuxtのポート番号を8000番に変更する
公式より引用▼
app/nuxt.config.jsexport default { server: { port: 8000, // デフォルト: 3000 host: '0.0.0.0' // デフォルト: localhost } // その他の設定 }実際の記述▼
app/nuxt.config.jsserver: { port: 8000, },ローカルホストを立ち上げる
$ cd app $ yarn dev // 下記が表示されたらhttp://localhost:8000/でアクセス可能 │ Nuxt.js @ v2.14.7 │ │ │ │ ▸ Environment: development │ │ ▸ Rendering: client-side │ │ ▸ Target: server │ │ │ │ Listening: http://localhost:8000/RailsでAPIサーバーを作る
// APIモード、DBはMySQLでrailsアプリケーションを作る $ rails new api --api -d mysql // DBを作る $ rails db:create Created database 'api_development' Created database 'api_test'ModelとControllerを作る
// Post Modelを作る $ rails g model Post title:string body:text // migrateする $ rails db:migrate // apiディレクトリの中のv1ディレクトリの中にpostsコントローラーを作る // test用のディレクトリの自動生成をスキップする $ rails g controller api::v1::posts --skip-test-frameworkルーティングの設定
/api/v1/posts
というルーティングを作るapi/config/routes.rbRails.application.routes.draw do namespace :api do namespace :v1 do resources :posts end end endPostsControllerを設定する
api/app/controllers/api/v1/posts_controller.rbclass Api::V1::PostsController < ApplicationController before_action :set_post, only: [:show, :update, :destroy] def index posts = Post.order(created_at: :desc) render json: { status: 'SUCCESS', message: 'Loaded posts', data: posts } end def show render json: { status: 'SUCCESS', message: 'Loaded the post', data: @post } end def create post = Post.new(post_params) if post.save render json: { status: 'SUCCESS', data: post } else render json: { status: 'ERROR', data: post.errors } end end def destroy @post.destroy render json: { status: 'SUCCESS', message: 'Deleted the post', data: @post } end def update if @post.update(post_params) render json: { status: 'SUCCESS', message: 'Updated the post', data: @post } else render json: { status: 'SUCCESS', message: 'Not updated', data: @post.errors } end end private def set_post @post = Post.find(params[:id]) end def post_params params.permit(:title, :body) end endPostmanを使って投稿してみる
Postman
使い方参考記事
- 下記のようになっていたらOK
Railsのコンソールで投稿できている確認する
// コンソールを立ち上げる $ rails c // 投稿を確認する irb(main):001:0> Post.first上記でPostmanと同じ内容が表示されていればOKです
※別の記事でNuxt.js側からデータを投稿する処理を実装します
- 投稿日:2020-10-18T17:28:27+09:00
Vue 3 Composition API を使ってみよう
Composition APIとは、
Composition APIとは、2020年09月18に正式リリースされた、Vue 3に追加された目玉機能です。
Composition APIはコンポーネントを構築するための新しい手法です。
Vueが抱えていた以下の課題を解決するためのものです。
- TypeScriptのサポート
- ロジックの再利用の難しさ
- アプリケーションが巨大になると、コードの把握が難しくなる
Vue CLIでVue 3を導入する
早速、Vue CLIでVue 3を使っていきましょう。
まずは、最新のVue CLIをインストールします。yarn global add @vue/cli@next # OR npm install -g @vue/cli@nextvue -V @vue/cli 4.5.4
次に、いつも通りプロジェクトを作成します。
Vue 3が選べるようになっています。vue create vue3-project Vue CLI v4.5.4 ? Please pick a preset: Default ([Vue 2] babel, eslint) ❯ Default (Vue 3 Preview) ([Vue 3] babel, eslint) Manually select features今回は、Manually select featuresから選択します。
? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, TS, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Pick a linter / formatter config: Standard ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? Noプロジェクトの作成が完了したら、早速起動してみましょう。
cd vue3-project npm run serve
TODOアプリの作成を例にして、Composition APIを使っていきましょう。
コンポーネントの例
まずは、Composition APIで作成されたコンポーネントの全体像を見ていきましょう。
次のように、以前の元とは大きく変わっていることがわかります。// MyTodo.vue <template> <todo-list v-for="todo in sortTodo" :todo="todo" :key="todo.id" @toggle="toggleTodo" @remove="removeTodo" /> <add-todo @add="addTodo" /> </template> <script lang="ts"> import { computed, defineComponent, reactive, watchEffect, onMounted } from 'vue' import TodoList from '@/components/TodoList.vue' import AddTodo from '@/components/AddTodo.vue' import { fetchTodo } from '@/api' import { Todo } from '@/types/todo' import { v4 as uuid } from 'uuid' interface State { todos: Todo[]; } export default defineComponent({ components: { TodoList, AddTodo }, setup () { const state = reactive<State>({ todos: [] }) onMounted(async () => { state.todos = await fetchTodo() }) const sortTodo = computed(() => state.todos.sort((a, b) => { return b.createdAt.getTime() - a.createdAt.getTime() })) const addTodo = (title: string) => { state.todos = [...state.todos, { id: uuid(), title, done: false, createdAt: new Date() }] } const removeTodo = (id: string) => { state.todos = state.todos.filter(todo => todo.id !== id) } const toggleTodo = (id: string) => { const todo = state.todos.find(todo => todo.id === id) if (!todo) return todo.done = !todo.done } watchEffect(() => console.log(state.todos)) return { sortTodo, addTodo, removeTodo, toggleTodo } } }) </script>// TodoList.vue <template> <div> <span>{{ todo.title }}</span> <input type="checkbox" value="todo.done" @change="toggle" /> </div> <div> {{ date }} </div> <div> <button @click="remove">削除</button> </div> </template> <script lang="ts"> import { computed, defineComponent, PropType } from 'vue' import { Todo } from '@/types/todo' export default defineComponent({ props: { todo: { type: Object as PropType<Todo> } }, emits: ['toggle', 'remove'], setup (props, context) { const date = computed(() => { if (!props.todo) return const { createdAt } = props.todo return `${createdAt.getFullYear()}/${createdAt.getMonth() + 1}/${createdAt.getDate()}` }) const toggle = () => { context.emit('toggle', props.todo!.id) } const remove = () => { context.emit('remove', props.todo!.id) } return { date, toggle, remove } } }) </script>// addTodo <template> <input type="text" v-model="state.inputValue" /> <button @click="onClick" :disabled="state.hasError">追加</button> <p v-if="state.hasError" class="error">タイトルが長すぎ!</p> </template> <script lang="ts"> import { defineComponent, reactive, watchEffect } from 'vue' interface State { inputValue: string; hasError: boolean; } export default defineComponent({ emits: ['add'], setup (_, context) { const state = reactive<State>({ inputValue: '', hasError: false }) const onClick = () => { context.emit('add', state.inputValue) state.inputValue = '' } watchEffect(() => { if (state.inputValue.length > 10) { state.hasError = true } else { state.hasError = false } }) return { state, onClick } } }) </script> <style scoped> .error { color: red; } </style>以前のような構造(これをOptions APIと呼びます)と異なり、date・methods・computed・ライフサイクルメソッドなどの区別がなくなり、全てがsetupメソッドの中に記述されています。一方で、componentsやpropsの記述は以前と変わりありません。
全てがsetupメソッド内に記述された結果、他のプロパティにアクセスする際に
this
が不要になりました。以前までのVueではこのthis
の制約によりアロー関数で記述することが敬遠されていたのですが、Composition APIではアロー関数により記述が可能になりました。setupメソッド内のデータは、returnされたものだけがtemplate内で使用できるようになります。
そのため、例えばMyTodo.vue
のsetupメソッドではstate
が宣言されていますがreturnされていないためこれを使用することはできません。state
の値は直接使用せずに、conputed
を通して使用するという意図を伝えることができます。それでは、もう少し具体的にここのプロパティを見ていきましょう。
コンポーネントの宣言
Vue 3では、今までの
Vue.extend
の宣言に変わり、defineComponent
が使われます。これにより型推論が効くようになります。JavaScriptで記述する場合には必要ありません。<script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({}) </script>リアクティブなデータ reactive または ref
reactive
またはref
は以前のdata
に相当するものです。どちらもジェネリクスで型定義を渡すことができます。reactive
個人的に、
reactive
のほうが以前のdataに近い印象を受けます。reactiveは1つのオブジェクトとしてデータを定義します。interface State { inputValue: string; hasError: boolean; } const state = reactive<State>({ inputValue: '', hasError: false })
reactive
の値にはオブジェクト形式でアクセスします。state.inputValue state.hasError分割代入すると、リアクティブにならない
reactive
を使用する注意点として、分割代入した値に対してはリアクティブ性が失われるという点があります。
AddTodo
コンポーネントを例に試してみましょう。<template> <input type="text" v-model="inputValue" /> <button @click="onClick" :disabled="hasError">追加</button> <p v-if="hasError" class="error">タイトルが長すぎ!</p> </template> <script lang="ts"> import { defineComponent, reactive, watchEffect } from 'vue' interface State { inputValue: string; hasError: boolean; } export default defineComponent({ emits: ['add'], setup (_, context) { // 分割代入でここのプロパティを受け取るように変更 let { inputValue, hasError } = reactive<State>({ inputValue: '', hasError: false }) const onClick = () => { context.emit('add', inputValue) inputValue = '' } watchEffect(() => { if (inputValue.length > 10) { hasError = true } else { hasError = false } }) return { inputValue, hasError, onClick } } }) </script> <style scoped> .error { color: red; } </style>リアクティブ性が失われていることがわかります。
このような状況に対する解決策として、
toRefs
関数が用意されています。toRefs
はリアクティブオブジェクトをプレーンオブジェクトに変換します。結果のオブジェクトの各プロパティは、元のオブジェクトの対応するプロパティの参照です。<template> <input type="text" v-model="inputValue" /> <button @click="onClick" :disabled="hasError">追加</button> <p v-if="hasError" class="error">タイトルが長すぎ!</p> </template> <script lang="ts"> import { defineComponent, reactive, toRefs, watchEffect } from 'vue' interface State { inputValue: string; hasError: boolean; } export default defineComponent({ emits: ['add'], setup (_, context) { const { inputValue, hasError } = toRefs(reactive<State>({ inputValue: '', hasError: false })) const onClick = () => { context.emit('add', inputValue) inputValue.value = '' } watchEffect(() => { if (inputValue.value.length > 10) { hasError.value = true } else { hasError.value = false } }) return { inputValue, hasError, onClick } } }) </script> <style scoped> .error { color: red; } </style>
toRefs
を適用すると、後述するref
で宣言されたものと同等の状態になります。そのため、template以外の場所からアクセスする際には、inputValue.value
のように.valueの値に対してアクセスします。ref
リアクティブなデータを宣言するもう一つの方法は
ref
を使用することです。
ref
はそれぞれの変数として宣言されます。let inputValue = ref('') let hasError = ref(false)
ref
で宣言された値にtemplate以外の場所からアクセスする場合、.valueからアクセスします。inputValue.value hasError.valuereactiveとrefどっちを使えばいい?
このように、Composition APIではリアクティブなデータの宣言に
reactive
とref
どちらも使用することができます。しかし、どちらを使用するかのベストプラクティスはまだ存在していないようです。Composition APIを効率的に理解するためには、どちらの方法を理解しておくべきだと述べられています。
メソッド
以前の
methods
に相当するものは、通常のJavaScriptの関数の宣言にになります。const addTodo = (title: string) => { state.todos = [...state.todos, { id: uuid(), title, done: false, createdAt: new Date() }] } const removeTodo = (id: string) => { state.todos = state.todos.filter(todo => todo.id !== id) } const toggleTodo = (id: string) => { const todo = state.todos.find(todo => todo.id === id) if (!todo) return todo.done = !todo.done }メソッドの宣言も同様に、returnしていないものはtemplate内で使用できません。
computed
computedは関数をcomputedで包むことで実装できます。
const sortTodo = computed(() => state.todos.sort((a, b) => { return b.createdAt.getTime() - a.createdAt.getTime() }))何度も言うように、computedの値もreturnする必要があります。
データの監視 watch watchEffect
watchもdataのように
watch
とwatchEffect
2つの方法が使えるようになりました。
以前のような使い方と近いのはwatch
です。watch
watch
は第一に引数に監視する対象のリアクティブな値を指定します。リアクティブな値とは、ref
、reactive
、computed
で宣言された値を指します。第二引数に実行するメソッドを渡します。
watch(state.todos, (oldTodo: Todo, newTodo: Todo) => { console.log(oldTodo, newTodo) })監視対象が複数ある場合、配列で指定します。
watch([state.inputValue, state.hasError], ([oldInputValue, newInputValue], [oldHasError, newHasError]) => { })watchEffect
watchEffect
は、監視対象を指定しません。computedのように、関数内の値が変更されたときに実行されます。watchEffect(() => console.log(state.todos))基本的に、
watchEffect
よりもwatch
のほうが機能が優れているようで、例えばwatchEffect
と比べてwatch
は次のような機能を提供します。
- 副作用の遅延実行。
- 監視対象の値を渡すので、意図が明確。
- 監視状態の以前の値と現在の値の両方にアクセスできる。
ライフサイクルメソッド
Composition APIのライフサイクルメソッドは
on
というプレフィックスがつけられました。onMounted(async () => { state.todos = await fetchTodo() })Options APIとの対応は以下の通りです。
Options API Conposition API beforeCreate use setup() created use setup() beforeMount onBeforeMount mounted onMounted beforeUpdate onBeforeUpdate updated onUpdated beforeDestroy onBeforeUnmount destroyed onUnmounted activated onActivated deactivated onDeactivated errorCaptured onErrorCaptured
beforeCrate
、crate
に相当するフックはなくなり、setup()で記述するようになりました。
さらに、以下のライフサイクルフックが追加されました。
- onRenderTracked
- onRenderTriggered
props
propsををsetupメソッド内で使用するために、setupメソッドは引数を受け取ります。第一引数はpropsそのものです。
export default defineComponent({ props: { todo: { type: Object as PropType<Todo> } }, setup (props, context) { const date = computed(() => { if (!props.todo) return const { createdAt } = props.todo return `${createdAt.getFullYear()}/${createdAt.getMonth() + 1}/${createdAt.getDate()}` }) return { date } })propsプロパティは今まで通りです。
propsプロパティに型定義がされている場合、そのとおりに推論されます。注意点として、以下のようにpropsを分割して受け取ると、リアクティブ性が失われてしまいます。
setup({ todo }, context) {}emit
setupメソッドの第二引数には、contextオブジェクトを受け取ります。contextオブジェクトからはOptions APIで
this
からアクセスできた一部のプロパティを提供します。context.attr context.slots context.emitどのプロパティも、先頭の
$
が外れていることに注意してください。
propsは使用せずに、contextオブジェクトだけ使用したいときには、第一引数を_
にして受け取らないようにします。setup(_, context) {}contextオブジェクトの中に
emit
が含まれているので、以前のように使用することができます。export default defineComponent({ emits: ['add'], setup (_, context) { const state = reactive<State>({ inputValue: '', hasError: false }) const onClick = () => { context.emit('add', state.inputValue) state.inputValue = '' } return { state, onClick } } })さらに注目すべき変更点として、Vue 3からは
emits
オブジェクトとしてそのコンポーネントがemitする可能性があるイベントを配列として宣言するようになりました。必須のオプションではないですが、コードを自己文章化することができ、推論もされるようになります。
モジュールに分割する
ここまでただ単にComposition APIに書き換えてみただけなので、結局コンポーネント内で宣言されており、また
state
の値に依存しているのであまり恩恵を感じないように思えます。ここからは、Composition APIの真骨頂であるコードの分割にコマを進めていきましょう。
Composition APIではdate、computedなので区切りがなくなったことで関心ことの分離が可能になりました。分割した関数は、
src/composables
配下に配置していきます。
それでは、まずはsortTodo
を切り出してみましょう。実装は次のようになります。
// src/composable/use-sort-todo.ts import { computed, isRef, Ref } from 'vue' import { Todo } from '@/types/todo' export default (todos: Ref<Todo[]>) => { const sortTodo = computed(() => todos.value.sort((a, b) => { return b.createdAt.getTime() - a.createdAt.getTime() })) return { sortTodo } }元々
state.todo
でアクセスしていた箇所を引数で受け取るようにしました。引数の型Refとしています。ロジック部分に変わりはありません。使用側では以下のように使います。
import { toRefs, defineComponent, reactive, watchEffect, onMounted } from 'vue' import useSortTodo from '@/composables/use-sort-todo' interface State { todos: Todo[]; } export default defineComponent({ setup () { const state = reactive<State>({ todos: [] }) const { todos } = toRefs(state) const { sortTodo } = useSortTodo(todos) // 中略 return { sortTodo, addTodo, removeTodo, toggleTodo } } })これで、sortTodoはVueオブジェクトに依存することなく、再利用可能な関数として取り出すことができました。
その他のメソッドも切り出してみると、だいぶコンポーネントがスッキリしました。
<template> <todo-list v-for="todo in sortTodo" :todo="todo" :key="todo.id" @toggle="toggleTodo" @remove="removeTodo" /> <add-todo @add="addTodo" /> </template> <script lang="ts"> import { defineComponent, watchEffect } from 'vue' import TodoList from '@/components/TodoList.vue' import AddTodo from '@/components/AddTodo.vue' import useTodos from '@/composables/use-todos' import useSortTodo from '@/composables/use-sort-todo' import useActionTodo from '@/composables/use-action-todo' export default defineComponent({ components: { TodoList, AddTodo }, setup () { const { todos } = useTodos() const { sortTodo } = useSortTodo(todos) const { addTodo, removeTodo, toggleTodo } = useActionTodo(todos) watchEffect(() => console.log(todos.value)) return { sortTodo, addTodo, removeTodo, toggleTodo } } }) </script>変更はこちらのレポジトリから参照できます。
https://github.com/azukiazusa1/vue3-project
参考
Vue Composition API
Vue3への移行は可能?Vue2のコードをComposition APIで書き換えてみた
Vue.js 3.0で搭載される Composition APIをリリースに先駆けて試してみた
Vue 3 に向けて Composition API を導入した話
Vue 2.xのOptions APIからVue 3.0のComposition APIへの移行で知っておくと便利なTips
Vue3リリース直前!導入されるcomposition APIを使ってみよう
- 投稿日:2020-10-18T16:35:21+09:00
VueとWP REST API でサムネイル付き一覧を作成&取得時に発生する再現性のないバグを回避する方法
WordpressとVueを組み合わせて一覧を作成していた際のコードサンプルと、
再現性のないバグ?っぽい挙動があり、情報がどこにもなかったためまとめます。バグの解決をしたいだけの人用まとめ
結論:投稿消して、新規投稿、かつサムネイルも新規アップロードしたら直るよ!
背景
WP REST APIを利用し、Wordpress外でVueを使ってお知らせ一覧を表示するということを行っていました。
サムネイル付きのデータが欲しい場合、取得urlの末尾に?embedをつけると、
戻り値の中に_embeddedが追加され、その中にあるsourceurlから取得できます。
以下戻り値の取得例。//戻り値がdataに格納されている上で thumbUrl = data[0]["_embedded"]["wp:featuredmedia"][0]["media_details"]["sizes"]["thumbnail"]["source_url"];["thumbnail"]を["lage"]などに変えればサイズを変えて取得も可能。
WP REST APIとVueでのサムネイル付き一覧作成するサンプルソースを掲載します。
VueとWP REST API でサムネイル付き一覧を作成するサンプル
<template> <div> <div v-for="item in slicedItems" :key="item.id"> <a :href="item.link"> <div class="img_box"> <span v-if="item.thumbnail"><img :src="item.thumbnail" alt="item.title.rendered" /></span> <span v-else><img :src="noImage" alt="no image" /></span> </div> <div class="txt_box"> <p class="date">{{ dateConv(item.date) }}</p> <h3>{{ item.title.rendered }}</h3> <div>{{ strReplace(item.excerpt.rendered) }}</div> </div> </a> </div> </div> </template> <script> export default { data() { return { items: [], }; }, props: { //urlに渡している内容はこれ。投稿をサムネ付きで4つ取得している。 //https://mydemosite.com/info/wp-json/wp/v2/posts?_embed&per_page=4 url: String, count: { type:String, default:"4", }, noImage: { type:String, default:"/assets/img/common/no_image.png", }, }, methods: { getPosts() { this.isLoading = true; var self = this; axios .get(this.url) // Success .then((response) => { if (response.status != 200) { //console.log(response.statusText); exit; } self.items = response.data; //サムネイルの取得とデータ格納 for(let i = 0; i < self.items.length; i++) { if(response.data[i]["_embedded"]["wp:featuredmedia"][0]["media_details"]["sizes"]["thumbnail"]["source_url"]){ self.items[i].thumbnail = response.data[i]["_embedded"]["wp:featuredmedia"][0]["media_details"]["sizes"]["thumbnail"]["source_url"]; } } }) // Error .catch((error) => { console.log(error); this.isLoading = false; }); }, dateConv(data){ var newdate = new Date(data); var year = newdate.getFullYear(); var month = newdate.getMonth() + 1; var day = newdate.getDate(); return year + '年' + month + '月' + day + '日'; }, strReplace(data){//…[…] var replaced = data.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,''); replaced = replaced.replace(/ \[…\]/g,'…'); return replaced; }, }, created() { this.getPosts(); }, }; </script>だいたいこんな感じで取得していました。
WordPressでいろいろいじっていたら突然サムネイルが取れなくなる
取得できたのを確認したあと、Wordpressで投稿のカテゴリを増やす、
また特定の記事でサムネイル画像を変更するなどしていました。その後、一覧を取得していたページに戻ったところ、
触った記事だけでなく、全記事のサムネイル画像が表示されなくなっていました。そこでconsoleにログを取得して確認していたところ、
wp:featuredmediaから先が正しく取れていないことがわかりました。
その中のデータを確認すると以下のようなエラーが返ってきています。code: "rest_forbidden" data:status: 401 message:"その操作を実行する権限がありません。"
そもそも権限にからむ操作をした記憶もなく、ソース自体変わっていなかったため原因が不明でした。
解決方法
国内外問わずググってもこれという情報がでず・・・
WP APIのissueで同様の質問を発見。
解答を見ていると、
「これ多分バグっぽくて、投稿消して再度投稿したら行けるよ」って書いてました。なので、言われた通り、
投稿消して、新規投稿、かつサムネイルも新規アップロード(←重要)したら直りました。日本語ファイルが原因だったのか、特殊文字が原因だったのか(サンプル①.jpgみたいな名前)、
再度試したけど再現せず、サンプル①.jpgみたいな名前の画像でもしっかりと表示され、
原因自体は不明でした。とにかく、このような現象にあった場合は、再投稿するほかないようです。
- 投稿日:2020-10-18T14:08:58+09:00
Vue.jsイベント修飾子メモ
<div id="app"> <p>現在{{ number }}回クリックされています。</p> <button v-on:click="countUp(3)">カウントアップ</button> <p v-on:mousemove="changeMousePosition(10, $event)">マウスをのせてください <span v-on:mousemove.stop.prevent>反応しないでください</span></p> <a v-on:click.prevent.stop href="https://google.com">Google</a> <p>X:{{x}},Y:{{y}}</p> <input type="text" v-on:keyup.enter="myAlert"> </div>.stop
JavaScript:event.stopPropagation()
<p v-on:mousemove="changeMousePosition(10, $event)">マウスをのせてください <span v-on:mousemove.stop.prevent>反応しないでください</span> </p>子要素のイベントが親のイベントを呼び出さない
spanの範囲に.stopを適応、「反応しないでください」の範囲のみ親要素のイベントが適応されなくなる。
.prevent
JavaScript:event.preventDefault()<a href="#top" @click.prevent="handler"></a>handlerは実行されるが、#topに移動はしない。
意図しない画面遷移、画面更新を避けるため.preventが有効
- 投稿日:2020-10-18T14:08:58+09:00
Vue.jsの基礎まとめ
<div id="app"> <p>現在{{ number }}回クリックされています。</p> <button v-on:click="countUp(3)">カウントアップ</button> <p v-on:mousemove="changeMousePosition(10, $event)">マウスをのせてください <span v-on:mousemove.stop.prevent>反応しないでください</span></p> <a v-on:click.prevent.stop href="https://google.com">Google</a> <p>X:{{x}},Y:{{y}}</p> <input type="text" v-on:keyup.enter="myAlert"> </div>.stop
JavaScript:event.stopPropagation()
<p v-on:mousemove="changeMousePosition(10, $event)">マウスをのせてください <span v-on:mousemove.stop.prevent>反応しないでください</span> </p>子要素のイベントが親のイベントを呼び出さない
spanの範囲に.stopを適応、「反応しないでください」の範囲のみ親要素のイベントが適応されなくなる。
.prevent
JavaScript:event.preventDefault()<a href="#top" @click.prevent="handler"></a>handlerは実行されるが、#topに移動はしない。
意図しない画面遷移、画面更新を避けるため.preventが有効
- 投稿日:2020-10-18T14:08:58+09:00
Vue.js基礎まとめ
よく使うイベント修飾子
.stop
JavaScript:event.stopPropagation()
<p v-on:mousemove="changeMousePosition(10, $event)">マウスをのせてください <span v-on:mousemove.stop.prevent>反応しないでください</span> </p>子要素のイベントが親のイベントを呼び出さない
spanの範囲に.stopを適応、「反応しないでください」の範囲のみ親要素のイベントが適応されなくなる。
.prevent
JavaScript:event.preventDefault()<a href="#top" @click.prevent="handler"></a>handlerは実行されるが、#topに移動はしない。
意図しない画面遷移、画面更新を避けるため.preventが有効
動的データを表現する
dataでは動的表現ができないためcomputedプロパティを使用computedプロパティの書き方new Vue({ el: "#app", data: { counter: 0, }, computed: { LessThanThreeComputed: function() { return this.counter > 3 ? '3より上' : '3以下' } }, })プロパティ名と処理する内容を記述するだけで簡単に動的データ「0 > 3」を表現することができる。
- 投稿日:2020-10-18T13:55:55+09:00
Vue3の基本構文②
Vueの構文についてコードベースで簡単にまとめます。
今回は以下の三つです。
- eventハンドリング
- class & styleバインディング
- computedプロパティー
eventハンドリング
index.html<div class="cart">Cart({{ cart }})</div> ... <button class="button">Add to Cart</button>main.jsdata() { return { cart: 0, ... } }
button
をクリックした際にcart
の値を増やしたい場合、以下のように書くことができます。インラインで書く(処理がシンプルな場合)
index.html<button class="button" v-on:click="cart += 1">Add to Cart</button>処理がシンプルな場合この様にインラインで書くことができますが、より複雑な場合は以下の様に
methods
を使います。メソッドを使って書く(基本的にこちらを使うことが多いです)
index.html<button class="button" v-on:click="addToCart">Add to Cart</button>
button
をクリックすると、addToCart
メソッドが呼ばれます。main.jsconst app = Vue.createApp({ data() { return { cart: 0, ... } }, methods: { addToCart() { this.cart += 1 } } })
methods
オプションの中にインラインで書いた処理と同じロジックのaddToCart
メソッドを追加します。ここで注意したいのが、methods
オプションの中ではthis.cart
を使ってVueインスタンスdataにあるcart
を参照します。ショートカット
index.html<button class="button" @click="addToCart">Add to Cart</button>
v-bind
の:
のようにv-on
も@
で短縮形で書くことができます。class & styleバインディング
index.html<div ... class="green" > </div>
green
クラスのCSSを要素にバインディングindex.html<div ... :style="{ color: green }" > </div>要素に直接スタイルをバインディング
computedプロパティ
main.jsconst app = Vue.createApp({ data() { return { firstName: '太郎', lastName: '田中' ... } }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } } })index.html<h1>{{ fullName }}</h1>Vueインスタンスdataのプロパティからcomputedプロパティを生成することができます。computedプロパティは一度アクセスされると内部メモリに保存され高速で処理ができます。そして、computedプロパティ(
fullName
)が依存しているdataプロパティー(firstNmae
,lastName
)が変更された場合、再度関数が評価されfullName
が更新されます。
- 投稿日:2020-10-18T10:42:08+09:00
[vue.js] ストップウォッチのコンポーネントを自作
ストップウォッチをvue.jsコンポーネントで実装してみました。
自作のブラウザゲームでクリア時間を計るのに使ってます。実行画面
時間は小数2桁まで表示。
Start/Stop ボタンと Reset ボタンを表示、ラップの記録機能は無し。ソースコード
stopWatch.vue<template> <div> <p>{{interval.toFixed(2)}}</p> <!-- 小数2桁まで表示 --> <button @click="startTimer()" v-show="!active">Start</button> <button @click="stopTimer()" v-show="active">Stop</button> <button @click="resetTimer()">Reset</button> </div> </template> <script> export default { name: 'stopWatch', data(){ return { active : false, // 実行状態 start : 0, // startを押した時刻 timer : 0, // setInterval()の格納用 interval : 0, // 計測時間 accum : 0, // 累積時間(stopしたとき用) } }, methods:{ startTimer(){ this.active = true; this.start = Date.now(); this.timer = setInterval(()=>{ this.interval = this.accum + (Date.now() - this.start) * 0.001;}, 10); // 10msごとに現在時刻とstartを押した時刻の差を足す }, stopTimer(){ this.active = false; this.accum = this.interval; clearInterval(this.timer); }, resetTimer(){ this.interval = 0; this.accum = 0; this.start = Date.now(); } } } </script>
- 投稿日:2020-10-18T02:45:39+09:00
【Vue.js 3】v-bindの機能ぜんぶ書く
たまたまVue.jsの
v-bind
について強く調べる機会があって、実はv-bind
の持つ全ての機能について説明してる記事って無いかも?と思ったので、「ぜんぶ」とまではいかないかもしれないけど思いつく範囲のv-bind
の機能を列挙するつもりです。
Vue.jsのバージョンは3に限定します(v2も調べ直すの面倒なので)。
v-bind
の基本Vue.jsのディレクティブの一つです。つまりテンプレートに記述するHTML属性っぽいところに記述できるVue.jsの構文の一つです。
ディレクティブがなんなのかのドキュメントはここ(英語)です。
v-bind
のAPIとしてのドキュメントはここ(英語)です。基本機能
HTML要素に使用すると、属性を動的に設定できます。コンポーネントに使用すると、コンポーネントのプロパティを動的に設定できます。
HTML要素に使用する例:
<template> <input v-bind:type="myInputType"> </template>この場合、出力されるHTMLは
<input>
のtype
属性に、myInputType
に保存された値が指定されます。
myInputType
='password'
の場合に出力されるHTMLの具体例は次です。<input type="password">コンポーネントに使用する例:
<template> <MyComponent v-bind:message="myMessage" /> </template>このコードは、
<MyComponent>
コンポーネントのmessage
プロパティに、myMessage
に保存された値が指定されます。HTMLの結果はコンポーネントの定義によるので割愛します。基本構文
HTMLでは
v-bind:【属性名】="【JavaScript式】"
のように記述します。記述は同じですがコンポーネントに使用するとv-bind:【プロパティ名】="【JavaScript式】"
となります。Vue.jsのディレクティブとして説明すると、ディレクティブ名が
bind
で、引数には属性名かプロパティ名を指定します。スクリプトにその属性かプロパティに与える値を取り出す式を記述するということになります。
(ディレクティブはv-【ディレクティブ名】:【引数】.【修飾子】="スクリプト"
みたいな構造を持ちます。詳細はこちら(英語))JavaScript式が書けるということは、次の例に書く
v-bind
は、いずれも有効なv-bind
です。<template> <input v-bind:type="myInputType"> <input v-bind:type="myTypes.foo.bar"> <input v-bind:type="types[kind]"> <input v-bind:type="getType(foo)"> <MyComponent v-bind:my-str-prop="prefix + str" /> <MyComponent v-bind:my-num-prop="num * 42" /> <MyComponent v-bind:my-obj-prop="{ foo: 1, bar: 2, baz }" /> <MyComponent v-bind:my-arr-prop="[1, 2, 3, foo]" /> </template>また
v-bind
には省略記法があり、
:【属性名】="【JavaScript式】"
のように記述しても同じです。<template> <input :type="myInputType"> <input :type="myTypes.foo.bar"> <input :type="types[kind]"> <input :type="getType(foo)"> <MyComponent :my-str-prop="prefix + str" /> <MyComponent :my-num-prop="num * 42" /> <MyComponent :my-obj-prop="{ foo: 1, bar: 2, baz }" /> <MyComponent :my-arr-prop="[1, 2, 3, foo]" /> </template>特別な使用
v-bind:class
ドキュメントはこの辺り(英語)です。
v-bind:class
のオブジェクト形式普通の
class
属性として考えると文字列のみ利用できそうですが、v-bind:class
にはオブジェクトを与えることができます。
与えるオブジェクトは、{ '【class名】': 【真偽値】 }
のような形式のオブジェクトです。真偽値がtrue
の場合は、そのclass
名が適用されfalse
の場合は適用されません。条件によってclass
を制御する時に便利です(文字列結合と三項演算子など使用する必要はありません)。<template> <div v-bind:class="{ 'item--active': isActive, 'item--hidden': !isShown }"></div> </template>
isActive
とisShown
がどちらもtrue
の時はitem--active
の値のみがtrue
のため、item--active
だけがclass
属性に付与されます。つまり次のHTMLが出力されます。<div class="item--active"></div>
v-bind:class
の配列形式
v-bind:class
には配列を与えることができます。配列の要素には与えるclass
名、およびオブジェクトを列挙します。すると配列で与えられたclass
名が全て適用されます。例:
<template> <div v-bind:class="['foo','bar']"></div> <div v-bind:class="['foo','bar', { 'item--active': isActive, 'item--hidden': !isShown }]"></div> </template>HTMLの出力:
<div class="foo bar"></div> <div class="foo bar item--active"></div>通常の
class
属性と併用通常の属性は、同じ属性に対して、
v-bind
と、普通の属性としての記述は併用できませんが、class
属性と次に紹介するstyle
属性は、併用することができます。
v-bind:class="..."
で与えるclass
名と、class="..."
で与えるclass
名はマージして適用されます。例:
<template> <div class="foo bar" v-bind:class="['baz','qux']"> </div> </template>HTMLの出力:
<div class="foo bar baz qux"></div>
v-bind:style
ドキュメントはこの辺り(英語)です。
v-bind:style
のオブジェクト形式
v-bind:style
もオブジェクトを与えることができます。
与えるオブジェクトは、{ '【style名】': '【styleの値】' }
のような形式のオブジェクトです。文字列結合を使用して、style
を頑張って作り出す必要はありません。また、プロパティ名に指定する【style名】はキャメルケースでも有効です。<template> <div v-bind:style="{ color: myColor, fontSize: myFontSize }"></div> </template>
myColor
が"red"
でmyFontSize
が"14px"
の場合、次のHTMLが出力されます。<div style="color: red; font-size: 14px;"></div>
v-bind:style
の配列形式
v-bind:style
には配列を与えることができます。配列の要素には与えるオブジェクトを列挙します。すると配列で与えられたstyle
が全て適用されます。例:
<template> <div v-bind:style="[{ color: myColor }, { fontSize: myFontSize }]"></div> </template>HTMLの出力:
<div style="color: red; font-size: 14px;"></div>自動プレフィックス
v-bind:style
とオブジェクトを使用して与えたスタイル名は、そのブラウザで動作するために必要なベンダープレフィックスを自動的に付与します。例:
<template> <div v-bind:style="{ 'line-clamp': 3 }"></div> </template>HTMLの出力(Chromeの場合):
<div style="-webkit-line-clamp: 3;"></div>通常の
style
属性と併用
v-bind:style
とstyle
属性は、併用することができます。
v-bind:style="..."
で与えるstyle
と、style="..."
で与えるstyle
はマージして適用されます。例:
<template> <div style="color: red;" v-bind:style="{ fontSize: '14px' }"> </div> </template>HTMLの出力:
<div style="color: red; font-size: 14px;"></div>
v-bind:key
ドキュメントはこの辺り(英語)です。
(本当はkey
属性(特別な属性)はv-bind
とは関係ないですが、併用することが多いと思うので書いておきます。)
key
属性を与えると、Vueは実際のDOMを描画するためのヒントとして使用します。よく使用される例をいくつか書いておきます。
ちなみにkey
属性は実際のHTMLには出力されません。
v-for
と使用
v-for
一緒に使用する場合は、最適化が主な目的です。
ドキュメントはこの辺り(英語)です。<template> <li v-for="item in items" v-bind:key="item.id"> {{ item.text }} </li> </template>
v-for
が<template>
にある場合は、v-bind:key
は<template>
に記述します。<template> <ul> <template v-for="item in items" v-bind:key="item.id"> <li>{{ item.text1 }}</li> <li>{{ item.text2 }}</li> </template> </ul> </template>
<transition-group>
と使用
<transition-group>
でアニメーションを意図通りに動かすためには、<transition-group>
の子要素でkey
を使用します。
ドキュメントはこの辺り(英語)です。<template> <transition-group name="list"> <span v-for="item in items" v-bind:key="item.id"> {{ item.text }} </span> </transition-group> </template>より高度な構文
先に書いた、基本構文以外の構文的な機能を書きます。
動的属性名(プロパティ名)
v-bind
はディレクティブなので動的引数を使用できます。
つまり、v-bind:【名前】
で指定していた【名前】は動的に変更できます。<template> <input v-bind:[foo]="bar"> </template>これは
foo
が"data-hello-bind"
で、bar
が"BAR DATA"
だった場合に出力されるHTMLの具体例は次です。<input data-hello-bind="BAR DATA">もちろん省略記法でも使用できます。
<template> <input :[foo]="bar"> </template>引数無し
v-bind="object"
と指定するとオブジェクトのプロパティとして与えた値を一括で適用します。つまり、
<template> <input v-bind="{ type:'text', maxlength: 3 }"> </template>は、HTMLでは、
<input type="text" maxlength="3">となります。
.camel
修飾子
v-bind:【名前】
で指定した【名前】を、キャメルケースに変換して実際に与えるらしいです(自分は使ったことないです。SVGで現れるキャメルケースの属性名の時に有用という噂を聞いたことがありますがよく知りません)。
詳細はこちら(英語)を参照してください。次のような形で使用します。
<svg v-bind:view-box.camel="viewBox /* viewBox='0,0,0,0'*/"></svg>出力はこのような形。
<svg viewBox="0,0,0,0"></svg>より高度な使用
<slot>
に使用スコープ付きスロットは
v-bind
を使用します。
ドキュメントはこの辺り(英語)です。
<slot>
でv-bind
を使用すると、子コンポーネントからv-bind
で与えられた値を、スロットコンテンツを生成する親コンポーネント側で使用できます。これはスコープ付きスロットと言われます。例:
ChildComponent<template> <div class="child"> <slot v-bind:foo="myFoo" /> </div> </template> <script> export default { data() { return { myFoo: "Hello" } } } </script>ParentComponent<template> <div class="parent"> <ChildComponent> <template v-slot="{ foo }"> {{ foo }} world </template> </ChildComponent> </div> </template>HTMLの出力:
<div class="parent"> <div class="child"> Hello world </div> </div>詳細はドキュメントを参照してください。
v-bind:ref
ドキュメントはこの辺り(英語)です。
普通に
ref="foo"
と書くと、これを書いた要素(またはコンポーネントのインスタンス)が$refs.foo
経由でアクセスできるようになりますが、v-bind:ref
を使用して、コールバック関数を与えるとコールバック関数によって、これを書いた要素(またはコンポーネントのインスタンス)を処理できます。<template> <div v-bind:ref="(el) => child = el /* childプロパティで受け取ります */">Foo</div> </template> <script> export default { data() { return { child: null } }, mounted() { console.log(this.child) /* <div>Foo</div> が出力されます。 */ } } </script>
v-bind:is
ドキュメントはこの辺り(英語)です。
is
は<component>
というVueの組み込みコンポーネントのプロパティです。
詳細はドキュメントを参照してください。HTML属性と値
いくつかの値は、それを与えるHTML属性によって、振る舞いが違います。
ドキュメントとしてはこの辺り(英語)とRFCだとこれ(英語)です。
null
またはundefined
null
かundefined
を与えると属性自体が付与されません。属性ごと与えたくない(属性を削除)したい場合は、null
かundefined
を与えておけば良さそうです。
空白文字列(''
)では属性自体はついたままなので要注意ということでもあります。例①<template> <input v-bind:type="nullValue"> <input v-bind:type="undefinedValue"> <input v-bind:type="falseValue"> <input v-bind:type="emptyValue"> <input v-bind:type="zeroValue"> </template> <script> export default { data () { return { nullValue: null, undefinedValue: undefined, falseValue: false, emptyValue: '', zeroValue: 0 } }, } </script>HTMLの出力<input> <input> <input type="false"> <input type> <input type="0">
false
真偽値属性(例えば
<input>
のdisabled
)の場合は空白文字列(''
)以外のfalse
な値を与えると属性自体が付与されません。
こちらも空白文字列(''
)では属性自体はついたままなので要注意です。特に属性としてはtrue
扱いであることに注意してください。例②<template> <input v-bind:disabled="nullValue"> <input v-bind:disabled="undefinedValue"> <input v-bind:disabled="falseValue"> <input v-bind:disabled="emptyValue"> <input v-bind:disabled="zeroValue"> </template> <script> export default { data () { return { nullValue: null, undefinedValue: undefined, falseValue: false, emptyValue: '', zeroValue: 0 } }, } </script>HTMLの出力<input> <input> <input> <input disabled> <input>まとめ
v-bind
の説明というより、Vueが特別に扱う属性とプロパティの機能説明って感じになってしまった気もしますね(汗)再度書きますが、Vue 3限定なので、Vue 2では使用できないことも書いてあります。
他のディレクティブも書いてみたい気持ちもあったけど、
v-bind
だけで結構疲れたのでもういいかな。
しかし、本当にこれで全部なのかどうかは自信ないです。。