TS 实战及面试题

777 阅读7分钟

主讲:云隐 & 路白

一、项目需要使用 typeScript

1. 引入和使用

webpack 打包配置

webpack 打包配置 => vue-cli - vue init/create ${myProject} ${template} => 配置 webpack => 编译时

  1. entry - 入口
  2. extentions 加上 ts 文件 area - 用于处理尝试的数据尾缀列表
  3. loaders - ts-loader,增加对于 ts 的处理 => 工程化

TS 配置

tsconfig.json 文件

2. vue / vuex + typescript

<template>
  <div>
    <vueComponent />
  </div>
</template>
// 1. 定义组件的方式上: 形式上 - extends
// const Component = {
	// TS 无法断定内部为 vue 组件,需要额外做申明处理 - Vue.prototype.xxxx
// }

// 申明当前组件模块 Vue.component or Vue.extend
import Vue from 'vue'
const Component = Vue.extend({
	// 类型推断
})

// 2. 全面拥抱面向对象 - 官方 vue-class-component
import Component from 'vue-class-component'

// @Component 本质 —— 类装饰器 => 利用装饰器,统一描述 vue 模板等概念
@Component({
	template: '<vueComponent />'
})
export default class myComponent extends Vue {
  message: string = 'Hello'
  onClick(): void {
  	console.log(this.message);
  }
}

// 3. 申明 - 利用 ts 的额外补充模块 declare => 实现独立模块的声明,使之可以被独立引用
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}
// 补充模块 - 通常使用 .d.ts 来做申明描述
declare module '/typings/vuePlugin.d.ts' {
  interface Vue  {
 		myProps: string
  }
}
// 实例中使用
let vm = new Vue()
console.log(vm.myProps)

// 4. props - 提供 propType 原地声明复合变量
import { propType } from 'vue'

interface customPayload {
  str: string,
  number: number,
  name: string
}

const Component = Vue.extend({
  props: {
    name: String,  // 字符串类型
    // 普通对象类型
    success: {
    	type: String
  	},
  	payload: {
    	type: Object as propType<customPayload>
    },
    callback: {
      type: Function as propType<() => void>
  	}
	}
})

// 5. computed 以及 method 中包含 this 且有 return 的方法 需要声明返回类型
computed: {
  getMsg(): string {
    return this.click() + "!"
  }
}

  methods: {
    click(): string {
    	return this.message + 'zhaowa'
    }
  }

// 6. vuex 的接入 ts - 声明使用
// vuex.d.ts 声明模块 - ComponentCustomProperties
import { ComponentCustomProperties } from 'vue'

declare module '@vue/runtime-core' {
  interface State {
    count: number
  }

  interface ComponentCustomProperties {
    $store: Store<State>
  }
}

// 7. api 形式编码实现 - 官方推荐
// store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'

export interface State {
  count: number
}

export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
  state: {
    count: 0
  }
})
// ##################################
// main.ts
import { createApp } from 'vue'
import { store, key } from './store'

const app = createApp({
	// 传入参数
})

// 利用了provider & inject
app.use(store, key) // => 传入injection Key => vue高级使用里会提到vue.use

app.mount('#app')

// ####################################
// 消费方
import { useStore } from 'vuex'
import { key } from './store'

export default {
  const store = useStore(key)

  store.state.count
}
// 标准接口形式的功能引入,核心利用了 vue 的 provide & inject

// 8. vuex 面向对象 - 使用 vuex-class
import { State, Action, Getter } from "vuex-class"

export default class App extends Vue {
  // 属性装饰器,整合了 store 状态
  @State login: boolean;

  // 事件装饰器,整合了 store 方法
  @Action setInit: () => void;

  get isLogin: boolean;

  mounted() {
  	this.setInit();
  }
}

二、项目实战

初始化配置

  • vue.config.js
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    hot: true,
    open: true,
    host: 'localhost',
  },
});
  • src\router\index.ts
import Vue, { AsyncComponent } from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';

const Main: AsyncComponent = (): any => import('@/views/MainView.vue');

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: '/',
    redirect: '/index'
  },
  {
    path: '/index',
    name: 'main',
    component: Main
  }
];

const router = new VueRouter({
  routes
});

