タイトル: メソッドの定義方法
SEOタイトル: Vue.js メソッド定義完全ガイド(Options API / Composition API / setup)
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>Options API</strong>: <code>methods: { greet() { ... } }</code> に並べる。<code>this</code> でデータにアクセス可</li><li><strong>Composition API + <code><script setup></code></strong>: <code>const greet = () => { ... }</code> として宣言、<code>this</code> 不要</li><li>テンプレートからの呼出: <code>@click="greet"</code> / 引数付き: <code>@click="greet('hello')"</code> / イベントオブジェクト: <code>@click="greet($event)"</code></li><li><strong>computed と methods の違い</strong>: computed はキャッシュされる / methods は毎回実行</li><li>子→親通信は <code>emit('custom-event', payload)</code>。<code>defineEmits</code> で型定義</li></ul></td></tr></table>
<h2>Vue.js のメソッド定義 2 つのスタイル</h2>
<p>Vue.js 3 では「Options API」と「Composition API」の 2 つの書き方があり、メソッド定義もそれぞれスタイルが異なります。</p>
<h2>1. Options API でのメソッド定義</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><!-- src/components/Greeting.vue -->
<template>
<div>
<p>{{ message }}</p>
<button @click="greet">挨拶する</button>
<button @click="greetTo('世界')">世界に挨拶</button>
<input @input="updateMessage" :value="message">
</div>
</template>
<script>
export default {
data() {
return {
message: 'こんにちは',
};
},
methods: {
greet() {
// this でデータと他メソッドにアクセス
this.message = 'やあ!';
this.logIt();
},
greetTo(name) {
this.message = `${name}、こんにちは!`;
},
updateMessage(event) {
this.message = event.target.value;
},
logIt() {
console.log('logged:', this.message);
},
},
};
</script></code></pre>
<h3>Options API のポイント</h3>
<ul>
<li>すべてのメソッドは <code>methods:</code> オブジェクト内に並べる</li>
<li>メソッド内では <strong><code>this</code></strong> で <code>data</code> / <code>props</code> / 他メソッド / <code>computed</code> にアクセス</li>
<li><strong>アロー関数で書いてはいけない</strong>(<code>this</code> が外側の <code>this</code> になってしまう)</li>
<li><strong>非同期処理</strong> (<code>async/await</code>) もそのまま使える</li>
</ul>
<h2>2. Composition API + <code><script setup></code></h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><!-- src/components/Greeting.vue (Composition API) -->
<template>
<div>
<p>{{ message }}</p>
<button @click="greet">挨拶する</button>
<button @click="greetTo('世界')">世界に挨拶</button>
<input @input="updateMessage" :value="message">
</div>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('こんにちは');
// アロー関数 or 通常 function どちらでも OK
const greet = () => {
message.value = 'やあ!';
logIt();
};
function greetTo(name) {
message.value = `${name}、こんにちは!`;
}
const updateMessage = (event) => {
message.value = event.target.value;
};
const logIt = () => {
console.log('logged:', message.value);
};
</script></code></pre>
<h3>Composition API のポイント</h3>
<ul>
<li>メソッドは<strong>通常の関数</strong>として <code><script setup></code> 内で宣言</li>
<li><code>this</code> は<strong>存在しない</strong>(不要)</li>
<li>ref/reactive の値には <code>.value</code> でアクセス(テンプレートでは自動アンラップ)</li>
<li>外部公開(テンプレートから使える)は<strong>宣言するだけで自動</strong></li>
<li>TypeScript との相性がよい</li>
</ul>
<h2>Options API vs Composition API 早見表</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>項目</th><th>Options API</th><th>Composition API + setup</th></tr></thead>
<tbody>
<tr><td>メソッド宣言</td><td><code>methods: { greet() {} }</code></td><td><code>const greet = () => {}</code></td></tr>
<tr><td>データ参照</td><td><code>this.count</code></td><td><code>count.value</code></td></tr>
<tr><td><code>this</code></td><td>あり(コンテキスト)</td><td>なし</td></tr>
<tr><td>アロー関数</td><td>NG(this が変わる)</td><td>OK</td></tr>
<tr><td>外部公開</td><td>methods 内に書くだけ</td><td>宣言するだけ</td></tr>
<tr><td>TS 補完</td><td>限定的</td><td>強力</td></tr>
<tr><td>ロジックの再利用</td><td>mixin(推奨されない)</td><td>composables(推奨)</td></tr>
<tr><td>学習コスト</td><td>低</td><td>中</td></tr>
</tbody>
</table>
<h2>テンプレートからの呼び出し方</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><template>
<!-- 引数なし: 関数参照を渡す -->
<button @click="greet">A</button>
<button v-on:click="greet">A(v-on の正式形)</button>
<!-- 引数あり: 呼び出し式 -->
<button @click="greet('hello')">B</button>
<!-- イベントオブジェクトを渡す -->
<input @input="handleInput($event)">
<!-- 引数 + イベント両方 -->
<button @click="handleClick('foo', $event)">C</button>
<!-- 修飾子 (.prevent, .stop, .once, .capture, .self) -->
<form @submit.prevent="onSubmit">...</form>
<a @click.stop="onClick">伝播止める</a>
<!-- キー修飾子 -->
<input @keyup.enter="onEnter">
<input @keyup.esc="onCancel">
<!-- ボタン修飾子 -->
<div @click.right="onRightClick">右クリック</div>
</template></code></pre>
<h2>computed と methods と watch の違い</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#fff3e0"><th>用途</th><th>methods</th><th>computed</th><th>watch</th></tr></thead>
<tbody>
<tr><td>計算結果を表示</td><td>毎回再実行</td><td><strong>★ キャッシュされる</strong></td><td>—</td></tr>
<tr><td>クリック等のイベント処理</td><td><strong>★ これが本職</strong></td><td>—</td><td>—</td></tr>
<tr><td>値が変わったときに副作用実行</td><td>—</td><td>—</td><td><strong>★ これが本職</strong></td></tr>
<tr><td>引数を受ける</td><td><strong>○</strong></td><td>○(が再評価されない)</td><td>—</td></tr>
<tr><td>非同期処理</td><td>○</td><td>×(同期のみ)</td><td>○</td></tr>
</tbody>
</table>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><script setup>
import { ref, computed, watch } from 'vue';
const count = ref(0);
// methods(呼び出すたびに実行)
const formatted = () => `現在: ${count.value}`;
// computed(依存値が変わったときだけ再計算、結果はキャッシュ)
const doubled = computed(() => count.value * 2);
// watch(変化に応じて副作用)
watch(count, (newVal, oldVal) => {
console.log(`変化: ${oldVal} → ${newVal}`);
localStorage.setItem('count', String(newVal));
});
</script>
<template>
<p>{{ formatted() }}</p> <!-- 関数呼び出し(毎回実行) -->
<p>{{ doubled }}</p> <!-- 値として参照(キャッシュ) -->
</template></code></pre>
<h2>子→親通信: emit</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><!-- 子コンポーネント Child.vue -->
<script setup>
// イベント名と型を宣言
const emit = defineEmits(['change', 'submit']);
const onClick = () => {
emit('change', { id: 1, name: 'foo' }); // 親に値を渡す
};
</script>
<template>
<button @click="onClick">送信</button>
</template>
<!-- 親 Parent.vue -->
<script setup>
import Child from './Child.vue';
const onChange = (payload) => {
console.log('子から:', payload);
};
</script>
<template>
<Child @change="onChange" />
</template></code></pre>
<h2>ライフサイクルとメソッドの組合せ</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const users = ref([]);
const intervalId = ref(null);
const fetchUsers = async () => {
const res = await fetch('/api/users');
users.value = await res.json();
};
onMounted(() => {
// 初期ロード
fetchUsers();
// 30秒ごとに更新
intervalId.value = setInterval(fetchUsers, 30000);
});
onUnmounted(() => {
// アンマウント時にタイマー解除
clearInterval(intervalId.value);
});
</script></code></pre>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e8f5e9"><th>Options API</th><th>Composition API</th><th>タイミング</th></tr></thead>
<tbody>
<tr><td><code>beforeCreate</code></td><td><code>setup()</code> 直前</td><td>インスタンス作成前</td></tr>
<tr><td><code>created</code></td><td><code>setup()</code> 内</td><td>インスタンス作成済 / DOM 前</td></tr>
<tr><td><code>beforeMount</code></td><td><code>onBeforeMount</code></td><td>DOM 挿入直前</td></tr>
<tr><td><code>mounted</code></td><td><code>onMounted</code></td><td>DOM 挿入後(API 呼出はここ)</td></tr>
<tr><td><code>beforeUpdate</code></td><td><code>onBeforeUpdate</code></td><td>再レンダリング前</td></tr>
<tr><td><code>updated</code></td><td><code>onUpdated</code></td><td>再レンダリング後</td></tr>
<tr><td><code>beforeUnmount</code></td><td><code>onBeforeUnmount</code></td><td>破棄直前</td></tr>
<tr><td><code>unmounted</code></td><td><code>onUnmounted</code></td><td>破棄完了</td></tr>
</tbody>
</table>
<h2>composables(ロジックの再利用)</h2>
<p>Composition API の真価。メソッド + 状態をまとめて切り出し、他のコンポーネントで再利用できます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// composables/useCounter.js
import { ref, computed } from 'vue';
export function useCounter(initial = 0) {
const count = ref(initial);
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => count.value = initial;
const isZero = computed(() => count.value === 0);
const doubled = computed(() => count.value * 2);
return { count, increment, decrement, reset, isZero, doubled };
}</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><!-- 任意のコンポーネントで使用 -->
<script setup>
import { useCounter } from '@/composables/useCounter';
const { count, increment, decrement, reset, doubled } = useCounter(10);
</script>
<template>
<p>カウント: {{ count }}(2 倍: {{ doubled }})</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">リセット</button>
</template></code></pre>
<h2>FAQ</h2>
<p><strong>Q: Options API と Composition API はどちらを使うべき?</strong><br />
A: 公式は<strong>新規プロジェクトでは Composition API を推奨</strong>。ただし Options API も将来も使えます。チームに合わせて選択。</p>
<p><strong>Q: <code>this</code> がうまく動かない</strong><br />
A: Options API の methods 内で<strong>アロー関数を使っていない</strong>か確認してください。<code>methods: { greet: () => { this.x } }</code> は NG。<code>methods: { greet() { this.x } }</code> が正しい。</p>
<p><strong>Q: <code>@click="greet"</code> と <code>@click="greet()"</code> の違い</strong><br />
A: 前者は<strong>関数参照を渡す</strong>(イベントオブジェクトが第一引数に入る)、後者は<strong>その場で呼び出して戻り値を渡す</strong>。引数を渡したいときだけ括弧を付けます。</p>