国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

React + TypeScript 實踐

這篇具有很好參考價值的文章主要介紹了React + TypeScript 實踐。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

主要內(nèi)容包括準備知識、如何引入 React、函數(shù)式組件的聲明方式、Hooks、useRef<T>、useEffect、useMemo<T> / useCallback<T>、自定義 Hooks、默認屬性 defaultProps、Types or Interfaces、獲取未導出的 Type、Props、常用 Props ts 類型、常用 React 屬性類型、Forms and Events、onSubmit、Operators、Tips、不要在 type 或 interface 中使用函數(shù)聲明、事件處理、Promise 類型、泛型參數(shù)的組件、什么時候使用泛型、基本概念、基礎應用、原理機制和需要注意的事項等,并結合實例形式分析了其使用技巧,希望通過本文能幫助到大家理解應用這部分內(nèi)容。

準備知識

  • 熟悉 React
  • 熟悉 TypeScript (參考書籍:2ality’s guide[1], 初學者建議閱讀:chibicode’s tutorial[2])
  • 熟讀 React 官方文檔 TS 部分[3]
  • 熟讀 TypeScript playground React 部分[4]

驗證環(huán)境

"umi": "^4.0.63"
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"typescript": "^5.0.0"

如何引入 React

import * as React from 'react'
import * as ReactDOM from 'react-dom'

這種引用方式被證明[5]是最可靠的一種方式, 推薦使用

而另外一種引用方式:

import React from 'react'
import ReactDOM from 'react-dom'

需要添加額外的配置:“allowSyntheticDefaultImports”: true (項目中雖然沒手動加,在umi項目的./src/.umi/tsconfig.json中可以看到有這個配置項)

函數(shù)式組件的聲明方式

第一種:也是比較推薦的一種,使用 React.FunctionComponent,簡寫形式:React.FC:

// Great
type AppProps = {
  message: string,
  children?: React.ReactNode	//React18以后需要加children屬性
}

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

需要注意的是React18以前不需要顯式指定children屬性,而根據(jù)官方文檔,React18需要明確列出:

React + TypeScript 實踐,前端,React,TypeScript,react.js,typescript,前端

React17中**@types/react**的對FC的定義,幫我們默認聲明了:

    interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P> | undefined;
        contextTypes?: ValidationMap<any> | undefined;
        defaultProps?: Partial<P> | undefined;
        displayName?: string | undefined;
    }

	type PropsWithChildren<P> = P & { children?: ReactNode | undefined };

React18中**@types/react**的對FC的定義,取消了默認聲明:

    interface FunctionComponent<P = {}> {
        (props: P, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P> | undefined;
        contextTypes?: ValidationMap<any> | undefined;
        defaultProps?: Partial<P> | undefined;
        displayName?: string | undefined;
    }

使用 React.FC 聲明函數(shù)組件和普通聲明以及 PropsWithChildren 的區(qū)別是:

  • React.FC 顯式地定義了返回類型,其他方式是隱式推導的
  • React.FC 對靜態(tài)屬性:displayName、propTypes、defaultProps 提供了類型檢查和自動補全
  • React.FC 為 children 提供了隱式的類型(ReactElement | null),但是目前,提供的類型存在一些 issue[6](問題)

比如以下用法 React.FC 會報類型錯誤:

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

實測截圖:

React + TypeScript 實踐,前端,React,TypeScript,react.js,typescript,前端

React + TypeScript 實踐,前端,React,TypeScript,react.js,typescript,前端

React + TypeScript 實踐,前端,React,TypeScript,react.js,typescript,前端

解決方法:

const App: React.FC<{children?: React.ReactNode}> = 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<{children?: React.ReactNode}> = 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?的方式聲明最簡單有效,推薦使用;如果出現(xiàn)類型不兼容問題,建議使用以下兩種方式:

第二種:使用?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<T>

大部分情況下,TS 會自動為你推導 state 的類型:

// `val`會推導為boolean類型, toggle接收boolean類型參數(shù)
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 });
	//typeof user自動推導類型
  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>;
}

但是,一些狀態(tài)初始值為空時(null),需要顯示地聲明類型:

  const [user, setUser] = React.useState(null)

  const showUser = React.useCallback((obj: typeof user) => {
    return `My name is ${obj.name}, My age is ${obj.age}`;	//ts報錯:“obj”可能為 “null”。
  }, []);

