学习笔记十 —— 自定义hooks设计原则 笔试实现

自定义Hooks设计(如何封装AI搜索组件?)

我们先来系统性地拆解不同场景下自定义 Hooks 的设计思路,并结合你提到的点,细化核心代码的每一行关键操作及其设计考量。这将帮助你建立对不同逻辑单元封装的感觉,并理解代码背后的"为什么"。


🧠 一、自定义 Hooks 设计核心原则回顾 (面试高频点)

在深入具体 Hook 之前,牢记这些贯穿始终的原则至关重要:

  1. 单一职责原则 (Single Responsibility Principle - SRP): 一个 Hook 只解决一个特定的问题或封装一个独立的逻辑单元。避免"瑞士军刀"式的 Hook。
    • 为什么? 提高复用性、可测试性、可维护性。修改一个功能不会意外影响其他功能。
  2. 明确输入输出 (Clear Input & Output): Hook 的参数(输入)和返回值(输出)必须清晰、简洁、有描述性。
    • 为什么? 使用者能快速理解如何调用这个 Hook 以及它能提供什么,降低认知负担。
  3. 命名规范 (Naming Convention): 必须以 use 开头(如 useFetch, useWindowSize),采用驼峰命名法,准确描述功能。
    • 为什么? 这是 React 的约定,便于识别是 Hook,工具(如 ESLint 插件)也能据此检查规则。
  4. 性能优化意识 (Performance Awareness): 合理使用 useMemo, useCallback, useRef 避免不必要的计算和重渲染。谨慎处理依赖数组和副作用。
    • 为什么? Hook 在每次组件渲染时都会执行,不当设计会导致性能瓶颈。
  5. 副作用管理 (Side Effect Management): 使用 useEffect (React) 或 onMounted/onUnmounted (Vue) 等处理副作用(API 请求、事件监听、定时器),并确保清理return清理函数)。
    • 为什么? 防止内存泄漏、无效更新和状态不一致。
  6. 遵循 Hooks 规则 (Rules of Hooks): 只在函数组件/其他 Hook 的最顶层调用 Hook,不能在条件、循环或嵌套函数中调用。
    • 为什么? React 依赖 Hook 的调用顺序来正确管理状态。违反规则会导致难以追踪的 bug。

🔧 二、不同场景 Hook 设计思路与代码深度拆解

我们选择四种最常见且具有代表性的场景,详细分析其设计思路并逐行解析核心代码。

🚀 1. 数据请求 Hook (e.g., useFetch / useAsync)

  • 设计思路:

    • 输入 (Input): 请求的 url (必选),可选的配置项(如 method, headers, initialData, 依赖项数组 dependencies 用于触发重新请求)。
    • 输出 (Output): 请求状态 (loading, error, data),可能包含一个手动触发重新请求的函数 (refetch)。
    • 核心逻辑: 使用 useState 管理 data, loading, error 状态。使用 useEffect(依赖 url 和可能的 dependencies)发起异步请求。在请求开始/成功/失败时更新状态。提供清理(如取消请求)选项。考虑防抖/节流(可选项,或由调用方控制依赖触发频率)。
    • 关键考量点: 请求取消、避免竞态条件、缓存策略(可扩展)。
  • 核心代码实现与逐行解析 (React 示例):

javascript 复制代码
import { useState, useEffect } from 'react';