export default router;
  • src\views\MainView.vue
<template>
  <div class="main">
    <div class="header">北京</div>
    <div class="main-content">
      <plates></plates>
      <prodList></prodList>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

import prodList from '@/components/prodList.vue';
import plates from '@/components/plates.vue';

@Component({
  name: 'MainView',
  components: {
    prodList,
    plates,
  },
})
export default class MainView extends Vue {}
</script>

<style lang="less" scoped>
.main {
  .header {
    background-color: #ff1395;
    padding: 20px;
    width: 100%;
  }
  .main-content {
    padding: 10px;
  }
}
</style>
  • src\components\plates.vue
<template>
  <div>plates</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Plates extends Vue {}
</script>

<style scoped lang="less"></style>
  • src\components\prodList.vue
<template>
  <div>prodList</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class ProdList extends Vue {}
</script>

<style scoped lang="less"></style>
  • yarn serve 启动项目

初始化 store

  • src\store\index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import mutations from './mutations';

Vue.use(Vuex);

let state = {};

export default new Vuex.Store({
  state,
  actions,
  mutations,
});
  • src\store\actions.ts
const actions = {};

export default actions;
  • src\store\mutations.ts
const mutations = {};

export default mutations;
  • src\store\types.ts
/**
 * 类型映射
 */
export default {
  SET_PLATES: 'SET_PLATES',
  SET_PROD_LIST: 'SET_PROD_LIST',
};
  • 设置 store 的命名空间
declare namespace StoreState {
  export interface plate {
    // 板块
  }

  export interface prod {
    // 商品
  }
}

修改 store

  • src\store\actions.ts
import { ActionTree } from 'vuex';
import axios from 'axios';

import TYPES from './types';

const actions: ActionTree<any, any> = {
  // 全局初始化
  async initAjax({ dispatch }) {
    // 获取板块
    dispatch('getPlates');
    // 获取列表
    dispatch('getProdList');
  },
  // 获取板块
  async getPlates({ state, commit }) {
    const res: Ajax.AjaxResponse = await axios
      .get('/plates')
      .then(res => res.data)
      .catch((e: string) => console.log(e));

    if (res && res.code === 200) {
      commit(TYPES.SET_PLATES, res.result.list);
    }
  },
  // 获取板块
  async getProdList({ state, commit }) {
    const res: Ajax.AjaxResponse = await axios
      .get('/prodList')
      .then(res => res.data)
      .catch((e: string) => console.log(e));

    if (res && res.code === 200) {
      commit(TYPES.SET_PROD_LIST, res.result.list);
    }
  },
};

export default actions;
  • src\store\mutations.ts
import { MutationTree } from 'vuex';

import TYPES from './types';

const mutations: MutationTree<any> = {
  [TYPES.SET_PLATES](state, plates): void {
    state.plates = plates;
  },
  [TYPES.SET_PROD_LIST](state, prodList): void {
    state.prodList = prodList;
  },
};

export default mutations;
  • src\store\index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import mutations from './mutations';

Vue.use(Vuex);

interface State {
  plates: StoreState.plate[];
  prodList: StoreState.prod[];
}

let state: State = {
  plates: [],
  prodList: [],
};

export default new Vuex.Store({
  state,
  actions,
  mutations,
});

声明 ajax、store

  • src\typings\shims-ajax.d.ts
declare namespace Ajax {
  // 返回
  export interface AjaxResponse {
    data: AjaxResponse;
  }

  // 返回接口数据
  export interface AjaxResponse {
    // 状态码
    code: number;
    // 数据
    result: any;
    // 提示信息
    message?: string;
  }
}
  • src\typings\shims-store.d.ts
declare namespace StoreState {
  export interface plate {
    // 板块
    url: string;
    imgUrl: string;
    name: string;
  }

  export interface prod {
    // 商品
    url: string;
    imgUrl: string;
    prodName: string;
    desc: string;
    price: string;
  }
}

Mock 数据

  • src\assets\mock\index.js
import Mock from 'mockjs';

import plates from './plates';
import prodList from './prod_list';

Mock.mock('/plates', 'get', () => {
  return {
    code: 200,
    result: plates,
  };
});

Mock.mock('/prodList', 'get', () => {
  return {
    code: 200,
    result: prodList,
  };
});
  • src\assets\mock\plates.js
