本文参考自 react-typescript-cheatsheet ,简单介绍函数式组件的 TypeScript 写法。如果想了解类组件的写法以及更多的细节,可以阅读原文档。
如果有什么补充,欢迎大家在评论区交流。
一、Props
和 TypeScript 中函数的类型声明类似:
interface Props {
/** 标题内容 */
text?: string
/** 组件插槽 */
children: React.ReactNode
}
export const Heading = (props: Props): JSX.Element => {
const { text = 'Hello, React + TypeScript', children } = props
return <h1>{children || text}</h1>
}
建议为每一个 prop 提供
/** comment */
文档注释,以获得更好的代码可读性:![]()
二、Hooks
1. useState
useState 可以根据传入的初始值推断出数据类型:

如果传入的是 null
,就要用到泛型和联合类型了:
interface User {
/** 名字 */
firstName: string
/** 年龄 */
age: number
}
const [state, setState] = useState<User | null>(null)
setState({ firstName: 'Jon', age: 20 })
2. useCallback
这个很简单,和 TypeScript 定义函数类型一样:
const memoCallback = useCallback((user: User) => {
console.log(user.firstName)
}, [])
可以看一下 useCallback
的类型定义:返回值类型就是回调函数类型 T
function useCallback<T extends Function>(callback: T, deps: DependencyList): T;
3. useReducer
使用可识别的联合类型声明 action :
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) {
// action.type 被推断为字符串字面量联合类型 "increment" | "decrement"
switch (action.type) {
case 'increment':
// payload 被推断为 number 类型
return { count: state.count + action.payload }
case 'decrement':
// payload 被推断为 string 类型
return { count: state.count - Number(action.payload) }
default:
throw new Error('unknown action\'s type')
}
}
export function Counter() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement', payload: '5' })}>
-
</button>
{/* 报错:'increment' 对应的 payload 为 number 类型,不能将 string 赋给 number */}
<button onClick={() => dispatch({ type: 'increment', payload: '5' })}>
+
</button>
</div>
)
}
4. useRef
-
只读,初始值是
null
。function Foo() { const inputRef = useRef<HTMLInputElement>(null) // Error: Cannot assign to 'current' because it is a read-only property. inputRef.current = 123 return <input ref={inputRef} /> }
-
可变
const intervalRef = useRef<number>() // number | undefined useEffect(() => { intervalRef.current = setInterval(() => {}) return () => clearInterval(intervalRef.current) }, [])
关于只读和可变的判断,可以看一下声明文件:
// 初始值传入 null ,表示 ref.current 只读 function useRef<T>(initialValue: T | null): RefObject<T>; // 否则 ref.current 可变 function useRef<T = undefined>(): MutableRefObject<T | undefined>;
5. useImperativeHandle
forwardRef
接收两个泛型:
- 暴露给外部的 handle 集合 T
- 组件接收的 props 类型 P
/// Person.tsx
/** 暴露给外部的 handle 集合 */
export interface PersonHandle {
say: () => void
}
interface PersonProps {
firstName: string
}
const Person = forwardRef<PersonHandle, PersonProps>((props, ref) => {
useImperativeHandle(ref, () => ({
say() {
console.log(props.firstName)
},
}))
return <div>Person: {props.firstName}</div>
})
/// App.tsx
export default function App() {
// 将 Person 组件提供的 PersonHandle 传递给 useRef 以获取类型提示
const personRef = useRef<PersonHandle>(null)
return <div>
<Person ref={personRef} firstName="Jon" />
<button onClick={() => personRef.current?.say()}>Click</button>
{/* Error: Property 'run' does not exist on type 'PersonHandle'. */}
<button onClick={() => personRef.current?.run()}>Click</button>
</div>
}
6. 自定义 Hooks
需要给返回的数组(元组)做 const 断言:
export function useLoading() {
const [isLoading, setState] = useState(false)
const load = (p: Promise<any>) => {
setState(true)
return p.finally(() => setState(false))
}
// 无 const 断言:(boolean | ((p: Promise<any>) => Promise<any>))[] ❎
// 调用者无法准确获取类型
// return [isLoading, load]
// 有 const 断言:readonly [boolean, (p: Promise<any>) => Promise<any>] ✅
return [isLoading, load] as const
}
如果自定义 hook 返回不止两个值,推荐使用对象代替数组(元组)。
三、事件
-
行内事件处理函数,自带类型提示:
-
单独声明的事件处理函数:
定义格式:React.事件类型<触发元素类型>
import React from 'react' export function App() { function onClick(e: React.MouseEvent<HTMLButtonElement>) { console.log(e.currentTarget) } return <button onClick={onClick}>Click</button> }
四、Context
// 定义 context 类型
type ThemeContextType = 'light' | 'dark'
// 使用泛型创建 context
const ThemeContext = createContext<ThemeContextType>('light')
// 子组件
function MyComponent() {
// theme 被推断为 ThemeContextType
const theme = useContext(ThemeContext)
return <div>Theme is {theme}.</div>
}
export function App() {
// 使用泛型调用 useState
const [theme, setTheme] = useState<ThemeContextType>('light')
function onClick() {
setTheme(theme === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext.Provider value={theme}>
<button onClick={onClick}>Toggle</button>
<MyComponent />
</ThemeContext.Provider>
)
}