Vue 2.x 如何接入
- vue-property-decorator
- vue-class-component
- vuex-class
依赖
npm install —save-dev typescript webpack webpack-cli ts-loader css-loader vue vue-loader vue-template-compiler
webpack.config.js
var path = require('path')
var webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
// other vue-loader options go here
}
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
resolve: {
extensions: ['.ts', '.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map',
plugins: [
// make sure to include the plugin for the magic
new VueLoaderPlugin()
]
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
tsconfig.json
使用 tsc —init 生成 tsconfig.json
{
"compilerOptions": {
"outDir": "./built/",
"sourceMap": true,
"strict": true,
"noImplicitReturns": true,
"module": "es2015",
"moduleResolution": "node",
"target": "es5"
},
"include": [
"./src/**/*"
]
}
helloComponent.ts
// src/components/Hello.ts
import Vue from "vue";
export default Vue.extend({
template: `
<div>
<div>Hello {{name}}{{exclamationMarks}}</div>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
`,
props: ['name', 'initialEnthusiasm'],
data() {
return {
enthusiasm: this.initialEnthusiasm,
}
},
methods: {
increment() { this.enthusiasm++; },
decrement() {
if (this.enthusiasm > 1) {
this.enthusiasm--;
}
},
},
computed: {
exclamationMarks(): string {
return Array(this.enthusiasm + 1).join('!');
}
}
});
使用单文件组件
告诉 TypeScript .vue文件在导入时是什么样子,新增 vue-shim.d.ts 文件。
我们不需要将该文件导入到任何地方。
它自动包含在 TypeScript 中,并告诉它导入的以 . Vue 结尾的任何内容与 Vue 构造函数本身具有相同的形状。
// src/vue-shims.d.ts
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
第一个单文件组件
<!-- src/components/Hello.vue -->
<template>
<div>
<div class="greeting">Hello {{name}}{{exclamationMarks}}</div>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
props: ['name', 'initialEnthusiasm'],
data() {
return {
enthusiasm: this.initialEnthusiasm,
}
},
methods: {
increment() { this.enthusiasm++; },
decrement() {
if (this.enthusiasm > 1) {
this.enthusiasm--;
}
},
},
computed: {
exclamationMarks(): string {
return Array(this.enthusiasm + 1).join('!');
}
}
});
</script>
<style>
.greeting {
font-size: 20px;
}
</style>
使用装饰器
组件也可以使用装饰器来定义。
在两个附加包的帮助下(vue-class-component和vue-property-decorator),我们的组件可以按照以下方式重写:
import { Vue, Component, Prop } from "vue-property-decorator";
@Component
export default class HelloDecorator extends Vue {
@Prop() name!: string;
@Prop() initialEnthusiasm!: number;
enthusiasm = this.initialEnthusiasm;
increment() {
this.enthusiasm++;
}
decrement() {
if (this.enthusiasm > 1) {
this.enthusiasm--;
}
}
get exclamationMarks(): string {
return Array(this.enthusiasm + 1).join('!');
}
}
vue-class-component
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>
vue-property-decorator
vue-property-decorator 是在 vue-class-component 基础上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器
- @Emit 指定事件 emit,可以使用此修饰符,也可以直接使用 this.$emit()
- @Inject 指定依赖注入)
- @Mixins mixin 注入
- @Model 指定 model
- @Prop 指定 Prop
- @Provide 指定 Provide
- @Watch 指定 Watch
- @Component export from vue-class-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
}
}
}
文件样式指南
api 顺序
vue 文件中 TS 上下文顺序
- data
- @Prop
- @State
- @Getter
- @Action
- @Mutation
- @Watch
- 生命周期钩子
- beforeCreate(按照生命周期钩子从上到下)
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- activated
- deactivated
- beforeDestroy
- destroyed
- errorCaptured(最后一个生命周期钩子)
vuex
store 下面一个文件夹对应一个模块,每一个模块都有一个 interface 进行接口管理
Vue 大型 TS 项目实践经验
1 业务层面千万做好类型检测或者枚举定义,这样不仅便利了开发,还能在出了问题的时候迅速定位 2 如果定义了 .d.ts 文件,请重新启动服务让你的服务能够识别你定义的模块,并重启 vscode 让编辑器也能够识别 3 设置好你的 tsconfig ,比如记得把 strictPropertyInitialization 设为 false,不然你定义一个变量就必须给它一个初始值 4 跨模块使用 vuex,请直接使用 rootGetters
拓展vue 的声明 增强类型 支持this.$http这种写法
什么时候建议用 TypeScript
- 大型项目
- 项目生命周期长
- 团队成员接受程度高
- 项目是框架、库