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>
问题根源
- 计算属性被当作方法调用:
LanguageSelector中setActivePopup是计算属性,返回activePopup的值,但父组件中用setActivePopup()的方式调用,Vue会将其识别为方法,而实际不存在该方法,触发类型错误。 - 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规范:
- 更新
LanguageSelector.vue模板,传递需要的属性和方法:
<template> <slot :activePopup="activePopup" :closePopup="closePopup" :openPopup="openPopup" :togglePopup="togglePopup" ></slot> </template>
同时可移除冗余的setActivePopup计算属性。
- 父组件通过作用域插槽接收并使用:
<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
相关产品推荐
相关产品推荐

