概述:最近在学习 vue & typeScript,发现有有一些写法和方法需要去梳理和总结。主要是参考 参考文章 vue-property-decorator、vue-class-component,所以选择一些关键点用于沉淀和思考。
publish:2019-09-15
目录
- 简介、环境配置(vue-cli)
- Vue - 组件模式(vue-property-decorator 的使用)
- Vuex - 状态管理(vuex-module-decorators、vuex-class 的使用)
简介、环境配置
vue-cli 中包含着 typescript 选项,只需要选择即可
vue create repo
# 手动配置的时候需要选择 TypeScript
Check the features needed for your project:
◉ Babel
◉ TypeScript
◯ Progressive Web App (PWA) Support
◯ Router
◉ Vuex
❯◉ CSS Pre-processors
◉ Linter / Formatter
◯ Unit Testing
◯ E2E Testing
vuex-module-decorators、vuex-class 的引入见
Vue - 组件模式
用 Typescript 来开发 Vue,主要是采用 vue-property-decorator,来赋予组件 Class 各种能力,具体有
-
@Component
主要是用来实现 class-style 形式的 Vue 组件import { Component, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue { public render() { return (<h5>Hello world</h5>); } }
与之前的
.vue
不同的是,利用render
来渲染组件。此时的class Hello world
中实现的组件的状态与逻辑-
data
组件状态,在 vue 中很关键的data
就是作为class
的属性来实现的import { Component, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue { private message: string = 'world'; public render() { const { message }: HelloWorld = this; return (<h5>Hello {message}</h5>); } }
使用
privat message
作为组件的状态; -
computed
组件的计算属性,在 class-style 形式的 vue 组件中,常采用get()
操作符来实现@Component export default class HelloWorld extends Vue { private message: string = 'world'; get subMessage(): string { return `boy ${this.message}`; } public render() { const { message, subMessage }: HelloWorld = this; return ( <div> <h5>Hello {message}</h5> <span>{subMessage}</span> </div> ); } }
-
methods
与data
类似,也是作为 class 的属性来实现的,常用于实现页面中的操作事件,例如点击、选择等@Component export default class HelloWorld extends Vue { private message: string = 'world'; get subMessage(): string { return `boy ${this.message}`; } public setMessage(msg: string): void { this.message = msg; } public render() { const { message, subMessage, setMessage }: HelloWorld = this; return ( <div> <h5>Hello {message}</h5> <span>{subMessage}</span> <button onClick={() => setMessage('person')}> Click </button> </div> ); } }
-
-
@Prop
作为单向数据流实现的关键一环,也是组件之间传递数据的关键import { Component, Prop, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue { @Prop({ default: 'Click' }) public readonly porpA!: string; public render() { const { porpA }: HelloWorld = this; return ( <div> <h5>{porpA}</h5> </div> ); } }
可以在
@Prop
的参数中配置相关options
,并且 vue-property-decorator 还提供了@PropSync
,它将props
与computed
复合使用,具体用法// 父组件 parent <Child name="hello world" @update:name="handleUpdate" /> // 子组件 child @Component export default class Child extends Vue { @PropSync('name', { type: String }) syncedName!: string public setSyncedName() { this.syncedName = 'delete'; } }
等同于之前单文件组件模式下的
export default { props: { name: { type: String } }, computed: { syncedName: { get() { return this.name }, set(value) { this.$emit('update:name', value) } } } }
-
@Emit
等同于this.$emit
,常用于触发自定义事件// tsx @Emit() addToCount(n: number) { this.count += n } // vue methods: { addToCount(n) { this.count += n this.$emit('add-to-count', n) } }
-
@Watch
指的是侦听属性,也就是当侦听的data
或者props
变化的时候,会触发对应变化import { Component, Watch, Vue } from 'vue-property-decorator'; @Component export default class HelloWorld extends Vue { private message: string = 'world'; @Watch('message', { immediate: true, deep: true }) public onMsgChange(val: string, oldVal: string) { console.log('onMsgChange', val, oldVal); } public render() { const { message }: HelloWorld = this; return ( <div> <h5>Hello {message}</h5> <input type='text' v-model={this.message}/> </div> ); } }
@Watch
的第一个参数代表监听的path
,第二个参数则是代表{ immediate: true, deep: true } //是否立即触发、深度侦听
-
@Provide
与@inject
这两个常用在组件开发上,简单的来说就是在父组件中通过 provider 来提供变量,然后在子组件中通过 inject 来注入变量,在 class-style 中import { Component, Inject, Provide, Vue } from 'vue-property-decorator' const symbol = Symbol('baz') @Component export class MyComponent extends Vue { @Inject() readonly foo!: string @Provide() foo = 'foo' }
注意这种共享的
data
是非响应式的
-
@ProvideReactive
以及@InjectReactive
与上面的用法差不多,但是共享的值,如果在父组件中发生了变化,那么在子组件中会捕捉到。 -
@ref
常用于直接访问组件import { Vue, Component, Ref } from 'vue-property-decorator' import AnotherComponent from '@/path/to/another-component.vue' @Component export default class YourComponent extends Vue { @Ref() readonly anotherComponent!: AnotherComponent @Ref('aButton') readonly button!: HTMLButtonElement } # 等同于 export default { computed() { anotherComponent: { cache: false, get() { return this.$refs.anotherComponent as AnotherComponent } }, button: { cache: false, get() { return this.$refs.aButton as HTMLButtonElement } } } }
-
@Model
常用于实现v-model
这样的指令,在之前的 vue 组件中export default { model: { prop: 'checked', event: 'change' }, props: { checked: { type: Boolean } } }
在 class-style 中,只需要用
@Model
来实现import { Vue, Component, Model } from 'vue-property-decorator' @Component export default class YourComponent extends Vue { @Model('change', { type: Boolean }) readonly checked!: boolean }
Vuex - 状态管理
之前在使用 Vuex 的时候,主要是依赖 state
、getters
、mutations
以及 actions
,并且可以将它们模块化
-
state
状态import { Module, VuexModule } from 'vuex-module-decorators' @Module export default class Vehicle extends VuexModule { wheels = 2 } // 来替代之前的 export default { state: { wheels: 2 } }
-
Getter
与 class-style 中的computed
类似,也是借助getters
来实现的import { Module, VuexModule } from 'vuex-module-decorators' @Module export default class Vehicle extends VuexModule { wheels = 2 get axles() { return this.wheels / 2 } } // 来代替之前的 export default { state: { wheels: 2 }, getters: { axles: (state) => state.wheels / 2 } }
-
Mutation
用来修改state
,与 Vuex in js 一样,只能用来实现同步操作import { Module, VuexModule, Mutation } from 'vuex-module-decorators' @Module export default class Vehicle extends VuexModule { wheels = 2 @Mutation puncture(n: number) { this.wheels = this.wheels - n } } // 来替代之前的 export default { state: { wheels: 2 }, mutations: { puncture: (state, payload) => { state.wheels = state.wheels - payload } } }
-
actions
用来实现异步操作const request = require('request') export default { state: { wheels: 2 }, mutations: { addWheel: (state, payload) => { state.wheels = state.wheels + payload } }, actions: { fetchNewWheels: async (context, payload) => { const wheels = await request.get(payload) context.commit('addWheel', wheels) } } } // 来替代之前的 const request = require('request') export default { state: { wheels: 2 }, mutations: { addWheel: (state, payload) => { state.wheels = state.wheels + payload } }, actions: { fetchNewWheels: async (context, payload) => { const wheels = await request.get(payload) context.commit('addWheel', wheels) } } }
以上,我们简单实现了一个 Vuex 的 module,那么如何使用呢?将上述代码梳理一下,并配置到 Vuex 中,具体
import { Module, VuexModule } from 'vuex-module-decorators'
@Module({ name: 'Vehicle', namespaced: true, stateFactory: true })
export default class Vehicle extends VuexModule {
public wheels = 2;
get axles() {
return this.wheels / 2;
}
@Mutation
public puncture(n: number): void {
this.wheels = this.wheels - n;
}
}
请注意 Module
的配置,我们需要将需要 namespaced: true
,并且为该空间命名,并且引入到项目中
import Vue from 'vue'
import Vuex from 'vuex'
import Vehicle from './Vehicle'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
Vehicle,
},
})
之后就是使用 vuex-class
,来获取该命名空间下的状态以及方法
import { Component, Vue } from 'vue-property-decorator';
import {
Getter,
Mutation,
namespace,
} from 'vuex-class';
const Vehicle = namespace('Vehicle');
@Component
export default class HelloWorld extends Vue {
// 引入 Vechicle 下的 Getters
@Vehicle.Getter('axles') public axles: number | undefined;
// 引入 Vechicle 下的 puncture
@Vehicle.Mutation('puncture')
public mutationPuncture!: (n: number) => void;
private message: string = 'world';
public render() {
const { message, axles, mutationPuncture }: HelloWorld = this;
return (
<div onClick={ () => mutationPuncture(1) }>
<h5 ref='quickEntry'>Hello {message} { axles }</h5>
</div>
);
}
}
利用 namespace
可以很方便的获取到 Vuex 的 Vehicle 模块,再配合 Getter
、Mutation
就可以完成引入,其他的引入方法与之类似,就不赘述了。
结论
总体来说整个体验的过程很像之前写 Mobx & React in Typescript 的感觉,很相像。在实际项目中使用的话,会有 ts 带来的一些便利,但是也感觉总有些牵强,其他后续 3.0,看看能否与 ts 产生奇妙的化学反应吧。