--- tags: [] create time: 2026-05-14 10:30 --- # 金山考试 - 武科大 - 第四次课(React 核心) ## 概述 本次考试围绕 **React 核心概念与性能优化**,涵盖以下知识点: - useState / useEffect / useMemo / useCallback 等 Hooks 的使用与陷阱 - React.memo 与组件渲染行为控制 - 单向数据流与组件通信模式(父子/兄弟/跨级) - Context API 的作用域与注意事项 - React Router 路由机制( BrowserRouter、Route、动态参数) - Redux 状态管理(Action、Reducer、中间件) - Axios 拦截器封装最佳实践 - 大列表渲染与 Vite 打包性能优化 - 前端监控埋点方案对比(GIF vs AJAX) ## 我的错题 > [!FAIL] ❌ 错题记录 > 以下题目作答错误,标注解题思路与关键误区。 ### Q14 — 兄弟组件通信 | 项目 | 内容 | |------|------| | 官方答案 | C(Context) | | 你的答案 | D(Redux) | > [!NOTE] 💡 为什么选 Context? > Redux 属于**全局状态管理库**,适用于跨多个不相关组件共享复杂状态的**大型项目**。而本题限定为「**兄弟组件之间的简单通信**」,使用 Context 更轻量、更符合场景定位——通过一个公共父组件创建 Context,两个兄弟组件分别消费即可,无需引入额外依赖。 ### Q29 — React.memo + 对象 prop | 项目 | 内容 | |------|------| | 官方答案 | D(父组件每次创建新对象引用) | | 你的答案 | (正确 ✅) | > [!NOTE] ℹ️ 标记已纠正 > 此题已掌握,保留备忘。React.memo 基于**浅比较**(shallow equal),只要 prop 引用地址变了就会重新渲染。**每次渲染都 new 一个对象**会导致引用始终不同,`useMemo` 可以缓存引用解决这个问题。 --- ## 武科大 - 第四次课 ### 一、单选题 #### 1. useState 闭包陷阱 【单选题】点击按钮后,以下 React 代码,console 的输出和按钮中的展示分别是什么?(2分) ```js function Counter() { const [count, setCount] useState(0); const handleClick = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); console.log(count); }; return ; } ``` A. 3, 3 B. 0, 1 C. 0, 3 D. 3, 1 **正确答案:** B **作答答案:** C > [!TIP] 🔑 核心考点 > `setCount(count + 1)` 三次调用中,`count` 是**同一闭包快照值 0**,所以实际等价于 `setCount(1)` 执行三次。但 React 会**批量合并**同一次事件中的状态更新,最终只渲染一次 → count = 1。 > > ⚠️ 若用函数式更新 `setCount(prev => prev + 1)` 三次,则结果为 3。 --- #### 2. React.memo + 对象 prop 【单选题】考虑一个使用 `React.memo` 包装的 `Child` 组件。如果父组件传递一个对象 `prop`,以下哪种方法可以确保 `Child` 组件在父组件重新渲染时不会不必要地重新渲染? A. 将 `Child` 组件从 `React.memo` 中移除 B. 在 `Child` 组件内部使用 `useEffect` 来比较 `prop` C. 将对象 `prop` 传递给 `useMemo`,并将其作为 `Child` 的 `prop` D. 将对象 `prop` 的所有属性分别作为独立的 `prop` 传递给 `Child` **正确答案:** C **作答答案:** C ✅ --- #### 3. 父组件状态更新 → 子组件渲染 【单选题】在 React 中,当父组件状态更新时子组件的渲染行为是怎样的?(2分) A. 子组件不会重新渲染,除非子组件自身的状态或属性变化 B. 子组件会随父组件的重新渲染而无条件重新渲染 C. 子组件仅在其接收的 props 发生变化时重新渲染 D. 父组件的渲染不影响子组件 **正确答案:** B **作答答案:** B ✅ > [!NOTE] ℹ️ 深入理解 > 父组件 re-render 会**默认触发所有子组件** re-render,无论 props 是否改变。如需跳过,可使用 `React.memo`、`useMemo`、`useCallback` 等手段进行优化。 --- #### 4. useMemo vs useCallback 【单选题】React 中 useMemo 和 useCallback 的主要区别是什么?(2分) A. useMemo 返回一个 memoized 值,useCallback 返回一个 memoized 函数 B. useMemo 和 useCallback 效果完全一样 C. useCallback 可以依赖多个变量,而 useMemo 只能依赖一个变量 D. 没有区别,两者在 React 中可以互换使用 **正确答案:** A **作答答案:** A ✅ --- #### 5. useCallback 的主要用途 【单选题】在 React 中 useCallback 的主要用途是什么?(2分) A. 用于在组件之间传递状态 B. 用于避免组件中的函数在每次渲染时都重新创建 C. 用于直接修改组件的 state D. 用于数据的结构化管理 **正确答案:** B **作答答案:** B ✅ --- #### 6. React 性能优化手段 【单选题】下面哪些是 React 性能优化的手段?(2分) ```js // 方案1 — useMemo 缓存计算结果 const memoizedValue = useMemo(() => computeValue(a), [a]); // 方案2 — useCallback 缓存函数引用 const handleClick = () => { /*...*/ }; const memoizedHandle = useCallback(handleClick, []); ``` A. 仅方案1 B. 仅方案2 C. 两者都需要 D. 都不需要 **正确答案:** C **作答答案:** C ✅ > [!TIP] 🔑 何时使用? > - `useMemo`:适合**昂贵计算**的结果缓存 > - `useCallback`:适合**传给子组件作为 prop 的函数**,配合 `React.memo` 防止子组件无谓重渲染 --- #### 7. 单项数据流 【单选题】在 React 中,以下关于单项数据流的描述,哪个是正确的?(3分) A. 数据可以在组件间随意双向流动 B. 数据只能从父组件流向子组件,子组件不能直接修改父组件的数据 C. 数据从子组件流向父组件,父组件接收数据后再分发给其他子组件 D. 数据在同级组件间可以自由流动 **正确答案:** B **作答答案:** B ✅ --- #### 8. React Router 核心原理 【单选题】React Router 的核心原理是基于以下哪种机制来实现路由匹配和渲染的?(2分) A. 事件监听 B. 虚拟 DOM 比对 C. URL 变化监听和组件映射 D. 状态管理 **正确答案:** C **作答答案:** C ✅ --- #### 9. 路由根组件 【单选题】在 React 中使用路由时,用于包裹整个应用路由组件的根组件是?(3分) A. Route B. BrowserRouter C. Switch D. Link **正确答案:** B **作答答案:** B ✅ > [!NOTE] ℹ️ 注意版本差异 > React Router v5 用 `BrowserRouter` + `Switch`;v6 改用 `BrowserRouter` + `` + ``。Switch 在 v6 已被废弃。 --- #### 10. React Context 错误说法 【单选题】关于 React Context 的说法,错误的是?(3分) A. 适合跨层级传递 props(避免 props drilling) B. 每次 Context 值变化时,所有消费组件都会重渲染 C. 可以通过 useContext Hook 或 Consumer 组件使用 D. 不能与类组件一起使用 **正确答案:** D **作答答案:** D ✅ > [!TIP] 🔑 Context 常见误区 > Context 本身**不优化性能**——值变化时所有 consumer 都会重渲染。应结合 `useMemo` 分割 context 或将消费逻辑抽取到自定义 Hook 中以缩小更新范围。 --- #### 11. Fragment 主要作用 【单选题】在 React 中,Fragment 的主要作用是?(3分) A. 为组件添加额外的 DOM 节点 B. 替代 div 减少冗余 DOM 层级 C. 优化组件的渲染性能 D. 包裹多个子元素且不添加额外节点 **正确答案:** D **作答答案:** D ✅ > [!NOTE] ℹ️ 两种写法 > - 完整语法:`` 或 `` > - 简写语法:`<>...` (不支持 key 属性) --- #### 12. 按需加载 【单选题】React Router 中如何实现按需加载?(3分) A. 使用 React.lazy 和 React.Suspense 实现 B. 使用 webpack 的 Code Splitting 实现 C. 使用 React Router 自带的按需加载功能实现 D. 使用 ES6 的 import() 函数实现 **正确答案:** A **作答答案:** A ✅ --- #### 13. 父向子传数据 【单选题】在 React 中,如何实现父组件向子组件传递数据?(3分) A. 使用 state 属性 B. 使用 props 属性 C. 使用 context 属性 D. 使用 ref 属性 **正确答案:** B **作答答案:** B ✅ --- #### 14. 兄弟组件通信 【单选题】在 React 中,实现兄弟组件之间的通信,相对来说下面哪个比较合适?(3分) A. 使用 props B. 使用 state C. 使用 context D. 使用 Redux **正确答案:** C **作答答案:** D --- #### 15. 子传父 【单选题】子传父在 React 中通常通过什么实现?(3分) A. 回调函数 B. 事件 C. 状态提升 D. 上升至共同的父组件 **正确答案:** A **作答答案:** A ✅ --- #### 16. React.createElement 【单选题】React.createElement 用于做什么?(3分) A. 创建类组件实例 B. 创建函数组件实例 C. 创建 DOM 元素 D. 创建 React 元素 **正确答案:** D **作答答案:** D ✅ --- #### 17. 获取 DOM 引用 【单选题】如果需要在 React 组件中保持对 DOM 元素的引用,应该使用哪个钩子?(3分) A. useState B. useEffect C. useRef D. useReducer **正确答案:** C **作答答案:** C ✅ --- #### 18. useState 返回值 【单选题】在 React 中,useState 钩子的返回值包含什么?(3分) A. 一个包含状态和更新状态的函数 B. 一个包含副作用和清理副作用的函数 C. 一个包含 DOM 引用和更新 DOM 的方法 D. 一个包含生命周期方法的对象 **正确答案:** A **作答答案:** A ✅ > [!NOTE] ℹ️ 解构方式 > ```js > const [count, setCount] = useState(0); > // ↑ 更新函数(非 setter) > // ↑ 当前状态值 > ``` --- #### 19. useEffect 清除副作用 【单选题】在 React 中,如何清除 useEffect 钩子中设置的副作用?(3分) A. 返回一个函数 B. 使用 useRef C. 使用 useState D. 使用 useMemo **正确答案:** A **作答答案:** A ✅ --- #### 20. useEffect 主要用途 【单选题】在 React 中,useEffect 钩子的主要用途是什么?(3分) A. 声明性地处理组件的副作用 B. 声明性地处理组件的样式 C. 声明性地处理组件的事件 D. 声明性地处理组件的渲染 **正确答案:** A **作答答案:** A ✅ --- #### 21. 动态参数写法 【单选题】在路由路径中定义动态参数(如用户 ID)的正确写法是?(3分) A. `/user?id=123` B. `/user/:id` C. `/user/{id}` D. `/user[id]` **正确答案:** B **作答答案:** B ✅ --- #### 22. 路由匹配组件 【单选题】以下哪个组件用于定义路由匹配规则?(3分) A. Link B. NavLink C. Route D. Switch **正确答案:** C **作答答案:** C ✅ --- #### 23. 全局状态管理适用场景 【单选题】以下哪种场景最适合使用全局状态管理库(如 Redux)?(3分) A. 多个不相关组件共享复杂状态 B. 单一组件内部状态管理 C. 兄弟组件简单数据传递 D. 跨页面数据持久化 **正确答案:** A **作答答案:** A ✅ > [!TIP] 🔑 选型决策树 > > ```mermaid > graph LR > A[需要共享状态?] -->|否| B[用 useState/useRef] > A -->|是| C{是否在同一个组件树中?} > C -->|是| D[父→子 props / 状态提升] > C -->|否| E{涉及组件数量?} > E -->|少且相邻| F[Context] > E -->|多且不相关| G[Redux/Zustand] > ``` --- #### 24. Redux reducer 【单选题】在 React-Redux 中,什么是 reducer?(3分) 以下哪个描述最准确地解释了 React-Redux 中的 reducer? A. reducer 是一个纯函数,它接收一个先前的状态和一个 action,返回一个新的状态 B. reducer 是一个对象,它定义了应用程序的状态和行为 C. reducer 是一个数组,它包含了应用程序的所有状态 D. reducer 是一个类,它封装了应用程序的所有逻辑 **正确答案:** A **作答答案:** A ✅ --- #### 25. Redux action 【单选题】在 React-Redux 中,什么是 action?(3分) 以下哪个描述最准确地解释了 React-Redux 中的 action? A. action 是一个对象,它描述了发生了什么事件,它包含了 type 和 payload 属性 B. action 是一个函数,它定义了应用程序的行为 C. action 是一个数组,它包含了应用程序的所有状态 D. action 是一个类,它封装了应用程序的所有逻辑 **正确答案:** A **作答答案:** A ✅ --- #### 26. Action 与 Reducer 关系 【单选题】Redux 中的 action 和 reducer 有什么关系?(3分) 在 Redux 中,action 和 reducer 是组成状态管理的两个重要概念。请问,action 和 reducer 之间的关系是什么? A. action 用于描述应用状态的结构,reducer 用于修改状态 B. action 用于存储应用状态的数据,reducer 用于获取状态 C. action 用于触发状态修改,reducer 用于响应状态修改 D. action 用于监听状态变化,reducer 用于修改状态 **正确答案:** C **作答答案:** C ✅ > [!NOTE] ℹ️ Redux 数据流全景 > > ```mermaid > flowchart LR > UI["组件发起 dispatch"] -->|Action| STORE["Store"] > STORE -->|派发| REDUCER["Reducer 纯函数"] > REDUCER -->|new State| STORE > STORE -->|订阅更新| UI > ``` --- #### 27. Redux 异步操作 【单选题】在 Redux 中,有时需要进行异步操作,比如发送网络请求。请问,以下哪个中间件可以用来实现异步操作?(3分) A. redux-thunk B. redux-saga C. redux-logger D. redux-observable **正确答案:** A **作答答案:** A ✅ > [!TIP] 🔑 常用中间件对比 > > | 中间件 | 特点 | > |--------|------| > | redux-thunk | 轻量,返回函数的 action creator,适合简单异步 | > | redux-saga | 基于 Generator,适合复杂异步流程控制 | > | redux-logger | 日志打印,不参与业务逻辑 | > | redux-observable | 基于 RxJS,响应式编程风格 | --- #### 28. Axios 拦截器问题 【单选题】axios 封装请求拦截器时,以下代码有什么问题?(3分) ```js import axios from 'axios'; const instance = axios.create({ baseURL: 'https://api.example.com', timeout: 5000 }); // 请求拦截器 instance.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => Promise.reject(error) ); // 响应拦截器 instance.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { // token过期,跳转登录 window.location.href = '/login'; } return Promise.reject(error); } ); export default instance; ``` A. 没有问题,这是标准的 axios 封装 B. token 过期时应该先尝试刷新 token C. 直接操作 window.location 会导致 SPA 路由失效 D. B 和 C 都对 **正确答案:** D **作答答案:** D ✅ > [!TIP] 🔑 最佳实践 > - **刷新 Token**:使用 refresh_token 流程重试原请求,而非直接跳登录 > - **SPA 导航**:使用 `router.push('/login')` 而非 `window.location.href`,避免整页刷新丢失状态 --- #### 29. React.memo + 新对象引用 【单选题】在使用 `React.memo` 时,如果父组件传递一个对象作为 `prop`,以下哪种情况可能导致子组件仍然不必要的重新渲染?(3分) A. 父组件传递的是一个原始类型的值,例如数字 B. 父组件传递的是一个使用 `useMemo` 缓存的对象 C. 子组件内部的状态发生了变化 D. 父组件每次渲染时都创建了一个新的对象 **正确答案:** D **作答答案:** D ✅ --- ### 二、多选题 #### 1. 大数据渲染优化 【多选题】项目中遇到大量数据渲染导致页面卡顿,以下哪些优化方案有效?(4分) ```js // 方案A:虚拟滚动 // 方案B:分页加载 const pageSize = 20; const currentPage = ref(1); const displayList = computed(() => { return list.slice(0, currentPage.value * pageSize); }); // 方案C:时间分片 function renderWithTimeSlice(list) { let current = 0; const pageSize = 50; function render() { const fragment = document.createDocumentFragment(); const end = Math.min(current + pageSize, list.length); for (let i = current; i < end; i++) { const item = createListItem(list[i]); fragment.appendChild(item); } container.appendChild(fragment); current = end; if (current < list.length) { requestAnimationFrame(render); } } render(); } // 方案D:Web Worker 处理数据 const worker = new Worker('process-data.js'); worker.postMessage(rawData); worker.onmessage = (e) => { renderList(e.data); }; ``` A. 只渲染可视区域 B. 按需加载 C. 避免长时间阻塞 D. 数据处理放在 Worker **正确答案:** ABCD **作答答案:** ABCD ✅ > [!NOTE] ℹ️ 各方案定位 > - **虚拟滚动**:DOM 节点数固定,滚动时复用节点 > - **分页加载**:减少一次性渲染量,配合无限滚动体验更佳 > - **时间分片**:将任务拆入微任务队列,避免主线程长时间占用 > - **Web Worker**:计算密集型任务放后台线程,主线程只管渲染 --- #### 2. Vite 打包优化 【多选题】Vite 项目打包优化时,以下哪些配置是有效的?(4分) ```js // vite.config.js export default { build: { rollupOptions: { output: { // 配置A:手动分包 manualChunks: { 'vendor': ['vue', 'vue-router', 'pinia'], 'ui': ['element-plus'] }, // 配置B:第三方库按包名分组 manualChunks(id) { if (id.includes('node_modules')) { return id.toString().split('node_modules/')[1].split('/')[0]; } } } }, // 配置C:启用压缩 minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true } }, // 配置D:调整 chunk 大小警告阈值 chunkSizeWarningLimit: 2000 } }; ``` A. 配置 A 可以有效减少首屏加载时间 B. 配置 B 比 A 更灵活,能自动分离第三方库 C. 配置 C 能减小包体积,但会增加构建时间 D. 配置 D 只是隐藏警告,不能真正优化性能 **正确答案:** ABCD **作答答案:** ABC > [!FAIL] 💔 漏选分析 > **配置 D 虽不直接优化性能,但它影响开发体验**——合理设置可避免频繁干扰,让真正的警告突出显示。这是一项工程化最佳实践。建议全选。 --- #### 3. 大列表渲染最佳实践 【多选题】以下性能优化的最佳实践是什么?(4分) ```js // 场景:大列表渲染 const List = ({ items }) => { return (
{items.map((item, index) => (
handleClick(item)}> {item.name}
))}
); }; ``` A. 使用 item.id 作为 key 而非 index B. 使用虚拟滚动 C. 使用 useMemo 包裹 items D. onClick 使用 useCallback **正确答案:** ABD **作答答案:** ABD ✅ > [!TIP] 🔑 为什么不选 C? > `useMemo` 包裹 items 并不能直接优化列表渲染性能——items 来自 props,其变化由父组件决定。关键在于 **key 稳定性**(A)、**渲染策略**(B)和**事件处理器引用稳定**(D)。 --- #### 4. useEffect + 键盘事件监听 【多选题】关于下面代码说法正确的是?(4分) ```js useEffect(() => { const onKeyDown = (e) => { const userAgent = navigator.userAgent; const isMac = /Macintosh|MacIntel|MacPPC|Mac68K/.test(userAgent); if ((isMac && e.metaKey && e.key.toLowerCase() === 'k') || (!isMac && e.ctrlKey && e.key.toLowerCase() === 'k')) { e.preventDefault(); inputRef.current?.focus(); } }; window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }, []); ``` A. 在 Mac 设备上按下 Command+K 键,或在非 Mac 设备上按下 Ctrl+K 键时,让 inputRef 对应的输入框获取焦点 B. 该 useEffect 仅在组件挂载时执行一次,因为其依赖数组为空 C. 清理函数会在组件卸载时执行,用于移除添加的 keydown 事件监听,避免内存泄漏 D. 清理函数的主要作用是阻止键盘事件的默认行为 **正确答案:** ABC **作答答案:** ABCD > [!FAIL] 💔 易错点 > **D 是错误的**:`e.preventDefault()` 阻止的是浏览器默认行为(如 Cmd+K 的搜索框聚焦),而清理函数 `removeEventListener` 的作用是**移除事件监听器本身**,二者职责完全不同。 --- #### 5. GIF 埋点 vs AJAX 【多选题】相比较 AJAX,有些公司前端监控都喜欢用 GIF 做埋点上报,以下哪些是 GIF 的优势?(4分) ```js // GIF 方案 const report = (data) => { const img = new Image(); img.src = `https://log.example.com/track.gif?data=${encodeURIComponent(JSON.stringify(data))}`; }; // Ajax 方案 const report = (data) => { fetch('https://log.example.com/track', { method: 'POST', body: JSON.stringify(data) }); }; ``` A. GIF 请求不需要等待响应,发送即走 B. GIF 可以跨域,不受 CORS 限制 C. 页面卸载时 GIF 请求不会被取消 D. GIF 体积最小(1x1 像素) **正确答案:** ABD **作答答案:** ABD ✅ > [!NOTE] ℹ️ 为什么不用 C? > 页面卸载(unload)时,`navigator.sendBeacon()` 可以保证请求发送,但普通 `` 请求**同样会被取消**,因为浏览器不再维护连接。GIF 的优势在于**零 CORS 限制**(资源加载不走 XHR 同源策略)和**极简体积**。 --- ## 关联笔记 - [[hhs/EXAM/Week03]](推测:前三次课相关考题) - [[hhs/EXAM/README]](考试文档索引)