function useFetch(url, options = {}, dependencies = []) {
  // 状态管理:清晰定义 Hook 管理的核心状态
  const [data, setData] = useState(options.initialData || null);
  const [loading, setLoading] = useState(true); // 初始状态为加载中
  const [error, setError] = useState(null);

  // 使用 ref 存储可取消的请求控制器 (AbortController)
  const abortControllerRef = useRef(new AbortController());

  // 核心副作用:执行数据获取
  useEffect(() => {
    // 1. 重置状态 (为新的请求做准备,避免旧数据残留)
    setLoading(true);
    setError(null);
    // 2. 创建新的 AbortController 并存储到 ref (确保每次请求使用新的控制器)
    const abortController = new AbortController();
    abortControllerRef.current = abortController;

    // 3. 定义异步请求函数 (封装请求逻辑)
    const fetchData = async () => {
      try {
        // 4. 发起请求:传入 signal 用于取消
        const signal = abortController.signal;
        const response = await fetch(url, { ...options, signal });
        // 5. 检查 HTTP 状态码 (非 2xx 响应也需要处理为错误)
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        // 6. 解析响应数据
        const result = await response.json();
        // 7. 检查请求是否已被取消 (避免在已卸载的组件上设置状态)
        if (!signal.aborted) {
          setData(result);
        }
      } catch (err) {
        // 8. 错误处理:仅处理非取消错误
        if (err.name !== 'AbortError') {
          setError(err);
        }
      } finally {
        // 9. 无论成功/失败/取消,只要未被取消,都结束加载状态
        if (!abortController.signal.aborted) {
          setLoading(false);
        }
      }
    };

    fetchData(); // 执行请求

    // 10. 清理函数:组件卸载或依赖变化导致重新请求前,取消进行中的请求
    return () => {
      abortController.abort(); // 关键!取消当前请求
    };
    // 11. 依赖数组:url 和传入的 dependencies 变化时重新执行 effect
  }, [url, ...dependencies]); // 依赖项明确

  // 12. (可选) 提供手动重新请求的函数
  const refetch = () => {
    // 可以通过改变一个无关的状态 (如 forceUpdate) 或直接触发 useEffect 依赖变化实现。
    // 更优雅的方式是利用依赖数组:这里假设调用 refetch 会改变 dependencies 中的某个值(由调用方控制)。
    // 或者,可以在这里重置状态并重新执行 fetchData 逻辑,但需要处理好取消和状态重置。
    console.warn('Manual refetch triggered. Implementation depends on how dependencies are managed.');
  };

  // 13. 明确输出:状态 + 可选操作
  return { data, loading, error, refetch };
}

📝 2. 表单管理 Hook (e.g., useForm / useFormState)

  • 设计思路:

    • 输入 (Input): 表单的初始值 (initialValues),可选的验证规则 (validationSchemavalidate 函数)。
    • 输出 (Output): 表单数据对象 (values),表单字段变更处理器 (handleChange),表单提交处理器 (handleSubmit),重置表单函数 (resetForm),错误状态对象 (errors),表单整体状态 (isValid, isSubmitting 等)。
    • 核心逻辑: 使用 useStateuseReducer (适合复杂表单) 管理整个表单的 valueserrors 状态。提供通用的 handleChange 函数(通常利用 name 属性)更新对应字段的值。在提交时执行验证(同步或异步)并处理提交逻辑。提供重置功能。
    • 关键考量点: 性能(避免大型表单频繁更新导致的性能问题)、验证策略(同步/异步、何时触发)、与 UI 库集成、嵌套字段处理。
  • 核心代码实现与逐行解析 (React useState 基础版):

javascript 复制代码
import { useState, useCallback } from 'react';

function useForm(initialValues = {}, validate) {
  // 1. 状态管理:表单值 (values) 和错误信息 (errors)
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  // 2. 通用字段变更处理器:利用字段的 `name` 属性进行更新
  const handleChange = useCallback((event) => {
    const { name, value, type, checked } = event.target;
    // 3. 处理 checkbox 的特殊情况 (checked 属性)
    const val = type === 'checkbox' ? checked : value;
    // 4. 更新状态:基于字段名 (name) 设置对应的值
    setValues(prevValues => ({
      ...prevValues, // 展开旧状态 (不可变性)
      [name]: val    // 更新目标字段
    }));
    // 5. (可选) 即时验证:如果提供了 validate 函数,并在变更时需验证
    if (validate) {
      // 6. 通常只验证当前变更的字段 (避免全表单验证开销)
      setErrors(prevErrors => ({
        ...prevErrors,
        [name]: validate(name, val, values) // 调用验证函数获取当前字段错误
      }));
    }
  }, [validate]); // 依赖 validate (如果它变化)

  // 7. 表单提交处理器
  const handleSubmit = useCallback((onSubmit) => (event) => {
    event.preventDefault(); // 阻止默认表单提交行为
    setIsSubmitting(true);
    // 8. 提交前进行全表单验证 (如果有验证函数)
    if (validate) {
      const newErrors = {};
      let isValid = true;
      // 9. 遍历所有字段进行验证
      Object.keys(values).forEach(name => {
        const error = validate(name, values[name], values);
        if (error) {
          newErrors[name] = error;
          isValid = false;
        }
      });
      setErrors(newErrors);
      // 10. 如果验证不通过,阻止提交
      if (!isValid) {
        setIsSubmitting(false);
        return;
      }
    }
    // 11. 调用外部传入的提交函数 (onSubmit),并传入当前表单值
    onSubmit(values);
    setIsSubmitting(false); // 假设提交是同步的,异步提交需在 .then/.catch 中处理
  }, [values, validate]); // 依赖 values 和 validate

  // 12. 重置表单函数
  const resetForm = useCallback(() => {
    setValues(initialValues);
    setErrors({});
    setIsSubmitting(false);
  }, [initialValues]); // 依赖 initialValues

  // 13. 计算表单整体是否有效 (可选,根据 errors 对象计算)
  const isValid = Object.keys(errors).length === 0;

  // 14. 明确输出:表单核心状态、操作函数、辅助状态
  return {
    values,        // 表单数据
    errors,        // 字段错误信息
    isSubmitting,  // 是否正在提交
    isValid,       // 表单整体是否有效 (基于当前 errors)
    handleChange,  // 字段变更处理器
    handleSubmit,  // 表单提交处理器 (高阶函数,需传入实际提交逻辑)
    resetForm,     // 重置表单函数
    setValues,     // (谨慎暴露) 直接设置 values (用于复杂情况如级联更新)
  };
}