//改成顯示聲明
type User = {
  name: string
  age: number
}		

  const [user, setUser] = React.useState<User|null>(null)

  const showUser = React.useCallback((obj: typeof user) => {
	//obj可能是null,需要判斷下
    if (null === obj) return '--';
    return `My name is ${obj.name}, My age is ${obj.age}`;
  }, []);

useRef<T>

當初始值為?null?時,有兩種創(chuàng)建方式:

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

這兩種的區(qū)別在于

  • 第一種方式的 ref1.current 是只讀的(read-only),我們無法修改current值,但是可以傳遞給內(nèi)置的 ref 屬性,綁定 DOM 元素?;
  • 第二種方式的 ref2.current 是可變的(類似于聲明類的成員變量)

為了驗證只讀屬性,將HTMLInputElement改成number:

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

  ref1.current = 1;	//ts報錯:無法為“current”賦值,因為它是只讀屬性。
  ref2.current = 2;

但是如果初始值不為null,那ref.current是可變的:

//寫不寫<number>都可以,跟useState一樣也有類型推導
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!);	//null!斷言
  React.useEffect(() => {
    //  不需要做類型檢查,需要人為保證ref1.current.focus一定存在
    doSomethingWith(ref1.current.focus())
  })
  return <div ref={ref1}> etc </div>
}

useEffect

useEffect?需要注意回調(diào)函數(shù)的返回值只能是函數(shù)或者?undefined

function App() {
  // undefined作為回調(diào)函數(shù)的返回值
  React.useEffect(() => {
    // do something...
  }, []);
  // 返回值是一個函數(shù)
  React.useEffect(() => {
    // do something...
    return () => {};
  }, []);
}

useMemo<T> / useCallback<T>

useMemo?和?useCallback?都可以直接從它們返回的值中推斷出它們的類型。

useCallback?的參數(shù)必須指定類型,否則 ts 會報錯,默認指定為?any:

  const value = 10, multiplier = 20;
  // 自動推斷result返回值為 number
  const result = React.useMemo(() => value * 2, [value]);
  // 自動推斷multiply 類型為 (value: number) => number
  const multiply = React.useCallback(
    (value: number) => value * multiplier,
    [multiplier]
  );

同時也支持傳入泛型,?useMemo?的泛型指定了返回值類型,useCallback?的泛型指定了參數(shù)類型

  // useMemo也可以顯式的指定返回值類型,返回值不一致會報錯
  //類型“() => number”的參數(shù)不能賦給類型“() => string”的參數(shù)。
  const result = React.useMemo<string>(() => 2, []);  //ts報錯:不能將類型“number”分配給類型“string”。

	//傳入的React.ChangeEventHandler<HTMLInputElement>指定了evt的類型
  const handleChange1 = React.useCallback<
    React.ChangeEventHandler<HTMLInputElement>
  >((evt) => {
    console.log(evt.target.value);
  }, []);
	//等同于(自己推斷的,未經(jīng)過實際項目驗證)
  const handleChange2 = React.useCallback(
    (evt: React.ChangeEvent<HTMLInputElement>) => {
      console.log(evt.target.value);
    },
    []
  );
	

自定義 Hooks

需要注意,自定義 Hook 的返回值如果是數(shù)組類型,TS 會自動推導為?Union?(聯(lián)合)類型,而我們實際需要的是數(shù)組里里每一項的具體類型,需要手動添加?const?斷言?進行處理:

//未使用as const推斷的類型:function useLoading(): (boolean | ((aPromise: Promise<any>) => Promise<void>))[]
//使用as const后推斷的類型:function useLoading(): readonly [boolean, (aPromise: Promise<any>) => Promise<void>]
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 需要處理,這里有一個方便的工具方法可以處理 數(shù)組 返回值:

//實際上就是把參數(shù)數(shù)組直接返回
function tuplify<T extends any[]>(...elements: T) {
  return elements;
}

//function useLoading(): (boolean | ((aPromise: Promise<any>) => Promise<void>))[]
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(): [boolean, (aPromise: Promise<any>) => Promise<void>]
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]

推薦方式:使用默認參數(shù)值來代替默認屬性:

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>
    {props.age}
    {props.name}
  </div>
);
//寫不寫好像都可以,TS都沒有報錯,TS版本號^5.0.0
Greet.defaultProps = defaultProps;

// 使用 將Gree的默認類型作為類型定義傳遞
const TestComponent = (props: React.ComponentProps<typeof Greet>) => {
  return (
    <div>
      {props.age}
      {props.name}
    </div>
  );
};

