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

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

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

前言

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

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

查看本文配套视频教程vue

ts能为咱们带来什么

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

  • 增长项目扩展性和维护性,尤为适合开源项目
  • vue3对ts的支持比之前更好了
  • ts能够增量引入,不须要梭哈

image-20210531151342630

可能的额外负担

固然也有负面影响:react

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

image.png

整合vue3+ts

下面咱们就整合ts到vue3中,主要有如下两种环境:面试

image.png

vue cli环境

新建立项目:vuex

vue create my-project
复制代码

image-20210510114727426

已存在项目:typescript

vue add typescript
复制代码

Vite环境

新建立项目:npm

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”得到文中完整代码

查看本文配套视频教程

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

相关文章
相关标签/搜索