原由

单纯用javascript其实还是有点局限性,比如枚举,虽然js中可以用对象实现,实际上就是定义一个对象,并让它readonly。但不如用typescript语法清晰明了。还有我一直想接触静态类型语言,包括之前想把Btools插件重制一下,目前也是用的vue+typescript起步了,不过搁置了。

加入 TypeScript 的方式

直接安装

因为目前开发的项目是用的electron-vue(Github)直接创建的,所以就想直接用包管理器进行安装,装好typescriptts-loader写了个简单的测试页面,发现是能运行的。

但我完全不清楚tsconfig.jsontypes等各种配置文件需要如何配置,所以暂时放弃了这种方案。

Vue 脚手架

网上查阅各种资料,发现可以直接用vue init创建一个typescript项目,然后用vue add electron-builderelectron的编译器添加到项目里,这种方式非常简单,配置文件都是别人做的现成的。

但问题它不是用的webpack,而是用的vue-cli-service。这个还好,但还有一个它会报错fs.existsSync is not a function,一直没能解决。

网上查的是执行线程问题,fs需要在主线程执行,而渲染线程获取不到。但目前找到的解决方法都没用。

梅开二度

最后实在没啥招,于是重回直接安装方案,毕竟也有配置文件可以参考了。于是参考了脚手架生成的各种文件和安装的库,最后总算是把整个框架搭的差不多了。

随之而来的也是各种问题。

Vue 重复加载问题

转ts不容易,一路摸黑排错,以下总结一下问题和解决方式

$attrs is readonly

这个问题我去网上搜的解决方法,90%都说是重新npm installnpm update就好,10%是说Vue重复加载会导致这个问题

Unknown custom element

[Vue warn]: Unknown custom element: <el-row>

这个错误是element-ui的,在模板中使用了<el-row>应该被element-ui解析替换成HTML,但实际没有找到element-ui

还有另一个类似的问题 ↓

内页 class 中无法获取某些值

main.tsVue中定义了一些全局的东西,但在内页无法获取

1
2
3
4
5
6
7
8
// 全局常量
Vue.appSetup = Vue.prototype.$appSetup = {
userDataDir: path.join(process.cwd(), 'userData')
}

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

但在内页的calss里无效,内页:

1
2
3
4
5
6
7
8
9
import { Component, Vue } from 'vue-property-decorator'

@Component
export default class Index extends Vue {
created() {
console.log(Vue.appSetup) // undefined
console.log(this.$appSetup) // undefined
}
}

Vue 重复加载问题的解决

其实总结以上原因,大致能猜出main.ts中的Vue和内页class中的Vue不是同一个东西

自己尝试的方式

1
2
import Vue from 'vue'
import { Component } from 'vue-property-decorator'

就是Vue导出来源跟main.ts相同,Component装饰器从vue-property-decorator导出

但这种方式依然无济于事,于是各种搜索

发现可用方案

最后终于发现了一篇文章:《关于electron-vue用typescript改写遇到的几个坑》
引用:

在我项目中的App.vue就加入了vue-property-decorator,它将组件改写成类,好了这里第一个大坑,就是 组件必须继承Vue,这个Vue又是从vue-property-decorator中引入的,我们必须在webpack.renderer.config.js配置一个东西:在whiteListedModules中多加一个vue-property-decorator的选项,否则每个组件都从vue-property-decorator引入一个Vue类,main.ts文件又import Vue from ‘vue’,之后webpack打包运行时会有多个vue的实例,Vue别名指向就不明确,vue组件无法识别已经注册好的<router-view ;之后倘若要引入vuex做状态化管理,也无法获取vuex里store存储的数据;vuex的数据改变了,template却无法渲染新的数据显示,而是会报错:”[Vue warn]: $attrs is readonly…found in …”

原来是需要在webpack配置文件中配置whiteListedModules,这样在打包的时候就不会出现多个Vue实例了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let whiteListedModules = [
'vue',
'vue-property-decorator',
'vue-class-component'
]

let rendererConfig = {
// ...
externals: [
...Object.keys(dependencies || {}).filter(
d => !whiteListedModules.includes(d)
)
]
// ...
}

但我只添加vue-property-decorator依然不行,应该是因为vue-property-decoratorComponentvue-class-component中的,不把vue-class-component写进去应该也会创建多个Vue实例,所以最后就加了个vue-class-component发现没问题。

types 的问题

typescript有类型检查,比如Vue.appSetup是我自己添加的属性,代码虽然能执行,但语法检查插件和编辑器是会报错的。

所以就需要各种@types,有些主流的软件包会自带类型声明,或者有些可以额外下载,比如@types/node@types/node-fetch@types/request等。

但如果是自定义的,需要自己写,vue的官方文档中也说了这个:《TypeScript 支持 #增强类型以配合插件使用》

types 问题的解决

这里我需要给Vue加一个静态属性appSetup和一个实例属性$appSetup,你需要在项目目录下建一个文件,比如我是在src下建了个文件夹types

其实你不建文件夹也无所谓,因为tsconfig.jsoninclude已经包含了src目录下的所有.ts等文件。

我在types文件夹中创建了一个类型声明文件global.d.ts,文件名可以随便起。

照着官方文档,写了

1
2
3
4
5
6
7
8
9
10
11
// 模块补充
declare module 'vue/types/vue' {
// 全局
interface VueConstructor {
appSetup: any
}
// 实例
interface Vue {
$appSetup: any
}
}

于是编辑器就不会报错了,也有代码提示了。

这个地方卡了好久,最后发现自己sb了,没看到官方文档的全局、实例是分开的。我当时只写了其中一个,所以还是各种报错。

补充知识

如果typescript中用变量作为对象的下标,会报any类型的问题。

比如有个网络请求,回调返回json

1
2
3
req(/* 略 */, function(json) { // 绑定元素“json”隐式具有“any”类型
console.log(json)
})

此时你需要声明类型。

1
2
3
req(/* 略 */, function(json: {[key: string]: any}) {
console.log(json)
})

即可解决。