vue2中使用ts,更健全的类型推断

vue中的使用场景

tsconfig注意事项

注意你需要引入 strict: true (或者至少 noImplicitThis: true,这是 strict 模式的一部分) 以利用组件方法中 this 的类型检查,否则它会始终被看作 any 类型。

需要注意以下的配置项,不然类型推断不能正常运作

// 启用所有严格类型检查选项
"strict": true,
// 禁止this 值为 any
"noImplicitThis": true,

Vue.extend()的作用

vue.extend方法已经声明了参数类型,所以vue.extend可以为你提供类型推断。

举个例子,看下面一段代码:

type vueExtend = (option: { name: string, $store: object, $route: object }) => void;
const vueInstance: vueExtend = ({ $ }) {};

vue原型混入属性或方法

平时我们写的 $store 或 $route 等方法插件向vue原型中添加了原型属性$store和$route。

那如何在ts中的vue原型中混入这些属性或方法呢?

从vuex导出的ts声明文件,我们可以看到下代码:

import Vue, { ComponentOptions } from "vue";
import { Store } from "./index";

declare module "vue/types/options" {
  interface ComponentOptions {
    store?: Store;
  }
}

declare module "vue/types/vue" {
  interface Vue {
    $store: Store;
  }
}

所以如果我们想混入$api的话也是需要这么做的。

先去新建一个global.d.ts,使用以下的代码

