在 Vue 中使用 Typescript
<h2><span style="color: rgba(216, 14, 75, 1)">前言</span></h2><p> <span style="font-size: 18px">恕我直言,用 Typescript 写 Vue 真的很难受,Vue 对 ts 的支持一般,如非万不得已还是别在 Vue 里边用吧,不过听说 Vue3 会增强对 ts 的支持,正式登场之前还是期待一下吧嘻嘻。</span></p>
<p><span style="font-size: 18px"> 本篇不会过多讲述 ts 语法,着重记录下 在 Vue 中使用 ts 的方法以及踩坑经过。</span></p>
<p><span style="font-size: 18px"> 如果是使用 Vue Cli2 搭建的项目,要注意 webpack 版本可能与 ts-loader 版本不匹配,可以降低 ts-loader 版本到 3.0+ 或者 将 webpack升级到 4.0+ (本篇所用版本 <span style="color: rgba(216, 14, 75, 1)">webpack@3.6.0 + ts-loader@3.5.0</span>)</span></p>
<h2><span style="color: rgba(216, 14, 75, 1)">主要步骤</span></h2>
<p><span style="font-size: 18px"> 1. 先要让 vue 项目可以识别 .ts 文件。安装关键依赖 <span style="color: rgba(216, 14, 75, 1)">npm i typescript ts-loader -D</span></span></p>
<p><span style="font-size: 18px"> 2. 在 webpack.base.config.js 中<span style="color: rgba(216, 14, 75, 1)">添加 ts-loader</span></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">resolve.extensions 里面加上.ts 后缀 之后引入.ts的时候可以不写后缀</span>
<span style="color: rgba(0, 0, 0, 1)">{
test: </span>/\.tsx?$/<span style="color: rgba(0, 0, 0, 1)">,
loader: </span>'ts-loader'<span style="color: rgba(0, 0, 0, 1)">,
exclude: </span>/node_modules/<span style="color: rgba(0, 0, 0, 1)">,
options: {
appendTsSuffixTo: [</span>/\.vue$/], <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">关键</span>
<span style="color: rgba(0, 0, 0, 1)">}
}</span></pre>
</div>
<p> <span style="font-size: 18px">3. 在根目录建<span style="color: rgba(216, 14, 75, 1)">tsconfig.json</span>文件 下面的配置仅供参考</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span>"include"<span style="color: rgba(0, 0, 0, 1)">: [
</span>"src/**/*"<span style="color: rgba(0, 0, 0, 1)">
],
</span>"exclude"<span style="color: rgba(0, 0, 0, 1)">: [
</span>"node_modules"<span style="color: rgba(0, 0, 0, 1)">
],
</span>"compilerOptions"<span style="color: rgba(0, 0, 0, 1)">: {
</span>"allowSyntheticDefaultImports": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"experimentalDecorators": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"allowJs": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"module": "esnext"<span style="color: rgba(0, 0, 0, 1)">,
</span>"target": "es5"<span style="color: rgba(0, 0, 0, 1)">,
</span>"moduleResolution": "node"<span style="color: rgba(0, 0, 0, 1)">,
</span>"isolatedModules": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"lib"<span style="color: rgba(0, 0, 0, 1)">: [
</span>"dom"<span style="color: rgba(0, 0, 0, 1)">,
</span>"es5"<span style="color: rgba(0, 0, 0, 1)">,
</span>"es2015.promise"<span style="color: rgba(0, 0, 0, 1)">
],
</span>"sourceMap": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"pretty": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p> <span style="font-size: 18px">4. 接着需要确保在 vue 中可以正常使用 ts 。安装 vue-class-component(为vue组件提供修饰器) <span style="color: rgba(216, 14, 75, 1)">vue-property-decorator</span>(更多的结合vue特性的修饰器) [ tslint tslint-loader tslint-config-standard(可选 约束.ts/.tsx代码格式)],这样就可以在 vue 文件中使用诸如 @Component、@Prop等装饰器了。注意:.vue 文件中的 script 标签要加上 <span style="color: rgba(216, 14, 75, 1)">lang="ts"</span>。关于 装饰器的使用可以参看下这位大哥的文章:https://segmentfault.com/a/1190000019906321</span></p>
<div class="cnblogs_code">
<pre><template>
<div class="count-down" v-html="countDown(endDate)">
</div>
</template>
<script lang="ts"><span style="color: rgba(0, 0, 0, 1)">
import { Component, Prop, Vue, Emit } from </span>"vue-property-decorator"<span style="color: rgba(0, 0, 0, 1)">
import moment from </span>'moment'<span style="color: rgba(0, 0, 0, 1)">
@Component
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> class CountDown extends Vue {
@Prop() endDate</span>!<span style="color: rgba(0, 0, 0, 1)">: string
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 变量后加!是非空断言</span>
<span style="color: rgba(0, 0, 0, 1)">
now: any </span>=<span style="color: rgba(0, 0, 0, 1)"> moment()
mounted() {
setInterval((): </span><span style="color: rgba(0, 0, 255, 1)">void</span> =><span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.now =<span style="color: rgba(0, 0, 0, 1)"> moment()
},</span>1000<span style="color: rgba(0, 0, 0, 1)">)
}
destroyed() {
}
get countDown(): object{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(endDate: any): string {
let m1: any </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.now
let m2: any </span>=<span style="color: rgba(0, 0, 0, 1)"> moment(endDate)
let du: any </span>= moment.duration(m2 - m1, 'ms'<span style="color: rgba(0, 0, 0, 1)">)
let hours: number </span>= du.get('hours'<span style="color: rgba(0, 0, 0, 1)">)
let mins: number </span>= du.get('minutes'<span style="color: rgba(0, 0, 0, 1)">)
let ss: number </span>= du.get('seconds'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(hours <= 0 && mins <= 0 && ss <= 0<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.$emit('timeout')</span>
<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.timeout()
</span><span style="color: rgba(0, 0, 255, 1)">return</span> "今日已结束"<span style="color: rgba(0, 0, 0, 1)">
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> `${<span style="color: rgba(0, 0, 255, 1)">this</span>.PrefixInteger(hours,2)} <span style="font-size: 16px;">小时</span> ${this.PrefixInteger(mins,2)} <span style="font-size: 16px;">分钟</span><span style="color: #F56C6C;"> ${<span style="color: rgba(0, 0, 255, 1)">this</span>.PrefixInteger(ss,2)} </span><span style="font-size: 16px;">秒</span><span style="color: rgba(0, 0, 0, 1)">`
}
}
}
@Emit()
timeout(){}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">数字前补 0 </span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> num传入的数字,n需要的字符长度</span>
<span style="color: rgba(0, 0, 0, 1)"> PrefixInteger(num: number, n: number): string {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> (Array(n).join('0') + num).slice(-<span style="color: rgba(0, 0, 0, 1)">n)
}
}
</span></script>
<style lang="less" scoped>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...</span>
</style></pre>
</div>
<p> </p>
<p> <span style="font-size: 18px">5. 将main.js 变成 main.ts 并且在 webpack.base.conf.js 修改入口为<span style="color: rgba(216, 14, 75, 1)">main.ts</span>,这一步至关重要。</span></p>
<p><span style="font-size: 18px"> 6. 在 src 目录下新建文件 <span style="color: rgba(216, 14, 75, 1)">shims-vue.d.ts</span> ,告诉 TypeScript *.vue 后缀的文件可以交给 vue 模块来处理,注意 在代码中导入 *.vue 文件的时候,需要写上 .vue 后缀,这里可以参考官网说明:增强类型以配合插件使用</span><br></p>
<div class="cnblogs_code">
<pre>declare module "*.vue"<span style="color: rgba(0, 0, 0, 1)"> {
import Vue from </span>"vue"<span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> Vue;
}</span></pre>
</div>
<h2><span style="color: rgba(216, 14, 75, 1)">踩坑记录</span></h2>
<p> <span style="font-size: 18px">1. 报错如下,报错原因一是没有将入口文件改成 ts,二是 webpack.base.conf.js 中引入 ts-loader 错误,没有加上 options: { appendTsSuffixTo: [/\.vue$/], }</span></p>
<div class="cnblogs_code">
<pre>Module build failed: Error: <span style="color: rgba(216, 14, 75, 1)">Could not find source file: 'XXX/src/App.vue'.</span></pre>
</div>
<p> <span style="font-size: 18px">2. <span style="color: rgba(216, 14, 75, 1)">全局属性报错 如 Vue.prototype.$msg = XXX</span>,需要将全局属性在 .d.ts 文件中声明</span></p>
<div class="cnblogs_code">
<pre>import Vue from "vue"<span style="color: rgba(0, 0, 0, 1)">;
import { AxiosInstance } from </span>"axios"<span style="color: rgba(0, 0, 0, 1)">;
import { ElMessage } from </span>"element-ui/types/message"<span style="color: rgba(0, 0, 0, 1)">;
declare module </span>"*.vue"<span style="color: rgba(0, 0, 0, 1)"> {
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> Vue;
}
declare module </span>'vue/types/vue'<span style="color: rgba(0, 0, 0, 1)"> {
interface Vue {
$http: AxiosInstance,
$message: ElMessage
}
}</span></pre>
</div>
<p> <span style="font-size: 18px">3. 使用上面这种声明全局属性的方式又会带来新的问题,报错<span style="color: rgba(216, 14, 75, 1)"> import App from './App.vue'处,找不到 App.vue 这个模块</span>,虽然不影响编译,但是这红色的波浪线就像老鼠屎,看着那叫一个难受呀。解决方法:将 shims-vue.d.ts 文件一分为二,将全局属性声明和 Vue 的声明分离;在 shims-vue.d.ts 文件同级目录下新建 vue.d.ts(名字不一定叫 vue,如 xxx.d.ts 也可以);关键是要将以下代码放在单独的 .d.ts 文件中</span></p>
<div class="cnblogs_code">
<pre>declare module '*.vue'<span style="color: rgba(0, 0, 0, 1)"> {
import Vue from </span>'vue'<span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> Vue
}</span></pre>
</div><br><br>
来源:https://www.cnblogs.com/eightFlying/p/vue_typescript.html
頁:
[1]