还在迟疑是否上ts?先上车再说!vue3+ts开发初体验

还在迟疑是否上ts?先上车再说!vue3+ts开发初体验
公众号「村长学前端」 @ B站「前端杨村长」

老实说我对ts写业务一直不太感冒,总感觉影响效率。但事有两面,比如以前写angular,被谷歌强迫一下,写一段时间之后感觉也不赖,有时还有一种莫名的优越感,ts+rxjs+decorator各种高大上,咱ng就是nb。像不像小媳妇儿嫁给第一次见面的小少爷,过着过着觉得还挺滋润,整天跑出去炫耀~

小伙伴们管我要vue3+ts项目很久了,我就纳闷了,写个vue3,为啥非要用ts哪?他们说ts写多有牌面呀,面试说起来都觉得高人一截。这说明一个卷的现状,现在前端JD里面越来越多提到ts要求,这让应聘者觉得ts是个加分项,所以就不得不学会。

前言

文本主要结合案例体验一下vue3+ts开发的实际效果。到底适不适合你和你的项目,还得根据各位看官自己掌握程度和项目实际情况综合判断。 本文主要涉及以下知识点:

  • ts能为我们带来什么
  • 可能的额外负担
  • 整合ts+vue3的两种姿势
  • ts编写vue组件的两种姿势
    • 传统选项方式
    • setup方式
  • ts编写vuex的两种姿势
    • 传统$store方式
    • setup方式

查看本文配套视频教程

ts能为我们带来什么

以下结论来自官方团队视频教程

  • 增加项目扩展性和维护性,尤其适合开源项目
  • vue3对ts的支持比以前更好了
  • ts可以增量引入,不需要梭哈

image-20210531151342630

可能的额外负担

当然也有负面影响:

  • 额外学习成本
  • 影响开发效率
  • 排期压力
  • 并非万能药丸
  • anyscript

image.png

整合vue3+ts

下面我们就整合ts到vue3中,主要有以下两种环境:

image.png

vue cli环境

新创建项目:

vue create my-project
复制代码

image-20210510114727426

已存在项目:

vue add typescript
复制代码

Vite环境

新创建项目:

npm init @vitejs/app
复制代码

image-20210531151929571

已存在项目,自己手撸~

使用TS编写Vue组件

编写一个组件常见任务:

  • 注册组件
  • data类型定义
  • props类型定义
  • methodscomputed类型支持
  • composition api中的类型支持

组件定义

使用<script lang="ts">defineComponent定义一个组件。

<template>
  <div>{{ counter }}</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  data() {
    return {
      counter: 0
    }
  },
});
</script>
复制代码

工具支持:Volar

data类型定义

data中的对象类型利用类型断言确定数据类型。

类型定义

首先定义一个类型,types.d.ts:

export type Todo = {
  id: number;
  name: string;
  completed: boolean;
}
复制代码

组件定义

定义一个组件Comp.vue,并引入这个类型:

<script lang="ts">
import type { Todo } from "../types";

export default defineComponent({
  data() {
    return {
      // 利用类型断言
      items: [] as Todo[]
    }
  },
  created() {
    // 此处会获得类型支持                          
    this.items.push({
      id: 1,
      name: 'vue3',
      completed: false
    })
  }
});
</script>
复制代码

模板定义

<template>
  <!-- 此处会获得类型支持 -->
  <div v-for="item in items" :key="item.id" class="todo-item">
    {{ item.name }}
  </div>
</template>
复制代码

props类型定义

props中的对象类型利用类型断言和PropType<T>

类型定义

export type TitleInfo = {
  value: string;
  color: string;
}
复制代码

属性定义

<script lang="ts">
// 属性类型需要PropType支持
import { PropType } from "vue";
import type { TitleInfo } from "../types"

export default defineComponent({
  props: {
    // 利用泛型类型约束对象类型
    titleInfo: Object as PropType<TitleInfo>,
  },
})
</script>
复制代码

模板中使用

<h1 :style="{ backgroundColor: titleInfo?.color }">{{ titleInfo?.value }}</h1>
复制代码
<Comp :title-info="{ value: '待办事项', color: '#41b883' }"></Comp>
复制代码

computed中的类型

computed要着重标识函数返回类型。

computed: {
  doubleCounter(): number {
    return this.counter * 2
  }
},
复制代码

methods中类型

标识函数形参和返回类型即可。

methods: {
  newTodo(todoName: string): Todo {
    return {
      id: this.items.length + 1,
      name: todoName,
      completed: false,
    };
  },
  addTodo(todo: Todo) {
    this.items.push(todo);
    this.todoName = ''
  },
},
复制代码

Setup Script

setup script方式编写代码会更加简洁。 下面范例代码目标是不改变template结构,重构script部分,以composition api方式实现,我们来看看有什么变化:

数据定义

单值利用泛型方法ref<T>()定义

<script setup lang="ts">
import { defineProps, ref, computed } from "vue";
import type { Todo } from "../types"
  
const items = ref<Todo[]>([]);
items.value.push({
  id: 1,
  name: "vue3",
  completed: false,
});
</script>
复制代码

属性定义

