序言
本文會側重於TypeScript(以下簡稱TS)在項目中與React的結合使用情況,而非TS的基本概念。關於TS的類型查看可以使用在線TS工具👉TypeScript游樂場
React元素相關
React元素相關的類型主要包括ReactNode、ReactElement、JSX.Element。
ReactNode。表示任意類型的React節點,這是個聯合類型,包含情況眾多;ReactElement/JSX。從使用表現上來看,可以認為這兩者是一致的,屬於ReactNode的子集,表示“原生的DOM組件”或“自定義組件的執行結果”。
使用示例如下:
const MyComp: React.FC<{ title: string; }> = ({title}) => <h2>{title}</h2>;
// ReactNode
const a: React.ReactNode =
null ||
undefined || <div>hello</div> || <MyComp title="world" /> ||
"abc" ||
123 ||
true;
// ReactElement和JSX.Element
const b: React.ReactElement = <div>hello world</div> || <MyComp title="good" />;
const c: JSX.Element = <MyComp title="good" /> || <div>hello world</div>;
原生DOM相關
原生的 DOM 相關的類型,主要有以下這么幾個:Element、 HTMLElement、HTMLxxxElment。
簡單來說: Element = HTMLElement + SVGElement。
SVGElement一般開發比較少用到,而HTMLElement卻非常常見,它的子類型包括HTMLDivElement、HTMLInputElement、HTMLSpanElement等等。
因此我們可以得知,其關系為:Element > HTMLElement > HTMLxxxElement,原則上是盡量寫詳細。
React合成事件相關
在 React 中,原生事件被處理成了React 事件,其內部是通過事件委托來優化內存,減少DOM事件綁定的。言歸正傳,React 事件的通用格式為[xxx]Event,常見的有MouseEvent、ChangeEvent、TouchEvent,是一個泛型類型,泛型變量為觸發該事件的 DOM 元素類型。
示例如下:
// input輸入框輸入文字
const handleInputChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
console.log(evt);
};
// button按鈕點擊
const handleButtonClick = (evt: React.MouseEvent<HTMLButtonElement>) => {
console.log(evt);
};
// 移動端觸摸div
const handleDivTouch = (evt: React.TouchEvent<HTMLDivElement>) => {
console.log(evt);
};
與hooks的結合
在hooks中,並非全部鈎子都與TS有強關聯,比如useEffect就不依賴TS做類型定義,我們挑選比較常見的幾個和TS強關聯的鈎子來看看。
useState
- 如果初始值能說明類型,就不用給 useState 指明泛型變量;
// ❌這樣寫是不必要的,因為初始值0已經能說明count類型
const [count, setCount] = useState<number>(0);
// ✅這樣寫好點
const [count, setCount] = useState(0);
- 如果初始值是
null或undefined,那就要通過泛型手動傳入你期望的類型,並在訪問屬性的時候通過可選鏈來規避語法錯誤。
interface IUser {
name: string;
age: number;
}
const [user, setUser] = React.useState<IUser | null>(null);
console.log(user?.name);
useRef
這個 hook 比較特別,它通常有兩種用途:
- 用來連接 DOM,以獲取到 DOM 元素;
// 連接DOM,初始值賦值為null,不能是undefined,如要指明泛型變量需要具體到HTMLxxxElement
// 如果我們確定inputRef.current在調用時一定是有值的,可以使用非空斷言,在null后添加!
const inputRef = useRef<HTMLInputElement>(null!);
const handleClick = () => {
inputRef.current.focus(); // 當然不用非空斷言,用inputEl.current?.focus()可選鏈也是可以的
}
return (
<input ref={inputRef} />
<button onClick={handleClick}>點擊</button>
)
- 用來存儲變量,由於是存儲在函數式組件的外部,比起 useState,它不會存在異步更新的問題,也不會存在由
capture-value特性引發的過時變量的問題,但是要注意賦值后由於ref引用沒變,不會引起重渲染。
// 通過初始值來自動指明泛型變量類型
const sum = useRef(0);
// 通過.current直接賦值
sum.current = 3;
// 不存在異步更新問題
console.log(sum.current); // 3
useSelector
useSelector用於獲取store中的狀態,其第一個固定參數為函數,函數的入參即為store,而store的類型RootState需要在store中提前定義好,一種常見的定義如下:
在store.ts中:
const store = createStore(rootReducer);
export type RootState = ReturnType<typeof rootReducer>;
使用時:
const { var1, var2 } = useSelector((store: RootState) => store.xxx);
自定義 hook
如果我們需要仿照 useState 的形式,返回一個數組出去,則需要在返回值的末尾使用as const,標記這個返回值是個常量,否則返回的值將被推斷成聯合類型。
const useInfo = () => {
const [age, setAge] = useState(0);
return [age, setAge] as const; // 類型為一個元組,[number, React.Dispatch<React.SetStateAction<number>>]
};
redux相關
對於action的定義,我們可以使用官方暴露的AnyAction,放寬對於action內部鍵值對的限制,如下:
import { AnyAction } from "redux";
const DEF_STATE = {
count: 0,
type: 'integer'
};
// 使用redux的AnyAction放寬限制
function countReducer(state = DEF_STATE, action: AnyAction) {
switch (action.type) {
case "INCREASE_COUNT":
return {
...state,
count: state.count + 1,
};
case "DECREASE_COUNT":
return {
...state,
count: state.count - 1,
};
default:
return state;
}
}
export default countReducer;
規約
- 子組件的入參命名為
[組件名]Props,如:
// 比如當前組件名為InfoCard
export interface InfoCardProps {
name: string;
age: number;
}
- interface接口類型以大寫開頭;
- 為后端接口的出入參書寫interface,同時使用利於編輯器提示的jsdoc風格做注釋,如:
export interface GetUserInfoReqParams {
/** 名字 */
name: string;
/** 年齡 */
age: number;
/** 性別 */
gender: string;
}
其他
鍵名或鍵值不確定如何處理?
// 表示鍵名不確定,鍵值限制為number類型
export interface NotSureAboutKey {
[key: string]: number;
}
// 當鍵名鍵值都不確定時,以下接口對任何對象都是適用的
export interface AllNotSure {
[key: string]: any;
}
如何在接口中使用泛型變量?
所謂泛型,就是預定義類型。它的目的是:達到類型定義的局部靈活,提高復用性。我們通常會在接口中使用泛型,如:
// 通常,我們會為接口的泛型變量指定一個默認類型
interface IHuman<T = unknown> {
name: string;
age: number;
gender: T;
}
// 其他地方使用時
const youngMan: IHuman<string> = {
name: 'zhangsan',
age: 18,
gender: 'male'
}
