יום 21: סוכן קולי
אני רוצה לסיים את סדרת 21 ימים עם OpenAI Agents עם דוגמה לפיתוח סוכן קולי. הספריה OpenAI Agents תומכת בשני סוגים של סוכנים קוליים.
הסוג הראשון הוא הרחבה של סוכני הטקסט. סוכן זה שנקרא בתיעוד Voice Agent לוקח קלט קולי מהמשתמש, מתמלל אותו לטקסט, מעביר את הטקסט לסוכן כדי להמשיך את השיחה ואז לוקח את הטקסט שהסוכן יצר והופך אותו לפלט קולי. מדובר בשילוב בין מנוע השלמת טקסט למנועי פיענוח וייצור דיבור.
הסוג השני, עליו נדבר בפוסט זה, נקרא בתיעוד סוכן Realtime ופה כבר מדובר בסוכן קולי אמיתי. זה סוכן שלוקח קובץ אודיו ומשלים אותו עם מודל שמאומן על קבצי אודיו. כלומר יש לנו מודל דיבור מיוחד שיודע לענות לפרומפטים קוליים. עבודה עם מודל כזה מהירה יותר ומאפשרת הבנה טובה יותר של ניואנסינים של השפה.
איך יוצרים סוכן זמן אמת
התוכנית שנבנה היום היא דוגמה קטנה לסוכן קולי בזמן אמת. התוכנית מציגה דף HTML עם כפתור, לחיצה על הכפתור מאפשרת למשתמש להתחיל שיחה עם הסוכן, כל הודעה מתומללת על ידי המודל וגם הטקסט וגם האודיו נשלחים חזרה לדפדפן ומוצגים על המסך.
החלק הראשון בתוכנית הוא החלק שיוצר סוכן זמן אמת וזה הקוד הבא:
פונקציית connect נקראת כשדפדפן מתחבר דרך Web Socket לשרת הפייתון שלנו. הקוד שומר את ה Web Socket בתוך מילון ואז יוצר את הסוכן. ביצירת סוכן זמן אמת אנחנו לא יכולים להעביר פרמטר model ואם רוצים מודל אחר יש להעביר אותו בתור פרמטר לבנאי של RealtimeRunner.
לאחר יצירת הסוכן הקוד יוצר session שזה הדבר שינהל את השיחה הקולית עם המשתמש.
איך לטפל בהודעה מהמשתמש
השורה האחרונה בקוד יצירת הסוכן הפעילה את הפונקציה
הפונקציה רצה על כל האירועים ב session עם async. זה נראה כמו לולאת for אבל למעשה זה יותר דומה ללולאת while, כי כל הזמן עשויים להיכנס ל session אירועים חדשים. גוף הלולאה ירוץ עבור כל אירוע חדש שנכנס ל session כל עוד ה session פתוח. תפקיד הלולאה הוא לשלוח את ההודעות ב session לדפדפן דרך ה Web Socket.
אבל איך הודעות נכנסות ל session אתם שואלים, ופה יש שתי דרכים. דרך אחת היא הודעות שנשלחות מהמשתמש. כשמשתמש שולח הודעה דרך ה web socket מופעל הקוד הבא:
אני רוצה לסיים את סדרת 21 ימים עם OpenAI Agents עם דוגמה לפיתוח סוכן קולי. הספריה OpenAI Agents תומכת בשני סוגים של סוכנים קוליים.
הסוג הראשון הוא הרחבה של סוכני הטקסט. סוכן זה שנקרא בתיעוד Voice Agent לוקח קלט קולי מהמשתמש, מתמלל אותו לטקסט, מעביר את הטקסט לסוכן כדי להמשיך את השיחה ואז לוקח את הטקסט שהסוכן יצר והופך אותו לפלט קולי. מדובר בשילוב בין מנוע השלמת טקסט למנועי פיענוח וייצור דיבור.
הסוג השני, עליו נדבר בפוסט זה, נקרא בתיעוד סוכן Realtime ופה כבר מדובר בסוכן קולי אמיתי. זה סוכן שלוקח קובץ אודיו ומשלים אותו עם מודל שמאומן על קבצי אודיו. כלומר יש לנו מודל דיבור מיוחד שיודע לענות לפרומפטים קוליים. עבודה עם מודל כזה מהירה יותר ומאפשרת הבנה טובה יותר של ניואנסינים של השפה.
איך יוצרים סוכן זמן אמת
התוכנית שנבנה היום היא דוגמה קטנה לסוכן קולי בזמן אמת. התוכנית מציגה דף HTML עם כפתור, לחיצה על הכפתור מאפשרת למשתמש להתחיל שיחה עם הסוכן, כל הודעה מתומללת על ידי המודל וגם הטקסט וגם האודיו נשלחים חזרה לדפדפן ומוצגים על המסך.
החלק הראשון בתוכנית הוא החלק שיוצר סוכן זמן אמת וזה הקוד הבא:
async def connect(self, websocket: WebSocket, session_id: str):
await websocket.accept()
self.websockets[session_id] = websocket
agent = RealtimeAgent(
name="AI Friend",
instructions=(
f"{RECOMMENDED_PROMPT_PREFIX} "
"You are a helpful agent. Be nice and chatty"
)
)
runner = RealtimeRunner(agent)
session_context = await runner.run()
session = await session_context.__aenter__()
self.active_sessions[session_id] = session
self.session_contexts[session_id] = session_context
# Start event processing task
asyncio.create_task(self._process_events(session_id))
פונקציית connect נקראת כשדפדפן מתחבר דרך Web Socket לשרת הפייתון שלנו. הקוד שומר את ה Web Socket בתוך מילון ואז יוצר את הסוכן. ביצירת סוכן זמן אמת אנחנו לא יכולים להעביר פרמטר model ואם רוצים מודל אחר יש להעביר אותו בתור פרמטר לבנאי של RealtimeRunner.
לאחר יצירת הסוכן הקוד יוצר session שזה הדבר שינהל את השיחה הקולית עם המשתמש.
איך לטפל בהודעה מהמשתמש
השורה האחרונה בקוד יצירת הסוכן הפעילה את הפונקציה
_process_events בתור משימה אסינכרונית. נמשיך לקוד פונקציה זו:async def _process_events(self, session_id: str):
try:
session = self.active_sessions[session_id]
websocket = self.websockets[session_id]
async for event in session:
event_data = await self._serialize_event(event)
print("Message received from agent - sending back to user")
print(event_data)
await websocket.send_text(json.dumps(event_data))
except Exception as e:
logger.error(f"Error processing events for session {session_id}: {e}")
הפונקציה רצה על כל האירועים ב session עם async. זה נראה כמו לולאת for אבל למעשה זה יותר דומה ללולאת while, כי כל הזמן עשויים להיכנס ל session אירועים חדשים. גוף הלולאה ירוץ עבור כל אירוע חדש שנכנס ל session כל עוד ה session פתוח. תפקיד הלולאה הוא לשלוח את ההודעות ב session לדפדפן דרך ה Web Socket.
אבל איך הודעות נכנסות ל session אתם שואלים, ופה יש שתי דרכים. דרך אחת היא הודעות שנשלחות מהמשתמש. כשמשתמש שולח הודעה דרך ה web socket מופעל הקוד הבא:
@app.websocket("/ws/{session_id}")
async def websocket_endpoint(websocket: WebSocket, session_id: str):
await manager.connect(websocket, session_id)
try:
while True:
data = await websocket.receive_text()
message = json.loads(data)
print("Message received from user:")
print(message)
if message["type"] == "audio":
# Convert int16 array to bytes
int16_data = message["data"]
audio_bytes = struct.pack(f"{len(int16_data)}h", *int16_data)
await manager.send_audio(session_id, audio_bytes)השרת לוקח את הקול מההודעה ומוסיף את ההודעה הקולית ל session באמצעות פקודת
איך הסוכן מדווח תשובות חזרה למשתמש
אחרי שההודעה הקולית מגיעה מהמשתמש ל session הסוכן, שגם מחובר לאותה session כמו שראינו בקוד היצירה שלו, מקבל את ההודעה ומגיב עליה - הסוכן גם מתמלל את ההודעה וגם מוסיף הודעה קולית משלו.
התוצאה תהיה אירועים חדשים ב session שיישלחו למשתמש דרך פונקציית
סך הכל התוכנית צריכה לטפל בשני היבטים של השיחה:
1. לקחת הודעות מהמשתמש ולהוסיף ל session.
2. לשלוח את כל האירועים מה session חזרה לדפדפן.
כל השאר מטופל על ידי סוכן זמן אמת שמחובר לאותו Session.
קוד צד לקוח
קוד צד הלקוח של הסוכן ארוך אבל לא מסובך. כל פעם שמגיעה הודעה תופעל הפונקציה
וזה המימוש:
ההקלטה קצת יותר ארוכה ומבוצעת בפונקציה הזו:
send_audio. כאן משמעות המילה send היא לא לשלוח את ההודעה לדפדפן אלא לשלוח הודעה קולית ל session.איך הסוכן מדווח תשובות חזרה למשתמש
אחרי שההודעה הקולית מגיעה מהמשתמש ל session הסוכן, שגם מחובר לאותה session כמו שראינו בקוד היצירה שלו, מקבל את ההודעה ומגיב עליה - הסוכן גם מתמלל את ההודעה וגם מוסיף הודעה קולית משלו.
התוצאה תהיה אירועים חדשים ב session שיישלחו למשתמש דרך פונקציית
_process_events שראינו ובפרט דרך פונקציית העזר שלה _serialize_event:async def _serialize_event(self, event: RealtimeSessionEvent) -> dict[str, Any]:
base_event: dict[str, Any] = {
"type": event.type,
}
if event.type == "agent_start":
base_event["agent"] = event.agent.name
elif event.type == "agent_end":
base_event["agent"] = event.agent.name
elif event.type == "handoff":
base_event["from"] = event.from_agent.name
base_event["to"] = event.to_agent.name
elif event.type == "tool_start":
base_event["tool"] = event.tool.name
elif event.type == "tool_end":
base_event["tool"] = event.tool.name
base_event["output"] = str(event.output)
elif event.type == "audio":
base_event["audio"] = base64.b64encode(event.audio.data).decode("utf-8")
elif event.type == "audio_interrupted":
pass
elif event.type == "audio_end":
pass
elif event.type == "history_updated":
base_event["history"] = [item.model_dump(mode="json") for item in event.history]
elif event.type == "history_added":
pass
elif event.type == "guardrail_tripped":
base_event["guardrail_results"] = [
{"name": result.guardrail.name} for result in event.guardrail_results
]
elif event.type == "raw_model_event":
base_event["raw_model_event"] = {
"type": event.data.type,
}
elif event.type == "error":
base_event["error"] = str(event.error) if hasattr(event, "error") else "Unknown error"
elif event.type == "input_audio_timeout_triggered":
pass
else:
assert_never(event)
return base_event
סך הכל התוכנית צריכה לטפל בשני היבטים של השיחה:
1. לקחת הודעות מהמשתמש ולהוסיף ל session.
2. לשלוח את כל האירועים מה session חזרה לדפדפן.
כל השאר מטופל על ידי סוכן זמן אמת שמחובר לאותו Session.
קוד צד לקוח
קוד צד הלקוח של הסוכן ארוך אבל לא מסובך. כל פעם שמגיעה הודעה תופעל הפונקציה
handleRealtimeEvent כדי לטפל בה:this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleRealtimeEvent(data);
};
וזה המימוש:
handleRealtimeEvent(event) {
// Add to raw events pane
this.addRawEvent(event);
// Add to tools panel if it's a tool or handoff event
if (event.type === 'tool_start' || event.type === 'tool_end' || event.type === 'handoff') {
this.addToolEvent(event);
}
// Handle specific event types
switch (event.type) {
case 'audio':
this.playAudio(event.audio);
break;
case 'audio_interrupted':
this.stopAudioPlayback();
break;
case 'history_updated':
this.updateMessagesFromHistory(event.history);
break;
}
}
ההקלטה קצת יותר ארוכה ומבוצעת בפונקציה הזו:
async startContinuousCapture() {
if (!this.isConnected || this.isCapturing) return;
// Check if getUserMedia is available
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error('getUserMedia not available. Please use HTTPS or localhost.');
}
try {
this.stream = await navigator.mediaDevices.getUserMedia({
audio: {sampleRate: 24000,
channelCount: 1,
echoCancellation: true,
noiseSuppression: true
}
});
this.audioContext = new AudioContext({ sampleRate: 24000 });
const source = this.audioContext.createMediaStreamSource(this.stream);
// Create a script processor to capture audio data
this.processor = this.audioContext.createScriptProcessor(4096, 1, 1);
source.connect(this.processor);
this.processor.connect(this.audioContext.destination);
this.processor.onaudioprocess = (event) => {
if (!this.isMuted && this.ws && this.ws.readyState === WebSocket.OPEN) {
const inputBuffer = event.inputBuffer.getChannelData(0);
const int16Buffer = new Int16Array(inputBuffer.length);
// Convert float32 to int16
for (let i = 0; i < inputBuffer.length; i++) {
int16Buffer[i] = Math.max(-32768, Math.min(32767, inputBuffer[i] * 32768));
}
this.ws.send(JSON.stringify({
type: 'audio',
data: Array.from(int16Buffer)
}));
}
};
this.isCapturing = true;
this.updateMuteUI();
} catch (error) {
console.error('Failed to start audio capture:', error);
}
}
כלומר פותחים ערוץ הקלטה מהדפדפן ושולחים כל chunk שמוקלט לשרת דרך ה web socket.
קוד התוכנית המלא
את קוד התוכנית יחד עם כל תוכניות הדוגמה מסדרת פוסטים זו ניתן למצוא בריפוזיטורי:
https://github.com/ynonp/learnagentssdk
GitHub
GitHub - ynonp/learnagentssdk
Contribute to ynonp/learnagentssdk development by creating an account on GitHub.
מדריך: מה זה בעצם API
הפעם אנחנו עם מדריך בסיסי שאני מקווה שיעזור לאנשי בדיקות ומנהלי מוצר כמו גם למפתחים להבין את ההגיון והמטרה של אחד המושגים החשובים בפיתוח מערכות ווב היום.
איך עובדים דפי אינטרנט
איפה מתחילים לדבר על APIs? האמת שלסיפור הזה יש הרבה התחלות אפשריות אבל בואו נבחר להתחיל עם דפי אינטרנט. נכון ממשקי פיתוח בין מערכות היו קיימים לפני האינטרנט אבל האופן בו דברים עובדים היום מאוד מושפע מטכנולוגיית האינטרנט.
את האינטרנט, או לפחות את הטכנולוגיות שמפעילות אותו, המציא טים ברנרס לי בשנת 1991 והטכנולוגיה הראשונה שהוא המציא נקראת HTTP (ראשי תיבות של Hyptertext Transfer Protocol). זהו פרוטוקול, כלומר שפה, שנועדה להגדיר איך מחשבים מעבירים ביניהם דפים ברשת האינטרנט. הפרוטוקול מגדיר בגדול שכל מחשב יכול להחזיק "דפים" ולכל דף יש מזהה. המזהה נראה כמו שם קובץ על מערכת יוניקס לדוגמה
טים ברנרס לי גם המציא דפדפן אינטרנט שאפשר לו לקרוא את הדפים האלה (שהיו כתובים בפורמט HTML) וקצת אחריו אנשים אחרים המציאו דפדפני אינטרנט נוספים והרחיבו את הפורמט של הדפים. ב 1996 ככל שיותר אנשים כתבו דפי אינטרנט עלה הצורך בביצוע פעולות דרך דפי האינטרנט והומצא אלמנט הטופס. בשביל להגיש טפסים טים ברנרס לי עדכן את פרוטוקול HTTP והוסיף פקודה בשם POST. בשביל להגיש טופס עדיין היה צריך לציין את הנתיב להגשה ובדרך כלל צורף לבקשה מידע מהטופס. כך נראתה בקשת POST שנוצרה מטופס:
אנחנו שמים לב שמתחת לפקודת POST מופיע עוד בלוק של מפתחות וערכים, בלוק זה נקרא הכותרות של הבקשה ואפשר להעביר אותן גם בבקשות GET. אחרי סיום הכותרות יש שורה ריקה ואחריה שורת הערכים של הטופס וטקסט זה נקרא "גוף הבקשה".
לאורך השנים נוספו לפרוטוקול HTTP פעלים נוספים והיום הפרוטוקול מונה את הפעלים GET, HEAD, OPTIONS, TRACE, POST, PUT, PATCH, DELETE. קוד בצד השרת, כלומר במחשב שמקבל את הבקשה, אחראי על טיפול בבקשה. הקוד מקבל את הפועל, את הנתיב, את הכותרות ואת גוף הבקשה וצריך לבצע פעולה ולהחזיר תשובה לדפדפן. לפעמים התשובה תהיה דף אינטרנט, לפעמים היא תהיה תמונה ולפעמים הפניה לעמוד אחר.
נסו את זה - בפעם הבאה שאתם גולשים ברשת פתחו בדפדפן את חלון כלי הפיתוח ועברו לטאב Network. שם תוכלו לראות את כל בקשות ה HTTP שהדפדפן שלכם שולח לשרת ואת תשובות השרת לכל בקשה.
עכשיו בואו נוסיף אפליקציה
דפי אינטרנט כתובים בשפה שנקראת HTML, וכן גם אותה המציא טים ברנרס לי. השפה מורכבת מתגיות ותוכן כאשר התגיות מתארות את התפקיד של כל בלוק או שורה בתוכן. לדוגמה הבלוק:
מתאר שורת כותרת עם הטקסט hello world. הבלוק:
מגדיר שורת כותרת ואחריה פיסקת טקסט רגילה. התגית h1 היא קיצור של המילה header כדי לתאר שורת כותרת והתגית p היא קיצור של המילה paragraph ולכן מתארת פיסקה. עם המצאת שפת HTML טים ברנרס לי המציא גם כלי שנקרא דפדפן אינטרנט שיודע לקרוא מסמכים בשפת HTML ולהציג אותם בצורה גרפית, למשל את הכותרות הוא הציג בגופן גדול יותר.
נקפוץ 15 שנה קדימה ונגיד שאנחנו רוצים לכתוב אפליקציה לטלפון. האפליקציה שלנו צריכה להציג חוקים של משחקי לוח פופולריים וכבר כתבנו את כל החוקים בצורה יפה בשפת HTML עבור דף האינטרנט שלנו. הבעיה היא שאת האפליקציה אנשים מתקינים על הטלפון ולא פותחים בתוך דפדפן - לא כל המידע שמופיע בדף האינטרנט רלוונטי גם לאפליקציה, וייתכן ובאפליקציה המפתחים ירצו להטמיע עיצוב או מבנה שונה מזה שמופיע ברשת.
הפיתרון - JSON
הפיתרון שנבחר ברוב המערכות היה להישאר עם HTTP אבל להיפרד מ HTML. במקום שהשרת יחזיר דף HTML עם כל התוכן והתפקיד הסמנטי של כל חלק בתוכן, השרת עדיין יקבל פניה בשפת HTTP מהלקוחות ויחזיר "משהו אחר", משהו שגם אפליקציה וגם דפדפן אינטרנט יכולים לקרוא. ברוב האינטרנט המשהו הזה נקרא JSON.
הפעם אנחנו עם מדריך בסיסי שאני מקווה שיעזור לאנשי בדיקות ומנהלי מוצר כמו גם למפתחים להבין את ההגיון והמטרה של אחד המושגים החשובים בפיתוח מערכות ווב היום.
איך עובדים דפי אינטרנט
איפה מתחילים לדבר על APIs? האמת שלסיפור הזה יש הרבה התחלות אפשריות אבל בואו נבחר להתחיל עם דפי אינטרנט. נכון ממשקי פיתוח בין מערכות היו קיימים לפני האינטרנט אבל האופן בו דברים עובדים היום מאוד מושפע מטכנולוגיית האינטרנט.
את האינטרנט, או לפחות את הטכנולוגיות שמפעילות אותו, המציא טים ברנרס לי בשנת 1991 והטכנולוגיה הראשונה שהוא המציא נקראת HTTP (ראשי תיבות של Hyptertext Transfer Protocol). זהו פרוטוקול, כלומר שפה, שנועדה להגדיר איך מחשבים מעבירים ביניהם דפים ברשת האינטרנט. הפרוטוקול מגדיר בגדול שכל מחשב יכול להחזיק "דפים" ולכל דף יש מזהה. המזהה נראה כמו שם קובץ על מערכת יוניקס לדוגמה
/wiki/HTTP, /en/api/client-sdks או /arunsupe/semantic-grep. פרוטוקול HTTP מגדיר שאם מישהו רוצה לבקש דף ממחשב מרוחק הוא שולח הודעה שמורכבת מהמילה GET ואחריה שם הדף למשל:GET /en/api/client-sdks/
טים ברנרס לי גם המציא דפדפן אינטרנט שאפשר לו לקרוא את הדפים האלה (שהיו כתובים בפורמט HTML) וקצת אחריו אנשים אחרים המציאו דפדפני אינטרנט נוספים והרחיבו את הפורמט של הדפים. ב 1996 ככל שיותר אנשים כתבו דפי אינטרנט עלה הצורך בביצוע פעולות דרך דפי האינטרנט והומצא אלמנט הטופס. בשביל להגיש טפסים טים ברנרס לי עדכן את פרוטוקול HTTP והוסיף פקודה בשם POST. בשביל להגיש טופס עדיין היה צריך לציין את הנתיב להגשה ובדרך כלל צורף לבקשה מידע מהטופס. כך נראתה בקשת POST שנוצרה מטופס:
POST /cgi-bin/subscribe.cgi HTTP/1.0
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
name=Alice+Smith&email=alice%40example.com
אנחנו שמים לב שמתחת לפקודת POST מופיע עוד בלוק של מפתחות וערכים, בלוק זה נקרא הכותרות של הבקשה ואפשר להעביר אותן גם בבקשות GET. אחרי סיום הכותרות יש שורה ריקה ואחריה שורת הערכים של הטופס וטקסט זה נקרא "גוף הבקשה".
לאורך השנים נוספו לפרוטוקול HTTP פעלים נוספים והיום הפרוטוקול מונה את הפעלים GET, HEAD, OPTIONS, TRACE, POST, PUT, PATCH, DELETE. קוד בצד השרת, כלומר במחשב שמקבל את הבקשה, אחראי על טיפול בבקשה. הקוד מקבל את הפועל, את הנתיב, את הכותרות ואת גוף הבקשה וצריך לבצע פעולה ולהחזיר תשובה לדפדפן. לפעמים התשובה תהיה דף אינטרנט, לפעמים היא תהיה תמונה ולפעמים הפניה לעמוד אחר.
נסו את זה - בפעם הבאה שאתם גולשים ברשת פתחו בדפדפן את חלון כלי הפיתוח ועברו לטאב Network. שם תוכלו לראות את כל בקשות ה HTTP שהדפדפן שלכם שולח לשרת ואת תשובות השרת לכל בקשה.
עכשיו בואו נוסיף אפליקציה
דפי אינטרנט כתובים בשפה שנקראת HTML, וכן גם אותה המציא טים ברנרס לי. השפה מורכבת מתגיות ותוכן כאשר התגיות מתארות את התפקיד של כל בלוק או שורה בתוכן. לדוגמה הבלוק:
<h1>hello world</h1>
מתאר שורת כותרת עם הטקסט hello world. הבלוק:
<h1>hello world</h1>
<p>HTML was also invented by Tim Berners Lee along with HTTP and the first web browser</p>
מגדיר שורת כותרת ואחריה פיסקת טקסט רגילה. התגית h1 היא קיצור של המילה header כדי לתאר שורת כותרת והתגית p היא קיצור של המילה paragraph ולכן מתארת פיסקה. עם המצאת שפת HTML טים ברנרס לי המציא גם כלי שנקרא דפדפן אינטרנט שיודע לקרוא מסמכים בשפת HTML ולהציג אותם בצורה גרפית, למשל את הכותרות הוא הציג בגופן גדול יותר.
נקפוץ 15 שנה קדימה ונגיד שאנחנו רוצים לכתוב אפליקציה לטלפון. האפליקציה שלנו צריכה להציג חוקים של משחקי לוח פופולריים וכבר כתבנו את כל החוקים בצורה יפה בשפת HTML עבור דף האינטרנט שלנו. הבעיה היא שאת האפליקציה אנשים מתקינים על הטלפון ולא פותחים בתוך דפדפן - לא כל המידע שמופיע בדף האינטרנט רלוונטי גם לאפליקציה, וייתכן ובאפליקציה המפתחים ירצו להטמיע עיצוב או מבנה שונה מזה שמופיע ברשת.
הפיתרון - JSON
הפיתרון שנבחר ברוב המערכות היה להישאר עם HTTP אבל להיפרד מ HTML. במקום שהשרת יחזיר דף HTML עם כל התוכן והתפקיד הסמנטי של כל חלק בתוכן, השרת עדיין יקבל פניה בשפת HTTP מהלקוחות ויחזיר "משהו אחר", משהו שגם אפליקציה וגם דפדפן אינטרנט יכולים לקרוא. ברוב האינטרנט המשהו הזה נקרא JSON.
🔥1
ג'ייסון, שזה קיצור ל JavaScript Object Notation, הוא שפה שהומצאה על ידי דאגלס קרוקפורד בשנת 2,000. בניגוד ל HTML שמתארת מסמכים, ג'ייסון היא שפה לתיאור אוביקטים כלומר מידע מובנה, אבל הייתרון הגדול שלה הוא שקל מאוד למחשבים לקרוא אוביקטי JSON - הרבה יותר קל מאשר לקרוא קבצי HTML. הנה דוגמה לקובץ JSON שמתאים לקובץ ה HTML שהראיתי קודם:
ברור שכל דבר שאפשר לתאר ב HTML אפשר לתאר גם ב JSON, אבל ה JSON הוא יותר תמציתי, מובנה והכי חשוב קל יותר לפיענוח עבור תוכנות מחשב. כל תוכנת מחשב שמקבלת JSON צריכה גם לכלול קוד שיציג את ה JSON, וכל תוכנה תוכל להציג את ה JSON בצורה שונה - דפדפן האינטרנט יהפוך את ה JSON ל HTML ויציג את זה; אפליקציית אייפון תשתמש במידע שב JSON כדי למלא שדות ספציפיים על המסך באפליקציה ואפליקציית אנדרואיד תשתמש בקוד משלה כדי להבין מה JSON מה צריך להופיע על המסך ולהציג אותו.
הבנה זאת הביאה לשידרוג במערכות ווב רבות - במקום להחזיר HTML האתרים התחילו להחזיר JSON. מערכות שונות לקחו את ה JSON הזה והציגו אותו כל אחת בצורה אחרת. חיבור אוטומטי בין שתי מערכות נקרא API או בעברית ממשק תכנותי. כשדפדפן אינטרנט פונה לשרת, מקבל אוביקט JSON, הופך אותו ל HTML ומציג על המסך אנחנו אומרים שדף האינטרנט נטען דרך API. כשאפליקציה פונה לשרת, מושכת אוביקט JSON, מפענחת אותו ומשתמשת במידע כדי להציג פרטים על המסך אנחנו אומרים שהאפליקציה מתקשרת עם השרת דרך API. אוסף הבקשות ששרת יודע לטפל בהן ואוסף התשובות שהוא מחזיר הוא הגדרת ה API - הגדרת השפה בה אפשר לדבר עם מערכת זו.
מה אפשר לעשות עם REST API
רגע, אז מאיפה הגיעה לפה המילה REST? אם API הוא קיצור של ממשק תכנותי ומייצג חיבור בין שתי מערכות המילה REST היא גם ראשי תיבות הפעם של Representational State Transfer. את השם הזה המציא רוי פילדינג בשנת 2000 כדי לייצג שיטת עבודה מסוימת של ממשקים בין מערכות. בחיבור מסוג REST API הנתיב, שזה שם הקובץ בבקשת ה HTTP, מייצג משאב. תשובת השרת מייצגת את התוכן או ה State של אותו משאב. אני חושב שדוגמה קטנה תעזור להבהיר כאן את המושגים - נניח שאנחנו בונים מערכת לניהול ספרים בספריה והמערכת מאפשרת להציג פרטים על ספרים לפי מזהי הספר. ספרים מזוהים על ידי מזהה שנקרא ISBN ולכן ניתן להשתמש ב ISBN כדי לייצג "ספר". נתיב לדוגמה במערכת הספרים שלנו יכול להיות:
בקשת רשת בשפת HTTP למשאב זה יכולה להיות:
והתשובה שהשרת יחזיר עשויה להיות:
מערכת מחשב שתרצה להשתמש ב API כדי לקבל מידע על הספר תשלח הודעת HTTP מסוג GET עם מזהה הספר, תקבל את הפרטים של הספר ותציג את הפרטים בצורה שמתאימה לה. ומה קורה אם במערכת יש טופס שמאפשר הוספת ספרים למערכת? גם את זה אפשר לפתור עם בקשת HTTP, הפעם בקשת PUT. המערכת תנהל את הטופס בצורה שמתאימה לה וכשהמשתמש יאשר להוסיף ספר תישלח בקשת PUT שנראית כך:
בממשק מסוג REST בדרך כלל כל משאב יתחיל בשם המשאב (בדוגמה שראינו זה היה book) ולפעמים אחריו יופיע מזהה ספציפי של אותו משאב. הפועל יקבע מה יש לעשות עם אותו משאב בדרך כלל לפי התבנית הבאה:
1. פעולת GET על המשאב, בלי לציין מזהה משאב, למשל
2. פעולת GET על המשאב עם ציון מזהה פריט יחזיר מידע על אותו פריט, למשל
3. פעולת POST על המשאב, בלי לציין מזהה, תיצור פריט חדש מאותו משאב. למשל
4. פעולת PUT על המשאב עם ציון מזהה תעדכן את הפרטים של אותו משאב לדוגמה פעולת
5. פעולת DELETE על המשאב עם ציון מזהה מוחקת את הפריט, לדוגמה
עוגיות והזדהות
{
header: "hello world",
text: "HTML was also invented by Tim Berners Lee along with HTTP and the first web browser"
}
ברור שכל דבר שאפשר לתאר ב HTML אפשר לתאר גם ב JSON, אבל ה JSON הוא יותר תמציתי, מובנה והכי חשוב קל יותר לפיענוח עבור תוכנות מחשב. כל תוכנת מחשב שמקבלת JSON צריכה גם לכלול קוד שיציג את ה JSON, וכל תוכנה תוכל להציג את ה JSON בצורה שונה - דפדפן האינטרנט יהפוך את ה JSON ל HTML ויציג את זה; אפליקציית אייפון תשתמש במידע שב JSON כדי למלא שדות ספציפיים על המסך באפליקציה ואפליקציית אנדרואיד תשתמש בקוד משלה כדי להבין מה JSON מה צריך להופיע על המסך ולהציג אותו.
הבנה זאת הביאה לשידרוג במערכות ווב רבות - במקום להחזיר HTML האתרים התחילו להחזיר JSON. מערכות שונות לקחו את ה JSON הזה והציגו אותו כל אחת בצורה אחרת. חיבור אוטומטי בין שתי מערכות נקרא API או בעברית ממשק תכנותי. כשדפדפן אינטרנט פונה לשרת, מקבל אוביקט JSON, הופך אותו ל HTML ומציג על המסך אנחנו אומרים שדף האינטרנט נטען דרך API. כשאפליקציה פונה לשרת, מושכת אוביקט JSON, מפענחת אותו ומשתמשת במידע כדי להציג פרטים על המסך אנחנו אומרים שהאפליקציה מתקשרת עם השרת דרך API. אוסף הבקשות ששרת יודע לטפל בהן ואוסף התשובות שהוא מחזיר הוא הגדרת ה API - הגדרת השפה בה אפשר לדבר עם מערכת זו.
מה אפשר לעשות עם REST API
רגע, אז מאיפה הגיעה לפה המילה REST? אם API הוא קיצור של ממשק תכנותי ומייצג חיבור בין שתי מערכות המילה REST היא גם ראשי תיבות הפעם של Representational State Transfer. את השם הזה המציא רוי פילדינג בשנת 2000 כדי לייצג שיטת עבודה מסוימת של ממשקים בין מערכות. בחיבור מסוג REST API הנתיב, שזה שם הקובץ בבקשת ה HTTP, מייצג משאב. תשובת השרת מייצגת את התוכן או ה State של אותו משאב. אני חושב שדוגמה קטנה תעזור להבהיר כאן את המושגים - נניח שאנחנו בונים מערכת לניהול ספרים בספריה והמערכת מאפשרת להציג פרטים על ספרים לפי מזהי הספר. ספרים מזוהים על ידי מזהה שנקרא ISBN ולכן ניתן להשתמש ב ISBN כדי לייצג "ספר". נתיב לדוגמה במערכת הספרים שלנו יכול להיות:
/book/0764363778
בקשת רשת בשפת HTTP למשאב זה יכולה להיות:
GET /book/0764363778
והתשובה שהשרת יחזיר עשויה להיות:
{
"name": "JavaScript: The Good Parts",
"author": "Douglas Crockford",
"rating": 4.4,
"price": "16$"
"instock": 10
}
מערכת מחשב שתרצה להשתמש ב API כדי לקבל מידע על הספר תשלח הודעת HTTP מסוג GET עם מזהה הספר, תקבל את הפרטים של הספר ותציג את הפרטים בצורה שמתאימה לה. ומה קורה אם במערכת יש טופס שמאפשר הוספת ספרים למערכת? גם את זה אפשר לפתור עם בקשת HTTP, הפעם בקשת PUT. המערכת תנהל את הטופס בצורה שמתאימה לה וכשהמשתמש יאשר להוסיף ספר תישלח בקשת PUT שנראית כך:
PUT /book/1234567890
{
"name": "The Art of Fiction",
"author": "Jane Doe",
"price": 19.99,
"rating": 4.5,
"instock": true
}
בממשק מסוג REST בדרך כלל כל משאב יתחיל בשם המשאב (בדוגמה שראינו זה היה book) ולפעמים אחריו יופיע מזהה ספציפי של אותו משאב. הפועל יקבע מה יש לעשות עם אותו משאב בדרך כלל לפי התבנית הבאה:
1. פעולת GET על המשאב, בלי לציין מזהה משאב, למשל
GET /books מחזיר אינדקס של הפריטים כלומר את כל הספרים.2. פעולת GET על המשאב עם ציון מזהה פריט יחזיר מידע על אותו פריט, למשל
GET /books/123 יחזיר פרטים על ספר שהמזהה שלו הוא 123.3. פעולת POST על המשאב, בלי לציין מזהה, תיצור פריט חדש מאותו משאב. למשל
POST /books תיצור ספר חדש.4. פעולת PUT על המשאב עם ציון מזהה תעדכן את הפרטים של אותו משאב לדוגמה פעולת
PUT /books/123 תעדכן את הפרטים של ספר 123.5. פעולת DELETE על המשאב עם ציון מזהה מוחקת את הפריט, לדוגמה
DELETE /books/123 תמחק את ספר מספר 123.עוגיות והזדהות
חלק חשוב מהחיבור בין מערכות מחשב, כלומר מה API, הוא ההזדהות. כשמערכת מחשב מקבלת הודעה היא צריכה לדעת מי שלח לה את ההודעה הזאת - מערכת אחרת? תוקף זדוני? מנהל הספריה שמחובר דרך הדפדפן במחשב שלו? גולש שמחפש ספר? לכל אחד יש הרשאות שונות, יכולות שונות ולכן גם כל אחד עשוי לקבל תשובה שונה.
הודעת HTTP כוללת בסך הכל 3 חלקים בהם אפשר לכתוב מידע: שורת המזהה שכוללת את הפועל והנתיב עליהם אנחנו מדברים, בלוק הכותרות ובלוק תוכן ההודעה. נתוני הזדהות נשלחים בבלוק הכותרות.
יש שני סוגים של נתוני הזדהות שאנחנו יכולים למצוא בכותרות של בקשות HTTP. סוג אחד נקרא Cookie וזו כותרת שנראית כך:
הבקשה בדוגמה מנסה למשוך משאב עם המזהה
מערכות רבות לא מציגות טפסים ב HTML ובכלל לא עוברות דרך דפדפן. עבורן נוצר מנגנון נוסף שהוא כותרת בשם Authorization. במערכת כזו נבצע איזשהו תהליך הזדהות באמצעות קריאת HTTP POST עם מזהה מסוים (ממש דומה להגשת טופס), גם כאן השרת יאמת את המשתמש, יגריל ערך אקראי וישמור אותו בבסיס הנתונים וישלח חזרה את אותו ערך למערכת שפנתה אליו. בבקשות הבאות אותה מערכת תצרף כותרת HTTP בשם Authorization ותשים בה את המזהה שהיא קיבלה. בקשה כזו תיראה כך:
כשאנחנו ניגשים ל API תמיד נרצה לברר איך אותו API מזהה את המשתמשים - מה הנתיב (או המשאב) בו אפשר לבצע הזדהות ולקבל אסימון גישה ובאיזה כותרת HTTP ובאיזה פורמט עליי להעביר את אסימון הגישה בבקשות הבאות.
סיכום
מערכות רבות באינטרנט שמחות לדבר עם מערכות אחרות בצורה אוטומטית דרך API. בתור משתמשים של אותן מערכות שווה לנו ללמוד גם את ה APIs, גם כדי להבין איך המערכות בנויות וגם כדי להשתמש בהן בצורה יצירתית. קיימים כלים רבים לתקשורת עם מערכות דרך REST API - אני משתמש הרבה בפקודה בשם curl, יש כלי גרפי בשם postman שהרבה אנשים אוהבים ויש כלים רבים נוספים. לא משנה איזה כלי תבחרו היכרות עם מבנה של בקשות ותשובות HTTP והבנה של הנתיבים ב API יעזרו לכם להבין טוב יותר את המערכות ולהשתמש נכון באותם כלים.
הודעת HTTP כוללת בסך הכל 3 חלקים בהם אפשר לכתוב מידע: שורת המזהה שכוללת את הפועל והנתיב עליהם אנחנו מדברים, בלוק הכותרות ובלוק תוכן ההודעה. נתוני הזדהות נשלחים בבלוק הכותרות.
יש שני סוגים של נתוני הזדהות שאנחנו יכולים למצוא בכותרות של בקשות HTTP. סוג אחד נקרא Cookie וזו כותרת שנראית כך:
GET /docs/introduction HTTP/1.1
Host: ai-sdk.dev
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-language: en-US,en;q=0.9
cache-control: no-cache
cookie: ko_id=7827ad98-2a94-440f-be5b-6dd5cfd84161; _hp2_id.3132448398=%7B%22
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
הבקשה בדוגמה מנסה למשוך משאב עם המזהה
/docs/introduction ומעבירה לשרת מספר כותרות HTTP. אחת הכותרת נקראת cookie והיא מכילה ערך שקצת קשה לקרוא. זה בסדר אנחנו לא צריכים לקרוא אותו מי שצריך לקרוא אותו זה שרת ה Web. השרת קורא את הערך ומבין ממנו מי הבן אדם שמולו. איך ערך ה cookie מתורגם לזהות הגולש אתם שואלים? פה נכנס לתמונה תהליך ההזדהות. משתמש ממלא טופס עם שם משתמש וסיסמה, השרת בודק שהסיסמה נכונה ורושם בבסיס הנתונים ליד שם המשתמש ערך אקראי שנקרא מזהה Session, את הערך האקראי הזה הוא מחזיר באותה תשובת HTTP שנשלחת אחרי מילוי הטופס. מכאן והלאה הדפדפן יצרף את אותו ערך אקראי לכל בקשה בתור כותרת ה HTTP שנקראת cookie. השרת יקבל את הערך האקראי הזה, יסתכל בבסיס הנתונים עבור איזה משתמש הוא נוצר וכך ידע מי שלח את הבקשה.מערכות רבות לא מציגות טפסים ב HTML ובכלל לא עוברות דרך דפדפן. עבורן נוצר מנגנון נוסף שהוא כותרת בשם Authorization. במערכת כזו נבצע איזשהו תהליך הזדהות באמצעות קריאת HTTP POST עם מזהה מסוים (ממש דומה להגשת טופס), גם כאן השרת יאמת את המשתמש, יגריל ערך אקראי וישמור אותו בבסיס הנתונים וישלח חזרה את אותו ערך למערכת שפנתה אליו. בבקשות הבאות אותה מערכת תצרף כותרת HTTP בשם Authorization ותשים בה את המזהה שהיא קיבלה. בקשה כזו תיראה כך:
POST /book/1234567890/ HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.dGVzdF91c2VyX2RhdGE.abc123dummySignature
Content-Type: application/json
Content-Length: 109
{
"name": "The Art of Fiction",
"author": "Jane Doe",
"price": 19.99,
"rating": 4.5,
"instock": true
}
כשאנחנו ניגשים ל API תמיד נרצה לברר איך אותו API מזהה את המשתמשים - מה הנתיב (או המשאב) בו אפשר לבצע הזדהות ולקבל אסימון גישה ובאיזה כותרת HTTP ובאיזה פורמט עליי להעביר את אסימון הגישה בבקשות הבאות.
סיכום
מערכות רבות באינטרנט שמחות לדבר עם מערכות אחרות בצורה אוטומטית דרך API. בתור משתמשים של אותן מערכות שווה לנו ללמוד גם את ה APIs, גם כדי להבין איך המערכות בנויות וגם כדי להשתמש בהן בצורה יצירתית. קיימים כלים רבים לתקשורת עם מערכות דרך REST API - אני משתמש הרבה בפקודה בשם curl, יש כלי גרפי בשם postman שהרבה אנשים אוהבים ויש כלים רבים נוספים. לא משנה איזה כלי תבחרו היכרות עם מבנה של בקשות ותשובות HTTP והבנה של הנתיבים ב API יעזרו לכם להבין טוב יותר את המערכות ולהשתמש נכון באותם כלים.
👍4
כמה דברים שמפחידים אותי ב AI
בואו נוריד מהשולחן כמה דברים שעולים שאותי לא מפחידים בכלל - ה AI לא יהפוך את המתכנתים למיותרים, אם כבר להיפך. ככל שיותר קוד נכתב כך יצטרכו יותר מתכנתים לתחזק אותו. ככל שמערכות הופכות יותר מורכבות כך יצטרכו יותר אנשים שיתכננו אותן וימצאו את השגיאות בהן. כבר היום אנחנו יכולים לבנות מוצרים דומים לאלה שבנינו לפני עשר שנים עם הרבה פחות אנשים, והנה לא בחרנו להמשיך לעבוד עם אקספלורר 6 ו Windows 95 וליצור אבטלה המונית. הכל טוב, כשאנחנו כותבים מערכות מתוחכמות יותר ומהר יותר עולם התוכנה משתפר.
סיפור נוסף שלא מפחיד אותי בכלל הוא בעיות אבטחה, באגים ומערכות שאנשים כותבים בלי להבין. אנשים תמיד כתבו מערכות בלי להבין, אני גדלתי בעולם שבו יכולת להיכנס לבסיס הנתונים של כל אתר אינטרנט כי כולם היו פתוחים ל SQL Injection. עם הזמן אנחנו בונים תשתיות שמשפרות את המצב וגם אם בתקופה הקרובה יהיו שוב הרבה מערכות לא מאובטחות גם המטוטלת הזאת תשנה כיוון.
ממה כן כדאי לחשוש? זאת הרשימה שלי:
1. פייק ניוז ופישינג - היכולת של AI ליצור קמפיינים אמינים של פישינג תלווה אותנו תקופה ארוכה מאוד. רק לא מזמן עלה לכותרות חוקר אבטחה שהצליח לבצע התחזות קולית כדי לעקוף מערכת זיהוי קולי של אחד הבנקים פה. יהיה קשה מאוד לסמוך על וידאו, קול וכמובן חתימה. הכל יהיה ניתן לזיוף.
2. התחזות הולכת להיות בעיה כשאנחנו מראיינים מועמדים לעבודה. זה כבר קורה עם כלים לרמאות בראיונות עבודה והולך להיות קשה להבין את הרמה המקצועית האמיתית של מועמד.
3. מוטיבציה הולכת להיות אתגר עצום. כש AI יכול לפתור בשבילך את שיעורי הבית אתה מפתח הרגלי עבודה גרועים וזה משהו שקשה להתגבר עליו.
4. חלומות יהיו קטנים יותר. כשלכל שאלה יש תשובה אין פליאה, אין תהייה ואין זמן לחלום על אפשרויות חדשות. זה מתחבר לקושי למצוא מוטיבציה שאנחנו כבר רואים בעבודה עם AI.
מה מפחיד אתכם בעידן ה AI? ואיך אתם חושבים נצליח להתמודד עם האתגרים? אשמח לשמוע בתגובות פה או בטלגרם.
בואו נוריד מהשולחן כמה דברים שעולים שאותי לא מפחידים בכלל - ה AI לא יהפוך את המתכנתים למיותרים, אם כבר להיפך. ככל שיותר קוד נכתב כך יצטרכו יותר מתכנתים לתחזק אותו. ככל שמערכות הופכות יותר מורכבות כך יצטרכו יותר אנשים שיתכננו אותן וימצאו את השגיאות בהן. כבר היום אנחנו יכולים לבנות מוצרים דומים לאלה שבנינו לפני עשר שנים עם הרבה פחות אנשים, והנה לא בחרנו להמשיך לעבוד עם אקספלורר 6 ו Windows 95 וליצור אבטלה המונית. הכל טוב, כשאנחנו כותבים מערכות מתוחכמות יותר ומהר יותר עולם התוכנה משתפר.
סיפור נוסף שלא מפחיד אותי בכלל הוא בעיות אבטחה, באגים ומערכות שאנשים כותבים בלי להבין. אנשים תמיד כתבו מערכות בלי להבין, אני גדלתי בעולם שבו יכולת להיכנס לבסיס הנתונים של כל אתר אינטרנט כי כולם היו פתוחים ל SQL Injection. עם הזמן אנחנו בונים תשתיות שמשפרות את המצב וגם אם בתקופה הקרובה יהיו שוב הרבה מערכות לא מאובטחות גם המטוטלת הזאת תשנה כיוון.
ממה כן כדאי לחשוש? זאת הרשימה שלי:
1. פייק ניוז ופישינג - היכולת של AI ליצור קמפיינים אמינים של פישינג תלווה אותנו תקופה ארוכה מאוד. רק לא מזמן עלה לכותרות חוקר אבטחה שהצליח לבצע התחזות קולית כדי לעקוף מערכת זיהוי קולי של אחד הבנקים פה. יהיה קשה מאוד לסמוך על וידאו, קול וכמובן חתימה. הכל יהיה ניתן לזיוף.
2. התחזות הולכת להיות בעיה כשאנחנו מראיינים מועמדים לעבודה. זה כבר קורה עם כלים לרמאות בראיונות עבודה והולך להיות קשה להבין את הרמה המקצועית האמיתית של מועמד.
3. מוטיבציה הולכת להיות אתגר עצום. כש AI יכול לפתור בשבילך את שיעורי הבית אתה מפתח הרגלי עבודה גרועים וזה משהו שקשה להתגבר עליו.
4. חלומות יהיו קטנים יותר. כשלכל שאלה יש תשובה אין פליאה, אין תהייה ואין זמן לחלום על אפשרויות חדשות. זה מתחבר לקושי למצוא מוטיבציה שאנחנו כבר רואים בעבודה עם AI.
מה מפחיד אתכם בעידן ה AI? ואיך אתם חושבים נצליח להתמודד עם האתגרים? אשמח לשמוע בתגובות פה או בטלגרם.
👍3🔥1👏1
חיבור ריאקט וריילס עם Inertia
יש מפתחי ריילס שממש לא אוהבים FrontEnd ויעשו הכל כדי לוותר על כתיבת JavaScript, ויש אחרים שהסיבה שלהם לקום בבוקר היא הפרונט אנד ו Rails אצלם נבחר פשוט כי הוא נותן פיתרון נוח לצד השרת. אם אתם שייכים למחנה השני סיכוי טוב שמאוד תאהבו את ספריית inertia. זו ספריה שמחברת בצורה טובה את קוד הפרונט אנד לאפליקציית Rails. אינרציה היא מתחרה של React On Rails ומתמקדת בניווט בתוך היישום ובהעברת מידע מצד השרת לצד הלקוח. בפוסט זה נראה את עקרונות העבודה המרכזיים של אינרציה.
יצירת הפרויקט
בשביל ליצור פרויקט Rails עם Inertia אני משתמש בפקודות הבאות:
הפקודה האחרונה מריצה wizard בו אני צריך לבחור את הפריימוורק בו אני רוצה לעבוד. אני בוחר ריאקט לדוגמה זו אבל המנגנון תומך גם ב vue ו svelte.
לאחר סיום ה wizard יש לי בתיקיית
דף אינרציה ראשון שלי
הדף הראשון באינרציה למעשה נוצר כבר מתוך ה wizard. הוא נקרא InertiaExample ואנחנו מוצאים את הקוד שלו בקובץ
דבר ראשון שקופץ לעין הוא יצירת הקומפוננטה:
בשביל ליצור את העמוד יש להעביר משתנה name. תזכרו את זה כי תכף נחזור אליו כדי להבין מאיפה הוא מגיע.
השימוש ב
הקומפוננטה המיוחדת Head מאפשרת למלא תוכן באלמנטים בראש העמוד ולכן השורה:
קובעת את ה title. בשביל שזה יעבוד הם הכניסו לקובץ
אינרציה תומך ב CSS Modules ולכן מבנה הקלאסים:
יש מפתחי ריילס שממש לא אוהבים FrontEnd ויעשו הכל כדי לוותר על כתיבת JavaScript, ויש אחרים שהסיבה שלהם לקום בבוקר היא הפרונט אנד ו Rails אצלם נבחר פשוט כי הוא נותן פיתרון נוח לצד השרת. אם אתם שייכים למחנה השני סיכוי טוב שמאוד תאהבו את ספריית inertia. זו ספריה שמחברת בצורה טובה את קוד הפרונט אנד לאפליקציית Rails. אינרציה היא מתחרה של React On Rails ומתמקדת בניווט בתוך היישום ובהעברת מידע מצד השרת לצד הלקוח. בפוסט זה נראה את עקרונות העבודה המרכזיים של אינרציה.
יצירת הפרויקט
בשביל ליצור פרויקט Rails עם Inertia אני משתמש בפקודות הבאות:
$ rails new inertia-demo
$ bundle add inertia_rails
$ bin/rails generate inertia:install
הפקודה האחרונה מריצה wizard בו אני צריך לבחור את הפריימוורק בו אני רוצה לעבוד. אני בוחר ריאקט לדוגמה זו אבל המנגנון תומך גם ב vue ו svelte.
לאחר סיום ה wizard יש לי בתיקיית
app/javascript מספר תיקיות חדשות: assets, entrypoints ו pages. אפשר להוסיף עוד תיקיות ככל שתרצו. נקודת הכניסה לאפליקציה היא הקובץ entrypoints/application.js.דף אינרציה ראשון שלי
הדף הראשון באינרציה למעשה נוצר כבר מתוך ה wizard. הוא נקרא InertiaExample ואנחנו מוצאים את הקוד שלו בקובץ
app/javascript/pages/InertiaExample.tsx. בואו נקרא אותו יחד:import { Head } from '@inertiajs/react'
import { useState } from 'react'
import inertiaSvg from '/assets/inertia.svg'
import reactSvg from '/assets/react.svg'
import viteRubySvg from '/assets/vite_ruby.svg'
import cs from './InertiaExample.module.css'
export default function InertiaExample({ name }: { name: string }) {
const [count, setCount] = useState(0)
return (
<>
<Head title="Inertia + Vite Ruby + React Example" />
<div className={cs.root}>
<h1 className={cs.h1}>Hello {name}!</h1>
<div>
<a href="https://inertia-rails.dev" target="_blank">
<img className={cs.logo} src={inertiaSvg} alt="Inertia logo" />
</a>
<a href="https://vite-ruby.netlify.app" target="_blank">
<img
className={\${cs.logo} ${cs.vite}\}
src={viteRubySvg}
alt="Vite Ruby logo"
/>
</a>
<a href="https://react.dev" target="_blank">
<img
className={\${cs.logo} ${cs.react}\}
src={reactSvg}
alt="React logo"
/>
</a>
</div>
<h2 className={cs.h2}>Inertia + Vite Ruby + React</h2>
<div className="card">
<button
className={cs.button}
onClick={() => setCount((count) => count + 1)}
>
count is {count}
</button>
<p>
Edit <code>app/frontend/pages/InertiaExample.jsx</code> and save to
test HMR
</p>
</div>
<p className={cs.readTheDocs}>
Click on the Inertia, Vite Ruby, and React logos to learn more
</p>
</div>
</>
)
}
דבר ראשון שקופץ לעין הוא יצירת הקומפוננטה:
export default function InertiaExample({ name }: { name: string }) {
בשביל ליצור את העמוד יש להעביר משתנה name. תזכרו את זה כי תכף נחזור אליו כדי להבין מאיפה הוא מגיע.
השימוש ב
useState רגיל לגמרי לריאקט ומלמד אותנו שקומפוננטות ריאקט הן קומפוננטות צד לקוח - אינרציה תומך ב Server Side Rendering אבל לא ב React Server Components. גם לא ראיתי התיחסות בתיעוד לנושא ה Caching אחרי SSR כך שנראה שה Use Case היותר פופולרי הוא אפליקציות צד-לקוח.הקומפוננטה המיוחדת Head מאפשרת למלא תוכן באלמנטים בראש העמוד ולכן השורה:
<Head title="Inertia + Vite Ruby + React Example" />
קובעת את ה title. בשביל שזה יעבוד הם הכניסו לקובץ
app/views/layouts/application.html.erb את השורה:<title inertia><%= content_for(:title) || "Inertia Demo" %></title>
אינרציה תומך ב CSS Modules ולכן מבנה הקלאסים:
import cs from './InertiaExample.module.css'
...
<div className={cs.root}>
וזה מסיים את הקומפוננטה הראשונה שלנו.
בשביל לראות את העמוד נלך לפי הכללים של ריילס. בקובץ
ובקונטרולר
וזו גם התשובה לשאלה שהתחלנו איתה את הקריאה - מאיפה מגיע הפרמטר name. באינרציה, הפרמטרים לקומפוננטת העמוד מגיעים מהקונטרולר שמפעיל render, וכך גם שם העמוד להצגה.
הוספת קומפוננטה
בואו נעדכן את העמוד נוציא את מונה הלחיצות לקומפוננטה שלו ונשים אותו כמה פעמים על הדף. בעבודה עם קומפוננטות אינרציה עובד בדיוק כמו ריאקט. אני יוצר תיקייה חדשה בשם
ואז בקומפוננטה InertiaExample אני מוסיף פקודת import:
ושימוש בקומפוננטה:
דף אינרציה שני וניווט ביניהם
לאינרציה יש עוד כח על ובנוסף להיותה ספריית תבניות היא מחליפה גם את React Router ומאפשרת ניווט צד לקוח בין דפים. בואו נראה איך זה עובד.
אני מעדכן את
לאחר מכן אני מעדכן את
אני יוצר עמוד חדש בקובץ
ומעדכן את דף הבית בקובץ
עכשיו נראה מה קרה פה:
1. הקונטרולר מעביר את הנתיבים לקומפוננטות. הנתיבים מוגדרים בתור פונקציות (בסגנון ריילס) כך שאם השמות עצמם יוחלפו בעתיד לא נצטרך לעדכן מחרוזות באפליקציה.
2. קומפוננטת Link מייצרת ניווט צד לקוח.
כשלוחצים על הלינק מדף הבית למעבר לדף אודות הדפדפן מבצע בקשת JSON ומקבל את התוכן הבא בלבד:
בשביל לראות את העמוד נלך לפי הכללים של ריילס. בקובץ
config/routes.rb אני מוצא את השורה:get 'inertia-example', to: 'inertia_example#index'
ובקונטרולר
app/controllers/inertia_example_controller.rb אני מוצא את ההגדרה:class InertiaExampleController < ApplicationController
def index
render inertia: 'InertiaExample', props: {
name: params.fetch(:name, 'World'),
}
end
end
וזו גם התשובה לשאלה שהתחלנו איתה את הקריאה - מאיפה מגיע הפרמטר name. באינרציה, הפרמטרים לקומפוננטת העמוד מגיעים מהקונטרולר שמפעיל render, וכך גם שם העמוד להצגה.
הוספת קומפוננטה
בואו נעדכן את העמוד נוציא את מונה הלחיצות לקומפוננטה שלו ונשים אותו כמה פעמים על הדף. בעבודה עם קומפוננטות אינרציה עובד בדיוק כמו ריאקט. אני יוצר תיקייה חדשה בשם
app/javascript/components בתוכה שם את הקובץ Counter.tsx ובו התוכן:import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div className="flex flex-col items-center justify-center h-screen bg-gray-100">
<h1 className="text-3xl font-bold mb-4">Counter: {count}</h1>
<div className="space-x-4">
<button
onClick={() => setCount(count - 1)}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition"
>
-
</button>
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition"
>
+
</button>
<button
onClick={() => setCount(0)}
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition"
>
Reset
</button>
</div>
</div>
)
}
ואז בקומפוננטה InertiaExample אני מוסיף פקודת import:
import Counter from '@/components/Counter';
ושימוש בקומפוננטה:
<Counter />
<Counter />
<Counter />
דף אינרציה שני וניווט ביניהם
לאינרציה יש עוד כח על ובנוסף להיותה ספריית תבניות היא מחליפה גם את React Router ומאפשרת ניווט צד לקוח בין דפים. בואו נראה איך זה עובד.
אני מעדכן את
config/routes.rb כדי שיכיל את שני הנתיבים:get 'inertia-example', to: 'inertia_example#index', as: :home
get 'inertia-about', to: 'inertia_example#about', as: :about
לאחר מכן אני מעדכן את
inertia_example_controller.rb לתוכן הבא:* frozen_string_literal: true *
class InertiaExampleController < ApplicationController
def index
render inertia: 'InertiaExample', props: {
name: params.fetch(:name, 'World'),
about_path:,
}
end
def about
render inertia: 'About', props: {
home_path:,
}
end
end
אני יוצר עמוד חדש בקובץ
app/javascript/pages/About.tsx עם התוכן הבא:import { Link } from '@inertiajs/react'
export default function About({
home_path
}: {
home_path: string,
}) {
return (
<div>
<Link href={home_path}>Back home</Link>
</div>
)
}
ומעדכן את דף הבית בקובץ
InertiaExample.tsx ומוסיף גם לו את הלינק:export default function InertiaExample({ name, about_path }:
{ name: string,
about_path: staring }) {
return (
<>
<Head title="Inertia + Vite Ruby + React Example" />
<div className={cs.root}>
<Link href={about_path}>About Us</Link>
עכשיו נראה מה קרה פה:
1. הקונטרולר מעביר את הנתיבים לקומפוננטות. הנתיבים מוגדרים בתור פונקציות (בסגנון ריילס) כך שאם השמות עצמם יוחלפו בעתיד לא נצטרך לעדכן מחרוזות באפליקציה.
2. קומפוננטת Link מייצרת ניווט צד לקוח.
כשלוחצים על הלינק מדף הבית למעבר לדף אודות הדפדפן מבצע בקשת JSON ומקבל את התוכן הבא בלבד:
{
"component": "About",
"props": {
"errors": {},
"home_path": "/inertia-example"
},
"url": "/inertia-about","version": "d114e02a9a610c261738bf4254648a71b3fce993",
"encryptHistory": true,
"clearHistory": false
}
וכן אנחנו כבר מבינים מהדוגמה כאן שבברירת מחדל אינרציה לא מפצל את קבצי ה JavaScript שלנו לפי דפים. הם כן תומכים במנגנון של פיצול קוד אם ממש נרצה אבל פשוט לא מפעילים אותו לכל פרויקט חדש.
מה הלאה
אינרציה כוללת עוד המון יכולות שלא הספקתי לסקור כאן כמו טיפול בהגשת טפסים, העלאת קבצים, ריענון אוטומטי של מידע (polling), ניהול מיקום הגלילה ועוד. אפשר למצוא המון דוגמאות בתיעוד שלהם כאן:
https://inertia-rails.dev/
אבל האמת שמעבר למעטפת של ניתוב והצגת דפים מדובר בסך הכל באפליקציית Client Side React כך שאפשר להשתמש בכל הספריות והיכולות שאנחנו מכירים מעבודה עם ריאקט ו vite. בצד השרת ריילס יתפקד כמו שרת API שבאופן אוטומטי יודע להחזיר קומפוננטה או JSON לפי דרישה.
בהשוואה ל React On Rails אינרציה נראה הרבה יותר פשוט ומינימליסטי. הוא לא מתאים לאפליקציות גדולות שצריכות Server Side Rendering או אופטימיזציות לבניה אבל באפליקציות צד לקוח הוא ייתן תוצאה מהירה וטובה.
Inertia Rails
Inertia Rails – Build React/Vue/Svelte SPAs with Rails | Inertia Rails
The official Rails adapter for Inertia.js. Build modern single-page applications using your existing Rails controllers and routes. No API required.
❤1👍1
איך לזוז יותר לאט
שלוש רמות של לימוד מחומר כתוב או AI:
1. השלמה אוטומטית - זה כשאני מבקש מה AI לכתוב את הקוד בשבילי ישר בתוך ה IDE ולהסביר מה הוא כתב, או כשאני מתחיל לכתוב ומשתמש בטאב כדי להשלים את המימושים.
2. העתקה - זה כשאני מבקש מה AI לכתוב את הקוד בתוך מסך הצ'ט או כשאני קורא מדריך כתוב בחלון אחד וכותב את אותו הקוד לבד מאפס בחלון ה IDE שלי (לא העתק-הדבק, ממש מקליד). פה כבר לקחתי צעד משמעותי קדימה מבחינת רמת הלימוד. אני לא בטוח למה אבל ההקלדה עצמה דורשת רמת הבנה יותר מעמיקה מ Copy/Paste.
3. שינויים קטנים - מדרגה שלישית זה כשאני משנה דברים קטנים בקוד או בהנחות היסוד שלו. לדוגמה אני מבקש מ AI לכתוב דוגמה לקומפוננטה בריאקט ואז הולך ל IDE ובונה את הקומפוננטה ב next.js או מחליף שפת תכנות וכותב את הקומפוננטה לבד ב Reagent שזה הריאקט ל ClojureScript. או אם אני לומד Machine Learning אני יכול לקרוא מדריך ב pytorch ולכתוב את דוגמת הקוד בעצמי ב C++ ב libtorch או ב torch-rb ברובי. כתיבת הקוד מחדש בסביבה קצת שונה דורשת רמה יותר גבוהה של הבנה ומכריחה אותנו לחזור ל AI ולשאול שאלות הבהרה על חלקים בטקסט, מה שעוד ממשיך ומחזק את ההבנה.
כשהמטרה היא ללמוד לעומק איך דברים עובדים יותר לאט זה יתרון.
שלוש רמות של לימוד מחומר כתוב או AI:
1. השלמה אוטומטית - זה כשאני מבקש מה AI לכתוב את הקוד בשבילי ישר בתוך ה IDE ולהסביר מה הוא כתב, או כשאני מתחיל לכתוב ומשתמש בטאב כדי להשלים את המימושים.
2. העתקה - זה כשאני מבקש מה AI לכתוב את הקוד בתוך מסך הצ'ט או כשאני קורא מדריך כתוב בחלון אחד וכותב את אותו הקוד לבד מאפס בחלון ה IDE שלי (לא העתק-הדבק, ממש מקליד). פה כבר לקחתי צעד משמעותי קדימה מבחינת רמת הלימוד. אני לא בטוח למה אבל ההקלדה עצמה דורשת רמת הבנה יותר מעמיקה מ Copy/Paste.
3. שינויים קטנים - מדרגה שלישית זה כשאני משנה דברים קטנים בקוד או בהנחות היסוד שלו. לדוגמה אני מבקש מ AI לכתוב דוגמה לקומפוננטה בריאקט ואז הולך ל IDE ובונה את הקומפוננטה ב next.js או מחליף שפת תכנות וכותב את הקומפוננטה לבד ב Reagent שזה הריאקט ל ClojureScript. או אם אני לומד Machine Learning אני יכול לקרוא מדריך ב pytorch ולכתוב את דוגמת הקוד בעצמי ב C++ ב libtorch או ב torch-rb ברובי. כתיבת הקוד מחדש בסביבה קצת שונה דורשת רמה יותר גבוהה של הבנה ומכריחה אותנו לחזור ל AI ולשאול שאלות הבהרה על חלקים בטקסט, מה שעוד ממשיך ומחזק את ההבנה.
כשהמטרה היא ללמוד לעומק איך דברים עובדים יותר לאט זה יתרון.
🔥2
קומיט בלי פוש (או - מתי ליצור את הענף)
הרבה מקומות עובדים עם גיט בשיטת עבודה דומה. בשביל להתחיל לעבוד על פיצ'ר יוצרים ענף, עושים קומיטים לאותו ענף, כשהפיצ'ר מוכן שולחים PR ובסוף אחרי המיזוג מוחקים את הענף. אני מבין למה זו גישה פופולרית:
1. שקיפות - כולם רואים את הענפים והעבודה של כולם.
2. גיבוי - אפשר לעשות push אחרי כל קומיט וכך הנתונים מגובים לשרת.
3. שיתוף פעולה - אני יכול במהלך העבודה לבקש עזרה מחבר או חברה, הם ימשכו את הענף שלי ישימו תיקון וידחפו.
יצירת ענף בתחילת העבודה על פיצ'ר עוזרת לשמור על סדר עבודה ומועילה במיוחד כשאנחנו רק מתחילים לעבוד עם גיט.
אבל זו רק דרך אחת לעבוד. בחיים מותר לגוון, במיוחד אם אתם קצת יותר מיומנים עם גיט. לדוגמה אם אני לא צריך לשתף את העבודה ורק חוקר רעיון, אני יכול לחכות עם יצירת הענף ועם ה push. אני מקודד על המכונה שלי, עדיין עושה קומיטים רגיל בכל צעד ואחרי שהפיצ'ר עובד אני מפעיל rebase כדי למחוק חלק מהקומיטים ולשנות הודעות של אחרים ורק אז יוצר את הענף ודוחף. בצורה כזאת כל האחרים בצוות רואים היסטוריה יותר ברורה של קומיטים. כל עוד לא דחפתי הרבה יותר קל לשנות או לזרוק קומיטים בלי לדאוג שאולי מישהו כבר משך את הקומיטים האלה.
דרך אפילו יותר טובה היא ליצור את הענף אבל לחכות עם הדחיפה שלו ושל הקומיטים. כשדוחפים ענף פעם ראשונה גיט מתלונן ודורש שניצור את הענף בצורה מפורשת בשרת:
אפשר להמשיך לעבוד על ענף מקומי בלי להגדיר את ה upstream ובלי לעשות push ולהיעזר בהודעה הזאת כדי לא לעשות push בטעות. מתי כן לדחוף? אחרי ה rebase כשכל הקומיטים עודכנו עם ההודעות הנכונות.
הרבה מקומות עובדים עם גיט בשיטת עבודה דומה. בשביל להתחיל לעבוד על פיצ'ר יוצרים ענף, עושים קומיטים לאותו ענף, כשהפיצ'ר מוכן שולחים PR ובסוף אחרי המיזוג מוחקים את הענף. אני מבין למה זו גישה פופולרית:
1. שקיפות - כולם רואים את הענפים והעבודה של כולם.
2. גיבוי - אפשר לעשות push אחרי כל קומיט וכך הנתונים מגובים לשרת.
3. שיתוף פעולה - אני יכול במהלך העבודה לבקש עזרה מחבר או חברה, הם ימשכו את הענף שלי ישימו תיקון וידחפו.
יצירת ענף בתחילת העבודה על פיצ'ר עוזרת לשמור על סדר עבודה ומועילה במיוחד כשאנחנו רק מתחילים לעבוד עם גיט.
אבל זו רק דרך אחת לעבוד. בחיים מותר לגוון, במיוחד אם אתם קצת יותר מיומנים עם גיט. לדוגמה אם אני לא צריך לשתף את העבודה ורק חוקר רעיון, אני יכול לחכות עם יצירת הענף ועם ה push. אני מקודד על המכונה שלי, עדיין עושה קומיטים רגיל בכל צעד ואחרי שהפיצ'ר עובד אני מפעיל rebase כדי למחוק חלק מהקומיטים ולשנות הודעות של אחרים ורק אז יוצר את הענף ודוחף. בצורה כזאת כל האחרים בצוות רואים היסטוריה יותר ברורה של קומיטים. כל עוד לא דחפתי הרבה יותר קל לשנות או לזרוק קומיטים בלי לדאוג שאולי מישהו כבר משך את הקומיטים האלה.
דרך אפילו יותר טובה היא ליצור את הענף אבל לחכות עם הדחיפה שלו ושל הקומיטים. כשדוחפים ענף פעם ראשונה גיט מתלונן ודורש שניצור את הענף בצורה מפורשת בשרת:
fatal: The current branch test has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin test
To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.
אפשר להמשיך לעבוד על ענף מקומי בלי להגדיר את ה upstream ובלי לעשות push ולהיעזר בהודעה הזאת כדי לא לעשות push בטעות. מתי כן לדחוף? אחרי ה rebase כשכל הקומיטים עודכנו עם ההודעות הנכונות.
👍1
אלמנט Activity ב React
אז React 19.2 הוסיפו אלמנט חדש שהרבה אנשים אוהבים בשם Activity. האם באמת צריך אותו? בואו נראה מה הוא עושה ואתן לכם להחליט עד סוף הפוסט.
אלמנט Activity עושה שני דברים, הראשון הוא הגדרת כלל העיצוב display: none לכל הילדים שלו כדי שהם עדיין יישארו בחיים אבל לא יופיעו על המסך. בממשקי טאבים מנגנון כזה יכול לעבוד טוב יותר ממחיקה מוחלטת של האלמנט כי כשרק משנים את ה display האלמנט עדיין שומר על ה state שלו וכל המידע ב DOM נשמר, אז אם יש לנו טופס באחד הטאבים ועוברים לטאב אחר וחוזרים הטופס עדיין ישמור על הערכים שנכתבו בו. נשים לב שגם לפני ריאקט 19.2 בנינו מנגנונים כאלה ופשוט הגדרנו לבד את כלל העיצוב display: none כשמשתמשים ניווטו בין טאבים.
הכח השני של Activity, והוא הדבר החדש שבגללו נכנסה קומפוננטה חדשה לריאקט הוא מחיקה של האפקטים והרצת פונקציות הניקוי של כל האפקטים וה Cleanup Refs. מצד אחד מנגנון זה עוזר אם יש לנו בתוך עץ הקומפוננטות של אחד הטאבים אפקטים שאין בהם צורך ביציאה מהטאב - לדוגמה אולי יש אלמנט שמשתמש במצלמה אז הגיוני לסגור את המצלמה כשעוברים טאב. בדוגמאות בתיעוד הם מדברים על נגן וידאו שכדאי לעצור לפני שיוצאים מהטאב. מצד שני אם האפקט שלכם מקשיב לאירועים מ Web Socket ומעדכן את ה UI אז כיבוי האפקט מפסיק את ההאזנה לאירועים וכך כשנחזור לטאב נצטרך לבצע עדכון יזום.
הנה דוגמה קטנה ל Activity בפעולה עם כמה הודעות debug כדי להבין איך זה עובד. אפשר להדביק אותה בכל יישום next 16:
הקומפוננטה ActivityDemo מציגה הודעה אחת כשנוצר האפקט והודעה נוספת כשה ref מתעדכן. כשמכבים את תיבת הבחירה הקומפוננטה הופכת בלתי נראית ואנחנו מקבלים את ההודעות unset ref ו stop effect כתוצאה מכיבוי האפקט וה Ref Cleanup Callback.
אז מה דעתכם? זו הקומפוננטה שהיתה חסרה לריאקט? אני חושב שלא. הסתרה של טאבים עשינו גם קודם עם הגדרת display: none ב CSS. מי שרצה לכבות אפקטים ספציפיים ביציאה מהטאב הגדיר את ה"נראות" בתור prop והשתמש בה במערך התלויות של האפקט. כיבוי כל האפקטים כברירת מחדל כנראה רק תבלבל.
למידע נוסף ועוד המון דוגמאות עם הקומפוננטה החדשה שווה להעיף מבט בדף התיעוד שלהם:
https://react.dev/reference/react/Activity
אז React 19.2 הוסיפו אלמנט חדש שהרבה אנשים אוהבים בשם Activity. האם באמת צריך אותו? בואו נראה מה הוא עושה ואתן לכם להחליט עד סוף הפוסט.
אלמנט Activity עושה שני דברים, הראשון הוא הגדרת כלל העיצוב display: none לכל הילדים שלו כדי שהם עדיין יישארו בחיים אבל לא יופיעו על המסך. בממשקי טאבים מנגנון כזה יכול לעבוד טוב יותר ממחיקה מוחלטת של האלמנט כי כשרק משנים את ה display האלמנט עדיין שומר על ה state שלו וכל המידע ב DOM נשמר, אז אם יש לנו טופס באחד הטאבים ועוברים לטאב אחר וחוזרים הטופס עדיין ישמור על הערכים שנכתבו בו. נשים לב שגם לפני ריאקט 19.2 בנינו מנגנונים כאלה ופשוט הגדרנו לבד את כלל העיצוב display: none כשמשתמשים ניווטו בין טאבים.
הכח השני של Activity, והוא הדבר החדש שבגללו נכנסה קומפוננטה חדשה לריאקט הוא מחיקה של האפקטים והרצת פונקציות הניקוי של כל האפקטים וה Cleanup Refs. מצד אחד מנגנון זה עוזר אם יש לנו בתוך עץ הקומפוננטות של אחד הטאבים אפקטים שאין בהם צורך ביציאה מהטאב - לדוגמה אולי יש אלמנט שמשתמש במצלמה אז הגיוני לסגור את המצלמה כשעוברים טאב. בדוגמאות בתיעוד הם מדברים על נגן וידאו שכדאי לעצור לפני שיוצאים מהטאב. מצד שני אם האפקט שלכם מקשיב לאירועים מ Web Socket ומעדכן את ה UI אז כיבוי האפקט מפסיק את ההאזנה לאירועים וכך כשנחזור לטאב נצטרך לבצע עדכון יזום.
הנה דוגמה קטנה ל Activity בפעולה עם כמה הודעות debug כדי להבין איך זה עובד. אפשר להדביק אותה בכל יישום next 16:
'use client';
import { useState, useRef, useEffect, Activity } from "react";
function refChanged(el: any) {
console.log(\set ref\);
return () => {
console.log(\unset ref\)
}
}
function ActivityDemo() {
useEffect(() => {
console.log(\start effect\)
return () => {
console.log(\stop effect\)
}
}, [])
return (
<p ref={refChanged}>hello world</p>
)
}
export default function Home() {
const [visible, setVisible] = useState(true);
return (
<div>
<input type="checkbox" checked={visible} onChange={() => setVisible(v => !v)} />
<Activity mode={visible ? "visible" : "hidden"}>
<ActivityDemo />
</Activity>
</div>
);
}
הקומפוננטה ActivityDemo מציגה הודעה אחת כשנוצר האפקט והודעה נוספת כשה ref מתעדכן. כשמכבים את תיבת הבחירה הקומפוננטה הופכת בלתי נראית ואנחנו מקבלים את ההודעות unset ref ו stop effect כתוצאה מכיבוי האפקט וה Ref Cleanup Callback.
אז מה דעתכם? זו הקומפוננטה שהיתה חסרה לריאקט? אני חושב שלא. הסתרה של טאבים עשינו גם קודם עם הגדרת display: none ב CSS. מי שרצה לכבות אפקטים ספציפיים ביציאה מהטאב הגדיר את ה"נראות" בתור prop והשתמש בה במערך התלויות של האפקט. כיבוי כל האפקטים כברירת מחדל כנראה רק תבלבל.
למידע נוסף ועוד המון דוגמאות עם הקומפוננטה החדשה שווה להעיף מבט בדף התיעוד שלהם:
https://react.dev/reference/react/Activity
react.dev
Activity – React
The library for web and native user interfaces