场景技巧 第二部分
如何处理前端请求的竞态问题?
前端请求的竞态问题,是指多个前端请求同时发起,导致服务器资源竞争,导致请求响应延迟,甚至出现请求超时的情况。例如搜索框场景,在用户快速输入的情况下,A 请求的结果还未返回,B 请求就已经发起,那么当 A 请求结果拿到时就变成 B 的结果了。
处理方式如下:
- 取消上一个请求,防止结果不匹配。主要是利用AbortController API在请求发起时来取消上一个未完成的请求,以此来保证请求的顺序性。
let controller;
async function search(query) {
if (controller) {
controller.abort(); // 取消上一个请求
}
controller = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
const data = await response.json();
// 处理并展示结果
} catch (error) {
if (error.name !== "AbortError") {
console.error("请求失败:", error);
}
}
}
// 示例:监听输入事件
const input = document.querySelector("input");
input.addEventListener("input", (e) => {
search(e.target.value);
});
2.使用请求标识。为每个请求分配唯一标识,并在请求响应时检查是否与当前请求匹配,只有最新的结果会被处理,以此来避免请求结果的错乱。
let latestRequestId = 0;
async function search(query) {
const requestId = ++latestRequestId;
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
if (requestId === latestRequestId) {
// 处理并展示结果
}
}
// 示例:监听输入事件
const input = document.querySelector("input");
input.addEventListener("input", (e) => {
search(e.target.value);
});
3.请求防抖。在用户输入时,延迟请求的发送,减少请求发送的频率。
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
async function search(query) {
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
// 处理并展示结果
}
const debouncedSearch = debounce(search, 300);
// 示例:监听输入事件
const input = document.querySelector("input");
input.addEventListener("input", (e) => {
debouncedSearch(e.target.value);
});
推荐方案是结合请求取消和防抖,这个适用于多数的搜索场景,简单代码示例如下:
let controller;
let timeoutId;
function debounce(func, delay) {
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
async function search(query) {
if (controller) {
controller.abort();
}
controller = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
});
const data = await response.json();
// 处理并展示结果
} catch (error) {
if (error.name !== "AbortError") {
console.error("请求失败:", error);
}
}
}
const debouncedSearch = debounce(search, 300);
// 示例:监听输入事件
const input = document.querySelector("input");
input.addEventListener("input", (e) => {
debouncedSearch(e.target.value);
});
如果处理 TypeScript 项目中复杂的类型定义?
对于TypeScript 项目中复杂的类型定义,可以使用一些高级特性来处理,例如泛型、条件类型、类型推断等。
// 使用条件类型实现类型安全的API响应
type ApiResponse<T> = {
success: true;
data: T;
} | {
success: false;
error: string;
};
// 泛型函数处理API响应
function handleResponse<T>(response: ApiResponse<T>): T {
if (response.success) {
return response.data;
}
throw new Error(response.error);
}
//实用工具类: 部分可选
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
如何制定大型项目的 CSS 架构方案?
1.首先明确架构的设计原则。
可预测性原则:样式行为应直观可预期。
可复用性原则:避免重复,建立可复用模式。
可维护性原则:易于修改和扩展。
低耦合性原则:组件样式独立,减少相互影响。
性能优先原则:控制选择器复杂度和文件体积。
2.分层架构设计。将样式分层,例如基础层、布局层、组件层、页面层等。
基础层:包含设计变量、CSS Reset、基础样式和通用工具类等。
布局层:包含网格系统(Grid/Flexbox)、主要的页面结构、响应式断点管理等。
组件层:原子组件(按钮、输入框等)、组合组件(卡片、导航栏等)、业务组件(表单、表格等)以及状态类(激活、禁用、选中等)。
页面层:特定页面的特定样式。注意:尽量少用页面特定样式。
3.技术选型策略。比较预处理器、CSS模块化、原子化CSS等技术的适用场景和优缺点,选择合适的技术方案。
4.文件组织结构。
styles/
│
├── base/ # 基础层
│ ├── _variables.scss
│ ├── _reset.scss
│ └── _typography.scss
│
├── layout/ # 布局层
│ ├── _grid.scss
│ └── _containers.scss
│
├── components/ # 组件层
│ ├── _buttons.scss
│ └── _card.scss
│
├── utilities/ # 工具类
│ ├── _spacing.scss
│ └── _display.scss
│
├── pages/ # 页面层(尽量少用)
│ └── _product.scss
│
└── main.scss # 主入口文件
5.性能优化策略。使用打包工具拆分CSS、移除未使用的CSS。
6.团队协作规范。
命名约定:遵循BEM命名规范,例如
.block__element--modifier
。样式编写顺序,定位->盒模型->排版->视觉->动画。
避免过度嵌套(例如Sass中不超过3层),避免使用
!important
,选择器特异性不超过(0,2,0)
(即同时有两个类选择器作用于同一个元素)。