This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-15 16:26:14 +08:00

805 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 <button onClick={handleClick}>{count}</button>;
}
```
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` + `<Routes>` + `<Route>`。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] ℹ️ 两种写法
> - 完整语法:`<React.Fragment>` 或 `<Fragment>`
> - 简写语法:`<>...</>` (不支持 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:虚拟滚动
<virtual-list
:data="list"
:item-height="80"
:buffer-scale="2"
/>
// 方案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 (
<div>
{items.map((item, index) => (
<div key={index} onClick={() => handleClick(item)}>
{item.name}
</div>
))}
</div>
);
};
```
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()` 可以保证请求发送,但普通 `<img>` 请求**同样会被取消**,因为浏览器不再维护连接。GIF 的优势在于**零 CORS 限制**(资源加载不走 XHR 同源策略)和**极简体积**。
---
## 关联笔记
- [[hhs/EXAM/Week03]](推测:前三次课相关考题)
- [[hhs/EXAM/README]](考试文档索引)