typescript基础系列
本文参考
1.配置项目
• tsconfig.json • ESLint / Prettier • VS Code 扩展和配置
VSCode 扩展和设置
我们添加了 ESLint 和 Prettier ,下一步就是在保存时自动修复/美化我们的代码。 首先,安装 VSCode 的 ESLint extension 和 Prettier extension 。这将使 ESLint 与您的编辑器无缝集成。 接下来,通过将以下内容添加到您的中来更新工作区设置 .vscode/settings.json : { "editor.formatOnSave": true } 保存时, VS Code 会发挥它的魔力并修复您的代码。很棒!
2.组件
因现在流行hooks,所以 react组件以函数组件为主。
import React from 'react'
// 函数声明式写法
function Heading(): React.ReactNode {
return <h1>My Website Heading</h1>
}
// 函数扩展式写法
const OtherHeading: React.FC = () => <h1>My Website Heading</h1>
React.FC 不使用 React.FC-TypeScript + React: Why I don't use React.FC
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
type PropsWithChildren<P> = P & { children?: ReactNode };
FC是FunctionComponent的简写, 这个类型定义了默认的 props(如 children)以及一些静态属性(如 defaultProps)
3.props
可以使用 interface 或 type 来定义 Props 。让我们看一个例子:
import React from 'react'
interface Props {
name: string;
color: string;
}
type OtherProps = {
name: string;
color: string;
}
// Notice here we're using the function declaration with the interface Props
function Heading({ name, color }: Props): React.ReactNode {
return <h1>My Website Heading</h1>
}
//// Notice here we're using the function expression with the type OtherProps
const OtherHeading: React.FC<OtherProps> = ({ name, color }) =><h1>My Website Heading</h1>
关于 interface 或 type ,我们建议遵循 react-typescript-cheatsheet 社区提出的准则:
- 在编写库或第三方环境类型定义时,始终将 interface 用于公共 API 的定义。
- 考虑为你的 React 组件的 State 和 Props 使用 type ,因为它更受约束。”
通常,在 React 和 TypeScript 项目中编写 Props 时,请记住以下几点:
- 始终使用 TSDoc 标记为你的 Props 添加描述性注释 /** comment */。
- 无论你为组件 Props 使用 type 还是 interfaces ,都应始终使用它们。
- 如果 props 是可选的,请适当处理或使用默认值。
interface vs type
- interface 和 type interface 和 type 都可以用来定义一些复杂的类型结构,最很多情况下是通用的,二者的区别在于:
- interface创建了一种新的类型,而 type 仅仅是别名,是一种引用;
- 如果 type 使用了 union operator (|) 操作符,则不能将 type implements 到 class 上;
- 如果 type 使用了 union(|) 操作符 ,则不能被用以 extends interface 5.type 不能像 interface 那样合并,其在作用域内唯一; [1]
默认 props 声明
实际上截止目前对于上面的使用FC类型声明的函数组件并不能完美支持 defaultProps:
import React, { FC } from 'react';
export interface HelloProps {
name: string;
}
export const Hello: FC<HelloProps> = ({ name }) => <div>Hello {name}!</div>;
Hello.defaultProps = { name: 'TJ' };
// ❌! missing name
<Hello />;
const TestFunction: FunctionComponent<Props> = ({ foo = "bar" }) => <div>{foo}</div>
如果一定要用defaultProps,则只能按照下面这样玩
import React, { PropsWithChildren } from 'react';
export interface HelloProps {
name: string;
}
// 直接使用函数参数声明
// PropsWithChildren只是扩展了children, 完全可以自己声明
// type PropsWithChildren<P> = P & {
// children?: ReactNode;
// }
const Hello = ({ name }: PropsWithChildren<HelloProps>) => <div>Hello {name}!</div>;
Hello.defaultProps = { name: 'TJ' };
// ✅ ok!
<Hello />;
这种方式也非常简洁, 只不过 defaultProps 的类型和组件本身的 props 没有关联性, 这会使得 defaultProps 无法得到类型约束, 所以必要时进一步显式声明 defaultProps 的类型:
Hello.defaultProps = { name: 'TJ' } as Partial<HelloProps>;
4.Hooks
使用 Hook 时, TypeScript 类型推断工作得很好。这意味着你没有什么好担心的。举个例子:
// `value` is inferred as a string
// `setValue` is inferred as (newValue: string) => void
const [value, setValue] = useState('')
在极少数情况下,你需要使用一个空值初始化 Hook ,可以使用泛型并传递联合以正确键入 Hook 。查看此实例:
type User = {
email: string;
id: string;
}
// the generic is the < >
// the union is the User | null
// together, TypeScript knows, "Ah, user can be User or null".
const [user, setUser] = useState<User | null>(null);
5.处理表单事件 最常见的情况之一是 onChange 在表单的输入字段上正确键入使用的。这是一个例子:
import React from 'react'
const MyInput = () => {
const [value, setValue] = React.useState('')
// 事件类型是“ChangeEvent”
// 我们将 “HTMLInputElement” 传递给 input
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value)
}
return <input value={value} onChange={onChange} id="input-example"/>
}
- ChangeEvent 类型实现源码 node_modules/@types/react/index.d.ts
- EventTarget 类型实现源码 node_modules/typescript/lib/lib.dom.d.ts 。
6.Promise 类型
在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。 Promise 是一个泛型类型,T 泛型变量用于确定使用 then 方法时接收的第一个回调函数(onfulfilled)的参数类型。 实例:
interface IResponse<T> {
message: string,
result: T,
success: boolean,
}
async function getResponse (): Promise<IResponse<number[]>> {
return {
message: '获取成功',
result: [1, 2, 3],
success: true,
}
}
getResponse()
.then(response => {
console.log(response.result)
})
-
我们首先声明 IResponse 的泛型接口用于定义 response 的类型,通过 T 泛型变量来确定 result 的类型。
-
然后声明了一个 异步函数 getResponse 并且将函数返回值的类型定义为 Promise<IResponse<number[]>> 。
-
最后调用 getResponse 方法会返回一个 promise 类型,通过 then 调用,此时 then 方法接收的第一个回调函数的参数 response 的类型为,{ message: string, result: number[], success: boolean} 。 Promise 实现源码 node_modules/typescript/lib/lib.es5.d.ts。
7. 第三方库
无论是用于诸如 Apollo 之类的 GraphQL 客户端还是用于诸如 React Testing Library 之类的测试,我们经常会在 React 和 TypeScript 项目中使用第三方库。发生这种情况时,你要做的第一件事就是查看这个库是否有一个带有 TypeScript 类型定义 @types 包。你可以通过运行:
#yarn
yarn add @types/<package-name>
#npm
npm install @types/<package-name>
• 第三方的库,如何得到类型支持 我们很难保证,第三方的库都原生支持 TS 类型,在你使用过一段时间 TS 后,你肯定安装过类似 @types/xxx 的类型库,安装类似这样的库,实际上就安装了某个库的描述文件,对于这些第三方库的类型的定义,都存储在DefinitelyTyped 这个仓库中,常用的第三方库在这里面都有定义了。在 TypeSearch 中可以搜索第三方库的类型定义包。
更深理解
当然也许你并不会满足于会用 TS,你还想知道 TS 的工作原理是什么。这时候推荐阅读下面两篇内容:
- TypeScript Compiler Internals · TypeScript Deep Dive ,TS 编译的核心还是 AST,这篇文章讲解了 TS 编译的五个阶段( Scanner /Parser / Binder /Checker /Emitter )分别是怎么工作的;
- Learn how to contribute to the TypeScript compiler on GitHub through a real-world example,则是另外一篇比较好的了解 TS 运行原理的资料。
关于 TS 的原理,还没顾上看,后续大家可以一起研究研究