You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue2迁移Vue3遇TypeError: setActivePopup不是函数问题求助

Vue2迁移Vue3报错:TypeError: setActivePopup is not a function

报错详情

错误栈

caught TypeError: setActivePopup is not a function
at eval (eval at compileToFunction (vue.esm-bundler.js:65:1), :204:43)
at renderFnWithContext (runtime-core.esm-bundler.js:868:1)
at renderSlot (runtime-core.esm-bundler.js:3025:1)
at Proxy.render (LanguageSelector.vue:2:3)
at renderComponentRoot (runtime-core.esm-bundler.js:914:1)
at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5649:1)
at ReactiveEffect.run (reactivity.esm-bundler.js:190:1)
at instance.update (runtime-core.esm-bundler.js:5763:1)
at setupRenderEffect (runtime-core.esm-bundler.js:5777:1)
at mountComponent (runtime-core.esm-bundler.js:5559:1)

相关代码

父组件调用代码

<language-selector>
    <div class="language-selector__overlay"
         v-show="setActivePopup()"
         :aria-hidden="!setActivePopup()"> <!-- v-cloak -->
    </div>

    <div class="language-selector__popup-wrapper"> <!-- v-cloak -->
        <focus-trap
            ref="focusTrap"
            :value="setActivePopup()">
            <div
                class="language-selector__popup"
                @keydown.esc.prevent="closePopup()"
                :open="setActivePopup()"
                v-show="setActivePopup()"
                :aria-hidden="!setActivePopup()">
                <% include LanguageSelector %>
            </div>
        </focus-trap>
    </div>
</div>
</language-selector>

LanguageSelector.vue组件代码

<template>
  <slot></slot>
</template>

<script>
import { FocusTrap } from 'focus-trap-vue';
import SwipeTrap from "../mixins/SwipeTrap";

export default {
  name: 'language-selector',
  mixins: [
    SwipeTrap,
  ],
  components: {
    // eslint-disable-next-line vue/no-unused-components
    'focus-trap': FocusTrap
  },
  data() {
    return {
      activePopup: null,
    };
  },
  computed: {
    setActivePopup() {
      return this.activePopup;
    }
  },
  methods: {
    togglePopup() {
      this.activePopup = !this.activePopup;
    },
    closePopup() {
      this.activePopup = false;

      // Remove styles for open menu
      const closeButton = document.getElementsByClassName('');
      for (let i = 0; i < closeButton.length; i += 1) {
        const aButton = closeButton[i];
        aButton.classList.remove('language-selector__button--open');
      }

      // Remove active styles and event listener from overlay
      const overlays = document.getElementsByClassName('language-selector__overlay');
      for (let i = 0; i < overlays.length; i += 1) {
        const overlay = overlays[i];
        overlay.classList.remove('language-selector__overlay--active');
        overlay.removeEventListener('click', this.closePopup);
      }
    },
    openPopup() {
      this.activePopup = true;

      // Apply styles for open menu
      const closeButton = document.getElementsByClassName('language-selector__button');
      for (let i = 0; i < closeButton.length; i += 1) {
        const aButton = closeButton[i];
        aButton.classList.add('language-selector__button--open');
      }

      // Apply active styles and event listener to overlay
      const overlays = document.getElementsByClassName('language-selector__overlay');
      for (let i = 0; i < overlays.length; i += 1) {
        const overlay = overlays[i];
        overlay.classList.add('language-selector__overlay--active');
        overlay.addEventListener('click', this.closePopup);
      }

      // Focus heading element on open
      this.$nextTick(() => {
        const langSelectorMobile = '.header__lang-selector--mobile';
        const langSelectorDesktop = '.header__lang-selector';
        const langSelectorHeader = '.language-selector__header';

        // check if we are viewing this in mobile or desktop
        // assume we are viewing on mobile
        let langSelectorEl = document.querySelector(langSelectorMobile);
        if (!langSelectorEl || window.getComputedStyle(langSelectorEl).display === 'none') {
          // we are viewing on desktop
          langSelectorEl = document.querySelector(langSelectorDesktop);
        }

        // find and focus the header
        const headerEl = langSelectorEl.querySelector(langSelectorHeader);
        headerEl.focus();
      });
    },
  },
};
</script>

问题根源

  1. 计算属性被当作方法调用:LanguageSelector中setActivePopup是计算属性,返回activePopup的值,但父组件中用setActivePopup()的方式调用,Vue会将其识别为方法,而实际不存在该方法,触发类型错误。
  2. Vue3插槽作用域变更:Vue3中插槽内容默认仅能访问父组件作用域,无法直接获取子组件的属性和方法,父组件直接调用子组件的closePopup、setActivePopup等,不符合Vue3的作用域规则。

修复方案

方案1:修正计算属性调用方式(仅解决语法错误)

将父组件中所有setActivePopup()改为setActivePopup(去掉函数调用括号),因为计算属性是属性而非方法:

<language-selector>
    <div class="language-selector__overlay"
         v-show="setActivePopup"
         :aria-hidden="!setActivePopup">
    </div>

    <div class="language-selector__popup-wrapper">
        <focus-trap
            ref="focusTrap"
            :value="setActivePopup">
            <div
                class="language-selector__popup"
                @keydown.esc.prevent="closePopup()"
                :open="setActivePopup"
                v-show="setActivePopup"
                :aria-hidden="!setActivePopup">
                <% include LanguageSelector %>
            </div>
        </focus-trap>
    </div>
</div>
</language-selector>

注:此方案仅解决setActivePopup的调用错误,无法解决父组件访问子组件方法的问题。

方案2:使用作用域插槽传递属性和方法(推荐)

通过作用域插槽将子组件的状态和方法暴露给父组件,符合Vue3规范:

  1. 更新LanguageSelector.vue模板,传递需要的属性和方法:
<template>
  <slot 
    :activePopup="activePopup"
    :closePopup="closePopup"
    :openPopup="openPopup"
    :togglePopup="togglePopup"
  ></slot>
</template>

同时可移除冗余的setActivePopup计算属性。

  1. 父组件通过作用域插槽接收并使用:
<language-selector v-slot="{ activePopup, closePopup }">
    <div class="language-selector__overlay"
         v-show="activePopup"
         :aria-hidden="!activePopup">
    </div>

    <div class="language-selector__popup-wrapper">
        <focus-trap
            ref="focusTrap"
            :value="activePopup">
            <div
                class="language-selector__popup"
                @keydown.esc.prevent="closePopup"
                :open="activePopup"
                v-show="activePopup"
                :aria-hidden="!activePopup">
                <% include LanguageSelector %>
            </div>
        </focus-trap>
    </div>
</div>
</language-selector>

额外优化建议

  • 移除LanguageSelector中冗余的setActivePopup计算属性,直接使用activePopup。
  • 替换原生DOM操作(getElementsByClassName、querySelector等)为Vue的ref引用或响应式数据绑定,避免直接操作DOM带来的潜在问题。

内容的提问来源于stack exchange,提问作者Pack Gaewmaneechot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 20:07:03