export default {
  list: [
    [
      {
        url: '',
        imgUrl:
          'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABxCAYAAAAea2caAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA39pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYWY3NTgyYS03ZGZhLTljNDgtYmJhYS0yZGMwNjQyNzcwOWQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUM0QkE3NzdEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUM0QkE3NzZEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZiM2YxYzU1LTc4YWUtNWE0ZS1iMzhiLTA1OWY5NmZmNjUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjlhNWE3MDMwLTA2MzUtMTM0YS04MTlkLTVkMzc1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',
        name: '蔬菜',
      },
      {
        url: '',
        imgUrl:
          'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABxCAYAAAAea2caAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA39pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYWY3NTgyYS03ZGZhLTljNDgtYmJhYS0yZGMwNjQyNzcwOWQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUM0QkE3NzdEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUM0QkE3NzZEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZiM2YxYzU1LTc4YWUtNWE0ZS1iMzhiLTA1OWY5NmZmNjUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjlhNWE3MDMwLTA2MzUtMTM0YS04MTlkLTVkMzc1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',
        name: '蛋类',
      },
      {
        url: '',
        imgUrl:
          'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABxCAYAAAAea2caAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA39pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYWY3NTgyYS03ZGZhLTljNDgtYmJhYS0yZGMwNjQyNzcwOWQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUM0QkE3NzdEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUM0QkE3NzZEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZiM2YxYzU1LTc4YWUtNWE0ZS1iMzhiLTA1OWY5NmZmNjUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjlhNWE3MDMwLTA2MzUtMTM0YS04MTlkLTVkMzc1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',
        name: '肉禽',
      },
      {
        url: '',
        imgUrl:
          'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABxCAYAAAAea2caAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA39pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYWY3NTgyYS03ZGZhLTljNDgtYmJhYS0yZGMwNjQyNzcwOWQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUM0QkE3NzdEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUM0QkE3NzZEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZiM2YxYzU1LTc4YWUtNWE0ZS1iMzhiLTA1OWY5NmZmNjUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjlhNWE3MDMwLTA2MzUtMTM0YS04MTlkLTVkMzc1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',
        name: '水果',
      },
      {
        url: '',
        imgUrl:
          'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABxCAYAAAAea2caAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA39pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYWY3NTgyYS03ZGZhLTljNDgtYmJhYS0yZGMwNjQyNzcwOWQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUM0QkE3NzdEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUM0QkE3NzZEMzk3MTFFOTg1OURDMThENEU4QUU4NkQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZiM2YxYzU1LTc4YWUtNWE0ZS1iMzhiLTA1OWY5NmZmNjUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjlhNWE3MDMwLTA2MzUtMTM0YS04MTlkLTVkMzc1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',
        name: '牛奶',
      },
    ],
  ],
};
  • src\assets\mock\prod_list.js
export default {
  list: [
    {
      url: '',
      imgUrl: 'http://www.zhaowaedu.com/static/img/img_heishan.1361392.jpg',
      prodName: 'zhaowa大餐',
      desc: '上海 徐汇',
      price: '200',
    },
    {
      url: '',
      imgUrl: 'http://www.zhaowaedu.com/static/img/img_heishan.1361392.jpg',
      prodName: 'zhaowa大餐',
      desc: '上海 徐汇',
      price: '200',
    },
    {
      url: '',
      imgUrl: 'http://www.zhaowaedu.com/static/img/img_heishan.1361392.jpg',
      prodName: 'zhaowa大餐',
      desc: '上海 徐汇',
      price: '200',
    },
    {
      url: '',
      imgUrl: 'http://www.zhaowaedu.com/static/img/img_heishan.1361392.jpg',
      prodName: 'zhaowa大餐',
      desc: '上海 徐汇',
      price: '200',
    },
  ],
};
  • src\main.ts
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'

import './assets/mock' // 引入 mock 数据

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

开发页面组件

  • src\components\plates.vue
<template>
  <div class="plates">
    <div class="plate-flex" v-for="(page, index) in plates" :key="index">
      <div class="plate" v-for="(module, index) in page" :key="index">
        <img :src="module.imgUrl" />
        <div class="name">{{ module.name }}</div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { State } from 'vuex-class';

