文章中英模式
布鲁斯前端JS面试题目 - 实现数据获取与 UI 更新
学习如何高效实现前端数据获取与 UI 更新流程,掌握异步数据处理、加载状态管理、错误处理及优化渲染的最佳实践。
文章中英模式
懒得看文章?那就来看视频吧
数据获取与 UI 更新概述
在前端开发中,从服务器获取数据并更新用户界面是一个基本但关键的任务。这个过程涉及异步请求处理、加载状态管理、错误处理以及高效的 UI 更新策略。设计良好的数据获取与 UI 更新流程是建立良好用户体验的基础。
主要挑战与考量
- 1. 异步处理:正确处理网络请求的异步性质
- 2. 加载状态:在数据加载期间提供适当的视觉反馈
- 3. 错误处理:优雅地处理可能出现的网络或数据错误
- 4. 数据转换:将API响应转换为适合UI呈现的格式
- 5. 优化渲染:避免不必要的渲染以提高性能
- 6. 数据缓存:实现适当的缓存策略以减少重复请求
基本的数据获取与 UI 更新实现
以下是一个基本的数据获取与 UI 更新示例,使用原生 JavaScript 和 DOM API:
// 基本的数据获取与 UI 更新实现
document.addEventListener('DOMContentLoaded', function() {
const userListElement = document.getElementById('user-list');
const loadingElement = document.getElementById('loading');
const errorElement = document.getElementById('error');
// 显示加载中的状态
function showLoading() {
loadingElement.style.display = 'block';
errorElement.style.display = 'none';
userListElement.innerHTML = '';
}
// 显示错误信息
function showError(message) {
loadingElement.style.display = 'none';
errorElement.style.display = 'block';
errorElement.textContent = message || '发生错误,请稍后再试';
userListElement.innerHTML = '';
}
// 显示数据
function renderUsers(users) {
loadingElement.style.display = 'none';
errorElement.style.display = 'none';
userListElement.innerHTML = '';
if (users.length === 0) {
userListElement.innerHTML = '<p>没有找到用户</p>';
return;
}
const userElements = users.map(user => {
return `
<div class="user-card">
<h3>${user.name}</h3>
<p>Email: ${user.email}</p>
</div>
`;
});
userListElement.innerHTML = userElements.join('');
}
// 获取用户数据
async function fetchUsers() {
try {
showLoading();
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const users = await response.json();
renderUsers(users);
} catch (error) {
console.error('获取用户数据失败:', error);
showError('无法加载用户数据: ' + error.message);
}
}
// 添加重试按钮事件处理
document.getElementById('retry-button').addEventListener('click', fetchUsers);
// 初始数据获取
fetchUsers();
});
对应的 HTML 结构如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<style>
.user-card {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 10px;
border-radius: 4px;
}
#loading, #error {
display: none;
padding: 15px;
}
#error {
color: red;
}
</style>
</head>
<body>
<h1>用户列表</h1>
<div id="loading">加载中,请稍候...</div>
<div id="error"></div>
<button id="retry-button">重试</button>
<div id="user-list"></div>
<script src="app.js"></script>
</body>
</html>
React 虚拟列表简单实现
以下是一个简洁的 React 虚拟列表实现,只渲染可视区域内的元素:
import React, { useState, useEffect, useRef } from 'react';
const VirtualList = ({ items, itemHeight, containerHeight = 400 }) => {
// 追踪滚动位置的状态
const [scrollTop, setScrollTop] = useState(0);
// 参考容器DOM元素
const containerRef = useRef(null);
// 用于防止过度渲染的标记
const ticking = useRef(false);
// 计算可见项目数量(多渲染2个项目作为缓冲)
const visibleCount = Math.ceil(containerHeight / itemHeight) + 2;
// 计算开始索引(减1是为了预先渲染一个项目)
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - 1);
// 计算结束索引(确保不超出数组范围)
const endIndex = Math.min(items.length - 1, startIndex + visibleCount);
// 滚动事件处理函数,使用requestAnimationFrame优化性能
const onScroll = (e) => {
const newTop = e.target.scrollTop;
if (!ticking.current) {
requestAnimationFrame(() => {
setScrollTop(newTop);
ticking.current = false;
});
ticking.current = true;
}
};
return (
<div
ref={containerRef}
onScroll={onScroll}
style={{ height: containerHeight, overflow: 'auto', position: 'relative' }}
>
{/* 创建一个与所有项目总高度相同的容器 */}
<div style={{ height: items.length * itemHeight }}>
{/* 只渲染可见范围内的项目 */}
{items.slice(startIndex, endIndex + 1).map((item, i) => {
const index = startIndex + i;
return (
<div
key={index}
style={{
position: 'absolute', // 使用绝对定位
top: index * itemHeight, // 根据索引计算正确的位置
height: itemHeight,
width: '100%',
}}
>
{item}
</div>
);
})}
</div>
</div>
);
};
// 使用示例
const App = () => {
// 生成1000个测试项目
const items = Array.from({ length: 1000 }, (_, i) => (
<div style={{ padding: '10px', borderBottom: '1px solid #eee' }}>
项目 #{i + 1}
</div>
));
return (
<div>
<h2>虚拟列表示例</h2>
<VirtualList
items={items}
itemHeight={50}
containerHeight={300}
/>
</div>
);
};
核心原理:
- 1. 只渲染可见区域的项目
- 2. 使用绝对定位放置元素
- 3. 容器高度固定,内容高度等于所有项目总高度
- 4. 根据滚动位置动态计算显示的项目