🎯 3. 事件监听/浏览器 API Hook (e.g., useWindowSize, useMousePosition, useKeyPress)

  • 设计思路:

    • 输入 (Input): 通常不需要复杂输入,可能需要配置项(如监听哪个 keyuseKeyPress,节流间隔)。
    • 输出 (Output): 监听的目标值(如 { width, height }, { x, y }, keyPressed)。
    • 核心逻辑: 使用 useState 存储监听的值。在 useEffect 中添加事件监听器(resize, mousemove, keydown/keyup)。在事件处理函数中更新状态。在清理函数中移除事件监听器。必须考虑性能! (使用节流 throttle 或防抖 debounce 限制高频事件更新频率)。
    • 关键考量点: 性能优化(节流/防抖)、SSR 兼容性(避免在 Node 环境访问 window)、清理的重要性。
  • 核心代码实现与逐行解析 (React useWindowSize 带节流):

javascript 复制代码
import { useState, useEffect } from 'react';

// 15. 节流函数工具 (避免高频更新)
function throttle(func, wait) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= wait) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

function useWindowSize(throttleWait = 100) { // 默认 100ms 节流
  // 16. 状态管理:窗口尺寸
  const [size, setSize] = useState({
    width: typeof window !== 'undefined' ? window.innerWidth : 0, // SSR 兼容
    height: typeof window !== 'undefined' ? window.innerHeight : 0,
  });

  useEffect(() => {
    // 17. SSR 检查:只在客户端执行
    if (typeof window === 'undefined') {
      return;
    }

    // 18. 定义处理窗口大小变化的函数 (应用节流)
    const handleResize = throttle(() => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }, throttleWait);

    // 19. 立即获取一次初始尺寸 (确保首次渲染准确)
    handleResize();
    // 20. 添加事件监听器 (监听 resize 事件)
    window.addEventListener('resize', handleResize);
    // 21. 清理函数:组件卸载时移除监听器 (并取消可能的节流等待)
    return () => {
      window.removeEventListener('resize', handleResize);
      // 如果节流实现有定时器,这里也需要 clearTimeout
    };
  }, [throttleWait]); // 依赖 throttleWait (如果变化,需要重新创建 throttle)

  // 22. 输出窗口尺寸对象
  return size;
}

⚙️ 4. 复杂状态/Reducer Hook (e.g., useReducer 封装购物车、复杂配置)

  • 设计思路:

    • 输入 (Input): 初始状态 (initialState),Reducer 函数 (reducer),可选的初始化函数 (init 函数)。
    • 输出 (Output): 当前状态 (state),分发 action 的函数 (dispatch),有时会封装特定的 action creators(函数返回 action 对象)。
    • 核心逻辑: 直接使用 React 内置的 useReducer Hook。定义清晰的 reducer 函数处理不同的 action.type。状态更新遵循不可变性原则(返回新对象/数组)。适合管理包含多个子值、逻辑复杂的 state,或者下一个 state 依赖于之前的 state 的场景。
    • 关键考量点: 清晰定义 action 类型、reducer 的纯净性、避免深拷贝性能问题(使用 Immer 等库可简化不可变更新)、何时选择 useReducer 替代 useState(状态逻辑复杂、有深层更新、需要可预测的状态变更)。
  • 核心代码实现与逐行解析 (React 基础 useReducer 示例 - 计数器增强版):

javascript 复制代码
import { useReducer } from 'react';

