Eol
Thanks for reading :)
Eol's Blog

Vue.js + TypeScript:class style component 添加 static 属性

Vue.js + TypeScript:class style component 添加 static 属性

Vue Router 的 Magic String

VueRouter 类的构造函数接受一个包含 routes: Array<RouterConfig> 属性的对象作为参数,一般来说 routes 这样定义:

import SomeComponent from 'path/to/component.vue'

const routes: Array<RouteConfig> = [
  {
    path: 'some-path',
    name: 'SomeComponentName',
    component: SomeComponent
  }
  // ...
]

在使用 Vue Router 时,如果要跳转到某个界面,直接将 <router-link> 标签的 to attribute 绑定到 { name: 'SomeComponentName' },或在组件的方法中 this.$router.push({ name: 'SomeComponentName' }) 即可。这样处理只需要每个组件的 name,而不需要记住他们在 Vue Router 中对应的路径,同时方便了对路径的重新定义。

然而我感觉还不够,因为从某种意义上来说,这不过是引入了一个新的 Magic String(即 name 变量)来解决其它 Magic String 问题的拆东墙补西墙的方法。

如果要选择什么字符串唯一地代表一个 class,自然是 class 的 name 属性。在这种想法下,我的 Vue Router 代码大概变成了这个样子:

import SomeComponent from 'path/to/component.vue'

const routes: Array<RouteConfig> = [
  {
    path: 'some-path',
    name: SomeComponent.name,
    component: SomeComponent
  }
  // ...
]

任何其他的代码中若要获取这个 name 之需要 import 这个组件类,然后用 name 属性获取。这样处理得还算可以,至少在 develop 环境下没有出现过问题。然而一旦进入 production 环境,问题就暴露了出来。

Minify 导致的问题

Vue 程序运行 yarn run build 进行生产打包时,Webpack 会将代码 minify,这让上述的解决方案成为了导致 bug 的根源。在我的测试中,build 之后的 Vue 程序不响应指定 name 的跳转。找到挂载 Vue App 的标签,查看路由,结果发现两个路由对象的 name 属性竟然都是 "a"

https://www.eolstudy.com/wp-content/uploads/2021/06/image-1.png

阅读调试 minify 之后的代码,发现 exportname 竟然直接用 "a" 指定了。

https://www.eolstudy.com/wp-content/uploads/2021/06/image-2-1024x314.png

至此可以认为任何涉及 minify 的场景,函数、对象、类等等的 name 属性不应该参与程序的运行逻辑,因为最小化后的代码虽然不改变逻辑,但改变变量名,而且还会因为 JavaScript 语言版本问题将一种语法改变成另一种语法(比如 ES6 的 class 没有出现在 minify 后的代码中)。

解决方案之一当然是 build 时关闭 minify 功能(见 https://github.com/vuejs/vue-cli/issues/4328#issuecomment-514250189)。

https://www.eolstudy.com/wp-content/uploads/2021/06/image-3.png
name 为 “Home”,没有被改变

但 minify 是 Web 开发的基本需求,因此用 classname 作为 RouterConfigname 是走不通的。

另一种解决方案也很显然了,就是手动指定某一字符串,绕开 minify 的影响。

最终方案

对每个 RouterConfig 中的 Componentclass,声明时增加一个属性,如:

@Component
export default class Home extends Vue {
  static readonly id = 'Home'
  // ...
}

但这个方案并不是到这里为止这么简单。直接在 RouterConfig 中使用时 TypeScript 会有如下报错:

Property 'id' does not exist on type 'VueConstructor<Vue>'

原因是 *.vue 文件并不直接能被 TypeScript 识别。import Vue 组件时,tsc 对该文件类型的识别借助 src/shims-vue.d.ts 文件。文件默认的内容为:

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

TypeScript 的 .d.ts 文件用于对非 TypeScript 代码描述类型。上述代码描述的意思即,所有以 .vue 结尾的文件都 export default 一个类型与 ‘vue’ 中的 Vue 相同的对象(即 VueConstructor<Vue>)。显然要让 TypeScript 知道追加的 id 的话,就要对这个文件下手了。

我魔改后的 vue.d.ts 文件内容如下:

declare module '*.vue' {
  import Vue from 'vue'
  export default class ComponentWithId extends Vue {
    static readonly id: string
  }
}

其实就是直接描述了用 class style component 的 .vue 文件的结构。

至此原问题和 TypeScript 的类型问题便都得到解决了。

# # # #
Homepage      Develop      Vue.js + TypeScript:class style component 添加 static 属性

Eol

Author

Leave a Reply

textsms
account_circle
email

Eol's Blog

Vue.js + TypeScript:class style component 添加 static 属性
Vue Router 的 Magic String VueRouter 类的构造函数接受一个包含 routes: Array<RouterConfig> 属性的对象作为参数,一般来说 routes 这样定义: import SomeComponent from…
Scan QR code to continue reading
2021-06-27