משחקים עם Antd - קומפוננטת עץ אסינכרונית
ספריית antd היא אחת מספריות ה UI הפופולריות בריאקט ואני לגמרי מבין למה. ה API מאוד פשוט, יש הרבה קומפוננטות שנראות טוב ואפשר להרחיב אותה יחסית בקלות. הנה דוגמה לקומפוננטה של העץ שלהם ולשימוש אסינכרוני בו כדי לייצג מידע שנטען משרת.
1. מה אנחנו בונים
הדוגמה של היום תשתמש בקומפוננטת Tree של antd כדי להראות עץ שמציג את כל הסרטים של מלחמת הכוכבים, וכשלוחצים על סרט הוא יטען את שמות כל הדמויות באותו סרט ויציג אותם בתור ילדים בעץ. ככה זה נראה ועובד בקודסנדבוקס:
2. איך בנוי עץ
בשביל להציג עץ עם קומפוננטת עץ של antd אנחנו צריכים מבנה נתונים שמכיל מערך של אוביקטים, כאשר לכל אוביקט יש מאפיין key ייחודי, מאפיין title אופציונאלי שמכיל את הטקסט של האוביקט ומאפיין children אופציונאלי שמכיל את כל הילדים. אם אתם יודעים שלא יהיו ילדים לצומת מסוים אפשר להוסיף מאפיין isLeaf עם הערך true. האוביקט הבא מייצג עץ לדוגמה:
[
{ key: 'a', title: 'A', children: [] },
{ key: 'b', title: 'B', children: [
{ key: 'bb', title: 'B-1', isLeaf: true },
{ key: 'bc', title: 'B-2', isLeaf: true },
}}
];
בניסוי שלי רציתי להראות עץ של סרטים כך שברמה החיצונית ביותר תהיה רשימה של סרטים ולכל סרט הילדים יהיו הדמויות שמופיעות באותו סרט. בשביל לבנות את העץ אני מתחיל עם רשימת הסרטים שנמצאת בקישור https://swapi.py4e.com/api/films/, ואז מפעיל את הפונקציה הבאה כדי להפוך את הרשימה לעץ:
function initTree(films) {
return films.map((film) => ({
key: `film-${film.episode_id}`,
title: film.title,
children: film.characters.map((url) => ({
key: url,
isLeaf: true,
})),
}));
}
3. מה קורה כשמשתמש לוחץ על סרט
עכשיו שיש לנו את הרמה של הסרטים אפשר להמשיך ולראות מה קורה כשמשתמש לוחץ על סרט. התשובה היא שאנחנו צריכים ליצור את כל נתוני העץ מחדש עם העץ המעודכן. בעבודה עם מבני נתונים מורכבים שצריכים להישמר בסטייט אני אוהב להשתמש בספריה immer שנותנת לי לגשת למבנה הנתונים כמו שהייתי כותב JavaScript רגיל והופכת את הקוד לגישה Immutable, כלומר מחזירה מבנה נתונים חדש רק עם השינויים שביצעתי. בעזרת immer כתבתי את שתי הפונקציות הבאות שלוקחות עץ ומפתח של סרט וממלאות את הילדים של הסרט בנתוני הדמויות האמיתיות מאותו הסרט:
async function loadCharacter(node) {
const res = await fetch(node.key);
const data = await res.json();
return { key: node.key, title: data.name, isLeaf: true };
}
async function loadChildren(treeData, filmKey, children) {
return produce(treeData, async (draft) => {
const node = draft.find((n) => n.key === filmKey);
node.children = await Promise.all(children.map(loadCharacter));
});
}
זה עובד כי שמרנו כבר קודם ב children של כל סרט רשימה של ילדים שהמפתח של כל אחד הוא בדיוק ה URL של הדמות. אני משתמש כאן ב Promise.all ולא בלולאה כדי שכל בקשות הרשת יישלחו במקביל ולא אחת אחרי השניה.
החלק האחרון שנשאר בקוד הוא הקומפוננטה עצמה שמדביקה את כל הפונקציות שכתבנו והיא נראית כך:
import React, { useState, useEffect } from 'react';
import produce from "immer"
import { Tree } from 'antd';
import 'antd/dist/antd.css';
export default function Demo() {
const [treeData, setTreeData] = useState(initTreeData);
useEffect(() => {
async function flow() {
const res = await fetch('https://swapi.py4e.com/api/films/');
const data = await res.json();
setTreeData(initTree(data.results));
};
flow();
}, []);
const onLoadData = async ({ key, children }) => {
const newTreeData = await loadChildren(treeData, key, children);
setTreeData(newTreeData);
};
return (
<Tree loadData={onLoadData} treeData={treeData} />
);
}
4. איפה ללמוד עוד
לעץ של antd יש עוד המון יכולות ואפשר לקרוא עליהן עם דוגמאות בדף התיעוד של העץ כאן: https://ant.design/components/tree/
והחלק היותר מעניין בדף הוא רשימת הקומפוננטות בצד שמאל. פשוט תלחצו על כל קומפוננטה מהרשימה כדי לראות מה היא עושה ואיך להשתמש בה.