前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React + TypeScript 实践

React + TypeScript 实践

作者头像
童欧巴
发布2021-08-20 11:24:59
5.3K0
发布2021-08-20 11:24:59
举报
文章被收录于专栏:前端食堂前端食堂

❗️ 准备知识

  • 熟悉 React
  • 熟悉 TypeScript (参考书籍:2ality's guide[1], 初学者建议阅读:chibicode's tutorial[2]
  • 熟读 React 官方文档 TS 部分[3]
  • 熟读 TypeScript playground React 部分[4]

本文档参考 TypeScript 最新版本

如何引入 React

代码语言:javascript
复制
import * as React from 'react'

import * as ReactDOM from 'react-dom'

这种引用方式被证明[5]是最可靠的一种方式, 推荐使用

而另外一种引用方式:

代码语言:javascript
复制
import React from 'react'

import ReactDOM from 'react-dom'

需要添加额外的配置:"allowSyntheticDefaultImports": true

函数式组件的声明方式

声明的几种方式

第一种:也是比较推荐的一种,使用 React.FunctionComponent,简写形式:React.FC:

代码语言:javascript
复制
// Great
type AppProps = {
  message: string
}

const App: React.FC<AppProps> = ({ message, children }) => (
  <div>
    {message}
    {children}
  </div>
)

使用用 React.FC 声明函数组件和普通声明以及 PropsWithChildren 的区别是:

  • React.FC 显式地定义了返回类型,其他方式是隐式推导的
  • React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全
  • React.FC 为 children 提供了隐式的类型(ReactElement | null),但是目前,提供的类型存在一些 issue[6](问题)

比如以下用法 React.FC 会报类型错误:

代码语言:javascript
复制
const App: React.FC = props => props.children
const App: React.FC = () => [1, 2, 3]
const App: React.FC = () => 'hello'

解决方法:

代码语言:javascript
复制
const App: React.FC<{}> = props => props.children as any
const App: React.FC<{}> = () => [1, 2, 3] as any
const App: React.FC<{}> = () => 'hello' as any
// 或者
const App: React.FC<{}> = props => (props.children as unknown) as JSX.Element
const App: React.FC<{}> = () => ([1, 2, 3] as unknown) as JSX.Element
const App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element

在通常情况下,使用 React.FC 的方式声明最简单有效,推荐使用;如果出现类型不兼容问题,建议使用以下两种方式:

第二种:使用 PropsWithChildren,这种方式可以为你省去频繁定义 children 的类型,自动设置 children 类型为 ReactNode:

代码语言:javascript
复制
type AppProps = React.PropsWithChildren<{ message: string }>
const App = ({ message, children }: AppProps) => (
  <div>
    {message}
    {children}
  </div>
)

第三种:直接声明:

代码语言:javascript
复制
type AppProps = {
  message: string
  children?: React.ReactNode
}

const App = ({ message, children }: AppProps) => (
  <div>
    {message}
    {children}
  </div>
)

Hooks

useState<T>

大部分情况下,TS 会自动为你推导 state 的类型:

代码语言:javascript
复制
// `val`会推导为boolean类型, toggle接收boolean类型参数
const [val, toggle] = React.useState(false)
// obj会自动推导为类型: {name: string}
const [obj] = React.useState({ name: 'sj' })
// arr会自动推导为类型: string[]
const [arr] = React.useState(['One', 'Two'])

使用推导类型作为接口/类型:

代码语言:javascript
复制
export default function App() {
  // user会自动推导为类型: {name: string}
  const [user] = React.useState({ name: 'sj', age: 32 })
  const showUser = React.useCallback((obj: typeof user) => {
    return `My name is ${obj.name}, My age is ${obj.age}`
  }, [])  return <div className="App">用户: {showUser(user)}</div>
}

但是,一些状态初始值为空时(null),需要显示地声明类型:

代码语言:javascript
复制
type User = {
  name: string
  age: number
}const [user, setUser] = React.useState<User | null>(null)

useRef<T>

当初始值为 null 时,有两种创建方式:

代码语言:javascript
复制
const ref1 = React.useRef<HTMLInputElement>(null)
const ref2 = React.useRef<HTMLInputElement | null>(null)

这两种的区别在于

  • 第一种方式的 ref1.current 是只读的(read-only),并且可以传递给内置的 ref 属性,绑定 DOM 元素
  • 第二种方式的 ref2.current 是可变的(类似于声明类的成员变量)
代码语言:javascript
复制
const ref = React.useRef(0)
React.useEffect(() => {
  ref.current += 1
}, [])

这两种方式在使用时,都需要对类型进行检查:

代码语言:javascript
复制
const onButtonClick = () => {
  ref1.current?.focus()
  ref2.current?.focus()
}

在某种情况下,可以省去类型检查,通过添加 ! 断言不推荐

代码语言:javascript
复制
// Bad
function MyComponent() {
  const ref1 = React.useRef<HTMLDivElement>(null!)
  React.useEffect(() => {
    //  不需要做类型检查,需要人为保证ref1.current.focus一定存在
    doSomethingWith(ref1.current.focus())
  })
  return <div ref={ref1}> etc </div>
}

useEffect

useEffect 需要注意回调函数的返回值只能是函数或者 undefined

代码语言:javascript
复制
function App() {
  // undefined作为回调函数的返回值
  React.useEffect(() => {
    // do something...
  }, [])
  // 返回值是一个函数
  React.useEffect(() => {
    // do something...
    return () => {}
  }, [])
}

useMemo<T> / useCallback<T>

useMemouseCallback 都可以直接从它们返回的值中推断出它们的类型

useCallback 的参数必须制定类型,否则 ts 不会报错,默认指定为 any

代码语言:javascript
复制
const value = 10
// 自动推断返回值为 number
const result = React.useMemo(() => value * 2, [value])
// 自动推断 (value: number) => number
const multiply = React.useCallback((value: number) => value * multiplier, [
  multiplier,
])

同时也支持传入泛型, useMemo 的泛型指定了返回值类型,useCallback 的泛型指定了参数类型

代码语言:javascript
复制
// 也可以显式的指定返回值类型,返回值不一致会报错
const result = React.useMemo<string>(() => 2, [])
// 类型“() => number”的参数不能赋给类型“() => string”的参数。
const handleChange = React.useCallback<
  React.ChangeEventHandler<HTMLInputElement>
>(evt => {
  console.log(evt.target.value)
}, [])

自定义 Hooks

需要注意,自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,而我们实际需要的是数组里里每一项的具体类型,需要手动添加 const 断言 进行处理:

代码语言:javascript
复制
function useLoading() {
  const [isLoading, setState] = React.useState(false)
  const load = (aPromise: Promise<any>) => {
    setState(true)
    return aPromise.then(() => setState(false))
  }
  // 实际需要: [boolean, typeof load] 类型
  // 而不是自动推导的:(boolean | typeof load)[]
  return [isLoading, load] as const
}

如果使用 const 断言遇到问题[7],也可以直接定义返回类型:

代码语言:javascript
复制
export function useLoading(): [
  boolean,
  (aPromise: Promise<any>) => Promise<any>
] {
  const [isLoading, setState] = React.useState(false)
  const load = (aPromise: Promise<any>) => {
    setState(true)
    return aPromise.then(() => setState(false))
  }
  return [isLoading, load]
}

如果有大量的自定义 Hook 需要处理,这里有一个方便的工具方法可以处理 tuple 返回值:

代码语言:javascript
复制
function tuplify<T extends any[]>(...elements: T) {
  return elements
}
function useLoading() {
  const [isLoading, setState] = React.useState(false)
  const load = (aPromise: Promise<any>) => {
    setState(true)
    return aPromise.then(() => setState(false))
  }

  // (boolean | typeof load)[]
  return [isLoading, load]
}

function useTupleLoading() {
  const [isLoading, setState] = React.useState(false)
  const load = (aPromise: Promise<any>) => {
    setState(true)
    return aPromise.then(() => setState(false))
  }

  // [boolean, typeof load]
  return tuplify(isLoading, load)
}

默认属性 defaultProps

大部分文章都不推荐使用 defaultProps , 相关讨论可以点击参考链接[8]

推荐方式:使用默认参数值来代替默认属性:

代码语言:javascript
复制
type GreetProps = { age?: number }
const Greet = ({ age = 21 }: GreetProps) => {
  /* ... */
}

defaultProps 类型

TypeScript3.0+[9] 在默认属性 的类型推导上有了极大的改进,虽然尚且存在一些边界 case 仍然存在问题[10]不推荐使用,如果有需要使用的场景,可参照如下方式:

代码语言:javascript
复制
type IProps = {
  name: string
}
const defaultProps = {
  age: 25,
}

// 类型定义
type GreetProps = IProps & typeof defaultProps
const Greet = (props: GreetProps) => <div></div>
Greet.defaultProps = defaultProps
// 使用
const TestComponent = (props: React.ComponentProps<typeof Greet>) => {
  return <h1 />
}
const el = <TestComponent name="foo" />

Types or Interfaces

在日常的 react 开发中 interfacetype 的使用场景十分类似

implementsextends 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:

代码语言:javascript
复制
class Point {
  x: number = 2
  y: number = 3
}
interface IShape {
  area(): number
}
type Perimeter = {
  perimeter(): number
}
type RectangleShape = (IShape | Perimeter) & Point

class Rectangle implements RectangleShape {
  // 类只能实现具有静态已知成员的对象类型或对象类型的交集。
  x = 2
  y = 3
  area() {
    return this.x + this.y
  }
}
interface ShapeOrPerimeter extends RectangleShape {}
// 接口只能扩展使用静态已知成员的对象类型或对象类型的交集

使用 Type 还是 Interface?

有几种常用规则:

  • 在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口
  • 在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type的约束性更强

interfacetype 在 ts 中是两个不同的概念,但在 React 大部分使用的 case 中,interfacetype 可以达到相同的功能效果,typeinterface 最大的区别是:

  • type 类型不能二次编辑,而 interface 可以随时扩展
代码语言:javascript
复制
interface Animal {
  name: string
}

// 可以继续在原有属性基础上,添加新属性:color
interface Animal {
  color: string
}
/********************************/
type Animal = {
  name: string
}
// type类型不支持属性扩展
// Error: Duplicate identifier 'Animal'
type Animal = {
  color: string
}

获取未导出的 Type

某些场景下我们在引入第三方的库时会发现想要使用的组件并没有导出我们需要的组件参数类型或者返回值类型,这时候我们可以通过 ComponentProps/ ReturnType 来获取到想要的类型。

代码语言:javascript
复制
// 获取参数类型
import { Button } from 'library' // 但是未导出props type
type ButtonProps = React.ComponentProps<typeof Button> // 获取props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClick
const AlertButton: React.FC<AlertButtonProps> = props => (
  <Button onClick={() => alert('hello')} {...props} />
)
代码语言:javascript
复制
// 获取返回值类型
function foo() {
  return { baz: 1 }
}
type FooReturn = ReturnType<typeof foo> // { baz: number }

Props

通常我们使用 type 来定义 Props,为了提高可维护性和代码可读性,在日常的开发过程中我们希望可以添加清晰的注释。

现在有这样一个 type

代码语言:javascript
复制
type OtherProps = {
  name: string
  color: string
}

在使用的过程中,hover 对应类型会有如下展示

代码语言:javascript
复制
// type OtherProps = {
//   name: string;
//   color: string;
// }
const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (
  <h1>My Website Heading</h1>
)

增加相对详细的注释,使用时会更清晰,需要注意,注释需要使用 /**/ // 无法被 vscode 识别

代码语言:javascript
复制
// Great
/**
 * @param color color
 * @param children children
 * @param onClick onClick
 */

type Props = {
  /** color */
  color?: string
  /** children */
  children: React.ReactNode
  /** onClick */
  onClick: () => void
}

// type Props
// @param color — color
// @param children — children
// @param onClick — onClick
const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
  return (
    <button style={{ backgroundColor: color }} onClick={onClick}>
      {children}
    </button>
  )
}