const el = <TestComponent name='foo' age={11} />;

Types or Interfaces

在日常的 react 開發(fā)中?interface?和?type?的使用場景十分類似

implements?與?extends?靜態(tài)操作,不允許存在一種或另一種實現(xiàn)的情況,所以不支持使用聯(lián)合類型:

class Point {
  x: number = 2
  y: number = 3
}
interface IShape {
  area(): number
}
type Perimeter = {
  perimeter(): number
}
//聯(lián)合類型,既可以是IShape,也可以是Perimeter,類型未知
type RectangleShape = (IShape | Perimeter) & Point;

class Rectangle implements RectangleShape {
  // TS報錯:類只能實現(xiàn)具有靜態(tài)已知成員的對象類型或?qū)ο箢愋偷慕患?/span>
  x = 2
  y = 3
  area() {
    return this.x + this.y
  }
}

//TS報錯: 接口只能擴展使用靜態(tài)已知成員的對象類型或?qū)ο箢愋偷慕患?/span>
interface ShapeOrPerimeter extends RectangleShape {}

使用 Type 還是 Interface?

有幾種常用規(guī)則:

  • 在定義公共 API 時(比如編輯一個庫)使用?interface,這樣可以方便使用者繼承接口
  • 在定義組件屬性(Props)和狀態(tài)(State)時,建議使用?type,因為?type的約束性更強

interface?和?type?在 ts 中是兩個不同的概念,但在 React 大部分使用的?case?中,interface?和?type?可以達到相同的功能效果,type?和?interface 最大的區(qū)別是:

  • type?類型不能二次編輯,而?interface?可以隨時擴展
interface Animal {
  name: string;
}

// 可以繼續(xù)在原有屬性基礎上,添加新屬性:color
interface Animal {
  color: string;
}
/********************************/
type Animal = {
  name: string;
};
// type類型不支持屬性擴展
// Error: Duplicate identifier 'Animal'
type Animal = {
  color: string;
};

獲取未導出的 Type

某些場景下我們在引入第三方的庫時會發(fā)現(xiàn)想要使用的組件并沒有導出我們需要的組件參數(shù)類型或者返回值類型,這時候我們可以通過 ComponentProps/ ReturnType 來獲取到想要的類型。

// 獲取參數(shù)類型
import { Button } from 'antd' // 但是未導出props type
type ButtonProps = React.ComponentProps<typeof Button> // 獲取props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClick屬性,Omit:TS提供的方法,去除類型中某些項

//即AlertButton用了Button的屬性,但是又去除了onClick
const AlertButton: React.FC<AlertButtonProps> = props => (
  <Button onClick={() => alert('hello')} {...props} />
)

const AlertButton2 = () => (
  //TS報錯:不能將類型“{ onClick: () => void; }”分配給類型“IntrinsicAttributes & AlertButtonProps”。
  //類型“IntrinsicAttributes & AlertButtonProps”上不存在屬性“onClick”。
  <AlertButton onClick={ () => alert('hello')}/>
)
// 獲取返回值類型
function foo() {
  return { baz: 1 };
}
type FooReturn = ReturnType<typeof foo>; // { baz: number }

Props

通常我們使用?type?來定義?Props,為了提高可維護性和代碼可讀性,在日常的開發(fā)過程中我們希望可以添加清晰的注釋。

現(xiàn)在有這樣一個?type:

type OtherProps = {
  name: string;
  color: string;
};

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

在使用的過程中,鼠標移到對應類型會有如下展示:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HyXwVetF-1688693203724)(https://s2.loli.net/2023/07/06/Soi185ZQ2pGUDBC.png)]

React + TypeScript 實踐,前端,React,TypeScript,react.js,typescript,前端

增加相對詳細的注釋,使用時會更清晰,需要注意,注釋需要使用?/**/?,?//?無法被 vscode 識別

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

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

鼠標再次移到對應類型:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jfBt8zlP-1688693203726)(https://s2.loli.net/2023/07/06/HshnAwpJMjF4zVB.png)]

React + TypeScript 實踐,前端,React,TypeScript,react.js,typescript,前端

常用 Props ts 類型

基礎屬性類型

//MyTypeHere、OptionalType為任意定義的類型
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: {}
  /** 列出對象全部數(shù)量的屬性 (推薦使用) */
  obj3: {
    id: string
    title: string
  }
  /** array of objects! (common) */
  objArr: {
    id: string
    title: string
  }[]
  /** 任意數(shù)量屬性的字典,具有相同類型*/
  dict1: {
    [key: string]: MyTypeHere
  }
  /** 作用和dict1完全相同 */
  dict2: Record<string, MyTypeHere>
  /** 任意完全不會調(diào)用的函數(shù) */
  onSomething: Function
  /** 沒有參數(shù)&返回值的函數(shù) */
  onClick: () => void
  /** 攜帶參數(shù)的函數(shù) */
  onChange: (id: number) => void
  /** 攜帶點擊事件的函數(shù) */
  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 // 差, 不支持數(shù)組
  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> // 表單事件, 泛型參數(shù)是event.target的類型
}

