与React集成
与React集成
TypeScript与React的结合为前端开发带来了类型安全和更好的开发体验。React的组件化思想与TypeScript的类型系统天然契合,两者搭配能显著提升代码质量和可维护性。
基础组件类型定义
React函数组件可以通过React.FC
泛型类型或直接标注props类型来定义。类组件则需要继承React.Component
并指定props和state类型。
// 函数组件写法
interface ButtonProps {
text: string;
onClick?: () => void;
}
const Button: React.FC<ButtonProps> = ({ text, onClick }) => {
return <button onClick={onClick}>{text}</button>;
};
// 类组件写法
interface CounterState {
count: number;
}
class Counter extends React.Component<{}, CounterState> {
state = { count: 0 };
increment = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
处理事件类型
React事件处理需要正确标注事件对象类型。React提供了完整的类型定义,如React.MouseEvent
、React.ChangeEvent
等。
const InputField = () => {
const [value, setValue] = React.useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Submitted:', value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
);
};
使用Hooks的类型
React Hooks与TypeScript结合使用时需要特别注意类型推断。useState
可以通过泛型参数显式指定类型。
interface User {
id: number;
name: string;
email: string;
}
const UserProfile = () => {
// 基本类型推断
const [count, setCount] = React.useState(0);
// 对象类型需要显式声明
const [user, setUser] = React.useState<User | null>(null);
// 复杂状态处理
const [todos, setTodos] = React.useState<Array<{id: string; text: string}>>([]);
React.useEffect(() => {
// 模拟API调用
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data));
}, []);
if (!user) return <div>Loading...</div>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
};
高阶组件类型
创建高阶组件时,需要正确处理组件props的类型传递。使用泛型可以保持类型信息不丢失。
interface WithLoadingProps {
isLoading: boolean;
}
function withLoading<P extends object>(
Component: React.ComponentType<P>
): React.FC<P & WithLoadingProps> {
return ({ isLoading, ...props }: P & WithLoadingProps) => {
return isLoading ? (
<div>Loading...</div>
) : (
<Component {...props as P} />
);
};
}
// 使用高阶组件
interface UserListProps {
users: User[];
}
const UserList: React.FC<UserListProps> = ({ users }) => (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
const UserListWithLoading = withLoading(UserList);
// 使用增强后的组件
const App = () => {
const [loading, setLoading] = React.useState(true);
const [users, setUsers] = React.useState<User[]>([]);
React.useEffect(() => {
setTimeout(() => {
setUsers([{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]);
setLoading(false);
}, 1000);
}, []);
return <UserListWithLoading isLoading={loading} users={users} />;
};
Context API的类型安全
使用Context时,创建类型化的createContext可以避免运行时错误。
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
// 提供默认值并指定类型
const ThemeContext = React.createContext<ThemeContextType>({
theme: 'light',
toggleTheme: () => {},
});
const ThemeProvider: React.FC = ({ children }) => {
const [theme, setTheme] = React.useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const ThemedButton = () => {
// 消费Context时获得正确的类型提示
const { theme, toggleTheme } = React.useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff',
}}
>
Toggle Theme
</button>
);
};
处理第三方库
当使用第三方React库时,可能需要扩展其类型定义或创建类型声明。
// 假设我们使用一个未提供类型的第三方库
declare module 'untyped-library' {
interface UntypedComponentProps {
size?: 'sm' | 'md' | 'lg';
variant?: 'primary' | 'secondary';
onClick?: () => void;
}
export const UntypedComponent: React.FC<UntypedComponentProps>;
}
// 使用扩展后的类型
import { UntypedComponent } from 'untyped-library';
const ThirdPartyUsage = () => {
return (
<UntypedComponent
size="md"
variant="primary"
onClick={() => console.log('Clicked')}
/>
);
};
性能优化与类型
使用React.memo
和useCallback
等优化技术时,类型系统可以帮助验证props是否变化。
interface ExpensiveComponentProps {
items: string[];
onSelect: (item: string) => void;
}
const ExpensiveComponent = React.memo<ExpensiveComponentProps>(
({ items, onSelect }) => {
console.log('Component rendered');
return (
<ul>
{items.map(item => (
<li key={item} onClick={() => onSelect(item)}>
{item}
</li>
))}
</ul>
);
},
(prevProps, nextProps) => {
// 自定义比较函数,返回true表示不需要重新渲染
return (
prevProps.items.length === nextProps.items.length &&
prevProps.items.every((item, i) => item === nextProps.items[i])
);
}
);
const ParentComponent = () => {
const [items] = React.useState(['Apple', 'Banana', 'Orange']);
const [selected, setSelected] = React.useState<string | null>(null);
// 使用useCallback避免每次渲染都创建新函数
const handleSelect = React.useCallback((item: string) => {
setSelected(item);
}, []);
return (
<div>
<p>Selected: {selected || 'None'}</p>
<ExpensiveComponent items={items} onSelect={handleSelect} />
</div>
);
};
表单处理与类型
复杂表单场景下,类型系统可以确保表单数据的结构一致性。
interface FormValues {
username: string;
password: string;
remember: boolean;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
const ComplexForm = () => {
const [values, setValues] = React.useState<FormValues>({
username: '',
password: '',
remember: false,
preferences: {
theme: 'light',
notifications: true,
},
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target;
setValues(prev => {
if (name.startsWith('preferences.')) {
const prefKey = name.split('.')[1] as keyof FormValues['preferences'];
return {
...prev,
preferences: {
...prev.preferences,
[prefKey]: type === 'checkbox' ? checked : value,
},
};
}
return {
...prev,
[name]: type === 'checkbox' ? checked : value,
};
});
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Form submitted:', values);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Username:
<input
type="text"
name="username"
value={values.username}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
Password:
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
<input
type="checkbox"
name="remember"
checked={values.remember}
onChange={handleChange}
/>
Remember me
</label>
</div>
<fieldset>
<legend>Preferences</legend>
<div>
<label>
Theme:
<select
name="preferences.theme"
value={values.preferences.theme}
onChange={handleChange}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
</div>
<div>
<label>
<input
type="checkbox"
name="preferences.notifications"
checked={values.preferences.notifications}
onChange={handleChange}
/>
Enable notifications
</label>
</div>
</fieldset>
<button type="submit">Submit</button>
</form>
);
};
路由集成
与React Router等路由库集成时,类型系统可以增强路由参数的安全性。
import { BrowserRouter as Router, Route, Link, useParams } from 'react-router-dom';
interface ProductParams {
id: string;
}
const ProductPage = () => {
// useParams钩子现在可以返回特定类型的参数
const { id } = useParams<ProductParams>();
const [product, setProduct] = React.useState<{ name: string; price: number }>();
React.useEffect(() => {
// 根据id获取产品数据
fetch(`/api/products/${id}`)
.then(res => res.json())
.then(data => setProduct(data));
}, [id]);
if (!product) return <div>Loading...</div>;
return (
<div>
<h2>{product.name}</h2>
<p>Price: ${product.price}</p>
</div>
);
};
const AppWithRouter = () => {
return (
<Router>
<nav>
<ul>
<li><Link to="/product/1">Product 1</Link></li>
<li><Link to="/product/2">Product 2</Link></li>
</ul>
</nav>
<Route path="/product/:id">
<ProductPage />
</Route>
</Router>
);
};
状态管理集成
与Redux或MobX等状态管理库集成时,类型系统可以确保action和state的一致性。
// Redux示例
import { createStore, applyMiddleware } from 'redux';
import { useSelector, useDispatch } from 'react-redux';
import thunk, { ThunkAction } from 'redux-thunk';
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface State {
todos: Todo[];
loading: boolean;
}
// 定义action类型
type Action =
| { type: 'ADD_TODO'; payload: string }
| { type: 'TOGGLE_TODO'; payload: number }
| { type: 'FETCH_TODOS_REQUEST' }
| { type: 'FETCH_TODOS_SUCCESS'; payload: Todo[] };
// 定义thunk action类型
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
State,
unknown,
Action
>;
// reducer函数
const reducer = (state: State = { todos: [], loading: false }, action: Action): State => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [
...state.todos,
{
id: Date.now(),
text: action.payload,
completed: false,
},
],
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
),
};
case 'FETCH_TODOS_REQUEST':
return { ...state, loading: true };
case 'FETCH_TODOS_SUCCESS':
return { ...state, loading: false, todos: action.payload };
default:
return state;
}
};
// 创建store
const store = createStore(reducer, applyMiddleware(thunk));
// action创建函数
const addTodo = (text: string): Action => ({
type: 'ADD_TODO',
payload: text,
});
const fetchTodos = (): AppThunk => dispatch => {
dispatch({ type: 'FETCH_TODOS_REQUEST' });
fetch('/api/todos')
.then(res => res.json())
.then(todos => {
dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos });
});
};
// React组件中使用
const TodoApp = () => {
const todos = useSelector((state: State) => state.todos);
const loading = useSelector((state: State) => state.loading);
const dispatch = useDispatch();
const [text, setText] = React.useState('');
React.useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text));
setText('');
}
};
if (loading) return <div>Loading todos...</div>;
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
/>
<button type="submit">Add Todo</button>
</form>
<ul>
{todos.map(todo => (
<li
key={todo.id}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
>
{todo.text}
</li>
))}
</ul>
</div>
);
};
// 在应用顶层提供store
const ReduxApp = () => (
<Provider store={store}>
<TodoApp />
</Provider>
);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn