2025 年,TypeScript 已然成为前端开发领域不可或缺的一部分,特别是在React项目中,其强类型特性为开发带来了安全性。本文将深入总结在 React 中使用 TypeScript 的常见场景,旨在帮助你快速掌握 React + TypeScript 的开发精髓,提升开发效率与项目质量!
React 组件声明
在 React 中,组件的声明方式有两种:
函数组件
和
类组件。
函数组件
函数组件是现代 React 开发中最常用的组件形式。使用 TypeScript 声明函数组件时,通常需要定义 props 的类型。在 React 中,有两种方法可以给函数组件定义类型:
React.FC
和
直接定义props类型。
React.FC
React.FC
(Functional Component 的缩写,因此也可以写成
React.FunctionComponent
)是 React 中用于定义函数组件的一个类型别名。它在 TypeScript 环境下特别有用,因为它不仅可以定义组件的属性(props)类型,还隐式地处理一些常见的 React 组件特性,如:
•
Props 类型推断
:当使用
React.FC
定义一个组件时,可以直接为该组件指定一个接口来描述它的
props
结构,从而获得更好的类型检查和自动补全支持。
•
Children Prop 自动包含
:默认情况下,
React.FC
会自动将
children
作为可选
props
包含进来。这意味着如果没有显式声明
children
在
props
接口中,仍然可以在 JSX 中使用它。
•
返回值类型
:
React.FC
显式指定了返回值类型为
JSX.Element
或者
null
,这有助于避免某些类型的错误。
在使用
React.FC
时,只需要将定义好的
props
类型传递给它即可,举个例子:
import React from 'react' ; interface GreetingProps { name : string ; } const Greeting : React . FC < GreetingProps > = (
{ name } ) => { return ( < div > Hello, {name}! div > ); }; export default Greeting ;
直接定义 props 类型
函数组件还可以直接给
props
定义类型而不使用
React.FC
,这样更加灵活和直观。
举个例子:
import React from 'react' ; // 定义组件属性接口 interface GreetingProps { name : string ; age ?: number ; children ?: React . ReactNode ; // 明确声明 children 类型(可选) } // 函数组件,直接为 props 指定类型 const Greeting = ({ name, age, children }: GreetingProps ): JSX . Element => { return ( < div > Hello, {name}! {age && `你 ${age} 岁!`} {children} div > ); }; export default Greeting ;
与使用
React.FC
相比,直接定义
props
类型的主要区别在于:
•
children
处理
:
React.FC
默认将
children
作为一个可选属性包含进来。如果不希望自动包含
children
,或者需要对其类型进行更详细的定义,直接定义
props
类型会更加合适。
•
返回类型
:
React.FC
显式地指定了返回值类型为
JSX.Element
或
null
,而直接定义
props
类型则没有这种限制。如果确实想要明确函数组件的返回值,可以显式添加返回值类型
JSX.Element
,它用来表示由 JSX 语法生成的 React 元素。虽然在大多数情况下 TypeScript 可以自动推断出返回类型,但显式标注可以帮助提高代码的可读性和类型安全性。
类组件
类组件的类型定义与函数组件有所不同。类组件使用
React.Component
类来定义,并且需要明确指定其
props
和
state
的类型。
React.Component
举个例子:
import React from 'react' ; // 定义类组件的 props 和 state 类型 interface IProps { name : string ; } interface IState { count : number ; } class MyComponent extends React.Component < IProps , IState > { constructor ( props : IProps ) { super (props); // 初始化状态 this . state = { count : 0 }; } render () { return ( < div > < p > Hello, {this.props.name}! p > < p > Count: {this.state.count} p > < button onClick = {() => this.setState({ count: this.state.count + 1 })}> 增加 button > div > ); } } export default MyComponent ;
类组件的类型继承自
React.Component
,并传递两个泛型参数:第一个是
props
类型,第二个是
state
类型。
•
IProps
:定义了组件的
props
类型,这里包含一个
name
属性。
•
IState
:定义了组件的
state
类型,这里包含一个
count
属性。
React.PureComponent
另外,React 提供的一个基类:
React.PureComponent
,它于优化性能。与
React.Component
类似,使用
React.PureComponent
时也需要指定
props
和
state
的类型。
import React from 'react' ; // 定义类组件的 props 和 state 类型 interface IProps { name : string ; } interface IState { count : number ; } class MyComponent extends React.PureComponent < IProps , IState > { constructor ( props : IProps ) { super (props); this . state = { count : 0 }; } incrementCount = () => { this . setState ( ( prevState ) => ({ count : prevState. count + 1 })); }; render () { return ( < div > < p > Hello, {this.props.name}! p > < p > Count: {this.state.count} p > < button onClick = {this.incrementCount} > Increment button > div > ); } } export default MyComponent ;
注意
:
React.PureComponent
通过实现
shouldComponentUpdate
生命周期方法来执行浅比较
props
和
state
,从而决定是否重新渲染组件。如果
props
和
state
没有发生变化,组件将不会重新渲染,这可以避免不必要的渲染操作,提升性能。
特殊情况(了解)
props 类型在调用时才知道
在 React 类组件中,有时可能遇到的情况是
props
的类型直到运行时才能确定。这种情况通常出现在动态生成的组件或需要根据上下文传递不同类型的
props
时。为了解决这个问题,可以使用 TypeScript 的泛型来定义组件。
通过使用泛型,可以在定义函数组件时指定一个类型参数,这样就可以在调用组件时传递具体的类型。
import React from 'react' ; // 直接在函数签名中使用泛型参数 const DisplayValue = ( props : { name : T }): JSX . Element => { return ( < div > {String(props.name)} div > ); }; export default DisplayValue ; // 在调用时指定具体的 props 类型 < DisplayValue < string > name= "Alice" /> < DisplayValue < number > name={ 42 } /> < DisplayValue < boolean > name={ true } />
注意:
中添加了一个逗号。在 TypeScript 中,当定义一个泛型函数时,如果只有一个泛型参数,理论上可以省略泛型参数后的逗号。然而,在某些情况下,TypeScript 的类型推断机制可能会要求你保留这个逗号,尤其是在 JSX 语法中使用泛型时。因此,这里保留了逗号,以避免 TypeScript 解析错误。
import React from 'react' ; // 定义一个泛型类组件 class GenericComponent extends React . PureComponent { render () { const { data } = this . props
as { data : string }; return ( < div > Data: {data} div > ); } } // 在调用时指定具体的 props 类型 const MyComponent = GenericComponent data : string ; extra : number }>; // 使用组件 const App : React . FC = () => { return < MyComponent data = "Hello, World!" extra = {42} /> ; }; export default App ;
高阶组件
在 React 中,高阶组件用于增强组件的功能,它通常接收一个组件并返回一个新的组件。使用 TypeScript 定义高阶组件的类型时,可以通过泛型和类型别名来确保类型安全性和灵活性。
举个例子:
import React from 'react' ; function withLoggerextends {}>( WrappedComponent : React . ComponentType
):
React . FC {
const EnhancedComponent : React . FC =
( props : P ) => { console . log ( 'Props:' , props); return < WrappedComponent { ...props } /> ; }; return EnhancedComponent ; }
在这个高阶组件中:
•
P extends {}
表示
P
是一个对象类型,代表原始组件的
props
。
•
React.ComponentType
表示
WrappedComponent
可以是任何接受
P
类型
props
的 React 组件(无论是函数组件还是类组件)。
• 返回的组件是一个
React.FC
,其
props
类型与原始组件相同。
React Hooks
useState
useState
用于在函数组件中添加状态,它的泛型参数用于指定状态变量的类型。在这里,
useState
明确指定了状态变量
count
的类型为
number
。
import React , { useState } from 'react' ; const MyComponent = ( ) => { // 定义状态变量count的类型为number const [count, setCount] = useState< number >( 0 ); return ( < div > Count: {count} < button onClick = {() => setCount(count + 1)}>Increase button > div > ); }
如果初始状态是
null
或
undefined
,需要使用联合类型来表示。
import React , { useState } from 'react' ; const MyComponent = ( ) => { // 类型为 string | null const [name, setName] = useState< string | null >( null ); return ( < div > Name: {name || 'No name'} div > ); }
如果状态是一个对象,并且希望初始值为一个空对象,可以通过类型断言(
{} as State
)来避免报错:
import React , { useState } from 'react' ; interface State { name
: string ; age : number ; } const MyComponent = ( ) => { const [state, setState] = useState< State >({} as State ); return ( < div > Name: {state.name}, Age: {state.age} div > ); }
不建议这么写,这样会绕过编译时的类型检查,最好还是提供一个具体的初始值。
import React , { useState } from 'react' ; interface State { name : string ; age : number ; } const MyComponent = () => { const [state, setState] = useState< State >({ name : '' , age : 0 , }); return ( < div > Name: {state.name}, Age: {state.age} div > ); }
useRef
在使用
useRef
时,需要根据不同的使用场景进行恰当的类型定义。
•
用于 DOM 引用
:当
useRef
用于引用 DOM 元素时,需要通过泛型参数指定其类型为对应的 DOM 元素类型(如
HTMLInputElement
)。
import React , { useRef } from 'react' ; const InputFocusExample : React . FC = () => { // 定义一个 ref,其类型为 HTMLInputElement const inputRef = useRef< HTMLInputElement >( null ); const focusInput = () => { if (inputRef. current ) { inputRef. current . focus (); } }; return ( <
div > < input ref = {inputRef} type = "text" placeholder = "Type something" /> < button onClick = {focusInput} > Focus Input button > div > ); }; export default InputFocusExample ;
在这个例子中,
useRef
(null)
明确指定了
inputRef
引用的是一个
HTMLInputElement
类型的 DOM 元素。因为在初始时
ref
还未关联到实际的 DOM 元素,所以初始值设为
null
。在调用
focus
方法前,需要先检查
inputRef.current
是否存在,避免出现
null
引用错误。
•
用于保存可变值
:当
useRef
用于保存可变值(非 DOM 元素)时,需要通过泛型参数指定其存储值的类型。
import React , { useRef, useEffect } from 'react' ; const CounterWithRef : React . FC = () => { // 定义一个 ref 来保存计数器的值,类型为 number const counterRef = useRef< number >( 0 ); useEffect ( () => { const intervalId = setInterval ( () => { counterRef. current += 1 ; console . log ( 'Counter:' , counterRef. current ); }, 1000 ); return () => { clearInterval (intervalId); }; }, []); return ( < div > < p > Check console for counter updates p > div > ); }; export
default CounterWithRef ;
这里
useRef
(0)
表示
counterRef
用于存储一个
number
类型的值,初始值为
0
。在
setInterval
回调函数中,可以直接修改
counterRef.current
的值,且不会触发组件重新渲染。
•
通用类型
:如果
useRef
可能存储多种类型的值或者不确定具体类型,可以使用更通用的类型,如
any
或
unknown
。不过要谨慎使用
any
,因为它会绕过 TypeScript 的类型检查。
import React , { useRef } from 'react' ; const GenericRefExample : React . FC = () => { // 使用 unknown 类型 const genericRef = useRef< unknown >( null ); const setValue = () => { genericRef. current = '前端充电宝' ; console . log (genericRef. current ); }; return ( < div > < button onClick = {setValue} > Set Value button > div > ); }; export default GenericRefExample ;
在这个例子中,
useRef
(null)
表明
genericRef
可以存储任意类型的值,但在使用
genericRef.current
时需要进行类型检查或类型断言。
useMemo
useMemo
用于性能优化,它会记住一个计算结果,只有当依赖项发生变化时才会重新计算。
import React , { useMemo, useState } from 'react' ; const MemoExample : React . FC = () => { const [a, setA] = useState ( 1 ); const [b, setB] = useState ( 2 ); // 定义计算结果的类型为 number const sum = useMemo< number >( () => { return a + b; }, [a, b]); return ( < div > < p > a: {a} p > < p > b: {b} p > < p > Sum: {sum} p > < button onClick = {() => setA(a + 1)}>Increment a button > < button onClick = {() => setB(b + 1)}>Increment b button > div > ); }; export default MemoExample ;
useMemo
是泛型函数,这里通过
明确指定了返回值的类型为
number
。
useCallback
useCallback
和
useMemo
类似,但它用于记忆一个函数,只有当依赖项改变时才会重新创建函数。
import React , { useCallback, useState } from 'react' ; const ParamCallbackExample : React . FC = () => { const [data, setData] = useState< number []>([]); // 指定 useCallback 返回函数的类型为 (num: number) => number[] const addNumber = useCallback< ( num : number ) => number []>( ( num ) => {
const newData = [...data, num]; setData (newData); return newData; }, [data]); return ( < div > < p > Data: {data.join(', ')} p > < button onClick = {() => addNumber(1)}>Add 1 button > div > ); }; export default ParamCallbackExample ;
useCallback
是泛型函数,通过
number[]>
明确指定了返回的回调函数的类型。这个类型表示回调函数接收一个
number
类型的参数
num
,并返回一个
number
类型的数组。
useContext
useContext
用于在组件树中共享数据。在使用之前,需要先创建上下文并指定其类型。
import React , { createContext, useContext } from 'react' ; // 定义上下文的类型 type ThemeContextType = { theme : string ; setTheme : ( theme : string ) => void ; }; // 创建上下文 const ThemeContext = createContext< ThemeContextType | null >( null ); // 定义 Provider 组件 const ThemeProvider : React . FC children : React . ReactNode }> = ( { children } ) => { const [theme, setTheme] = useState< string >( 'light' ); const value : ThemeContextType = { theme, setTheme }; return ( < ThemeContext.Provider value = {value} > {children} ThemeContext.Provider > ); }; // 定义使用上下文的组件 const ThemeConsumer
: React . FC = () => { const themeContext = useContext ( ThemeContext ); if (!themeContext) { return < p > Context not provided p > ; } return ( < div > < p > Current theme: {themeContext.theme} p > < button onClick = {() => themeContext.setTheme('dark')}>Set Dark Theme button > div > ); }; export { ThemeProvider , ThemeConsumer };
其中,
createContext
是泛型函数,这里通过
指定了上下文的值的类型。使用
| null
是因为在某些情况下,上下文可能没有被提供值,此时会返回
null
。
useReducer
useReducer
用于管理复杂的状态逻辑,我们需要为
action
和
state
定义类型。
import React , { useReducer } from 'react' ; // 定义 action 的类型 type CounterAction = { type : 'increment' } | { type : 'decrement' }; // 定义 state 的类型 type CounterState = { count : number }; // 定义 reducer 函数 const counterReducer = ( state : CounterState , action : CounterAction ): CounterState => { switch (action. type ) { case 'increment' : return { count : state. count + 1 }; case 'decrement' : return { count : state.
count - 1 }; default : return state; } }; const CounterWithReducer : React . FC = () => { const [state, dispatch] = useReducer (counterReducer, { count : 0 }); return ( < div > < p > Count: {state.count} p > < button onClick = {() => dispatch({ type: 'increment' })}>Increment button > < button onClick = {() => dispatch({ type: 'decrement' })}>Decrement button > div > ); }; export default CounterWithReducer ;
自定义 Hooks
自定义 Hooks 同样需要根据其功能和返回值进行类型定义。
import React , { useState, useEffect } from 'react' ; // 自定义 Hook 用于监听窗口大小变化 const useWindowSize = () => { const [windowSize, setWindowSize] = useStatewidth : number ; height : number }>({ width : window . innerWidth , height : window . innerHeight }); useEffect ( () => { const handleResize = () => { setWindowSize ({ width : window . innerWidth , height : window . innerHeight }); }; window . addEventListener ( 'resize' , handleResize); return () => { window . removeEventListener ( 'resize' , handleResize); }; }, []); return windowSize; }; const WindowSizeDisplay : React . FC = () =>
{ const size = useWindowSize (); return ( < div > < p > Window width: {size.width} p > < p > Window height: {size.height} p > div > ); }; export default WindowSizeDisplay ;
useDeferredValue
useDeferredValue
是 React 18 引入的一个 Hook,其目的是用于创建一个值的延迟版本,以便在渲染时可以将高优先级的更新与低优先级的更新分离,提升应用的响应性能。
useDeferredValue
接受一个泛型参数
T
,表示传入的值的类型,返回值的类型与传入值的类型相同。
function useDeferredValue( value : T): T;
举个例子:
import React , { useState, useDeferredValue } from 'react' ; const ExampleComponent : React . FC = () => { // 定义一个字符串类型的状态 const [inputText, setInputText] = useState< string >( '' ); // 使用 useDeferredValue 创建 inputText 的延迟版本 const deferredText = useDeferredValue< string >(inputText); return ( < div > < input type = "text" value = {inputText} onChange = {(e) => setInputText(e.target.value)} placeholder="Type something" /> < p > Immediate value: {inputText} p > < p > Deferred value: {deferredText} p > div > ); }; export default ExampleComponent ;
React 事件处理
在处理事件时需要对事件对象进行类型定义,以此确保代码的类型安全性。
例如,当处理按钮的点击事件时,需要使用
React.MouseEvent
类型来定义事件对象。
import React from 'react' ; const ButtonComponent : React . FC = () => { const handleClick = ( event : React . MouseEvent < HTMLButtonElement > ) => { // event 是 MouseEvent 类型,可以访问相关属性和方法 console . log ( '按钮被点击了' , event. clientX , event. clientY ); }; return ( < button onClick = {handleClick} > 点击我 button > ); }; export default ButtonComponent ;
在这段代码中,
React.MouseEvent
明确了事件对象的类型。泛型参数
HTMLButtonElement
表示事件触发的元素类型为
元素。
事件类型
事件类型包括事件对象类型和事件处理函数类型:
•
事件对象类型
:指在事件处理函数中接收到的事件参数的类型。React 提供了多种事件类型,如
MouseEvent
,
ChangeEvent
,
KeyboardEvent
等,可以通过这些类型来明确事件对象的结构。
•
事件处理函数类型
:指事件处理函数本身的类型签名。它包括函数的参数类型(如事件对象类型)和返回值类型。当定义一个事件处理函数的类型时,实际上也隐含地定义了事件对象的类型。
何时定义:
•
简单场景
:如果只需要处理简单的事件,并且不需要复杂的类型推断或额外的功能,通常可以直接使用
事件对象类型
定义,而不需要显式定义事件处理函数的类型。
•
复杂场景
:如果事件处理函数逻辑较为复杂,或者需要传递额外的参数、处理异步操作等,显式定义事件处理函数的类型可以帮助你更好地管理代码并提高可读性。例如:
•
多个相似的事件处理函数
:如果有多个类似的事件处理函数,显式定义类型可以避免重复代码并提高一致性。
•
复杂的事件处理逻辑
:如果事件处理函数包含复杂的逻辑(如异步操作、状态更新、副作用等),显式定义类型有助于清晰地表达意图。
•
类型检查和推断
:在某些情况下,TypeScript 可能无法正确推断出事件处理函数的类型,显式定义类型可以帮助解决这些问题。
常见的事件对象类型、事件处理函数包括:
事件类型
类型
描述
典型元素
MouseEvent
MouseEventHandler
button
KeyboardEvent
KeyboardEventHandler
input
ChangeEvent
ChangeEventHandler
用于处理表单元素值变化的事件,如输入框内容的变化。
input
FormEvent
FormEventHandler
form
FocusEvent
FocusEventHandler
用于处理获得或失去焦点的事件,如当一个输入框获得或失去焦点时。
input
TouchEvent
TouchEventHandler
用于处理触摸屏上的触摸操作,如触摸开始、移动、结束等。
WheelEvent
WheelEventHandler
用于处理与鼠标滚轮相关的事件,比如用户使用鼠标滚轮进行滚动操作时触发的事件。
DragEvent
DragEventHandler
用于处理拖放操作相关的事件,如开始拖动、拖动进入目标区域等。
SyntheticEvent
SyntheticEventHandler
所有上述事件类型都是基于
SyntheticEvent
,它是 React 对原生浏览器事件进行封装后的通用事件类型。它提供了一组跨浏览器一致的属性和方法。
其中,泛型参数
T
表示触发事件的 DOM 元素类型,默认是
Element
。
元素类型
元素类型指的是 React 中不同 HTML 元素或者自定义组件对应的类型,借助这些类型,我们能够精确处理元素的事件、属性以及其他特性。React 为各种 HTML 元素都提供了对应的类型,这些类型一般以
HTML
开头,后面接上元素名称,例如
HTMLButtonElement
、
HTMLInputElement
等。
import React from
'react' ; // 处理按钮点击事件,指定元素类型为 HTMLButtonElement const handleButtonClick = ( event : React . MouseEvent < HTMLButtonElement > ) => { console . log ( '按钮被点击了' ); }; // 处理输入框值变化事件,指定元素类型为 HTMLInputElement const handleInputChange = ( event : React . ChangeEvent < HTMLInputElement > ) => { console . log ( '输入框的值发生了变化' ); }; const MyComponent : React . FC = () => { return ( < div > < button onClick = {handleButtonClick} > 点击我 button > < input type = "text" onChange = {handleInputChange} /> div > ); }; export default MyComponent ;
常见的元素类型包括:
•
HTMLDivElement
:表示
元素。
•
HTMLButtonElement
:表示
元素。
•
HTMLTextAreaElement
:表示
元素。
•
HTMLSelectElement
:表示
元素。
•
HTMLAnchorElement
:表示
(超链接)元素。
•
HTMLImageElement
:表示
元素。
•
HTMLFormElement
: 表示
元素。
•
HTMLUListElement
: 表示
(无序列表)元素。
•
HTMLOListElement
: 表示
(有序列表)元素。
元素属性类型
元素属性类型用于定义元素可以接收的属性的类型,这样能保证传递给元素的属性符合预期。每个 HTML 元素都有其预定义的属性类型。例如,
元素的
value
属性类型通常是
string
,
disabled
属性类型是
boolean
。
常见的元素属性类型如下:
•
HTMLAttributes
:表示 HTML 属性类型。
•
ButtonHTMLAttributes
:表示按钮属性类型。
•
FormHTMLAttributes
:表示表单属性类型。
•
ImgHTMLAttributes
:表示图片属性类型。
•
InputHTMLAttributes
:表示输入框属性类型。
•
LinkHTMLAttributes
:表示链接属性类型。
•
MetaHTMLAttributes
:表示 meta 属性类型。
•
SelectHTMLAttributes
:表示选择框属性类型。
•
TableHTMLAttributes
:表示表格属性类型。
•
TextareaHTMLAttributes
:表示输入区(文本区域)属性类型。
•
VideoHTMLAttributes
:表示视频属性类型。
•
SVGAttributes
:表示 SVG 属性类型。
•
WebViewHTMLAttributes
:表示 WebView 属性类型。
如果需要扩展原生 HTML 的属性,比如自定义按钮组件,这时就需要用到元素属性类型:
import React from 'react' ; // 使用 ButtonHTMLAttributes 定义自定义按钮组件的属性类型 interface CustomButtonProps extends React . ButtonHTMLAttributes < HTMLButtonElement > { customText ?: string ; // 自定义属性 } // 自定义按钮组件 const CustomButton : React . FC < CustomButtonProps > = ( { customText, ...rest } ) => { return ( < button { ...rest }> {customText || '默认文本'} button > ); }; const App : React . FC = () => { return ( < div > < CustomButton customText = "自定义按钮文本" type = "submit" disabled = {false} onClick = {() => console.log('CustomButton 被点击了')} /> div > ); }; export default App ;
其中,
CustomButtonProps
接口继承自
React.ButtonHTMLAttributes
,这使得
CustomButton
组件可以接收
元素的所有属性。
注意事项
•
事件类型导入
:始终从
react
导入详细的事件类型,不要使用原生
Event
类型:
import { ChangeEvent , MouseEvent } from 'react' ; ✅ // 不要使用 const handle = ( e : Event ) => {} ❌
•
明确泛型参数
:为事件类型指定具体的元素类型,不要使用默认的
Element
类型:
// 明确指定按钮元素 const handleClick = ( e : MouseEvent < HTMLButtonElement