14.

【Vue.jsエラー】Emitted value instead of an instance of Error

編集
この記事の要点
  • (Emitted value instead of an instance of Error)Vue.js のコンポーネントテンプレートエラー
  • 原因: Vue 2 系の制約「<template> 直下に複数ルート要素」
  • 対処: ルート要素を 1 つだけにする(<div> 等でラップ)
  • Vue 3 ではマルチルート OK: Vue 3 へ移行すれば解消
  • Vue 2 でも functional component や JSX なら複数ルート可

 

エラーの状況

(Emitted value instead of an instance of Error)
  Error compiling template:

    <template>
      <div>...</div>
      <div>...</div>
    </template>

  - Component template should contain exactly one root element.
    If you are using v-if on multiple elements, use v-else-if to chain them instead.

Vue 2 系の制約: 1 つのコンポーネントテンプレートにはルート要素が 1 つだけでなければなりません。

原因と対処

原因 1: 複数のルート要素

<!-- ダメな例 -->
<template>
    <div class="header">ヘッダー</div>
    <div class="body">本文</div>
    <div class="footer">フッター</div>
</template>

<!-- 修正: 1 つの親要素でラップ -->
<template>
    <div class="wrapper">
        <div class="header">ヘッダー</div>
        <div class="body">本文</div>
        <div class="footer">フッター</div>
    </div>
</template>

原因 2: 条件分岐で複数ルート

<!-- ダメな例 -->
<template>
    <div v-if="isLoggedIn">ようこそ</div>
    <div v-else>ログインしてください</div>
</template>

<!-- これは実は OK -->
<!-- v-if と v-else は単一ルートとして扱われる -->

<!-- ダメな例 (条件複数) -->
<template>
    <div v-if="status === 'loading'">読込中</div>
    <div v-if="status === 'success'">完了</div>  <!-- ← v-else-if にすべき -->
    <div v-if="status === 'error'">エラー</div>
</template>

<!-- 修正: v-else-if でチェーン -->
<template>
    <div v-if="status === 'loading'">読込中</div>
    <div v-else-if="status === 'success'">完了</div>
    <div v-else-if="status === 'error'">エラー</div>
</template>

原因 3: コメントが影響することも

<!-- ダメな例 (古い Vue だとコメントも要素扱い) -->
<template>
    <!-- コメント -->
    <div>本体</div>
</template>

<!-- 修正 -->
<template>
    <div>
        <!-- コメント -->
        本体
    </div>
</template>

原因 4: 改行 / テキストノードが混在

<!-- ダメな例 -->
<template>
    テキスト
    <div>div</div>
</template>

<!-- 修正 -->
<template>
    <div>
        テキスト
        <div>div</div>
    </div>
</template>

Vue 3 ではマルチルート可

Vue 3 から「Fragments」が導入され、複数ルート要素 OK になりました:

<!-- Vue 3 ではこれが OK -->
<template>
    <header>ヘッダー</header>
    <main>本文</main>
    <footer>フッター</footer>
</template>

属性の継承注意

<!-- 親から渡される属性は明示的に -->
<template>
    <header v-bind="$attrs">ヘッダー</header>  <!-- どこに属性を付けるか明示 -->
    <main>本文</main>
</template>

<script setup>
defineOptions({
    inheritAttrs: false  // 暗黙の継承を無効化
});
</script>

Vue 2 で複数ルートを扱う方法

① Functional Component

<!-- Vue 2 の functional component -->
<template functional>
    <div>
        <slot />
        <slot name="footer" />
    </div>
</template>

<!-- render 関数で複数子要素を返す -->
<script>
export default {
    functional: true,
    render(h, context) {
        return [
            h("header", "ヘッダー"),
            h("main", context.slots().default),
            h("footer", "フッター")
        ];
    }
};
</script>

② JSX で複数ルート (Babel + jsx プラグイン)

export default {
    render() {
        return [
            <header>ヘッダー</header>,
            <main>本文</main>,
            <footer>フッター</footer>
        ];
    }
};