常用 Props ts 类型

基础属性类型

代码语言:javascript
复制
type AppProps = {
  message: string
  count: number
  disabled: boolean
  /** array of a type! */
  names: string[]
  /** string literals to specify exact string values, with a union type to join them together */
  status: 'waiting' | 'success'
  /** 任意需要使用其属性的对象(不推荐使用,但是作为占位很有用) */
  obj: object
  /** 作用和`object`几乎一样,和 `Object`完全一样 */
  obj2: {}
  /** 列出对象全部数量的属性 (推荐使用) */
  obj3: {
    id: string
    title: string
  }
  /** array of objects! (common) */
  objArr: {
    id: string
    title: string
  }[]
  /** 任意数量属性的字典,具有相同类型*/
  dict1: {
    [key: string]: MyTypeHere
  }
  /** 作用和dict1完全相同 */
  dict2: Record<string, MyTypeHere>
  /** 任意完全不会调用的函数 */
  onSomething: Function
  /** 没有参数&返回值的函数 */
  onClick: () => void
  /** 携带参数的函数 */
  onChange: (id: number) => void
  /** 携带点击事件的函数 */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void
  /** 可选的属性 */
  optional?: OptionalType
}

常用 React 属性类型

代码语言:javascript
复制
export declare interface AppBetterProps {
  children: React.ReactNode // 一般情况下推荐使用,支持所有类型 Great
  functionChildren: (name: string) => React.ReactNode
  style?: React.CSSProperties // 传递style对象
  onChange?: React.FormEventHandler<HTMLInputElement>
}

