React + TypeScript 实践

发表于 3年以前  | 总阅读数:493 次

❗️ 准备知识

  • 熟悉 React

  • 熟悉 TypeScript (参考书籍:2ality's guide[1], 初学者建议阅读:chibicode's tutorial[2])

  • 熟读 React 官方文档 TS 部分[3]

  • 熟读 TypeScript playground React 部分[4]

本文档参考 TypeScript 最新版本

如何引入 React

import * as React from 'react'

import * as ReactDOM from 'react-dom'

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

而另外一种引用方式:

import React from 'react'

import ReactDOM from 'react-dom'

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

函数式组件的声明方式

声明的几种方式

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

// 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 会报类型错误:

const App: React.FC = props => props.children
const App: React.FC = () => [1, 2, 3]
const App: React.FC = () => 'hello'

解决方法:

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:

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

第三种:直接声明:

type AppProps = {
  message: string
  children?: React.ReactNode
}

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

Hooks

useState

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

// `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'])

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

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),需要显示地声明类型:

type User = {
  name: string
  age: number
}
const [user, setUser] = React.useState<User | null>(null)

useRef

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

const ref1 = React.useRef<HTMLInputElement>(null)
const ref2 = React.useRef<HTMLInputElement | null>(null)

这两种的区别在于

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

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

const onButtonClick = () => {
  ref1.current?.focus()
  ref2.current?.focus()
}

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

// 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

function App() {
  // undefined作为回调函数的返回值
  React.useEffect(() => {
    // do something...
  }, [])
  // 返回值是一个函数
  React.useEffect(() => {
    // do something...
    return () => {}
  }, [])
}

useMemo / useCallback

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

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

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 的泛型指定了参数类型

// 也可以显式的指定返回值类型,返回值不一致会报错
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 断言 进行处理:

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],也可以直接定义返回类型:

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 返回值:

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]

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

type GreetProps = { age?: number }
const Greet = ({ age = 21 }: GreetProps) => {
  /* ... */
}

defaultProps 类型

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

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 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:

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 可以随时扩展
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 来获取到想要的类型。

// 获取参数类型
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} />
)
// 获取返回值类型
function foo() {
  return { baz: 1 }
}
type FooReturn = ReturnType<typeof foo> // { baz: number }

Props

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

现在有这样一个 type

type OtherProps = {
  name: string
  color: string
}

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

// type OtherProps = {
//   name: string;
//   color: string;
// }
const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (
  <h1>My Website Heading</h1>
)

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

// 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 类型

基础属性类型

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 属性类型

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 :void

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 提供的委托类型,两种方法均可。

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,如果目标表单有想要访问的自定义命名输入,可以使用类型扩展

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 这个必传参数:

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

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

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

// 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

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

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

✅

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 类型,但是这样就失去了我们对代码进行静态检查的意义。

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 的类型别名来定义事件处理函数的类型

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,如果该参数是派生类型,则不能将其传递给参数是基类的函数。

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 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。

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 属性都是指定了传参格式,如果想不指定,而是想通过传入参数的类型去推导实际类型,这就要用到泛型。

const TestB = ({ name, name2 }: { name: string; name2?: string }) => {
  return (
    <div className="test-b">
      TestB--{name}
      {name2}
    </div>
  )
}

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

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 时代我们会很轻易地甩出一行

const id = arg => arg

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

type idBoolean = (arg: boolean) => boolean
type idNumber = (arg: number) => number
type idString = (arg: string) => string
// ...

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

function id<T>(arg: T): T {
  return arg
}

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

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

type Partial<T> = { [P in keyof T]?: T[P] }

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

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

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/-R_IhF4IC_wcstCshP1eAQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录