@Component({
  name: 'Plates',
})
export default class Plates extends Vue {
  @State('plates') plates!: StoreState.plate[];
}
</script>

<style scoped lang="less">
.plates {
  .plate-flex {
    display: flex;

    .plate {
      flex: 1;
      text-align: center;

      img {
        height: 30px;
        width: 30px;
      }
    }
  }
}
</style>
  • src\components\prodList.vue
<template>
  <div class="prod-list">
    <div class="prod" v-for="(prod, index) in products" :key="index">
      <div class="prod-logo">
        <img :src="prod.imgUrl" alt="img" />
      </div>
      <div class="content">{{ prod.prodName }} - {{ prod.desc }} - {{ prod.price }}</div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { State } from 'vuex-class';

@Component
export default class ProdList extends Vue {
  @State('prodList') prodList!: StoreState.prod[];

  get products(): StoreState.prod[] {
    const prodList = this.prodList;
    return prodList;
  }
}
</script>

<style scoped lang="less">
.prod-list {
  padding-top: 20px;
  .prod {
    padding: 0 10px 10px 10px;
    display: flex;
  }
  .prod-logo {
    position: relative;

    img {
      width: 100px;
      height: 120px;
    }
  }
}
</style>
  • src\views\MainView.vue
<template>
  <div class="main">
    <div class="header">北京</div>
    <div class="main-content">
      <plates></plates>
      <prodList></prodList>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Action } from 'vuex-class';

import prodList from '@/components/prodList.vue';
import plates from '@/components/plates.vue';

@Component({
  name: 'MainView',
  components: {
    prodList,
    plates,
  },
})
export default class MainView extends Vue {
  @Action('initAjax') initAjax!: () => void;

  mounted() {
    this.initAjax();
  }
}
</script>

<style lang="less" scoped>
.main {
  .header {
    background-color: #ff1395;
    padding: 20px;
    width: 100%;
  }
  .main-content {
    padding: 10px;
  }
}
</style>

三、常见面试题

1、你觉得使用 TS 的好处是什么?

  • TypeScriptJavaScript 的加强版,它给 JavaScript 添加了可选的静态类型和基于类的面向对象编程,它拓展了 JavaScript 的语法。所以 TS 的功能比 JS 只多不少;
  • Typescript 是纯面向对象的编程语言,包含类和接口的概念;
  • TS 在开发时就能给出编译错误,而 JS 错误则需要在运行时才能暴露;
  • 作为强类型语言,你可以明确知道数据的类型;
  • TS 中有很多很方便的特性,比如可选链;

2、type 和 interface 的异同

重点:用 interface 描述数据结构,用 type 描述类型。

  • 都可以描述一个对象或者函数
interface User {
  name: string;
  age: number;
}

interface SetUser {
  (name: string, age: number): void;
}

type User = {
  name: string;
  age: number;
};

type SetUser = (name: string, age: number) => void;
  • 都允许拓展(extends

interfacetype 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends typetype 也可以 extends interface

虽然效果差不多,但是两者语法不同。

// interface extends interface
interface Name {
  name: string;
}
interface User extends Name {
  age: number;
}

// type extends type
type Name = {
  name: string;
};
type User = Name & { age: number };

// interface extends type
type Name = {
  name: string;
};
interface User extends Name {
  age: number;
}

// type extends interface
interface Name {
  name: string;
}
type User = Name & {
  age: number;
};
  • 只有 type 可以做的

type 可以声明基本类型别名,联合类型,元组等类型;

// 基本类型别名
type Name = string;

// 联合类型
interface Dog {
  wong();
}
interface Cat {
  miao();
}

type Pet = Dog | Cat;

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet];

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div;

3、如何基于一个已有类型,扩展出一个大部分内容相似,但是有部分区别的类型?

  • Pick:复合类型中,取出几个想要的类型的组合;
  • Omit:以一个类型为基础支持剔除某些属性,然后返回一个新类型;
interface Test {
  name: string;
  sex: number;
  height: string;
}

type Sex = Pick<Test, 'sex'>;
/* 
  type Sex = {
    sex: number;
  }
*/