Forms and Events

onChange

change?事件,有兩個定義參數(shù)類型的方法。

第一種方法使用推斷的方法簽名(例如:React.FormEvent <HTMLInputElement>?:void

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?提供的委托類型,兩種方法均可。

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,如果目標表單有想要訪問的自定義命名輸入,可以使用類型擴展

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: 用于類型區(qū)分
  • keyof: 獲取 object 的 key
  • O[K]: 屬性查找
  • [K in O]: 映射類型
  • + or - or readonly or ?: 加法、減法、只讀和可選修飾符
  • x ? Y : Z: 用于泛型類型、類型別名、函數(shù)參數(shù)類型的條件類型
  • !: 可空類型的空斷言
  • as: 類型斷言
  • is: 函數(shù)返回類型的類型保護(沒用過)

Tips

使用查找類型訪問組件屬性類型

通過查找類型減少?type?的非必要導出,如果需要提供復雜的?type,應當提取到作為公共 API 導出的文件中。

現(xiàn)在我們有一個 Counter 組件,需要 name 這個必傳參數(shù):

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

在其他引用它的組件中我們有兩種方式獲取到 Counter 的參數(shù)類型

第一種是通過?typeof?操作符(推薦):

// Great
import Counter from './d-tips1'
//獲取Counter的類型并添加新的屬性age
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 中使用函數(shù)聲明

保持一致性,類型/接口的所有成員都通過相同的語法定義。

–strictFunctionTypes?在比較函數(shù)類型時強制執(zhí)行更嚴格的類型檢查,但第二種聲明方式下嚴格檢查不生效。

? 盡量使用這種方式
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	不能將類型“Comparer<Dog>”分配給類型“Comparer<Animal>”。 類型 "Animal" 中缺少屬性 "wow",但類型 "Dog" 中需要該屬性
dogComparer = animalComparer // Ok

//使用這種類型定義檢查較寬松,容易引發(fā)未知問題
interface Comparer1<T> {
  compare(a: T, b: T): number
}
declare let animalComparer1: Comparer1<Animal>
declare let dogComparer1: Comparer1<Dog>
animalComparer1 = dogComparer1 // Ok
dogComparer1 = animalComparer1 // Ok

事件處理

我們在進行事件注冊時經(jīng)常會在事件處理函數(shù)中使用?event?事件對象,例如當使用鼠標事件時我們通過?clientX、clientY?去獲取指針的坐標。

大家可能會想到直接把?event?設置為?any?類型,但是這樣就失去了我們對代碼進行靜態(tài)檢查的意義。

function handleEvent(event: any) {console.log(event.clientY)
}

試想下當我們注冊一個?Touch?事件,然后錯誤的通過事件處理函數(shù)中的?event?對象去獲取其?clientY?屬性的值,在這里我們已經(jīng)將?event?設置為?any?類型,導致?TypeScript?在編譯時并不會提示我們錯誤, 當我們通過?event.clientY?訪問時就有問題了,因為?Touch?事件的?event?對象并沒有?clientY?這個屬性。

通過?interface?對?event?對象進行類型聲明編寫的話又十分浪費時間,幸運的是?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> 過渡事件對象
事件處理函數(shù)類型

當我們定義事件處理函數(shù)時有沒有更方便定義其函數(shù)類型的方式呢?答案是使用?React?聲明文件所提供的?EventHandler?類型別名,通過不同事件的?EventHandler?的類型別名來定義事件處理函數(shù)的類型

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

用法:

const App = () => {
  const onClick: React.MouseEventHandler<HTMLDivElement> = (e) => {
    alert(e);
  };
  return <div onClick={onClick}>TEST</div>;
};

bivarianceHack?為事件處理函數(shù)的類型定義,函數(shù)接收一個?event?對象,并且其類型為接收到的泛型變量?E?的類型, 返回值為?void

關于為何是用 bivarianceHack 而不是(event: E): void,這與 strictfunctionTypes 選項下的功能兼容性有關。(event: E): void,如果該參數(shù)是派生類型,則不能將其傳遞給參數(shù)是基類的函數(shù)。

示例(不太理解其實):

class Animal {
  private x: undefined;
}
class Dog extends Animal {
  private d: undefined;
}
type EventHandler<E extends Animal> = (event: E) => void;
// fails under strictFunctionTyes 不能將類型“(o: Dog) => void”分配給類型“EventHandler<Animal>”。參數(shù)“o”和“event” 的類型不兼容。類型 "Animal" 中缺少屬性 "d",但類型 "Dog" 中需要該屬性。
let z: EventHandler<Animal> = (o: Dog) => {}; 
type BivariantEventHandler<E extends Animal> = {
  bivarianceHack(event: E): void;
}['bivarianceHack'];
let y: BivariantEventHandler<Animal> = (o: Dog) => {};

Promise 類型

在做異步操作時我們經(jīng)常使用?async?函數(shù),函數(shù)調(diào)用時會?return?一個?Promise?對象,可以使用?then?方法添加回調(diào)函數(shù)。Promise<T>?是一個泛型類型,T?泛型變量用于確定?then?方法時接收的第一個回調(diào)函數(shù)的參數(shù)類型。

//這里用泛型T主要是可擴展,畢竟result可能是任何類型
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?的類型。然后聲明了一個 異步函數(shù)?getResponse?并且將函數(shù)返回值的類型定義為?Promise<IResponse<number[]>>?。最后調(diào)用?getResponse?方法會返回一個?promise?類型,通過?then?調(diào)用,此時?then?方法接收的第一個回調(diào)函數(shù)的參數(shù)?response?的類型為,{ message: string, result: number[], success: boolean}?。

泛型參數(shù)的組件

下面這個組件的 name 屬性都是指定了傳參格式,如果想不指定,而是想通過傳入?yún)?shù)的類型去推導實際類型,這就要用到泛型。

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

