Vue+TypeScript中使用mapState遇未定义错误,求优化方案
Vuex mapState结合TypeScript时的报错问题
我在使用TypeScript编写Vue组件时,使用mapState后出现报错:TypeError: Cannot read properties of undefined (reading 'someTitle')。组件代码如下:
<template> <SomeComponent :title="title"> </SomeComponent> </template> <script lang="ts"> import Vue from 'vue' ... const MyComponentProps = Vue.extend({ }) @Component({ components: { SomeComponent, ... }, ...mapGetters(['currentSubscription']), ...mapState({ content: (state: RootState) => state.content, }) }) export default class MyComponent extends MyComponentProps { content!: ContentModel get title () { return this.content.someTitle } </script>
目前发现两种可行但不满意的处理方式:直接通过this.$store.state.content.someTitle读取状态时无报错;用watch监听content并赋值给title能正常运行,但该方案冗余且可读性差,违背了mapState的设计初衷。
现咨询两个问题:
- 为何直接调用store无报错?
- 能否不使用watch,实现mapState与计算属性的结合?
问题1:直接调用store无报错的原因
- 直接访问
this.$store.state.content时,Vuex的state是响应式且初始化完成的,哪怕初始值是undefined,后续state被赋值后Vue的响应式系统会自动更新模板。但用mapState映射的content在组件类中用非空断言!声明后,组件初始化阶段,mapState的映射属性还没完成注入,导致计算属性title首次执行时this.content还是undefined,触发报错。 - 核心差异:直接访问
$store是直接取全局状态,不存在属性注入的时机差;而mapState映射的属性是作为组件计算属性注入的,初始化时的执行顺序晚于类中计算属性的首次调用。
问题2:不使用watch实现mapState与计算属性结合的方案
有两种可靠的解决方式:
方案1:将计算属性移至@Component装饰器的computed中
把title计算属性和mapState、mapGetters放在同一个computed对象里,确保依赖的映射属性已经完成注入:
<template> <SomeComponent :title="title" /> </template> <script lang="ts"> import Vue from 'vue' import { Component, mapState, mapGetters } from 'vue-property-decorator' import SomeComponent from './SomeComponent.vue' import { RootState, ContentModel } from '@/store/types' const MyComponentProps = Vue.extend({}) @Component({ components: { SomeComponent }, computed: { ...mapGetters(['currentSubscription']), ...mapState({ content: (state: RootState) => state.content, }), title() { // 用可选链避免undefined报错,也可以设置默认值 return this.content?.someTitle || '' } } }) export default class MyComponent extends MyComponentProps {} </script>
方案2:在类的计算属性中添加空值保护
如果坚持在类中定义计算属性,必须给content添加运行时的空值判断,不要仅依赖TypeScript的非空断言:
<template> <SomeComponent :title="title" /> </template> <script lang="ts"> import Vue from 'vue' import { Component, mapState, mapGetters } from 'vue-property-decorator' import SomeComponent from './SomeComponent.vue' import { RootState, ContentModel } from '@/store/types' const MyComponentProps = Vue.extend({}) @Component({ components: { SomeComponent }, computed: { ...mapGetters(['currentSubscription']), ...mapState({ content: (state: RootState) => state.content, }) } }) export default class MyComponent extends MyComponentProps { content!: ContentModel get title() { // 可选链+默认值,避免content为undefined时报错 return this.content?.someTitle || '' // 也可以用更严谨的条件判断 // return this.content && this.content.someTitle ? this.content.someTitle : '' } } </script>
额外提示
TypeScript的非空断言!只是类型层面的声明,无法保证运行时的值一定存在。所以必须在运行时添加空值保护,同时方案1更符合Vue Class Component结合Vuex的最佳实践,能彻底避免执行时机导致的问题。
内容的提问来源于stack exchange,提问作者Tom Carmi
相关产品推荐
相关产品推荐

