作家村上春树在《当我谈跑步时我谈些什么》里说道:“Pain is inevitable,but suffering is optional” 即 苦楚不可避免,但咱们能够抉择不受苦。这个选择权,来自不反抗过来,不担心将来。
大家好,我是柒八九。
在前几天,咱们开拓了--TypeScript实战系列,次要讲TS
在React
中的利用实战。
大家如果对React
理解/相熟的话,想必都听过Hook
。在当下的React
开发中,函数组件大行其道。而Hook
就是为了给函数组件增加外部状态还有解决副作用的。换句话说,Hook
曾经在当初的React
的开发中, 变得不可代替。
而,明天咱们就简略的聊聊,如何利用TS
对Hook
进行类型化解决。有一点须要特地指出,对hook
进行类型化解决,须要利用泛型的语法,如果对泛型没有一个大体的理解,还是须要异步一些惯例材料中,先进行简略的学习。
- TS_React:应用泛型来改善类型
- typescriptlang_generics
好了,天不早了。咱们开始粗发。
你能所学到的知识点
React
各种hook
的类型化解决,总有一款,让你骑虎难下
文章概要
- 依赖类型推断
- 类型化 useState
- 类型化 useReducer
- 类型化 useRef
- 类型化 forwardRef
- 类型化 useEffect 和 useLayoutEffect
- 类型化 useMemo 和 useCallback
- 类型化 useContext
- 类型化自定义hook
1. 依赖类型推断
在绝大部分,TS
都能够依据hook
中的值来推断它们的类型:也就是咱们常说的类型推断
何为类型推断,简略来说:类型推断就是基于赋值表达式推断类型的能⼒。ts
采纳将类型标注申明放在变量之后(即类型后置)的形式来对变量的类型进行标注。而使⽤类型标注后置的益处就是编译器能够通过代码所在的高低⽂推导其对应的类型,⽆须再申明变量类型。
像
- 具备初始化值的变量
- 有默认值的函数参数
- 函数返回的类型
都能够依据高低⽂推断进去。
例如,上面的代码能够在ts
环境中失常运行,且可能通过类型推断推导出name
的类型为string
类型。
const [name, setName] = useState('前端柒八九');
何时不能依赖类型推断
上面的两种情境下,类型推断有点力不从心
ts
推断出的类型过于宽松- 类型推断谬误
推断出的类型过于宽松
咱们之前的例子--有一个字符串类型的name
。然而咱们假如这个name
只能有两个预约的值中的一个。
在这种状况下,咱们会心愿name
有一个十分具体的类型,例如这个类型。
type Name = '前端柒八九' | '前端工程师' ;
这种类型同时应用联结类型和字面类型。
在这种状况下,推断的类型过于宽松(是string
,而不是咱们想要的2个字符串的特定子集),这种状况下就必须本人指定类型。
const [name, setName] = useState<Name>('前端柒八九');
类型推断谬误
有时,推断的类型是谬误的(或者限制性太强不是你想要的类型)。这种状况常常产生在React
的useState
默认值中。比方说,name
的初始值是null
。
const [name, setName] = useState(null);
在这种状况下,TypeScript
会推断出name
是null类型
的(这意味着它总是null)。这显然是谬误的:咱们当前会想把 name
设置成一个字符串。
此时你必须通知 TypeScript
,它能够是别的类型。
const [name, setName] = useState<string | null>(null);
通过这样解决后,TypeScript
会正确理解name
能够是null
也能够是string
。
这里要提到的一件事是,当类型推断不起作用时,应该依附泛型参数而不是类型断言。const [name, setName] = useState<Name>('前端柒八九');
举荐应用const [name, setName] = useState('前端柒八九' as Name);
不举荐应用
2. 类型化 useState
在文章结尾,咱们曾经通过类型推断讲过了,如何解决useState
的各种状况。这里就不在赘述了。
const [name, setName] = useState<string | null>(null);
3. 类型化 useReducer
useReducer
的类型比 useState
要简单一些。如果看过源码的同学,可能有印象,其实useState
就是useReducer
的简化版。
针对useReducer
有两样货色要类型化解决:state
和action
。
这里有一个useReducer
的简略例子。针对input
做简略的数据收集解决。
import { useReducer } from 'react';const initialValue = { username: '', email: '',};const reducer = (state, action) => { switch (action.type) { case 'username': return { ...state, username: action.payload }; case 'email': return { ...state, email: action.payload }; case 'reset': return initialValue; default: throw new Error(`未定义的action: ${action.type}`); }};const Form = () => { const [state, dispatch] = useReducer(reducer, initialValue); return ( <div> <input type="text" value={state.username} onChange={(event) => dispatch({ type: 'username', payload: event.target.value }) } /> <input type="email" value={state.email} onChange={(event) => dispatch({ type: 'email', payload: event.target.value }) } /> </div> );};export default Form;
类型化 reducer 的state
咱们有两个抉择来类型化reducer-state
。
- 应用初始值(如果有的话)和
typeof
操作符 - 应用类型别名
应用typeof 操作符
const initialValue = { username: '', email: '',};+ const reducer = (state: typeof initialValue, action) => { switch (action.type) { case 'username': return {...state, username: action.payload }; case 'email': return {...state, email: action.payload }; case 'reset': return initialValue; default: throw new Error(`未定义的action: ${action.type}`); }};
应用类型别名
+type State = {+ username: string;+ email: string;+};const initialValue = { username: '', email: '',};+ const reducer = (state: State, action) => { switch (action.type) { case 'username': return { ...state, username: action.payload }; case 'email': return { ...state, email: action.payload }; case 'reset': return initialValue; default: throw new Error(`未定义的action: ${action.type}`); }};
类型化 reducer 的action
reducer-action
的类型比reducer-state
要难一点,因为它的构造会依据具体的action
而扭转。
例如,对于 username-action
,咱们可能冀望有以下类型。
type UsernameAction = { type: 'username'; payload: string;};
但对于 reset-action
,咱们不须要payload
字段。
type ResetAction = { type: 'reset';};
咱们能够借助联结类型区别对待不同的action
。
const initialValue = { username: "", email: ""};+type Action =+ | { type: "username"; payload: string }+ | { type: "email"; payload: string }+ | { type: "reset" };+ const reducer = (state: typeof initialValue, action: Action) => { switch (action.type) { case "username": return {...state, username: action.payload }; case "email": return { ...state, email: action.payload }; case "reset": return initialValue; default: throw new Error(`未定义的action: ${action.type}`); }};
Action类型
示意的是,它能够承受联结类型中蕴含的三种类型中的任何一种。因而,如果 TypeScript
看到 action.type
是username
,它就会主动晓得它应该是第一种状况,并且payload
应该是一个string
。
通过对state/action
类型化后,useReducer
可能从reducer
函数的type
中推断出它须要的所有。
上面是整体的代码。(省略了,jsx
局部)
import { useReducer } from 'react';const initialValue = { username: '', email: '',};+type Action =+ | { type: 'username'; payload: string }+ | { type: 'email'; payload: string }+ | { type: 'reset' };+const reducer = (state: typeof initialValue, action: Action) => { switch (action.type) { case 'username': return { ...state, username: action.payload }; case 'email': return { ...state, email: action.payload }; case 'reset': return initialValue; default: throw new Error(`Unknown action type: ${action.type}`); }};const Form = () => { const [state, dispatch] = useReducer(reducer, initialValue); return ( ...省略了.. );};export default Form;
4. 类型化 useRef
useRef
有两个主要用途
- 保留一个自定义的可变值(它的值变更不会触发更新)。
- 放弃对一个DOM对象的援用
类型化可变值
它基本上与 useState
雷同。想让useRef
保留一个自定义的值,你须要通知它这个类型。
function Timer() {+ const intervalRef = useRef<number | undefined>(); useEffect(() => { const id = setInterval(() => { // ... }); intervalRef.current = id; return () => { clearInterval(intervalRef.current); }; }); // ...}
类型化 DOM 节点
在DOM节点上应用useRef
的一个经典用例是解决input
元素的focus
。
mport { useRef, useEffect } from 'react';const AutoFocusInput = () => {+ const inputRef = useRef(null); useEffect(() => {+ inputRef.current.focus(); }, []);+ return <input ref={inputRef} type="text" value="前端柒八九" />;};export default AutoFocusInput;
TypeScript
有内置的DOM元素类型。这些类型的构造总是雷同的:
如果
name
是你正在应用的HTML标签的名称,相应的类型将是HTMLNameElement
。
这里有几个特例
<a>
标签的类型为HTMLAnchorElement
<h1>
标签的类型为HTMLHeadingElement
对于<input>
,该类型的名称将是HTMLInputElement
。
mport { useRef, useEffect } from 'react';const AutoFocusInput = () => {+ const inputRef = useRef<HTMLInputElement>(null); useEffect(() => {+ inputRef.current?.focus(); }, []); return <input ref={inputRef} type="text" value="前端柒八九" />;};export default AutoFocusInput;
留神:在inputRef.current?.focus()
上加了一个?
。这是因为对于 TypeScript
,inputRef.current
可能是空的。在这种状况下,咱们晓得它不会是空的,因为它是在 useEffect
第一次运行之前由 React
填充的。
5. 类型化 forwardRef
有时想把ref
转发给子组件。要做到这一点,在 React
中咱们必须用 forwardRef
来包装组件。
import { ChangeEvent } from 'react';type Props = { value: string, handleChange: (event: ChangeEvent<HTMLInputElement>) => void,};const TextInput = ({ value, handleChange }: Props) => { return <input type="text" value={value} onChange={handleChange} />;};
例如,存在一个组件TextInput
而咱们想在父组件的调用处,通过ref
来管制子组件input
。
此时,就须要用forwardRef
来解决。
import { forwardRef, ChangeEvent } from 'react';type Props = { value: string; handleChange: (event: ChangeEvent<HTMLInputElement>) => void;};+const TextInput = forwardRef<HTMLInputElement, Props>(+ ({ value, handleChange }, ref) => { return (+ <input ref={ref} type="text" value={value} onChange={handleChange} /> ); });
此语法只须要向 forwardRef
提供它应该期待的HTMLElement
(在这种状况下是HTMLInputElement
)。
有一点,须要指出:组件参数ref
和props
的程序与泛型的<HTMLInputElement, Props>
不一样。
6. 类型化 useEffect 和 useLayoutEffect
你不用给他们任何类型
惟一须要留神的是隐式返回。useEffect
外面的回调应该是什么都不返回,或者是一个会清理任何副作用的Destructor
函数(析构函数,这个词借用了C++中类的说法)
7. 类型化 useMemo 和 useCallback
你不用给他们任何类型
8. 类型化 useContext
为context
提供类型是非常容易的。首先,为context
的值创立一个类型,而后把它作为一个泛型提供给createContext
函数。
import React, { createContext, useEffect, useState, ReactNode } from 'react';+type User = {+ name: string;+ email: string;+ freeTrial: boolean;+};+type AuthValue = {+ user: User | null;+ signOut: () => void;+};+const AuthContext = createContext<AuthValue | undefined>(undefined);type Props = { children: ReactNode;};const AuthContextProvider = ({ children }: Props) => { const [user, setUser] = useState(null); const signOut = () => { setUser(null); }; useEffect(() => { // 副作用解决 }, []); return (+ <AuthContext.Provider value={{ user, signOut }}> {children}+ </AuthContext.Provider> );};export default AuthContextProvider;
一旦你向createContext
提供了泛型,残余的事,都由ts
为你代劳。
上述实现的一个问题是,就TypeScript
而言,context
的值能够是未定义的。也就是在咱们应用context
的值的时候,可能取不到。此时,ts
可能会拦截代码的编译。
如何解决context
的值可能是未定义的状况呢。咱们针对context
的获取能够应用一个自定义的hook
。
export const useAuthContext = () => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuthContext必须在AuthContext上下文中应用'); } return context;};
通过类型爱护,使得咱们在应用context
的时候,总是有值的。
9. 类型化自定义hook
类型化自定义hook
基本上和类型化一般函数一样
针对如何类型化一般函数,在一些教程中很多,一搜一大把。这里也不过多形容。
咱们来看一个比拟有意思的例子。有一个自定义hook
,它想要返回一个元祖。
const useCustomHook = () => { return ['abc', 123];};
而TypeScipt
将扩充 useCustomHook
的返回类型为(number | string)[]
(一个能够蕴含数字或字符串的数组)。显然,这不是你想要的,你想要的是第一个参数总是一个字符串,第二个例子总是一个数字。
所以,这种状况下,咱们能够利用泛型对返回类型做一个限度解决。
const useCustomHook = (): [string, number] => { return ['abc', 123];};
后记
分享是一种态度。
参考资料:
- React_Ts_类型化hook
- 重写TS
- TS官网
全文完,既然看到这里了,如果感觉不错,顺手点个赞和“在看”吧。