declare module "vue/types/vue" {
  interface Vue {
    $api: {
      queryHotel: (options: any) => Promise;
    };
  }
 
// 使用
this.$api.queryHotel(payload).then(() => {});

推断vue组件实例类型

为什么需要推断vue实例类型,这是有使用场景的。

比如我们需要拿到子组件的引用,并调用子组件的方法的时候。

假设此时子组件为SpaceDrawer, ref为spaceDrawer。

// 同步移除物联树的节点
(this.$refs.spaceDrawer as any).removeCheckNode(delRoomNode[0]);

获取SpaceDrawer实例类型
type SpaceDrawerRef = InstanceType;

// 同步移除物联树的节点
(this.$refs.spaceDrawer as SpaceDrawerRef).removeCheckNode(delRoomNode[0]);

采用高级类型InstanceType可取得vue组件实例SpaceDrawer。

vue中重新声明data中属性的类型

vue中的类型得益于vue.extend能自动推断类型,但是有时候你需要自定义类型。如下例子

export default Vue.extend({
  data() {
      return {
        obj: {
          name: '',
          value: ''
        }
      }
  },
  methods: {
    handleObj(type: string) {
     // 这样是可以的
      this.obj.name = 'xxx';
     // 这样是不行的,(比如type就是name)
      this.obj[type] = 'xxx' 
    }
  }
})

如上两种情况,第二种显然需要重新定义类型,因为这需要用可索引类型了。

我们可以对data内的字段重新定义类型,也就是类型断言。
如下:

type ObjType = { [key: string]: string; name: string; value: string }
export default Vue.extend({
  data() {
      return {
        obj: {
          name: '',
          value: ''
        } as ObjType,
      }
  },
  methods: {
    handleObj(type: string) {
     // 这样是可以的
      this.obj[type] = 'xxx' 
    }
  }
})

vue中重新声明props的类型

跟data是差不多,只是vue的类型系统已经为我们提供了断言的类型 - PropType

用法如下:

export default Vue.extend({
    props: {
        spaceModel: {
            type: Number as PropType,
            default: SpaceModelEnum.All,
        },
        spaceOperator: {
            type: String as PropType,
            default: SpaceOperator.CREATE,
        }
    },
}

computed中get,set使类型推断异常

这里需要注意一下,有时候会遇到类型推断不起作用了,并且computed中存在异常。
这是因为computed的异常阻断了类型的进一步推断。

如下情况,此时已经不能推断出prop里面的属性:

解决方法就是,对这种写法的类型申明补全。

让mixins得到类型推断

让mixins得到类型推断

待更新 ...

枚举让代码更清晰

相信大家都知道,枚举有两种。

  • 一种是常量枚举
  • 一种是普通枚举
// 1、常量枚举
const enum State { SUCCESS = 0, FAIL }
// 2、枚举
enum State { SUCCESS = 0, FAIL }

那什么时候用常量枚举,什么时候用普通枚举。

你只需要知道常量枚举在js运行时是ts编译后的值。

什么意思?

如果你把常量枚举当成一个常量是不行的,因为它并不存在。

enum SpaceModelEnum {
    All = 0,
    ONLY_BUILDING_ROOM,
    ONLY_FLOOR_ROOM,
    ONLY_ROOM,
}
const enum SpaceType {
    BUILDING = 0,
    FLOOR,
    ROOM,
}

export default Vue.extend({
    name: 'SpaceCreate',
    data() {
        return {
            // 可以 
            SpaceModelEnum,
            // 不行
            SpaceType
        };
    },
})

枚举用处很大,可以大大提高代码的可读性,当然用map也是能接受的。

switch (level) {
    case SpaceType.BUILDING:
        break;
    case SpaceType.FLOOR:
        parentNode = spaceDestruct.genBuildingNode();
        spaceTree.push(parentNode);
        break;
    case SpaceType.ROOM:
        const buildingNode = spaceDestruct.genAll();
        parentNode = buildingNode.children[0];
        spaceTree.push(buildingNode);
    default:
        break;
}

这里可以很清楚的知道level可能是building,floor,room类型。而不是0,1,2这种可读性很低的代码。

ts常规用法

常用的ts高级类型

  • Record - 经常用的,便捷定义可索引类型,Record
  • ReturnType - 获得函数的返回类型
  • ConstructParameter - 获取构造函数参数类型
  • typeof - 取得对象的类型等
  • keyof - 获取接口类型的key,组合为联合类型
  • in - 枚举联合类型

一个优秀的ts高级类型开源库:
ts-toolbelt

一篇关于高级类型的博文:
【速查手册】TypeScript 高级类型 cheat sheet

使用泛型动态推断api接口返回的数据类型

不同的请求接口,返回不同的数据类型,那么我们如何能够定义这种类型呢?我们很容易想到用泛型。

但是每个接口都会返回固定的格式:

{
    code: number;
    msg: string | null;
    result: any;
}

那么我们第一步需要把通用的格式抽出来,接下来result就是通过泛型去动态定义了。
这是我封装后的例子。

type ResType = {
    code: number;
    msg: string | null;
    result: T;
};

api(urlProp: string, params: Record, config = {}): Promise> {
    const { get: urlGet, post: urlPost } = this.urlObject;
    let url = urlGet[urlProp];


    if (url && url.length > 0) {
        return ajax.get(url, params, config);
    }


    url = urlPost[urlProp];
    return ajax.post(url, params, config);
}

const ajax = {
    post(
        url: string,
        params: Record,
        config: AxiosRequestConfig = {}
    ): Promise> {
        return new Promise((resolve, reject) => {
            axios.post(url, params, config).then(
                (res: AxiosResponse) => {
                    const { data = {} } = res;
                    resolve(data);
                },
                (err) => {
                    reject(err);
                }
            );
        });
    },
};

如何使用呢?

type HotelSpaceNode = {
    roomId: string;
    name: string;
    parentId: string;
}
api('http://debug.aqara.com/getHotelSpaceNode', {}).then(({ code, result }) => {
      if (code === 0) {
          // 此时result的类型就是HotelSpaceNode[]
          this.spaceTreeData = result;
      }
  });

类型断言(强制类型转换)

这里有个例子。
比如我定义了空对象,那么我需要往对象中动态加入属性,但是这个对象依赖于某个接口。

怎么做呢?

type mapNodeType = {
    spaceName: string;
    spaceId: string;
    spaceType: number;
    parentId: string;
}

const mapNode = {};
const {
    level,
    childrenList,
    roomId,
    name,
    parentId,
    state,
    spaceState
} = currentNode;


mapNode.spaceId = roomId;
mapNode.spaceName = name;
mapNode.spaceType = level;
mapNode.parentId = parentId;

当然上面的例子用类型声明更好一点,不管怎么样,都能提供类型推断和更高的代码可读性。

还有一种是需要赋予默认值的时候。

genFloorNode(spaceNode: FloorSpaceNode = {}) {
    const theSpaceNode = spaceNode;
    if (_.isEmpty(spaceNode)) {
        theSpaceNode.spaceType = SpaceType.FLOOR;
    }
    return new FloorSpaceNode(theSpaceNode);
}

你可能感兴趣的:(vue.js,typescript,前端)