关于 TypeScript 的注释:React Hooks
介绍
这些说明应该有助于更好地理解,TypeScript并且在需要查找如何在特定情况下利用 TypeScript 时可能会有所帮助。所有示例均基于 TypeScript 3.2。
React Hooks
在“TypeScript 笔记”系列的这一部分中,我们将了解如何使用 TypeScript 对 React Hooks 进行类型化,并在此过程中了解有关 Hooks 的更多信息。
我们将参考React 官方文档中关于 Hook 的内容,如果您需要了解更多关于 Hook 的知识或需要针对特定问题的具体解决方案,那么
这份文档将是一个非常宝贵的资源。通常,Hook 已于 16.8 版本添加到 React 中,使开发者能够在函数组件中使用状态,而在此之前,这只能在类组件中使用。文档指出,Hook 分为基本 Hook 和附加 Hook。
基本 Hook 包括useState、useEffect,useContext附加 Hook 包括useReducer、useCallback、useMemo、useRef。
useState
让我们从基本的钩子开始useState,顾名思义,它应该用于状态处理。
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<number | null>(null);
// const state: number | null
const [state, setState] = useState<{id: number, name: string} | null>(null);
// const state: {id: number; name: string;} | null
const [state, setState] = useState<number | undefined>(undefined);
// const state: number | null
值得注意的是,setState与类组件相反,使用更新挂钩函数需要返回完整的状态。
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
另一个基本钩子是useEffect,它在处理副作用(例如日志记录、突变或订阅事件监听器)时非常有用。通常,useEffect需要一个运行效果的函数,该效果可以选择返回一个清理函数,这对于取消订阅和删除监听器非常有用。此外,useEffect还可以提供第二个参数,其中包含一个值数组,以确保效果函数仅在其中一个值发生更改时运行。这确保我们可以控制效果的运行时间。
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source]
);
从文档中的原始示例来看,我们可以注意到,使用时不需要任何额外的输入useEffect。
当我们尝试在 effect 函数中返回非函数或未定义的内容时,TypeScript 会报错。
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);
return <div style={{ color }}>Welcome</div>;
};
再次强调,我们不需要对类型做太多处理。类型是可以推断出来的。
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}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
目前state无法正确推断。但我们可以通过为 Reducer 函数添加类型来改变这种情况。通过在Reducer 函数内部定义state和,我们现在可以推断所提供的。让我们调整一下这个例子。actionstateuseReducer
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
// ...
}
从上面的例子中可以看出,useReducer由于函数reducer类型正确,类型是通过延迟初始化来推断的。
关于,我们不需要知道太多useReducer。
使用回调
有时我们需要记忆回调。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与 非常相似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
最后,我们再看一个钩子:useRef。
使用 时useRef,我们可以访问一个可变的引用对象。此外,我们可以将一个初始值传递给useRef,该初始值用于初始化current可变引用对象公开的属性。这在尝试访问函数 fe 内部的某些组件时很有用。再次,让我们使用文档中的示例。
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 (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
我们可以看到 TypeScript 报错,因为我们useRef用初始化了null,这是一个有效的例子,因为有时设置引用可能会在稍后的时间点发生。
这意味着,我们在使用 时需要更加明确useRef。
function TextInputWithFocusButton() {
const inputEl = useRef<HTMLInputElement>(null);
const onButtonClick = () => {
inputEl.current.focus(); // Error! Object is possibly 'null'
};
// ...
}
useRef使用via 定义实际类型时,更具体useRef<HTMLInputElement>仍然无法消除错误。实际上current,检查属性是否存在可以避免编译器报错。
function TextInputWithFocusButton() {
const inputEl = useRef<HTMLInputElement>(null);
const onButtonClick = () => {
if (inputEl.current) {
inputEl.current.focus(); // Works!
}
};
// ...
}
useRef也可以用作实例变量。
如果我们需要更新current属性,则需要使用useRef泛型类型Type | null:
function sleep() {
const timeoutRefId = useRef<number | null>();
useEffect(() => {
const id = setTimeout(() => {
// ...
});
if (timeoutRefId.current) {
timeoutRefId.current = id;
}
return () => {
if (timeoutRefId.current) {
clearTimeout(timeoutRefId.current);
}
};
});
// ...
}
关于 React Hooks,还有一些更有趣的内容需要学习,但这些内容并非 TypeScript 独有。如果您对此主题更感兴趣,请参阅React 官方文档中关于 Hooks 的内容。
现在,我们应该对如何定义 React Hooks 的类型有了很好的理解。
如果您有任何问题或反馈,请在此处发表评论或通过 Twitter 联系:A. Sharif
文章来源:https://dev.to/busypeoples/notes-on-typescript-react-hooks-28j2
后端开发教程 - Java、Spring Boot 实战 - msg200.com