前言
都快 2021 年了,TS 早就不是什么新技术,而是前端需要掌握的硬核知识,所以今天就从一些库入手开始探究 TS 的应用以及一些小技巧
看本篇文章之前需要的一些基础知识:
-
JS 基础知识
-
熟悉 react-router
-
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 的泛型参数呢?
显然是要使用 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 的类型声明中的小技巧
小技巧
-
in keyof
Params extends { [K in keyof Params]?: string } = {}
这两个经常成对出现,它能够把类型中的 key 提取出来,in 则类似于遍历所有 key 设置到类型中去,上面这段代码将 Params 所有的 value 设置为可选的 string 类型
-
Omit
Omit<P, keyof RouteComponentProps<any>> & WithRouterProps<C>
Omit 是 TS 内置的类型,它的作用就是剔除掉某个属性与值,第二个参数是要剔除的属性值,详细的讲解可以参考这篇文章:
mariusschulz.com/blog/the-om…