如果需要外部傳入?yún)?shù)類型,只需 ->

type Props<T> = {
  name: T;
  name2?: T;
};
const TestC: <T extends React.ReactNode>(
  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>
  );
};

什么時候使用泛型

當你的函數(shù),接口或者類:

  • 需要作用到很多類型的時候,舉個 ?

當我們需要一個 id 函數(shù),函數(shù)的參數(shù)可以是任何值,返回值就是將參數(shù)原樣返回,并且其只能接受一個參數(shù),在 js 時代我們會很輕易地甩出一行

const id = arg => arg

由于其可以接受任意值,也就是說我們的函數(shù)的入?yún)⒑头祷刂刀紤摽梢允侨我忸愋?,如果不使用泛型,我們只能重復的進行定義

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 我們可以通過泛型遞歸來實現(xiàn)

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文章來源地址http://www.zghlxwxcb.cn/news/detail-536873.html

到了這里,關于React + TypeScript 實踐的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • React+Typescript清理項目環(huán)境

    React+Typescript清理項目環(huán)境

    上文 創(chuàng)建一個 React+Typescript 項目 我們創(chuàng)建出了一個 React配合Ts開發(fā)的項目環(huán)境 那么 本文 我們先將環(huán)境清理感覺 方便后續(xù)開發(fā) 我們先來聊一下React的一個目錄結構 跟我們之前開發(fā)的React項目還是有一些區(qū)別 public 主要是存放一些靜態(tài)資源文件 例如 html 圖片 icon之類的 其中

    2024年02月13日
    瀏覽(29)
  • React框架:TypeScript支持的JavaScript庫

    React 框架是一個功能強大的 JavaScript 庫,讓用戶可以輕松地構建高度動態(tài)的用戶界面。它借助虛擬 DOM 的思想實現(xiàn)高效的性能,并具有易于使用和靈活的編程接口。隨著越來越多的人開始使用 React ,在不斷的發(fā)展和變化中, React 框架現(xiàn)在加入了 TypeScript 的支持,使其成為一個

    2024年02月11日
    瀏覽(37)
  • react項目運行卡在編譯:您當前運行的TypeScript版本不受@TypeScript eslint/TypeScript estree的官方支持

    react項目運行卡在編譯:您當前運行的TypeScript版本不受@TypeScript eslint/TypeScript estree的官方支持

    錯誤信息具體如下: 搜索了一下,是typescript版本的問題,提示我版本需要在3.3.0和4.5.0中間,我查看了package.json,顯示版本為4.1.3,然后一直給我提示我的版本是4.9.5,全局搜索一下,發(fā)現(xiàn)package-lock.json文件中typescript版本是4.9.5,那么兩個文件有什么區(qū)別呢,如下: 兩個都是依

    2024年01月24日
    瀏覽(32)
  • 【React】React18+Typescript+craco配置最小化批量引入Svg并應用的組件

    【React】React18+Typescript+craco配置最小化批量引入Svg并應用的組件

    無論是哪種 Web UI 框架都不可避免的要與 Svg 打交道,那么批量引入才更方便管理 Svg 。 https://ryanhutzley.medium.com/dynamic-svg-imports-in-create-react-app-d6d411f6d6c6 https://github.com/airbnb/babel-plugin-inline-react-svg/issues/51 https://blog.shianqi.com/2017/12/13/Webpack/SVG-sprite/ https://pganalyze.com/blog/building-svg

    2024年04月10日
    瀏覽(29)
  • 基于React、Typescript和Solidity的NFT完整教程

    基于React、Typescript和Solidity的NFT完整教程

    基于React、Typescript和Solidity的NFT完整教程 了解如何使用 React / Next JS、Solidity 和 Pinata(IPFS) 在以太坊上創(chuàng)建 NFT 市場 課程英文名:NFT Marketplace in React, Typescript Solidity - Full Guide 此視頻教程共5.0小時,中英雙語字幕,畫質(zhì)清晰無水印,源碼附件全 課程編號:300 百度網(wǎng)盤地址:ht

    2023年04月09日
    瀏覽(24)
  • 在 React+Typescript 項目環(huán)境中創(chuàng)建并使用組件

    在 React+Typescript 項目環(huán)境中創(chuàng)建并使用組件

    上文 React+Typescript清理項目環(huán)境 我們將自己創(chuàng)建的項目環(huán)境 好好清理了一下 下面 我們來看組件的創(chuàng)建 組件化在這種數(shù)據(jù)響應式開發(fā)中肯定是非常重要的。 我們現(xiàn)在src下創(chuàng)建一個文件夾 叫 components 就用他專門來處理組件業(yè)務 然后 我們在下面創(chuàng)建一個 hello.tsx 注意 是tsx 別習

    2024年02月12日
    瀏覽(32)
  • React + Typescript + Antd:封裝通用的字典組件DXSelect

    在開發(fā)中,我們經(jīng)常遇到這樣的場景,在表單中,有個下拉框,選擇對應的數(shù)據(jù)。 那么這個下拉框的選項,就是字典。一搬的做法是,通過antd的Select來實現(xiàn),代碼如下:

    2024年02月15日
    瀏覽(41)
  • TypeScript基礎篇 - React-Rollup-TS環(huán)境實戰(zhàn)

    目錄 Rollup+React+TS scripts/rollup.confog.js package.js Rollup 是一個 JavaScript 模塊打包器,可以將小塊代碼編譯成大塊復雜的代碼,例如 library 或應用程序。主要是做組件化的,如vite scripts/rollup.confog.js package.js

    2024年02月16日
    瀏覽(26)
  • 以antd為例 React+Typescript 引入第三方UI庫

    以antd為例 React+Typescript 引入第三方UI庫

    本文 我們來說說 第三方UI庫 其實應用市場上的 第三方UI庫都是非常優(yōu)秀的 那么 react 我們比較熟的肯定還是 antd 我們還是來用它作為演示 這邊 我們先訪問他的官網(wǎng) https://3x.ant.design/index-cn 點擊開始使用 在左側(cè) 有一個 在 TypeScript 中使用 通過圖標我們也可以看出 這個UI庫與

    2024年02月09日
    瀏覽(29)
  • TypeScript 在前端開發(fā)中的應用實踐

    TypeScript 已經(jīng)成為前端開發(fā)領域越來越多開發(fā)者的首選工具。它是一種靜態(tài)類型的超集,由 Microsoft 推出,為開發(fā)者提供了強大的靜態(tài)類型檢查、面向?qū)ο缶幊毯湍K化開發(fā)的特性,解決了 JavaScript 的動態(tài)類型特性帶來的一些問題。 在本篇博文中,我們將深入探討 TypeScript 在

    2024年02月15日
    瀏覽(29)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包