export declare interface AppProps {
  children1: JSX.Element // 差, 不支持数组
  children2: JSX.Element | JSX.Element[] // 一般, 不支持字符串
  children3: React.ReactChildren // 忽略命名,不是一个合适的类型,工具类类型
  children4: React.ReactChild[] // 很好
  children: React.ReactNode // 最佳,支持所有类型 推荐使用
  functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type
  style?: React.CSSProperties // 传递style对象
  onChange?: React.FormEventHandler<HTMLInputElement> // 表单事件, 泛型参数是event.target的类型
}

Forms and Events

onChange

change 事件,有两个定义参数类型的方法。

第一种方法使用推断的方法签名(例如:React.FormEvent <HTMLInputElement> :void

代码语言:javascript
复制
import * as React from 'react'

type changeFn = (e: React.FormEvent<HTMLInputElement>) => void
const App: React.FC = () => {
  const [state, setState] = React.useState('')
  const onChange: changeFn = e => {
    setState(e.currentTarget.value)
  }
  return (
    <div>
      <input type="text" value={state} onChange={onChange} />
    </div>
  )
}

第二种方法强制使用 @types / react 提供的委托类型,两种方法均可。

代码语言:javascript
复制
import * as React from 'react'const App: React.FC = () => {
  const [state, setState] = React.useState('')
  const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
    setState(e.currentTarget.value)
  }
  return (
    <div>
      <input type="text" value={state} onChange={onChange} />
    </div>
  )
}

