React + TypeScript 常用類型匯總
本文適合對TypeScript感興趣的小伙伴閱讀~
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~
一、前言
基本prop類型示例 有用的 React Prop 類型示例 函數(shù)組件 類組件 form和event Context forwardRef/createRef 有用的hooks HOC Linting
二、基本prop類型示例
常規(guī)的程序中使用的 TypeScript 類型列表:
type AppProps = {
message: string;
count: number;
disabled: boolean;
/** 一個類型的數(shù)組!*/
names: string[];
/** 用于指定精確字符串值的字符串文字,使用聯(lián)合類型將它們連接在一起 */
status: "waiting" | "success";
/** 任何對象,只要你不使用它的屬性(不常見,但用作占位符)*/
obj: object;
obj2: {}; // 和 `object` 差不多,和 `Object` 完全一樣
/** 具有任意數(shù)量屬性的對象 (PREFERRED) */
obj3: {
id: string;
title: string;
};
/** 對象數(shù)組!(常見的) */
objArr: {
id: string;
title: string;
}[];
/** 具有任意數(shù)量的相同類型屬性的 dict 對象 */
dict1: {
[key: string]: MyTypeHere;
};
dict2: Record<string, MyTypeHere>; // 相當于 dict1
/** 任何函數(shù),只要你不調(diào)用它(不推薦) */
onSomething: Function;
/** 不接受或不返回任何內(nèi)容的函數(shù)(非常常見) */
onClick: () => void;
/** 帶有命名props的函數(shù)(非常常見) */
onChange: (id: number) => void;
/** 接受事件的函數(shù)類型語法(非常常見) */
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
/** 接受事件的替代函數(shù)類型語法(非常常見) */
onClick(event: React.MouseEvent<HTMLButtonElement>): void;
/** 一個可選的props(非常常見!) */
optional?: OptionalType;
};三、有用的 React Prop 類型示例
export declare interface AppProps {
children?: React.ReactNode; // 最好,接受 React 可以渲染的所有內(nèi)容
childrenElement: JSX.Element; // 單個 React 元素
style?: React.CSSProperties; // 傳遞樣式props
onChange?: React.FormEventHandler<HTMLInputElement>; // 形成事件!泛型參數(shù)是 event.target 的類型
props: Props & React.ComponentPropsWithoutRef<"button">; // 模擬按鈕元素的所有 props 并明確不轉(zhuǎn)發(fā)其 ref
props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // 模擬 MyButtonForwardedRef 的所有 props 并顯式轉(zhuǎn)發(fā)其 ref
}
type還是interface?
在創(chuàng)作庫或第 3 方環(huán)境類型定義時,始終用于公共 API 的定義,因為這允許使用者在缺少某些定義時通過聲明合并來擴展它們。
考慮為您的 React 組件 Props 和 State 使用,以保持一致性并且因為它受到更多限制。
四、函數(shù)組件
type AppProps = {
message: string;
}; /* 如果導(dǎo)出使用 `interface` 以便消費者可以擴展 */
// 聲明函數(shù)組件的最簡單方法;推斷返回類型。
const App = ({ message }: AppProps) => <div>{message}</div>;
// 您可以選擇注釋返回類型,這樣如果您不小心返回了其他類型,則會引發(fā)錯誤
const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;
// 你也可以內(nèi)聯(lián)類型聲明;消除了命名props類型,但看起來重復(fù)
const App = ({ message }: { message: string }) => <div>{message}</div>;
hook
const [state, setState] = useState(false);
// `state` 被推斷為布爾值
// `setState` 只接受布爾值許多鉤子都是用 null-ish 默認值初始化的,你可能想知道如何提供類型。
顯式聲明類型,并使用聯(lián)合類型:
const [user, setUser] = useState<User | null>(null);
setUser(newUser);
const [user, setUser] = useState<User>({} as User);
setUser(newUser);
import { useReducer } from "react";
const initialState = { count: 0 };
type ACTIONTYPE =
| { type: "increment"; payload: number }
| { type: "decrement"; payload: string };
function reducer(state: typeof initialState, action: ACTIONTYPE) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - Number(action.payload) };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
-
</button>
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
</>
);
}
useEffect和都useLayoutEffect用于執(zhí)行副作用并返回一個可選的清理函數(shù),這意味著如果它們不處理返回值,則不需要類型。
使用 時useEffect,注意不要返回除函數(shù) or 以外的任何東西undefined,否則 TypeScript 和 React 都會提示你。
這在使用箭頭函數(shù)時可能很微妙:
function DelayedEffect(props: { timerMs: number }) {
const { timerMs } = props;
useEffect(
() =>
setTimeout(() => {
/* do stuff */
}, timerMs),
[timerMs]
);
// 反面例子!setTimeout 隱式返回一個數(shù)字
// 因為箭頭函數(shù)體沒有用大括號括起來
return null;
}
在 TypeScript 中,返回一個只讀或可變useRef的引用,取決于您的類型參數(shù)是否完全覆蓋初始值。選擇一個適合您的用例。
僅提供元素類型作為參數(shù),并null用作初始值。.current在這種情況下,返回的引用將具有由 React 管理的只讀引用TypeScript 期望將此 ref 提供給元素的ref prop:
function Foo() {
// - 如果可能,請盡可能具體。例如,HTMLDivElement
// 比 HTMLElement 好,也比 Element 好得多。
// - 從技術(shù)上講,這會返回 RefObject<HTMLDivElement>
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// 注意 ref.current 可能為空。這是意料之中的
// 有條件地渲染被引用的元素,或者你可能忘記分配它
if (!divRef.current) throw Error("divRef is not assigned");
// 現(xiàn)在 divRef.current 肯定是 HTMLDivElement
doSomethingWith(divRef.current);
});
// 將 ref 賦予一個元素,以便 React 可以管理它
return <div ref={divRef}>etc</div>;
}
const divRef = useRef<HTMLDivElement>(null!);
// 無需檢查是否為空
doSomethingWith(divRef.current);
function Foo() {
// 從技術(shù)上講,這將返回 MutableRefObject<number | 空>
const intervalRef = useRef<number | null>(null);
// 你自己管理 ref(這就是為什么它被稱為 MutableRefObject!)
useEffect(() => {
intervalRef.current = setInterval(...);
return () => clearInterval(intervalRef.current);
}, []);
// ref 不會傳遞給任何元素的 "ref" 屬性
return <button onClick={/* clearInterval the ref */}>Cancel timer</button>;
}
import { useState } from "react";
export function useLoading() {
const [isLoading, setState] = useState(false);
const load = (aPromise: Promise<any>) => {
setState(true);
return aPromise.finally(() => setState(false));
};
return [isLoading, load] as const; // 推斷 [boolean, typeof load] 而不是 (boolean | typeof load)[]
}五、類組件
type MyProps = {
// 使用 `interface` 也可以
message: string;
};
type MyState = {
count: number; // 像這樣
};
class App extends React.Component<MyProps, MyState> {
state: MyState = {
// 可選的第二個注解,用于更好的類型推斷
count: 0,
};
render() {
return (
<div>
{this.props.message} {this.state.count}
</div>
);
}
}
class App extends React.Component<{ message: string }, { count: number }> {
state = { count: 0 };
render() {
return (
<div onClick={() => this.increment(1)}>
{this.props.message} {this.state.count}
</div>
);
}
increment = (amt: number) => {
this.setState((state) => ({
count: state.count + amt,
}));
};
}
class App extends React.Component<{
message: string;
}> {
pointer: number; // 像這樣
componentDidMount() {
this.pointer = 3;
}
render() {
return (
<div>
{this.props.message} and {this.pointer}
</div>
);
}
}
派生狀態(tài)可以使用鉤子來實現(xiàn),這也可以幫助設(shè)置memoization。
class Comp extends React.Component<Props, State> {
static getDerivedStateFromProps(
props: Props,
state: State
): Partial<State> | null {
//
}
}
class Comp extends React.Component<
Props,
ReturnType<typeof Comp["getDerivedStateFromProps"]>
> {
static getDerivedStateFromProps(props: Props) {}
}
type CustomValue = any;
interface Props {
propA: CustomValue;
}
interface DefinedState {
otherStateField: string;
}
type State = DefinedState & ReturnType<typeof transformPropsToState>;
function transformPropsToState(props: Props) {
return {
savedPropA: props.propA, // 保存以備memoization
derivedState: props.propA,
};
}
class Comp extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
otherStateField: "123",
...transformPropsToState(props),
};
}
static getDerivedStateFromProps(props: Props, state: State) {
if (isEqual(props.propA, state.savedPropA)) return null;
return transformPropsToState(props);
}
}六、form和event
如果需要單獨定義事件處理程序,IDE 工具在這里真的很方便,因為 @type 定義帶有豐富的類型。輸入要查找的內(nèi)容,通常自動完成功能會為您提供幫助。onChange這是表單事件的樣子
type State = {
text: string;
};
class App extends React.Component<Props, State> {
state = {
text: "",
};
// 在 = 的右側(cè)輸入
onChange = (e: React.FormEvent<HTMLInputElement>): void => {
this.setState({ text: e.currentTarget.value });
};
render() {
return (
<div>
<input type="text" value={this.state.text} onChange={this.onChange} />
</div>
);
}
}
// 在 = 的左側(cè)輸入
onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({text: e.currentTarget.value})
}
鍵入 onSubmit,在表單中包含不受控制的組件
如果不太關(guān)心事件的類型,可以使用 React.SyntheticEvent。
如果目標表單具有想要訪問的自定義命名輸入,可以使用類型斷言:
<form
ref={formRef}
onSubmit={(e: React.SyntheticEvent) => {
e.preventDefault();
const target = e.target as typeof e.target & {
email: { value: string };
password: { value: string };
};
const email = target.email.value; // 類型檢查!
const password = target.password.value; // 類型檢查!
// ...
}}
>
<div>
<label>
Email:
<input type="email" name="email" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Log in" />
</div>
</form>
七、Context
import { createContext } from "react";
interface AppContextInterface {
name: string;
author: string;
url: string;
}
const AppCtx = createContext<AppContextInterface | null>(null);
// 應(yīng)用程序中的提供程序
const sampleAppContext: AppContextInterface = {
name: "Using React Context in a Typescript App",
author: "thehappybug",
url: "http://www.example.com",
};
export const App = () => (
<AppCtx.Provider value={sampleAppContext}>...</AppCtx.Provider>
);
// 在你的應(yīng)用中使用
import { useContext } from "react";
export const PostInfo = () => {
const appContext = useContext(AppCtx);
return (
<div>
Name: {appContext.name}, Author: {appContext.author}, Url:{" "}
{appContext.url}
</div>
);
};
interface ContextState {
// 使用上下文設(shè)置你想要處理的狀態(tài)類型,例如
name: string | null;
}
// 設(shè)置一個空對象為默認狀態(tài)
const Context = createContext({} as ContextState);
// 像在 JavaScript 中一樣設(shè)置上下文提供程序
import { createContext, useContext } from "react";
const currentUserContext = createContext<string | undefined>(undefined);
function EnthusasticGreeting() {
const currentUser = useContext(currentUserContext);
return <div>HELLO {currentUser!.toUpperCase()}!</div>;
}
function App() {
return (
<currentUserContext.Provider value="Anders">
<EnthusasticGreeting />
</currentUserContext.Provider>
);
}
const currentUserContext = createContext<string | undefined>(undefined);
// ^^^^^^^^^^^^^^^^^^return <div>HELLO {currentUser!.toUpperCase()}!</div>;
//
這是不幸的,因為我們知道稍后在我們的應(yīng)用程序中,a Provider將填充上下文。
有幾個解決方案:
1、可以通過斷言非空來解決這個問題:
const currentUserContext = createContext<string>(undefined!);
import { createContext, useContext } from "react";
/**
* 創(chuàng)建上下文和提供者的助手,沒有預(yù)先的默認值,并且
* 無需一直檢查未定義。
*/
function createCtx<A extends {} | null>() {
const ctx = createContext<A | undefined>(undefined);
function useCtx() {
const c = useContext(ctx);
if (c === undefined)
throw new Error("useCtx must be inside a Provider with a value");
return c;
}
return [useCtx, ctx.Provider] as const; // 'as const' 使 TypeScript 推斷出一個元組
}
// 用法:
// 我們?nèi)匀恍枰付ㄒ粋€類型,但沒有默認值!
export const [useCurrentUserName, CurrentUserProvider] = createCtx<string>();
function EnthusasticGreeting() {
const currentUser = useCurrentUserName();
return <div>HELLO {currentUser.toUpperCase()}!</div>;
}
function App() {
return (
<CurrentUserProvider value="Anders">
<EnthusasticGreeting />
</CurrentUserProvider>
);
}
import { createContext, useContext } from "react";
/**
* 創(chuàng)建上下文和提供者的助手,沒有預(yù)先的默認值,并且
* 無需一直檢查未定義。
*/
function createCtx<A extends {} | null>() {
const ctx = createContext<A | undefined>(undefined);
function useCtx() {
const c = useContext(ctx);
if (c === undefined)
throw new Error("useCtx must be inside a Provider with a value");
return c;
}
return [useCtx, ctx.Provider] as const; // 'as const' 使 TypeScript 推斷出一個元組
}
// 用法
export const [useCtx, SettingProvider] = createCtx<string>(); // 指定類型,但不需要預(yù)先指定值
export function App() {
const key = useCustomHook("key"); // 從鉤子中獲取值,必須在組件中
return (
<SettingProvider value={key}>
<Component />
</SettingProvider>
);
}
export function Component() {
const key = useCtx(); // 仍然可以在沒有空檢查的情況下使用!
return <div>{key}</div>;
}
import {
createContext,
Dispatch,
PropsWithChildren,
SetStateAction,
useState,
} from "react";
export function createCtx<A>(defaultValue: A) {
type UpdateType = Dispatch<SetStateAction<typeof defaultValue>>;
const defaultUpdate: UpdateType = () => defaultValue;
const ctx = createContext({
state: defaultValue,
update: defaultUpdate,
});
function Provider(props: PropsWithChildren<{}>) {
const [state, update] = useState(defaultValue);
return <ctx.Provider value={{ state, update }} {...props} />;
}
return [ctx, Provider] as const; // 或者,[typeof ctx, typeof Provider]
}
// 用法
import { useContext } from "react";
const [ctx, TextProvider] = createCtx("someText");
export const TextContext = ctx;
export function App() {
return (
<TextProvider>
<Component />
</TextProvider>
);
}
export function Component() {
const { state, update } = useContext(TextContext);
return (
<label>
{state}
<input type="text" onChange={(e) => update(e.target.value)} />
</label>
);
}八、forwardRef/createRef
檢查Hooks 部分的useRef.
createRef:
import { createRef, PureComponent } from "react";
class CssThemeProvider extends PureComponent<Props> {
private rootRef = createRef<HTMLDivElement>(); // 像這樣
render() {
return <div ref={this.rootRef}>{this.props.children}</div>;
}
}
import { forwardRef, ReactNode } from "react";
interface Props {
children?: ReactNode;
type: "submit" | "button";
}
export type Ref = HTMLButtonElement;
export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
<button ref={ref} className="MyClassName" type={props.type}>
{props.children}
</button>
));
type ClickableListProps<T> = {
items: T[];
onSelect: (item: T) => void;
mRef?: React.Ref<HTMLUListElement> | null;
};
export function ClickableList<T>(props: ClickableListProps<T>) {
return (
<ul ref={props.mRef}>
{props.items.map((item, i) => (
<li key={i}>
<button onClick={(el) => props.onSelect(item)}>Select</button>
{item}
</li>
))}
</ul>
);
}
// 重新聲明 forwardRef
declare module "react" {
function forwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}
// 只需像以前一樣編寫組件!
import { forwardRef, ForwardedRef } from "react";
interface ClickableListProps<T> {
items: T[];
onSelect: (item: T) => void;
}
function ClickableListInner<T>(
props: ClickableListProps<T>,
ref: ForwardedRef<HTMLUListElement>
) {
return (
<ul ref={ref}>
{props.items.map((item, i) => (
<li key={i}>
<button onClick={(el) => props.onSelect(item)}>Select</button>
{item}
</li>
))}
</ul>
);
}
export const ClickableList = forwardRef(ClickableListInner);九、有用的hooks
import { useState } from "react";
// 用法
function App() {
// 類似于 useState 但第一個 arg 是本地存儲中值的鍵。
const [name, setName] = useLocalStorage<string>("name", "Bob");
return (
<div>
<input
type="text"
placeholder="Enter your name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
}
// Hook
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
// 狀態(tài)來存儲我們的值
// 將初始狀態(tài)函數(shù)傳遞給 useState,因此邏輯只執(zhí)行一次
const [storedValue, setStoredValue] = useState<T>(() => {
try {
// 按鍵從本地存儲中獲取
const item = window.localStorage.getItem(key);
// 解析存儲的 json 或者如果沒有則返回 initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// 如果錯誤也返回initialValue
console.log(error);
return initialValue;
}
});
// 返回 useState 的 setter 函數(shù)的包裝版本,它...
// ... 將新值保存到 localStorage。
const setValue = (value: T | ((val: T) => T)) => {
try {
// 允許 value 是一個函數(shù),所以我們有與 useState 相同的 API
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// 保存狀態(tài)
setStoredValue(valueToStore);
// 保存到本地存儲
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// 更高級的實現(xiàn)將處理錯誤情況
console.log(error);
}
};
return [storedValue, setValue];
}
import { useState, useEffect } from 'react';
function App() {
const columnCount = useMedia<number>(
// 媒體查詢
['(min-width: 1500px)', '(min-width: 1000px)', '(min-width: 600px)'],
// 列數(shù)(與上述按數(shù)組索引的媒體查詢有關(guān))
[5, 4, 3],
// 默認列數(shù)
2
);
// 創(chuàng)建列高數(shù)組(從 0 開始)
let columnHeights = new Array(columnCount).fill(0);
// 創(chuàng)建包含每列項目的數(shù)組數(shù)組
let columns = new Array(columnCount).fill().map(() => []) as Array<DataProps[]>;
(data as DataProps[]).forEach(item => {
// 獲取最短列的索引
const shortColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
// 添加項目
columns[shortColumnIndex].push(item);
// 更新高度
columnHeights[shortColumnIndex] += item.height;
});
// 渲染列和項目
return (
<div className="App">
<div className="columns is-mobile">
{columns.map(column => (
<div className="column">
{column.map(item => (
<div
className="image-container"
style={{
// 將圖像容器大小調(diào)整為圖像的縱橫比
paddingTop: (item.height / item.width) * 100 + '%'
}}
>
<img src={item.image} alt="" />
</div>
))}
</div>
))}
</div>
</div>
);
}
// Hook
const useMedia = <T>(queries: string[], values: T[], defaultValue: T) => {
// 包含每個查詢的媒體查詢列表的數(shù)組
const mediaQueryLists = queries.map(q => window.matchMedia(q));
// 根據(jù)匹配的媒體查詢獲取值的函數(shù)
const getValue = () => {
// 獲取第一個匹配的媒體查詢的索引
const index = mediaQueryLists.findIndex(mql => mql.matches);
// 返回相關(guān)值,如果沒有則返回默認值
return values?.[index] || defaultValue;
};
// 匹配值的狀態(tài)和設(shè)置器
const [value, setValue] = useState<T>(getValue);
useEffect(
() => {
// 事件監(jiān)聽回調(diào)
// 注意:通過在 useEffect 之外定義 getValue,我們確保它具有 ...
// ... 鉤子參數(shù)的當前值(因為這個鉤子回調(diào)在掛載時創(chuàng)建一次)。
const handler = () => setValue(getValue);
// 使用上述處理程序為每個媒體查詢設(shè)置一個偵聽器作為回調(diào)。
mediaQueryLists.forEach(mql => mql.addListener(handler));
// 在清理時移除監(jiān)聽器
return () => mediaQueryLists.forEach(mql => mql.removeListener(handler));
},
[] // 空數(shù)組確保效果僅在掛載和卸載時運行
);
return value;
}
// 用法
const task = useAsyncTask(async (data: any) => await myApiRequest(data));
task.run(data);
useEffect(() => {
console.log(task.status); // 'IDLE' | 'PROCESSING' | 'ERROR' | 'SUCCESS';
}, [task.status]);
// 執(zhí)行
import { useCallback, useState } from "react";
type TStatus = "IDLE" | "PROCESSING" | "ERROR" | "SUCCESS";
function useAsyncTask<T extends any[], R = any>(
task: (...args: T) => Promise<R>
) {
const [status, setStatus] = useState<TStatus>("IDLE");
const [message, setMessage] = useState("");
const run = useCallback(async (...arg: T) => {
setStatus("PROCESSING");
try {
const resp: R = await task(...arg);
setStatus("SUCCESS");
return resp;
} catch (error) {
let message = error?.response?.data?.error?.message || error.message;
setMessage(message);
setStatus("ERROR");
throw error;
}
}, []);
const reset = useCallback(() => {
setMessage("");
setStatus("IDLE");
}, []);
return {
run,
status,
message,
reset,
};
}
export default useAsyncTask;
export function useFetch(request: RequestInfo, init?: RequestInit) {
const [response, setResponse] = useState<null | Response>(null);
const [error, setError] = useState<Error | null>();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const abortController = new AbortController();
setIsLoading(true);
(async () => {
try {
const response = await fetch(request, {
...init,
signal: abortController.signal,
});
setResponse(await response?.json());
setIsLoading(false);
} catch (error) {
if (isAbortError(error)) {
return;
}
setError(error as any);
setIsLoading(false);
}
})();
return () => {
abortController.abort();
};
}, [init, request]);
return { response, error, isLoading };
}
// type guards
function isAbortError(error: any): error is DOMException {
if (error && error.name === "AbortError") {
return true;
}
return false;
}十、HOC
一個 HOC 示例
注入props
interface WithThemeProps {
primaryColor: string;
}
在組件中的使用
在組件的接口上提供可用的props,但在包裝在 HoC 中時為組件的消費者減去。
interface Props extends WithThemeProps {
children?: React.ReactNode;
}
class MyButton extends React.Component<Props> {
public render() {
// 使用主題和其他props渲染元素。
}
private someInternalMethod() {
// 主題值也可在此處作為props使用。
}
}
export default withTheme(MyButton);使用組件
現(xiàn)在,在使用組件時,可以省略primaryColor props或覆蓋通過上下文提供的props。
<MyButton>Hello button</MyButton> // 有效
<MyButton primaryColor="#333">Hello Button</MyButton> // 同樣有效
聲明 HoC
實際的 HoC。
export function withTheme<T extends WithThemeProps = WithThemeProps>(
WrappedComponent: React.ComponentType<T>
) {
// 嘗試為 React 開發(fā)工具創(chuàng)建一個不錯的 displayName。
const displayName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
// 創(chuàng)建內(nèi)部組件。這里計算出來的 Props 類型是魔法發(fā)生的地方。
const ComponentWithTheme = (props: Omit<T, keyof WithThemeProps>) => {
// 獲取要注入的props。這可以通過上下文來完成。
const themeProps = useTheme();
// props隨后出現(xiàn),因此可以覆蓋默認值。
return <WrappedComponent {...themeProps} {...(props as T)} />;
};
ComponentWithTheme.displayName = `withTheme(${displayName})`;
return ComponentWithTheme;
}
// 向組件注入靜態(tài)值,以便始終提供它們
export function inject<TProps, TInjectedKeys extends keyof TProps>(
Component: React.JSXElementConstructor<TProps>,
injector: Pick<TProps, TInjectedKeys>
) {
return function Injected(props: Omit<TProps, TInjectedKeys>) {
return <Component {...(props as TProps)} {...injector} />;
};
}
十一、Linting
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint
"scripts": {
"lint": "eslint 'src/**/*.ts'"
},
module.exports = {
env: {
es6: true,
node: true,
jest: true,
},
extends: "eslint:recommended",
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
parserOptions: {
ecmaVersion: 2017,
sourceType: "module",
},
rules: {
indent: ["error", 2],
"linebreak-style": ["error", "unix"],
quotes: ["error", "single"],
"no-console": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ vars: "all", args: "after-used", ignoreRestSiblings: false },
],
"@typescript-eslint/explicit-function-return-type": "warn", // 考慮對對象字面量和函數(shù)返回類型使用顯式注釋,即使它們可以被推斷出來。
"no-empty": "warn",
},
};
{
"extends": [
"airbnb",
"prettier",
"prettier/react",
"plugin:prettier/recommended",
"plugin:jest/recommended",
"plugin:unicorn/recommended"
],
"plugins": ["prettier", "jest", "unicorn"],
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"es6": true,
"browser": true,
"jest": true
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"parser": "typescript-eslint-parser",
"rules": {
"no-undef": "off"
}
}
]
}
面試題庫推薦



十二、最后
在看源碼前,我們先去官方文檔復(fù)習下框架設(shè)計理念、源碼分層設(shè)計 閱讀下框架官方開發(fā)人員寫的相關(guān)文章 借助框架的調(diào)用棧來進行源碼的閱讀,通過這個執(zhí)行流程,我們就完整的對源碼進行了一個初步的了解 接下來再對源碼執(zhí)行過程中涉及的所有函數(shù)邏輯梳理一遍
關(guān)注我,一起攜手進階
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~
