805 lines
22 KiB
Markdown
805 lines
22 KiB
Markdown
---
|
||
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]](考试文档索引)
|