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

アラートダイアログの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.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
export default {
  server: {
    port: 8000, // デフォルト: 3000
    host: '0.0.0.0' // デフォルト: localhost
  }
  // その他の設定
}

実際の記述▼

app/nuxt.config.js
server: {
  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.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :posts
    end
  end
end

PostsControllerを設定する

api/app/controllers/api/v1/posts_controller.rb
class 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
end

Postmanを使って投稿してみる

Postman
使い方参考記事
- 下記のようになっていたらOK
Postman.gif

Railsのコンソールで投稿できている確認する

// コンソールを立ち上げる
$ rails c

// 投稿を確認する
irb(main):001:0> Post.first

上記でPostmanと同じ内容が表示されていればOKです

※別の記事でNuxt.js側からデータを投稿する処理を実装します

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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@next
vue -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

スクリーンショット 20201017 20.19.14.png

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>

reactive.gif

リアクティブ性が失われていることがわかります。

このような状況に対する解決策として、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の値に対してアクセスします。

reactiveref.gif

ref

リアクティブなデータを宣言するもう一つの方法はrefを使用することです。
refはそれぞれの変数として宣言されます。

let inputValue = ref('')
let hasError = ref(false)

refで宣言された値にtemplate以外の場所からアクセスする場合、.valueからアクセスします。

inputValue.value
hasError.value

reactiveとrefどっちを使えばいい?

このように、Composition APIではリアクティブなデータの宣言にreactiverefどちらも使用することができます。しかし、どちらを使用するかのベストプラクティスはまだ存在していないようです。

Ref vs Reactive

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のようにwatchwatchEffect2つの方法が使えるようになりました。
以前のような使い方と近いのはwatchです。

watch

watchは第一に引数に監視する対象のリアクティブな値を指定します。リアクティブな値とは、refreactivecomputedで宣言された値を指します。

第二引数に実行するメソッドを渡します。

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

beforeCratecrateに相当するフックはなくなり、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プロパティに型定義がされている場合、そのとおりに推論されます。

スクリーンショット 20201018 14.41.51.png

注意点として、以下のように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する可能性があるイベントを配列として宣言するようになりました。

必須のオプションではないですが、コードを自己文章化することができ、推論もされるようになります。

スクリーンショット 20201018 14.53.53.png

モジュールに分割する

ここまでただ単に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を使ってみよう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VueとWP REST API でサムネイル付き一覧を作成&取得時に発生する再現性のないバグを回避する方法

WordpressとVueを組み合わせて一覧を作成していた際のコードサンプルと、
再現性のないバグ?っぽい挙動があり、情報がどこにもなかったためまとめます。

バグの解決をしたいだけの人用まとめ

結論:投稿消して、新規投稿、かつサムネイルも新規アップロードしたら直るよ!

背景

WP REST APIを利用し、Wordpress外でVueを使ってお知らせ一覧を表示するということを行っていました。
サムネイル付きのデータが欲しい場合、取得urlの末尾に?embedをつけると、
戻り値の中に_embeddedが追加され、その中にあるsource
urlから取得できます。
以下戻り値の取得例。

//戻り値が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){//…[&hellip;]
      var replaced = data.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'');
      replaced = replaced.replace(/ \[&hellip;\]/g,'');
      return replaced;
    },
  },
  created() {
    this.getPosts();
  },
};
</script>

だいたいこんな感じで取得していました。

WordPressでいろいろいじっていたら突然サムネイルが取れなくなる

取得できたのを確認したあと、Wordpressで投稿のカテゴリを増やす、
また特定の記事でサムネイル画像を変更するなどしていました。

その後、一覧を取得していたページに戻ったところ、
触った記事だけでなく、全記事のサムネイル画像が表示されなくなっていました。

そこでconsoleにログを取得して確認していたところ、
wp:featuredmediaから先が正しく取れていないことがわかりました。
その中のデータを確認すると以下のようなエラーが返ってきています。

code: "rest_forbidden"
data:status: 401
message:"その操作を実行する権限がありません。"

そもそも権限にからむ操作をした記憶もなく、ソース自体変わっていなかったため原因が不明でした。

解決方法

国内外問わずググってもこれという情報がでず・・・
WP APIのissueで同様の質問を発見。
解答を見ていると、
「これ多分バグっぽくて、投稿消して再度投稿したら行けるよ」って書いてました。

なので、言われた通り、
投稿消して、新規投稿、かつサムネイルも新規アップロード(←重要)したら直りました。

日本語ファイルが原因だったのか、特殊文字が原因だったのか(サンプル①.jpgみたいな名前)、
再度試したけど再現せず、サンプル①.jpgみたいな名前の画像でもしっかりと表示され、
原因自体は不明でした。

とにかく、このような現象にあった場合は、再投稿するほかないようです。

参考 https://github.com/WP-API/WP-API/issues/2596

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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が有効

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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が有効

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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」を表現することができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3の基本構文②

Vueの構文についてコードベースで簡単にまとめます。
今回は以下の三つです。

  • eventハンドリング
  • class & styleバインディング
  • computedプロパティー

eventハンドリング

index.html
<div class="cart">Cart({{ cart }})</div>
...
<button class="button">Add to Cart</button>
main.js
data() {
  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.js
const 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.js
const 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が更新されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[vue.js] ストップウォッチのコンポーネントを自作

ストップウォッチをvue.jsコンポーネントで実装してみました。
自作のブラウザゲームでクリア時間を計るのに使ってます。

実行画面

image.png
時間は小数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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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>

isActiveisShownがどちらも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:stylestyle属性は、併用することができます。

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

nullundefinedを与えると属性自体が付与されません。属性ごと与えたくない(属性を削除)したい場合は、nullundefinedを与えておけば良さそうです。
空白文字列('')では属性自体はついたままなので要注意ということでもあります。

例①
<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だけで結構疲れたのでもういいかな。
しかし、本当にこれで全部なのかどうかは自信ないです。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む