// 23. 定义 Action 类型 (使用常量或 TypeScript 类型)
const ACTIONS = {
  INCREMENT: 'increment',
  DECREMENT: 'decrement',
  SET: 'set',
  RESET: 'reset',
};

// 24. Reducer 函数:纯函数,接收当前 state 和 action,返回新 state
function counterReducer(state, action) {
  switch (action.type) {
    case ACTIONS.INCREMENT:
      return { count: state.count + 1 }; // 不可变更新:返回新对象
    case ACTIONS.DECREMENT:
      return { count: state.count - 1 };
    case ACTIONS.SET:
      // 25. action 携带 payload (要设置的值)
      return { count: action.payload };
    case ACTIONS.RESET:
      // 26. 重置到初始状态 (假设 initialState 是 { count: 0 })
      return { count: 0 };
    default:
      // 27. 未知 action 类型:返回当前 state (或抛出错误)
      return state;
  }
}

// 28. (可选) Action Creators:封装创建 action 对象的函数
function increment() {
  return { type: ACTIONS.INCREMENT };
}
function decrement() {
  return { type: ACTIONS.DECREMENT };
}
function setCount(value) {
  return { type: ACTIONS.SET, payload: value };
}
function resetCount() {
  return { type: ACTIONS.RESET };
}

function useEnhancedCounter(initialCount = 0) {
  // 29. 使用 useReducer:传入 reducer 函数和初始状态
  const [state, dispatch] = useReducer(counterReducer, { count: initialCount });

  // 30. 封装更语义化的操作函数 (内部调用 dispatch)
  const incrementCounter = () => dispatch(increment()); // 使用 action creator
  const decrementCounter = () => dispatch(decrement());
  const setCounter = (value) => dispatch(setCount(value));
  const resetCounter = () => dispatch(resetCount());

  // 31. 输出:当前状态 + 操作函数
  return {
    count: state.count, // 暴露具体的状态值
    increment: incrementCounter,
    decrement: decrementCounter,
    set: setCounter,
    reset: resetCounter,
  };
}

📊 三、设计思路对比与选择

特征 数据请求 Hook (useFetch) 表单管理 Hook (useForm) 事件监听 Hook (useWindowSize) 复杂状态 Hook (useEnhancedCounter)
核心状态管理 data, loading, error values, errors, isSubmitting size (或其他目标值) state (通常是一个对象)
主要副作用 API 调用 (fetch/axios) 表单提交逻辑 事件监听 (resize 等) 无 (Reducer 是纯函数)
关键优化点 请求取消、竞态处理、缓存 字段级更新、验证触发时机 节流/防抖 (必须!) 不可变更新、避免深拷贝开销
清理需求 高 (取消请求) 低 (通常无持续副作用) (移除事件监听器)
输入复杂度 中 (URL, options, deps) 中/高 (initialValues, 验证规则) 低 (可能配置节流间隔) 中 (Reducer, initialState)
输出复杂度 中 (状态 + refetch) (值、处理器、错误、状态) 低 (目标值) 中 (状态 + dispatch/封装函数)
适用场景 任何异步数据获取 表单交互 响应视窗变化、鼠标/键盘事件 购物车、复杂配置、多步骤流程

💎 四、总结与面试要点

  1. 理解原则是根基: 面试中阐述自定义 Hook 设计时,务必清晰传达 单一职责、明确输入输出、命名规范、性能意识、副作用管理、遵循规则 这些核心原则。
  2. 场景化设计思维: 不要试图设计一个"万能" Hook。根据要解决的具体问题类型 (数据获取、表单、事件、复杂状态)选择合适的设计模式和核心 API (useState, useEffect, useReducer, useCallback, useMemo, useRef)。
  3. 代码细节体现功力:
    • 状态初始化: 考虑 null vs 空数组/对象,SSR 兼容性。
    • 依赖数组: 精确管理,避免遗漏导致陈旧闭包,避免冗余导致无效重运行。
    • 清理函数: 绝对必要 对于请求、事件监听、定时器。这是防止内存泄漏和错误的关键。
    • 性能优化: 高频事件用节流/防抖 ;大型状态更新用不可变数据+结构共享useImmer ;避免在渲染中做高开销计算用 useMemo ;避免回调函数频繁重建用 useCallback(配合稳定依赖)。
    • 错误处理: 健壮处理异步错误(API 失败、网络错误),区分可取消错误(AbortError)。
    • 类型安全 (TS): 强烈推荐使用 TypeScript 定义 Hook 的输入参数、状态类型、返回值类型,极大提升可维护性和使用体验。
  4. 权衡与取舍: 例如,useForm 是选择简单的 useState 还是更强大的 useReducer?即时验证还是提交时验证?暴露 setValues 还是只提供 handleChange?根据业务复杂度和团队约定做决定。
  5. 测试性: 设计 Hook 时就考虑如何测试。逻辑与 UI 分离的自定义 Hook 天然更易于进行单元测试。

