前言
本文讲解如何在 Vue 项目中使用 TypeScript 来搭建并开发项目,并在此过程中踩过的坑 。
TypeScript 具有类型系统,且是 JavaScript 的超集,TypeScript 在 2018年 势头迅猛,可谓遍地开花。
Vue3.0 将使用 TS 重写,重写后的 Vue3.0 将更好的支持 TS。2019 年 TypeScript 将会更加普及,能够熟练掌握 TS,并使用 TS 开发过项目,将更加成为前端开发者的优势。
所以笔者就当然也要学这个必备技能,就以 边学边实践 的方式,做个博客项目来玩玩。
此项目是基于 Vue 全家桶 + TypeScript + Element-UI 的技术栈,且已经开源,github 地址 blog-vue-typescript 。
因为之前写了篇纯 Vue 项目搭建的相关文章 基于vue+mint-ui的mobile-h5的项目说明 ,有不少人加我微信,要源码来学习,但是这个是我司的项目,不能提供原码。
所以做一个不是我司的项目,且又是 vue 相关的项目来练手并开源吧。
1. 效果
效果图:
- pc 端
- 移动端
完整效果请看:https://biaochenxuying.cn
2. 功能
已经完成功能
- [x] 登录
- [x] 注册
- [x] 文章列表
- [x] 文章归档
- [x] 标签
- [x] 关于
- [x] 点赞与评论
- [x] 留言
- [x] 历程
- [x] 文章详情(支持代码语法高亮)
- [x] 文章详情目录
- [x] 移动端适配
- [x] github 授权登录
待优化或者实现
- [ ] 使用 vuex-class
- [ ] 更多 TypeScript 的优化技巧
- [ ] 服务器渲染 SSR
3. 前端主要技术
所有技术都是当前最新的。
- vue: ^2.6.6
- typescript : ^3.2.1
- element-ui: 2.6.3
- vue-router : ^3.0.1
- webpack: 4.28.4
- vuex: ^3.0.1
- axios:0.18.0
- redux: 4.0.0
- highlight.js: 9.15.6
- marked:0.6.1
4. 5 分钟上手 TypeScript
如果没有一点点基础,可能没学过 TypeScript 的读者会看不懂往下的内容,所以先学点基础。
TypeScript 的静态类型检查是个好东西,可以避免很多不必要的错误, 不用在调试或者项目上线的时候才发现问题 。
- 类型注解
TypeScript 里的类型注解是一种轻量级的为函数或变量添加约束的方式。变量定义时也要定义他的类型,比如常见的 :
// 布尔值 let isDone: boolean = false; // 相当于 js 的 let isDone = false; // 变量定义之后不可以随便变更它的类型 isDone = true // 不报错 isDone = "我要变为字符串" // 报错
// 数字 let decLiteral: number = 6; // 相当于 js 的 let decLiteral = 6;
// 字符串 let name: string = "bob"; // 相当于 js 的 let name = "bob";
// 数组 // 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组: let list: number[] = [1, 2, 3]; // 相当于 js 的let list = [1, 2, 3]; // 第二种方式是使用数组泛型,Array<元素类型>: let list: Array<number> = [1, 2, 3]; // 相当于 js 的let list = [1, 2, 3];
// 在 TypeScript 中,我们使用接口(Interfaces)来定义 对象 的类型。 interface Person { name: string; age: number; } let tom: Person = { name: 'Tom', age: 25 }; // 以上 对象 的代码相当于 let tom = { name: 'Tom', age: 25 };
// Any 可以随便变更类型 (当这个值可能来自于动态的内容,比如来自用户输入或第三方代码库) let notSure: any = 4; notSure = "我可以随便变更类型" // 不报错 notSure = false; // 不报错
// Void 当一个函数没有返回值时,你通常会见到其返回值类型是 void function warnUser(): void { console.log("This is my warning message"); }
// 方法的参数也要定义类型,不知道就定义为 any function fetch(url: string, id : number, params: any): void { console.log("fetch"); }
以上是最简单的一些知识点,更多知识请看 TypeScript 中文官网
5. 5 分钟上手 Vue +TypeScript
vue-class-component 对 Vue
组件进行了一层封装,让 Vue
组件语法在结合了 TypeScript
语法之后更加扁平化:
<template> <div> <input v-model="msg"> <p>prop: {{propMessage}}</p> <p>msg: {{msg}}</p> <p>helloMsg: {{helloMsg}}</p> <p>computed msg: {{computedMsg}}</p> <button @click="greet">Greet</button> </div> </template> <script> import Vue from 'vue' import Component from 'vue-class-component' @Component({ props: { propMessage: String } }) export default class App extends Vue { // initial data msg = 123 // use prop values for initial data helloMsg = 'Hello, ' + this.propMessage // lifecycle hook mounted () { this.greet() } // computed get computedMsg () { return 'computed ' + this.msg } // method greet () { alert('greeting: ' + this.msg) } } </script>
上面的代码跟下面的代码作用是一样的:
<template> <div> <input v-model="msg"> <p>prop: {{propMessage}}</p> <p>msg: {{msg}}</p> <p>helloMsg: {{helloMsg}}</p> <p>computed msg: {{computedMsg}}</p> <button @click="greet">Greet</button> </div> </template> <script> export default { // 属性 props: { propMessage: { type: String } }, data () { return { msg: 123, helloMsg: 'Hello, ' + this.propMessage } }, // 声明周期钩子 mounted () { this.greet() }, // 计算属性 computed: { computedMsg () { return 'computed ' + this.msg } }, // 方法 methods: { greet () { alert('greeting: ' + this.msg) } }, } </script>
vue-property-decorator 是在 vue-class-component
上增强了更多的结合 Vue
特性的装饰器,新增了这 7 个装饰器:
@Emit
@Inject
@Model
@Prop
@Provide
@Watch
@Component
(从vue-class-component
继承)
在这里列举几个常用的@Prop/@Watch/@Component
, 更多信息,详见官方文档
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator' @Component export class MyComponent extends Vue { @Prop() propA: number = 1 @Prop({ default: 'default value' }) propB: string @Prop([String, Boolean]) propC: string | boolean @Prop({ type: null }) propD: any @Watch('child') onChildChanged(val: string, oldVal: string) { } }
上面的代码相当于:
export default { props: { checked: Boolean, propA: Number, propB: { type: String, default: 'default value' }, propC: [String, Boolean], propD: { type: null } } methods: { onChildChanged(val, oldVal) { } }, watch: { 'child': { handler: 'onChildChanged', immediate: false, deep: false } } }
- vuex-class
vuex-class :在 vue-class-component
写法中 绑定 vuex
。
import Vue from 'vue' import Component from 'vue-class-component' import { State, Getter, Action, Mutation, namespace } from 'vuex-class' const someModule = namespace('path/to/module') @Component export class MyComp extends Vue { @State('foo') stateFoo @State(state => state.bar) stateBar @Getter('foo') getterFoo @Action('foo') actionFoo @Mutation('foo') mutationFoo @someModule.Getter('foo') moduleGetterFoo // If the argument is omitted, use the property name // for each state/getter/action/mutation type @State foo @Getter bar @Action baz @Mutation qux created () { this.stateFoo // -> store.state.foo this.stateBar // -> store.state.bar this.getterFoo // -> store.getters.foo this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true }) this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true }) this.moduleGetterFoo // -> store.getters['path/to/module/foo'] } }