發(fā)布于:2021-02-05 13:55:20
0
338
0
導(dǎo)言
這些注釋應(yīng)該有助于更好地理解TypeScript
,并且在需要查找如何在特定情況下利用TypeScript時(shí)可能會有所幫助。所有示例都基于TypeScript 3.2。
React Hooks
在“TypeScript注釋”系列的這一部分中,我們將了解如何使用TypeScript鍵入React Hooks,并在此過程中了解有關(guān)hooks的更多信息。
我們將參考官方React文檔中關(guān)于hooks的內(nèi)容,在需要了解更多關(guān)于hooks的信息或需要特定問題的具體答案時(shí),這是一個(gè)非常有價(jià)值的來源。
通常,在16.8中添加了hooks來React,使開發(fā)人員能夠在函數(shù)組件中使用狀態(tài),這在類組件中是唯一可能的。文檔說明有基本掛鉤和附加掛鉤。
基本掛鉤有useState
、useEffect
、useContext
,附加掛鉤包括useReducer
、useCallback
、useMemo
、useRef
。
useState
讓我們從useState
開始,這是一個(gè)基本的hooks,顧名思義,應(yīng)該用于狀態(tài)處理。
const [state, setState] = useState(initialState);
看看上面的例子,我們可以看到useState
返回一個(gè)狀態(tài)值以及一個(gè)函數(shù)來更新它。但是我們?nèi)绾屋斎?/span>state
和setState
?
有趣的是,TypeScript可以推斷出類型,這意味著通過定義一個(gè)initialState
,可以推斷出狀態(tài)值和更新函數(shù)的類型。
const [state, setState] = useState(0);
// const state: number
const [state, setState] = useState("one");
// const state: string
const [state, setState] = useState({
id: 1,
name: "Test User"
});
/*
const state: {
id: number;
name: string;
}
*/
const [state, setState] = useState([1, 2, 3, 4]);
// const state: number[]
上面的例子很好地說明了我們不需要手工打字。但是如果我們沒有初始狀態(tài)呢?上述示例在嘗試更新狀態(tài)時(shí)會中斷。
我們可以在需要時(shí)使用手動(dòng)定義類型useState。
const [state, setState] = useState(null);
// const state: number | null
const [state, setState] = useState(null);
// const state: {id: number; name: string;} | null
const [state, setState] = useState(undefined);
// const state: number | null
可能還需要注意的是,與setState
類內(nèi)組件相反,使用update hook函數(shù)需要返回完整的狀態(tài)。
const [state, setState] = useState({
id: 1,
name: "Test User"
});
/*
const state: {
id: number;
name: string;
}
*/
setState({name: "New Test User Name"}); // Error! Property 'id' is missing
setState(state => {
return {...state, name: "New Test User Name"}
}); // Works!
另一件有趣的事情是,我們可以通過將函數(shù)傳遞給useState
const [state, setState] = useState(() => {
props.init + 1;
});
// const state: number
同樣,TypeScript可以推斷狀態(tài)類型。
這意味著我們在使用useState
時(shí)不需要做太多的工作,只有在沒有初始狀態(tài)的情況下,因?yàn)樵诔跏间秩緯r(shí)可能會計(jì)算實(shí)際的狀態(tài)形狀。
useEffect
另一個(gè)基本的hooks是useEffect
,它在處理諸如日志記錄、突變或訂閱事件偵聽器之類的副作用時(shí)非常有用。一般來說,useEffect
需要一個(gè)函數(shù)來運(yùn)行一個(gè)效果,該效果可以選擇性地返回一個(gè)clean-up函數(shù),該函數(shù)對于取消訂閱和刪除偵聽器非常有用。此外,useEffect
還可以提供第二個(gè)參數(shù),該參數(shù)包含一個(gè)值數(shù)組,以確保僅當(dāng)其中一個(gè)值被刪除時(shí),效果函數(shù)才會運(yùn)行改變。這確保了我們可以控制何時(shí)運(yùn)行效果。
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source]
);
以文檔中的原始示例為例,我們可以注意到在使用useEffect
時(shí)不需要任何額外的鍵入。
TypeScript在嘗試返回非函數(shù)或effect函數(shù)中未定義的內(nèi)容時(shí)會抱怨。
useEffect(
() => {
subscribe();
return null; // Error! Type 'null' is not assignable to void | (() => void)
}
);
這也適用于useLayoutEffect
,只在運(yùn)行效果時(shí)不同。
useContext
useContext
需要一個(gè)上下文對象,并返回所提供上下文的值。當(dāng)提供者更新上下文時(shí),會觸發(fā)重新呈現(xiàn)??匆幌孪旅娴睦討?yīng)該會明白:
const ColorContext = React.createContext({ color: "green" });
const Welcome = () => {
const { color } = useContext(ColorContext);
returnWelcome;
};
同樣,我們不需要做太多關(guān)于類型的事情。類型是推斷出來的。
const ColorContext = React.createContext({ color: "green" });
const { color } = useContext(ColorContext);
// const color: string
const UserContext = React.createContext({ id: 1, name: "Test User" });
const { id, name } = useContext(UserContext);
// const id: number
// const name: string
useReducer
有時(shí)我們處理的是更復(fù)雜的狀態(tài),這可能也取決于前一個(gè)狀態(tài)。useReducer
接受一個(gè)函數(shù),該函數(shù)根據(jù)先前的狀態(tài)和操作計(jì)算特定的狀態(tài)。以下示例摘自官方文檔。
const [state, dispatch] = useReducer(reducer, initialArg, init);
如果我們看一下文檔中的示例,就會注意到我們需要做一些額外的鍵入工作。檢查稍微調(diào)整的示例:
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (<> Count: {state.count}dispatch({type: 'increment'})}>+dispatch({type: 'decrement'})}>-</>
);
}
目前無法正確推斷。但是我們可以通過為reducer函數(shù)添加類型來改變這一點(diǎn)。通過在reducer函數(shù)中定義state
和action
,我們現(xiàn)在可以推斷useReducer
提供的state
。讓我們改編一下這個(gè)例子。
type ActionType = {
type: 'increment' | 'decrement';
};
type State = { count: number };
function reducer(state: State, action: ActionType) {
// ...
}
現(xiàn)在我們可以確保在Counter
中推斷類型:
function Counter({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, initialState);
// const state = State
// ...
}
當(dāng)試圖分派一個(gè)不存在的類型時(shí),我們將收到一個(gè)錯(cuò)誤。
dispatch({type: 'increment'}); // Works!
dispatch({type: 'reset'});
// Error! type '"reset"' is not assignable to type '"increment" | "decrement"'
useReducer
也可以在需要時(shí)延遲初始化,因?yàn)橛袝r(shí)可能需要首先計(jì)算初始狀態(tài):
function init(initialCount) {
return {count: initialCount};
}
function Counter({ initialCount = 0 }) {
const [state, dispatch] = useReducer(red, initialCount, init);
// const state: State
// ...
}
從上面的示例中可以看出,由于正確鍵入了reducer
函數(shù),因此使用延遲初始化的useReducer
來推斷類型。
關(guān)于useReducer
,我們不需要知道太多。
useCallback
有時(shí)我們需要回憶回叫。useCallback
僅當(dāng)其中一個(gè)值發(fā)生更改時(shí),才接受一個(gè)內(nèi)聯(lián)回調(diào)和一個(gè)輸入數(shù)組來更新備忘錄。讓我們看一個(gè)例子:
const add = (a: number, b: number) => a + b;
const memoizedCallback = useCallback(
(a) => {
add(a, b);
},
[b]
);
有趣的是,我們可以用任何類型調(diào)用memoizedCallback,而不會看到TypeScript抱怨:
memoizedCallback("ok!"); // Works!
memoizedCallback(1); // Works!
在這種特定情況下,memoizedCallback
可以處理字符串或數(shù)字,盡管add
函數(shù)需要兩個(gè)數(shù)字。要解決這個(gè)問題,我們需要在編寫內(nèi)聯(lián)函數(shù)時(shí)更加具體。
const memoizedCallback = useCallback(
(a: number) => {
add(a, b);
},
[b]
);
現(xiàn)在,我們需要傳遞一個(gè)數(shù)字,否則編譯器會投訴。
memoizedCallback("ok");
// Error! Argument of type '"ok"' is not assignable to argument of type 'number'
memoizedCallback(1); // Works!
useMemo
useMemo
與useCallback
非常相似,但返回的是已記憶的值,而不是已記憶的回調(diào)。以下內(nèi)容摘自文檔。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
因此,如果我們在上面的基礎(chǔ)上構(gòu)建一個(gè)示例,我們注意到不需要對類型做任何事情:
function calculate(a: number): number {
// do some calculations here...
}
function runCalculate() {
const calculatedValue = useMemo(() => calculate(a), [a]);
// const calculatedValue : number
}
useRef
最后,我們再看一個(gè)hooks:useRef
當(dāng)使用useRef
時(shí),我們可以訪問可變的引用對象。此外,我們可以將初始值傳遞給useRef
,該值用于初始化可變r(jià)ef對象公開的current
屬性。這在嘗試訪問函數(shù)f.e.中的某些組件時(shí)非常有用。同樣,讓我們使用文檔中的示例。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus(); // Error! Object is possibly 'null'
};
return (<>Focus the input</>
);
}
我們可以看到TypeScript在抱怨,因?yàn)槲覀冇?/span>null
初始化了useRef
,這是一個(gè)有效的情況,因?yàn)橛袝r(shí)設(shè)置引用可能發(fā)生在稍后的時(shí)間點(diǎn)。
這意味著,在使用useRef
時(shí),我們需要更加明確。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus(); // Error! Object is possibly 'null'
};
// ...
}
當(dāng)通過定義實(shí)際類型來使用useRef
時(shí),更加具體化仍然不能消除錯(cuò)誤。實(shí)際檢查current
屬性是否存在,可以防止編譯器抱怨。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
if (inputEl.current) {
inputEl.current.focus(); // Works!
}
};
// ...
}
useRef
也可用作實(shí)例變量。
如果需要更新current
屬性,則需要將useRef
與泛型類型Type | null
一起使用:
function sleep() {
const timeoutRefId = useRef();
useEffect(() => {
const id = setTimeout(() => {
// ...
});
if (timeoutRefId.current) {
timeoutRefId.current = id;
}
return () => {
if (timeoutRefId.current) {
clearTimeout(timeoutRefId.current);
}
};
});
// ...
}
關(guān)于React hooks,還有一些更有趣的東西需要學(xué)習(xí),但不是特定于TypeScript的。
作者介紹