文章中英模式
布魯斯前端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-TW">
<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. 根據滾動位置動態計算顯示的項目