优秀的编程知识分享平台

网站首页 > 技术文章 正文

Vue & Vuex in Typescript - 使用指南

nanyue 2024-09-07 16:40:53 技术文章 7 ℃


概述:最近在学习 vue & typeScript,发现有有一些写法和方法需要去梳理和总结。主要是参考 参考文章 vue-property-decorator、vue-class-component,所以选择一些关键点用于沉淀和思考。

publish:2019-09-15

可与 React & Redux in TypeScript - 静态类型指南 对比着看

目录

  • 简介、环境配置(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 的引入见

vuex-module-decorators 官方文档

vue-class ReadMe。

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 产生奇妙的化学反应吧。

Tags:

最近发表
标签列表