React HOC(高阶组件)详解
8.1 HOC(高阶组件)详解
🎯 HOC概念与原理
高阶组件(Higher-Order Component,简称HOC)是React中用于复用组件逻辑的一种高级技术。HOC本质上是一个函数,它接收一个组件作为参数,并返回一个新的增强组件。
// HOC的基本结构
const withEnhancement = (WrappedComponent) => {
const EnhancedComponent = (props) => {
// 增强逻辑
return <WrappedComponent {...props} />;
};
return EnhancedComponent;
};
// 使用HOC
const EnhancedComponent = withEnhancement(OriginalComponent);
🏗️ HOC的设计原则
1. 纯函数特性
// ✅ 好的实践:HOC应该是纯函数
const withLogging = (WrappedComponent) => {
const WithLoggingComponent = (props) => {
console.log('Props:', props);
return <WrappedComponent {...props} />;
};
WithLoggingComponent.displayName = `withLogging(${getDisplayName(WrappedComponent)})`;
return WithLoggingComponent;
};
// 获取组件显示名称的工具函数
const getDisplayName = (WrappedComponent) => {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
};
2. 不修改原始组件
// ❌ 错误实践:直接修改原组件
const badHOC = (WrappedComponent) => {
WrappedComponent.prototype.componentDidMount = () => {
console.log('Modified componentDidMount');
};
return WrappedComponent;
};
// ✅ 正确实践:返回新组件
const goodHOC = (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
console.log('Enhanced componentDidMount');
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
🔧 常用HOC模式实现
1. 日志记录HOC
// 详细的日志记录HOC
const withLogger = (options = {}) => (WrappedComponent) => {
const {
logProps = false,
logState = false,
logMount = true,
logUpdate = true
} = options;
return class extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
if (logMount) {
console.log(`[Mount] ${getDisplayName(WrappedComponent)}`, {
props: logProps ? this.props : undefined,
timestamp: new Date().toISOString()
});
}
}
componentDidUpdate(prevProps, prevState) {
if (logUpdate) {
console.log(`[Update] ${getDisplayName(WrappedComponent)}`, {
prevProps: logProps ? prevProps : undefined,
currentProps: logProps ? this.props : undefined,
timestamp: new Date().toISOString()
});
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
// 使用示例
const LoggedButton = withLogger({
logProps: true,
logMount: true,
logUpdate: true
})(Button);
2. 权限控制HOC
// 高级权限控制HOC
const withAuthorization = (options) => (WrappedComponent) => {
const {
requiredPermissions = [],
requiredRoles = [],
requireAuth = false,
fallbackComponent = null,
redirectTo = null
} = options;
return function AuthorizedComponent(props) {
const { user } = useContext(AuthContext);
const navigate = useNavigate();
// 检查是否需要认证
if (requireAuth && !user) {
if (redirectTo) {
navigate(redirectTo);
}
return fallbackComponent || <div>请先登录</div>;
}
// 检查角色权限
if (requiredRoles.length > 0 && user) {
const hasRequiredRole = requiredRoles.some(role =>
user.roles.includes(role)
);
if (!hasRequiredRole) {
return fallbackComponent || <div>权限不足</div>;
}
}
// 检查具体权限
if (requiredPermissions.length > 0 && user) {
const hasRequiredPermission = requiredPermissions.every(permission =>
user.permissions.includes(permission)
);
if (!hasRequiredPermission) {
return fallbackComponent || <div>权限不足</div>;
}
}
return <WrappedComponent {...props} />;
};
};
// 使用示例
const AdminPanel = withAuthorization({
requireAuth: true,
requiredRoles: ['admin'],
requiredPermissions: ['panel.access']
})(Dashboard);
3. 加载状态HOC
// 智能加载状态HOC
const withLoading = (options = {}) => (WrappedComponent) => {
const {
loadingProp = 'loading',
errorProp = 'error',
LoadingComponent = DefaultLoading,
ErrorComponent = DefaultError,
delay = 200 // 延迟显示loading
} = options;
return function WithLoadingComponent(props) {
const [showLoading, setShowLoading] = useState(false);
const isLoading = props[loadingProp];
const error = props[errorProp];
useEffect(() => {
let timeoutId;
if (isLoading) {
timeoutId = setTimeout(() => {
setShowLoading(true);
}, delay);
} else {
setShowLoading(false);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [isLoading]);
// 错误状态处理
if (error) {
return <ErrorComponent error={error} />;
}
// 加载状态处理
if (isLoading && showLoading) {
return <LoadingComponent />;
}
return <WrappedComponent {...props} />;
};
};
// 默认加载组件
const DefaultLoading = () => (
<div className="loading-spinner">
<div className="spinner"></div>
<p>加载中...</p>
</div>
);
// 默认错误组件
const DefaultError = ({ error }) => (
<div className="error-message">
<h3>出错了</h3>
<p>{error.message || '未知错误'}</p>
</div>
);
4. 数据获取HOC
// 通用数据获取HOC
const withData = (options) => (WrappedComponent) => {
const {
dataSource,
dataProp = 'data',
loadingProp = 'loading',
errorProp = 'error',
transform = null,
cache = false,
refetchOnMount = false
} = options;
return function WithDataComponent(props) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const cacheRef = useRef(new Map());
const fetchData = useCallback(async () => {
if (cache && cacheRef.current.has(dataSource)) {
const cachedData = cacheRef.current.get(dataSource);
setData(cachedData);
return;
}
setLoading(true);
setError(null);
try {
const result = await dataSource();
const transformedData = transform ? transform(result) : result;
setData(transformedData);
if (cache) {
cacheRef.current.set(dataSource, transformedData);
}
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [dataSource, transform, cache]);
useEffect(() => {
fetchData();
}, [fetchData]);
// 手动刷新方法
const refetch = () => {
if (cache) {
cacheRef.current.delete(dataSource);
}
fetchData();
};
const enhancedProps = {
...props,
[dataProp]: data,
[loadingProp]: loading,
[errorProp]: error,
refetch
};
return <WrappedComponent {...enhancedProps} />;
};
};
// 使用示例
const UserListWithData = withData({
dataSource: () => api.fetchUsers(),
dataProp: 'users',
transform: (data) => data.users,
cache: true
})(UserList);
🔗 HOC链式调用与组合
1. 多HOC组合
// 组合工具函数
const compose = (...hocs) => (Component) => {
return hocs.reduceRight((acc, hoc) => hoc(acc), Component);
};
// 使用组合函数
const EnhancedComponent = compose(
withLogger({ logProps: true }),
withAuthorization({ requireAuth: true }),
withLoading({ delay: 300 }),
withData({
dataSource: api.fetchUserProfile,
dataProp: 'profile'
})
)(UserProfile);
// 或者使用pipe函数(从左到右组合)
const pipe = (...hocs) => (Component) => {
return hocs.reduce((acc, hoc) => hoc(acc), Component);
};
2. 参数化HOC工厂
// 创建可配置的HOC工厂
const createConfigurableHOC = (defaultConfig = {}) => {
return (config = {}) => (WrappedComponent) => {
const finalConfig = { ...defaultConfig, ...config };
return function ConfigurableHOC(props) {
// 使用finalConfig创建增强逻辑
return <WrappedComponent {...props} config={finalConfig} />;
};
};
};
// 创建特定用途的HOC
const withApiConfig = createConfigurableHOC({
baseUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
});
// 使用配置化的HOC
const ApiComponent = withApiConfig({
baseUrl: 'https://custom-api.example.com',
timeout: 10000
})(MyComponent);
⚡ HOC性能优化
1. 记忆化HOC
// 使用React.memo优化HOC
const memoizedHOC = (WrappedComponent) => {
const MemoizedComponent = React.memo(WrappedComponent);
MemoizedComponent.displayName = `memoized(${getDisplayName(WrappedComponent)})`;
return MemoizedComponent;
};
// 自定义比较函数的HOC
const withCustomCompare = (areEqual) => (WrappedComponent) => {
const MemoizedComponent = React.memo(
WrappedComponent,
(prevProps, nextProps) => areEqual(prevProps, nextProps)
);
return MemoizedComponent;
};
2. 避免不必要的重新渲染
// 性能优化的HOC
const withPerformanceOptimization = (WrappedComponent) => {
return class extends React.Component {
shouldComponentUpdate(nextProps) {
// 自定义更新逻辑
return !shallowEqual(this.props, nextProps);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
🐛 HOC常见陷阱与解决方案
1. Props传递问题
// ❌ 问题:静默覆盖props
const badWithTheme = (WrappedComponent) => {
return (props) => {
// 可能覆盖传入的theme prop
return <WrappedComponent theme="dark" {...props} />;
};
};
// ✅ 解决:谨慎处理props覆盖
const goodWithTheme = (defaultTheme = 'light') => (WrappedComponent) => {
return (props) => {
const { theme = defaultTheme, ...restProps } = props;
return <WrappedComponent theme={theme} {...restProps} />;
};
};
2. 静态方法丢失
// 解决静态方法丢失问题
const hoistStatics = (target, source) => {
Object.getOwnPropertyNames(source)
.filter(name => typeof source[name] === 'function')
.forEach(name => {
target[name] = source[name];
});
return target;
};
const withStatics = (WrappedComponent) => {
const EnhancedComponent = (props) => {
return <WrappedComponent {...props} />;
};
// 拷贝静态方法
hoistStatics(EnhancedComponent, WrappedComponent);
return EnhancedComponent;
};
3. Refs传递问题
// 解决Refs传递问题
const withForwardRef = (WrappedComponent) => {
const WithForwardRef = React.forwardRef((props, ref) => {
return <WrappedComponent {...props} forwardedRef={ref} />;
});
WithForwardRef.displayName = `withForwardRef(${getDisplayName(WrappedComponent)})`;
return WithForwardRef;
};
// 在被包装组件中使用ref
const OriginalComponent = ({ forwardedRef, ...props }) => {
return <div ref={forwardedRef} {...props} />;
};
📊 HOC与其他模式对比
| 特性 | HOC | Render Props | 自定义Hooks |
|---|---|---|---|
| 复用逻辑 | ✅ | ✅ | ✅ |
| 代码嵌套 | 可能较深 | 较深 | 浅 |
| 类型安全 | 一般 | 良好 | 优秀 |
| 组合性 | 良好 | 优秀 | 优秀 |
| 学习成本 | 中等 | 中等 | 低 |
| 性能 | 一般 | 一般 | 优秀 |
🎯 HOC最佳实践总结
- 单一职责原则:每个HOC只负责一个特定的功能
- 命名规范:使用
with前缀,清晰的displayName - Props透明:避免静默覆盖props,谨慎处理
- 性能考虑:使用React.memo,避免不必要的重渲染
- 类型安全:在TypeScript项目中提供完整的类型定义
- 文档完善:为每个HOC提供详细的使用文档和示例
- 测试覆盖:编写单元测试确保HOC的可靠性
HOC是React中强大的模式,但也要谨慎使用,避免过度抽象导致代码难以理解和维护。
8.2 Render Props模式
🎯 Render Props概念与原理
Render Props是一种在React组件之间共享代码的简单技术,其核心思想是:一个组件接收一个函数作为props,该函数返回React元素并调用该函数而不是实现自己的渲染逻辑。
// Render Props的基本结构
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
// 使用示例
<MouseTracker
render={({ x, y }) => (
<h1>鼠标位置: ({x}, {y})</h1>
)}
/>
🏗️ Render Props的设计模式
1. 基础Render Props
// 通用数据提供者组件
class DataProvider extends React.Component {
state = {
data: null,
loading: false,
error: null
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
this.setState({ loading: true, error: null });
try {
const data = await this.props.dataSource();
this.setState({ data, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
};
render() {
return this.props.children({
...this.state,
refetch: this.fetchData
});
}
}
// 使用示例
<DataProvider dataSource={api.fetchUsers}>
{({ data, loading, error, refetch }) => {
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
if (!data) return <div>暂无数据</div>;
return (
<div>
<button onClick={refetch}>刷新</button>
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}}
</DataProvider>
2. 表单处理Render Props
// 通用表单状态管理
class FormProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
values: props.initialValues || {},
errors: {},
touched: {},
isSubmitting: false
};
}
setFieldValue = (name, value) => {
this.setState(prevState => ({
values: {
...prevState.values,
[name]: value
}
}));
};
setFieldError = (name, error) => {
this.setState(prevState => ({
errors: {
...prevState.errors,
[name]: error
}
}));
};
setFieldTouched = (name, touched = true) => {
this.setState(prevState => ({
touched: {
...prevState.touched,
[name]: touched
}
}));
};
validateField = (name, value) => {
const validationSchema = this.props.validationSchema;
if (validationSchema && validationSchema[name]) {
return validationSchema[name](value);
}
return '';
};
handleChange = (name) => (event) => {
const value = event.target.value;
this.setFieldValue(name, value);
// 实时验证
const error = this.validateField(name, value);
this.setFieldError(name, error);
};
handleBlur = (name) => () => {
this.setFieldTouched(name);
const { values } = this.state;
const error = this.validateField(name, values[name]);
this.setFieldError(name, error);
};
handleSubmit = (callback) => async (event) => {
event.preventDefault();
this.setState({ isSubmitting: true });
try {
await callback(this.state.values);
} catch (error) {
console.error('表单提交错误:', error);
} finally {
this.setState({ isSubmitting: false });
}
};
getFieldProps = (name) => ({
value: this.state.values[name] || '',
onChange: this.handleChange(name),
onBlur: this.handleBlur(name),
error: this.state.touched[name] && this.state.errors[name]
});
render() {
return this.props.render({
...this.state,
setFieldValue: this.setFieldValue,
setFieldError: this.setFieldError,
setFieldTouched: this.setFieldTouched,
handleSubmit: this.handleSubmit,
getFieldProps: this.getFieldProps
});
}
}
// 使用示例
const LoginForm = () => (
<FormProvider
initialValues={{ email: '', password: '' }}
validationSchema={{
email: (value) => {
if (!value) return '邮箱不能为空';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return '邮箱格式不正确';
}
return '';
},
password: (value) => {
if (!value) return '密码不能为空';
if (value.length < 6) return '密码至少6位';
return '';
}
}}
>
{({ values, getFieldProps, handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit(async (values) => {
console.log('提交表单:', values);
// 提交逻辑
})}>
<div>
<input
{...getFieldProps('email')}
placeholder="邮箱"
type="email"
/>
</div>
<div>
<input
{...getFieldProps('password')}
placeholder="密码"
type="password"
/>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '登录中...' : '登录'}
</button>
</form>
)}
</FormProvider>
);
3. 动画Render Props
// 动画状态管理组件
class AnimationProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
isAnimating: false,
progress: 0,
duration: props.duration || 300,
easing: props.easing || 'ease'
};
}
start = (callback) => {
if (this.state.isAnimating) return;
this.setState({ isAnimating: true, progress: 0 });
const startTime = performance.now();
const duration = this.state.duration;
const animate = (currentTime) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
this.setState({ progress });
if (progress < 1) {
requestAnimationFrame(animate);
} else {
this.setState({ isAnimating: false });
if (callback) callback();
}
};
requestAnimationFrame(animate);
};
stop = () => {
this.setState({ isAnimating: false, progress: 1 });
};
reset = () => {
this.setState({ isAnimating: false, progress: 0 });
};
render() {
const { children } = this.props;
const { isAnimating, progress } = this.state;
return children({
isAnimating,
progress,
start: this.start,
stop: this.stop,
reset: this.reset
});
}
}
// 使用示例
const AnimatedBox = () => (
<AnimationProvider duration={1000}>
{({ progress, start, isAnimating }) => (
<div>
<div
style={{
width: 100 * progress,
height: 100,
backgroundColor: '#007bff',
transition: 'width 0.1s ease-out'
}}
/>
<button
onClick={() => start()}
disabled={isAnimating}
>
{isAnimating ? '动画进行中' : '开始动画'}
</button>
</div>
)}
</AnimationProvider>
);
🔧 高级Render Props模式
1. 多层Render Props组合
// 组合多个数据源
const ComposedProvider = ({ children }) => (
<DataProvider dataSource={api.fetchUser}>
{({ data: user, loading: userLoading }) => (
<DataProvider dataSource={api.fetchPosts}>
{({ data: posts, loading: postsLoading }) => (
<DataProvider dataSource={api.fetchComments}>
{({ data: comments, loading: commentsLoading }) => {
const combinedData = {
user,
posts,
comments,
loading: userLoading || postsLoading || commentsLoading,
ready: !userLoading && !postsLoading && !commentsLoading
};
return children(combinedData);
}}
</DataProvider>
)}
</DataProvider>
)}
</DataProvider>
);
// 使用示例
<ComposedProvider>
{({ user, posts, comments, loading, ready }) => {
if (loading) return <div>加载中...</div>;
if (!ready) return <div>准备中...</div>;
return (
<div>
<h1>欢迎, {user.name}</h1>
<div>
<h2>文章列表 ({posts.length})</h2>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
<span>评论数: {comments.filter(c => c.postId === post.id).length}</span>
</div>
))}
</div>
</div>
);
}}
</ComposedProvider>
2. 条件Render Props
// 条件渲染提供者
class ConditionalProvider extends React.Component {
state = {
condition: this.props.initialCondition || false,
value: null
};
toggle = () => {
this.setState(prevState => ({
condition: !prevState.condition
}));
};
setCondition = (condition) => {
this.setState({ condition });
};
setValue = (value) => {
this.setState({ value });
};
render() {
const { children, trueComponent, falseComponent } = this.props;
const { condition, value } = this.state;
return children({
condition,
value,
toggle: this.toggle,
setCondition: this.setCondition,
setValue: this.setValue,
render: condition ? trueComponent : falseComponent
});
}
}
// 使用示例
const ConditionalComponent = () => (
<ConditionalProvider
trueComponent={<div>条件为真</div>}
falseComponent={<div>条件为假</div>}
>
{({ condition, toggle, render }) => (
<div>
<button onClick={toggle}>
切换条件 (当前: {condition ? '真' : '假'})
</button>
<div>{render}</div>
</div>
)}
</ConditionalProvider>
);
⚡ Render Props性能优化
1. 记忆化渲染函数
// 使用React.memo优化Render Props
const OptimizedProvider = ({ children }) => {
const [state, setState] = useState({ count: 0 });
// 记忆化渲染函数
const memoizedChildren = useMemo(() => {
return children(state);
}, [state, children]);
return memoizedChildren;
};
// 或者使用useCallback
const CallbackProvider = ({ children }) => {
const [state, setState] = useState({ count: 0 });
const renderProps = useCallback(() => {
return children(state);
}, [state, children]);
return renderProps();
};
2. 避免匿名函数
// ❌ 避免在render中创建匿名函数
const BadExample = () => (
<DataProvider>
{({ data }) => (
<button onClick={() => console.log(data)}>
点击
</button>
)}
</DataProvider>
);
// ✅ 使用稳定的函数引用
const GoodExample = () => {
const handleClick = useCallback((data) => {
console.log(data);
}, []);
return (
<DataProvider>
{({ data }) => (
<button onClick={() => handleClick(data)}>
点击
</button>
)}
</DataProvider>
);
};
🔗 Render Props与其他模式对比
1. Render Props vs HOC
// Render Props方式
const WithMouse = ({ render }) => (
<MouseTracker render={render} />
);
// HOC方式
const withMouse = (Component) => (props) => (
<MouseTracker
render={mouseProps => <Component {...props} {...mouseProps} />}
/>
);
// 使用对比
// Render Props
<WithMouse render={({ x, y }) => <Mouse x={x} y={y} />} />
// HOC
const EnhancedMouse = withMouse(Mouse);
<EnhancedMouse />
2. Render Props vs 自定义Hooks
// Render Props方式
const MouseTrackerRenderProps = ({ children }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return children(position);
};
// 自定义Hook方式
const useMousePosition = () => {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return position;
};
// 使用对比
// Render Props
<MouseTrackerRenderProps>
{({ x, y }) => <div>鼠标位置: {x}, {y}</div>}
</MouseTrackerRenderProps>
// 自定义Hook
const MouseComponent = () => {
const { x, y } = useMousePosition();
return <div>鼠标位置: {x}, {y}</div>;
};
🛠️ 实用Render Props组件库
1. 状态管理组件
// 通用状态管理
class StateProvider extends React.Component {
constructor(props) {
super(props);
this.state = props.initialState || {};
}
updateState = (updates) => {
this.setState(prevState => ({
...prevState,
...updates
}));
};
resetState = () => {
this.setState(this.props.initialState || {});
};
render() {
return this.props.children({
...this.state,
updateState: this.updateState,
resetState: this.resetState,
setState: this.setState.bind(this)
});
}
}
// 使用示例
<StateProvider initialState={{ count: 0, name: 'React' }}>
{({ count, name, updateState, resetState }) => (
<div>
<p>计数: {count}</p>
<p>名称: {name}</p>
<button onClick={() => updateState({ count: count + 1 })}>
增加计数
</button>
<button onClick={() => updateState({ name: name + '!' })}>
修改名称
</button>
<button onClick={resetState}>
重置状态
</button>
</div>
)}
</StateProvider>
2. 网络请求组件
// 网络请求管理
class FetchProvider extends React.Component {
state = {
data: null,
loading: false,
error: null,
status: 'idle' // idle, loading, success, error
};
execute = async (url, options = {}) => {
this.setState({ loading: true, status: 'loading', error: null });
try {
const response = await fetch(url, options);
const data = await response.json();
this.setState({
data,
loading: false,
status: 'success'
});
return data;
} catch (error) {
this.setState({
error,
loading: false,
status: 'error'
});
throw error;
}
};
reset = () => {
this.setState({
data: null,
loading: false,
error: null,
status: 'idle'
});
};
render() {
return this.props.children({
...this.state,
execute: this.execute,
reset: this.reset
});
}
}
// 使用示例
<FetchProvider>
{({ data, loading, error, status, execute, reset }) => (
<div>
<button
onClick={() => execute('https://api.example.com/data')}
disabled={loading}
>
获取数据
</button>
<button onClick={reset}>重置</button>
{status === 'loading' && <div>加载中...</div>}
{status === 'error' && <div>错误: {error.message}</div>}
{status === 'success' && (
<pre>{JSON.stringify(data, null, 2)}</pre>
)}
</div>
)}
</FetchProvider>
📊 Render Props最佳实践
1. 命名规范
// ✅ 清晰的prop命名
<MouseTracker render={mouse => <Mouse {...mouse} />} />
<DataFetcher children={({ data, loading, error }) => ...} />
// 使用children作为render prop
<MouseTracker>
{mouse => <Mouse {...mouse} />}
</MouseTracker>
2. 类型安全(TypeScript)
interface MouseTrackerProps {
render?: (mouse: MousePosition) => React.ReactNode;
children?: (mouse: MousePosition) => React.ReactNode;
}
interface MousePosition {
x: number;
y: number;
}
const MouseTracker: React.FC<MouseTrackerProps> = ({ render, children }) => {
const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
const mouseProps = {
x: position.x,
y: position.y
};
return (render || children)(mouseProps);
};
3. 错误边界处理
class RenderPropsErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Render Props Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>出错了</div>;
}
return this.props.children;
}
}
// 使用错误边界
<RenderPropsErrorBoundary fallback={<div>渲染出错</div>}>
<MouseTracker>
{mouse => <MouseComponent {...mouse} />}
</MouseTracker>
</RenderPropsErrorBoundary>
🎯 Render Props优缺点总结
✅ 优点
- 灵活性高:可以动态决定渲染内容
- 组合性好:容易组合多个Render Props
- 可读性强:渲染逻辑集中在一处
- 类型安全:TypeScript支持良好
- 调试友好:渲染层次清晰
❌ 缺点
- 嵌套地狱:多层嵌套可能导致代码难以阅读
- 性能问题:匿名函数可能影响性能优化
- 学习成本:需要理解函数式编程概念
- 代码冗余:有时会产生重复代码
Render Props模式为React组件间的逻辑共享提供了强大而灵活的解决方案,在适当场景下使用能显著提升代码的可维护性和复用性。
8.3 自定义Hooks详解
🎯 自定义Hooks概念与原理
自定义Hooks是React 16.8引入的强大特性,它允许你复用状态逻辑而不改变组件层次结构。自定义Hook是一个以use开头的函数,其内部可以调用其他Hook。
// 自定义Hook的基本结构
function useCustomHook(initialValue) {
const [state, setState] = useState(initialValue);
// 可以使用其他Hook
useEffect(() => {
// 副作用逻辑
}, []);
return [state, setState];
}
// 使用自定义Hook
function MyComponent() {
const [value, setValue] = useCustomHook(0);
return <button onClick={() => setValue(value + 1)}>{value}</button>;
}
🏗️ 自定义Hooks设计原则
1. 命名规范
// ✅ 正确的命名规范
const useLocalStorage = (key, initialValue) => { ... };
const useApi = (url) => { ... };
const useForm = (initialValues) => { ... };
const useDebounce = (value, delay) => { ... };
// ❌ 错误的命名
const localStorage = (key, initialValue) => { ... }; // 缺少use前缀
const UseApi = (url) => { ... }; // 首字母不应该大写
const use_api = (url) => { ... }; // 应该使用驼峰命名
2. 单一职责原则
// ✅ 职责单一的Hook
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
};
// ❌ 职责过多的Hook
const useEverything = () => {
// 混合了计数器、表单、API调用等多种逻辑
const [count, setCount] = useState(0);
const [form, setForm] = useState({});
const [data, setData] = useState(null);
// ... 太多职责
};
🔧 实用自定义Hooks实现
1. useLocalStorage - 本地存储管理
// 本地存储Hook
const useLocalStorage = (key, initialValue) => {
// 获取初始值
const getStoredValue = () => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
};
const [storedValue, setStoredValue] = useState(getStoredValue);
// 更新localStorage和state
const setValue = useCallback((value) => {
try {
// 允许传入函数来更新值
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
// 删除存储项
const removeValue = useCallback(() => {
try {
window.localStorage.removeItem(key);
setStoredValue(initialValue);
} catch (error) {
console.error(`Error removing localStorage key "${key}":`, error);
}
}, [key, initialValue]);
return [storedValue, setValue, removeValue];
};
// 使用示例
const App = () => {
const [name, setName] = useLocalStorage('userName', '');
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="输入你的名字"
/>
<p>你好, {name}!</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题: {theme}
</button>
</div>
);
};
2. useApi - API请求管理
// API请求Hook
const useApi = (url, options = {}) => {
const {
immediate = true,
dependencies = [],
onSuccess,
onError,
retryCount = 0,
retryDelay = 1000
} = options;
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [retryAttempts, setRetryAttempts] = useState(0);
// 发起请求
const execute = useCallback(async (requestUrl = url) => {
setLoading(true);
setError(null);
try {
const response = await fetch(requestUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
if (onSuccess) {
onSuccess(result);
}
setRetryAttempts(0); // 重置重试计数
return result;
} catch (err) {
setError(err);
// 重试逻辑
if (retryAttempts < retryCount) {
setTimeout(() => {
setRetryAttempts(prev => prev + 1);
execute(requestUrl);
}, retryDelay * (retryAttempts + 1)); // 指数退避
}
if (onError) {
onError(err);
}
throw err;
} finally {
setLoading(false);
}
}, [url, onSuccess, onError, retryCount, retryDelay, retryAttempts]);
// 重置状态
const reset = useCallback(() => {
setData(null);
setLoading(false);
setError(null);
setRetryAttempts(0);
}, []);
// 组件挂载时自动执行
useEffect(() => {
if (immediate && url) {
execute();
}
}, [execute, immediate, url, ...dependencies]);
return {
data,
loading,
error,
execute,
reset,
retryAttempts
};
};
// 使用示例
const UserList = () => {
const { data: users, loading, error, execute } = useApi(
'https://jsonplaceholder.typicode.com/users',
{
immediate: true,
onSuccess: (data) => console.log('获取用户成功:', data),
onError: (err) => console.error('获取用户失败:', err),
retryCount: 3
}
);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
if (!users) return <div>暂无数据</div>;
return (
<div>
<button onClick={() => execute()}>
刷新用户列表
</button>
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
</div>
);
};
3. useForm - 表单状态管理
// 表单管理Hook
const useForm = (initialValues = {}, validationSchema = {}) => {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
// 验证单个字段
const validateField = useCallback((name, value) => {
if (validationSchema[name]) {
const validator = validationSchema[name];
return validator(value, values);
}
return '';
}, [validationSchema, values]);
// 验证整个表单
const validateForm = useCallback(() => {
const newErrors = {};
Object.keys(validationSchema).forEach(name => {
const error = validateField(name, values[name]);
if (error) {
newErrors[name] = error;
}
});
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}, [validationSchema, values, validateField]);
// 设置字段值
const setFieldValue = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// 实时验证
if (touched[name]) {
const error = validateField(name, value);
setErrors(prev => ({ ...prev, [name]: error }));
}
}, [touched, validateField]);
// 处理字段变更
const handleChange = useCallback((name) => (event) => {
const value = event.target.type === 'checkbox'
? event.target.checked
: event.target.value;
setFieldValue(name, value);
}, [setFieldValue]);
// 处理字段失焦
const handleBlur = useCallback((name) => () => {
setTouched(prev => ({ ...prev, [name]: true }));
const error = validateField(name, values[name]);
setErrors(prev => ({ ...prev, [name]: error }));
}, [validateField, values]);
// 获取字段的props
const getFieldProps = useCallback((name) => ({
value: values[name] || '',
onChange: handleChange(name),
onBlur: handleBlur(name),
error: touched[name] && errors[name],
helperText: touched[name] && errors[name]
}), [values, handleChange, handleBlur, touched, errors]);
// 提交表单
const handleSubmit = useCallback(async (onSubmit) => {
setIsSubmitting(true);
setTouched(Object.keys(values)); // 触发所有字段的验证
const isValid = validateForm();
if (isValid) {
try {
await onSubmit(values);
} catch (error) {
console.error('表单提交错误:', error);
}
}
setIsSubmitting(false);
return isValid;
}, [values, validateForm]);
// 重置表单
const resetForm = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
setIsSubmitting(false);
}, [initialValues]);
return {
values,
errors,
touched,
isSubmitting,
setFieldValue,
handleChange,
handleBlur,
getFieldProps,
handleSubmit,
resetForm,
validateForm
};
};
// 使用示例
const RegistrationForm = () => {
const {
values,
errors,
isSubmitting,
getFieldProps,
handleSubmit,
resetForm
} = useForm(
{ email: '', password: '', confirmPassword: '' },
{
email: (value) => {
if (!value) return '邮箱不能为空';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return '邮箱格式不正确';
}
return '';
},
password: (value) => {
if (!value) return '密码不能为空';
if (value.length < 8) return '密码至少8位';
return '';
},
confirmPassword: (value, allValues) => {
if (!value) return '确认密码不能为空';
if (value !== allValues.password) return '两次密码不一致';
return '';
}
}
);
const onSubmit = async (formData) => {
console.log('提交表单:', formData);
// 提交逻辑
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(onSubmit);
}}>
<div>
<input {...getFieldProps('email')} placeholder="邮箱" />
</div>
<div>
<input
{...getFieldProps('password')}
type="password"
placeholder="密码"
/>
</div>
<div>
<input
{...getFieldProps('confirmPassword')}
type="password"
placeholder="确认密码"
/>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '提交中...' : '注册'}
</button>
<button type="button" onClick={resetForm}>
重置
</button>
</form>
);
};
4. useDebounce - 防抖处理
// 防抖Hook
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
// 防抖回调Hook
const useDebouncedCallback = (callback, delay) => {
const callbackRef = useRef(callback);
const timeoutRef = useRef(null);
// 确保callback总是最新的
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
const debouncedCallback = useCallback((...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callbackRef.current(...args);
}, delay);
}, [delay]);
// 清理函数
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return debouncedCallback;
};
// 使用示例
const SearchComponent = () => {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
const { data, execute } = useApi(
debouncedSearchTerm
? `https://api.example.com/search?q=${debouncedSearchTerm}`
: null
);
const handleSearch = useDebouncedCallback((term) => {
console.log('搜索:', term);
}, 300);
return (
<div>
<input
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
handleSearch(e.target.value);
}}
placeholder="搜索..."
/>
{data && (
<div>
<h3>搜索结果:</h3>
<ul>
{data.results.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
)}
</div>
);
};
5. useToggle - 状态切换
// 切换状态Hook
const useToggle = (initialState = false) => {
const [state, setState] = useState(initialState);
const toggle = useCallback(() => {
setState(prev => !prev);
}, []);
const setTrue = useCallback(() => {
setState(true);
}, []);
const setFalse = useCallback(() => {
setState(false);
}, []);
return [state, toggle, setTrue, setFalse];
};
// 多状态切换Hook
const useMultiToggle = (initialState, options) => {
const [state, setState] = useState(initialState);
const setValue = useCallback((value) => {
if (options.includes(value)) {
setState(value);
}
}, [options]);
const next = useCallback(() => {
const currentIndex = options.indexOf(state);
const nextIndex = (currentIndex + 1) % options.length;
setState(options[nextIndex]);
}, [state, options]);
const prev = useCallback(() => {
const currentIndex = options.indexOf(state);
const prevIndex = currentIndex === 0 ? options.length - 1 : currentIndex - 1;
setState(options[prevIndex]);
}, [state, options]);
return [state, setValue, next, prev];
};
// 使用示例
const ModalComponent = () => {
const [isModalOpen, toggleModal, openModal, closeModal] = useToggle(false);
const [theme, setTheme, nextTheme] = useMultiToggle('light', ['light', 'dark', 'auto']);
return (
<div className={`theme-${theme}`}>
<button onClick={toggleModal}>
{isModalOpen ? '关闭弹窗' : '打开弹窗'}
</button>
<button onClick={nextTheme}>
切换主题: {theme}
</button>
{isModalOpen && (
<div className="modal">
<h2>这是一个弹窗</h2>
<p>当前主题: {theme}</p>
<button onClick={closeModal}>关闭</button>
</div>
)}
</div>
);
};
⚡ 高级自定义Hooks模式
1. useInterval - 间隔器管理
// 智能间隔器Hook
const useInterval = (callback, delay, options = {}) => {
const { immediate = false, deps = [] } = options;
const savedCallback = useRef();
const intervalId = useRef();
// 记录最新的callback
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// 设置间隔器
const setupInterval = useCallback(() => {
if (delay !== null) {
intervalId.current = setInterval(() => {
savedCallback.current();
}, delay);
}
}, [delay]);
// 清理间隔器
const clearInterval = useCallback(() => {
if (intervalId.current) {
window.clearInterval(intervalId.current);
}
}, []);
// 立即执行
useEffect(() => {
if (immediate && delay !== null) {
savedCallback.current();
}
}, [immediate, delay]);
// 处理间隔器的设置和清理
useEffect(() => {
setupInterval();
return clearInterval;
}, [setupInterval, clearInterval, ...deps]);
// 手动控制
const start = useCallback(() => {
clearInterval();
setupInterval();
}, [clearInterval, setupInterval]);
const stop = useCallback(() => {
clearInterval();
}, [clearInterval]);
return { start, stop };
};
// 使用示例
const TimerComponent = () => {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useToggle(false);
const { start, stop } = useInterval(
() => {
setSeconds(prev => prev + 1);
},
isRunning ? 1000 : null,
{ immediate: true }
);
const handleStart = () => {
setIsRunning(true);
start();
};
const handleStop = () => {
setIsRunning(false);
stop();
};
const handleReset = () => {
setSeconds(0);
handleStop();
};
return (
<div>
<h2>计时器: {seconds}秒</h2>
<button onClick={handleStart} disabled={isRunning}>
开始
</button>
<button onClick={handleStop} disabled={!isRunning}>
停止
</button>
<button onClick={handleReset}>
重置
</button>
</div>
);
};
2. useInfiniteScroll - 无限滚动
// 无限滚动Hook
const useInfiniteScroll = (fetchMore, options = {}) => {
const {
threshold = 100,
root = null,
rootMargin = '0px',
hasMore = true,
loading = false
} = options;
const [page, setPage] = useState(1);
const observerRef = useRef();
const targetRef = useRef();
const fetchMoreData = useCallback(async () => {
if (loading || !hasMore) return;
try {
await fetchMore(page + 1);
setPage(prev => prev + 1);
} catch (error) {
console.error('加载更多数据失败:', error);
}
}, [fetchMore, page, loading, hasMore]);
// 设置Intersection Observer
useEffect(() => {
const target = targetRef.current;
if (!target || !hasMore) return;
observerRef.current = new IntersectionObserver(
(entries) => {
const [entry] = entries;
if (entry.isIntersecting && !loading) {
fetchMoreData();
}
},
{
root,
rootMargin,
threshold
}
);
observerRef.current.observe(target);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
}
};
}, [fetchMoreData, hasMore, loading, root, rootMargin, threshold]);
const reset = useCallback(() => {
setPage(1);
}, []);
return {
targetRef,
page,
reset,
fetchMore: fetchMoreData
};
};
// 使用示例
const InfiniteList = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const fetchMoreItems = async (page) => {
setLoading(true);
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`
);
const newItems = await response.json();
if (newItems.length === 0) {
setHasMore(false);
} else {
setItems(prev => page === 1 ? newItems : [...prev, ...newItems]);
}
} catch (error) {
console.error('加载数据失败:', error);
} finally {
setLoading(false);
}
};
const { targetRef, reset } = useInfiniteScroll(fetchMoreItems, {
threshold: 0.1,
hasMore,
loading
});
const handleRefresh = () => {
setItems([]);
setHasMore(true);
reset();
};
return (
<div>
<button onClick={handleRefresh}>刷新</button>
<div style={{ height: '500px', overflow: 'auto' }}>
{items.map(item => (
<div key={item.id} style={{ padding: '20px', borderBottom: '1px solid #eee' }}>
<h3>{item.title}</h3>
<img src={item.thumbnailUrl} alt={item.title} />
</div>
))}
<div ref={targetRef} style={{ textAlign: 'center', padding: '20px' }}>
{loading && <div>加载中...</div>}
{!hasMore && <div>没有更多数据了</div>}
</div>
</div>
</div>
);
};
🛠️ 自定义Hooks最佳实践
1. 性能优化
// ❌ 性能问题:在每次渲染时创建新函数
const BadHook = () => {
const [state, setState] = useState(0);
const handleClick = () => setState(state + 1); // 每次渲染都创建新函数
return { state, handleClick };
};
// ✅ 性能优化:使用useCallback
const GoodHook = () => {
const [state, setState] = useState(0);
const handleClick = useCallback(() => {
setState(prev => prev + 1); // 使用函数式更新
}, []); // 空依赖数组,函数引用稳定
return { state, handleClick };
};
2. 错误处理
// 带错误处理的Hook
const useSafeAsync = () => {
const [state, setState] = useState({
data: null,
loading: false,
error: null
});
const mountedRef = useRef(true);
useEffect(() => {
return () => {
mountedRef.current = false;
};
}, []);
const execute = useCallback(async (asyncFn) => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const data = await asyncFn();
if (mountedRef.current) {
setState({ data, loading: false, error: null });
}
return data;
} catch (error) {
if (mountedRef.current) {
setState(prev => ({
...prev,
loading: false,
error: error.message
}));
}
throw error;
}
}, []);
return { ...state, execute };
};
3. TypeScript类型定义
// 泛型自定义Hook
interface UseApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
interface UseApiReturn<T> extends UseApiState<T> {
execute: () => Promise<T>;
reset: () => void;
}
const useApi = <T = any>(url: string): UseApiReturn<T> => {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: false,
error: null
});
const execute = useCallback(async (): Promise<T> => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const response = await fetch(url);
const data: T = await response.json();
setState({ data, loading: false, error: null });
return data;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setState(prev => ({
...prev,
loading: false,
error: errorMessage
}));
throw error;
}
}, [url]);
const reset = useCallback(() => {
setState({ data: null, loading: false, error: null });
}, []);
return { ...state, execute, reset };
};
📊 自定义Hooks使用场景总结
| 场景类型 | 推荐Hook | 解决的问题 |
|---|---|---|
| 表单处理 | useForm | 表单状态管理、验证、提交 |
| 数据获取 | useApi, useLocalStorage | API请求、本地存储 |
| 用户交互 | useToggle, useDebounce | 状态切换、防抖处理 |
| 性能优化 | useInfiniteScroll, useInterval | 无限滚动、定时器管理 |
| 副作用管理 | useSafeAsync, useEventListener | 异步操作、事件监听 |
🎯 自定义Hooks开发指南
- 命名规范:始终以
use开头,使用驼峰命名 - 单一职责:每个Hook只负责一个特定功能
- 性能考虑:适当使用useCallback和useMemo
- 错误处理:提供完善的错误处理机制
- 类型安全:使用TypeScript提供类型定义
- 文档完善:编写清晰的使用文档和示例
- 测试覆盖:编写单元测试确保Hook的可靠性
自定义Hooks是React现代开发的精髓,它让组件逻辑复用变得前所未有的简单和优雅。掌握自定义Hooks是成为React高级开发者的必经之路。
8.4 Context API高级用法
🎯 Context API深度理解
Context API是React提供的跨组件层级共享数据的解决方案,它避免了props drilling问题。现代Context API(React 16.3+)提供了更强大和灵活的功能。
// Context的基本使用
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}
🏗️ Context架构设计
1. 多层Context结构
// 定义多个Context
const AuthContext = React.createContext(null);
const ThemeContext = React.createContext('light');
const LanguageContext = React.createContext('zh');
// 组合Provider
const AppProviders = ({ children }) => {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [language, setLanguage] = useState('zh');
return (
<AuthContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<LanguageContext.Provider value={{ language, setLanguage }}>
{children}
</LanguageContext.Provider>
</ThemeContext.Provider>
</AuthContext.Provider>
);
};
// 自定义Hook简化Context使用
const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within LanguageProvider');
}
return context;
};
// 使用示例
const Dashboard = () => {
const { user } = useAuth();
const { theme, setTheme } = useTheme();
const { language } = useLanguage();
return (
<div className={`dashboard theme-${theme}`}>
<h1>{language === 'zh' ? '欢迎' : 'Welcome'} {user?.name}</h1>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</div>
);
};
2. Context分片优化
// 避免Context过度耦合
const UserContext = React.createContext();
const UserActionsContext = React.createContext();
const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const login = async (credentials) => {
setLoading(true);
setError(null);
try {
const userData = await authService.login(credentials);
setUser(userData);
return userData;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
};
const logout = () => {
setUser(null);
authService.logout();
};
const updateProfile = async (updates) => {
try {
const updatedUser = await authService.updateProfile(user.id, updates);
setUser(updatedUser);
return updatedUser;
} catch (err) {
setError(err.message);
throw err;
}
};
return (
<UserContext.Provider value={{ user, loading, error }}>
<UserActionsContext.Provider value={{ login, logout, updateProfile }}>
{children}
</UserActionsContext.Provider>
</UserContext.Provider>
);
};
// 自定义Hooks
const useUserState = () => {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUserState must be used within UserProvider');
}
return context;
};
const useUserActions = () => {
const context = useContext(UserActionsContext);
if (!context) {
throw new Error('useUserActions must be used within UserProvider');
}
return context;
};
// 组件只订阅需要的Context
const UserProfile = () => {
const { user } = useUserState();
const { updateProfile } = useUserActions();
const handleUpdate = (updates) => {
updateProfile(updates).catch(console.error);
};
return (
<div>
<h2>{user?.name}</h2>
<button onClick={() => handleUpdate({ name: 'New Name' })}>
更新资料
</button>
</div>
);
};
⚡ Context性能优化策略
1. 选择性更新
// 使用useMemo优化Context值
const OptimizedProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
// 只在state变化时创建新的context值
const contextValue = useMemo(() => ({
state,
dispatch
}), [state]);
return (
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
);
};
// 分离频繁变化的值
const OptimizedThemeContext = React.createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const [mode, setMode] = useState('normal');
// 静态值很少变化,使用useMemo
const staticValues = useMemo(() => ({
colors: {
light: { primary: '#007bff', secondary: '#6c757d' },
dark: { primary: '#0d6efd', secondary: '#495057' }
},
fonts: {
primary: 'Arial, sans-serif',
secondary: 'Georgia, serif'
}
}), []); // 空依赖,永远不变
// 动态值
const dynamicValues = {
theme,
mode,
setTheme,
setMode
};
return (
<OptimizedThemeContext.Provider value={{ ...staticValues, ...dynamicValues }}>
{children}
</OptimizedThemeContext.Provider>
);
};
2. Context消费者优化
// 使用React.memo优化消费者组件
const ExpensiveComponent = React.memo(({ data }) => {
console.log('ExpensiveComponent渲染');
return (
<div>
{data.items.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});
// 使用自定义Hook优化Context访问
const useOptimizedContext = (selector) => {
const context = useContext(MyContext);
// 使用selector减少不必要的重渲染
return useMemo(() => selector(context), [context, selector]);
};
// 使用示例
const SmartComponent = () => {
// 只订阅需要的数据
const items = useOptimizedContext(ctx => ctx.items);
const loading = useOptimizedContext(ctx => ctx.loading);
if (loading) return <div>加载中...</div>;
return <ExpensiveComponent data={{ items }} />;
};
3. Context延迟加载
// 延迟加载Context提供者
const LazyContextProvider = ({ children, fallback = null }) => {
const [isReady, setIsReady] = useState(false);
const [contextData, setContextData] = useState(null);
useEffect(() => {
const initializeContext = async () => {
try {
// 异步初始化Context数据
const data = await loadInitialData();
setContextData(data);
setIsReady(true);
} catch (error) {
console.error('Context初始化失败:', error);
}
};
initializeContext();
}, []);
if (!isReady) {
return fallback || <div>初始化中...</div>;
}
return (
<MyContext.Provider value={contextData}>
{children}
</MyContext.Provider>
);
};
🔧 高级Context模式
1. Context组合模式
// Context组合工厂
const createContextCombiner = (contexts) => {
const CombinedProvider = ({ children }) => {
return contexts.reduceRight((acc, { Context, value }) => (
<Context.Provider value={value}>
{acc}
</Context.Provider>
), children);
};
return CombinedProvider;
};
// 使用示例
const contexts = [
{ Context: ThemeContext, value: themeValue },
{ Context: AuthContext, value: authValue },
{ Context: LanguageContext, value: languageValue }
];
const CombinedProvider = createContextCombiner(contexts);
const App = () => (
<CombinedProvider>
<MainApp />
</CombinedProvider>
);
2. Context中间件模式
// Context中间件系统
const createContextWithMiddleware = (defaultValue, middleware = []) => {
const Context = React.createContext(defaultValue);
const MiddlewareProvider = ({ children, value }) => {
let processedValue = value;
// 应用中间件
middleware.forEach(middlewareFn => {
processedValue = middlewareFn(processedValue);
});
return (
<Context.Provider value={processedValue}>
{children}
</Context.Provider>
);
};
return { Context, Provider: MiddlewareProvider };
};
// 日志中间件
const loggingMiddleware = (contextValue) => {
return new Proxy(contextValue, {
get(target, prop) {
const value = target[prop];
console.log(`Context访问: ${prop} =`, value);
return value;
},
set(target, prop, value) {
console.log(`Context更新: ${prop} =`, value);
target[prop] = value;
return true;
}
});
};
// 验证中间件
const validationMiddleware = (schema) => (contextValue) => {
return new Proxy(contextValue, {
set(target, prop, value) {
if (schema[prop] && !schema[prop](value)) {
console.error(`验证失败: ${prop}`);
return false;
}
target[prop] = value;
return true;
}
});
};
// 使用示例
const { Context: UserContext, Provider: UserProvider } = createContextWithMiddleware(
null,
[
loggingMiddleware,
validationMiddleware({
name: (value) => typeof value === 'string' && value.length > 0,
age: (value) => typeof value === 'number' && value >= 0
})
]
);
3. 动态Context创建
// 动态Context工厂
const createDynamicContext = (name) => {
const Context = React.createContext(null);
Context.displayName = `${name}Context`;
const Provider = ({ children, ...props }) => (
<Context.Provider value={props}>
{children}
</Context.Provider>
);
return { Context, Provider };
};
// 使用示例
const createNamespaceContext = (namespace) => {
const contexts = new Map();
return (contextName, defaultValue = null) => {
const fullName = `${namespace}.${contextName}`;
if (!contexts.has(fullName)) {
contexts.set(fullName, createDynamicContext(fullName));
}
return contexts.get(fullName);
};
};
const createContext = createNamespaceContext('App');
// 创建多个Context
const { Context: TodoContext } = createContext('todos', []);
const { Context: UserContext } = createContext('users', null);
const { Context: SettingsContext } = createContext('settings', {});
🌐 Context与状态管理集成
1. Context + Reducer模式
// 定义Action类型
const ActionTypes = {
ADD_TODO: 'ADD_TODO',
TOGGLE_TODO: 'TOGGLE_TODO',
DELETE_TODO: 'DELETE_TODO',
SET_FILTER: 'SET_FILTER'
};
// Reducer
const todoReducer = (state, action) => {
switch (action.type) {
case ActionTypes.ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload]
};
case ActionTypes.TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case ActionTypes.DELETE_TODO:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case ActionTypes.SET_FILTER:
return {
...state,
filter: action.payload
};
default:
return state;
}
};
// Context Provider
const TodoProvider = ({ children }) => {
const [state, dispatch] = useReducer(todoReducer, {
todos: [],
filter: 'all'
});
// Action creators
const actions = useMemo(() => ({
addTodo: (text) => {
dispatch({
type: ActionTypes.ADD_TODO,
payload: {
id: Date.now(),
text,
completed: false
}
});
},
toggleTodo: (id) => {
dispatch({
type: ActionTypes.TOGGLE_TODO,
payload: id
});
},
deleteTodo: (id) => {
dispatch({
type: ActionTypes.DELETE_TODO,
payload: id
});
},
setFilter: (filter) => {
dispatch({
type: ActionTypes.SET_FILTER,
payload: filter
});
}
}), []);
const contextValue = useMemo(() => ({
...state,
...actions
}), [state, actions]);
return (
<TodoContext.Provider value={contextValue}>
{children}
</TodoContext.Provider>
);
};
// 自定义Hook
const useTodos = () => {
const context = useContext(TodoContext);
if (!context) {
throw new Error('useTodos must be used within TodoProvider');
}
return context;
};
// 使用示例
const TodoList = () => {
const { todos, filter, addTodo, toggleTodo, deleteTodo, setFilter } = useTodos();
const filteredTodos = useMemo(() => {
switch (filter) {
case 'completed':
return todos.filter(todo => todo.completed);
case 'active':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}, [todos, filter]);
return (
<div>
<TodoInput onAdd={addTodo} />
<FilterButtons
currentFilter={filter}
onFilterChange={setFilter}
/>
<TodoList
todos={filteredTodos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
</div>
);
};
2. Context与外部状态管理库集成
// Redux集成
const ReduxContextProvider = ({ store, children }) => {
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
});
return unsubscribe;
}, [store]);
const dispatch = store.dispatch;
const contextValue = useMemo(() => ({
state,
dispatch
}), [state]);
return (
<ReduxContext.Provider value={contextValue}>
{children}
</ReduxContext.Provider>
);
};
// Zustand集成
const ZustandContextProvider = ({ useStore, children }) => {
const store = useStore();
return (
<ZustandContext.Provider value={store}>
{children}
</ZustandContext.Provider>
);
};
// 使用示例
const ReduxComponent = () => {
const { state, dispatch } = useContext(ReduxContext);
return (
<div>
<p>计数: {state.counter}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
增加
</button>
</div>
);
};
🎯 Context最佳实践
1. Context设计原则
// ✅ 好的实践:单一职责Context
const ThemeContext = React.createContext({
theme: 'light',
colors: {},
setTheme: () => {}
});
const UserContext = React.createContext({
user: null,
login: async () => {},
logout: () => {}
});
// ❌ 避免的实践:过度聚合Context
const AppContext = React.createContext({
theme: 'light',
user: null,
notifications: [],
settings: {},
// 太多不相关的状态...
});
2. Context错误边界
// Context错误边界
class ContextErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Context Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Context出错了</div>;
}
return this.props.children;
}
}
// 使用错误边界包裹Provider
const SafeProvider = ({ children }) => (
<ContextErrorBoundary>
<MyContext.Provider value={contextValue}>
{children}
</MyContext.Provider>
</ContextErrorBoundary>
);
3. Context测试策略
// 测试工具
const createContextWrapper = (Context, value) => ({ children }) => (
<Context.Provider value={value}>
{children}
</Context.Provider>
);
// 测试示例
const { render, screen } = testingLibrary;
describe('MyComponent', () => {
const mockContextValue = {
user: { name: 'Test User' },
theme: 'light'
};
const Wrapper = createContextWrapper(MyContext, mockContextValue);
it('应该正确显示用户信息', () => {
render(<MyComponent />, { wrapper: Wrapper });
expect(screen.getByText('Test User')).toBeInTheDocument();
});
});
📊 Context API优缺点总结
✅ 优点
- 避免props drilling:跨组件层级传递数据
- 灵活性强:可以传递任何类型的数据
- 组合性好:多个Context可以组合使用
- TypeScript支持:良好的类型推导
- 性能可控:通过选择性更新优化性能
❌ 缺点
- 性能问题:Context值变化会导致所有消费者重渲染
- 复杂性增加:过度使用会导致Context Hell
- 调试困难:Context变化不易追踪
- 测试复杂:需要模拟Context提供者
- 耦合度高:组件与Context产生依赖
🎯 Context使用建议
- 适度使用:只在真正需要跨层级传递数据时使用Context
- 分片设计:将大的Context拆分成多个小的、职责单一的Context
- 性能优化:使用useMemo和useCallback避免不必要的重渲染
- 类型安全:使用TypeScript提供完整的类型定义
- 错误处理:为Context添加错误边界和验证机制
- 文档完善:为每个Context提供清晰的使用文档和示例
Context API是React生态中重要的状态共享方案,合理使用能显著提升应用的可维护性和开发效率。在复杂应用中,Context往往与状态管理库结合使用,共同构建强大的状态管理体系。
8.5 错误边界和错误处理
🎯 错误边界概念与原理
错误边界是React组件,可以捕获其子组件树中任何位置的JavaScript错误,记录这些错误,并显示回退UI而不是崩溃的白屏。错误边界无法捕获以下错误:
- 事件处理器中的错误
- 异步代码中的错误(如setTimeout、requestAnimationFrame)
- 服务端渲染中的错误
- 错误边界自身抛出的错误
// 基础错误边界
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// 更新state,下一次渲染将显示回退UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 记录错误信息
console.error('错误边界捕获到错误:', error, errorInfo);
this.setState({
error: error,
errorInfo: errorInfo
});
// 发送错误报告到监控服务
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div style={{ padding: '20px', border: '1px solid red' }}>
<h2>出错了!</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
// 使用示例
const App = () => (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
🏗️ 高级错误边界实现
1. 功能完整的错误边界
class AdvancedErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorId: null,
retryCount: 0,
timestamp: null
};
this.maxRetries = props.maxRetries || 3;
this.errorHistory = [];
}
static getDerivedStateFromError(error) {
return {
hasError: true,
errorId: generateErrorId(),
timestamp: new Date().toISOString()
};
}
componentDidCatch(error, errorInfo) {
const errorData = {
error,
errorInfo,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
userId: this.getCurrentUserId()
};
this.errorHistory.push(errorData);
// 发送错误报告
this.reportError(errorData);
this.setState({
error,
errorInfo
});
}
getCurrentUserId = () => {
// 从Context或其他地方获取用户ID
return this.props.userId || 'anonymous';
};
reportError = async (errorData) => {
try {
// 发送到错误监控服务
await fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errorData)
});
} catch (reportError) {
console.error('发送错误报告失败:', reportError);
}
};
handleRetry = () => {
const { retryCount } = this.state;
if (retryCount < this.maxRetries) {
this.setState(prevState => ({
hasError: false,
error: null,
errorInfo: null,
retryCount: prevState.retryCount + 1
}));
}
};
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
retryCount: 0,
timestamp: null
});
};
render() {
const { hasError, error, errorInfo, errorId, retryCount } = this.state;
const {
fallback,
showDetails = false,
showRetry = true,
maxRetries = 3
} = this.props;
if (hasError) {
if (fallback) {
return typeof fallback === 'function'
? fallback({ error, errorInfo, errorId, retryCount, onRetry: this.handleRetry })
: fallback;
}
return (
<div className="error-boundary">
<div className="error-content">
<h2>应用遇到错误</h2>
<p>错误ID: {errorId}</p>
<p>时间: {new Date(this.state.timestamp).toLocaleString()}</p>
{showRetry && retryCount < maxRetries && (
<button onClick={this.handleRetry}>
重试 ({retryCount}/{maxRetries})
</button>
)}
{showRetry && retryCount >= maxRetries && (
<button onClick={this.handleReset}>
重置页面
</button>
)}
{showDetails && (
<details className="error-details">
<summary>错误详情</summary>
<pre style={{ whiteSpace: 'pre-wrap', textAlign: 'left' }}>
{error && error.toString()}
<br />
{errorInfo && errorInfo.componentStack}
</pre>
</details>
)}
</div>
</div>
);
}
return this.props.children;
}
}
// 生成唯一错误ID
const generateErrorId = () => {
return `ERR_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
// 使用示例
const App = () => (
<AdvancedErrorBoundary
fallback={({ error, onRetry }) => (
<div className="custom-error-fallback">
<h3>自定义错误界面</h3>
<p>错误信息: {error.message}</p>
<button onClick={onRetry}>重试</button>
</div>
)}
showDetails={process.env.NODE_ENV === 'development'}
maxRetries={3}
>
<MainApplication />
</AdvancedErrorBoundary>
);
2. Hook版本错误边界(使用第三方库)
// 使用react-error-boundary库
import { ErrorBoundary } from 'react-error-boundary';
const ErrorFallback = ({ error, resetErrorBoundary }) => (
<div role="alert">
<p>出错了:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
const MyErrorHandler = (error, errorInfo) => {
console.error('自定义错误处理:', error, errorInfo);
// 发送到错误监控服务
};
const App = () => (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={MyErrorHandler}
onReset={() => {
// 重置应用状态
console.log('应用已重置');
}}
>
<MainApp />
</ErrorBoundary>
);
⚡ 错误分类和处理策略
1. 网络错误处理
// 网络错误边界
const NetworkErrorBoundary = ({ children }) => {
const [networkError, setNetworkError] = useState(null);
const handleNetworkError = useCallback((error) => {
if (error.name === 'NetworkError' || error.message.includes('fetch')) {
setNetworkError(error);
}
}, []);
const clearNetworkError = useCallback(() => {
setNetworkError(null);
}, []);
// 监听网络状态
useEffect(() => {
const handleOnline = () => {
if (networkError) {
clearNetworkError();
}
};
const handleOffline = () => {
setNetworkError(new Error('网络连接已断开'));
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, [networkError, clearNetworkError]);
if (networkError) {
return (
<div className="network-error">
<h2>网络错误</h2>
<p>{networkError.message}</p>
<button onClick={clearNetworkError}>重试</button>
</div>
);
}
return (
<ErrorContext.Provider value={{ handleNetworkError }}>
{children}
</ErrorContext.Provider>
);
};
2. 异步错误处理
// 异步错误处理Hook
const useAsyncError = () => {
const [, setError] = useState();
const resetError = useCallback(() => {
setError(undefined);
}, []);
const throwAsyncError = useCallback((error) => {
setError(() => {
throw error;
});
}, []);
return { throwAsyncError, resetError };
};
// 使用示例
const AsyncComponent = () => {
const { throwAsyncError } = useAsyncError();
const fetchData = async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
throwAsyncError(error);
}
};
return (
<div>
<button onClick={fetchData}>获取数据</button>
</div>
);
};
// 包裹在错误边界中
const App = () => (
<ErrorBoundary>
<AsyncComponent />
</ErrorBoundary>
);
🛠️ 错误监控和报告系统
1. 错误收集器
// 错误收集器
class ErrorCollector {
constructor() {
this.errors = [];
this.maxErrors = 100;
this.subscribers = [];
}
addError(errorData) {
const enhancedError = {
...errorData,
id: generateErrorId(),
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
sessionId: this.getSessionId(),
userId: this.getUserId()
};
this.errors.unshift(enhancedError);
if (this.errors.length > this.maxErrors) {
this.errors = this.errors.slice(0, this.maxErrors);
}
// 通知订阅者
this.notifySubscribers(enhancedError);
// 发送到服务器
this.reportToServer(enhancedError);
}
getSessionId() {
if (!sessionStorage.getItem('sessionId')) {
sessionStorage.setItem('sessionId', generateSessionId());
}
return sessionStorage.getItem('sessionId');
}
getUserId() {
// 从token或Context获取用户ID
return localStorage.getItem('userId') || 'anonymous';
}
subscribe(callback) {
this.subscribers.push(callback);
return () => {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
};
}
notifySubscribers(errorData) {
this.subscribers.forEach(callback => {
try {
callback(errorData);
} catch (e) {
console.error('订阅者回调出错:', e);
}
});
}
async reportToServer(errorData) {
try {
await fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errorData)
});
} catch (e) {
console.error('发送错误报告失败:', e);
// 本地存储,稍后重试
this.storeForRetry(errorData);
}
}
storeForRetry(errorData) {
const retryQueue = JSON.parse(localStorage.getItem('errorRetryQueue') || '[]');
retryQueue.push(errorData);
localStorage.setItem('errorRetryQueue', JSON.stringify(retryQueue));
}
getErrors(filter = {}) {
let filtered = this.errors;
if (filter.level) {
filtered = filtered.filter(error => error.level === filter.level);
}
if (filter.since) {
filtered = filtered.filter(error =>
new Date(error.timestamp) >= new Date(filter.since)
);
}
return filtered;
}
clearErrors() {
this.errors = [];
}
}
const errorCollector = new ErrorCollector();
2. 错误报告组件
// 错误报告组件
const ErrorReporter = ({ visible, onClose }) => {
const [errors, setErrors] = useState([]);
const [filter, setFilter] = useState({});
useEffect(() => {
const unsubscribe = errorCollector.subscribe((errorData) => {
setErrors(prev => [errorData, ...prev]);
});
const loadErrors = () => {
setErrors(errorCollector.getErrors(filter));
};
loadErrors();
return unsubscribe;
}, [filter]);
const handleExport = () => {
const dataStr = JSON.stringify(errors, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `errors-${Date.now()}.json`;
link.click();
URL.revokeObjectURL(url);
};
if (!visible) return null;
return (
<div className="error-reporter">
<div className="error-reporter-header">
<h3>错误报告</h3>
<button onClick={onClose}>关闭</button>
</div>
<div className="error-reporter-controls">
<button onClick={handleExport}>导出错误日志</button>
<button onClick={() => errorCollector.clearErrors()}>
清除所有错误
</button>
</div>
<div className="error-list">
{errors.map(error => (
<ErrorItem key={error.id} error={error} />
))}
</div>
</div>
);
};
const ErrorItem = ({ error }) => (
<div className="error-item">
<div className="error-header">
<span className="error-id">{error.id}</span>
<span className="error-time">
{new Date(error.timestamp).toLocaleString()}
</span>
</div>
<div className="error-message">
{error.error?.message || error.message}
</div>
<details className="error-details">
<summary>详细信息</summary>
<pre>{JSON.stringify(error, null, 2)}</pre>
</details>
</div>
);
🎯 错误恢复策略
1. 自动恢复机制
// 自动恢复Hook
const useErrorRecovery = (maxRetries = 3, delay = 1000) => {
const [retryCount, setRetryCount] = useState(0);
const [isRecovering, setIsRecovering] = useState(false);
const recover = useCallback(async (recoveryFn) => {
if (retryCount >= maxRetries) {
console.error('达到最大重试次数,无法自动恢复');
return false;
}
setIsRecovering(true);
try {
await new Promise(resolve => setTimeout(resolve, delay * (retryCount + 1)));
const result = await recoveryFn();
setRetryCount(0); // 成功后重置计数
return true;
} catch (error) {
setRetryCount(prev => prev + 1);
console.error(`恢复尝试 ${retryCount + 1} 失败:`, error);
return false;
} finally {
setIsRecovering(false);
}
}, [retryCount, maxRetries, delay]);
const reset = useCallback(() => {
setRetryCount(0);
setIsRecovering(false);
}, []);
return {
recover,
reset,
isRecovering,
retryCount,
canRetry: retryCount < maxRetries
};
};
// 使用示例
const DataComponent = () => {
const [data, setData] = useState(null);
const { recover, isRecovering, canRetry } = useErrorRecovery();
const fetchData = async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('数据获取失败');
}
const result = await response.json();
setData(result);
};
const handleFetch = async () => {
const success = await recover(fetchData);
if (!success && !canRetry) {
alert('无法恢复,请刷新页面');
}
};
return (
<div>
<button
onClick={handleFetch}
disabled={isRecovering}
>
{isRecovering ? '恢复中...' : '获取数据'}
</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
};
📊 错误处理最佳实践
1. 分层错误处理
// 全局错误边界
const GlobalErrorBoundary = ({ children }) => (
<ErrorBoundary
fallback={<GlobalErrorFallback />}
onError={(error, errorInfo) => {
errorCollector.addError({
type: 'global',
error,
errorInfo,
level: 'critical'
});
}}
>
{children}
</ErrorBoundary>
);
// 页面级错误边界
const PageErrorBoundary = ({ children, pageName }) => (
<ErrorBoundary
fallback={<PageErrorFallback pageName={pageName} />}
onError={(error, errorInfo) => {
errorCollector.addError({
type: 'page',
pageName,
error,
errorInfo,
level: 'error'
});
}}
>
{children}
</ErrorBoundary>
);
// 组件级错误处理
const ComponentWithErrorHandling = ({ componentName, ...props }) => {
const { throwAsyncError } = useAsyncError();
const handleError = useCallback((error) => {
errorCollector.addError({
type: 'component',
componentName,
error,
level: 'warning'
});
throwAsyncError(error);
}, [componentName, throwAsyncError]);
return (
<ErrorBoundary
fallback={<ComponentErrorFallback componentName={componentName} />}
onError={handleError}
>
<ActualComponent {...props} />
</ErrorBoundary>
);
};
2. 错误级别分类
// 错误级别定义
const ErrorLevels = {
FATAL: 'fatal', // 致命错误,需要立即处理
ERROR: 'error', // 普通错误,影响功能
WARNING: 'warning', // 警告,不影响主要功能
INFO: 'info' // 信息,用于调试
};
// 错误处理策略
const getErrorStrategy = (level) => {
switch (level) {
case ErrorLevels.FATAL:
return {
showFallback: true,
reportImmediately: true,
allowRetry: false,
notifyUser: true
};
case ErrorLevels.ERROR:
return {
showFallback: true,
reportImmediately: true,
allowRetry: true,
notifyUser: true
};
case ErrorLevels.WARNING:
return {
showFallback: false,
reportImmediately: false,
allowRetry: true,
notifyUser: false
};
case ErrorLevels.INFO:
return {
showFallback: false,
reportImmediately: false,
allowRetry: false,
notifyUser: false
};
default:
return {
showFallback: false,
reportImmediately: false,
allowRetry: false,
notifyUser: false
};
}
};
🎯 错误边界使用建议
- 合理分层:在不同层级设置错误边界,避免全局覆盖
- 友好提示:提供用户友好的错误信息,避免技术术语
- 恢复机制:提供重试和恢复功能,提升用户体验
- 监控告警:建立完善的错误监控和告警机制
- 日志记录:记录详细的错误信息,便于问题排查
- 性能考虑:错误处理不应影响应用性能
- 测试覆盖:为错误处理逻辑编写充分的测试
错误边界和完善的错误处理机制是构建健壮React应用的重要组成部分,能够显著提升应用的稳定性和用户体验。
8.6 性能优化技巧
🎯 React性能优化概述
React性能优化的核心是减少不必要的渲染和DOM操作。现代React提供了多种工具和API来帮助开发者优化应用性能,包括React.memo、useMemo、useCallback、代码分割、虚拟化等。
⚡ 组件级优化
1. React.memo使用
// 基础React.memo
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('ExpensiveComponent渲染');
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});
// 自定义比较函数
const OptimizedComponent = React.memo(({ data, config }) => {
return <div>复杂渲染逻辑</div>;
}, (prevProps, nextProps) => {
// 自定义比较逻辑
return (
prevProps.data.length === nextProps.data.length &&
prevProps.data.every((item, index) =>
item.id === nextProps.data[index].id
) &&
prevProps.config.sortBy === nextProps.config.sortBy
);
});
2. useMemo和useCallback优化
// useMemo优化计算密集型操作
const DataProcessor = ({ items, filter }) => {
const processedData = useMemo(() => {
console.log('数据处理中...');
return items
.filter(item => item.category === filter)
.sort((a, b) => a.value - b.value)
.map(item => ({
...item,
processed: true
}));
}, [items, filter]);
return <DataList data={processedData} />;
};
// useCallback优化函数引用
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleItemClick = useCallback((item) => {
console.log('点击项目:', item);
// 复杂处理逻辑
}, []); // 空依赖,函数引用稳定
const handleIncrement = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>计数: {count}</p>
<button onClick={handleIncrement}>增加</button>
<ChildComponent onItemClick={handleItemClick} />
</div>
);
};
🏗️ 渲染优化策略
1. 条件渲染优化
// 优化前:每次渲染都创建新组件
const BadConditionalRender = ({ showDetails, data }) => (
<div>
{showDetails && <DetailedComponent data={data} />}
</div>
);
// 优化后:使用布尔值控制
const GoodConditionalRender = ({ showDetails, data }) => {
const MemoizedDetailedComponent = useMemo(() => (
<DetailedComponent data={data} />
), [data]);
return (
<div>
{showDetails ? MemoizedDetailedComponent : null}
</div>
);
};
2. 列表渲染优化
// 虚拟列表实现
const VirtualList = ({ items, itemHeight = 50, containerHeight = 300 }) => {
const [scrollTop, setScrollTop] = useState(0);
const visibleStart = Math.floor(scrollTop / itemHeight);
const visibleEnd = Math.min(
visibleStart + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
const visibleItems = items.slice(visibleStart, visibleEnd);
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
{visibleItems.map((item, index) => (
<div
key={item.id}
style={{
height: itemHeight,
position: 'absolute',
top: (visibleStart + index) * itemHeight,
width: '100%'
}}
>
<ListItem data={item} />
</div>
))}
</div>
</div>
);
};
🔄 状态管理优化
1. 状态分离和合并
// 分离不相关的状态
const SeparatedStateComponent = () => {
const [userData, setUserData] = useState(null);
const [uiState, setUiState] = useState({ loading: false, error: null });
const [filters, setFilters] = useState({ category: '', sortBy: 'name' });
// 状态更新互不影响
const handleUserLoad = async () => {
setUiState(prev => ({ ...prev, loading: true, error: null }));
try {
const user = await fetchUser();
setUserData(user);
} catch (error) {
setUiState(prev => ({ ...prev, error: error.message }));
} finally {
setUiState(prev => ({ ...prev, loading: false }));
}
};
return <Component userData={userData} uiState={uiState} filters={filters} />;
};
2. 使用useReducer优化复杂状态
const complexStateReducer = (state, action) => {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_DATA':
return { ...state, data: action.payload, loading: false };
case 'SET_ERROR':
return { ...state, error: action.payload, loading: false };
case 'UPDATE_FILTER':
return { ...state, filters: { ...state.filters, ...action.payload } };
default:
return state;
}
};
const ComplexStateComponent = () => {
const [state, dispatch] = useReducer(complexStateReducer, {
data: [],
loading: false,
error: null,
filters: { category: '', sortBy: 'date' }
});
const handleFilterChange = useCallback((newFilters) => {
dispatch({ type: 'UPDATE_FILTER', payload: newFilters });
}, []);
return (
<div>
<FilterComponent filters={state.filters} onChange={handleFilterChange} />
<DataComponent data={state.data} loading={state.loading} />
</div>
);
};
🚀 代码分割和懒加载
1. 路由级别代码分割
// React.lazy实现
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
const Contact = React.lazy(() => import('./pages/Contact'));
const App = () => (
<Router>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</Router>
);
// 高级懒加载实现
const createLazyComponent = (importFn, fallback = null) => {
const LazyComponent = React.lazy(importFn);
return (props) => (
<Suspense fallback={fallback || <DefaultFallback />}>
<LazyComponent {...props} />
</Suspense>
);
};
const LazyHome = createLazyComponent(
() => import('./pages/Home'),
<Spinner />
);
2. 组件级别代码分割
// 动态导入组件
const DynamicComponent = ({ componentName, ...props }) => {
const [Component, setComponent] = useState(null);
useEffect(() => {
const loadComponent = async () => {
try {
const { [componentName]: LoadedComponent } = await import(
`./components/${componentName}`
);
setComponent(() => LoadedComponent);
} catch (error) {
console.error(`加载组件 ${componentName} 失败:`, error);
}
};
loadComponent();
}, [componentName]);
if (!Component) return <div>加载中...</div>;
return <Component {...props} />;
};
📊 性能监控和分析
1. 性能监控Hook
const usePerformanceMonitor = (componentName) => {
const renderStartTime = useRef();
const renderCount = useRef(0);
useEffect(() => {
renderStartTime.current = performance.now();
});
useEffect(() => {
const renderEndTime = performance.now();
const renderTime = renderEndTime - renderStartTime.current;
renderCount.current += 1;
if (renderTime > 16) { // 超过一帧时间
console.warn(`${componentName} 渲染时间过长: ${renderTime.toFixed(2)}ms`);
}
// 发送性能数据
if (renderCount.current % 100 === 0) {
// 每渲染100次上报一次数据
reportPerformance(componentName, renderTime, renderCount.current);
}
});
};
// 使用示例
const MonitoredComponent = ({ data }) => {
usePerformanceMonitor('MonitoredComponent');
return <div>组件内容</div>;
};
2. 渲染性能分析
// React DevTools Profiler
const ProfilerWrapper = ({ id, children, onRender }) => (
<React.Profiler id={id} onRender={onRender}>
{children}
</React.Profiler>
);
const App = () => {
const handleRender = (id, phase, actualDuration) => {
console.log(`组件 ${id} ${phase} 阶段渲染耗时: ${actualDuration}ms`);
};
return (
<ProfilerWrapper id="App" onRender={handleRender}>
<MainApp />
</ProfilerWrapper>
);
};
🎯 性能优化最佳实践
- 避免过度优化:只在必要时进行性能优化
- 测量先行:先测量性能瓶颈,再针对性优化
- 合理使用memo:不要滥用React.memo,避免内存开销
- 状态设计:设计合理的状态结构,避免不必要的状态更新
- 虚拟化:对长列表使用虚拟化技术
- 代码分割:合理分割代码,减少初始加载时间
- 监控告警:建立性能监控体系,及时发现性能问题
性能优化是一个持续的过程,需要根据实际应用的特点和性能需求来制定相应的优化策略。
8.7 组件设计模式
🎯 受控组件与非受控组件
1. 受控组件模式
// 受控输入组件
const ControlledInput = ({ value, onChange, ...props }) => {
const handleChange = (e) => {
onChange(e.target.value);
};
return <input value={value || ''} onChange={handleChange} {...props} />;
};
// 使用受控组件
const ControlledForm = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
});
const handleChange = (field) => (value) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<ControlledInput
value={formData.name}
onChange={handleChange('name')}
placeholder="姓名"
/>
<ControlledInput
value={formData.email}
onChange={handleChange('email')}
type="email"
placeholder="邮箱"
/>
<ControlledInput
value={formData.age}
onChange={handleChange('age')}
type="number"
placeholder="年龄"
/>
<button type="submit">提交</button>
</form>
);
};
2. 非受控组件模式
// 非受控组件Hook
const useUncontrolledInput = (defaultValue = '') => {
const inputRef = useRef();
const getValue = useCallback(() => {
return inputRef.current?.value || defaultValue;
}, [defaultValue]);
const setValue = useCallback((value) => {
if (inputRef.current) {
inputRef.current.value = value;
}
}, []);
const reset = useCallback(() => {
if (inputRef.current) {
inputRef.current.value = defaultValue;
}
}, [defaultValue]);
return {
ref: inputRef,
getValue,
setValue,
reset
};
};
// 使用非受控组件
const UncontrolledForm = () => {
const nameInput = useUncontrolledInput();
const emailInput = useUncontrolledInput();
const handleSubmit = (e) => {
e.preventDefault();
const formData = {
name: nameInput.getValue(),
email: emailInput.getValue()
};
console.log('表单数据:', formData);
};
const handleReset = () => {
nameInput.reset();
emailInput.reset();
};
return (
<form onSubmit={handleSubmit}>
<input {...nameInput} placeholder="姓名" />
<input {...emailInput} type="email" placeholder="邮箱" />
<button type="submit">提交</button>
<button type="button" onClick={handleReset}>重置</button>
</form>
);
};
🏗️ 复合组件模式
1. 基础复合组件
// 复合组件父组件
const Card = ({ children, className = '', ...props }) => {
return (
<div className={`card ${className}`} {...props}>
{children}
</div>
);
};
// 子组件
Card.Header = ({ children, className = '', ...props }) => (
<div className={`card-header ${className}`} {...props}>
{children}
</div>
);
Card.Body = ({ children, className = '', ...props }) => (
<div className={`card-body ${className}`} {...props}>
{children}
</div>
);
Card.Footer = ({ children, className = '', ...props }) => (
<div className={`card-footer ${className}`} {...props}>
{children}
</div>
);
// 使用复合组件
const App = () => (
<Card>
<Card.Header>
<h2>卡片标题</h2>
</Card.Header>
<Card.Body>
<p>卡片内容区域</p>
</Card.Body>
<Card.Footer>
<button>操作按钮</button>
</Card.Footer>
</Card>
);
2. Context增强复合组件
// 使用Context的复合组件
const TabsContext = React.createContext();
const Tabs = ({ children, defaultTab = 0, onTabChange }) => {
const [activeTab, setActiveTab] = useState(defaultTab);
const handleTabChange = (index) => {
setActiveTab(index);
if (onTabChange) {
onTabChange(index);
}
};
const contextValue = {
activeTab,
handleTabChange
};
return (
<TabsContext.Provider value={contextValue}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
};
Tabs.TabList = ({ children }) => {
return <div className="tab-list">{children}</div>;
};
Tabs.Tab = ({ index, children, disabled = false }) => {
const { activeTab, handleTabChange } = useContext(TabsContext);
const isActive = activeTab === index;
return (
<button
className={`tab ${isActive ? 'active' : ''}`}
onClick={() => !disabled && handleTabChange(index)}
disabled={disabled}
>
{children}
</button>
);
};
Tabs.TabPanels = ({ children }) => {
const { activeTab } = useContext(TabsContext);
return (
<div className="tab-panels">
{Children.toArray(children)[activeTab]}
</div>
);
};
Tabs.TabPanel = ({ children }) => {
return <div className="tab-panel">{children}</div>;
};
// 使用示例
const TabExample = () => (
<Tabs defaultTab={0}>
<Tabs.TabList>
<Tabs.Tab index={0}>标签1</Tabs.Tab>
<Tabs.Tab index={1}>标签2</Tabs.Tab>
<Tabs.Tab index={2}>标签3</Tabs.Tab>
</Tabs.TabList>
<Tabs.TabPanels>
<Tabs.TabPanel>内容1</Tabs.TabPanel>
<Tabs.TabPanel>内容2</Tabs.TabPanel>
<Tabs.TabPanel>内容3</Tabs.TabPanel>
</Tabs.TabPanels>
</Tabs>
);
🔄 提供者模式
1. 主题提供者
const ThemeContext = React.createContext();
const ThemeProvider = ({ theme, children }) => {
const [currentTheme, setCurrentTheme] = useState(theme || 'light');
const toggleTheme = () => {
setCurrentTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const themeValue = useMemo(() => ({
theme: currentTheme,
colors: themes[currentTheme],
toggleTheme
}), [currentTheme]);
return (
<ThemeContext.Provider value={themeValue}>
<div className={`theme-${currentTheme}`}>
{children}
</div>
</ThemeContext.Provider>
);
};
const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
// 使用示例
const ThemedButton = () => {
const { theme, colors, toggleTheme } = useTheme();
return (
<button
style={{
backgroundColor: colors.primary,
color: colors.text
}}
onClick={toggleTheme}
>
当前主题: {theme}
</button>
);
};
🔧 状态提升模式
1. 基础状态提升
// 将状态提升到最近的公共祖先组件
const TemperatureCalculator = () => {
const [temperature, setTemperature] = useState('');
const [scale, setScale] = useState('c');
const handleCelsiusChange = (value) => {
setTemperature(value);
setScale('c');
};
const handleFahrenheitChange = (value) => {
setTemperature(value);
setScale('f');
};
const toCelsius = (fahrenheit) => {
return (fahrenheit - 32) * 5 / 9;
};
const toFahrenheit = (celsius) => {
return (celsius * 9 / 5) + 32;
};
const tryConvert = (temperature, convert) => {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
};
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={handleFahrenheitChange}
/>
<BoilingVerdict
celsius={parseFloat(celsius)}
/>
</div>
);
};
📊 组件设计模式总结
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 受控组件 | 表单处理、数据同步 | 数据流清晰、易验证 | 代码量较多 |
| 非受控组件 | 简单表单、快速开发 | 代码简洁、性能好 | 数据同步困难 |
| 复合组件 | UI组件库、可配置组件 | API灵活、组合性强 | 学习成本高 |
| 提供者模式 | 主题、配置、全局状态 | 解耦、可测试 | 可能过度设计 |
| 状态提升 | 兄弟组件通信 | 单向数据流 | 层级过深问题 |
🎯 组件设计最佳实践
- 单一职责:每个组件只负责一个功能
- 组合优于继承:通过组合构建复杂组件
- Props设计:设计清晰、类型安全的Props接口
- 可测试性:组件应该易于单元测试
- 可访问性:考虑无障碍访问需求
- 文档完善:为组件提供详细的使用文档
- 性能考虑:避免不必要的重渲染
良好的组件设计模式能显著提升代码的可维护性、可复用性和团队协作效率。
8.8 TypeScript与React最佳实践
🎯 TypeScript在React中的应用
1. 组件类型定义
// 基础函数组件类型
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
size?: 'small' | 'medium' | 'large';
}
const Button: React.FC<ButtonProps> = ({
children,
onClick,
variant = 'primary',
disabled = false,
size = 'medium'
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant} btn-${size}`}
>
{children}
</button>
);
};
// 泛型组件
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
emptyMessage?: string;
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
if (items.length === 0) {
return <div>{emptyMessage || '暂无数据'}</div>;
}
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>
{renderItem(item, index)}
</li>
))}
</ul>
);
}
// 使用示例
interface User {
id: string;
name: string;
email: string;
}
const UserList: React.FC<{ users: User[] }> = ({ users }) => {
return (
<List
items={users}
keyExtractor={user => user.id}
renderItem={(user) => (
<div>
<span>{user.name}</span>
<span>{user.email}</span>
</div>
)}
emptyMessage="暂无用户"
/>
);
};
2. Hooks类型定义
// 自定义Hook类型
interface UseApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
interface UseApiReturn<T> extends UseApiState<T> {
execute: () => Promise<T>;
reset: () => void;
}
const useApi = <T = any>(url: string): UseApiReturn<T> => {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: false,
error: null
});
const execute = useCallback(async (): Promise<T> => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: T = await response.json();
setState({ data, loading: false, error: null });
return data;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
setState(prev => ({
...prev,
loading: false,
error: errorMessage
}));
throw error;
}
}, [url]);
const reset = useCallback(() => {
setState({ data: null, loading: false, error: null });
}, []);
return { ...state, execute, reset };
};
// 使用示例
const UserData: React.FC = () => {
const { data: user, loading, error, execute } = useApi<User>('/api/user/1');
useEffect(() => {
execute().catch(console.error);
}, [execute]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!user) return <div>暂无数据</div>;
return <div>欢迎, {user.name}</div>;
};
🏗️ 高级TypeScript模式
1. 条件类型和映射类型
// 条件类型定义Props
type ControlType = 'input' | 'select' | 'textarea';
interface ControlConfig {
label: string;
type: ControlType;
options?: string[]; // 仅select类型需要
}
type ControlProps<T extends ControlType> = Omit<ControlConfig, 'type'> & {
type: T;
value: string;
onChange: (value: string) => void;
} & (T extends 'select' ? { options: string[] } : {});
// 渲染器组件
const ControlRenderer = <T extends ControlType>({
type,
value,
onChange,
...props
}: ControlProps<T>) => {
const commonProps = { value, onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => onChange(e.target.value) };
switch (type) {
case 'input':
return <input {...commonProps} {...props as any} />;
case 'select':
return (
<select {...commonProps}>
{('options' in props ? props.options : []).map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
);
case 'textarea':
return <textarea {...commonProps} {...props as any} />;
}
};
// 使用示例
const FormExample = () => {
const [name, setName] = useState('');
const [category, setCategory] = useState('');
return (
<div>
<ControlRenderer
type="input"
label="姓名"
value={name}
onChange={setName}
/>
<ControlRenderer
type="select"
label="分类"
value={category}
onChange={setCategory}
options={['科技', '体育', '娱乐']}
/>
</div>
);
};
2. 类型安全的Context
// Context类型定义
interface AuthContextType {
user: User | null;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
register: (userData: RegisterData) => Promise<void>;
isLoading: boolean;
}
interface LoginCredentials {
email: string;
password: string;
}
interface RegisterData {
name: string;
email: string;
password: string;
}
// 创建Context
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// Provider组件
const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(false);
const login = useCallback(async (credentials: LoginCredentials) => {
setIsLoading(true);
try {
const user = await authService.login(credentials);
setUser(user);
} catch (error) {
throw error;
} finally {
setIsLoading(false);
}
}, []);
const logout = useCallback(() => {
authService.logout();
setUser(null);
}, []);
const register = useCallback(async (userData: RegisterData) => {
setIsLoading(true);
try {
const user = await authService.register(userData);
setUser(user);
} catch (error) {
throw error;
} finally {
setIsLoading(false);
}
}, []);
const value: AuthContextType = {
user,
login,
logout,
register,
isLoading
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
// 类型安全的Hook
const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
// 使用示例
const ProtectedComponent: React.FC = () => {
const { user, logout } = useAuth();
if (!user) {
return <div>请先登录</div>;
}
return (
<div>
<h1>欢迎, {user.name}</h1>
<button onClick={logout}>退出登录</button>
</div>
);
};
🛠️ 类型工具和实用程序
1. 常用类型工具
// 深度Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// 深度Required
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
// 选择性可选
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// 选择性必需
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
// API响应类型
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
code: number;
}
// 分页响应类型
interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
// 表单状态类型
type FormState<T> = {
values: T;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
};
// 使用示例
interface UserProfile {
id: string;
name: string;
email: string;
avatar?: string;
settings: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
// 使用类型工具
type UpdateUserProfile = Partial<UserProfile>;
type UserProfileForm = FormState<UserProfile>;
2. 组件Props类型工具
// HTML属性扩展
type BaseProps<T extends HTMLElement> = {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
} & React.HTMLAttributes<T>;
// Button组件类型
interface ButtonProps extends BaseProps<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
icon?: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}
// Input组件类型
interface InputProps extends BaseProps<HTMLInputElement> {
type?: 'text' | 'email' | 'password' | 'number';
label?: string;
error?: string;
helperText?: string;
value?: string;
onChange?: (value: string) => void;
}
// 组件实现
const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'md',
loading = false,
icon,
className = '',
...props
}) => {
return (
<button
className={`btn btn-${variant} btn-${size} ${className}`}
disabled={loading}
{...props}
>
{loading && <span className="spinner" />}
{icon && <span className="icon">{icon}</span>}
{children}
</button>
);
};
const Input: React.FC<InputProps> = ({
label,
error,
helperText,
value,
onChange,
className = '',
...props
}) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(e.target.value);
};
return (
<div className={`input-group ${className}`}>
{label && <label className="input-label">{label}</label>}
<input
className={`input ${error ? 'input-error' : ''}`}
value={value || ''}
onChange={handleChange}
{...props}
/>
{error && <span className="error-message">{error}</span>}
{helperText && <span className="helper-text">{helperText}</span>}
</div>
);
};
📊 TypeScript最佳实践
1. 项目配置
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": "src",
"paths": {
"@/*": ["*"],
"@/components/*": ["components/*"],
"@/hooks/*": ["hooks/*"],
"@/utils/*": ["utils/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
2. ESLint配置
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"@typescript-eslint/recommended",
"react-app",
"react-app/jest"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": "error",
"react/prop-types": "off"
}
}
🎯 TypeScript与React最佳实践总结
- 类型优先:优先使用TypeScript定义清晰的类型
- 严格模式:启用严格模式避免类型错误
- 类型推导:合理利用TypeScript的类型推导
- 工具类型:善用TypeScript工具类型简化代码
- 泛型使用:在需要时使用泛型提高代码复用性
- 配置优化:合理配置TypeScript和ESLint
- 文档完善:为复杂类型提供完整的JSDoc注释
TypeScript为React开发提供了强大的类型安全保障,能够显著提升代码质量、开发效率和团队协作体验。
🎉 第8章 学习总结
✅ 完成任务清单
恭喜!你已经成功完成了第8章"高级特性"的全部学习任务:
- ✅ HOC高阶组件 - 掌握高阶组件的设计模式、创建方法和最佳实践
- ✅ Render Props模式 - 理解Render Props的原理、使用场景和性能优化
- ✅ 自定义Hooks详解 - 学会创建、使用和测试自定义Hooks
- ✅ Context API高级用法 - 掌握Context的高级特性和性能优化策略
- ✅ 错误边界和错误处理 - 构建完善的错误处理和恢复机制
- ✅ 性能优化技巧 - 实施从组件到应用的全链路性能优化
- ✅ 组件设计模式 - 应用成熟的组件设计模式提升代码质量
- ✅ TypeScript与React - 利用TypeScript增强类型安全和开发体验
📚 核心知识点回顾
🎯 逻辑复用模式对比
- HOC模式:适合横切关注点的逻辑注入,但可能产生包装地狱
- Render Props:灵活性强,但嵌套过深时影响可读性
- 自定义Hooks:现代化的逻辑复用方案,语法简洁,性能优秀
- Context API:跨层级数据共享,但需要谨慎使用以避免性能问题
🔧 高级开发技能
- 错误边界:构建健壮的错误处理机制,提升应用稳定性
- 性能优化:掌握React.memo、useMemo、useCallback等优化工具
- 组件设计:运用设计模式构建可维护、可复用的组件
- 类型安全:使用TypeScript提供完整的类型保护
🛠️ 实用开发工具
// 常用开发模式组合
const EnhancedComponent = compose(
withErrorBoundary, // 错误边界
withPerformanceMonitor, // 性能监控
withAuthCheck, // 权限检查
memo // 性能优化
)(MyComponent);
// 自定义Hook组合
const { data, loading, error, refetch } = useApi('/api/users');
const { theme, toggleTheme } = useTheme();
const { user, logout } = useAuth();
🚀 实际应用场景
1. 企业级应用架构
// 复杂的组件组合
const App = () => (
<ErrorBoundary>
<ThemeProvider>
<AuthProvider>
<Router>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/admin" element={
<ProtectedRoute requiredRoles={['admin']}>
<AdminPanel />
</ProtectedRoute>
} />
</Routes>
</Router>
</AuthProvider>
</ThemeProvider>
</ErrorBoundary>
);
2. 高性能列表组件
// 虚拟化长列表
const VirtualizedList = <T,>({
items,
renderItem,
keyExtractor,
itemHeight
}: VirtualListProps<T>) => {
const { visibleRange, containerRef } = useVirtualization({
itemCount: items.length,
itemHeight
});
return (
<div ref={containerRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
{items.slice(visibleRange.start, visibleRange.end).map((item, index) => (
<div
key={keyExtractor(item)}
style={{
position: 'absolute',
top: (visibleRange.start + index) * itemHeight,
width: '100%'
}}
>
{renderItem(item, visibleRange.start + index)}
</div>
))}
</div>
</div>
);
};
🎯 学习成果应用
通过本章学习,你现在具备了以下高级开发能力:
- 架构设计能力:能够设计复杂、可维护的React应用架构
- 性能优化能力:能够从多个层面进行性能分析和优化
- 错误处理能力:能够构建健壮的错误处理和恢复机制
- 模式应用能力:能够根据场景选择合适的设计模式
- 类型安全能力:能够利用TypeScript提升代码质量和开发效率
💡 核心心得
- 模式选择:根据具体场景选择最合适的技术方案
- 性能优先:始终关注组件渲染性能和用户体验
- 错误健壮:完善的错误处理是生产级应用的必要条件
- 类型安全:TypeScript能够显著提升开发效率和代码质量
- 持续学习:React生态不断发展,需要保持学习的态度
🚀 下一步学习建议
- 深入源码:研究React源码,理解底层实现原理
- 生态扩展:学习Redux、MobX等状态管理库的进阶用法
- 测试覆盖:掌握React Testing Library进行全面的组件测试
- 工程化:学习现代前端工程化工具链的配置和优化
- 跨平台:探索React Native等跨平台开发技术
恭喜你完成了React高级特性的学习!你已经掌握了构建专业级React应用的核心技能,现在可以应对复杂的企业级开发需求了。继续实践和探索,成为真正的React专家!🎉
第9章 性能优化
⚡ 章节学习目标
本章将深入探讨React应用的性能优化技术,从理论基础到实战应用,全面涵盖组件优化、渲染优化、状态管理优化、代码分割、内存管理等核心领域。通过学习本章,你将掌握构建高性能React应用的完整技能体系,能够应对复杂的性能挑战。
📋 学习任务清单
✅ React性能优化基础理论
✅ 组件级性能优化详解
✅ 渲染优化策略和技巧
✅ 状态管理性能优化
✅ 代码分割和懒加载优化
✅ 内存管理和资源优化
✅ 性能监控和分析工具
✅ 企业级性能优化方案
🎯 核心知识点概览
- 渲染机制理解 - 深入理解React的渲染流程和优化机会
- 组件优化技术 - 掌握组件级别的各种性能优化手段
- 状态管理优化 - 优化状态更新和数据流性能
- 代码分割策略 - 实现智能的代码分割和懒加载
- 内存管理 - 避免内存泄漏,优化资源使用
- 性能监控 - 建立完善的性能监控和分析体系
- 并发特性 - 利用React 18的并发能力提升性能
- 最佳实践 - 总结业界成熟的性能优化实践
💡 学习建议
- 理论结合实践:理解优化原理,通过实际项目验证效果
- 测量优先:先用工具测量性能瓶颈,再针对性优化
- 渐进优化:从最明显的性能问题开始,逐步深入优化
- 基准对比:建立性能基准,量化优化效果
- 持续监控:建立性能监控体系,及时发现性能问题
- 避免过度优化:在关键路径上进行优化,避免过早优化

浙公网安备 33010602011771号