onSubmit

如果不太关心事件的类型,可以直接使用 React.SyntheticEvent,如果目标表单有想要访问的自定义命名输入,可以使用类型扩展

代码语言:javascript
复制

import * as React from 'react'

const App: React.FC = () => {
  const onSubmit = (e: React.SyntheticEvent) => {
    e.preventDefault()
    const target = e.target as typeof e.target & {
      password: { value: string }
    } // 类型扩展
    const password = target.password.value
  }
  return (
    <form onSubmit={onSubmit}>
      <div>
        <label>
          Password:
          <input type="password" name="password" />
        </label>
      </div>
      <div>
        <input type="submit" value="Log in" />
      </div>
    </form>
  )
}

Operators

常用的操作符,常用于类型判断

  • typeof and instanceof: 用于类型区分
  • keyof: 获取 object 的 key
  • O[K]: 属性查找
  • [K in O]: 映射类型
  • + or - or readonly or ?: 加法、减法、只读和可选修饰符
  • x ? Y : Z: 用于泛型类型、类型别名、函数参数类型的条件类型
  • !: 可空类型的空断言
  • as: 类型断言
  • is: 函数返回类型的类型保护

Tips

使用查找类型访问组件属性类型

通过查找类型减少 type 的非必要导出,如果需要提供复杂的 type,应当提取到作为公共 API 导出的文件中。

现在我们有一个 Counter 组件,需要 name 这个必传参数:

代码语言:javascript
复制
// counter.tsx
import * as React from 'react'
export type Props = {
  name: string
}
const Counter: React.FC<Props> = props => {
  return <></>
}
export default Counter

在其他引用它的组件中我们有两种方式获取到 Counter 的参数类型

第一种是通过 typeof 操作符(推荐

代码语言:javascript
复制
// Great
import Counter from './d-tips1'
type PropsNew = React.ComponentProps<typeof Counter> & {
  age: number
}
const App: React.FC<PropsNew> = props => {
  return <Counter {...props} />
}
export default App

第二种是通过在原组件进行导出

代码语言:javascript
复制
import Counter, { Props } from './d-tips1'
type PropsNew = Props & {
  age: number
}
const App: React.FC<PropsNew> = props => {
  return (
    <>
      <Counter {...props} />
    </>
  )
}
export default App

不要在 type 或 interface 中使用函数声明

保持一致性,类型/接口的所有成员都通过相同的语法定义。

--strictFunctionTypes 在比较函数类型时强制执行更严格的类型检查,但第一种声明方式下严格检查不生效。

代码语言:javascript
复制
✅

interface ICounter {
  start: (value: number) => string
}

❌

interface ICounter1 {
  start(value: number): string
}



?

interface Animal {}
interface Dog extends Animal {
  wow: () => void
}
interface Comparer<T> {
  compare: (a: T, b: T) => number
}
declare let animalComparer: Comparer<Animal>
declare let dogComparer: Comparer<Dog>
animalComparer = dogComparer // Error
dogComparer = animalComparer // Ok
interface Comparer1<T> {
  compare(a: T, b: T): number
}
declare let animalComparer1: Comparer1<Animal>
declare let dogComparer1: Comparer1<Dog>
animalComparer1 = dogComparer // Ok
dogComparer1 = animalComparer // Ok

事件处理

我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时我们通过 clientXclientY 去获取指针的坐标。

大家可能会想到直接把 event 设置为 any 类型,但是这样就失去了我们对代码进行静态检查的意义。

代码语言:javascript
复制
function handleEvent(event: any) {、
  console.log(event.clientY)
}

试想下当我们注册一个 Touch 事件,然后错误的通过事件处理函数中的 event 对象去获取其 clientY 属性的值,在这里我们已经将 event 设置为 any 类型,导致 TypeScript 在编译时并不会提示我们错误, 当我们通过 event.clientY 访问时就有问题了,因为 Touch 事件的 event 对象并没有 clientY 这个属性。

通过 interfaceevent 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。

Event 事件对象类型
  • ClipboardEvent<T = Element> 剪切板事件对象
  • DragEvent<T =Element> 拖拽事件对象
  • ChangeEvent<T = Element> Change 事件对象
  • KeyboardEvent<T = Element> 键盘事件对象
  • MouseEvent<T = Element> 鼠标事件对象
  • TouchEvent<T = Element> 触摸事件对象
  • WheelEvent<T = Element> 滚轮时间对象
  • AnimationEvent<T = Element> 动画事件对象
  • TransitionEvent<T = Element> 过渡事件对象
事件处理函数类型

当我们定义事件处理函数时有没有更方便定义其函数类型的方式呢?答案是使用 React 声明文件所提供的 EventHandler 类型别名,通过不同事件的 EventHandler 的类型别名来定义事件处理函数的类型

代码语言:javascript
复制
type EventHandler<E extends React.SyntheticEvent<any>> = {
  bivarianceHack(event: E): void
}['bivarianceHack']
type ReactEventHandler<T = Element> = EventHandler<React.SyntheticEvent<T>>
type ClipboardEventHandler<T = Element> = EventHandler<React.ClipboardEvent<T>>
type DragEventHandler<T = Element> = EventHandler<React.DragEvent<T>>
type FocusEventHandler<T = Element> = EventHandler<React.FocusEvent<T>>
type FormEventHandler<T = Element> = EventHandler<React.FormEvent<T>>
type ChangeEventHandler<T = Element> = EventHandler<React.ChangeEvent<T>>
type KeyboardEventHandler<T = Element> = EventHandler<React.KeyboardEvent<T>>
type MouseEventHandler<T = Element> = EventHandler<React.MouseEvent<T>>
type TouchEventHandler<T = Element> = EventHandler<React.TouchEvent<T>>
type PointerEventHandler<T = Element> = EventHandler<React.PointerEvent<T>>
type UIEventHandler<T = Element> = EventHandler<React.UIEvent<T>>
type WheelEventHandler<T = Element> = EventHandler<React.WheelEvent<T>>
type AnimationEventHandler<T = Element> = EventHandler<React.AnimationEvent<T>>
type TransitionEventHandler<T = Element> = EventHandler<
  React.TransitionEvent<T>
>

bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void

关于为何是用 bivarianceHack 而不是(event: E): void,这与 strictfunctionTypes 选项下的功能兼容性有关。(event: E): void,如果该参数是派生类型,则不能将其传递给参数是基类的函数。

代码语言:javascript
复制
class Animal {
  private x: undefined
}
class Dog extends Animal {
  private d: undefined
}
type EventHandler<E extends Animal> = (event: E) => void
let z: EventHandler<Animal> = (o: Dog) => {} // fails under strictFunctionTyes
type BivariantEventHandler<E extends Animal> = {
  bivarianceHack(event: E): void
}['bivarianceHack']
let y: BivariantEventHandler<Animal> = (o: Dog) => {}

Promise 类型

在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。Promise<T> 是一个泛型类型,T 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。

代码语言:javascript
复制
type 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}