通过理解这些不同场景的设计思路和代码细节,你应该能更系统地构思如何封装自己的 Hook,并在面试中清晰地阐述你的设计决策和实现考量。记住,好的自定义 Hook 是逻辑清晰、接口友好、性能稳健、易于维护和测试的独立单元。

以下是针对封装 AI 搜索组件的自定义 Hook 设计思路及实现详解,结合前端面试的技术要点展开:

🔍 一、设计核心原则

  1. 逻辑与UI分离
    • 将搜索的数据管理(请求、状态、错误处理)与UI渲染解耦,通过 Hook 封装通用逻辑,组件仅负责展示。
  2. 输入输出明确
    • 输入:搜索 API 函数、初始参数、防抖配置等。
    • 输出 :搜索状态(loading/error/data)、触发搜索的函数、参数更新方法。
  3. 性能优化
    • 内置防抖(debounce)减少高频请求,依赖项精准控制重请求条件。

🛠️ 二、Hook 结构设计(useAISearch

javascript 复制代码
import { useState, useEffect, useCallback, useRef } from 'react';

function useAISearch({ api, initialQuery = '', debounceTime = 300 }) {
  const [query, setQuery] = useState(initialQuery);
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const timerRef = useRef();

  // 防抖触发搜索
  const triggerSearch = useCallback(() => {
    if (!query.trim()) {
      setResults([]);
      return;
    }
    setLoading(true);
    setError(null);
    api(query)
      .then((data) => setResults(data))
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false));
  }, [api, query]);

  // 防抖逻辑
  useEffect(() => {
    if (timerRef.current) clearTimeout(timerRef.current);
    timerRef.current = setTimeout(triggerSearch, debounceTime);
    return () => clearTimeout(timerRef.current);
  }, [query, debounceTime, triggerSearch]);

  // 暴露给组件的接口
  return {
    query,
    setQuery,      // 更新搜索词
    results,      // 搜索结果
    loading,      // 加载状态
    error,        // 错误信息
    search: triggerSearch, // 手动触发搜索(如按钮点击)
  };
}

⚙️ 三、关键实现细节

  1. 防抖控制
    • 使用 useRef 存储计时器,避免 setTimeout 闭包问题,debounceTime 参数支持动态调整。
  2. 异步安全处理
    • 请求前后更新 loading 状态,错误捕获后重置状态并提示,避免组件崩溃。
  3. 条件请求
    • 空搜索词自动清空结果,避免无效请求。
  4. 内存管理
    • 清理计时器(clearTimeout)防止组件卸载后执行无效更新。

💡 四、组件集成示例

jsx 复制代码
function AISearchComponent() {
  const { query, setQuery, results, loading, error, search } = useAISearch({
    api: fetchSearchResults, // 传入搜索API函数
    debounceTime: 500,
  });

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="输入关键词..."
      />
      <button onClick={search}>搜索</button>
      
      {loading && <p>搜索中...</p>}
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <ul>
        {results.map((result) => (
          <li key={result.id}>{result.title}</li>
        ))}
      </ul>
    </div>
  );
}

🚀 五、面试技术亮点

  1. 自定义 Hook 设计范式
    • 遵循单一职责原则(仅处理搜索逻辑)和明确输入输出(参数/返回值清晰)。
  2. 性能优化实践
    • 防抖减少请求次数、useCallback 避免函数重复创建、条件渲染控制。
  3. 错误边界处理
    • 异步请求的 try/catch 封装,提升用户体验。
  4. 扩展性
    • 支持自定义防抖时间、API 替换,适配不同场景(如实时搜索/按钮触发)。

🔮 六、进阶优化方向

  • 缓存策略 :结合 useMemo 缓存历史结果,减少重复请求。
  • 取消请求 :集成 AbortController 终止进行中的请求。
  • 类型安全:使用 TypeScript 定义 API 响应格式,增强代码可靠性。

