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最佳实践总结

  1. 单一职责原则:每个HOC只负责一个特定的功能
  2. 命名规范:使用with前缀,清晰的displayName
  3. Props透明:避免静默覆盖props,谨慎处理
  4. 性能考虑:使用React.memo,避免不必要的重渲染
  5. 类型安全:在TypeScript项目中提供完整的类型定义
  6. 文档完善:为每个HOC提供详细的使用文档和示例
  7. 测试覆盖:编写单元测试确保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开发指南

  1. 命名规范:始终以use开头,使用驼峰命名
  2. 单一职责:每个Hook只负责一个特定功能
  3. 性能考虑:适当使用useCallback和useMemo
  4. 错误处理:提供完善的错误处理机制
  5. 类型安全:使用TypeScript提供类型定义
  6. 文档完善:编写清晰的使用文档和示例
  7. 测试覆盖:编写单元测试确保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使用建议

  1. 适度使用:只在真正需要跨层级传递数据时使用Context
  2. 分片设计:将大的Context拆分成多个小的、职责单一的Context
  3. 性能优化:使用useMemo和useCallback避免不必要的重渲染
  4. 类型安全:使用TypeScript提供完整的类型定义
  5. 错误处理:为Context添加错误边界和验证机制
  6. 文档完善:为每个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
      };
  }
};

🎯 错误边界使用建议

  1. 合理分层:在不同层级设置错误边界,避免全局覆盖
  2. 友好提示:提供用户友好的错误信息,避免技术术语
  3. 恢复机制:提供重试和恢复功能,提升用户体验
  4. 监控告警:建立完善的错误监控和告警机制
  5. 日志记录:记录详细的错误信息,便于问题排查
  6. 性能考虑:错误处理不应影响应用性能
  7. 测试覆盖:为错误处理逻辑编写充分的测试

错误边界和完善的错误处理机制是构建健壮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>
  );
};

🎯 性能优化最佳实践

  1. 避免过度优化:只在必要时进行性能优化
  2. 测量先行:先测量性能瓶颈,再针对性优化
  3. 合理使用memo:不要滥用React.memo,避免内存开销
  4. 状态设计:设计合理的状态结构,避免不必要的状态更新
  5. 虚拟化:对长列表使用虚拟化技术
  6. 代码分割:合理分割代码,减少初始加载时间
  7. 监控告警:建立性能监控体系,及时发现性能问题

性能优化是一个持续的过程,需要根据实际应用的特点和性能需求来制定相应的优化策略。


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灵活、组合性强 学习成本高
提供者模式 主题、配置、全局状态 解耦、可测试 可能过度设计
状态提升 兄弟组件通信 单向数据流 层级过深问题

🎯 组件设计最佳实践

  1. 单一职责:每个组件只负责一个功能
  2. 组合优于继承:通过组合构建复杂组件
  3. Props设计:设计清晰、类型安全的Props接口
  4. 可测试性:组件应该易于单元测试
  5. 可访问性:考虑无障碍访问需求
  6. 文档完善:为组件提供详细的使用文档
  7. 性能考虑:避免不必要的重渲染

良好的组件设计模式能显著提升代码的可维护性、可复用性和团队协作效率。


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最佳实践总结

  1. 类型优先:优先使用TypeScript定义清晰的类型
  2. 严格模式:启用严格模式避免类型错误
  3. 类型推导:合理利用TypeScript的类型推导
  4. 工具类型:善用TypeScript工具类型简化代码
  5. 泛型使用:在需要时使用泛型提高代码复用性
  6. 配置优化:合理配置TypeScript和ESLint
  7. 文档完善:为复杂类型提供完整的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>
  );
};

🎯 学习成果应用

通过本章学习,你现在具备了以下高级开发能力:

  1. 架构设计能力:能够设计复杂、可维护的React应用架构
  2. 性能优化能力:能够从多个层面进行性能分析和优化
  3. 错误处理能力:能够构建健壮的错误处理和恢复机制
  4. 模式应用能力:能够根据场景选择合适的设计模式
  5. 类型安全能力:能够利用TypeScript提升代码质量和开发效率

💡 核心心得

  • 模式选择:根据具体场景选择最合适的技术方案
  • 性能优先:始终关注组件渲染性能和用户体验
  • 错误健壮:完善的错误处理是生产级应用的必要条件
  • 类型安全:TypeScript能够显著提升开发效率和代码质量
  • 持续学习:React生态不断发展,需要保持学习的态度

🚀 下一步学习建议

  1. 深入源码:研究React源码,理解底层实现原理
  2. 生态扩展:学习Redux、MobX等状态管理库的进阶用法
  3. 测试覆盖:掌握React Testing Library进行全面的组件测试
  4. 工程化:学习现代前端工程化工具链的配置和优化
  5. 跨平台:探索React Native等跨平台开发技术

恭喜你完成了React高级特性的学习!你已经掌握了构建专业级React应用的核心技能,现在可以应对复杂的企业级开发需求了。继续实践和探索,成为真正的React专家!🎉


第9章 性能优化

⚡ 章节学习目标

本章将深入探讨React应用的性能优化技术,从理论基础到实战应用,全面涵盖组件优化、渲染优化、状态管理优化、代码分割、内存管理等核心领域。通过学习本章,你将掌握构建高性能React应用的完整技能体系,能够应对复杂的性能挑战。

📋 学习任务清单

✅ React性能优化基础理论

✅ 组件级性能优化详解

✅ 渲染优化策略和技巧

✅ 状态管理性能优化

✅ 代码分割和懒加载优化

✅ 内存管理和资源优化

✅ 性能监控和分析工具

✅ 企业级性能优化方案

🎯 核心知识点概览

  1. 渲染机制理解 - 深入理解React的渲染流程和优化机会
  2. 组件优化技术 - 掌握组件级别的各种性能优化手段
  3. 状态管理优化 - 优化状态更新和数据流性能
  4. 代码分割策略 - 实现智能的代码分割和懒加载
  5. 内存管理 - 避免内存泄漏,优化资源使用
  6. 性能监控 - 建立完善的性能监控和分析体系
  7. 并发特性 - 利用React 18的并发能力提升性能
  8. 最佳实践 - 总结业界成熟的性能优化实践

💡 学习建议

  • 理论结合实践:理解优化原理,通过实际项目验证效果
  • 测量优先:先用工具测量性能瓶颈,再针对性优化
  • 渐进优化:从最明显的性能问题开始,逐步深入优化
  • 基准对比:建立性能基准,量化优化效果
  • 持续监控:建立性能监控体系,及时发现性能问题
  • 避免过度优化:在关键路径上进行优化,避免过早优化

posted @ 2025-11-29 18:20  seven3306  阅读(4)  评论(0)    收藏  举报