泛型参数的组件

下面这个组件的 name 属性都是指定了传参格式,如果想不指定,而是想通过传入参数的类型去推导实际类型,这就要用到泛型。

代码语言:javascript
复制
const TestB = ({ name, name2 }: { name: string; name2?: string }) => {
  return (
    <div className="test-b">
      TestB--{name}
      {name2}
    </div>
  )
}

如果需要外部传入参数类型,只需 ->

代码语言:javascript
复制
type Props<T> = {
  name: T
  name2?: T
}
const TestC: <T>(props: Props<T>) => React.ReactElement = ({ name, name2 }) => {
  return (
    <div className="test-b">
      TestB--{name}
      {name2}
    </div>
  )
}

const TestD = () => {
  return (
    <div>
      <TestC<string> name="123" />
    </div>
  )
}

什么时候使用泛型

当你的函数,接口或者类:

  • 需要作用到很多类型的时候,举个 ?

当我们需要一个 id 函数,函数的参数可以是任何值,返回值就是将参数原样返回,并且其只能接受一个参数,在 js 时代我们会很轻易地甩出一行

代码语言:javascript
复制
const id = arg => arg

由于其可以接受任意值,也就是说我们的函数的入参和返回值都应该可以是任意类型,如果不使用泛型,我们只能重复的进行定义

代码语言:javascript
复制
type idBoolean = (arg: boolean) => boolean
type idNumber = (arg: number) => number
type idString = (arg: string) => string
// ...

如果使用泛型,我们只需要

代码语言:javascript
复制
function id<T>(arg: T): T {
  return arg
}

// 或
const id1: <T>(arg: T) => T = arg => {
  return arg
}
  • 需要被用到很多地方的时候,比如常用的工具泛型 Partial

功能是将类型的属性变成可选, 注意这是浅 Partial

代码语言:javascript
复制
type Partial<T> = { [P in keyof T]?: T[P] }

如果需要深 Partial 我们可以通过泛型递归来实现

代码语言:javascript
复制
type DeepPartial<T> = T extends Function
  ? T
  : T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T
type PartialedWindow = DeepPartial<Window>

参考资料

[1]

2ality's guide: http://2ality.com/2018/04/type-notation-typescript.html

[2]

chibicode's tutorial: https://ts.chibicode.com/todo/

[3]

TS 部分: https://reactjs.org/docs/static-type-checking.html#typescript

[4]

React 部分: http://www.typescriptlang.org/play/index.html?jsx=2&esModuleInterop=true&e=181#example/typescript-with-react

[5]

被证明: https://www.reddit.com/r/reactjs/comments/iyehol/import_react_from_react_will_go_away_in_distant/

[6]

一些 issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33006

[7]

问题: https://github.com/babel/babel/issues/9800

[8]

参考链接: https://twitter.com/hswolff/status/1133759319571345408

[9]

TypeScript3.0+: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html

[10]

存在一些边界 case 仍然存在问题: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61

公众号:前端食堂

知乎:童欧巴

掘金:童欧巴

这是一个终身学习的男人,他在坚持自己热爱的事情,欢迎你加入前端食堂,和这个男人一起开心的变胖~

“如果你觉得读了本文有收获的话可以点个在看让我看到。阅读过程中有任何问题、想法或者感触也欢迎你在下方留言,也可以在后台回复加群进入食堂的交流群。 沟通创造价值,分享带来快乐。也欢迎你分享给身边有需要的同学,利他就是最好的利己。 ”

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端食堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ❗️ 准备知识
  • 如何引入 React
  • 函数式组件的声明方式
  • Hooks
    • useState<T>
      • useRef<T>
        • useEffect
          • useMemo<T> / useCallback<T>
            • 自定义 Hooks
            • 默认属性 defaultProps
              • defaultProps 类型
              • Types or Interfaces
                • 使用 Type 还是 Interface?
                  • 获取未导出的 Type
                  • Props
                  • 常用 Props ts 类型
                    • 基础属性类型
                      • 常用 React 属性类型
                      • Forms and Events
                        • onChange
                          • onSubmit
                          • Operators
                          • Tips
                            • 使用查找类型访问组件属性类型
                              • 不要在 type 或 interface 中使用函数声明
                                • 事件处理
                                  • Event 事件对象类型
                                  • 事件处理函数类型
                                • Promise 类型
                                  • 泛型参数的组件
                                    • 什么时候使用泛型
                                    领券
                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档