学习笔记十 —— 自定义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的异常处理与扩展性设计。

相关推荐
Entropy-Lee26 分钟前
JavaScript 语句和函数
开发语言·前端·javascript
Wcowin1 小时前
MkDocs文档日期插件【推荐】
前端·mkdocs
xw52 小时前
免费的个人网站托管-Cloudflare
服务器·前端
网安Ruler2 小时前
Web开发-PHP应用&Cookie脆弱&Session固定&Token唯一&身份验证&数据库通讯
前端·数据库·网络安全·php·渗透·红队
!win !2 小时前
免费的个人网站托管-Cloudflare
服务器·前端·开发工具
饺子不放糖2 小时前
基于BroadcastChannel的前端多标签页同步方案:让用户体验更一致
前端
饺子不放糖2 小时前
前端性能优化实战:从页面加载到交互响应的全链路优化
前端
Jackson__2 小时前
使用 ICE PKG 开发并发布支持多场景引用的 NPM 包
前端
饺子不放糖2 小时前
前端错误监控与异常处理:构建健壮的Web应用
前端
cos2 小时前
FE Bits 前端周周谈 Vol.1|Hello World、TanStack DB 首个 Beta 版发布
前端·javascript·css