TS是最好的语言(一)_TS

前言

都快 2021 年了,TS 早就不是什么新技术,而是前端需要掌握的硬核知识,所以今天就从一些库入手开始探究 TS 的应用以及一些小技巧

看本篇文章之前需要的一些基础知识:

  1. JS 基础知识
  2. 熟悉 react-router
  3. TS 官方文档至少看一遍,TS 的基本的类型了熟于心

如果没有看过 TS 官方文档的话,可以先去看官方文档,也可以试玩一下 TS 编译器,链接如下:

www.tslang.cn/docs/home.h…

react-router

从古老的用法开始:

以前使用 react-router 中的 history 或者获取路由参数必须先将组件用 withRouter 包装,但是如果我们不声明 App 参数类型那么我们只能从参数中取到路由参数:

// 示例一import React from "react";import { withRouter } from "react-router";const App = withRouter(({ history, match, a }) => {  // ... a取不到,会报错
  // 类型“RouteComponentProps<any, StaticContext, unknown> & { children?: ReactNode; }”上不存在属性“a”。ts(2339)});复制代码

这一堆报错我们很懵先不管他,我们直接声明 App 的参数类型:

// 示例二import React from "react";import { withRouter } from "react-router";interface IProps {}const App = withRouter(({}: IProps) => {  //  类型“({}: IProps) => any”的参数不能赋给类型“ComponentClass<RouteComponentProps<any,
  //  StaticContext, unknown>, any> | FunctionComponent<RouteComponentProps<any, StaticContext,
  //  unknown>> | (FunctionComponent<...> & ComponentClass<...>) | (ComponentClass<...> &
  //  FunctionComponent<...>)”的参数。
  //  不能将类型“({}: IProps) => any”分配给类型“FunctionComponent<RouteComponentProps<any, StaticContext, unknown>>”。
  //  参数“__0”和“props” 的类型不兼容。
  //  类型 "RouteComponentProps<any, StaticContext, unknown> & { children?: ReactNode; }" 中缺少属性 "a",但类型 "IProps" 中需要该属性。});复制代码

即使什么都不写也是一堆报错,????,此时真的不知道怎么办,TS 不知道怎么办的时候就点进去看代码,发现 withRouter 是有两个泛型参数的:

// RouteComponentProps是从react-router中声明的router参数类型,而ComponentType就是我们熟知的class组件或者函数组件,返回值我们先不管export function withRouter<  P extends RouteComponentProps<any>,  C extends React.ComponentType<P>
  component: C & React.ComponentType<P>): React.ComponentClass<  Omit<P, keyof RouteComponentProps<any>> & WithRouterProps<C>
> &  WithRouterStatics<C>;复制代码

根据这个函数的声明是否能正确地写出 withRouter 的泛型参数呢?

TS是最好的语言(一)_TS_02

显然是要使用 RouteComponentProps,它需要传进去我们的路由参数

export interface RouteComponentProps<
  Params extends { [K in keyof Params]?: string } = {},
  C extends StaticContext = StaticContext,
  S = H.LocationState
> {  history: H.History<S>;
  location: H.Location<S>;
  match: match<Params>;
  staticContext?: C;
}复制代码

所以我们声明两个类型,一个是函数本身接受的参数,一个是路由参数

interface IProps {name:string}interface IRouteProps = RouteComponentProps<{appId:string}>const App = withRouter<IRouteProps, FC<IRouteProps & IProps>>(({ name, match }) => {// appId可以获得到代码提示// ...// const { params: { appId } } = match}复制代码

此时感受到了一点:从来没有像写 TS 这样的享受,能够把逻辑写的更加明确,代码更加潇洒,而且还有提示

还有一类组件,它们是路由渲染的组件,本身带有RouteComponentProps,所以不需要使用withRouter包装

const App:FC<IRouteProps & IProps>> = ({history,match,name})=>{
}复制代码

现在 react-router 有了 hooks,我们完全可以直接使用 hooks 来获取 history对象

const App:FC<IProps> = () => {  const history = useHistory();
};复制代码

下面我们着重来看 withRouter 和 RouteComponentProps 的类型声明中的小技巧

小技巧

  1. in keyof

    Params extends { [K in keyof Params]?: string } = {}

    这两个经常成对出现,它能够把类型中的 key 提取出来,in 则类似于遍历所有 key 设置到类型中去,上面这段代码将 Params 所有的 value 设置为可选的 string 类型

  2. Omit

    Omit<P, keyof RouteComponentProps<any>> & WithRouterProps<C>

    Omit 是 TS 内置的类型,它的作用就是剔除掉某个属性与值,第二个参数是要剔除的属性值,详细的讲解可以参考这篇文章:

    mariusschulz.com/blog/the-om…