const a: Sex = { sex: 1 };

type WithoutSex = Omit<Test, 'sex'>;
/* 
  type WithoutSex = {
    name: string;
    height: string;
  }
*/

const b: WithoutSex = { name: '1111', height: 'sss' };

4、什么是泛型,泛型的具体使用?

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。

可以把泛型理解为代表类型的参数。

interface Test<T = any> {
  userId: T;
}

type TestA = Test<string>;
type TestB = Test<number>;

const a: TestA = {
  userId: '111',
};

const b: TestB = {
  userId: 2222,
};

5、实现一个路由跳转,通过 TS 约束参数的 routeHelper

routeHelper 的实现:

/* 
  要求:实现 RouterHelper,提前约束跳转页面的必选参数

  须知:vue-router 里面的 key 和 value 都是 string
*/

import { Dictionary } from 'vue-router/types/router';
/* 
  type Dictionary<T> = { [key: string]: T };
*/
import Router, { RoutePath } from '../router';

export type BaseRouteType = Dictionary<string>;
/* 
  type BaseRouteType = {
    [key: string]: string;
  }
*/

export interface IndexParam extends BaseRouteType {
  name: string;
}

export interface AboutPageParam extends BaseRouteType {
  testName: string;
}

export interface UserPageParam extends BaseRouteType {
  userId: string;
}

// 定义路径和参数的映射
export interface ParamsMap {
  [RoutePath.Index]: IndexParam;
  [RoutePath.About]: AboutPageParam;
  [RoutePath.User]: UserPageParam;
}

export class RouterHelper {
  /* 
    T extends RoutePath:定义一个泛型 T,T 被 RoutePath 所约束

    RoutePath {
      Index = '/',
      About = '/about',
      User = '/user'
    }
  */
  public static replace<T extends RoutePath>(routePath: T, params: ParamsMap[T]) {
    Router.replace({
      path: routePath,
      query: params,
    });
  }

  public static push<T extends RoutePath>(routePath: T, params: ParamsMap[T]) {
    Router.push({
      path: routePath,
      query: params,
    });
  }
}

使用方式:

import { RouterHelper } from '../lib/routerHelper';

RouterHelper.push(RoutePath.About, { testName: '123' });

6、写一个计算时间的装饰器

代码实现:

/* 
  要求:实现倒计时
*/
import { EventEmitter } from 'eventemitter3';

// 定个接口:格式化的数据格式
export interface RemainTimeData {
  /**
   * 天数
   */
  days: number;
  /**
   * 小时数
   */
  hours: number;
  /**
   * 分钟数
   */
  minutes: number;
  /**
   * 秒数
   */
  seconds: number;
  /**
   * 毫秒数
   */
  count: number;
}

export type CountdownCallback = (remainTimeData: RemainTimeData, remainTime: number) => void;

enum CountdownStatus {
  running,
  paused,
  stopped,
}

export enum CountdownEventName {
  START = 'start',
  STOP = 'stop',
  RUNNING = 'running',
}

interface CountdownEventMap {
  [CountdownEventName.START]: []; // 空数组表示回调函数没有参数
  [CountdownEventName.STOP]: [];
  [CountdownEventName.RUNNING]: [RemainTimeData, number];
}

export function fillZero(num: number) {
  return `0${num}`.slice(-2);
}

export class Countdown extends EventEmitter<CountdownEventMap> {
  private static COUNT_IN_MILLISECOND: number = 1 * 100;
  private static SECOND_IN_MILLISECOND: number = 10 * Countdown.COUNT_IN_MILLISECOND;
  private static MINUTE_IN_MILLISECOND: number = 60 * Countdown.SECOND_IN_MILLISECOND;
  private static HOUR_IN_MILLISECOND: number = 60 * Countdown.MINUTE_IN_MILLISECOND;
  private static DAY_IN_MILLISECOND: number = 24 * Countdown.HOUR_IN_MILLISECOND;

  private endTime: number; // 结束时间
  private remainTime: number = 0; // 剩余时间
  private status: CountdownStatus = CountdownStatus.stopped;
  private step: number;

  /**
   * endTime:结束时间
   * step:多少秒跳一次,默认值 1000ms(1e3)
   */
  constructor(endTime: number, step: number = 1e3) {
    super();

    this.endTime = endTime;
    this.step = step;

    this.start();
  }