利用defineProps()定义属性,常用手法有两种:

  • 泛型方式:defineProps<{ titleInfo: TitleInfo }>()
  • 参数方式:defineProps({ titleInfo: Object as PropType<TitleInfo> })

计算属性

使用computed()定义,通常类型可以推断出来。

const counter = ref(0);
const doubleCounter = computed(() => counter.value * 2);
复制代码

方法

就是普通函数,定义形参类型和返回值类型即可。

const todoName = ref("");

function newTodo(todoName: string): Todo {
  return {
    id: items.value.length + 1,
    name: todoName,
    completed: false,
  };
}
function addTodo(todo: Todo) {
  items.value.push(todo);
  todoName.value = "";
}
复制代码

使用TS编写Vuex

vuex整体对ts的支持比较蹩脚,这是以前架构问题引起的,我们一起来感受一下:

创建Store

创建store实例,store/index.ts

import { createStore, Store } from "vuex";
import { State } from "./vuex";

const store = createStore({
  state: {
    counter: 0,
  },
});

export default store;
复制代码

引入vue,main.ts

createApp(App).use(store).mount("#app");
复制代码

使用,Comp.vue

import { mapState } from "vuex";

export default defineComponent({
  computed: {
    // 映射state counter
    ...mapState(['counter']),
    doubleCounter(): number {
      // $store已经有类型了
      return this.$store.state.counter * 2;
    },
  },
}
复制代码

$store类型化

我们希望this.$store是有明确类型的, 这需要为组件选项添加一个明确类型的$store属性,可以为ComponentCustomProperties扩展$store属性,store/vuex.d.ts

import { ComponentCustomProperties } from "vue";
import { Store } from "vuex";

// declare your own store states
export interface State {
  counter: number;
}

declare module "@vue/runtime-core" {
  // provide typings for `this.$store`
  interface ComponentCustomProperties {
    $store: Store<State>;
  }
}
复制代码

useStore()类型化

setup中使用useStore时要类型化,共需要三步:

  1. 定义 InjectionKey
  2. app安装时提供InjectionKey
  3. 传递 InjectionKeyuseStore

定义一个InjectionKey,约束StoreState类型,store/index.ts

import { InjectionKey } from "vue";
import { State } from "./vuex";

// define injection key
export const key: InjectionKey<Store<State>> = Symbol();
复制代码

main.ts中作为参数2传入vuex插件

import { key } from "./store";
// 作为参数2传入key
createApp(App).use(store, key).mount("#app");
复制代码

使用时,store就可以有明确类型了,CompSetup.vue

import { useStore } from 'vuex'
import { key } from '../store'

const store = useStore()
const counter = computed(() => store.state.counter);
复制代码

简化使用

封装useStore,避免每次导入key,store/index.ts

import { useStore as baseUseStore } from "vuex";
export function useStore() {
  return baseUseStore(key);
}
复制代码

使用变化,CompSetup.vue

import { useStore } from '../store'
const store = useStore()
复制代码

模块化

创建模块文件,store/modules/todo.ts

import { Module } from "vuex";
import { State } from "../vuex";
import type { Todo } from "../../types";

const initialState = {
  items: [] as Todo[],
};

export type TodoState = typeof initialState;

export default {
  namespaced: true,
  state: initialState,
  mutations: {
    initTodo(state, payload: Todo[]) {
      state.items = payload;
    },
    addTodo(state, payload: Todo) {
      state.items.push(payload)
    }
  },
  actions: {
    initTodo({ commit }) {
      setTimeout(() => {
        commit("initTodo", [
          {
            id: 1,
            name: "vue3",
            completed: false,
          },
        ]);
      }, 1000);
    }
  },
} as Module<TodoState, State>;
复制代码

引入子模块,store/index.ts

import todo from "./modules/todo";
const store = createStore({
  modules: {
    todo,
  },
});
复制代码

状态中添加模块信息,vuex.d.ts

import type { TodoState } from "./modules/todo";

export interface State {
  todo?: TodoState;
}
复制代码

组件中使用,Comp.vue

export default {
  data() {
    return {
      // items: [] as Todo[],
    };
  },
  computed: {
    items(): Todo[] {
      return this.$store.state.todo!.items
    }
  },
  methods: {
    addTodo(todo: Todo) {
      // this.items.push(todo);
      this.$store.commit("todo/addTodo", todo);
      this.todoName = "";
    },
  },
}
复制代码

setup中使用,CompSetup.vue

const items = computed(() => store.state.todo!.items)
store.dispatch('todo/initTodo')

function addTodo(todo: Todo) {
  // items.value.push(todo);
  store.commit('todo/addTodo', todo)
  todoName.value = "";
}
复制代码

总结

vue3+ts体验中规中矩,跟react相比还有差距,尤其vuex这块支持比较弱,仅能做到state类型支持,getters依然any,子模块mutationsactions更是完全抓瞎,这个是以前架构问题,估计以后会有vuex5来解决。

ts显然还是做开源库和框架更好一点,业务编写不是特别必要。

源码

微信搜索并关注“村长学前端”,回复“ts+vue3”获得文中完整代码

查看本文配套视频教程

感谢大家观看,我是村长,一个热爱分享的程序猿。如果觉得本文还不错,记得点赞+收藏哦,说不定哪天就用得上!

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改