发布于:2021-02-05 13:55:20
0
294
0
导言
这些注释应该有助于更好地理解TypeScript
,并且在需要查找如何在特定情况下利用TypeScript时可能会有所帮助。所有示例都基于TypeScript 3.2。
React Hooks
在“TypeScript注释”系列的这一部分中,我们将了解如何使用TypeScript键入React Hooks,并在此过程中了解有关hooks的更多信息。
我们将参考官方React文档中关于hooks的内容,在需要了解更多关于hooks的信息或需要特定问题的具体答案时,这是一个非常有价值的来源。
通常,在16.8中添加了hooks来React,使开发人员能够在函数组件中使用状态,这在类组件中是唯一可能的。文档说明有基本挂钩和附加挂钩。
基本挂钩有useState
、useEffect
、useContext
,附加挂钩包括useReducer
、useCallback
、useMemo
、useRef
。
useState
让我们从useState
开始,这是一个基本的hooks,顾名思义,应该用于状态处理。
const [state, setState] = useState(initialState);
看看上面的例子,我们可以看到useState
返回一个状态值以及一个函数来更新它。但是我们如何输入state
和setState
?
有趣的是,TypeScript可以推断出类型,这意味着通过定义一个initialState
,可以推断出状态值和更新函数的类型。
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[]
上面的例子很好地说明了我们不需要手工打字。但是如果我们没有初始状态呢?上述示例在尝试更新状态时会中断。
我们可以在需要时使用手动定义类型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
类内组件相反,使用update hook函数需要返回完整的状态。
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!
另一件有趣的事情是,我们可以通过将函数传递给useState
const [state, setState] = useState(() => {
props.init + 1;
});
// const state: number
同样,TypeScript可以推断状态类型。
这意味着我们在使用useState
时不需要做太多的工作,只有在没有初始状态的情况下,因为在初始渲染时可能会计算实际的状态形状。
useEffect
另一个基本的hooks是useEffect
,它在处理诸如日志记录、突变或订阅事件侦听器之类的副作用时非常有用。一般来说,useEffect
需要一个函数来运行一个效果,该效果可以选择性地返回一个clean-up函数,该函数对于取消订阅和删除侦听器非常有用。此外,useEffect
还可以提供第二个参数,该参数包含一个值数组,以确保仅当其中一个值被删除时,效果函数才会运行改变。这确保了我们可以控制何时运行效果。
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source]
);
以文档中的原始示例为例,我们可以注意到在使用useEffect
时不需要任何额外的键入。
TypeScript在尝试返回非函数或effect函数中未定义的内容时会抱怨。
useEffect(
() => {
subscribe();
return null; // Error! Type 'null' is not assignable to void | (() => void)
}
);
这也适用于useLayoutEffect
,只在运行效果时不同。
useContext
useContext
需要一个上下文对象,并返回所提供上下文的值。当提供者更新上下文时,会触发重新呈现。看一下下面的例子应该会明白:
const ColorContext = React.createContext({ color: "green" });
const Welcome = () => {
const { color } = useContext(ColorContext);
returnWelcome;
};
同样,我们不需要做太多关于类型的事情。类型是推断出来的。
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
有时我们处理的是更复杂的状态,这可能也取决于前一个状态。useReducer
接受一个函数,该函数根据先前的状态和操作计算特定的状态。以下示例摘自官方文档。
const [state, dispatch] = useReducer(reducer, initialArg, init);
如果我们看一下文档中的示例,就会注意到我们需要做一些额外的键入工作。检查稍微调整的示例:
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函数添加类型来改变这一点。通过在reducer函数中定义state
和action
,我们现在可以推断useReducer
提供的state
。让我们改编一下这个例子。
type ActionType = {
type: 'increment' | 'decrement';
};
type State = { count: number };
function reducer(state: State, action: ActionType) {
// ...
}
现在我们可以确保在Counter
中推断类型:
function Counter({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, initialState);
// const state = State
// ...
}
当试图分派一个不存在的类型时,我们将收到一个错误。
dispatch({type: 'increment'}); // Works!
dispatch({type: 'reset'});
// Error! type '"reset"' is not assignable to type '"increment" | "decrement"'
useReducer
也可以在需要时延迟初始化,因为有时可能需要首先计算初始状态:
function init(initialCount) {
return {count: initialCount};
}
function Counter({ initialCount = 0 }) {
const [state, dispatch] = useReducer(red, initialCount, init);
// const state: State
// ...
}
从上面的示例中可以看出,由于正确键入了reducer
函数,因此使用延迟初始化的useReducer
来推断类型。
关于useReducer
,我们不需要知道太多。
useCallback
有时我们需要回忆回叫。useCallback
仅当其中一个值发生更改时,才接受一个内联回调和一个输入数组来更新备忘录。让我们看一个例子:
const add = (a: number, b: number) => a + b;
const memoizedCallback = useCallback(
(a) => {
add(a, b);
},
[b]
);
有趣的是,我们可以用任何类型调用memoizedCallback,而不会看到TypeScript抱怨:
memoizedCallback("ok!"); // Works!
memoizedCallback(1); // Works!
在这种特定情况下,memoizedCallback
可以处理字符串或数字,尽管add
函数需要两个数字。要解决这个问题,我们需要在编写内联函数时更加具体。
const memoizedCallback = useCallback(
(a: number) => {
add(a, b);
},
[b]
);
现在,我们需要传递一个数字,否则编译器会投诉。
memoizedCallback("ok");
// Error! Argument of type '"ok"' is not assignable to argument of type 'number'
memoizedCallback(1); // Works!
useMemo
useMemo
与useCallback
非常相似,但返回的是已记忆的值,而不是已记忆的回调。以下内容摘自文档。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
因此,如果我们在上面的基础上构建一个示例,我们注意到不需要对类型做任何事情:
function calculate(a: number): number {
// do some calculations here...
}
function runCalculate() {
const calculatedValue = useMemo(() => calculate(a), [a]);
// const calculatedValue : number
}
useRef
最后,我们再看一个hooks:useRef
当使用useRef
时,我们可以访问可变的引用对象。此外,我们可以将初始值传递给useRef
,该值用于初始化可变ref对象公开的current
属性。这在尝试访问函数f.e.中的某些组件时非常有用。同样,让我们使用文档中的示例。
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在抱怨,因为我们用null
初始化了useRef
,这是一个有效的情况,因为有时设置引用可能发生在稍后的时间点。
这意味着,在使用useRef
时,我们需要更加明确。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus(); // Error! Object is possibly 'null'
};
// ...
}
当通过定义实际类型来使用useRef
时,更加具体化仍然不能消除错误。实际检查current
属性是否存在,可以防止编译器抱怨。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
if (inputEl.current) {
inputEl.current.focus(); // Works!
}
};
// ...
}
useRef
也可用作实例变量。
如果需要更新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);
}
};
});
// ...
}
关于React hooks,还有一些更有趣的东西需要学习,但不是特定于TypeScript的。
作者介绍