  public start() {
    this.emit(CountdownEventName.START);
    this.status = CountdownStatus.running;
    this.countdown();
  }

  public stop() {
    this.emit(CountdownEventName.STOP);

    this.status = CountdownStatus.stopped;
  }

  // 倒计时 - 使用了递归
  private countdown() {
    if (this.status !== CountdownStatus.running) {
      return;
    }

    // 边界条件,使用 Math.max 是为了防止出现负数的情况
    // 正常情况下,endTime > now
    // 如果 endTime < now 了,this.endTime - Date.now() 就是负数,所以和 0 取最大值
    this.remainTime = Math.max(this.endTime - Date.now(), 0);

    this.emit(CountdownEventName.RUNNING, this.parseRemainTime(this.remainTime), this.remainTime);

    if (this.remainTime > 0) {
      setTimeout(() => this.countdown(), this.step);
    } else {
      this.stop();
    }
  }

  // 时间戳的换算逻辑
  private parseRemainTime(remainTime: number): RemainTimeData {
    let time = remainTime;

    const days = Math.floor(time / Countdown.DAY_IN_MILLISECOND);
    time = time % Countdown.DAY_IN_MILLISECOND;

    const hours = Math.floor(time / Countdown.HOUR_IN_MILLISECOND);
    time = time % Countdown.HOUR_IN_MILLISECOND;

    const minutes = Math.floor(time / Countdown.MINUTE_IN_MILLISECOND);
    time = time % Countdown.MINUTE_IN_MILLISECOND;

    const seconds = Math.floor(time / Countdown.SECOND_IN_MILLISECOND);
    time = time % Countdown.SECOND_IN_MILLISECOND;

    const count = Math.floor(time / Countdown.COUNT_IN_MILLISECOND);

    return {
      days,
      hours,
      minutes,
      seconds,
      count,
    };
  }
}

使用方式:

import { Countdown, CountdownEventName, fillZero } from '../lib/countdown';

const countdown = new Countdown(Date.now() + 60 * 60 * 1000, 10);
countdown.on(CountdownEventName.RUNNING, remainTimeData => {
  const { hours, minutes, seconds, count } = remainTimeData;
  this.timeDisplay = [hours, minutes, seconds, count].map(fillZero).join(':');
});

7、写一个缓存的装饰器

/**
 * 缓存的装饰器
 */
const cacheMap = new Map();

export function EnableCache(target: any, name: string, descriptor: PropertyDescriptor) {
  const val = descriptor.value;
  descriptor.value = async function (...args: any) {
    const cacheKey = name + JSON.stringify(args);
    if (!cacheMap.get(cacheKey)) {
      const cacheValue = Promise.resolve(val.apply(this, args)).catch(_ => cacheMap.set(cacheKey, null));
      cacheMap.set(cacheKey, cacheValue);
    }

    console.log('cacheMap =========== ', cacheMap);
    return cacheMap.get(cacheKey);
  };
  return descriptor;
}

8、其他装饰器

/**
 * 方法执行之前执行的方法
 */
export function before(beforeFn: any) {
  return function (target: any, name: any, descriptor: any) {
    const oldValue = descriptor.value;

    descriptor.value = function () {
      beforeFn.apply(this, arguments);
      return oldValue.apply(this, arguments);
    };

    return descriptor;
  };
}

/**
 * 方法执行之后执行的方法
 */
export function after(afterFn: any) {
  return function (target: any, name: any, descriptor: any) {
    const oldValue = descriptor.value;

    descriptor.value = function () {
      const ret = oldValue.apply(this, arguments);
      afterFn.apply(this, arguments);
      return ret;
    };

    return descriptor;
  };
}

/**
 * 方法执行耗时
 */
export function measure(target: any, name: any, descriptor: any) {
  const oldValue = descriptor.value;

  descriptor.value = async function () {
    const start = Date.now();
    console.log('start ========= ', start);
    const ret = await oldValue.apply(this, arguments);
    console.log(`${name}执行耗时 ${Date.now() - start}ms`);
    return ret;
  };

  return descriptor;
}

四、tsconfig.json 配置项

{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}