💎 总结 :封装 useAISearch 的核心在于逻辑复用性能可控。通过分离数据流与UI,开发者可快速构建高性能搜索组件,同时保持代码可维护性。此设计模式体现了 React Hooks 的核心价值------将复杂逻辑抽象为可插拔单元。


以下是5个面试高频自定义Hook实现题目,结合大厂真题和原理深度整理,附核心思路与实现要点,助你高效备战:

🔥 高频自定义Hooks实现题目

1. 防抖搜索Hook(useDebounceSearch

题目描述 :封装带防抖的搜索Hook,减少高频请求
核心思路

javascript 复制代码
function useDebounceSearch(api, delay = 300) {
  const [results, setResults] = useState([]);
  const timerRef = useRef();
  
  const search = useCallback((query) => {
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(async () => {
      const data = await api(query);
      setResults(data);
    }, delay);
  }, [api, delay]);
  
  return { results, search };
}

关键考点

  • 防抖逻辑(setTimeout + clearTimeout
  • useRef保存计时器避免闭包陷阱
  • 依赖项处理(api变化需重建函数)

2. 强制刷新Hook(useForceUpdate

题目描述 :不修改state的前提下强制组件重新渲染
核心思路

javascript 复制代码
const useForceUpdate = () => {
  const [_, forceUpdate] = useReducer(x => x + 1, 0);
  return useCallback(() => forceUpdate(), []);
};

关键考点

  • 利用useReducer的不可变更新触发渲染
  • 返回稳定函数引用(useCallback空依赖)
  • 原理:状态计数变化引发组件更新

3. 异步请求Hook(useFetch

题目描述 :封装带加载/错误状态的数据请求逻辑
核心思路

javascript 复制代码
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const fetchData = async () => {
      try {
        const res = await fetch(url, { signal: controller.signal });
        setData(await res.json());
      } catch (err) { 
        if (err.name !== 'AbortError') setError(err); 
      } finally { 
        setLoading(false); 
      }
    };
    fetchData();
    return () => controller.abort(); // 取消未完成请求
  }, [url]);

  return { data, loading, error };
}

关键考点

  • 请求取消(AbortController避免竞态)
  • 错误边界处理(区分取消错误与真实错误)
  • 依赖数组控制请求触发时机

4. Redux状态管理Hook(useReducer进阶)

题目描述 :用自定义Hook实现Redux核心功能(状态+dispatch)
核心思路

javascript 复制代码
const useCustomRedux = (reducer, initialState) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  // 封装action creators
  const bindActionCreators = (actions) => 
    Object.keys(actions).reduce((acc, key) => ({
      ...acc, 
      [key]: (...args) => dispatch(actions[key](...args))
    }), {});
  
  return { state, ...bindActionCreators(actions) };
};

关键考点

  • useReducer管理复杂状态逻辑
  • Action Creators封装(避免手动写dispatch
  • 对比Redux:缺少中间件/时间旅行但轻量

5. 响应式窗口尺寸Hook(useWindowSize

题目描述 :实时获取浏览器窗口尺寸变化
核心思路

javascript 复制代码
function useWindowSize() {
  const [size, setSize] = useState({ 
    width: window.innerWidth, 
    height: window.innerHeight 
  });

  useEffect(() => {
    const handleResize = () => setSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

关键考点

  • 事件监听与清理(useEffect清理函数)
  • 节流优化(高频事件需加throttle
  • SSR兼容(typeof window检测)

💡 扩展练习建议

  1. usePrevious :保存上一次的state值(useRef追踪历史值)
  2. useLocalStorage :同步localStorage与组件状态(JSON.stringify/parse处理)
  3. useClickOutside :点击组件外部时触发回调(document.addEventListener + ref判断)
  4. useHover :检测元素悬停状态(mouseenter/mouseleave事件绑定)

⚠️ 面试核心考察点

  • 设计原则 :单一职责、输入输出明确、命名规范(useXxx
  • 性能意识 :合理使用useMemo/useCallback、避免无效渲染
  • 边界处理:竞态取消、错误捕获、SSR兼容
  • 原理深度:闭包陷阱、依赖数组作用、Hooks调用规则

💪 练习建议:手写实现后对比ahooks源码(github.com/alibaba/hoo...),学习工业级Hook的异常处理与扩展性设计。

相关推荐
崔庆才丨静觅20 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606121 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了21 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅21 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅21 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 天前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 天前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅1 天前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊1 天前
jwt介绍
前端
爱敲代码的小鱼1 天前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax