ToCode
Відкрити в Telegram
1 420
Підписники
+124 години
+17 днів
-430 день
Архів дописів
1 420
חווית המתכנת
כן יש מושג כזה ואפילו עם ראשי תיבות - DX, Developer Experience.
קו המחשבה כאן הוא שאם אנחנו כותבים כלים שלמתכנתים ולמתכנתות יהיה קל להשתמש בהם אז התוכנות שאותם אנשים יכתבו יהיו טובות יותר, מהירות יותר ובטוחות יותר. מצד שני כשאנשים צריכים לעבוד קשה כדי ללמוד איך להשתמש "נכון" בכלי שלך הם יעשו יותר טעויות והתוצאה (האפליקציה שהם כותבים) תהיה פחות טובה.
דוגמה טובה ל DX היא כשאנחנו בונים API. אם קל להשתמש ב API הזה וקל למשוך ממנו רק את המידע שהמתכנתים צריכים אז האפליקציות שייכתבו עם אותו API יצאו טובות יותר. אם ה API מכריח את המשתמשים למשוך הרבה יותר מידע ממה שהם צריכים אז למפתחים שישתמשו באותו API לא תהיה ברירה והם יאלצו לכתוב מערכות פחות טובות.
או אם אני כותב פריימוורק עם תיעוד טוב אז מתכנתים ישתמשו בפריימוורק כמו שתכננתי והמערכת שהם כותבים תהיה מהירה יותר. אם אני לא כותב תיעוד הם ישתמשו בה בצורה שלא חשבתי עליה והתוצאה תהיה באגים מוזרים ומערכת איטית ולא מאובטחת.
הבעיה ב DX מתחילה כש DX מתחיל לבוא על חשבון UX, וכשאנחנו מבלבלים בין כלים לכתיבת מערכות טובות ל"כלים שכיף לנו להשתמש בהם". פריזמה מפורסמת בתור ה ORM עם ה DX הטוב ביותר בעולם, אבל בלי אפשרות לעשות JOIN בין טבלאות כל יישום שישתמש בפריזמה יצטרך לעשות הרבה יותר עבודה (נכון אוטומטית בלי לערב את המתכנתים, אבל עדיין) ולכן יישומים רבים יהיו איטיים בהרבה בהשוואה לכאלה שייכתבו עם דריזל.
עוד דוגמה מפורסמת היתה פונגאפ (ואחריה קורדובה) שנתנה למפתחים אפשרות לכתוב אפליקציות מובייל בכלים של פיתוח ווב שהם כבר מכירים, אבל ייצרה אפליקציות שעבדו לאט והיו מוגבלות מבחינת היכולות והאינטגרציה עם הטלפון.
כשעובדים בצוות האתגר הוא כפול: אותם כלים שמספקים DX מהאגדות הרבה פעמים עברו מספיק ליטוש כדי שלא נראה את הבעיות באפליקציות POC קטנות. לפעמים יש תחושה שאותם כלים נכתבו ספציפית עבור אותן אפליקציות קטנות. הרבה פעמים החברים בצוות לא להוטים לוותר על חווית המפתח המושקעת מתוך הרגשה שבעיות הביצועים הם דאגה של העתיד או שאולי בכלל לא יגיעו אלינו.
אין לי פיתרון קל למצבים האלה. ככלל ככל שאנחנו מבינים יותר טוב את הכלים ואת המגבלות המובנות שלהם קל לנו יותר לבחור את אותם כלים שמשלבים חווית מפתחים טובה עם מוצר טוב ללקוח.
1 420
פה כמעט התייאשתי עד שמצאתי את הפוסט הזה:
https://aymarino.github.io/polygon-area/
שמסביר על נוסחה בשם נוסחת שרוך הנעל שמאפשרת לחשב שטח של פוליגון רק לפי הקודקודים שלו. בשביל להשתמש בנוסחה תחילה הפכתי את רשימת ההוראות לרשימה של קודקודים:
def coordinates(input: Source, parseLine: (line: String) => PaintingInstruction): List[(Long, Long, Long)] =
input
.getLines()
.scanLeft((0L, 0L, 0L)) { (acc, line) =>
val (row, column, distance) = acc
val paintingInstruction = parseLine(line)
paintingInstruction.dir match
case "L" => (row, column - paintingInstruction.count, distance + paintingInstruction.count)
case "R" => (row, column + paintingInstruction.count, distance + paintingInstruction.count)
case "U" => (row - paintingInstruction.count, column, distance + paintingInstruction.count)
case "D" => (row + paintingInstruction.count, column, distance + paintingInstruction.count)
}.toList
הערך השלישי ברשימה הוא המרחק הכולל של הצורה, וזה יעזור לנו בהמשך החישוב. כתבתי שתי פונקציות לקריאת שורות להתאים לשני חלקי התרגיל:
def parsePart1(line: String): PaintingInstruction =
val lineRE = """(\w) (\d+) \((#\w+)\)""".r
val List(d, count, color) = lineRE.findFirstMatchIn(line).get.subgroups
PaintingInstruction(d, count.toInt, color)
def parsePart2(line: String): PaintingInstruction =
val lineRE = """\w \d+ \(#(\w\w\w\w\w)(\w)\)""".r
val List(count, d) = lineRE.findFirstMatchIn(line).get.subgroups
val distance = java.lang.Long.parseLong(count, 16)
val direction = d match
case "0" => "R"
case "1" => "D"
case "2" => "L"
case "3" => "U"
PaintingInstruction(direction, distance, "...")
ואז את הנוסחה לשטח פוליגון:
def shoelace(coordinates: List[(Long, Long, Long)]): Long =
coordinates.zip(coordinates.tail).map { pair =>
val ((x1, y1, d1), (x2, y2, d2)) = pair
x1 * y2 - y1 * x2
}
.sum
.abs / 2
זה הקוד לחלק הראשון:
def day18part1(): Unit =
val coords = coordinates(Source.fromResource("day18.txt"), parsePart1)
val distance = coords.last._3
val area = shoelace(coords)
val poolSize = area + distance / 2 + 1
println(poolSize)
וזה החלק השני:
def day18part1(): Unit =
val coords = coordinates(Source.fromResource("day18.txt"), parsePart2)
val distance = coords.last._3
val area = shoelace(coords)
val poolSize = area + distance / 2 + 1
println(poolSize)
אני מודה שבמבט ראשון התרגיל הזה נראה לי משעמם ולכן לקח לי הרבה זמן לגשת אליו. בסופו של דבר היה טוויסט ולמדתי כמה נוסחאות חדשות כך שיצא מוצלח.1 420
פיתרון Advent Of Code יום 18 בסקאלה
מזמן לא כתבנו Advent Of Code וזו הזדמנות מצוינת להיזכר ולהתקדם. יש לנו 25 חידות סך הכל אז אנחנו ממש מתקרבים לסיום. בואו נראה את יום 18, את הניסיון הראשון הארוך והלא מוצלח שלי ואז את הפיתרון האמיתי.
מה צריך לחשב
היום אנחנו הולכים לחפור בריכה. בדרך כלל כשמציירים מטריצה דו-מימדית אנחנו מדמיינים שאנחנו מסתכלים על המשטח מלמעלה, אבל בתרגיל היום אנחנו מסתכלים על הבריכה מהצד. זה הציור להמחשה:
#######
#.....#
###...#
..#...#
..#...#
###.###
#...#..
##..###
.#....#
.######
איך חופרים את הבריכה אתם שואלים? אין בעיה זה בדיוק הקלט לפאזל. בשביל לחפור אנחנו מקבלים רשימת הוראות שנראית כך:
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)
האותיות R, D, L ו U קובעות לאיזה כיוון לחפור (שמאלה, למטה, ימינה או למעלה) והמספר קובע כמה מטר. נתעלם בינתיים מהמספר האחרון בשורה.
אחרי שחפרנו את הבריכה ממלאים אותה במים וזה נראה ככה:
#######
#######
#######
..#####
..#####
#######
#####..
#######
.######
.######
ועכשיו צריך לגלות כמה משבצות עם סולמית יש בציור. בדוגמה יש 64 (אתם יכולים לספור).
איך לא לספור סולמיות
כשראיתי את המטריצה הדבר הראשון שרציתי לעשות היה לשמור אותה במפה בסקאלה כשהמפתח הוא הקואורדינטות והערך הוא סולמית או מחרוזת הציור או true או מה שלא יהיה.
אחרי זה רציתי לקחת נקודה בתוך הבריכה ולהתחיל לחפש ממנה את כל הנקודות שסביבה כלפי חוץ עד שאני מגיע לקו הבריכה וכך אני יכול לדעת כמה משבצות נמצאות בתוך שטח הבריכה. מוסיפים את זה להיקף ומצאנו את מספר הסולמיות. אפילו כתבתי את הקוד.
שתי הפונקציות האלה חופרות את הבריכה:
case class PaintingInstruction(dir: String, count: Long, color: String)
def dig(input: Source): Canvas =
val lineRE = """(\w) (\d+) \((#\w+)\)""".r
input
.getLines()
.foldLeft(((0, 0), Map[(Int, Int), PaintingInstruction]())) { (acc, line) =>
Try {
val (pos, canvas) = acc
val List(d, count, color) = lineRE.findFirstMatchIn(line).get.subgroups
val paintingInstruction = PaintingInstruction(d, count.toInt, color)
paint(pos, canvas, paintingInstruction)
}.recover { err =>
// skip line in case of error
println(s"Error in line ${line} - ${err}")
acc
}.get
}._2
def paint(pos: Point, canvas: Canvas, cmd: PaintingInstruction): (Point, Canvas) =
0L.until(cmd.count).foldLeft((pos, canvas)) { (acc, count) =>
val ((row, column), canvas) = acc
val nextCanvas = canvas.updated((row, column), cmd)
cmd.dir match
case "R" => ((row, column + 1), nextCanvas)
case "L" => ((row, column - 1), nextCanvas)
case "U" => ((row - 1, column), nextCanvas)
case "D" => ((row + 1, column), nextCanvas)
}
והפונקציה הזאת מחפשת את כל הנקודות בתוך הבריכה:
@tailrec
def floodFill(canvas: Canvas, finished: Set[Point], inProgress: List[Point]): Set[Point] =
inProgress match
case head :: tail =>
val (row, column) = head
println(head)
val neighbors = List(
(row-1, column),
(row+1, column),
(row, column-1),
(row, column+1),
).filterNot { p => canvas.contains(p) }
.filterNot(finished.contains)
.filterNot(inProgress.contains)
floodFill(canvas, finished + head, tail ++ neighbors)
case Nil => finished
הקוד פותר את החלק הראשון אבל נכשל בצורה כואבת בחלק השני והוא גם לא יעיל.
ניסיון שני
בחלק השני של התרגיל מספרים לנו שבעצם קראנו את השורה לא נכון כל הזמן. המספר האחרון הוא הדבר החשוב - חמש הספרות הראשונות שלו הן מספר בבסיס 16, והסיפרה האחרונה מייצגת את הכיוון 0 עבור ימינה, 1 עבור למטה 2 עבור שמאלה ו 3 אומר למעלה. המספרים גדולים ולכן כבר לא ריאלי לשמור את כל המשבצות במטריצה. אם היינו שומרים ומחשבים קלט הדוגמה היה מסתכם ב 952408144115 משבצות.1 420
הבלבול בין "האם" ל"מתי"
שלוש שאלות לפני שמתקדמים - האם צריך? האם צריך עכשיו? מתי כדאי?
האם צריך לכתוב בדיקות? וודאי
האם צריך עכשיו לכתוב בדיקות? לא בטוח
מתי הזמן הכי טוב לכתוב בדיקות?
האם צריך תואר? וודאי
האם צריך תואר עכשיו? לא חושב
מתי הזמן הכי טוב ללמוד תואר באוניברסיטה?
האם צריך לשדרג את הפוסטגרס? ברור
האם צריך עכשיו לשדרג את הפוסטגרס? לא חייב
מתי הזמן הכי טוב לשדרג תלויות בפרויקט?
שימו לב איך השאלה היחידה שפותחת מקום לדיון היא "מתי", ולכן זאת השאלה היחידה מבין השלוש ששווה לדבר עליה. כשיש לנו את ה"מתי" במקום אפשר להתקדם בביטחה בלי לדאוג מתי ה"האם" יהפוך ל"האם עכשיו".
1 420
המחיר של האבסטרקציה הלא נכונה
כשבוחרים אבסטרקציה לא נכונה יש רק שתי אפשרויות:
1. אפשר לעבוד מסביב לבעיה ולבנות המון מעקפים וטיפול במקרי הקצה הלא הגיוניים.
2. אפשר לקחת צעד אחורה ולשנות אבסטרקציה.
השיטה השנייה רק מרגישה יותר יקרה.
1 420
הקסם של Suspense בריאקט
הרבה זמן היה קשה לראות את זה, אבל עכשיו עם Server Components הקומפוננטה Suspense מקבלת כח חדש והופכת להכרחית בהרבה אפליקציות next.js. הסיפור הולך כך:
1. יש לנו עמוד עם חלקים שונים שזמן הטעינה שלהם שונה - לדוגמה אזור תוכן מרכזי שמגיע מהר מהזיכרון או קבצים, כתבות ארוכות יותר שמגיעות מקריאה לבסיס נתונים ואזור צד שמגיע מקריאה ל API צד שלישי (שרת לשרת).
2. אנחנו רוצים לשלוח ללקוח את העמוד כמה שיותר מהר, וכשיגיעו הנתונים מבסיס הנתונים או מה API "להשלים" אותם בעמוד.
בעבר הדרך היחידה לעשות את זה היתה מתוך קוד צד לקוח - אנחנו מקבלים את העמוד ואז JavaScript בצד לקוח פונה לשרת ומתחיל שתי בקשות רשת חדשות עבור המידע מבסיס הנתונים והמידע מה API צד-שלישי. כל בקשה תחזור בזמן שלה והתוכן יוצג כשהתוצאות יגיעו. החיסרון במנגנון הזה הוא שאנחנו צריכים לחכות שהדפדפן יטען את אפליקציית הריאקט, ישלח הודעות חדשות לשרת ורק אז השרת פונה למשוך את המידע הנוסף שצריך. בנוסף (וזו סתם תכונה מעצבנת של ריאקט), אנחנו צריכים להתמודד בצד לקוח עם מצב של קומפוננטה שנמצאת במצב טעינה ולהציג תוכן חלופי בקוד הקומפוננטה.
הפונקציה
use שתיכנס בריאקט 19 תאפשר לשפר קצת את המצב מבחינת אפליקציית צד הלקוח בכך שנוכל לעצור את הרינדור באמצע ולחכות ל Promise (קצת כמו await באמצע קוד קומפוננטה), ואז לא נצטרך לטפל במצב שהקומפוננטה "טוענת". מנגנון Server Components וה Component Streaming מאפשר ללכת עוד צעד קדימה ולטפל בכל הסיפור הזה בצד השרת.
שימו לב לדוגמה הבאה:
import { Suspense } from 'react';
/* We've moved data fetching into the changlog compoment */
async function ChangelogWithDataFetching() {
const changelogData = await getChangelogData()
return <Changelog data={changelogData} />
}
/*
...and wrapped that component in Suspense.
The user can see the page immediately, while the changelog component loads
*/
export default function Page() {
return (
<Layout>
<Sidebar>
{/* other sidebar stuff */}
<Suspense fallback={<ChangelogPlaceholder />}>
<ChangelogWithDataFetching />
</Suspense>
</Sidebar>
{/* other page stuff */}
</Layout>
)
}
קוד הגיע מכאן.
יש להם את הקומפוננטה ChangelogWithDataFetching שבסך הכל מוסיפה קריאה לפונקציה אסינכרונית כדי למשוך מידע ואז מרנדרת את הקומפוננטה Changelog. המקום היחיד שיצטרך לדאוג לאיך הקומפוננטה נראית במצב טעינה זה הקומפוננטה ChangelogPlaceholder שמופיעה ב Suspense. הצעד הזה קדימה - גם בקומפוננטות צד שרת אבל גם תכף בקומפוננטות צד לקוח עם use - נותן ל Suspense כח על חדש ואני חושב שיגרום לנו להשתמש הרבה יותר בקומפוננטה זו.1 420
תרגיל בריפקטורינג טייפסקריפט
בואו נתחיל עם ממשק טייפסקריפט הבא של פריט ופונקציה לעדכון הפריט:
interface Item {
id: number;
text: string;
likes: number;
price: number;
}
function updateItem(item: Pick<Item, 'id'> & Partial<Item>) {
return fetch(\/api/update/${item.id}\, {
method: 'POST',
headers: { contentType: 'application/json' },
body: JSON.stringify(item)
});
}
הפונקציה updateItem מקבלת אחד או יותר מהשדות של Item אבל חייבת לקבל את השדה id ושולחת את המידע לשרת לצורך עדכון.
השינוי
עכשיו אנחנו רוצים להוסיף לפריט גם תמונה. נשים לב שתמונה מתנהגת אחרת משאר השדות, בהעלאה סוג המידע של תמונה הוא File, אבל כשמקבלים פריט מהשרת סוג המידע הוא string:
interface Item {
id: number;
text: string;
likes: number;
price: number;
image: string;
}
interface ItemForUpload {
id: number;
text?: string;
likes?: number;
price?: number;
image?: File;
}
בנוסף בשביל להעלות תמונה צריך להגדיר ב HTML טופס:
<form onSubmit={handleSubmit}>
<input type="file" name="image" />
<input type="submit" value="Upload" />
</form>
וכשיש אירוע submit צריך לקחת את פרטי הטופס לאוביקט FormData ואותו לשלוח לשרת:
function handleSubmit(e) {
e.preventDefault();
const form = e.target as HTMLFormElement;
const data = new FormData(form);
fetch(\/api/update/${item.id}\, {
method: 'POST',
body: data,
});
}
התרגיל - בואו ננסה לאחד את שני הממשקים.
פיתרון -
בגלל שפונקציית העדכון שמקבלת FormData יכולה לטפל גם בעדכון שאר הפרטים ב Item, עדיף לי להשתמש בה. הבעיה היא ש FormData לא כולל וולידציה על טיפוסים וחבל לי להפסיד וולידציה זו בכל שאר התוכנית, במיוחד אם כבר יש לי הרבה מקומות בתוכנית שמשתמשים בפונקציה updateItem הקיימת.
הפיתרון יהיה לשנות את updateItem כדי שתשמור את החתימה שלה (וכך את הגדרות הטיפוסים), אבל במימוש לקרוא לפונקציה ששולחת FormData וכך למחוק את הקוד הכפול.
צעד ראשון יהיה לבנות ממשק אחד לפונקציית ה update. בשביל להוסיף את ה Image אני מגדיר את הטיפוס:
type ItemForUpload = Pick<Item, 'id'> & Partial<Omit<Item, 'image'>> & { image?: File }
שכולל את כל השדות של Item במצב Partial, חוץ מ image שמשנה את סוגו ל File. פונקציית העדכון עכשיו יכולה להיות:
function update(item: ItemForUpload) {
const fd = new FormData();
for (const [key, value] of Object.entries(item)) {
if (typeof value === 'number') {
fd.append(key, String(value));
} else {
fd.append(key, value);
}
}
updateWithForm(item.id, fd);
}
function updateWithForm(id: number, fd: FormData) {
return fetch(\/api/update/${id}\, {
method: 'POST',
body: fd,
});
}
קוד חיצוני שהשתמש קודם ב updateItem יכול להמשיך להשתמש ב update ולקבל את אותו Type Safety. קוד שרוצה להעביר טופס יוכל לקרוא ישירות ל updateWithForm.1 420
ריאקט, אפקט וספריות משיכת מידע
בחור פירסם ברדיט (אני חושב שזה בחור כי הכינוי שלו מסתיים ב boyy) שהוא ניסה להתקבל לעבודה, הגיש מבחן בית בריאקט והפיתרון שלו לא היה מספיק טוב ולכן לא התקבל. מה שמיוחד בפוסט הזה זה שהחברה העבירה לו פידבק על הדברים בגללם הוא נכשל ואחד הסעיפים בפידבק היה:
> Data is requested twice in development mode, this is due to using a useEffects in Strict Mode for querying - We’d recommend using a library to help with fetching and checking out this: https://react.dev/learn/you-might-not-need-an-effect
המאמר בקישור ארוך ומדבר על המון מקרים בהם אנשים משתמשים באפקטים בצורה לא נכונה. החלק שמדבר על אפקטים ומשיכת מידע משרת מתחיל בכותרת Fetching data. אבל בחלק זה הם דווקא מסבירים שמשיכת מידע משרת היא אכן אפקט ויש להשתמש ב useEffect בשבילה. ככה גם אני מלמד בקורסים פה וככה נכון לחשוב על הסיפור. הבעיה כאן היא לא שימוש במנגנון לא נכון (אפקט), אלא שימוש לא נכון במנגנון. האפקט לא צריך למשוך תמיד את המידע אלא לסנכרן בין המידע שעל השרת למידע ששמור בצד לקוח באיזשהו סטייט גלובאלי של האפליקציה.
השימוש בספרייה לשליפת מידע כמו react-query או swr או RTK Query לא מחליף את האפקט. לספריה אין מנגנון קסם לשליפת מידע מלבד useEffect, אלא שהאפקט שממומש בקוד הספריה עושה יותר עבודה מהמימוש הנאיבי שאותו משתמש הגיש.
(והסקרנים שרוצים לגלות מה בדיוק ספריית fetch עושה מוזמנים להציץ במימוש של swr. שימו לב שם ש
useIsomorphicLayoutEffect קורא ל useEffect או ל useLayoutEffect).
נ.ב. בעיה שניה שהיתה לי עם הפידבק הזה היא הפוקוס על הבעיה במצב פיתוח. נכון מימוש נאיבי של אפקט גורם למשיכת מידע מהשרת פעמיים במצב פיתוח, אבל זה לא חשוב בשום צורה וזאת לא הבעיה עם מימוש זה. רוב הזמן האפליקציה ממילא לא תרוץ במצב פיתוח. הבעיה במימוש היא שסטייט של קומפוננטה הוא לא המקום הנכון לשמור Cache של מידע שמגיע מהשרת כי הרבה פעמים אנחנו צריכים את אותו מידע במופעים שונים של אותה קומפוננטה או בקומפוננטות שונות על העמוד.1 420
מה יכול להשתבש ב next.js
אני מאוד אוהב את next.js ועדיין חושב שזו דרך טובה להתחיל פרויקט חדש, אבל גירסה 15 שתכף יוצאת מהתנור מזכירה לנו ש next לא מתאים לכל פרויקט ולכל צוות. הנה כמה נקודות ששווה לשקול לפני שמאמצים פריימוורק זה:
1. זזים מהר ושוברים דברים - בגירסה 13 הם יצאו עם רעיון חדש לארכיטקטורה של אפליקציה, בגירסה 14 זה כבר היה ברירת המחדל. בגירסה 14 הם בנו מנגנון Caching מסוים, בגירסה 15 הם כבר שינו חלק מברירות המחדל שלו. גירסה 15 תומכת כבר ב React Compiler שעדיין נמצא בבטא ובאופן כללי נקסט הוא המקום בו כל החידושים של ריאקט קורים. זה נחמד אם אנחנו מוכנים לזוז מהר ולשחק עם טכנולוגיות, זה פחות נחמד שנתיים אחרי כשאנחנו 4 גירסאות מאחור. כרגע הדגש של האנשים שבונים את נקסט הוא פרויקטים חדשים. שימו לב שאתם מוכנים למסע.
2. אם כבר יש לכם קוד צד שרת אז לרוב next.js יהיה מיותר. נכון יש לו אינטגרציה טובה מאוד עם קוד צד לקוח, אבל אם כל מה שה next יעשה זה לדבר ב REST עם קוד של שרת אחר אז חבל על התוספת לתשתית.
3. ה Deployment ל Vercel הוא פינוק, אבל מהר מאוד הם ידחפו אתכם לתוכנית בתשלום ושם יש Lock In מסוים, במיוחד אם אתם משתמשים ב APIs של ורסל כמו בסיס הנתונים או מערכת איחסון הקבצים שלהם.
4. נקסט לא תומך Deno.
5. בגלל התנועה המהירה יש עדיין מחסור בספריות וידע לגבי דברים שאמורים להיות פשוטים כמו טיפול בהעלאת קבצים או ניהול משתמשים. זה לא שאי אפשר לקנפג את זה, אבל צריך תמיד לוודא שהמדריך שקוראים מתאים לגירסה העדכנית ביותר של נקסט.
6. מנגנון Server Actions והאינטגרציה הכמעט חינמית בין ה Client ל Server עלול ליצור בעיות אבטחה כי אנחנו לא מבינים מספיק מה רץ איפה. בגלל שהתפיסה לגבי next היא שמדובר בכלי להרמה מהירה של מערכות אני בטוח שנראה הרבה בעיות אבטחה ככל שיותר אנשים ישתמשו בו, וכנראה גם בעיות אבטחה בפלטפורמה עצמה כמו הסיפור הזה.
סך הכל אני עדיין אוהב את נקסט אבל לא הייתי ממליץ עליו בצורה עיוורת. הוא יכול לתת פיתרון מאוד מהיר לפיתוח אפליקציית ריאקט והעלאה לרשת במיוחד בשילוב האיחסון של ורסל, אבל ככל שהמערכת תגדל דברים עלולים להסתבך.
1 420
לא רוצה באגים בחלק הזה
אם נשאל מתכנתים שלקחו ספריה כמו left-pad בשעתו למה הם לא פשוט העתיקו קוד מהרשת למערכת שלהם אפשר לדמיין שהתשובה תהיה דומה למשפט בכותרת - "אני לא רוצה באגים בחלק הזה בקוד".
וזה נכון. כשאנחנו מסתכלים על קטעי קוד בסטאק אוברפלו, או כאלה ש Chat GPT כתב בשבילנו, הם אף פעם לא מושלמים. הם לא מטפלים בכל המצבים, הם לא מתמודדים טוב עם שגיאות, הם תבנית, התחלה, והם דורשים עוד עבודה.
כשאנחנו חושבים על ספריה (קוד שלא רואים) אנחנו מדמיינים שמישהו כן טיפל בכל המצבים. ויש בזה גם הגיון טכני - הרי ברור שמי שמעלה קוד לסטאק אוברפלו צריך לשמור את הקוד קצר כדי שאנשים יוכלו לקרוא אותו. מי שכותב ספריה יכול להשתולל ולרשום את כל הפרטים כי זה לא צריך להיכנס לדף אינטרנט.
וכך אנחנו שמחים כש next.js בונה בשבילנו את כל מנגנון האינטגרציה בין קוד צד שרת ללקוח, ואפילו שעד לפני כמה חודשים כל ה Ajax הזה היה כתוב אצלי בקוד אני שמח לנשום לרווחה ולהיפרד מהקוד הזה, כי "אני לא רוצה באגים בחלק הזה".
גישה שנייה מעניינת היא זאת של shadcn - אנחנו עושים קוד בשביל שתעתיק אותו אליך לפרויקט. הסיסמה שלהם - The code is yours.
זה לא שיש גישה אחת יותר טובה מהשניה אלא שהמשחק הוא תיאום ציפיות - רוב הזמן אני מצפה שקוד בספרייה יהיה Production Ready וקוד שצריך להעתיק אותו יהיה בסיסי וידרוש תיקונים. חשוב לוודא שזה גם מה שכותבי הקוד המקוריים התכוונו אליו.
1 420
היום למדתי: קונפליקטים באוביקט style בריאקט
חשבתם פעם מה קורה כשמגדירים גם background וגם background-color ב inline style על קומפוננטה? נו, גם אני לא. עד שנתקלתי בבאג מוזר שדברים פתאום איבדו עיצוב. קודפן להמחשה:
https://codepen.io/ynonp/pen/jOoqKGx
<iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/jOoqKGx?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/jOoqKGx">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>
וזה הקוד:
import React from 'https://esm.sh/react@18.2.0'
import ReactDOM from 'https://esm.sh/react-dom@18.2.0'
const baseStyle = { backgroundColor: 'blue', color: 'white', width: '100%' };
const App = () => {
const [clicked, setClicked] = React.useState(false);
const style = clicked
? Object.assign({}, baseStyle, { background: 'red' })
: baseStyle;
return(
<div className="box"
style={style}
onClick={() => {
setClicked(v => !v);
}}
>
{JSON.stringify([style, clicked])}
</div>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))
לפי הקוד הריבוע מתחיל עם צבע כחול, לחיצה הופכת את הצבע לאדום והלחיצה הבאה תחזיר את העיצוב למה שהיה בהתחלה כלומר תחזיר את הצבע לכחול.
אלא שזה לא מה שריאקט רואה.
מבחינת ריאקט אחרי שהגדרתי background על האלמנט באוביקט ה style ואז מחקתי משם את ה background, ה background-color שהיה שם קודם מפסיק לעבוד ואנחנו רואים שהקופסה הופכת לבנה. עוד לחיצות לא יהפכו אותה יותר לכחולה שוב.
כמו תמיד אפשר לתקן את זה עם key שונה לשני המצבים, אבל זה לא מה שרצינו שם. ה Takeaway המרכזי כאן הוא לשים לב לא לערבב background עם background-color, ובכל מקרה עדיף להשתמש ב tailwind ולא להיכנס לפינות האלה.
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
