前端面試通常完全不關心資料結構與演算法(DSA)。
而對於那些還記得在學校或大學學習 DSA 的我們來說,所有的例子都感覺非常純粹演算法化(這是有原因的),但幾乎沒有任何例子或指導,告訴我們我們日常使用的產品如何利用這個概念。
“我真的需要這個嗎?”
你一定問過自己很多次,對吧?👀
以下是一些你今天可以在你的 React 應用中利用的資料結構!👇
相關閱讀:https://dev.to/middleware/going-from-sde1-to-sde2-and-beyond-what-it-actually-takes-1cld
陣列在 React 中隨處可見。如果你需要幫助理解 .map()
或 .filter()
是如何工作的,那麼你可能稍微看這篇文章看得太早了!但不要擔心—一旦你習慣了這些陣列方法,你會發現它們對於渲染列表、管理元件狀態和轉換資料是多麼重要。
在 React 應用中,當你處理大量實體(如用戶或帖子)時,將你的資料標準化為物件(雜湊映射)能讓讀取和更新變得更加高效。與其使用深度嵌套的結構,你可以根據 ID 對實體進行映射。
範例:根據 ID 從標準化儲存中讀取
const postsById = {
1: { id: 1, title: '第一篇文章', content: '第一篇文章的內容' },
2: { id: 2, title: '第二篇文章', content: '第二篇文章的內容' }
};
const postIds = [1, 2];
function PostList() {
return (
<div>
{postIds.map(id => (
<Post key={id} post={postsById[id]} />
))}
</div>
);
}
function Post({ post }) {
return (
<div>
<h2>{post.title}</h2>
<p>{post.content}</p>
</div>
);
}
這種模式允許高效的資料存取,特別是當大型資料集中的更新或讀取需要快速進行,而不必重新渲染整個集合。
雙向鏈結串列在你需要來自前一個和下一個元素的上下文時非常有用—想像一下在 照片畫廊 中導航,每張圖片顯示其鄰近的圖片以供參考。我們將目前的節點直接儲存在元件狀態中,而不是使用索引。
範例:帶上下文的元素間導航的雙向鏈結串列
class Node {
constructor(value) {
this.value = value;
this.next = null;
this.prev = null;
}
}
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
}
add(value) {
const newNode = new Node(value);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
}
}
const imageList = new DoublyLinkedList();
imageList.add({ id: 1, src: 'image1.jpg', alt: '第一張圖片' });
imageList.add({ id: 2, src: 'image2.jpg', alt: '第二張圖片' });
imageList.add({ id: 3, src: 'image3.jpg', alt: '第三張圖片' });
function Gallery() {
const [currentNode, setCurrentNode] = useState(imageList.head);
return (
<div>
{currentNode.prev && (
<img src={currentNode.prev.value.src} alt={currentNode.prev.value.alt} className="prev-image" />
)}
<img src={currentNode.value.src} alt={currentNode.value.alt} className="main-image" />
{currentNode.next && (
<img src={currentNode.next.value.src} alt={currentNode.next.value.alt} className="next-image" />
)}
<div>
<button onClick={() => setCurrentNode(currentNode.prev)} disabled={!currentNode.prev}>
上一張
</button>
<button onClick={() => setCurrentNode(currentNode.next)} disabled={!currentNode.next}>
下一張
</button>
</div>
</div>
);
}
在這個 React 元件中:
堆疊允許你使用後進先出(LIFO)邏輯高效地管理撤銷/重做操作。通過使用不可變操作(concat
、slice
),我們可以確保狀態保持不變。
範例:利用不可變的 push
和 pop
進行撤銷/重做
const [undoStack, setUndoStack] = useState([]);
const [redoStack, setRedoStack] = useState([]);
const [formState, setFormState] = useState({ name: '', email: '' });
const updateForm = (newState) => {
setUndoStack(prev => prev.concat([formState])); // 不可變推入
setRedoStack([]); // 清除重做堆疊
setFormState(newState);
};
const undo = () => {
if (undoStack.length > 0) {
const lastState = undoStack.at(-1);
setUndoStack(prev => prev.slice(0, -1)); // 不可變彈出
setRedoStack(prev => prev.concat([formState])); // 將當前狀態移至重做堆疊
setFormState(lastState);
}
};
const redo = () => {
if (redoStack.length > 0) {
const lastRedo = redoStack.at(-1);
setRedoStack(prev => prev.slice(0, -1)); // 不可變彈出
setUndoStack(prev => prev.concat([formState])); // 將當前狀態推入撤銷堆疊
setFormState(lastRedo);
}
};
隊列以先進先出(FIFO)的方式運作,非常適合確保像 API 呼叫或通知這樣的任務按照正確的順序處理。
範例:排隊 API 呼叫
const [apiQueue, setApiQueue] = useState([]);
const enqueueApiCall = (apiCall) => {
setApiQueue(prevQueue => prevQueue.concat([apiCall])); // 不可變推入
};
const processQueue = () => {
if (apiQueue.length > 0) {
const [nextCall, ...restQueue] = apiQueue;
nextCall().finally(() => setApiQueue(restQueue)); // 不可變彈出
}
};
樹結構在處理嵌套元件時非常常見,如評論樹、資料夾結構或選單。
範例:遞迴呈現評論樹
const commentTree = {
id: 1,
text: "第一條評論",
children: [
{ id: 2, text: "對第一條評論的回覆", children: [] },
{ id: 3, text: "另一條回覆", children: [{ id: 4, text: "嵌套回覆" }] }
]
};
function Comment({ comment }) {
return (
<div>
<p>{comment.text}</p>
{comment.children?.map(child => (
<div style={{ paddingLeft: '20px' }} key={child.id}>
<Comment comment={child} />
</div>
))}
</div>
);
}
另一篇可能對你有用的熱門文章:https://dev.to/middleware/write-less-fix-never-the-art-of-highly-reliable-code-5a0i
範例 1:在多個視圖間路由
你可以將網頁間的路由表示為圖,以確保單頁應用中的靈活導航路徑。
const routesGraph = {
home: ['about', 'contact'],
about: ['home', 'team'],
contact: ['home'],
};
function navigate(currentRoute, targetRoute) {
if (routesGraph[currentRoute].includes(targetRoute)) {
console.log(`從 ${currentRoute} 導航到 ${targetRoute}`);
} else {
console.log(`從 ${currentRoute} 到 ${targetRoute} 的路由無效`);
}
}
範例 2:用戶關係建模
圖結構非常適合建模社交連結或任何多個實體互相連接的關係。
const usersGraph = {
user1: ['user2', 'user3'],
user2: ['user1', 'user4'],
user3: ['user1'],
user4: ['user2']
};
function findConnections(userId) {
return usersGraph[userId] || [];
}
console.log(findConnections('user1')); // 輸出: ['user2', 'user3']
注意:我們使用圖來顯示 Middleware 中的審核者依賴。
那些 DSA 課程在當時或許感覺抽象,但資料結構無時無刻不在推動著你在 React 中的世界。
物件、堆疊、隊列、鏈結串列、樹和圖不僅僅是理論—它們是你每天所構建的乾淨、高效且可擴展應用的基礎。
所以下次當你在隊列中管理狀態或處理複雜的 UI 邏輯時,記住:你從學校以來一直在為這個訓練。💪
告訴我你最常使用哪些資料結構!