ToCode @tocodeil Channel on Telegram

ToCode

@tocodeil


טיפים קצרים למתכנתים מאת ינון פרק

ToCode (Hebrew)

האם אתה מתכנת שמחפש טיפים והוראות קצרות כדי לשפר את הכישורים שלך? אז הצטרף אלינו לערוץ הטלגרם ToCode שנועד למתכנתים מתחילים ומנוסים כאחד. בערוץ זה תמצא טיפים והדרכות במגוון נושאים כמו פיתוח אפליקציות, תכנות בשפות שונות, ועוד. האיש מאחורי ערוץ ToCode הוא ינון פרק, מתכנת מנוסה שיעזור לך להבין מושגים מורכבים בצורה פשוטה וברורה. אם אתה רוצה להשפיע על העולם וליצור יישום חדשני, הצטרף עכשיו לערוץ ToCode והתחל ללמוד ולהתפתח כמתכנת מקצועי. תהנה מהמגוון והאיכות שהערוץ מציע!

ToCode

29 Jan, 05:08


חידת Vue - איך עובד Scoped Style
נכתוב את הקוד הבא בקומפוננטת App.vue:

<script setup>
import Child from './Child.vue';
</script>

<template>
<p class="red">Hello World from App</p>
<Child />
</template>

<style scoped>
.red {
color: red;
}
</style>


וזה Child.vue:

<script setup></script>

<template>
<button class="red">
Hello World from Child
</button>
</template>


אפשר לראות את הדוגמה לייב בקישור הזה:
https://tinyurl.com/4nff62kp

עכשיו לשאלות - למה הטקסט בכפתור מופיע באדום? ואיך אפשר "לנתק" אותו מהעיצוב של App כדי שלא יקבל את הגדרת האדום משם?

שימו לב שהגדרות העיצוב הן בבלוק styled scoped ושזו לא הבעיה שמתוארת כאן:
https://www.tocode.co.il/blog/2021-10-watch-out-inherited-css, למרות שגם הבעיה בפוסט ההוא מעניינת.

ToCode

29 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-vue-scoped-style-leak

ToCode

28 Jan, 05:07


ואם בכל זאת אני רוצה להעביר משתנה ריאקטיבי ב Vue לילדים?
הקוד הבא ב Vue לא עובד, או לפחות לא עושה את מה שהתכוונתי. קומפוננטה עליונה:

<script setup>
import { ref } from 'vue'
import Child from './Child.vue';
const x = ref(10)
</script>

<template>
<Child :x="x" />
</template>


וקומפוננטת Child:

<script setup lang="ts">
const {x} = defineProps<{x: any}>();

function inc() {
x.value++;
}
</script>

<template>
<p>x = {{x}}</p>
<button @click="inc">+1</button>
</template>


נראה כאילו Child מקבל אוביקט ריאקטיבי x ומעלה את ערכו ב-1. בפועל העברת הפרמטר דרך props מעבירה את ה value של האוביקט הריאקטיבי, כלומר בתוך Child המשתנה x הוא מספר. למספר אין שדה .value ולכן הקוד נכשל.

מה בכל זאת אפשר לעשות?
ל Vue יש שני פיתרונות טובים למצב הזה: הראשון הוא ש Child ידווח על אירוע ו Parent יטפל באירוע וישנה את x, והשני זה מקרו בשם defineModel שבגדול מטפל בדינמיקה הזאת בשבילנו אבל מבחינת הקוד נראה לגמרי כמו העברת משתנה ריאקטיבי לקומפוננטה. שניהם פיתרונות טובים.

הפיתרון ש Vue לא רצו שניישם הוא לעטוף את המשתנה הריאקטיבי באוביקט. מוזר אבל עובד:

<script setup>
import { ref } from 'vue'
import Child from './Child.vue';
const x = ref({value: 10})
</script>

<template>
<Child :x="x" />
</template>


<script setup lang="ts">
const {x} = defineProps<{x: any}>();

function inc() {
x.value++;
}
</script>

<template>
<p>x = {{x.value}}</p>
<button @click="inc">+1</button>
</template>


ברור למה זה עובד - ref הוא רקורסיבי ולכן גם value שלו יהיה משתנה ריאקטיבי, אבל כשמעבירים אוביקט דרך props ויו לא מבטל את הריאקטיביות של השדות הפנימיים בצורה רקורסיבית ולכן Child יכול לשנות את המידע.

מה שלדעתי מעניין בדוגמה הזאת הוא כמה קל "לפתור" את הבעיה בצורה לא נכונה אולי אפילו בלי לשים לב שזה פיתרון לא נכון. שתי הדרכים ה"נכונות" לפיתרון דורשות היכרות עם מנגנונים נוספים של vue, ודווקא הפיתרון העקום הוא בעצם הפשוט ביותר, מהבחינה שהוא לא דורש עוד מנגנונים.

הלקח שלי מהדוגמה הזאת הוא שרק בגלל שפיתרון מסוים הוא היחיד שאני רואה זה עדיין לא אומר שזה הפיתרון הנכון. בשביל להגיע לפיתרון הנכון צריך לפעמים להמשיך לקרוא בתיעוד, להתלבט בין כמה אפשרויות ולהבין את היתרונות והחסרונות של כל אפשרות.

ToCode

28 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-vue-pass-reactive-data-down

ToCode

27 Jan, 05:08


מאחר ו vercel תומך ב nuxt אין לנו בעיה לעשות deploy בלחיצה אחת ולקבל את כל הפינוקים של vercel. בסיס הנתונים הוא עדיין neon וגם הוא שמור בענן.

ToCode

27 Jan, 05:08


תבנית פרויקט: nuxt, drizzle, auth0
אתמול פירסמתי פה תבנית לפרויקט React שמשתמש ב Next.js ומכיל קומפוננטות צד שרת, משיכת מידע מבסיס נתונים וניהול משתמשים עם auth0. היום נראה את החלק השני של הפוסט ונבנה את אותה תבנית עבור nuxt ליישומי vue. הקוד כאן:

https://github.com/ynonp/nuxt-drizzle-auth0-demo

בואו נראה מה יש בריפו.

ניהול משתמשים
ניהול המשתמשים במערכת מתחבר ל auth0 כדי שאנחנו לא נצטרך להתאמץ, בדיוק כמו בדוגמת ה next. זה אומר שבצד שלנו אנחנו צריכים לטפל באירוע של משתמש שהתחבר או התנתק, ונוכל להשתמש בפונקציות של auth0 כדי להבין אם יש משתמש מחובר.

הקוד מתחיל בקובץ server/routes/auth/auth0.get.ts:

export default defineOAuthAuth0EventHandler({
async onSuccess(event, { user, tokens }) {
await setUserSession(event, {
user: {
id: user.sub
}
})

return sendRedirect(event, '/hello')
},
// Optional, will return a json error and 401 status code by default
onError(event, error) {
console.log(\error in login\);
return sendRedirect(event, '/')
},
})


זה הקוד שמטפל באירועים שמגיעים מ auth0 כשמשתמש מתחבר למערכת. בצד של nuxt אני לוקח את user.sub שזה מזהה המשתמש ושותל אותו ב session כדי שנוכל לגשת אליו מהקומפוננטות ומנתיבי צד שרת.

הקומפוננטה הבאה בקובץ pages/hello.vue כבר כוללת קוד שמאפשר חיבור למערכת:

<script setup>
import People from '../components/People.vue';

const { loggedIn, user, session, fetch, clear } = useUserSession()
</script>

<template>
<div v-if="loggedIn">
<h1>Welcome {{ user.id }}!</h1>
<p>Logged in since {{ session.loggedInAt }}</p>
<button @click="clear">Logout</button>
<People />
</div>
<div v-else>
<h1>Not logged in</h1>
<a href="/auth/auth0">Login with Auth0</a>
</div>
</template>


בשביל להתחבר למערכת צריך רק לעבור לנתיב /auth/auth0 ובשביל להתנתק צריך להפעיל את הפונקציה clear שמנקה את ה Session. כל הקוד הזה משתמש במודול auth-utils של nuxt.

בשביל החיבור ל Auth0 יש להגדיר את משתני הסביבה שלהם בקובץ ה .env. אלה המפתחות שעליכם להגדיר בקובץ כדי שהפרויקט יעבוד:

NUXT_SESSION_PASSWORD=
NUXT_OAUTH_AUTH0_CLIENT_ID=
NUXT_OAUTH_AUTH0_CLIENT_SECRET=
NUXT_OAUTH_AUTH0_DOMAIN=
DATABASE_URL=


חיבור לבסיס נתונים
החיבור לדריזל משתמש בדיוק באותו קוד שהראיתי אתמול לגבי next. בצד של nuxt העברת המידע מבסיס הנתונים לפרונט אנד קצת שונה:

1. יש להגדיר נתיב צד שרת שימשוך את הנתונים מבסיס הנתונים.

2. יש להפעיל את הפונקציה useAsyncData של nuxt מתוך הקומפוננטה וממנה למשוך את המידע מהנתיב שיצרנו.

בשביל הסעיף הראשון אני יוצר קובץ server/routes/api/people.get.ts עם התוכן הבא:

import { usersTable } from '@/db/schema';
import { db } from "@/db/drizzle";

export default eventHandler(async (event) => {
try {
const res = await requireUserSession(event)
const users = await db.select().from(usersTable);
return users;
} catch (err) {
return [];
}
})


אם המשתמש מחובר הוא יקבל את רשימת המשתמשים מבסיס הנתונים ובשביל המשחק אם המשתמש לא מחובר נחזיר לו רשימה ריקה.

הקומפוננטה שמשתמשת בנתיב זה שמורה בקובץ components/People.vue וזה הקוד שלה:

<script setup lang="ts">
const { data, status, error, refresh } = await useAsyncData(
'people',
() => $fetch('/api/people', {
credentials: 'include',
headers: useRequestHeaders(['cookie']),
})
)
</script>

<template>
<h1>People. Status = {{ status }}</h1>
<ul>
<li v-for="person in data">{{ person.email }}</li>
</ul>
</template>

<style lang="css" scoped>
</style>


השימוש ב fetch מתוך useAsyncData נראה כמו בקשת רשת אבל למעשה זו בקשה מקומית כי הקוד רץ בצד השרת ב SSR. כשהקוד יישלח לדפדפן הוא יכלול כבר את התשובה (כלומר את רשימת המשתמשים). נשים לב שאני חייב להעביר את ה cookie מהדפדפן בשביל לשמור על ה Session. שאר הקוד די פשוט הוא לוקח את רשימת המשתמשים מהנתיב שהגדרנו ומציג את כתובות המייל שלהם על המסך.

ToCode

27 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-project-template-nuxt-auth0-drizzle

ToCode

26 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-next-drizzle-auth0-demo

ToCode

25 Jan, 05:06


מה עושים עם הקוד שלא מבינים?
דילמה אמיתית. והיא תמיד היתה.

אחד המקומות המפחידים ביותר מתכנתים נקרא Tutorial Hell. להיות ב Tutorial Hell אומר שאתה תקוע לראות מדריכים שמסבירים איך לכתוב דברים אבל לא משנה כמה מדריכים תראה לא תצליח לכתוב קוד חדש בעצמך. היום כש ChatGPT יכול לכתוב לך מדריך שפותר באופן מיידי כל בעיה בקוד ה Tutorial Hell לא נראה כל כך רע, מה שאומר שאנחנו נשארים בו הרבה יותר זמן.

הגורם ל Tutorial Hell הוא המעגל הזדוני של הצלחה מדומיינת. אני יכול לגרום למערכת לעשות מה שאני רוצה באמצעות ביצוע הוראות שקראתי במדריך באינטרנט, אפילו אם אני לא מבין את ההוראות עצמן. מספיק ללכת צעד צעד עם המדריך (או בעולם של היום לתת ל AI לשכתב חלקים מהמדריך תוך כדי תנועה אם דברים לא עובדים) ויש לי מערכת. ובכל זאת מעניין לשאול, מה עושים עם כל הקוד הזה שאנחנו קוראים, מעתיקים ולא מבינים?

בואו נחלק את התשובה לפי סוג הפרויקט. בפרויקט לימודי המטרה שלנו מראש היתה ללמוד איך דברים עובדים, ולכן נשמע הגיוני להשתמש כמה שפחות ב Tutorials. קוד שאני לא מבין הוא חסר ערך בקונטקסט הזה, כי ממילא אני בונה את המערכת רק בשביל ללמוד איך דברים עובדים. כיוון טוב לפרויקט לימודי יהיה לכן לקרוא טוטוריאלס או תשובות של Chat GPT רק במידה וזה נותן כיוון למאמרים יותר מעמיקים על אותו נושא, אחרי זה ללכת לקרוא את התיעוד והמידע היותר מעמיק, ובסוף לכתוב את הקוד בעצמך בלי להסתכל בטוטוריאל ובלי להעתיק קוד. אם דברים לא עובדים אפשר לחזור לקרוא במדריך אבל המטרה בסוף תהיה להיות מסוגלים לכתוב את הכל מהראש וכמובן להסביר לעצכם מה המשמעות ומה עושה כל שורה.

בפרויקט פרודקשן התשובה נראית יותר מורכבת, כי לכאורה שימוש ב Tutorial חוסך זמן לימוד ומאפשר לסיים משימות מהר יותר. אני מכיר אפילו חברות שמתגמלות מפתחים לפי כמה משימות הם מסיימים בחודש ולכן מתכנתים שרוצים בונוס רק ירוויחו מהעתקת קוד שהם לא מבינים לפרויקט פרודקשן. אבל גם פה כשעוצרים לחשוב על זה אנחנו מבינים שיש לנו עבודה חשובה יותר מהעתקת קוד. בפרויקט פרודקשן יש שני אתגרים שעדיין דורשים התערבות אנושית:

1. רגישות לטעויות או לפגיעה בנתוני פרודקשן.

2. התאמה לשיטות העבודה הקיימות בפרויקט, הימנעות מכפל קוד ויצירת אבסטרקציות חדשות כדי שהקידוד בהמשך יהיה מהיר יותר.

לכן גם בפרויקט פרודקשן העתקת קוד בלי להבין מ Tutorial או מ Chat GPT פוגעת בפרודוקטיביות לטווח ארוך.

הפרויקט היחיד שאני מרגיש בנוח להעתיק אליו קוד שאני לא מבין הוא פרויקט הוכחת יכולת (Proof of Concept), כשאני רוצה לבנות משהו מהר כדי להראות ללקוח או לחבר, או להבין לעצמי מה היכולות של ספריה מסוימת. במצב כזה אני עובד עם ספריות וכלים שאני לא הכי מכיר ורק רוצה לראות אם הם יכולים לפתור לי את הבעיה ואם כדאי לי בכלל ללמוד אותם, ולכן אני מרגיש יותר נוח להעתיק קוד שבאותו רגע אני לא מבין, מתוך הבנה שאם הכלים האלה יעמדו במבחן אצטרך ממילא ללמוד אותם לעומק ואז כבר אזרוק את קוד ה POC כדי לבנות את המנגנון מחדש במערכת האמיתית שלי.

נ.ב. הפוסט הזה מדבר ספציפית על קוד שאני מעתיק בלי להבין. הרבה פעמים אני אשתמש ב Chat GPT כדי לכתוב קוד שהייתי יכול לכתוב לבד ושאני מבין בדיוק איך הוא אמור להיראות, לדוגמה כשאני מבקש מ Chat GPT לכתוב את רשימת כל הימים בשבוע בתור מערך ב JavaScript ברור שהייתי יכול לכתוב את זה לבד אבל הוא חסך לי הקלדה. כלי ה AI הם כלים מדהימים ואני שמח להשתמש בהם כמה שיותר, כל עוד זה באמת תורם לאיכות הקוד ומהירות הפיתוח.

ToCode

25 Jan, 05:06


https://www.tocode.co.il/blog/2025-01-code-you-dont-understand

ToCode

24 Jan, 05:08


הפחד מעבודה לא מושלמת
הפחד מעבודה לא מושלמת הוא אמיתי. לפעמים הוא גורם לנו לוותר על פרויקט לפני שאנחנו אפילו מתחילים (כי אין לי זמן עכשיו לסיים את זה או להשקיע מספיק), באמצע העבודה (חשבתי שזה יהיה רעיון טוב אבל בעצם זה לא שווה את המאמץ, יש לי רעיון חדש טוב יותר) ואפילו אחרי שיש משהו מוכן וצריך רק להראות אותו (זה לא מספיק טוב).

המחשבות שגורמות לפחד נעות סביב:

1. יש רק הזדמנות אחת וחבל לי לפספס אותה.

2. אם אזרוק את מה שבניתי תהיה לי יותר מוטיבציה לנסות שוב.

3. חבל לבזבז זמן או מאמץ על פרויקט שלא יצליח, עדיף להתמקד בדברים אחרים שאולי כן יהיו מושלמים.

4. אנשים סביבי שיראו פרויקט בינוני יסיקו מזה עליי ועל הרמה שלי כמפתח.

אף פעם לא קל לשנות סיפורים בראש אבל הנה כמה רעיונות ששווה לנסות לאמץ במקום:

1. החיים מלאים הזדמנויות, וההזדמנויות באות לעתים יותר קרובות לאנשים שמוכנים להתאמץ בשביל לנצל אותן.

2. הפורטפוליו שלי נמצא תמיד בצמיחה. כל פרויקט הוא הבסיס לפרויקט הבא.

3. חבל לבזבז עוד שעה בנטפליקס או בפייסבוק, או אפילו בקורס. הלימוד הכי טוב הוא דרך עשייה.

4. כדי לבנות פרויקט טוב עליי לבנות קודם פרויקטים בינוניים ואפילו גרועים.

אימוץ תפיסת עולם של צמיחה יכול לפתוח את הדלת ליותר עשייה ולעשייה טובה יותר.

ToCode

24 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-perfect

ToCode

23 Jan, 05:08


היום למדתי: v-bind ו style scoped
ויו מאפשר להשתמש ב v-bind בתוך בלוק CSS כדי לחבר קוד עיצוב למידע ריאקטיבי. זה עובד ממש נחמד וזאת הדוגמה מהאתר שלהם:

<script setup>
import { ref } from 'vue'
const theme = ref({
color: 'red',
})
</script>

<template>
<p>hello</p>
</template>

<style scoped>
p {
color: v-bind('theme.color');
}
</style>


אבל מה קורה כשמנסים את זה בלי scoped? כלומר מה אם ננסה:

<script setup>
import { ref } from 'vue'
const theme = ref({
color: 'red',
})
</script>

<template>
<p>hello</p>
</template>

<style>
p {
color: v-bind('theme.color');
}
</style>


בשביל להבין את התשובה צריך להבין איך v-bind בתוך CSS עובד - כש vue רואה v-bind בתוך CSS הוא מגדיר משתנה CSS על האלמנט הראשי של הקומפוננטה עם הערך שכתוב בתוך ה v-bind והביטוי המלא v-bind('theme.color) מוחלף בשיערוך המשתנה.

עכשיו כבר ברור למה זה לא עובד כמו שהיינו מצפים כשבלוק ה style אינו scoped. כן כלל ה CSS שנכתוב יכול לפעול על כל אלמנטי ה p בעמוד, אבל כל אלמנטי ה p שלא נמצאים בתוך הקומפוננטה לא מכילים את משתנה ה CSS המיוחד ולכן לא יקבלו את הגדרת הצבע.

ToCode

23 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-vue-scoped-style-vbind

ToCode

22 Jan, 05:08


כשהמחשב שוכח להגיד שיש בעיה
הבעיות שהכי מעצבן למצוא הן אלה בהן נראה שהמחשב מרמה אותנו בכוונה. נראה כאילו הוא יודע שהוא לא עושה מה שהתכוונתי אבל בכל זאת הוא יושב שם בצד ומגחך בזמן שאני מנסה להבין למה מה שביקשתי זה לא מה שבאמת התכוונתי.

הסיפור שלי היום הוא על התרגיל הכי קל בינתיים ב Advent Of Code 2024 הוא יום מספר 3. בתרגיל הזה הקלט מורכב מרצפים כאלה:

xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))


ואנחנו צריכים לחשב את סכום המכפלות אבל כשיש פקודת don't() מפסיקים לספור עד שמוצאים פקודת do() ואז ממשיכים בחישוב, כלומר בקלט לדוגמה המכפלות הרלוונטיות הן:

mul(2,4), mul(8,5)


והסכום הוא 48. קל? בטח. אפשר למחוק את כל מה שבין don't ל do ולסכום את מה שנשאר. הנה ה Ruby הראשון שניסיתי לכתוב:

str = File.read('input.txt').gsub(/don't\(\).*?(?:do\(\)|$)/, '')
pp str.scan(/mul\((\d{1,3}),(\d{1,3})\)/).map { _1.to_i * _2.to_i }.sum


רואים את הבעיה? קחו רגע לחשוב על זה. לי זה לקח אפילו יותר מרגע.

הסיפור כאן הוא שהקלט יכול להכיל יותר משורה אחת. במצב כזה אולי יהיה don't() בשורה אחת אבל ה do() יגיע רק בשורה הבאה. סימן הדולר בביטוי הרגולארי וגם הנקודה יודעים לטפל בקלט של שורה אחת בלבד ולכן הקוד נשבר. המוקש הוא ש gsub לא מתלונן כשהוא מקבל כמה שורות ופשוט מחליף את הטקסט בכל השורות, לדוגמה:

3.3.5 :010 > str = "one\ntwo\nthree\n"
=> "one\ntwo\nthree\n"
3.3.5 :011 > puts str.gsub(/t/, 'T')
one
Two
Three
=> nil


בגלל זה הקוד המקורי שכתבתי כן עושה משהו ואפילו מוחק את כל ה don't-ים מהקלט, אבל לא עושה את העבודה בצורה מספיק מדויקת בגלל אותה בעיה של מעבר שורות.

פיתרון? נו ברגע שרואים את זה אפילו ChatGPT יכול לתקן - רק צריך לעבור לגירסת השורות-רבות של הביטוי הרגולארי כלומר להוסיף מאפיין m ולהפוך את הדולר, שמסמן סוף שורה, ל z שמסמן את סוף המחרוזת. זה הקוד:

str = File.read('input.txt').gsub(/don't\(\).*?(?:do\(\)|\z)/m, '')
pp str.scan(/mul\((\d{1,3}),(\d{1,3})\)/).map { _1.to_i * _2.to_i }.sum


או בגירסת שורה-אחת כמו שאוהבים ברובי:

pp File.read('input.txt')
.gsub(/don't\(\).*?(?:do\(\)|\z)/m, '')
.scan(/mul\((\d{1,3}),(\d{1,3})\)/)
.map { _1.to_i * _2.to_i }
.sum

ToCode

22 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-aoc2024day3

ToCode

21 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-makers

ToCode

21 Jan, 05:07


תוכנית המנטורינג לבניית פרויקט - פרטי המסלול
שבוע שעבר פרסמתי כאן שאני מתכנן לפתוח קבוצת מנטורינג לפיתוח פרויקטים. במהלך השבוע ובעקבות שיחות טובות עם כמה מכם סגרתי תוכנית עבודה מסודרת לסדנה, ואני שמח לפרסם פה את המבנה המדויק שלה:

מטרת הסדנה היא לתת לך את המקום, הסביבה והדחיפה להפוך את הידע התיאורטי שלך בפיתוח לידע מעשי דרך עבודה על פרויקט שהוא כולו שלך מקצה לקצה.

הכנות לסדנה במהלך פברואר אני אעבוד אחד-על-אחד עם כל אחד מהמשתתפים כדי לייצר תוכנית עבודה אישית שלך לפיתוח הפרויקט. התוכנית תתאים לזמן פיתוח של חודש עבודה בהשקעה של כ 8-10 שעות בשבוע ותכיל איפיון מפורט והערכות זמנים. סגירת תוכנית עבודה במהלך פברואר היא תנאי לכניסה לסדנה.

מהלך הסדנה קבוצת העבודה תפעל במהלך חודש מרץ ובשני מישורים: הנחיה אישית ולימוד הדדי קבוצתי.

במישור ההנחיה האישית אני אהיה שם אתכם במהלך הפיתוח כדי לוודא שאתם מצליחים לעמוד בתוכנית שיצרנו, לעזור כשאתם נתקעים ולעבור על הקוד כדי לראות שאתם בונים דברים ברמה גבוהה ושתהיו גאים בתוצאה.

במישור הקבוצתי הקוד שנכתוב יישמר במאגר גיטהאב פתוח לשאר חברי הקבוצה. אנחנו נשתף אחד עם השני אבני דרך מרכזיות בפיתוח, אתגרים מעניינים שעבדנו עליהם וכלי עבודה בהם אנחנו משתמשים ודרך שיתוף הידע נוכל ללמוד ממפתחים כמונו שעובדים על פרויקטים שונים. אנחנו נקרא את הקוד אחד של השני ונשאיר Code Reviews הדדיים. העבודה עם מפתחים נוספים בקבוצה תעזור לכם לייצר קשרים שיעזרו לכם גם בהמשך ותחשוף אתכם לטכנולוגיות ושיטות עבודה חדשות.

מבנה הפרויקט כל משתתף ומשתתפת יעבדו על פרויקט משלהם בטכנולוגיה לבחירתם, וכן אני מצפה שיהיה סלט טכנולוגי מעניין. יחד עם זאת מבנה העבודה על הפרויקט יהיה דומה אצל כולם: בשבוע הראשון נבנה את הגירסה הראשונה על מחשב הפיתוח, בשבוע השני נעבוד על Deployment ו CI/CD ונקים Github Actions כדי לוודא שיש לנו דרך מהירה מפיתוח לפרודקשן והחל מהשבוע השלישי נתחיל לשלב פיצ'רים מתקדמים יותר של סביבת הפיתוח שבחרנו כמו Queues, Caches, Load Time Optimisations, ושאר רעיונות שיהפכו את הפרויקט שלנו למעניין. השבוע הרביעי יוקדש לבניית דף נחיתה ובדיקות אוטומטיות לפרויקט כדי שיהיה קל יותר למפתחים אחרים להצטרף ולתרום.

סיום הסדנה בסיום הסדנה יהיה לכם פרויקט שבניתם לבד בסביבת פרודקשן ותהיה לכם שיטת עבודה מסודרת לפיתוח והעלאת פיצ'רים נוספים, אם תחליטו להמשיך לעבוד על הפרויקט גם בהמשך.

הסדנה מתאימה לאלה מכם שיש להם כבר ידע בפיתוח, שעובדים היום בצוות ואפילו עובדים על מוצר אבל במהלך היום יום בעבודה מפתחים רק חלק מהמוצר הגדול ורוצים לחוות עבודה על מוצר מקצה לקצה.

אם זה נשמע כמוכם אני מאוד ממליץ לנצל את ההזדמנות ולהצטרף. יש עוד מספר מקומות אחרונים, זה מחזור ראשון אז המחיר ידידותי והקבוצה תהיה קטנה ואינטימית. לפרטים והצטרפות השאירו לי הודעה ונקבע שיחה מסודרת.

ToCode

20 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-next-big-thing

ToCode

20 Jan, 05:08


הרעיון הגדול הבא
ברור שיהיה טוב אם הרעיון הבא שלך יהיה להיט ויראלי. ברור שהיית רוצה לכתוב את הוורדל הבא, את הטוויטר הבא, את הפלאפי בירד הבא או אפילו את ה tailwind הבא. ברור.

לצערנו אין לנו דרך טובה ליצור להיטים. מה שמאפיין להיטים זה שהם לא צפויים, שהם כאילו באו משום מקום ובהתחלה לא ברור מה אנשים מוצאים בהם. אני זוכר שראיתי את האייפון הראשון וחשבתי "למי אכפת?, הנוקיה שלי טוב יותר". אני זוכר שראיתי את טיילווינד בפעם הראשונה וחשבתי "מי ירצה להשתמש בזה?". אין מצב שהרעיון הזה יצליח. וכן כמו שאלה הצליחו היו המון המון פרויקטים שלא התרוממו, שאנשים לא התלהבו מהם למרות שהיוצרים שלהם מאוד השקיעו ומאוד רצו ומאוד קיוו. הרעיון הגדול הבא יגיע, אבל אנחנו לא יודעים מאיזה כיוון.

והבעיה בלחפש את הרעיון הגדול הבא היא שזה יכול להיות משתק. בגלל שהרעיון שלי כזה פשוט, רגיל ולא מקורי אולי אין טעם בכלל לבנות אותו, למי אכפת מעוד מערכת ווב, אני אפילו לא בטוח שהחברים שלי ירצו להשתמש בזה. עדיף להמשיך לחשוב עד שיגיע הרעיון הגדול הבא.

יותר טוב להתאמן על בניית רעיונות אבל בצורה מהירה. לא פרויקט צד שעובדים עליו קצת כל שבוע במשך 5 שנים בלי שיראה אור יום, אלא פרויקט שעולה לאוויר אחרי חודש כבר אפשר להשתמש בו. השימוש היום יומי בפרויקט יעזור לך לחדד את הרעיון ולהבין אם צריך להוסיף פיצ'רים או לזרוק אותו ולהמשיך לפרויקט הבא. כשאנחנו מתרגלים להעלות מהר רעיונות לאוויר אנחנו מגדילים את הסיכוי שנצליח להגיע לרעיון הגדול הבא, ומגדילים מאוד את הסיכוי שנצליח להביא אותו לידי מימוש.

נ.ב. היכולת לעבור בזמן סביר מרעיון למוצר חשובה לעבודה על פרויקטי צד ויזמות - אבל לא פחות מכך לעבודה של שכירים. דווקא בתור שכירים הרבה יותר קל לנו למצוא רעיונות כי אנחנו נתקלים כל היום באתגרים ופיתרונות עקומים בעבודה, והרבה יותר קשה לנו לקבל מהבוס זמן לעבוד על הרעיונות שלנו. שכירים שיודעים לקחת רעיון ולהפוך אותו מהר למוצר (לפני שמישהו ישים לב) מקבלים הזדמנות להביא את המוצר שלהם הכי מהר לאנשים שצריכים אותו.

ToCode

18 Jan, 05:06


תודה שאמרת לי
בגלל ש AI הוא לא בן אדם, כשהוא כותב קוד הוא הרבה פעמים מתעלם מדברים שהוא כבר יודע. לדוגמה בן אדם שכותב קוד נניח בפלאסק ומכיר את הפונקציה url_for יעדיף תמיד להשתמש בה בלינקים ויכתוב תגית a עם:

<a href="{{ url_for('tasks') }}">All Tasks</a>


אבל AI שכותב קוד, למרות שהוא מכיר את url_for יותר טוב ממך, עדיין יכול לכתוב:

<a href="/tasks">All Tasks</a>


ואז כשאתה שואל אותו משהו כמו "למה לא השתמשת ב url_for" הוא מיד מתנצל עם איזה "תודה שאמרת לי וכל הכבוד ששמת לב, והנה אני מתקן את הקוד".

כשזה קורה התגובה הראשונה שלי היא להתעצבן על המחשב - מה זה אומרת תודה שאמרת לי!? אם ידעת ש url_for עדיף למה לא כתבת את זה מההתחלה???

אבל השנים לימדו אותי שבעיות לא נפתרות כשצועקים על המחשב. במקום אני משתדל לזכור את הפרומפט הבא אחרי שקיבלתי מימוש מ AI:

> Please perform a full code review

רוב הזמן באיטרציה השניה נקבל קוד ברמה יותר גבוהה.

ToCode

18 Jan, 05:06


https://www.tocode.co.il/blog/2025-01-your-right

ToCode

17 Jan, 05:07


ההערה הזאת היא בכלל פיצ'ר
נניח שבניתם פיצ'ר של חיפוש לאתר. הפיצ'ר על עוד בפרודקשן והתעסקתם עם המון דברים במהלך הפיתוח ואתם במתח לראות מי יחפש מה ואם המנגנון יצליח למצוא תוצאות רלוונטיות, ואז אתם מקבלים ב Code Review את ההערה:

> Consider adding rate limiting to the search endpoint

יודעים מה? בואו נסבך עוד קצת את הסיטואציה, ונניח שמי שנתן את ההערה הוא בכלל AI עכשיו לשאלה, מה עושים עם זה?

מצד אחד ברור ש Rate Limit זה רעיון טוב, מי יודע איזה בעיות יהיו אם בוטים מרושעים יפציצו את ה Endpoint בבקשות חיפוש, וזה גם נכון שנקודת קצה של חיפוש יכולה להיות יותר רגישה למתקפות DoS כי חיפוש דורש יותר משאבי מערכת. מצד שני אנחנו כבר עובדים על החיפוש הזה תקופה ארוכה, עד עכשיו התמקדמו בלהחזיר את התוצאות הטובות ביותר ואנחנו עדיין לא בטוחים שאנחנו בכלל בכיוון לגבי זה. למי יש זמן להוסיף עכשיו מנגנון Rate Limit שאולי יקלקל דברים או ידחה את העלייה לפרודקשן רק בגלל סיכון אבטחה פוטנציאלי עתידני. מצד שלישי למערכות שונות יש רגישות שונה לתקלות, אולי אתם חיים בעולם שלא יסלח לכם על נפילה בעקבות מתקפת DoS.

אני חושב שיותר נכון להסתכל על הערה מהסוג הזה בתור Feature Request יותר מאשר הערת מימוש. במקרה של Rate Limit זה משהו שצריך לעלות בעקבות Security Review על גירסה. לפעמים ה Security Review קורה על כל גירסה לפני העלאה ואז נקבל דוח ונתרגם אותו למשימות שיהפכו לקומיטים, לפעמים ה Security Review יבוא אחרי מתקפת ה DoS הראשונה.

הפרדת ההרחבות למנגנון אחר (אולי לפתוח כרטיס חדש בג'ירה, אולי לתקן את תהליך העבודה ולהוסיף שלבים) יכולה לעזור לנו להתמקד בפיתוח ולהביא מהר יותר קוד טוב יותר לפרודקשן.

ToCode

17 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-actually-thats-a-feature

ToCode

16 Jan, 05:07


חידת Vue: ריאקטיביות
נתון קוד Vue הבא:

<script setup lang="ts">
import {ref, computed} from 'vue';

const data = { count: 0 };
const value = ref(data);
function btn1() {
data.count = 5;
}

function btn2() {
value.value.count++;
}
</script>

<template>
<div>
<p>
Value is: <span>{{ value.count }}</span></p>
<button @click="btn1">Button 1</button>
<button @click="btn2">Button 2</button>
<hr />
</div>
</template>


מה יופיע על המסך אחרי לחיצה על שני הכפתורים לפי הסדר? מה יקרה אם נלחץ רק על הראשון? למה זה קורה?

מה יקרה אם נשנה את התבנית ל:

<template>
<div>
<p>
Value is: <span>{{ data.count }}</span></p>
<button @click="btn1">Button 1</button>
<button @click="btn2">Button 2</button>
<hr />
</div>
</template>


מה יהיו ערכי המשתנים אחרי לחיצה על הכפתורים? מה יופיע על המסך?

ToCode

16 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-vue-reactivity-quiz

ToCode

15 Jan, 05:07


טיפ טייפסקריפט: יבוא קבצי JSON גדולים
לטייפסקריפט יש פיצ'ר ממש חמוד לעבודה עם קבצי JSON שנקרא resolveJsonModule. אנחנו מגדירים ב tsconfig.json את האופציה באופן הבא:

{
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": ["src"]
}


ואז כותבים:

import settings from './settings.json';


ובאופן אוטומטי טייפסקריפט קורא את הקובץ settings.json, מזהה לבד את טיפוס הנתונים בקובץ ומגדיר את האוביקט settings בצורה נכונה, כלומר אם הקובץ שלי מכיל את התוכן:

[{
"repo": "TypeScript",
"dry": false,
"debug": false
}]


ואני אנסה לכתוב בקוד:

import settings from './settings.json';
console.log(settings[0].foo);


טייפסקריפט יצעק שאין מאפיין foo לאוביקטים במערך. קסם נכון? כמעט. הבעיה מתחילה כשמנסים לטעון אוביקט JSON גדול, למשל 10 מגה. קובץ כזה מאט את כל ה VS Code ובסוף טייפסקריפט לא מצליח לקרוא אותו כי הוא גדול מדי ואנחנו תקועים בלי הגדרת טיפוסים.

בעבודה עם קבצי JSON גדולים שווה להכיר את האופציה allowArbitraryExtensions של טייפסקריפט, אופציה שבעזרתה נוכל להגדיר את הטיפוס של ה JSON ידנית, נחסוך לטייפסקריפט עבודה וכך נקבל VS Code מהיר יותר והגדרות טיפוסים אמינות. ה tsconfig.json נראה ככה:

{
"compilerOptions": {
"allowArbitraryExtensions": true,
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": ["src"]
}


ובתיקיית src אנחנו יוצרים קובץ בשם של ה json אבל עם d באמצע וסיומת ts, לדוגמה אם ל json שלי קוראים deposits.json אז אני יוצר קובץ בשם deposits.d.json.ts. בקובץ הגדרת הטיפוס אני כותב:

export interface SavingsData {
INTERESTSDATE: string;
SAVINGSPROGRAMBYAGE: string;
AGE: string;
BANK: string;
SAVINGSPLAN: string;
SAVINGSPERIOD: string;
FIXEDWITHOUTLINKAGEVAL: number;
FIXEDLINKEDCONSUMERPRICEINDVAL: number;
VARIABLESPREAD: number;
}

declare const array: Array<SavingsData>;
export default array;


ומפה אנחנו מסודרים. אפשר לייבא את הקובץ deposits.json אפילו שהוא שוקל 10 מגה וטייפסקריפט ישתמש בהגדרת הטיפוס שבנינו במקום לקרוא את הקובץ ולנסות להבין את הטיפוס לבד.

ToCode

15 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-ts-import-large-json

ToCode

14 Jan, 05:07


למה קשה לנו עם "בעצם זה לא היה רעיון טוב"
אחד האתגרים המשמעותיים בבניית צוות הוא בחירת המינון והתיזמון של "בעצם זה לא היה רעיון טוב". מתכנתים שאומרים את זה יותר מדי פעמים או מוקדם מדי לא מצליחים להתקדם. אלה שיגידו את זה פחות מדי פעמים או מאוחר מדי ייתקעו בכיוון הלא נכון ובסוף לא יגיעו ליעד.

מותר לעבור קופה בסופר כשגילינו שהתור ארוך מדי אבל לא כדאי לעבור יותר מפעם אחת בקנייה.

בעבודה על פרויקט תוכנה כדאי תמיד לחשוב על היעד ולא רק על העלות הנוכחית. לדוגמה התחלתי לבנות פרויקט ואחרי שלושה חודשים של פיתוח העליתי גירסה ראשונה לפרודקשן וגיליתי שבחרתי בסיס נתונים לא מתאים. על פניו שינוי בסיס נתונים עכשיו יכול להחזיר אותי שלושה חודשים אחורה ולכן שווה לעשות מאמץ ולגרום לבסיס הנתונים שבחרתי לעבוד. אבל למעשה מערכת תוכנה יכולה להיות בשימוש שנים ארוכות, והישארות עם בסיס נתונים לא מתאים זה נזק שאני אצטרך לחיות איתו עכשיו שנים קדימה.

"בעצם זה לא היה רעיון טוב" צריך להיות חלק מהלקסיקון שלנו בפיתוח פרויקטים. לא יותר מדי כמובן, אבל גם לא פחות מדי.

נ.ב. 1 עד היום עבדתי עם ווידג'ט של תגובות של Disqus שעבד בחינם ובגדול בסדר. לצערי קיבלתי מהם הודעה שהם החליטו להוסיף פירסומות לווידג'ט לכל הלקוחות החינמיים ולכן הסרתי אותם מהאתר. לגירסה הבאה כבר אכתוב משהו בעצמי או אשתמש בספריית קוד פתוח. בינתיים מוזמנים לכתוב תגובות במייל או בטלגרם.

נ.ב. 2 יש לכם רעיון לפרויקט ולא בטוחים איך להתחיל? התחלתם לעבוד על פרויקט אבל לא מצליחים להגיע לפרודקשן? אני אשמח לעזור. במרץ אני פותח תוכנית ליווי לבניית פרויקטים. אם מעניין אתכם להצטרף לחצו כאן ונתאם שיחת היכרות.

ToCode

14 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-actually-no

ToCode

13 Jan, 05:09


הזמנה לסדנה - מנטורינג לבניית פרויקט
פרויקטי תוכנה יכולים לשנות את העולם. רבים מהם כבר עשו את זה. ומה שמדהים בפרויקטי תוכנה זה שההתחלה שלהם יכולה להיות מאוד מהירה: ג'ימייל, וורדל, טוויטר, פלאפי בירד, גיט ופרויקטים רבים נוספים החלו את דרכם בכמה ימים או שבועות של פיתוח שהוביל לפרוטוטייפ והשאר היסטוריה. וכל זה היה לפני עידן ה AI.

הכלים שיש לנו היום לבניית מערכות תוכנה, ובמיוחד החיבור המהיר ל AI, הם טובים בהרבה מכל מה שאפשר היה לחלום עליו עד לפני מספר שנים. אינסוף פרויקטים שפעם היו מסובכים מדי למימוש או יקרים מדי הפכו ריאליים, ואינסוף עבודה שפעם לקחה המון זמן יכולה להתבצע היום במהירות על ידי AI. אז מה עדיין עוצר אותנו?

האמת שלא מעט דברים:

1. אין לי זמן להשקיע בפרויקט עכשיו.

2. אין לי רעיון טוב.

3. יש לי יותר מדי רעיונות.

4. אין לי מושג איך מתחילים.

5. חבל על הזמן, ממילא לא יצא מזה כלום.

הכל נכון אבל במקביל אנחנו גם יודעים שתיק עבודות הוא אחד הדברים הכי חשובים כשאנחנו באים להתראיין בתור מפתחים. אנחנו יודעים שרק דרך התנסות אמיתית בעבודה על פרויקט אנחנו באמת יכולים ללמוד ולהבין טכנולוגיה. אנחנו יודעים שעם כל הכבוד לעבודה בצוות במקום עבודה, יש ערך עצום בבניית פרויקט מאפס בעצמך.

בתחילת מרץ אני מתכוון לפתוח קבוצת מנטורינג לעבודה על פרויקט Full Stack במשך ארבעה שבועות ובאמצעות שילוב כלי AI עם כל הסטאק הטכנולוגי שאנחנו כבר מכירים. העבודה בקבוצה תתן לכם את המקום והכלים ליצור פרויקט מאפס ועד פרודקשן, ואתם לא לבד: אנחנו נקיים מפגשים קבוצתיים בזום בהם נלמד אחד מהשני ונציג את העבודה שעשינו, נחזיק קבוצת דיונים בווטסאפ ותקבלו פגישות אחד על אחד איתי לייעוץ טכנולוגי ו Code Review ספציפי על הפרויקט. העבודה במסגרת קבוצתית ועם מטרה ברורה של בניית פרויקט תתן גם לכם את המוטיבציה להתאמץ יותר ולהגיע לקו הסיום עם פרויקט בפרודקשן.

המחזור הראשון יהיה מוגבל ל 8 אנשים כדי להבטיח הצלחה ותשומת לב אישית לכל אחד ואחת. אם אתם בעניין ורוצים כבר לשריין מקום או לשמוע עוד פרטים תשאירו כבר הודעה ולא תצטרכו לעמוד בתור. אפשר לענות ישירות למייל או להשאיר הודעה באתר בקישור:

https://www.tocode.co.il/contacts/new

ToCode

13 Jan, 05:09


https://www.tocode.co.il/blog/2025-01-mentoring-workshop

ToCode

12 Jan, 05:07


ניהול טפסים בריאקט עם Formik ו Yup
טפסים מדכאים אותי. באמת. לאורך זמן אנחנו רק מוסיפים להם פונקציונאליות והופכים אותם ליותר מסובכים, שדות מסוימים מופיעים ואחרים נעלמים לפי בחירות קודמות בטופס, מה שאומר שבריאקט טפסים מובילים להמון משתני סטייט. ספריית פורמיק מציעה דרך קלה לצמצם את מספר משתני הסטייט, ובשילוב עם yup גם קל לנו (יחסית) לארגן את הקוד ולהפריד בין הלוגיקה של הוולידציה למראה של הטופס. בואו נראה איך זה עובד.

קוד הוולידציה
אני מתקין את שתי הספריות עם:

npm install formik yup


ואז יוצר תיקייה חדשה בשם schema ובתוכה קובץ לוולידציית טופס ראשונה, אני קורא לו LoginForm.ts. זה תוכן הקובץ:

import * as Yup from 'yup';

export default Yup.object().shape({
email: Yup.string()
.email('Invalid email format')
.required('Email is required'),
password: Yup.string()
.required('Password is required'),
rememberMe: Yup.boolean().default(false),
});


הקובץ מתאר את הציפיות שלי מהערכים בטופס ואת הודעות השגיאה שאני רוצה להציג אם דברים לא תואמים לסכימה. אפשר גם להוסיף לכל שדה וולידציות משלכם בתור פונקציות אסינכרוניות, וכך למשל טופס רישום יכול לוודא שכתובת המייל לא רשומה כבר למערכת. אפשר להגדיר גם קשר בין שדות כדי לוודא ששדה אימות סיסמה באמת מכיל את אותו טקסט כמו הסיסמה או שבחירה בשדה מסוים דורשת בחירה גם בשדה אחר. כתבתי על yup בפוסטים אחרים ועוד אכתוב עליו, אבל באמת זו ספריה עם הרבה יכולות ושווה ללמוד עליה.

קוד הטופס
בצד של הטופס יש ממשק מאוד פשוט בין פורמיק ל yup וזה נראה כך:

import { useFormik } from 'formik';
import loginFormSchema from './schema/LoginForm';

export default () => {
const formik = useFormik({
initialValues: {email: '', password: '', rememberMe: false},
validationSchema: loginFormSchema,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});

return (
<form onSubmit={formik.handleSubmit}>
<div>
<label>
Email
<input
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
</label>
</div>

<div>
<label>
Password:
<input
name="password"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password ? (
<div>{formik.errors.password}</div>
) : null}
</label>
</div>

<div>
<label>
<input
type="checkbox"
name="rememberMe"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
checked={formik.values.rememberMe}
/>
Remember Me
</label>
</div>

<button type="submit">Login</button>
</form>
);
};


הפקודה שמחברת בין הטופס לסכימה היא:

validationSchema: loginFormSchema,


והבלוק הזה אחרי כל input אחראי על הצגת הודעות השגיאה ליד השדה:

{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}

ToCode

12 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-formik-yup-react-demo

ToCode

11 Jan, 05:07


* can become safe by removing exactly one level. *
def is_safe_with_dampener?(levels)
# If already safe, no need to remove anything.
return true if is_safe?(levels)

# Otherwise, try removing each level in turn.
(0...levels.size).each do |idx|
new_levels = levels[0...idx] + levels[idx+1..-1]
return true if is_safe?(new_levels)
end

false
end

* --- Read the input --- *
* Typically you'd read from a file or from standard input. *
* For Advent of Code, you might do something like: *
* lines = File.readlines("input.txt").map(&:strip) *
* Here we'll read from standard input (ARGF) to keep it general. *
lines = ARGF.each_line.map(&:strip)

* Convert each line of space-separated numbers into an integer array. *
reports = lines.map { |line| line.split.map(&:to_i) }

* --- Part 1: Count how many reports are safe under the original rules --- *
part1_safe_count = reports.count { |levels| is_safe?(levels) }

* --- Part 2: Count how many reports are safe with the "Problem Dampener" --- *
part2_safe_count = reports.count { |levels| is_safe_with_dampener?(levels) }

* Output the answers *
puts "Part 1 (original rules) safe reports: #{part1_safe_count}"
puts "Part 2 (with Problem Dampener) safe reports: #{part2_safe_count}"


מבחינת הקוד אין פה הברקות. אישית אני לא משתמש ב return בתוך בלוקים שעוברים לפונקציות, וגם אני לא חושב שיש צורך בכל כך הרבה הערות, אבל עדיין צריך להוריד את הכובע. העובדה שאפשר להמשיך את השיחה כדי לגלות בעיות ולשפר את הקוד יכולה לשפר משמעותית גם את חווית הלימוד.

ToCode

11 Jan, 05:07


פיתרון Advent Of Code 2024 יום 2 ב Ruby
כן אני אוהב לפתור חידות תכנות והחידות של Advent Of Code, מצליחות הרבה פעמים להפתיע, למרות המלל הרב. בפוסט זה נראה פיתרון לא מלהיב מדי של יום 2 מהסט האחרון ברובי ואנסה לדמיין איך היה נראה פיתרון יעיל יותר.

האתגר
נתון קלט שבנוי מרשימות של מספרים:

7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9


והאתגר מחולק לשני חלקים. בחלק הראשון צריך למצוא כמה מהרשימות האלה מתאימות לסט התנאים הבא:

1. או שהרשימה רק עולה או רק יורדת.

2. קצב העלייה או הירידה לא גבוה מ-3.

בחלק השני עלינו לדמיין שאפשר למחוק מספר אחד מהרשימה ולראות אם עכשיו הרשימה תתאים לתנאי.

פיתרון חלק 1
החלק הראשון היה די פשוט והמפתח לפיתרון הוא השורה הבאה:

line.split.map(&:to_i).each_cons(2).map { _2 - _1 }


אני לוקח שורה, חותך אותה למספרים, הופך כל מספר ל integer ואז רץ על כל הזוגות ברשימה ומחשב את ההפרש ביניהם. סך הכל אם הרשימה התחילה כך:

line = '7 6 4 2 1'


אז שורת הקריאה תחזיר לי:

3.3.5 :021 > line.split.map(&:to_i).each_cons(2).map { _2 - _1 }
=> [-1, -2, -2, -1]


אחרי שיש את זה קל לרוץ על ההפרשים ולבדוק שהכל מתאים לתנאים, וסך הכל הפיתרון:

filename = ARGV[0]

parsed_input = File.foreach(filename).map do |line|
line.split.map(&:to_i).each_cons(2).map { _2 - _1 }
end

safe = parsed_input.filter do |seq|
(seq.all?(&:positive?) || seq.all?(&:negative?)) && (seq.all? { it.abs <= 3 })
end

pp safe.count


פיתרון חלק 2
בחלק השני היה צריך לזהות אם אפשר למחוק איבר אחד מהרשימה ולקבל רשימה תקינה. הדרך הקלה לגשת לזה היתה פשוט לנסות למחוק איברים ולראות אם יש מישהו שבלעדיו הרשימה תקינה. זה הקוד:

filename = ARGV[0]

def check_seq(seq)
diff_seq = seq.each_cons(2).map { _2 - _1 }
(diff_seq.all?(&:positive?) || diff_seq.all?(&:negative?)) && (diff_seq.all? { it.abs <= 3 })
end

parsed_input = File.foreach(filename).map do |line|
line.split.map(&:to_i)
end

safe = parsed_input.filter do |seq|
check_seq(seq) ||
seq.each_index.any? do |i|
check_seq(seq.reject.with_index { _2 == i })
end
end

pp safe.count


בשביל למחוק אינדקס מרשימה ברובי השתמשתי בשורה:

[2, 3, 5].reject.with_index { _2 == 1 }


וכן זה עובד אבל אני מודה שלא שימח אותי במיוחד בגלל שהוא עושה הרבה עבודה כפולה. אם נדמיין את הרשימה הבאה:

line = '2 3 4 5 6 7 8 9 10 11 12 100'


אז ברור שתחילת הרשימה תקינה והמספר 100 בסוף הוא הבעייתי. אין טעם לנסות למחוק את כל האינדקסים בהתחלה כי אנחנו כבר יודעים שגם איתם הכל בסדר, ואפשר מראש למחוק רק את האינדקס הראשון שמכיל הפרה. אני חושב שזה יהיה שיפור טוב לתוכנית אבל מודה שלא הספקתי לכתוב אותו כי הוא קצת יאריך את הקוד.

פיתרון של Chat GPT
המטרה של Advent Of Code אף פעם לא היתה לפתור את התרגיל בשביל שיהיה לך את קוד הפיתרון (אנשים תמיד פרסמו את התשובות שלהם ברשת ואין בעיה להעתיק), אלא בשביל האתגר האישי והחוויה הלימודית. מסיבה זו אני לא חושב שזה משנה ש Chat GPT יכול לפתור את התרגילים בקלות. ובכל זאת יש משהו קסום ביכולת להדביק דף שאלה ל Chat GPT ולקבל קוד שעובד. זה הקוד שקיבלתי ממנו:

* !/usr/bin/env ruby *

* --- Helpers --- *

* Returns true if the array of levels is strictly increasing or strictly decreasing *
* with each adjacent difference in the range [1..3]. *
def is_safe?(levels)
return true if levels.size < 2 # Trivially safe if 0 or 1 level

# We check both possibilities (strictly increasing or strictly decreasing).
is_increasing = true
is_decreasing = true

(0...levels.size-1).each do |i|
diff = levels[i+1] - levels[i]

# Must be in [-3..-1] or [1..3]; if it's 0 or outside ±3 => not safe
return false if diff.abs < 1 || diff.abs > 3

# If it's not strictly positive, can't be strictly increasing
is_increasing = false if diff <= 0

# If it's not strictly negative, can't be strictly decreasing
is_decreasing = false if diff >= 0
end

# Safe if either strictly increasing or strictly decreasing
is_increasing || is_decreasing
end

* Returns true if the array of levels is safe as-is OR *

ToCode

11 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-aoc2024day2-ruby

ToCode

10 Jan, 05:08


<p>{lastName}</p>
<button onClick={swap}>Swap</button>
<Link href="/">Counter</Link>
</div>
)
}


הפונקציה useNamesSelector מוגדרת בסלייס של names ומתאימה ל State של דף השמות. טייפסקריפט מספיק חכם בשביל להבין שה State בתוך פונקציה זו הוא מסוג Names, וכך יש לי השלמה אוטומטית ובדיקת טיפוסים.

חיבור ל next
נשאר לנו רק לחבר את הקוד ל next. אני משתמש ב Pages Router. בקובץ _app.tsx אני כותב:

import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { makeStore } from '../redux/store'
import { Provider } from 'react-redux'

export default function App({ Component, pageProps }: AppProps) {
const store = makeStore(pageProps);
return <Provider store={store}>
<Component />
</Provider>
}


וכך יוצר את ה store מתוך ה pageProps, שזה אוביקט המידע שהשרת הכין. בכל אחד מהדפים אני מאתחל את ה pageProps, לדוגמה דף השמות נראה כך:

import Image from 'next/image'
import Names from '@/components/Names';

export const getServerSideProps = (async () => {
return { props: {firstName: 'first', lastName: 'last'} };
});

export default function Home() {
return (
<main>
<Names />
</main>
)
}


הפונקציה getServerSideProps מאתחלת את אוביקט המידע, נקסט יעביר אותו ל App ושם ייווצר ה Store וכל מעבר דף ה Store יאותחל עם המידע החדש שמגיע מהשרת.

ToCode

10 Jan, 05:08


דוגמת קוד: שילוב Redux עם Next.js
שילוב בין next.js ל Redux יכול להיות מבלבל כי כל אחת משתי הספריות רוצה לעשות משהו שהספריה השנייה עושה - בדוגמאות של next הם שמחים להראות איך עובדים רק עם next ומנהלים סטייט לגמרי בתוך העולם שלהם, ובדוגמאות של רידאקס הם שמחים להראות איך לכתוב store לעמוד אחד אבל כשצריך להתמודד עם מספר עמודים וסטייט בצד שרת יותר קשה למצוא דוגמאות.

בפוסט זה אני מציג רעיון אחד לשילוב בין הספריות שמבוסס על העקרונות הבאים:

1. קוד צד שרת מכין אוביקט מידע (דרך שליפה מבסיס הנתונים, גישה לשירותי צד שלישי או כל מנגנון אחר).

2. אוביקט המידע הזה הופך ל Store של רידאקס ונשלח לצד הלקוח.

3. קומפוננטות שקוראות מרידאקס צריכות לבחור מה העמוד הראשי שהן מצפות להיות בו, כדי שנדע איזה State Object הן צפויות לקבל.

קוד ה Server Side Rendering יכול לעבוד עם אוביקט המידע שהשרת הכין וקוד צד לקוח יכול לעשות dispatch ל Actions לתוך אוביקט המידע הזה.

הריפו של הדוגמה זמין כאן:

https://github.com/ynonp/next-pages-redux

בואו נראה איך זה עובד.

קובץ ה store
תחנה ראשונה היא הקובץ store.ts. מה שחשוב כאן הוא להגדיר פונקציה בשם makeStore שמקבלת את הסטייט הראשוני ומחזירה store שמתאימה לו. אני גם הוספתי תמיכה בפעולת set שכותבת ערך לאיזשהו שדה ב store:

import { configureStore, createReducer } from '@reduxjs/toolkit'
import { set } from './actions';

export function makeStore(initialState: any) {
return configureStore({
reducer: createReducer(initialState, builder => {
builder.addCase(set, (state, action) => {
// @ts-ignore
state[action.payload.key] = action.payload.value;
})
})
})
}

// Infer the \RootState\ and \AppDispatch\ types from the store itself
export type TState = ReturnType<typeof makeStore>;

export type RootState = ReturnType<TState['getState']>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = TState['dispatch']


קבצי הדפים
בפרויקט הדוגמה יש שני דפים: counter ו names. לכל דף יש אוביקט סטייט ראשי שונה, רק אחרי הפעלת makeStore נדע מי אוביקט הסטייט שבו אנחנו משתמשים. זה הקוד של counter.ts:

import {useSelector, TypedUseSelectorHook} from 'react-redux';

export type Counter = {
count: number;
}

export const initialState: Counter = {
count: 0,
}

export const useCounterSelector: TypedUseSelectorHook<Counter> = useSelector;


וזה הקוד של names.ts:

import {useSelector, TypedUseSelectorHook} from 'react-redux';
import { createAction } from '@reduxjs/toolkit';
import type {SetFieldPayload} from './types';

export type Names = {
firstName: string,
lastName: string,
}

export const initialState: Names = {
firstName: 'a',
lastName: 'b',
}

export const useNamesSelector: TypedUseSelectorHook<Names> = useSelector;


נשים לב שבפרויקט הדוגמה בחרתי להשתמש ב Action יחיד שיתאים לכל הדפים, אבל בהחלט אפשר היה לדמיין שכל דף יהיה slice עם actions משלו. במצב כזה הפונקציה makeStore היתה מקבלת Reducer במקום רק initial state.

הקומפוננטות
קומפוננטות שונות יכולות להיכנס לדפים שונים, כי הן פונות לנתיבים אחרים בסטייט. יותר מזה כשאני נכנס לדף מסוים רק הפריטים של דף זה בכלל זמינים בתוך הסטייט, כלומר אני לא יכול להציג קומפוננטה שפונה ל names בתוך דף ה counter ולהיפך.

בשביל לציין את זה בקוד הקומפוננטה הגדרתי פונקציית useSelector שונה לכל דף, וכך קומפוננטת ה names משתמשת ב selector שמתאים לדף שלה, כלומר:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { set } from '../redux/actions';
import Link from 'next/link'

import { useNamesSelector } from '@/redux/pages/names';

export default function() {
const dispatch = useDispatch();

const firstName = useNamesSelector(s => s.firstName);
const lastName = useNamesSelector(s => s.lastName);

function swap() {
dispatch(set({key: 'firstName', value: lastName}))
dispatch(set({key: 'lastName', value: firstName}))
}

return (
<div>
<p>{firstName}</p>

ToCode

10 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-combine-redux-and-next

ToCode

09 Jan, 05:22


https://www.tocode.co.il/blog/2025-01-floating-points-percentages

ToCode

09 Jan, 05:22


למה קשה לחשב שברים
אבי בר-אלי מתלונן בדה מרקר על מחשבי הביטוח הלאומי שלא יודעים לחשב את מס הבריאות החדש כי הוא מכיל שלוש ספרות אחרי הנקודה העשרונית. כל מי שקצת מבין במחשבים עלול להרגיש מרומה כאן: מה אכפת למחשב כמה ספרות יש אחרי הנקודה העשרונית? ולמה הביטוח הלאומי מעריך את השינוי במיליון וחצי ש"ח? אז אני אומנם לא יודע כלום על מערכות הביטוח הלאומי אבל בפוסט הזה אנסה לעשות תרגיל מחשבתי כדי להבין למה זה מסובך.

איך מחשבים שברים
כל שפת תכנות כולל הישנות יודעת לבצע חישובים. לדוגמה בשפת פייתון אנחנו יכולים לכתוב את הקוד הבא כדי להדפיס את המכפלה של 2 ו-3 ולקבל על המסך 6:

x = 3
y = 2
print(x * y)


הבעיה של מחשבים מתחילה כשצריך לחשב שברים. מסיבות היסטוריות וטכניות טובות (אפשר לקרוא עליהן בויקיפדיה) רוב שפות התכנות כוללות בעיות דיוק בעבודה עם שברים, לדוגמה באותו פייתון אם אני כותב:

x = 0.1
y = 0.2
print(x + y)


אני אקבל את התוצאה:

0.30000000000000004


ולא את 0.3 כמו שאולי אפשר היה לצפות. או בדוגמה נוספת:

>>> 2 - 1.1
0.8999999999999999


יש מצבים בהם אנחנו מוכנים לחיות עם אי דיוקים כאלה, ומצבים אחרים בהם אנחנו מעדיפים למצוא דרך אחרת לשמור את המידע שלנו כדי לא ליפול במלכודות ובאי דיוקים.

מה עושים עם הכסף
אחד המקומות בהם טעות זו יכולה להיות הכי מרגיזה הוא בעבודה עם כסף. ברור שאם מישהו קונה מוצר שעולה 2 ש"ח ומשלם שקל ועשר אגורות אנחנו צריכים להחזיר לו 90 אגורות, אפילו שהתרגיל בתוכנית המחשב לא משקף את זה.

מסיבה זו טריק נפוץ לעבודה עם שברים שיעזור לנו לשמור את הדיוק הוא להפוך אותם למספרים שלמים, לדוגמה בעבודה עם כסף אנחנו יכולים פשוט לשמור את המספר באגורות. בסיפור כזה התרגיל של אותו לקוח שקנה בשקל ועשרה מוצר שעולה שני שקלים יראה כך:

>>> 200 - 110
90


וכולם מבינים פה שצריך להחזיר 90 אגורות.

אז מה קרה לביטוח הלאומי? אני לא יודע. אבל אולי הם השתמשו בטריק דומה - נניח שהם שמרו בבסיס הנתונים ובקוד את כל המספרים מוכפלים ב 100 כדי לא לעבוד עם שברים, ורק בהדפסה הציגו את המספר כאילו מדובר על שבר עם שתי ספרות אחרי הנקודה העשרונית. אם זה מה שהם עשו, ברור למה יהיה מסובך לתקן את זה ל-3 ספרות:

1. עליהם למצוא את כל המידע בבסיס הנתונים ששמור בפורמט שהם יצרו ולעדכן את הפורמט ל-3 ספרות אחרי הנקודה העשרונית.

2. עליהם למצוא את כל המקומות בקוד בהם מוגדרים מספרים בפורמט הזה ולתקן ל-3 ספרות.

3. עליהם למצוא את כל קטעי הקוד שקוראים מידע או כותבים אותו ולתקן אותם כדי לכפול באלף במקום במאה.

וכן זה המון עבודה ובמערכות ישנות גם פוטנציאל גבוה לטעויות.

מה עושים במקום? כרגע לא הרבה. יש דברים שפשוט קשה לשנות אותם. כן כדאי לזכור כשאנחנו בונים מערכות חדשות להיזהר מהסקת מסקנות כלליות על העולם מהנתונים שיש לנו, כלומר גם אם עכשיו כל הנתונים שלנו נראים כאילו אחוזים יכולים לכלול רק שתי ספרות אחרי הנקודה העשרונית, מגבלה זו אינה הכרחית למושג האחוז ובהחלט אפשר לדמיין אחוזים קטנים יותר. אלטרנטיבה שלפעמים יכולה להיות טובה יותר היא להשתמש במספרים דצימליים גדולים (קיימים בשפות תכנות מודרניות) שם החישוב יותר מדויק. בפייתון יש לנו:

from decimal import *

>>> Decimal('0.1') + Decimal('0.2')
Decimal('0.3')


שימו לב שהקפתי את 0.1 ו 0.2 בסימן של מחרוזת, כדי שפייתון לא ישמור אותם בטעות בייצוג הנקודה העשרונית.

ToCode

08 Jan, 05:08


ניסוי ריאקט: משתנה סטייט לקריאה וכתיבה
ב Vue אנחנו יוצרים משתנה סטייט עם הפקודה ref ומקבלים משהו שאפשר לכתוב אליו וגם לקרוא ממנו לדוגמה הקוד הבא מציג מונה לחיצות עם משתנה סטייט ששומר את מספר הלחיצות:

<script setup>
import { ref } from 'vue'

const counter = ref(0)
function inc() { counter.value++ }
</script>

<template>
<p>{{counter}}</p>
<button @click="inc">+1</button>
</template>


בריאקט הפקודה useState מחזירה שני דברים: את הערך עצמו של משתנה הסטייט ופונקציה שמעדכנת אותו. בואו ננסה לעטוף את useState כדי לקבל רק דבר אחד שיתנהג כמו משתנה סטייט ב vue.

פיתרון: useRefState
בשביל לקבל רק אוביקט אחד שמאפשר כתיבה וקריאה אני משתמש בפרוקסי. הפרוקסי יעטוף את האוביקט ויתפוס כתיבות לשדה value שלו, ואז יחליף כל כתיבה לשדה value בהפעלת ה setter. כך נראה הקוד:

function toRef(value, setter) {
return new Proxy(
{ value },
{
get(target, prop, receiver) {
return value;
},
set(obj, prop, value) {
setter(value);
},
}
);
}

function useRefState(initialValue) {
const [value, setter] = useState(initialValue);
return toRef(value, setter);
}


בתוך קומפוננטה אפשר להשתמש במנגנון בדיוק כמו ב Vue:

export default function App() {
const counter = useRefState(0);
function inc() {
counter.value++;
}

return (
<div className="App">
<p>{counter.value}</p>
<button onClick={inc}>+1</button>
<button onClick={() => counter.value--}>-1</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}


הפונקציה inc מעלה ב-1 את ה value של counter, וזה מספיק בשביל לגרום לרינדור מחדש של הקומפוננטה. הצגת ה value מתוך התבנית מראה את הערך שלו.

עדיין יש מספר פערים בפיתרון:

1. ב Vue אפשר להשתמש ב counter ישירות בתוך התבנית. ריאקט לא מרשה לי לכתוב אובייקטים בתוך התבנית ולכן חייבים להשתמש ב .value גם שם.

2. ומה שיותר משמעותי, ב Vue רק קריאה ממשתנה ריאקטיבי יוצרת מנוי לערך של אותו משתנה. קומפוננטה שלא קוראת ממשתנה ריאקטיבי לא תתרנדר מחדש כשאותו משתנה יתעדכן. בריאקט השינויים קורים ברזולוציה של קומפוננטה ולכן מספיק שיש עדכון למשתנה סטייט שהוגדר בקומפוננטה מסוימת כדי שקומפוננטה זו תתרנדר מחדש.

נ.ב. בתור קונספט בשביל ליצור משתנה סטייט של "דבר אחד" אני מעדיף להשתמש בפונקציה. הפעלה של הפונקציה בלי פרמטרים מחזירה את הערך הנוכחי והפעלה עם פרמטרים תעדכן, כלומר:

function useRefState(initialValue) {
const [value, setter] = useState(initialValue);
return function (...args) {
if (args.length > 0) {
setter(...args);
} else {
return value;
}
};
}

export default function App() {
const counter = useRefState(0);
function inc() {
counter((c) => c + 1);
}

return (
<div className="App">
<p>{counter()}</p>
<button onClick={inc}>+1</button>
<button onClick={() => counter((c) => c - 1)}>-1</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}

ToCode

08 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-react-single-access-state

ToCode

07 Jan, 05:07


תרגיל טייפסקריפט: פעולת עדכון גנרית ב Redux
בדוגמת ההתחלה המהירה של Redux Toolkit הם מציעים את הקוד הבא עבור סלייס של מונה:

import { createSlice } from '@reduxjs/toolkit'

const initialState = {
value: 0,
}

export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer


לא מפתיע שזה מתאים בדיוק לתפיסת העולם של Redux - מעט מידע, שניתן לשינוי במספר דרכים (פעולות), כל דרך שינוי עם המגבלות והבדיקות שלה. אבל בעולם האמיתי יש עוד מקרה נפוץ שלא מדברים עליו בתיעוד שלהם, וזה אוביקט עם הרבה מידע שמאפשר שינויים יחסית גמישים. דמיינו את אוביקט המידע הבא:

export interface CounterSliceState {
value: number
status: "idle" | "loading" | "failed"
foo: string
bar: number
buz: Array<string>
}


ואולי יהיו שם עוד 20 שדות מטיפוסים שונים. כתבו פעולת Set גנרית (אחת) שתקבל מפתח וערך מהסוג שמתאים לו ותכתוב את זה לאוביקט המידע.

פיתרון
בשביל הפיתרון עלינו תחילה לכתוב את ה Reducer. אפשר להגדיר משהו כזה:

extraReducers: (builder) => {
builder.addCase(setField, (state, action) => {
const { key, value } = action.payload;
// @ts-ignore
state[key] = value;
});
},


אני מוכן להתעלם כאן מבעיות טייפסקריפט כי הוולידציה מתרחשת קודם, בעת יצירת ה Action. הקוד הבא יוצר Action גנרי עבור כתיבה לשדה כלשהו באוביקט מידע:

type SetFieldPayload = {
[K in keyof CounterSliceState]: {
key: K;
value: CounterSliceState[K];
};
}[keyof CounterSliceState];

export const setField = createAction<SetFieldPayload2>('counter/set');


ועכשיו הקוד הזה מתקמפל:

setField({key: 'bar', value: 5}) // compiles OK


אבל זה זורק שגיאה:

setField({key: 'bar', value: '?'}) // compilation error


נ.ב. אפשר להרחיב את הפיתרון ולכתוב גם למפתח מקונן (בקשו מ Chat GPT את הקוד אם לא מצאתם את הרקורסיה), אבל אני לא ממליץ. ככל שטייפסקריפט נהיה מסובך מדי הוא מפסיק להיות שימושי, ובאופן כללי רידאקס עובד יותר טוב עם סלייסים שטוחים. יותר קל לקחת את החלק המקונן ולהפריד אותו לסלייס משלו.

ToCode

07 Jan, 05:07


https://www.tocode.co.il/blog/2025-01-typescript-generic-redux-setter

ToCode

06 Jan, 05:08


מה מודדים
בהתלבטות בין שני קורסים יש דברים שמאוד קל למדוד: אפשר להשוות את אורך הקורסים, את המחיר, את המחיר לשעה, אפשר להסתכל על עבודות של בוגרים אחרים ולמדוד כמה הפרויקט נראה מעניין ואפילו אפשר להשוות כמה תארים או כמה שנות ניסיון יש למרצים.

אבל את הדברים שחשוב למדוד הרבה יותר קשה לזהות: חשוב למדוד כמה מוטיבציה הקורס ייתן לי כדי להמשיך ללמוד גם אחריו. חשוב למדוד כמה מהר הקורס יאפשר לי להתחיל לעבוד לבד, וכמה רחוק אוכל להגיע בעזרת הרעיונות והעקרונות שאלמד שם. חשוב למדוד כמה גבוה הסיכוי שלי למצוא עבודה אחרי הקורס והאם המרצים באמת יוכלו להבין את המצב שלי ולתת לי עצות רלוונטיות עבורי.

בשביל למדוד את הדברים החשובים לא מספיק להסתכל בסילבוס - צריך ממש לדבר עם המרצים ועם בוגרים, להשתתף בשיעורי ניסיון ואולי אפילו ללמוד מראש בבית כדי להגיע עם מספיק רקע ולשאול את השאלות הנכונות.

רק בגלל שקל למדוד משהו לא אומר שהוא נותן אינדיקציה טובה להצלחה.

ToCode

06 Jan, 05:08


https://www.tocode.co.il/blog/2025-01-measuring

ToCode

05 Jan, 05:07


ואפשר לראות את התוצאה בקישור:

https://ynonp.github.io/echarts-birth-demo/

וגם את קוד הפרויקט המלא בגיטהאב:

https://github.com/ynonp/echarts-birth-demo

מעבר לנוחות העבודה עם echarts, דוגמה זו הזכירה לי כמה קל לכתוב היום אתר סטטי בלי שלב בנייה ולאחסן אותו על Github Pages. בזכות התמיכה המובנית ב ES Modules ובטעינת קבצי JSON מתוך ES Modules אנחנו מקבלים בחינם הרבה מהדברים בשבילם היינו צריכים בעבר שלב בנייה.

ToCode

05 Jan, 05:07


דוגמת Echarts - ניתוח נתוני לידות
ספריית Echarts נותנת דרך קלה מאוד ליצור תרשימים וגרפים ב Web. בשילוב עם מאגר הנתונים הישראלי הממשלתי אפשר להתנסות בקלות בניתוח נתונים ושימוש בגרפים. הנה דוגמה קצרה עם קובץ של נתוני לידות שלקחתי מהקישור הזה:

https://data.gov.il/dataset/birth-data

איך נראה המידע
מאגר הנתונים כולל קובץ CSV שמכיל את העמודות: חודש לידה, גיל האם, מספר עוברים, שבוע לידה, מין התינוק ומשקל. יש פה הרבה עם מה לעבוד ואני בחרתי בשביל הדוגמה לבנות גרף עמודות שיראה את התפלגות הגילאים של היולדות בכל חודש בשנת 2014 (לה מתאים הקובץ). בשביל לעבוד עם הנתונים התחלתי עם הספריה csvkit של פייתון והפכתי את קובץ הנתונים ל JSON באמצעות הפקודה:

in2csv --encoding "utf-8" births.csv | csvjson > births.json


אלה שלושת האוביקטים הראשונים מהקובץ בשביל הדוגמה:

[
{
"birth_month": 1.0,
"mother_age": "<18",
"parity": "1",
"gestation_week": "37-41",
"birth_sex": "F",
"birth_weight": "2500-2599"
},
{
"birth_month": 1.0,
"mother_age": "<18",
"parity": "1",
"gestation_week": "37-41",
"birth_sex": "F",
"birth_weight": "2500-2599"
},
{
"birth_month": 1.0,
"mother_age": "<18",
"parity": "1",
"gestation_week": "37-41",
"birth_sex": "F",
"birth_weight": "2500-2599"
}
]


הכנת הנתונים לגרף
עכשיו אנחנו מגיעים לחלק המעניין וצריכים לארגן את המידע כך שיתאים ל ECharts. אני רוצה להציג גרף עמודות מאוזן. בגרף עמודות פשוט אנחנו מציגים ערכים מתוך סידרה כלומר היינו צריכים רשימה של מספרים. אני רוצה להציג בכל עמודה את הלידות מחולקות לפי גיל האם, ולכן אחלק את הנתונים למספר רשימות, רשימה לכל קבוצת גילאים:

const dataByAge = _.chain(data).groupBy('mother_age').mapValues(
row => _.chain(row).groupBy('birth_month').mapValues(v => v.length).value()
).value()

const series = _.toPairs(dataByAge).map(([mother_age, entries]) => ({
name: mother_age,
type: "bar",
stack: "total",
label: {
show: true,
},
emphasis: {
focus: "series",
},
data: Object.values(entries),
})
)


התוצאה היא רשימה של אוביקטים, כלומר של סדרות, כאשר שם כל סידרה הוא קבוצת הגילאים של האם והערכים (במפתח data) הם רשימה בה כל איבר הוא מספר הלידות בקבוצת הגיל הנתונה בחודש אחר, כלומר האיבר הראשון הוא כמה לידות היו בינואר בקבוצת גיל זו, אחרי זה כמה לידות היו בפברואר וכך הלאה.

סיכום והקוד המלא
קוד ה JavaScript המלא לציור הגרף הוא:

import data from './births.json' with { type: "json" };
import _ from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'

window.data = data;
const months = {
1: 'January',
2: 'February',
3: 'March',
4: 'April',
5: 'May',
6: 'June',
7: 'July',
8: 'August',
9: 'September',
10: 'October',
11: 'November',
12: 'December'
};

var dom = document.getElementById("chart-container");
var myChart = echarts.init(dom, null, {
renderer: "canvas",
useDirtyRect: false,
});
var app = {};
var option;

const dataByAge = _.chain(data).groupBy('mother_age').mapValues(
row => _.chain(row).groupBy('birth_month').mapValues(v => v.length).value()
).value()

const series = _.toPairs(dataByAge).map(([mother_age, entries]) => ({
name: mother_age,
type: "bar",
stack: "total",
label: {
show: true,
},
emphasis: {
focus: "series",
},
data: Object.values(entries),
})
)

option = {
tooltip: {
trigger: "axis",
axisPointer: {
// Use axis to trigger tooltip
type: "shadow", // 'shadow' as default; can also be 'line' or 'shadow'
},
},
legend: {},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: {
type: "value",
},
yAxis: {
type: "category",
data: Object.values(months),
},
series: series,
};

if (option && typeof option === "object") {
myChart.setOption(option);
}

window.addEventListener("resize", myChart.resize);

ToCode

30 Dec, 05:08


האם להשתמש בטרנספורמציות על מידע ב echarts?
אחד הפיצ'רים החמודים של echarts הוא היכולת להריץ Data Pipeline בתוך קוד יצירת הגרף. אני מודה שאני עדיין מתלבט מתי להשתמש בזה ומתי להריץ את המניפולציה על המידע מראש, במיוחד כשמשלבים את echarts עם ספריית פרונטאנד כמו ריאקט או ויו. בכל מקרה ובינתיים בשביל המשחק בואו נראה איך זה עובד. שימו לב להגדרת המידע הבאה:

  dataset: [
{
// 1) Raw data from your JSON array
id: 'raw',
source: data
},
{
// 2) Sort descending by SUMACCIDEN
id: 'sorted',
fromDatasetId: 'raw',
transform: {
type: 'sort',
config: {
dimension: 'SUMACCIDEN', // the field to sort by
order: 'desc'
}
}
},
],


אני מתחיל עם מערך של אוביקטים בשם data, ובמקום למיין אותו מראש בקוד אני משתמש בטרנספורמציה של echarts כדי להסתכל על המידע הממוין. היתרון הוא שאפשר לייצר טרנספורמציות שונות ולהציג גרפים אחרים לכל טרנספורמציה, או לחבר את הטרנספורמציות למערכת ניהול האירועים של echarts.

החיסרון הוא שהתיעוד לא מלהיב ואם צריכים טרנספורמציה שאין להם זה יכול לתסכל. לדוגמה לא מצאתי דרך להציג רק 10 ערכים גבוהים ביותר אחרי הסינון. האלטרנטיבה היא להשתמש ב JavaScript פשוט:

const sortedData = data
.filter(item => item.city && item.SUMACCIDEN)
.sort((a, b) => b.SUMACCIDEN - a.SUMACCIDEN)
.slice(0, 10);


ייתרון נוסף של הגישה הפשוטה הוא שלא חייבים לטעון את רכיב הטרנספורמציות, מה שמקטין את הבאנדל.

דוגמה? בטח. הקוד הבא יציג לכם את עשר הערים עם הכי הרבה תאונות דרכים בספטמבר 2024 ומספר תאונות הדרכים בכל עיר בגרף עמודות:

<script setup>
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { BarChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
DatasetComponent,
} from 'echarts/components';
import VChart from 'vue-echarts';
import { ref } from 'vue';

// Register only the components we need (modular import)
use([
CanvasRenderer,
BarChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
DatasetComponent,
]);

// Fetch your JSON array of objects (converted from CSV)
const data = await fetch('https://assets.codepen.io/5217/data.json').then(res => res.json());

const sortedData = data
.filter(item => item.city && item.SUMACCIDEN)
.sort((a, b) => b.SUMACCIDEN - a.SUMACCIDEN)
.slice(0, 10);

const option = ref({
title: {
text: 'Top 10 Cities with Car Accidents',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
// We use ECharts “dataset” + transforms for sorting and limiting to top 10
dataset: {
source: sortedData,
id: 'sorted'
},
xAxis: {
type: 'category'
},
yAxis: {
name: 'Number of Accidents'
},
series: [
{
type: 'bar',
datasetId: 'sorted',
// Map city -> x-axis category, SUMACCIDEN -> y-axis value
encode: {
x: 'city',
y: 'SUMACCIDEN'
},
// Optional styling
itemStyle: {
color: '#5470c6'
}
}
]
});

</script>

<template>
<!-- v-chart from vue-echarts -->
<v-chart class="chart" :option="option" autoresize />
</template>

<style scoped>
.chart {
height: 600px;
width: 100%;
}
</style>


בשביל להריץ תצטרכו לשים אותו בתוך תוכנית vue ולהתקין את vue-echarts ו echarts.

ToCode

30 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-vue-charts-data-transforms

ToCode

29 Dec, 05:07


שלוש סיבות לעבוד מקומית במיוחד בתור מתחילים
לא מזמן ראיתי פוסט שהמליץ למתחילים לעבוד רק עם סביבות פיתוח אונליין, כדי לא להסתבך עם התקנות. אני מכיר טוב את הבעיה כולל מחברים שמלמדים תכנות: העבודה מ Google Colab, מ Code Sandbox או סטאקבליץ או אפילו מתוך replit הרבה יותר קלה לתלמידים ומורים מעדיפים לא להסתבך עם התקנות שאולי ייכשלו כי יש בעיה ספציפית למישהו על המחשב.

גם התלמידים לא מתלהבים להתקין סביבת פיתוח מקומית. כשלומדים משהו חדש אנחנו להוטים לראות איך זה עובד, ואם אני צריך לשרוף עכשיו חצי יום על התקנות זה יכול להוריד את כל החשק ללימוד. ובכל זאת כדאי לזכור:

1. לא תמיד יהיה לך חיבור אינטרנט - בתור מתחילים (אבל גם בתור וותיקים), יש ערך ליכולת שלנו בכל רגע לפתוח את סביבת הפיתוח ולנסות משהו. ככל שמשהו יותר נגיש אנחנו עושים יותר ממנו, ואם יש לי סביבת פיתוח מותקנת מקומית יש יותר סיכוי שאפעיל אותה ואנסה משהו חדש גם בלי אינטרנט. יותר מזה, באותם מצבים שאין לי אינטרנט חווית הלמידה שלי עשויה להיות טובה יותר כי אני לא אתפתה להעתיק קודים מאיזה AI ויש לי זמן לקרוא ולחשוב על הדברים שאני בונה.

2. זיהוי שגיאות לרוב עובד טוב יותר בסביבה מקומית - נכון ההתקנה הראשונית של סביבה מקומית יותר מורכבת, אבל אחרי שהכל מותקן חווית העבודה השוטפת ובמיוחד הודעות השגיאה נוטות להיות ברורות יותר. אפשר להגיד שזה דבר אחד פחות שיכול להישבר.

3. המחקר קל יותר - בעבודה מקומית יותר קל לנו להיכנס לקוד כולל לקוד של ספריות חיצוניות או הגדרות הפרויקט כדי לראות מה כתוב שם ולהבין מה יש בפנים. היכולת לחקור איך דברים עובדים לרוב עוזרת ללמוד, גם אם היא לפעמים מאטה אותנו.

היתה תקופה שחשבתי ש Docker יהיה סוג של פיתרון ביניים, שיאפשר לעבוד עם כלי עבודה חדשים ולדלג על מכשול ההתקנות. אני לא כל כך בטוח בזה יותר: רוב הזמן אפילו כשאני מריץ דברים מתוך דוקר על המחשב שלי ולכאורה לא צריך להתקין כלום יוצא שאני כן צריך להיכנס לקונטיינר ולסדר דברים או להבין איך הם עובדים וזה לא באמת חסך לי זמן או עבודה.

מה דעתכם? כשאתם לומדים כלי חדש מעדיפים להתקין מקומית, לפתח אונליין או להריץ קונטיינר?

ToCode

29 Dec, 05:07


https://www.tocode.co.il/blog/2024-12-learning-local

ToCode

28 Dec, 05:07


שמות בעלי משמעות?
רובי 3.4 הוסיפה תמיכה במילה שמורה חדשה - it. עכשיו אפשר לכתוב:

users
.reject(&:admin?)
.flat_map { find_teams(it) }
.uniq


מעניין לשים לב שרק לפני 5 שנים יצאה גירסה 2.7 של רובי שהוסיפה את התמיכה ב _1, והיתה הרבה התלהבות סביב זה כי עד אז היינו כותבים:

users
.reject(&:admin?)
.flat_map { |user| find_teams(users) }
.uniq


והחל מ 2019 אנחנו יכולים לכתוב:

users
.reject(&:admin?)
.flat_map { find_teams(_1) }
.uniq


ופה המקום לחזור לפיסקה הראשונה ולשאול - מה בעצם קרה פה? למה צריך גם _1 (ואת החברים שלו _2 לפרמטר השני, _3 לשלישי ועד _9) וגם את it שמתנהג בדיוק כמו _1 שכבר קיים?

אבל אולי יותר נכון להסתכל על התוספת הזאת כמו עוד חלק בפאזל התרבותי של רובי. כן חלק מהמטרה שם היא ליצור שפה עם הרבה מילים, ושתהיה מילה שמתאימה לכל דבר. כשרואים _1 בתוך בלוק אפשר לתהות האם יש גם _2 ולמה הוא לא כתוב שם. כשרואים it ברור שיש רק פרמטר אחד, ועבור אותם מקרים המילה החדשה שיפרה קצת את הקריאות של הקוד. ומה עם "צריכה להיות רק דרך אחת ברורה לעשות דברים?" נו, זה בכלל משפה אחרת.

ToCode

28 Dec, 05:07


https://www.tocode.co.il/blog/2024-12-ruby-it

ToCode

27 Dec, 05:08


white-space: pre-wrap;
}
</style>

ToCode

27 Dec, 05:08


קבלת מידע ב Streaming היא דווקא די פשוטה
ממשקי Chat מאוד אוהבים להשתמש ב Streaming כי ל AI לוקח המון זמן לייצר את התשובה. בצד הלקוח משיכת מידע משרת HTTP ב Streaming התבררה כמשימה די פשוטה אני מדביק פה את הקוד ואחריו ההסבר:

export default async function* sendQuestion(questionText: string) {
const res = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'llama3.2',
prompt: questionText,
stream: true,
}),
})

if (!res.ok) throw new Error('Failed to connect to Ollama server')

const reader = res.body?.getReader()
if (!reader) throw new Error('Failed to initialize stream reader')

while (true) {
const { done, value } = await reader.read()
if (done) break

// Decode the stream data
const chunk = new TextDecoder().decode(value)
const json = JSON.parse(chunk)
yield json
}
}


הקוד פונה לשרת של Ollama שרץ אצלי מקומית על המחשב ושולח לו שאלה. הפרמטר stream מקבל ערך אמת אבל זה לא הכרחי כי זו ברירת המחדל של פרמטר זה. אם התשובה היתה מגיעה בתור אוביקט אחד הייתי מפעיל את res.json() או res.text() כדי לקבל אותה, אבל בגלל שהתשובה מגיעה בתור זרם של אוביקטים אני קורא ל getReader על ה body, כדי לקבל ממשק קריאה. ה body הוא בעצם ReadableStream ואפשר לקרוא עליו בהרחבה בדף התיעוד:

https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader

אחרי שלקחתי את ה Reader מתחיל החלק המעייף - עלינו לקרוא מה Reader את המידע חלק אחרי חלק ולפענח כל פעם את החלק הבא בתור אוביקט JSON. בקוד הזה אני מניח שכל Chunk יכיל אוביקט JSON שלם, למרות שבעולם האמיתי ייתכן ויידרש טיפול יותר פרטני כשפיענוח של Chunk נכשל כי אולי זה חלק שמחזיק רק חלק מאוביקט ה JSON וההמשך שלו הוא ב Chunk הבא. שימו לב לשימוש ב TextDecoder שמונע בעיה דומה עבור פיענוח הטקסט, כלומר ה decode של TextDecoderיודע לחתוך את הביטים העודפים אם יש ולהדביק אותם להתחלה של ה Chunk הבא כדי שיוכל להפוך ביטים לטקסט.

הפונקציה כולה היא Generator ואפשר להשתמש בה למשל מתוך קומפוננטת vue באופן הבא:

<script setup lang="ts">
import { ref } from 'vue'
import streamChatResponse from '../streamChatResponse';
const question = ref('')
const response = ref('')
const isLoading = ref(false)
const error = ref<string | null>(null)

async function sendQuestion() {
for await (const chunk of streamChatResponse(question.value)) {
response.value += chunk.response;
}
question.value = '';
}

</script>

<template>
<div class="chat-container">
<form @submit.prevent="sendQuestion" class="question-form">
<textarea
v-model="question"
placeholder="Ask a question..."
:disabled="isLoading"
class="question-input"
/>
<button
type="submit"
:disabled="isLoading || !question.trim()"
class="submit-button"
>
{{ isLoading ? 'Thinking...' : 'Send' }}
</button>
</form>

<div v-if="error" class="error-message">
{{ error }}
</div>

<div v-if="response" class="response-container">
<div class="response-text">{{ response }}</div>
</div>
</div>
</template>

<style scoped>
.chat-container {
max-width: 800px;
margin: 0 auto;
padding: 1rem;
}

.question-form {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}

.question-input {
flex: 1;
min-height: 80px;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
}

.submit-button {
padding: 0.5rem 1rem;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.submit-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}

.error-message {
color: #dc3545;
margin-bottom: 1rem;
}

.response-container {
padding: 1rem;
background-color: #f8f9fa;
border-radius: 4px;
}

.response-text {

ToCode

27 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-streaming-http-responses

ToCode

26 Dec, 05:08


הכלי הכי טוב
בשביל לכתוב יישום ריאקט גדול בלי באגים ולהצליח להתמודד עם דרישות חדשות שמגיעות לאורך זמן בטוח צריך להבין מה זה State Management, צריך להחליט אם רוצים להשתמש ב Context כדי לשמור סטייט, אם מסתפקים בספריית ניהול סטייט קטנה כמו Valtio או שהולכים על כל הקופה ומשלבים רידאקס. אבל בתחילת הדרך או כשהיישום קטן הכלים האלה יכולים מאוד לבלבל.

וכאן השאלה - עד כמה צריך להתאמץ להבין איך רידאקס עובד כשאנחנו רק לומדים ריאקט? עד כמה חשוב להבין איך GraphQL עובד כשאנחנו רק מתחילים ללמוד על פיתוח קוד צד שרת? עד כמה חשוב "להתרגל" לשיטת עבודה מסוימת רק בגלל שהיא Best Practice ותעזור לנו בעתיד?

כלים מתקדמים הם מלכודת גם של מורים וגם של תלמידים. תלמידים רוצים ללמוד עליהם כי זה מה שנמצא בשימוש בתעשייה ואנחנו רוצים לדעת לעבוד כמו שכולם עובדים. מורים רוצים ללמד אותם כי כשאנחנו מלמדים כלים מסובכים הקורס נראה יותר קשה ותלמידים מרגישים שהם הצליחו יותר, או שלא היו מצליחים ללמוד את הכלי בלי הקורס. אבל האמת היא שרובנו נרוויח אם נוותר על הכלים המתקדמים בשלבי הלימוד הראשונים ונתמקד ביסודות. אותם דברים שכשלומדים אותם הם לא נראים מסובכים אבל אחרי מספיק תרגול ועבודה איתם מתחילים להגיע לבעיות שעבורן פותחו הכלים המתקדמים.

הכלי הכי טוב הוא כלי שפותר בעיה שעכשיו יש לך.

ToCode

26 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-best-tool-for-the-job

ToCode

25 Dec, 05:08


דוגמת קוד: וידוא קלט בשרת עם yup
לפני כמה ימים הראיתי כאן איך לבדוק קלט בטופס צד לקוח עם yup. ראינו ש yup מגיע עם המון יכולות אבל החיבור שלו לממשק דורש עבודה וברוב המקרים אפשר לקבל תוצאה טובה יותר דרך הכלים המובנים ב HTML. מצד שני בעבודת צד שרת אנחנו מגלים כמה yup יעיל וגם המבנה האסינכרוני שלו נראה מאוד הגיוני, כי ממילא כל ה APIs ב node הם אסינכרוניים. שימו לב לקוד הבא לצד שרת עבור REST API עם Hono:

app.post('/signup', async (c) => {
const body = await c.req.parseBody()
try {
await signupUserSchema.validate(body);
// create the user and redirect
return c.json({ ok: true });
} catch (err) {
throw new HTTPException(401, {message: String(err)})
}
});


הקוד לוקח נתונים שהגיעו מבקשת POST לטופס ומוודא שהם תקינים תוך שימוש בסכימה של yup. המבנה הזה טוב כי הוא מפריד בין "מה שצריך לעשות" ל"איך צריך לעשות את זה", כלומר ברור כשמסתכלים על הקוד שיש פה וולידציה, ברור מה קורה אם הוולידציה מצליחה או אם היא נכשלת, אבל הוולידציה עצמה מבוססת על סכימה שמוגדרת במקום אחר. אם בעתיד נרצה לשנות שדות בטופס או את חוקי הוולידציה שלהם לא נצטרך לגעת בקוד של ה Endpoint.

ורק בשביל להשלים את התמונה זה קוד הסכימה מתוך הקובץ signupUserSchema:

import {object, string} from 'yup';

export default object().shape({
email: string().required().email(),
password: string().required().min(3),
});


וקוד הטופס:

<!DOCTYPE html>
<html>
<body>
<h1>Sign Up</h1>
<form action="/signup" method="post">
<label>
Email
<input type="email" name="email" required />
</label>

<label>
Password
<input type="password" name="password" required minlength="3" />
</label>

<input type="submit" />
</form>
</body>
</html>

ToCode

25 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-yup-server-side-validation

ToCode

24 Dec, 05:08


"רק" עוד render אחד
הבעיה היא לא ה"רק" עוד render אחד שהקומפוננטה שלך מוסיפה למערכת בגלל שימוש לא נכון ב useEffect.

הבעיה היא מאות ה render-ים שיתווספו בארבע השנים הקרובות כשאנשים יעתיקו את הקוד הזה לעוד מקומות וישתמשו בקומפוננטה בדרכים לא צפויות.

יש רק שתי דרכים לזוז קדימה ולשמור על מהירות לאורך זמן:

1. אפשר לכתוב תמיד את הקוד הכי נכון (קשה מאוד).

2. אפשר לכתוב קוד עובד ופעם בכמה ימים לנקות אותו (גם קשה מאוד).

וכן ברור לי שאין דרך קלה לזוז קדימה במהירות לאורך זמן. החיים זה לא ספר לימוד וראינו כבר מספיק מערכות שיהיה קשה מדי להציל. ובכל זאת, נסו לזכור את זה בפעם הבאה שאתם חושבים "אף אחד לא ישים לב אם אוסיף רק עוד render אחד".

ToCode

24 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-just-one-more-render

ToCode

23 Dec, 05:08


איך כדאי לבנות
מתכנתים מנוסים יודעים יותר טוב לראות בעיות מראש ולחזות איפה דברים אולי יישברו. כשאנחנו מגייסים מתכנתים מנוסים נוכל להשתמש במיומנות זאת בראיונות עבודה וכך גם לקבל שיחה יותר מעניינת וגם לסנן טוב יותר אנשים שבאמת עבדו עם טכנולוגיה מסוימת ולמדו מטעויות שעשו. הנה כמה דוגמאות לסוג השאלות ששווה לחפש:

1. יש לי שרת REST API ואלה ה Endpoints שלו. מה יכולות להיות הבעיות בממשק שבחרתי? איזה דרישה או דרישות לקוח יהיה לי קשה לממש?

2. נתונה ספריית ריאקט וזה ה API שלה. באיזה סוגי מערכות יהיה קל לשלב אותה? ואיפה יהיה קשה?

3. נתון מבנה הטבלאות בבסיס הנתונים. למה כדאי לשים לב כשיוצרים את בסיס הנתונים? איזה פיצ'רים יהיה קל או קשה לממש עם טבלאות אלה? איזה פיצ'ר עלול לחייב אותי ל Data Migration משמעותי?

4. נתון קוד שעושה X. איזה דרישה חדשה יכולה להגיע מהלקוח שתשבור את הקוד הזה? עם איזה דרישות לקוח יהיה מאוד קל להתמודד בעזרת הקוד הקיים?

5. נתונים שלושה מימושים לפונקציה X, כולם מחזירים את אותה תוצאה. מה היתרונות והחסרונות של כל מימוש?

המשותף לכולן: במקום לשאול "איך עושים X" אנחנו עוברים לשאול "איך כדאי לעשות X".

ToCode

23 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-senior-interview-questions

ToCode

22 Dec, 05:07


וולידציית קלטים בצד לקוח זה תמיד כאב ראש אבל זה רק נהיה יותר מעייף כשצריך לחבר בין הוולידציה לספריית SPA. ספריות קוד לטיפול אוטומטי בדברים האלה קיימות אבל האמת שבלי קוד תמיד עדיף על קוד שמוחבא בתוך ספרייה. שימוש בפקודות המובנות ב HTML חוסך אלמנטים עודפים ב DOM וחוסך משתנים ריאקטיביים וכך גם עבודת JavaScript.

ומה אתכם? איך אתם אוהבים לנהל את הטפסים שלכם? אל תתביישו לשתף בתגובות או בטלגרם.

ToCode

22 Dec, 05:07


דוגמת קוד: בדיקת טופס עם yup ב vue
הדרך הכי קלה היום לעבוד עם טפסים היא לדלג עליהם בספריית ה SPA שלנו, ולהשתמש במאפייני ה HTML ו ValidityState של רכיבי הקלט כדי להציג הודעות למשתמשים. ועדיין לפעמים אנחנו צריכים בדיקות יותר מסובכות או שרוצים להריץ תהליך יותר מסודר סביב הנושא של בדיקות קלט בצד לקוח. במצבים כאלה נוכל להשתמש בספריה כמו yup כדי לתאר את הטופס ובקוד לעדכן את הודעות השגיאה בתוך אפליקציית vue או ריאקט. בואו נראה איך זה עובד.

סכימת yup
ספריית yup היא ספריית וולידציה למידע שמאפשרת להריץ בדיקות ולבצע המרות על קלט שקיבלה. יש לה המון אופציות איך להגדיר את הקלט ואנחנו ניקח כאן דוגמה פשוטה עבור טופס רישום שמקבל כתובת אימייל וסיסמה, שניהם הכרחיים ואורך הסיסמה צריך להיות לפחות 8 תווים. זה הקוד כדי ליצור את הסכימה עם yup:

import {object, string} from 'yup';

export const signupFormSchema = object({
email: string()
.email()
.required(),

password: string()
.min(8, 'password must be at least 8 characters long')
.required(),
});


סכימה של yup יודעת לקבל אוביקט ולהגיד אם הוא מתאים או לא בעזרת פונקציה אסינכרונית בשם validate. לדוגמה הקוד הבא:

try {
await signupFormSchema.validate({email: '[email protected]', password: '12345678'});
console.log('OK');
} catch (err) {
console.log(err);
}


מדפיס את ההודעה OK, אבל הקוד הזה מדפיס הודעת שגיאה כי הסיסמה קצרה מדי:

try {
await signupFormSchema.validate({email: '[email protected]', password: '123'});
console.log('OK');
} catch (err) {
console.log(err);
}


חיבור הסכימה לטופס
חיבור הסכימה לטופס הוא הסיבה שאני מעדיף כמעט תמיד להשתמש בפקודות המובנות ב HTML על פני קוד, במיוחד בעבודה עם ספריות SPA. אלה הדברים שהייתי צריך לעשות ב vue בשביל החיבור (וכן אני יודע שיש ספריות שיעשו את החיבור הזה בשבילי. להחביא קוד בתוך ספריה לא מעלים אותו):

1. הגדרתי משתנה ריאקטיבי שישמור את השגיאות.

2. הגדרתי משתנה ריאקטיבי נוסף שישמור את מצב ההגשה של הטופס (אם כבר הגשתי אותו או לא).

3. הגדרתי פונקציה לטפל באירוע submit של הטופס ולבצע וולידציה על המידע שבטופס.

4. הוספתי אלמנטי HTML להצגת הודעות השגיאה.

סך הכל קוד הטופס ב vue הוא:

<script setup lang="ts">
import {signupFormSchema} from '../schemas/signup1';
import { ValidationError } from 'yup';
import {computed, ref, Ref} from 'vue';
const errors = ref<Array<ValidationError>>([]);
const status = ref<'pending'|'submitted'>('pending');

const errorsObject: Ref<Record<string, Array<ValidationError>>> = computed(() =>
Object.groupBy(errors.value, e => e.path))

async function signup(ev: Event) {
const form = ev.target as HTMLFormElement;
const fd = new FormData(form);
const data = Object.fromEntries(fd);

try {
await signupFormSchema.validate(data, {abortEarly: false})
errors.value = [];
status.value = 'submitted';
} catch (err) {
if (err instanceof ValidationError) {
errors.value = err.inner;
console.log(err.inner);
} else {
throw err
}
}
}
</script>

<template>
<div v-if="status == 'submitted'">
Ready!
</div>
<form @submit.prevent="signup">
<div class="errors" v-if="errors.length > 0">
<h3>Form Erorrs:</h3>
<ul >
<li
v-for="(error, index) in errors"
:key="index"
>
{{ error.message }}
</li>
</ul>
</div>

<div>
<label>
<span>Email: </span>
<input name="email" type="email" />
</label>
</div>
<div>
<label>
<span>
Password:
</span>
<input name="password" type="password" />
</label>
</div>
<div>
<input type="submit" value="Signup" :disabled="status === 'submitted'" />
</div>
</form>
</template>

<style scoped>

label > span {
width: 100px;
display: inline-block;
}

form > div {
text-align: left;
margin: 10px;
}

.errors {
text-align: left;
list-style: none;
padding-left: 10px;
}
</style>


מסקנות

ToCode

22 Dec, 05:07


https://www.tocode.co.il/blog/2024-12-vue-yup-form-example

ToCode

21 Dec, 05:07


אלא אם כן אתם נטפליקס
כשגולש נכנס אליכם לאתר, כמה זמן הדפדפן שלו צריך לעבוד ולהריץ JavaScript לפני שהוא יכול להשתמש באתר? מסתבר שבממוצע התשובה תלויה הרבה יותר בטכנולוגיה איתה בחרתם לבנות את האתר מאשר בפיצ'רים של האתר עצמו. הלכתי ל Page Speed לבדוק 9 אתרי ריאקט ו next.js ולא הופתעתי מהתוצאות:

1. בשביל לקרוא מאמר מהאתר של vercel צריך להריץ JavaScript במשך 2.5 שניות.

2. דף הבית של nike העסיק את הדפדפן במשך 8.1 שניות רק בהרצת הקוד.

3. דף הבית של solana עם הסלוגן Poweful for developers, Fast for everyone בילה "רק" 1.6 שניות בהרצת קוד.

4. לפני שבוחרים ארוחה ב wegmans הדפדפן צריך לבלות 10 שניות בהרצת ה JavaScript שלהם.

5. לפני שבוחרים קורס באקדמיה של קאן צריך לחכות 2.9 שניות כדי להריץ את ה JavaScript אצלם בעמוד.

6. לפני שאפשר לקרוא עדכונים ברדיט נקדיש 2.3 שניות להרצת JavaScript.

7. בקורסרה נבלה 3.8 שניות בהרצת ה JavaScript לפני שנוכל להיכנס לקורסים.

8. יודמי טיפה יותר טובים עם 3.1 שניות זמן ריצה של JavaScript בכניסה לעמוד.

צריך להגיד - משתמשים לא רוצים לבלות זמן בהמתנה ל JavaScript שירוץ. אם אפשר היה לבנות את האתר בצורה שתתן את אותה פונקציונאליות אבל בלי 10 שניות של זמן ריצה של JavaScript כולם היו שמחים יותר. מה שמשותף לכל האתרים ברשימה הוא הבחירה ב React, חלקם ישירות וחלקם גם עם next.js. כן יהיה מעניין לבדוק גם פריימוורקים אחרים אבל לא זאת הנקודה כאן. בואו נסתכל על עוד שני אתרים מעניינים:

הראשון הוא האתר הזה, טוקוד. בטוקוד בכניסה לדף הבית הדפדפן יבלה 0.8 שניות בהרצת JavaScript. זה יותר טוב מכל אתרי הריאקט שברשימה (וכן זו אחת הסיבות בגללן נפרדתי מריאקט כבר לפני כמה שנים). אני לא נטפליקס ואין לי את את הרצון או המשאבים להילחם בפריימוורק כדי שדפים ייטענו מהר.

אתר שני הוא נטפליקס, וידעתם שהוא יבוא. נטפליקס יודעים מה הם עושים ומוכנים להשקיע בתהליכי פיתוח, אופטימיזציות ומה לא. עמוד ה jobs שלהם בכתובת jobs.netflix.com משתמש בריאקט בצורה מאוד אפקטיבית ומבלה רק 0.6 שניות בהרצת JavaScript כשהדף נטען. אגף גם אתר התיעוד של ריאקט מאוד יעיל ומגיע לתוצאות דומות ואף טובות יותר.

הסיפור עם הפריימוורק הוא לא שכל דף ריאקט או next הולך להיטען לאט או לבלות המון זמן בהרצת JavaScript. אם אתם יודעים מה אתם עושים ומוכנים להתייחס לבעיות ביצועים כמו לכל באג אחר אפשר לקבל זמני טעינה טובים למרות ריאקט. מילת המפתח היא "למרות".

ToCode

21 Dec, 05:07


https://www.tocode.co.il/blog/2024-12-unless-youre-netflix

ToCode

20 Dec, 05:08


ה fix השני מיותר
דילן הואנג החליט לסגור את הסטארט-אפ שלו ופרסם את כל הקוד שהם כתבו בגיטהאב כדי שדברים לא ילכו לפח. אני אוהב כאלה הזדמנויות כי זה מאפשר הצצה לפיתוח של מערכת אמיתית, לחצים אמיתיים וקוד אמיתי שנכתב.

כשאני מתחיל להסתכל על קוד אחד המסכים הראשונים שאני פותח הוא הסיטוריית הקומיטים. המטרה היא להבין קצת את המחשבה של מי שכותב את הקוד, לפני שאני צולל לבעיות עצמן ולפיתרונות שנבחרו. בריפו של konfig, הסטארט-אפ של אותו דילן, תופעה אחת בלטה מאוד במסך הקומיטים. הנה כמה דוגמאות:

f1ed4259c fix
75e825f62 fix
c388772ec remove unnecessary env group


1cc57ce33 fix sizing of logo
d26a25c47 fix sizing


f510e4f5d update
89007aad9 update
0d9701a14 updates to workflow


הרבה מהקומיטים, או יותר נכון הודעות הקומיט, מופיעות שוב ושוב. בדוגמה הראשונה אני מדמיין שאחרי שהוא הוריד את ה env group המיותרים היתה איזו תקלה וה fix מתייחס לתיקון אותה תקלה, ואולי התיקון לא הצליח מספיק טוב בפעם הראשונה אז מיד אחריו היה fix נוסף שמתקן את התיקון. מוכר? ברור. אבל יש גם דרך יותר טובה. ננסה את זה.

יצרתי ריפו ריק ובתוכו רק את שלושת הקומיטים כך שהלוג הוא:

41a1948 fix
0a197d4 fix
e7572cf remove unnecessary env group
daf174b initial commit



בגלל שלא דחפתי את השינויים לשום מקום, כל מה שצריך בשביל לשנות את שלושת הודעות הקומיט הוא "לחזור אחורה" שלושה קומיטים ולעשות קומיט מחדש:

$ git reset HEAD~3
Unstaged changes after reset:
M textfile.txt

$ git add .
$ git commit -m 'remove unnecessary env group'


הפרויקט כעת נמצא באותו מצב של קומיט 41a1948 אבל הלוג הוא:

622a0da remove unnecessary env group
daf174b initial commit


ומי שיסתכל בלוג יראה בתוך קומיט 622a0da את כל השינויים של כל שלושת הקומיטים המקוריים.

נ.ב. יש מערכות שמכריחות אותך לעשות Push כדי להפעיל CI Pipeline. שינוי קומיטים אחרי דחיפה הוא תהליך הרבה פחות ידידותי ויכול לבלבל מפתחים אחרים שכבר משכו את הקומיטים הקודמים. במצבים כאלה אני אוהב ליצור ענף נפרד לצורך הפעלת ה CI ואז אחרי שסיימתי לבדוק אני ממזג את הענף ומוחק את הקומיטים המיותרים עם ריבייס.

ToCode

20 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-git-second-fix

ToCode

19 Dec, 05:08


לא צריך לבחור שניים
משולש ניהול הפרויקטים אומר שבהינתן פרויקט אפשר לדמיין את הזמן שהוא ייקח, איכות התוצאה והתקציב בתור משולש, ושכל שינוי באחד יבוא על חשבון משהו מהאחרים. אפשר לדמיין את זה בפרויקט תוכנה כשאנחנו חושבים שאפשר לבנות את המוצר יותר מהר אבל בצורה פחות איכותית.

לפעמים המשולש הזה עוזר לנו להבין את המציאות.

רוב הזמן המשולש הוא הסחת דעת ומפריע לנו לראות דרכים טובות יותר, גם כשההתלבטות היא לא באמת התלבטות. כך אפשר לדמיין-

"מצד אחד אני רוצה לעבוד על פרויקטים מעניינים, אבל מצד שני רוצה להרוויח יותר כסף".

"מצד אחד אני רוצה לבנות מוצר יותר מהר, אבל מצד שני לא רוצה להתפשר על איכות הקוד".

כשאנחנו שמים את הדברים בתוך המשולש אנחנו מתעלמים בכוונה מהאופציות שמשלבות את שני הדברים, כי לכאורה אופציות כאלה לא אמורות להיות בכלל על השולחן. אז הפרילאנסרית שמחפשת פרויקטים מעניינים יותר מראש מחפשת גם את אלה שמשלמים פחות, כי אלה שמשלמים הרבה בטח ידרשו ממנה יותר מדי וויתורים (וכך תפסיד את הלקוח שגם יש לו פרויקט מעניין וגם מוכן לשלם הרבה בשביל הבן אדם הנכון לבצע אותו). או המתכנת שבטוח שאיכות קוד אומר שצריך לכתוב דברים לאט, וכך מפסיד הזדמנויות להשתמש בתשתיות קיימות או לבנות דברים בצורה יותר חכמה שתאפשר גם מהירות פיתוח גדולה יותר וגם קוד יותר איכותי.

כלל אצבע טוב בסיפור הזה הוא לא להניח מראש שיש בחירה בין אילוצים שונים. לפעמים באמת צריך לבחור שניים, אבל עדיף שזו לא תהיה ברירת המחדל.

ToCode

19 Dec, 05:08


https://www.tocode.co.il/blog/2024-12-dont-pick-two

ToCode

18 Dec, 06:42


שלושה טריקים ששיפרו לי משמעותית את ציון הביצועים ב Page Speed
אחרי משחקי הביצועים אתמול חשבתי שיהיה מעניין לבדוק גם את הבלוג שלי פה בטוקוד ולראות מה Page Speed חושב עליו והאם אפשר לשפר. האמת שבאתי מאוד אופטימי לבדיקה כי בראש חשבתי שמדובר בסך הכל באתר סטטי כמעט בלי JavaScript ושרת שרוב הזמן מגיב מהר. לא נעים לשתף מה Page Speed חשב עליו, אבל בואו רק נגיד שזה לא תאם לציפיות. מסתבר שלאורך השנים הוספתי לאתר פיצ'רים קטנים בלי לחשוב על ההשפעה שלהם על זמני הטעינה, ובלי לחשוב עד הסוף אם אני מוסיף אותם בדרך הטובה ביותר. הטעויות הקטנות האלה מצטברות ומשפיעות.

אלה שלושת הטריקים שיישמתי כדי לשפר משמעותית את ציון ה Page Speed שעכשיו עומד על 94 לדף של פוסט מהבלוג:

טעינת נכסים סטטיים דרך Apache
האתר רץ על Rails וההמלצה באתרי ריילס היא לא להעמיס על השרת עם נכסים סטטיים (כלומר קבצי תמונות, js, css וכן הלאה). מספיק שבגלל עומס על השרת ייקח לו 3 שניות להיזכר שהוא צריך לשלוח קובץ CSS והאתר נטען לאט אצל מישהו. שרתי Apache ו nginx או CDN-ים למיניהם הרבה יותר טובים בשליחת קבצים סטטיים לגולשים, לכן הטריק הראשון היה להוריד משרת האפליקציה את המשימה הזאת ולהעביר אותה לשרת ה Apache שעומד לפניו.

השהיית טעינת JavaScript עד שנצטרך אותו
קופסת ה Comments שנמצאת פה מתחת לכל פוסט מגיעה מ disqus וה JavaScript שלהם הוא פח. נגן הוידאו שמשתמשים בו כדי לראות את הסרטים בקורסים מגיע מ Spotlightr ויש גם כמה אלמנטים של UI שמגיעים מספריית flowbite. את כל אלה העברתי לטעינה מושהית כך שייטענו רק כשנצטרך אותם, כלומר אם גולש באמת קורא את הפוסט וגולל עד למטה את העמוד אז נטען את תיבת ה Comments, אחרת אין טעם לטעון את ה JS שלהם. אם יש בעמוד סרט אפשר לטעון את הנגן של ספוטלייטר, אבל בטח לא צריך לטעון אותו בכל דף.

השינוי הזה מאוד שימח את Page Speed שמודד כמה זמן מעבד ה JavaScript של העמוד לקח.

מחיקת JavaScript שלא צריך
בהמשך להשהייה שמתי לב שיש גם ספריות JS שפעם השתמשתי בהן והיום כבר לא. הבולטת בהן היא Google Analytics ממנה נפרדתי לטובת Simple Analytics, אבל היו עוד כמה ספריות של UI שלא היה צריך. מחיקה של JavaScript תמיד משמחת את Page Speed וכך קיבלתי אצלם עוד כמה נקודות.

נקודות להמשך
אחרי השיפור בדפי הבלוג העבודה עדיין רחוקה מלהסתיים. יש עדיין קבצי JavaScript שאני חושב שאפשר לוותר עליהם, ה Page Speed חושב שעדיין יש לי יותר מדי CSS ואפשר לקצץ גם שם ועוד לא נגעתי בעמוד הבית או בדפי הקורסים. ועדיין יצאתי מהמשחק מחוזק ואופטימי לגבי שיפורים שעוד אפשר יהיה ליישם בהמשך.

הנקודה המרכזית שהיתה לי מעניינת בניסוי הזה היא שמושגים כמו הנתיב הקריטי ונכסים שחוסמים Render כבר מוטמעים בתוך הפריימוורקס וכמעט לא הייתי צריך לגעת בסדר הטעינה של דברים. גם מהירות הרשת היא מאוד טובה ואפשר לשלוח קבצים גדולים יותר בלי לפגוע בביצועים. האתגר המרכזי שעדיין נשאר הוא לכתוב ולשלוח פחות JavaScript, כי זמן הריצה הופך להיות צוואר הבקבוק של אתרים.

ToCode

18 Dec, 06:42


https://www.tocode.co.il/blog/2024-12-page-speed-improvements

ToCode

16 Dec, 07:27


https://www.tocode.co.il/blog/2024-12-when-best-practices-change

ToCode

25 Nov, 06:26


אבל ביקשתי לשים את זה באמצע
נתבונן על הגדרת ה CSS הבאה:

body, html {
height: 100vh;
padding: 0;
margin: 0;
}

main {
display: flex;
place-items: center;
background: #a0a0a0;
}


וה HTML שבתוך ה body כולל בסך הכל:

<main>hello world</main>


קצת מפתיע לגלות שהטקסט לא מופיע באמצע המסך, לא אופקית ולא אנכית. מה קורה כאן?

1. אלמנט ה body אכן מוגדר לתפוס את כל הגובה של המסך, אבל אין הגדרת גובה על ה main ולכן גובהו ייקבע לפי התוכן. מאחר והתוכן הוא שורה בודדת זה יהיה גובה האלמנט.

2. הפקודה place-items: center באמת קובעת ל main למקם את הילדים שלו באמצע שלו. אבל שימו לב שכל גובהו הוא שורה אחת ולכן השורה הבודדת תופסת את כל הגובה.

3. ומה לגבי הרוחב? אלמנט main תופס בברירת מחדל את כל רוחב העמוד, ולכן כשמנסים לשים אותו "באמצע" הוא עדיין תופס את כל רוחב העמוד.

איך בכל זאת נשים את הטקסט באמצע המסך, גם אופקית וגם אנכית? ברגע שמבינים את הבעיה התיקונים הם פשוטים:

1. נקבע את הגובה של main למספר קבוע או לגובה של המיכל שלו, במקום לגובה של התוכן שלו, על ידי הגדרת height: 100%.

2. אם אנחנו רוצים שהטקסט יהיה באמצע ה main נוכל להגדיר מאפיין text-align על ה main. הגדרת text align קובעת את יישור הטקסט בתוך בלוק בתצוגה.

3. אם אנחנו רוצים להגדיר מאפייני בלוק נוספים לטקסט נוכל להגדיר סביבו p ולקבוע את רוחב הפיסקה להיות בדיוק רוחב התוכן. כך אפשר יהיה להוסיף עוד פיסקאות ולקבל מספר אלמנטים באמצע.

סך הכל הדוגמה עם היישור בקודפן:

<iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/WbeeQqm?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/WbeeQqm">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>

ובקישור:
https://codepen.io/ynonp/pen/WbeeQqm

ToCode

25 Nov, 06:26


https://www.tocode.co.il/blog/2024-11-center-items

ToCode

24 Nov, 05:14


https://www.tocode.co.il/blog/2024-11-teachers-and-chatgpt

ToCode

24 Nov, 05:14


מורים בעידן ה Chat GPT
מבחינת תפקיד המורה Chat GPT רק חיזק תהליכים שכבר היו לפניו: כל הידע ממילא קיים ברשת, לכל שאלה יש כבר תשובות מצוינות והסברים מדויקים, ועכשיו אפשר אפילו לשאול כשלא מבינים ולקבל הסבר מותאם אישית.

אז מה נשאר למורים לעשות? הנה שלוש משימות חשובות:

1. לקבוע את התפריט - מסתבר שאין דרך אחת ללמד React (או כל נושא אחר). מורים שונים יוכלו לבנות תוכניות לימודים שונות, כלומר יבחרו את הנושאים וסדר הנושאים שעלינו ללמוד. כן יש הכל באינטרנט, אבל הלימוד הוא הרבה יותר יעיל כשמישהו עוזר לי לבחור מה הדבר הבא שצריך לדעת.

2. לזהות טעויות - אני יודע לשאול את Chat GPT מה אפשר לשפר בקוד שכתבתי, ולפעמים יהיו לו עצות טובות אבל הוא רחוק שנות אור מלהבין את הצרכים האמיתיים שלי ולזהות פערי הבנה. אני צריך מורה שיעזור לי לשים לב כשאני עושה דברים לא בסדר, מתוך הבנה של עולם התוכן שלי והאתגרים הספציפיים בתוכו.

3. לתת מילה טובה - אחרי ההתלהבות הראשונית לימוד דורש התמדה לאורך זמן. הרבה יותר קל להתמיד כשאנחנו לא לבד, כשיש מישהו שמחכה לראות כמה התקדמתי השבוע וישמח איתי כשדברים מצליחים (או יעודד אותי כשאני רוצה לוותר).

ככל שנקדים להבין שאין לנו בעלות על הידע נוכל להתפנות לאתגרים האמיתיים של מורים במאה ה-21. כי אם יש משהו שלמדנו מהשנים האחרונות הוא שיותר ידע זה לא מספיק בשביל לעשות אנשים חכמים יותר.

ToCode

23 Nov, 05:14


2. בעת לחיצה על הכפתור הפונקציה פונה לשרת ומפעילה את הפונקציה likeLink שמוגדרת שם. זה נראה כמו הפעלה רגילה של פונקציה אבל בעצם מדובר על Server Action - כלומר קריאת Ajax שגורמת להפעלת פונקציה בצד שרת והחזרת התוצאה. הפונקציה ממשיכה לבדיקת התוצאה ולפי זה מחליטה אם העדכון הצליח ואם כן היא מעלה את מספר הלייקים של הלינק. באפליקציה גדולה יותר היינו יכולים לשלב רידאקס ואז היינו יכולים בצד שרת ליצור את אוביקט ה Action המתאים והפונקציה בצד הדפדפן היתה עושה לו dispatch.

3. אתם שמים לב שהעברתי את ה user בתור prop. לצערי אין ל RSC תמיכה ב Context. יש כמה טריקים שאפשר לעשות כדי לעקוף את זה ולהגדיר Context בצד לקוח אבל ארחיב על זה בפוסט אחר. זה לא היה מספיק חשוב לכאן. ממילא במערכת אמיתית תשתמשו במנגנון ניהול משתמשים חכם יותר.

סקריפטים לניהול
בעבודה עם דריזל קיט הפקודה:

$ npx drizzle-kit generate


מסתכלת על הסכימה ועל בסיס הנתונים ומייצרת קובץ SQL שמעדכן את בסיס הנתונים כדי להתאים לסכימה. הקובץ נשמר בתיקיית drizzle ואפשר לראות שבפרויקט הדוגמה יש לי קובץ אחד כזה עם התוכן הבא:

CREATE TABLE \likes\ (
\id\ integer PRIMARY KEY AUTOINCREMENT NOT NULL,
\user_id\ integer NOT NULL,
\link_id\ integer NOT NULL,
FOREIGN KEY (\user_id\) REFERENCES \users\(\id\) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (\link_id\) REFERENCES \links\(\id\) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE \links\ (
\id\ integer PRIMARY KEY AUTOINCREMENT NOT NULL,
\user_id\ integer NOT NULL,
\href\ text NOT NULL,
FOREIGN KEY (\user_id\) REFERENCES \users\(\id\) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE \users\ (
\id\ integer PRIMARY KEY AUTOINCREMENT NOT NULL,
\name\ text NOT NULL
);



שימו לב להערה --> statement-breakpoint - היא חייבת להופיע בין פקודות שונות בקובץ כדי שירוצו כל הפקודות. היא נוצרת אוטומטית מפקודת generate אבל אם אתם עושים עדכון ידני אל תשכחו לכתוב אותה.

בנוסף ליצירת הסכימה אני אוהב להחזיק סקריפטים להכנסת מידע לבסיס הנתונים או מחיקתו. בפרויקט הדוגמה יצרתי סקריפט אחד ששומר מידע לפיתוח:

import { db } from './db';
import { usersTable, linksTable, likesTable } from './db/schema';

async function seedDatabase() {
// Insert users
const userIds = await db.insert(usersTable).values([
{ name: 'Alice' },
{ name: 'Bob' },
{ name: 'Charlie' },
{ name: 'Dave'},
]).returning({ id: usersTable.id });

// Insert links
const linkIds = await db.insert(linksTable).values([
{ user_id: userIds[0].id, href: 'https://www.duckduckgo.com' },
{ user_id: userIds[1].id, href: 'https://www.tocode.co.il' },
{ user_id: userIds[2].id, href: 'https://nextjs.org/blog/next-15' },
]).returning({ id: linksTable.id });

// Insert likes
await db.insert(likesTable).values([
{ user_id: userIds[0].id, link_id: linkIds[1].id },
{ user_id: userIds[1].id, link_id: linkIds[2].id },
{ user_id: userIds[2].id, link_id: linkIds[1].id },
{ user_id: userIds[1].id, link_id: linkIds[0].id },
{ user_id: userIds[0].id, link_id: linkIds[1].id },
]);

console.log('Database seeded successfully!');
}

seedDatabase().catch(console.error);


וסקריפט אחר שמוחק את המידע:

import { db } from './db';
import { usersTable, linksTable, likesTable } from './db/schema';

async function truncateDatabase() {
await db.delete(usersTable)
await db.delete(linksTable);
await db.delete(likesTable);
}

truncateDatabase().catch(console.error);


לדריזל אין עדיין דרך מובנית להריץ סקריפטים כאלה אז הוספתי את הפקודות ל package.json:

  "scripts": {
"dev": "next dev",
"seed-dev": "tsx src/seed-dev.ts",
"truncate-dev": "tsx src/truncate-dev-db.ts",
"build": "next build",
"start": "next start",
"lint": "next lint"
},


סיכום
החלקים המרכזיים בפרויקט next עם בסיס נתונים הם בסך הכל:

ToCode

23 Nov, 05:14


1. יצירת קבצי לוגיקה לגישה לבסיס הנתונים, בדרך כלל ליד הגדרת הסכימה. אני אוהב את דריזל ואת kysely, עבדתי עם Sequelize ב JavaScript (לפני טייפסקריפט) וגם הייתי מרוצה. את TypeORM לא אהבתי. אבל בסוף זה רק עניין של טעם כולם טובים.

2. יצירת קומפוננטות צד שרת שמושכות את המידע והופכות אותו ל JSX.

3. יצירת קומפוננטות צד לקוח שיפעילו עם Server Actions את קבצי הלוגיקה.

האתגר המרכזי שעדיין נשאר באינטגרציה הוא לקחת את התוצאה של ה Server Action ולעדכן את המידע שעל המסך. בדוגמה שלי הייתי צריך להגדיר את likesCount בתור סטייט בשביל שאוכל לעדכן אותו. גישה אחרת שראיתי זה להפעיל refresh ולתת לשרת לרנדר מחדש את ה Server Components עם המידע המעודכן.

ToCode

23 Nov, 05:14


.leftJoin(users, eq(links.user_id, users.id))
.leftJoin(likes, eq(links.id, likes.link_id))
.groupBy(links.id, users.name);
}

export async function likeLink(user_id: number, link_id: number) {
console.log(\user id = ${user_id}, link_id = ${link_id}\);
// Check if the like already exists
const existingLike = await db
.select()
.from(likes)
.where(and(eq(likes.user_id, user_id), eq(likes.link_id, link_id)))
.limit(1);

if (existingLike.length > 0) {
console.log('User has already liked this link.');
return { success: false, message: 'You have already liked this link.' };
}

// Add the like
const newLike = await db
.insert(likes)
.values({ user_id, link_id })
.returning();

console.log('Like added successfully:', newLike);
return { success: true, message: 'Like added successfully!', like: newLike };

}


עכשיו דריזל יכול להיות מעייף ועמוס, אבל לקירבה ל SQL יש גם יתרונות. קודם כל ברור מאוד מה קורה בכל פונקציה, ומאוד קל ל AI לקבל סכימה ולכתוב בשבילכם שאילתות דריזל. בניגוד לכלי ORM, פה אם יש טעות בשאילתה או בעיית ביצועים רואים אותה מיד.

העברת המידע לדפדפן
החלק השני בתוכנית הוא העברת המידע לדפדפן, ופה next.js עושה את הקסם שלו. הקובץ page.tsx הוא Server Component ולכן הוא יכול להפעיל כל פונקציה בצד שרת, כולל פונקציות גישה לבסיס הנתונים. זה הקוד:

import { queryHomepageLinks } from "@/db/links";
import { login } from "@/db/users";
import Link from './client/Link';

export default async function Home() {
const homepageLinks = await queryHomepageLinks();
const currentUser = (await login('Dave')).user!;

return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<ul className="space-y-4 text-left text-gray-500 dark:text-gray-400">
{homepageLinks.map(link => (
<Link user={currentUser} key={link.linkId} link={link} />
))}
</ul>
</main>
</div>
);
}


בשביל הפשטות כל מערכת ההתחברות פה מאוד מנוונת. כל מי שנכנס למערכת מזדהה בתור "Dave" ובלי סיסמה. יותר מעניין זה המערך homepageLinks, שמגיע מהפונקציה queryHomepageLinks שהוגדרה בקובץ הגישה לבסיס הנתונים. במערך הזה אנחנו כבר משתמשים בכתיבת הקומפוננטה.

עכשיו פה המקום להזכיר שעד לפני כמה שנים (ועדיין במערכות רבות שאני מכיר) שיטת העבודה עם מידע שמגיע מבסיס הנתונים היתה כתיבת שאילתה כפולה - קודם לוקחים את ה HTML, CSS, JS מהשרת, ואז יש קריאת Ajax שמושכת את המידע הראשוני (מערך הלינקים בדוגמה שלנו). העבודה עם RSC חוסכת את הכפילות ויותר מזה, מאחר שהקומפוננטה מתרנדרת רק בצד השרת והקוד שלה אפילו לא נשלח לדפדפן.

מה שכן נשלח לדפדפן זה הקוד של קומפוננטת Link שמוגדרת בקובץ client/link.tsx:

'use client'
import { likeLink } from "@/db/links";
import { useState } from "react";

export default function Link({link, user}: {
link: {
linkId: number,
href: string,
likesCount: number,
},
user: { id: number, name: string },
}) {
const [likesCount, setLikesCount] = useState(link.likesCount);

async function handleClick() {
const result = await likeLink(user.id, link.linkId);
if (result.success) {
setLikesCount(c => c + 1);
} else {
alert(result.message);
}
}

return <li className="flex items-center space-x-3 rtl:space-x-reverse">
<span className="mx-2 inline-block w-4">{likesCount}</span>
<button onClick={handleClick} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded">+1</button>
<span className="font-bold">{link.href}</span>
</li>
};


הקומפוננטה מקבלת את הלינק מקומפוננטת צד השרת ומציגה אותו, אבל יש לה עוד שני טריקים:

1. משתמש יכול ללחוץ על פלוס כדי לעשות "לייק" ללינק. בשביל זה הקומפוננטה מגדירה משתנה state שמתחיל עם הערך שהגיע מהשרת אבל יוכל להשתנות בעקבות לחיצה על כפתורים.

ToCode

23 Nov, 05:14


פרויקט דוגמה: next עם בסיס נתונים drizzle
הרבה זמן אני רוצה להקליט דוגמת וידאו שמסבירה איך לבנות פרויקט ב next.js בשילוב עם בסיס נתונים. מאחר ואני לא בטוח מתי אגיע לזה חשבתי להתחיל עם מדריך טקסט שיראה את כל החלקים החשובים. יום אחד אולי גם יהיה וידאו ואז אוסיף לינק לפוסט הזה.

מבנה המערכת
האמת ש Server Actions ו React Server Components אחראיים לרוב מה שקורה בפרויקט הזה, ובסיס הנתונים הוא רק החלק הקטן.
את הקוד המלא אתם יכולים למצוא בקישור:
https://github.com/ynonp/demo-next-sql-drizzle

אלה החלקים המרכזיים בקוד:

1. ספריית src/db מחזיקה את כל הקוד שקשור לעבודה עם בסיס הנתונים.

2. בזכות השימוש ב Server Actions ו RSC אין צורך להגדיר API. הגישה לשרת היא בסך הכל הפעלת פונקציה. בפרויקט הדוגמה יש גישה אחת מתוך קוד צד שרת בקובץ page.tsx וגישה אחת מתוך קוד צד לקוח בקובץ client/link.tsx.

3. בתיקייה הראשית של הפרויקט הגדרתי שני סקריפטים להכנסת מידע לדוגמה לבסיס הנתונים ומחיקתו. בנוסף בתיקיית הפרויקט הראשית הקובץ drizzle.config.ts אחראי על הגדרת החיבור לבסיס הנתונים.

עכשיו בואו נעבור על שלושת החלקים.

בסיס הנתונים
בסיס הנתונים נשמר בדוגמה בתור קובץ SQLite ואנחנו רואים את פרטי ההתחברות בקובץ הקונפיגורציה של דריזל:

import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './drizzle',
schema: './src/db/schema.ts',
dialect: 'sqlite',
dbCredentials: {
url: process.env.DB_FILE_NAME!,
},
});


הפרויקט משתמש ב drizzle-kit כך שהגדרות בסיס הנתונים מגיעות מקובץ הסכימה. בפרויקט הדוגמה שכתבתי יש משתמשים, המשתמשים מפרסמים לינקים ויכולים לעשות "לייק" ללינקים שהם או אחרים פרסמו. זאת הסכימה:

import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const usersTable = sqliteTable("users", {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
});

export const linksTable = sqliteTable("links", {
id: int().primaryKey({ autoIncrement: true }),
user_id: int().references(() => usersTable.id, {onDelete: 'cascade'}).notNull(),
href: text().notNull(),
});

export const likesTable = sqliteTable('likes', {
id: int().primaryKey({autoIncrement: true}),
user_id: int().references(() => usersTable.id, {onDelete: 'cascade'}).notNull(),
link_id: int().references(() => linksTable.id, {onDelete: 'cascade'}).notNull(),
});


בנוסף אני מאוד אוהב להגדיר פונקציות לעבודה עם בסיס הנתונים בקבצים קטנים בתיקיית db. אני מסדר את הפונקציות לפי נושאים ולכל נושא יש קובץ, לדוגמה הקובץ users.ts אחראי על פונקציות שקשורות למשתמשים. זה תוכנו:

import {linksTable, usersTable, likesTable as likes} from './schema';
import {db} from './index';
import { eq } from 'drizzle-orm';

export async function login(name: string) {
// Check if the user already exists
const existingUser = await db
.select()
.from(usersTable)
.where(eq(usersTable.name, name))
.limit(1);

if (existingUser.length > 0) {
console.log('User logged in:', existingUser[0]);
return { success: true, user: existingUser[0] };
} else {
return { success: false, user: null };
}
}


בקבצים האלה תישמר כל הלוגיקה של המערכת. בגלל ש Drizzle לא קשיח כמו TypeORM או Sequelize, אפשר לארגן את הקבצים בכל צורה שמתאימה לכם. אני אוהב להתחיל פרויקט עם קובץ לוגיקה אחד ולאט לאט לפצל אותו לקבצים יותר קטנים כשהוא מתחיל להרגיש עמוס.

אגב קובץ הלוגיקה השני מהתיקייה נקרא links ושם כתבתי את השאילתות למשיכת פרטי הלינקים:

'use server';

import {
linksTable as links,
usersTable as users,
likesTable as likes} from './schema';
import {db} from './index';
import { eq, sql, and } from 'drizzle-orm';

/**
* Returns a list of all links joined with their authors
* and number of likes
*/
export async function queryHomepageLinks() {
return db.select({
linkId: links.id,
href: links.href,
authorName: users.name,
likesCount: sql<number>\COUNT(${likes.id})\, // Aggregates likes
})
.from(links)

ToCode

23 Nov, 05:14


https://www.tocode.co.il/blog/2024-11-next-drizzle-demo

ToCode

22 Nov, 05:15


שימו לב - ריאקט 19 ו next
גירסה 15 של נקסט כבר מגיעה עם ריאקט 19 (ה RC, כי ה 19 הרשמי עוד לא יצא). אני בטוח ש vercel יודעים שהם מסתמכים על גירסת RC ושהאקוסיסטם עדיין לא תואם. אני לא בטוח שאני מבין למה הם כל כך ממהרים.

למה זה מעייף? הנה התקנה חדשה של next ו drizzle:

$ npx create-next-app@latest my-next-app
$ cd my-next-app
$ npm i drizzle-kit drizzle-orm @libsql/client dotenv

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: [email protected]
npm error Found: [email protected]
npm error node_modules/react
npm error react@"19.0.0-rc-66855b96-20241106" from the root project
npm error peer react@"*" from @op-engineering/[email protected]
npm error node_modules/@op-engineering/op-sqlite
npm error peerOptional @op-engineering/op-sqlite@">=2" from [email protected]
npm error node_modules/drizzle-orm
npm error drizzle-orm@"*" from the root project
npm error 4 more (expo-sqlite, expo, @expo/dom-webview, react-native-webview)
npm error
npm error Could not resolve dependency:
npm error peerOptional react@">=18" from [email protected]
npm error node_modules/drizzle-orm
npm error drizzle-orm@"*" from the root project


וזה של react-select:

$ npm install react-select

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: [email protected]
npm error Found: [email protected]
npm error node_modules/react
npm error react@"19.0.0-rc-66855b96-20241106" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^16.8.0 || ^17.0.0 || ^18.0.0" from [email protected]
npm error node_modules/react-select
npm error react-select@"*" from the root project


ויש עוד המון.

עכשיו אני יודע איך לשנמך את הריאקט בפרויקט נקסט החדש שלי (כל עוד הוא משתמש ב Pages Router, כי App Router כן דורש את ריאקט 19). אני גם יודע איך להישאר בגירסה 14 של נקסט עוד קצת, למרות ש 15 כולל שיפורים ותיקוני באגים והייתי מעדיף לקבל אותם לפרויקט. במקרה הגרוע אני גם יודע להכריח את npm להתקין את התלויות או לעשות fork לתלויות ולבנות גירסאות שלהן שמתאימות לריאקט 19. וכן אפשר גם לחכות עד שיצא ריאקט 19.

מה שצורם כאן הוא התפיסה בקהילה לגבי התפקיד של next והחוויה של מתכנתים רבים כאילו next זה איזושהי ברירת מחדל לפיתוח ריאקט. עם כל האהבה ל next כדאי לזכור שהמון אנשים עובדים בריאקט בצד לקוח ומושכים מידע מ REST API בצד שרת והכל בסדר. נקסט הוא לא הדרך היחידה או אפילו הטובה ביותר לבנות יישומי ריאקט. כן הוא מספק פיתרון לסוג מסוים של יישומים ואינטגרציה טובה בין צד לקוח וצד שרת, אבל יחד עם זאת כשצריך לבחור בין חידושים ליציבות בנקסט מעדיפים את החידושים, וזו לא בחירה שמתאימה לכל אחד.

ToCode

22 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-next-react-19

ToCode

21 Nov, 05:15


מציפיות לתוכנית עבודה
יש שתי בעיות עם ציפיות גבוהות מדי:

1. כשזה לא מצליח אנחנו נוטים להתאכזב ולהתייאש.

2. ככל שאנחנו פחות מבינים בתחום ככה הציפיות שלנו יהיו עוד יותר לא ריאליות.

כשהתחלתי ללמוד ספרדית פגשתי בחור שסיפר שהוא למד ספרדית במטוס לטיול הגדול בדרום אמריקה, וכשהוא נחת שם הוא דיבר ספרדית עם כולם והכל הלך לו בקלות. "ספרדית זה ממש קל" הוא אמר. נדבקתי במוטיבציה והחלטתי שגם אני יכול ללמוד ספרדית בשעתיים. אומנם לא היה לי טיול מתוכנן לדרום אמריקה אבל היה לי ספר בספרדית בשם "צלה של הרוח״ והחלטתי שאותו אני אקרא. בתור בחור שלמד חודשיים דרך דואולינגו הייתי בטוח שכל מה שצריך זה מילון וסבלנות. אבל כמובן שלא הערכתי נכון את מידת הסבלנות שבאמת היתה דרושה.

גם בלימודי תכנות יש אקוסיסטם שלם של ציפיות לא ריאליות - בקורסים, ביוטיוב, ברשתות חברתיות ומה לא. אנשים יספרו לכם איך בלי שום ידע קודם ובעזרת AI הם בונים אפליקציות ואתרים, ושתוך שעתיים אפשר ללמוד להיות מתכנתים, למצוא עבודה ואפילו משקיעים לסטארט-אפ. הם לא משקרים, בהחלט יכול להיות שלהם זה הצליח, כמו שבהחלט יכול להיות שהבחור שפגשתי למד ספרדית בכמה שעות טיסה. אבל זה לא אומר שזה יצליח עבורכם.

דרך יותר ריאלית להעריך את החלום ולהפוך אותו לתוכנית עבודה זה להסתכל על המסלולים שרוב האנשים עושים - דברו עם 10 או 20 אנשים שעשו את מה שאתם רוצים לעשות ותבררו מה הם עשו וכמה זמן זה לקח. לזה תוסיפו מרווחי ביטחון ומפה אפשר לבנות תוכנית עבודה.

ToCode

21 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-dreams-come-true

ToCode

20 Nov, 05:15


מה באמת הבעיה בדוגמת ריאקט הזו
אני אוהב לדמיין מה היה קורה אם. השבוע מתן בורנקראוט הציע תרגיל מחשבה כזה בפוסט כאן:

https://matanbobi.dev/posts/stop-passing-setter-functions-to-components

הקוד שהוא הדביק נראה ככה:

// Form.jsx 
function Form() {
const [formData, setFormData] = useState({ name: '' });
return (
<div>
<h1>Form</h1>
{/* Pass the setter function down to ChildComponent */}
<Input name={formData.name} setFormData={setFormData} />
<button onClick={() => console.log(formData)}>Submit</button>
</div>
);
};

// Input.jsx
function Input({ name, setFormData }) {
const handleInputChange = (event) => {
// Directly using the setFormData setter function from the parent
setFormData((prevData) => ({ ...prevData, name: event.target.value }));
};

return (
<div>
<label>
Name:
<input type="text" value={name} onChange={handleInputChange} />
</label>
</div>
);
};


הקומפוננטה Input מקבלת את setFormData מ Form ומפעילה אותה כשהטקסט משתנה. עכשיו לפני שנגיע ל"מה אם" אנחנו כבר רואים שלקומפוננטה Input יש בעיה - הפונקציה שהיא קיבלה מגיעה עם חתימה שלא מתאימה לה. קומפוננטת Input רוצה לדווח שהיה שינוי בשדה שלה, אבל צריכה לדווח את זה בצורה לא טבעית:

setFormData((prevData) => ({ ...prevData, name: event.target.value })); 


הטענה של מתן בפוסט היתה שארכיטקטורה זו היא בעייתית כי אם מחר יהיה שינוי באיך ש Form שומר את הסטייט שלו, אז החתימה של setFormData עשויה להשתנות ואז נצטרך לשנות גם את Input. המסקנה היא שלא כדאי להעביר setters שמתקבלים מ useState לקומפוננטות פנימיות.

אומנם אהבתי את הדוגמה אבל אני לא בטוח שאני מסכים עם המסקנה. נדמיין שהטופס היה בנוי כך:

// Form.jsx 
function Form() {
const [name, setName] = useState('');
const [favoriteColor, setFavoriteColor] = useState('#000000);

return (
<div>
<h1>Form</h1>
<Input label="Name" value={name} setter={setName} />
<Input label="Favorite Color" value={favoriteColor} setter={setFavoriteColor} />
</div>
);
};

// Input.jsx
function Input({ label, value, setter }) {
const handleInputChange = (event) => {
setter(event.target.value);
};

return (
<div>
<label>
{label}:
<input type="text" value={value} onChange={handleInputChange} />
</label>
</div>
);
};


במצב כזה עדיין העברתי את ה setters פנימה לקומפוננטת ה Input אבל אם יהיה שינוי באיך ש Form שומר את המידע לא תהיה בעיה לשנות את הקוד רק בקומפוננטת Form - למשל ליצור פונקציה חדשה שמקבלת את הערך החדש ולהעביר אותה פנימה ל Input. כלומר מה שהיה חשוב בדינמיקה בין שתי הקומפוננטות הוא הממשק ביניהן.

פה אפשר להוסיף התלבטות שניה והיא האם עדיף לשמור את כל אוביקט הטופס בתור State יחיד או לשבור אותו למספר משתנים פשוטים. בדוגמה אנחנו רואים ששימוש במספר משתנים פשוטים מעודד אותנו להעביר פחות מידע לקומפוננטות ולכן מבחינת ממשק אני ממליץ על שיטה זו.

ToCode

20 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-react-passing-setters-down

ToCode

19 Nov, 05:15


אנחנו בונים
"אנחנו" זאת הדרך הכי מהירה לקבל ביטחון, כח והרגשת השפעה. "אנחנו בונים דרך חדשה לשמור מידע בענן", "אנחנו בונים חומת אש חדשה שתשמור מכל פריצה", "אנחנו מלמדים קורסים מתקדמים בפיתוח תוכנה". אחרי "אנחנו" אפשר לכתוב משפט מפוצץ שגם יהיה נכון, כי המון אנשים עובדים עליו יחד.

"אנחנו" אומר שאני לא לבד, שיש עוד אנשים שמאמינים בדבר שאני עושה, עוד אנשים שמקדישים את הזמן שלהם כדי לקדם את אותו דבר. כשאנחנו מצליחים אני חלק ממשהו גדול, וכשהדבר שאנחנו בונים לא בדיוק מצליח או לא מצליח כמו שחשבתי שצריך זה בטח לא בגללי.

לעומתו "אני" הוא הרבה יותר קטן. "אני מלמד קורס תכנות מונחה עצמים בסביבת Python", "אני מתקן בעיות במאגרי גיט", "אני גורם לאתרים להיטען מהר יותר". סגנון הדיבור הזה לא מוגבל רק לפרילאנסרים, גם מי שעובד בחברה יכול לבחור להציג את העשייה שלו דרך "אני" או דרך "אנחנו", עם היתרונות והחסרונות של כל גישה.

במקום לבחור צד, אני ממליץ להתאמן על שני הסגנונות ולהחליף ביניהם. הסיפור הוא פחות הזהות שלכם ויותר הסיטואציה ומול מי אתם מדברים. כשיש ספק, עדיף להתחיל עם "אני".

ToCode

19 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-i-vs-we

ToCode

18 Nov, 05:16


כמה באמת עולה כרטיס לוטו?
קל לראות למה אנשים נמשכים לאפשרות של זכייה בלוטו - בין אם זה הגרלה אמיתית לכסף או לוטו במובן המטאפורי, ההזדמנות להשקיע מעט ולהרוויח הרבה. החלום של אולי יהיה לי מזל ואקבל קיצור, אולי מצאתי דיל מטורף שאף אחד לא מצא לפניי, אולי עליתי פה על משהו.

אנחנו אוהבים את זה כי הלוטו מסתדר רעיונות שיש לנו כבר בראש:

1. "אם לא תנסה איך תזכה"

2. "זה רק 20 ש"ח, חבל לא לנסות"

3. "ממילא אין לי משהו אחר לעשות עם הכסף"

וככה בן אדם מחפש עבודה ומתחיל לשלוח קורות חיים לחברות השמה ושולח לעוד ועוד מעסיקים פוטנציאליים כי "אם לא תנסה איך תזכה", ו"כמה כבר עולה לי לשלוח עוד מייל" ו"ממילא אני יושב בבית כל היום ויש לי המון זמן פנוי". אבל הזמן שעובר לא מביא ליותר ראיונות אלא דווקא להיפך.

או בן אדם חולם על הסטארט-אפ הבא ומתחיל לבנות מוצר שנראה לו מדליק, מתעסק בעיקר בצד הטכנולוגי בלי לחשוב על משתמשים פוטנציאליים ושיווק וכל החלקים הקשים של בניית עסק, כי "אם לא תנסה איך תזכה", "זה רק שעה ביום חבל לא לנסות" וגם "מה עדיף לבזבז את הזמן על נטפליקס?" ואחרי כמה חודשים הבן אדם עוזב ומתחיל פרויקט חדש או שהמוצר עולה לאוויר ומגלים שזה לא מעניין אף אחד.

המחיר האמיתי של כרטיס לוטו הוא לא עלות הכרטיס אלא צורת החשיבה של השתתפות בהגרלה. צורת חשיבה שמעודדת ניסיון על פני הצלחה וכישלון על פני מחויבות. וכן כרגע זה באמת נראה שאין אופציה אחרת, שהדבר היחיד שיש לעשות הוא להמשיך לעבוד על אותו פרויקט, לשלוח קורות חיים לעוד מעסיק או לקנות טופס הגרלה. וזאת בדיוק הסיבה שלא כדאי לעשות את זה.

תמיד יש עוד אופציות. אבל המחיר האמיתי של כרטיס לוטו הוא שהשתתפות בהגרלה מסתירה את האופציה האחרת, הקשה יותר: האופציה של לחסוך 20 ועוד 20 ועוד 20. האופציה של לכוון לתפקיד מדויק ולעשות הכל כדי להשיג אותו. האופציה של לבנות את הפרויקט שבאמת אנשים צריכים. כשקשה לראות את האופציה הבטוחה, הדבר הראשון לעשות הוא לצאת מההגרלה.

ToCode

18 Nov, 05:16


https://www.tocode.co.il/blog/2024-11-cost-of-a-lottery-ticket

ToCode

17 Nov, 05:15


סקאלת הישגיות - סקרנות
- הולך ל Chat GPT ומבקש פיתרון.

- הולך ל Chat GPT ומבקש 3 פיתרונות, עם יתרונות וחסרונות של כל אחד כדי שאוכל לבחור את הטוב ביותר.

- הולך ל Chat GPT ומבקש כיוון כללי לפיתרון. את השאר אמצא בגוגל ובתיעוד.

- מתחיל לכתוב לבד, מחפש בתיעוד כשנתקע. ל Chat GPT מגיע רק עם שאלות ספציפיות כשאני לא מבין מהתיעוד.

- כותב לבד POC, מחפש לשבור אותו ורק כשהכל עובד ואני מבין כל פסיק מוחק את ה POC וכותב את הקוד האמיתי.

- כותב לבד קוד אמיתי אחרי מחקר וכתיבת POC, ואז מוסיף בדיקות ותיעוד.

מדובר בסקאלה ואין דרך אחת נכונה. המשחק הוא לשלב בין האפשרויות לפי המשימה הרלוונטית כדי ליצור תמהיל בו אני גם מתקדם ובונה דברים וגם לומד איך הם עובדים.

ToCode

17 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-curiosity

ToCode

16 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-too-easy

ToCode

16 Nov, 05:15


קל מדי זה לא סימן טוב
אחד הדברים שלמדתי כשהתחלתי לעשות כושר - אם תרגיל קל מדי אתה כנראה לא עושה אותו נכון.

והדבר אפילו יותר נכון כשמדובר בקוד.

הקריטריונים שלנו למה נחשב טוב משתפרים עם הזמן. במיוחד בתחילת הדרך עדיין קשה לנו לראות שאנחנו מתרגלים את הדבר הלא נכון ומשתפרים בפרמטרים שהם לא כאלה חשובים. תחושת ההצלחה היא חשובה ולכן אם אני בתחילת הדרך נאבק שעה בתרגיל ובסוף מגיע לתוצאה זה ממש בסדר, גם אם הטכניקה לא היתה טובה (וגם אם חצי מהתוצאה כתב ChatGPT).

אבל אז אנחנו משתפרים ולומדים להשתמש ב Chat GPT טוב יותר או לחפש בגוגל טוב יותר או לגרום לדברים לעבוד יותר מהר, ופתאום אותו תרגיל או תרגילים דומים נהיו הרבה יותר קלים. הרבה אנשים יעצרו כאן ויגידו "אני יודע ריאקט" או "אני יודע פייתון" ומפה זה רק עוד מאותו דבר.

החוכמה היא לעצור כאן ולהתעכב רגע על אותו תרגיל קל מדי. לחפש לשבור אותו. לחפש שינויים קטנים שיגרמו לו להיות יותר קשה. החיפוש הזה לבדו הוא בעל ערך, והאוצר האמיתי הוא כשמוצאים את הטכניקה הנכונה. את השינוי באופן הפיתרון שגורם לתרגיל להיות מאתגר וללמד אותך משהו חדש על הפריימוורק. כמו בשיר של שלמה ארצי, יש להאט, להביט ולשים לב לפרטים כדי להתקדם לארץ חדשה.

ToCode

15 Nov, 05:15


if (value > maxValue.value) {        
maxIndex.value = counterIndex;
maxValue.value = value;
}
}
</script>

<template>
<Counter @change="(newValue) => onNewValue(0, newValue)" :isMax="maxIndex == 0" />
<Counter @change="(newValue) => onNewValue(1, newValue)" :isMax="maxIndex == 1" />
<Counter @change="(newValue) => onNewValue(2, newValue)" :isMax="maxIndex == 2" />
<Counter @change="(newValue) => onNewValue(3, newValue)" :isMax="maxIndex == 3" />
</template>


ואת קומפוננטת המונה באופן הבא:

<script setup lang="ts">
import { ref, computed } from 'vue'

const count = ref(0)
const emit = defineEmits(['change']);
const {isMax} = defineProps<{isMax?: boolean}>();
const background = computed(() => isMax ? "green" : "yellow");

function onClick() {
count.value++;
emit('change', count.value);
}
</script>

<template>
<div>
<p>Value = {{ count }}</p>
<button @click="onClick">+1</button>
</div>
</template>

<style scoped>
div {
background-color: v-bind(background);
padding: 20px;
}

button {
color: white;
}

p {
color: black;
}
</style>


יתרונות וחסרונות בשתי הגישות
נשים לב להבדלים וליתרונות והחסרונות בין שתי הגישות:

1. הגישה הראשונה היתה יותר פשוטה מנקודת מבט של כל קומפוננטת מונה וגם מנקודת המבט של הקומפוננטה העוטפת. כל המידע של כל המונים נשמר במערך אחד וכל פעם שהוא משתנה בודקים מי הערך המקסימלי ומעדכנים את כל המונים.

2. הגישה השנייה טובה יותר מבחינת ביצועים. כל פעם שמתעדכן מונה צריך לבצע רק השוואה אחת, במקום לרוץ על כל מערך המונים ולחפש את הערך המקסימלי.

אבל ההבדל הכי גדול בין שתי הגישות הוא התלות בין הקומפוננטה למקום שלה. בגישה הראשונה אנחנו לא יכולים לקחת קומפוננטת מונה ולהוציא אותה למקום אחר בפרויקט. היא תמיד צריכה להיות בתוך קומפוננטה שמעבירה לה את מספר הלחיצות, והקומפוננטה העוטפת חייבת לטפל באירוע לחיצה ולעדכן את הערך. הקומפוננטה שיצרנו היא "חלק מ" מבנה גדול יותר, ואינה יכולה לתפקד בצורה עצמאית.

בגישה השנייה כל קומפוננטת מונה היא עצמאית. אפשר להעביר אותה למקום אחר בפרויקט והיא תמשיך לעבוד בתור מונה לחיצות. בנוסף לתפקיד הזה שלה, היא גם מדווחת על שינויים לקומפוננטה שעוטפת אותה כדי שאפשר יהיה לשנות לה את הצבע. הגישה השנייה היא לכן יותר מודולרית, היא מאפשרת לנו יותר חופש פעולה לשינויים עתידיים ותאפשר בעתיד יותר בקלות לפרק ולבנות מחדש את היחסים בין הקומפוננטות.

כשאתם בונים קומפוננטות Vue נסו לחפש התנהגות בסיסית הגיונית ועצמאית לכל קומפוננטה, ולבנות את הקשר בין הקומפוננטות בתור שכבה נוספת שלא פוגעת בעצמאות כל אחת מהקומפוננטות הבודדות, כדי שיהיה לכם קל לשנות את הקוד בעתיד.

עכשיו אתם
1. עדכנו את שני הפיתרונות כך שישתמשו ב v-for במקום לשכפל את יצירת המונה 4 פעמים.

2. שימו לב ששני הפיתרונות לא מתנהגים אותו דבר. כשיש מספר מונים עם אותו ערך מקסימלי בפיתרון השני הירוק לא משתנה אבל בפיתרון הראשון הצבע הירוק יינתן למונה העליון ביותר שמציג את הערך המקסימלי. עדכנו את הקוד של שני הפיתרונות כך שאם למספר מונים יש את אותו ערך מקסימלי אז אף מונה לא ייצבע בירוק.

ToCode

15 Nov, 05:15


דוגמת Vue: מספר מונים וצביעת הגדול ביותר
בואו נכתוב תוכנית המציגה 4 מונים וצובעת את מונה הלחיצות שמראה את המספר הגדול ביותר בצבע שונה - וכן ננסה את זה ב Vue.

פיתרון 1 - שמירת כל המידע בקומפוננטה העליונה
לכתוב מונה לחיצות ב Vue זה קל ובעצם אנחנו מקבלים כזה בחינם כשיוצרים פרויקט חדש. אבל אם נשים 4 קומפוננטות כאלה על המסך לא תהיה לנו דרך לתקשר בין 4 הקומפוננטות. כל אחת תספור לחיצות בצורה עצמאית ולא תדע מה מספר הלחיצות בקומפוננטות האחרות, ולכן לא ברור איך היא תדע להשתמש בצבע רקע שונה אם מספר הלחיצות אצלה הוא הגדול ביותר.

דרך אחת לחבר בין ארבעת הקומפוננטות היא ליצור קומפוננטה עוטפת ולהעביר את משתני ה ref של המונים לקומפוננטה העוטפת, בתור מערך. כל פעם שמשתמש לוחץ על כפתור באחת מקומפוננטות המונים אנחנו נעביר אירוע "נלחץ" לקומפוננטה העוטפת והיא כבר תעדכן את המספרים. הקומפוננטה העוטפת תוכל למצוא מה הערך הגדול ביותר ומי המונה עם הערך הגדול ביותר ולשמור את הערכים במשתני computed, וגם להעביר אותם פנימה לקומפוננטות המונים הפנימיות וכך קומפוננטת המונה עם הערך הגדול ביותר תוכל לצבוע את עצמה בירוק.

הקוד לקומפוננטה העוטפת הוא בסך הכל:

<script setup lang="ts">
import {ref, computed} from 'vue';
import Counter from './Counter.vue';

const counts = ref([0, 0, 0, 0]);
const maxValue = computed(() => Math.max(...counts.value));
const maxIndex = computed(() => counts.value.findIndex(el => el === maxValue.value));

function onClick(counterIndex) {
counts.value[counterIndex]++;
}
</script>

<template>
<p>max value = {{ maxValue }}</p>
<p>max index = {{ maxIndex }}</p>
<Counter @click="onClick(0)" :isMax="maxIndex == 0" :count="counts[0]"/>
<Counter @click="onClick(1)" :isMax="maxIndex == 1" :count="counts[1]"/>
<Counter @click="onClick(2)" :isMax="maxIndex == 2" :count="counts[2]"/>
<Counter @click="onClick(3)" :isMax="maxIndex == 3" :count="counts[3]"/>
</template>


ובגלל שכל המידע מנוהל בקומפוננטה זו קומפוננטות המונים יכולות להתמקד בהצגת המונה וב CSS לקביעת הצבע, והקוד של קומפוננטת המונה נראה כך:

<script setup lang="ts">
import { ref, computed } from 'vue'

const emit = defineEmits(['click']);
const {isMax, count} = defineProps<{isMax?: boolean, count: number}>();
const background = computed(() => isMax ? "green" : "yellow");

function onClick() {
emit('click');
}
</script>

<template>
<div>
<p>Value = {{ count }}</p>
<button @click="onClick">+1</button>
</div>
</template>

<style scoped>
div {
background-color: v-bind(background);
padding: 20px;
}

button {
color: white;
}

p {
color: black;
}
</style>


פיתרון 2 - שמירת המונים בכל קומפוננטה וערך המקסימום בקומפוננטה העליונה
דרך אחרת לגשת לאותו אתגר היא לשים לב שהקומפוננטה העליונה בעצם לא עושה כלום עם ערכי המונים שהיא שומרת. כלומר כן היא צריכה את הערכים כדי להעביר אותם לקומפוננטות המונים, אבל המידע היחיד שמשותף לכל המונים זה הערך המקסימלי והאינדקס של הערך המקסימלי. לכן אפשר לארגן אחרת את הקוד ולהגדיר שכל מונה ידווח על אירוע כל פעם שהערך שלו מתעדכן. הקומפוננטה העוטפת תתפוס את האירוע ותבדוק - אם הערך החדש גדול יותר מהערך המקסימלי הנוכחי היא תשמור את הערך הזה בתור הערך המקסימלי החדש ותעדכן את המונה המקסימלי להיות המונה שזרק את האירוע. בכל מצב אחר אפשר להתעלם מהאירוע ולהמשיך הלאה.

בשביל הפיתרון אצטרך לספר לכם משהו נוסף לגבי אירועים - אירועים ב vue יכולים להישלח יחד עם פרמטר, והפרמטר מגיע לקוד הטיפול באירוע. מבחינת קוד זה די פשוט, הפונקציה emit יכולה לקבל פרמטר נוסף אחרי שם האירוע:

emit('change', 10);


ואפשר להעביר כמה פרמטרים שרוצים לפונקציה. בקוד הטיפול באירוע אנחנו מקבלים את הפרמטרים האלה בתור פרמטרים לפונקציה שמטפלת באירוע, או בכתיב החץ בתוך התבנית כלומר:

<Counter @change="(newValue) => doSomethingWith(newValue)" />


עכשיו שאנחנו יודעים את זה נוכל לכתוב את הקומפוננטה העוטפת ולקבל:

<script setup lang="ts">
import {ref} from 'vue';
import Counter from './Counter.vue';

const maxValue = ref<null|number>(null);
const maxIndex = ref<null|number>(null);

function onNewValue(counterIndex, value) {

ToCode

15 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-vue-example-counters

ToCode

14 Nov, 05:15


ארבעה דברים שממש אהבתי ב Nuxt
נוקסט היא גירסת ה Full Stack של Vue המאפשרת פיתוח יישומים מלאים עם Server Side Rendering. אני חייב להודות שכשהתחלתי לשחק עם הפריימוורק הייתי בטוח שאני הולך לפגוש עוד next.js ושמחתי למצוא מספר פיצ'רים ייחודיים שמיד עשו חשק ללמוד יותר. אלה הדברים שכבר ממבט ראשון על nuxt אהבתי במיוחד:

1. יבוא אוטומטי - כן אני יודע VS Code משלים פקודות import בלי מאמץ ובכל זאת אין קוד תמיד עדיף על קוד שנכתב מהר. קטע כזה עובד ב nuxt בלי שנצטרך להוסיף שום פקודת import:

<script setup lang="ts">
/* ref() and computed() are auto-imported */
const count = ref(1)
const double = computed(() => count.value * 2)
</script>


2. מערכת מודולים מובנית - לקח לי שתי לחיצות להוסיף את Tailwind לאפליקציית ה nuxt שבניתי דרך מנגנון המודולים שלהם. יש מודולים לחיבור לבסיסי נתונים, לחיבור לפורטלי תשלום, לניהול משתמשים, מוניטורינג של האפליקציה ועוד.

3. כלי הפיתוח המובנית שמציגים את זמן טעינת העמוד בכל עמוד במצב פיתוח. הרבה מאוד פעמים בכתיבת יישומים במיוחד בפריימוורקס שאנחנו לא הכי מכירים אנחנו עושים שטויות שיכבידו על זמני הטעינה של העמוד, וזה משהו שאנחנו עלולים לגלות רק מאוד מאוחר בזמן הפיתוח. הצגה של המידע הזה מול העיניים כבר מהרגע הראשון אומרת משהו על סדרי העדיפויות של המפתחים שבנו את התשתית וזה מעודד.

4. התיעוד של nuxt מאוד מושקע. הוא גם כולל מדריכים איך לעשות המון דברים בפריימוורק וגם מסביר על מבנה פרויקט, איזה תיקיות יש ומה צריך לשים בכל תיקייה. בהחלט אחד מעמודי התיעוד הטובים שראיתי.

כן צריך להגיד ש nuxt הוא לא פריימוורק פשוט. יש הרבה יותר פונקציות, חיבורים ויכולות בהשוואה ל next.js. גם תבנית הפרויקט החדש היתה קצת דלה בדרך כלל תבניות כאלה כוללות הדגמה של יותר יכולות של הפריימוורק. אני בכל מקרה בינתיים לקוח מרוצה ובטח אבנה איתו כמה דוגמאות ואפרסם פה בעתיד.

ToCode

14 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-nuxt

ToCode

13 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-python-project-suggestions

ToCode

12 Nov, 06:26


זהירות פלסטר
המתכון הוא פשוט-

1. כותבים קוד תשתית.

2. צריכים פיצ'ר או תיקון באג והקוד לא מסתדר עם התשתית.

3. מתקנים את התשתית.

4. בונים את הפיצ'ר עם התשתית החדשה.

מה שקורה בפועל הוא ששלב 3 הרבה יותר קשה מכל האחרים, לכן אנחנו מדביקים במקומו "פלסטר" שהוא תיקון זמני שעוקף את התשתית הקיימת. אנחנו לא חושבים על הפלסטר הזה בתור תשתית חדשה כי הוא פותר רק בעיה קטנה וספציפית בפיצ'ר שפשוט לא הצלחתי לבנות עם התשתית הקיימת. המחשבה היא "אף אחד אף פעם לא יצטרך שוב מעקף כזה, ולכן אין טעם לכתוב את הקוד גנרי מדי. ובכלל אולי אני פשוט לא מבין את התשתית הגאונית של האפליקציה וכן יש דרך שפשוט לא ראיתי בתוך התשתית לבנות את הפיצ'ר שרציתי, אז אני אכתוב את הפלסטר בינתיים וכשאהיה מספיק חכם או יהיה לי מספיק זמן אני כבר אעדכן את התשתית או אמצא את הדרך הנכונה לכתוב את זה".

את ההמשך של הסיפור אתם מכירים - הפלסטר הופך לפיתרון הקבוע, מועתק לעוד מקומות באפליקציה והופך לתשתית החדשה שאף אחד לא חשב עליה מראש.

אם אתם צריכים פיצ'ר שהתשתית לא מאפשרת הבעיה היא התשתית לא הפיצ'ר שלכם. קחו את הזמן לתקן את התשתית, עדיף לפני בניית הפלסטר, ובטוח לפני שהפלסטר משכפל את עצמו לשאר הקוד.

ToCode

12 Nov, 06:26


https://www.tocode.co.il/blog/2024-11-just-a-patch-fixed

ToCode

12 Nov, 05:14


https://www.tocode.co.il/blog/2024-11-just-a-patch

ToCode

11 Nov, 05:16


לא שוב טייפסקריפט
בואו נכתוב קומפוננטת Vue שיכולה להיות מופעלת בשני אופנים - או שאפשר להעביר לה כ Property מספר או שמעבירים לה שתי מחרוזות. אם מעבירים מספר אז המחרוזת הראשונה תהיה המספר שעבר והמחרוזת השנייה תהיה טקסט קבוע. ניסיון ראשון עשוי להיראות כך:

<script setup lang="ts">
const props = defineProps<
| {number: number}
| {text1: string, text2: string}>();

const text1 = "number" in props ? String(props.number) : props.text1;
const text2 = "number" in props ? "Great Number!" : props.text2;
</script>

<template>
<p>{{ text1 }}</p>
<p>{{ text2 }}</p>
</template>


והקריאה לקומפוננטה עשויה להיראות כך:

<Demo :number="5" />
<Demo text1="hello" text2="world" />


זה עבד מבחינת טייפסקריפט אבל הקוד עצמו נכשל. בהגדרת props עם defineProps ב Vue, אוביקט הפרופס תמיד מכיל את כל הפרופס האפשריים, כלומר בשתי ההפעלות הוא יקבל גם את number, גם את text1 וגם את text2, פשוט בהפעלה הראשונה number מקבל את הערך 5 והטקסטים יהיו undefined ובהפעלה השניה זה number שיהיה undefined והטקסטים מקבלים את הערכים הנכונים שלהם.

רעיון אחד לצאת מהסיפור עשוי להיות לעדכן את הקוד שמושך את הטקסטים מ props ולתקן את הבדיקה. הבעיה שזה לא ממש עובד:

const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;


טייפסקריפט לא מאפשר לגשת ל props.number כי מבחינתו אולי אוביקט ה props לא כולל את number. מה שכן יעבוד הוא לעדכן את defineProps כך שכל אפשרות תכיל התיחסות לדברים באפשרות השניה אבל עם טיפוס never כדי שלא יעבירו ערכים למפתחות אלה:

const props = defineProps<
| {number: number, text1?: never, text2?: never}
| {number?: never, text1: string, text2: string}>();

const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;


הקוד הזה עובד ובנוסף יש לנו בדיקת טייפסקריפט טובה. שתי השורות האלה תקינות:

<Demo :number="5" />
<Demo text1="hello" text2="world" />


ושלושת אלה מראות פס אדום על הקומפוננטה כדי שנזהה הפעלה לא נכונה:

<Demo :number="5" text1="hello" text2="world"  />
<Demo text1="hello" />
<Demo />

ToCode

11 Nov, 05:16


https://www.tocode.co.il/blog/2024-11-typescript-vue-props

ToCode

10 Nov, 05:13


הבעיה עם עצות גרועות
הבעיה עם עצות גרועות היא שהן גרועות. טוב תכלס זאת הבעיה היותר קלה איתן.

עצות גרועות באמת הן כאלה שקשה לזהות שהן גרועות. וזאת בעיה הרבה יותר גדולה, כי רוב הזמן (בבעיות מעניינות) גם לעצות טובות לוקח זמן לעבוד. עצות גרועות נראות כמו עצות טובות, אבל עד שאתה מבין שעבדו עליך זה כבר הרבה יותר קשה לתקן. הנה כמה מאפיינים של עצות גרועות שמתחפשות לטובות:

1. עצה גרועה תשתמש בז'ארגון לא מובן ותנצל פערי ידע בין היועץ למקבל העצה.

2. עצה גרועה הרבה פעמים כן תפתור בעיה, פשוט לא את הבעיה שלך (זה מה שגורם לה להיות מבלבלת).

3. עצה גרועה תתמקד בתוצאה ולא בתהליך, ולא תאפשר לך להבין בזמן שאתה בדרך הלא נכונה.

בואו נחבר את זה רגע לדוגמה. נניח שיש לי בעיה של איטיות ב DB ואני לא מבין למה שליפות מסוימות חוזרות לאט. אני מצלצל ליועץ מומחה לבסיסי נתונים והוא מתחיל להסביר לי על אינדקסים וכמה הם חשובים, וגם מוסיף המון מידע טכני על סוגי האינדקסים ובסוף משכנע אותי לארגן מחדש את הטבלאות והאינדקסים כי רק ככה בעיית הביצועים תיפטר. הוא יוכל גם להפנות אותי ללקוחות אחרים שלו שעשו תהליך דומה ובסוף קיבלו שאילתות מהירות יותר. הפיתרון באמת פותר בעיה אבל לא בטוח שאת הבעיה שלי.

יועץ טוב יותר ייכנס איתי לקוד שלי ויראה לי איך לנתח מה בסיס נתונים עושה כשמריצים שליפה, איזה אינדקסים נמצאים בשימוש ואיזה לא, ומתוך זה יראה לי ממש מה גורם לאיטיות. הדגש פה הוא לתת לי את הכלים להבין את הבעיה ולהתמודד איתה, ולעזור לי כשאני נתקע, במקום לתת לי פיתרון שעבד במקומות אחרים אבל לא בטוח שיעבוד עבורי (ואפילו אם יעבוד לא בטוח אם ימשיך לעבוד עבורי בפעם הבאה שאתקל בבעיה).

הבעיה עם עצות גרועות היא שעצות גרועות הן פשוט עצות טובות שיצאו מקונטקסט.

ToCode

10 Nov, 05:13


https://www.tocode.co.il/blog/2024-11-bad-advice

ToCode

09 Nov, 05:13


באמת צריך לדעת לענות על השאלה הזאת?
יש המון בעיות שאפשר לפתור גם בלי להבין כל שורת קוד בפרויקט.
ואפילו לא מעט בעיות שאפשר לפתור בלי להבין את שורות הקוד הרלוונטיות לבעיה.

ויש מספיק שאלות שיהיה מאוד מעניין ולחלוטין לא מועיל לגלות את התשובה להן. כמה עמוק שווה לצלול בקוד של ספריה חיצונית רק בשביל למצוא באג בקוד שלה, כשאפשר במקום לשדרג גירסה של הספריה החיצונית ולהתקדם עם הפרויקט? לא חבל על הזמן שלנו?

איך נדע כמה עמוק צריך לחפור בתקלה מסוימת כדי להבין אותה עד הסוף? זה תלוי בפרויקט אבל ככלל זה סוג השאלות שלא הייתי מוותר על למצוא להן תשובה במערכות Full Stack:

1. מה מחובר למה? מאיפה מגיע המידע ולאן הוא הולך? איפה המידע נשמר?

2. למה אוביקט מסוים נשלח ברשת? למה תוכן מסוים חוזר? איזה חלק בתוכנה אחראי לכל חלק ב HTML?

3. מה קורה כשאני לוחץ על כפתור או מבצע אירוע אחר? איזה חלקים בקוד לוקחים חלק בטיפול באירוע.

4. מה הספריות החיצוניות בהן אני משתמש? מה תפקיד כל ספריה? באיזה גירסה כל ספריה? מתי היא שודרגה לאחרונה? מה הגירסה העדכנית ביותר שלה? מי מתחזק את הספריה החיצונית הזאת?

5. איזה שרתים יש לי במערכת? מה קורה אם אחד מהם מפסיק לעבוד? מה התוכנות שרצות על כל שרת? מה תפקיד כל תוכנה?

עכשיו אתם- מתחזקים מערכת Full Stack? על איזה שאלות לא תוותרו לעצמכם ותתאמצו למצוא את התשובות?

ToCode

09 Nov, 05:13


https://www.tocode.co.il/blog/2024-11-must-solve-questions

ToCode

08 Nov, 05:15


עשר שנים של פרונטאנד
אפאל שה כתב על הפרויקטים המובילים ב JavaScript לפני 10 שנים ואיפה הם היום. פוסט מרתק ומלא נוסטלגיה ואני ממליץ לקרוא אותו. בקצרה הוא מחלק את הפרויקטים לנושאים הבאים:

1. פרונטאנד

2. בקאנד

3. באנדלרים

4. מריצי משימות

5. כלי בדיקות

אני לא יודע למה הוא בחר בחלוקה הזאת, אבל כן רוצה להוסיף על הניתוח המעולה שלו כמה מילים על מה השתנה בכל נושא.

פרונטאנד
לפני 10 שנים SPA היו הטרנד הגדול. מאז למדנו שהם לא כאלה טובים כי לא לכולם יש JavaScript ומנועי חיפוש לא הכי מרוצים מהם ובכלל למדנו לאהוב את הדפדפן. התוצאה של השיעור הזה היתה סיבוב חוזר של המטוטלת לכיוון של פריימוורקים של Full Stack. היום מאוד נפוץ למצוא את צוות הפרונטאנד מתחזק יישום next.js.

מעניין לשים לב שלמרות שהפריימוורקים האלה רצים בצד שרת הם לא מחליפים את קוד צד השרת הרגיל. בהרבה חברות אני רואה תהליך של מעבר מיישום SPA שכתוב ב JavaScript בלבד ומתחבר ל API, ליישום Next.js או רמיקס שמושך מידע מ API מבצע רינדור בצד שרת ומחזיר קוד ריאקט ללקוח. צוות בקאנד ממשיך לתחזק את מכונת "צד השרת" וצוות הפרונט אנד הפך בעצם לצוות Full Stack ובונה קוד ב Next.js.

לאן זה הולך? מבחינת פריימוורק אין ספק שריאקט היא השורדת החזקה והוותיקה ביותר. מצד אחד קשה לראות את הסוף של ריאקט באקוסיסטם הנוכחי ומצד שני דברים כאלה מגיעים במכה אחת. ריאקט היא היום מסובכת מדי ורק הופכת יותר מסובכת עם כל גירסה חדשה, ודברים כאלה לא מחזיקים לאורך זמן.

בקאנד
למרות הרבה ניסיונות לחדש בתחום הזה בסוף אנשים חוזרים לאקספרס כי אנחנו מעדיפים את המוכר במיוחד כשהוא לא מספיק גרוע. גרף קיו אל לא הצליח לאיים על המודל של REST וגם לא דברים כמו tRPC. בסוף יש משהו נוח ויפה בשרת שפשוט מחזיר JSON-ים.

לאן זה הולך? קשה לדעת. אולי נתחיל לכתוב שרתים ב Rust, אולי Go יתחזק שוב, ואולי נישאר עם אקספרס עוד 10 שנים.

באנדלרים
וואו כמה השתנה פה וכמה עוד הולך להשתנות. לפני עשר שנים RequireJS עוד היה דבר, וובפאק היה חדש ומהפכני אבל יותר מהכלים הסיפור הגדול של הבאנדלרים זה השינויים בפלטפורמה. לפני 10 שנים היינו צריכים להמציא מנגנונים שיאפשרו לכתוב JavaScript בקבצים שונים ואז לחבר את כל הקבצים האלה לקובץ JS אחד לשלוח לדפדפן כי דפדפנים לא יכלו להוריד הרבה קבצים במקביל מאותו דומיין. מאז ES Modules הפך לסטנדרט ודפדפנים (וגם node.js) יודעים לקרוא פקודות import ו export באופן טבעי, ה JavaScript נהיה הרבה יותר גדול ופתאום לחבר הכל לקובץ אחד זה רעיון טפשי כי הקובץ גדול מדי ומאט את זמן הטעינה של העמוד.

היום המטרה העיקרית של באנדלרים היא לתמוך ב FrontEnd Frameworks שאנחנו בוחרים, לדוגמה לקמפל את ה JSX של ריאקט או את קבצי ה Vue או ה TypeScript לקוד JavaScript שדפדפן יוכל להבין. קשה לראות אותם נעלמים אבל ככל שהם צריכים לעשות פחות הם הופכים להרבה פחות מעניינים.

לאן זה הולך? בעתיד הנראה לעין יהיו פרויקטים שיחליפו את הבאנלדר ב Import Maps. כן לאט בהתחלה ואז יותר מהם. לדעתי גם נתחיל לראות Front End Frameworks שתומכות ב Import Maps ולא דורשות Build Step ויהיה מי שיתעניין בזה (DHH כבר מדבר על גישה כזאת בריילס, אני חושב שיהיו עוד). מצד שני זה לא מה שיחסל את ריאקט וחבריו ולכן באנדלרים ימשיכו ללוות אותנו לפחות בתור עוד רכיב בתוך ה Full Stack Framework שנבחר.

מריצי משימות
כן פעם היו דברים בשם grunt ו gulp וכן השתמשנו בהם כדי להריץ את הבאנדלר. ואז הבאנדלר נהיה מספיק טוב ועברנו להשתמש ב npm כך שלא היה בהם יותר צורך. יש אנשים שחוזרים ל Makefile-ים, יש את ג'אסט שגם כתבתי עליו ואני חושב שהוא אחלה אבל יותר מהכל יש npm scripts ו Github Actions.

לאן זה הולך? קשה לי לראות את התחום של מריצי משימות מתרומם שוב. זה היה רעיון יפה לתקופה אבל כבר אין בו צורך.

כלי בדיקות
לפני 10 שנים מוקה וג'סמין היו בחירות פופולריות. היום שתיהן עדיין עובדות אבל רבים מעדיפים את Jest, אבל האמת העצובה היא שלפני 10 שנים לא כתבנו המון בדיקות והיום אנחנו עדיין לא כותבים המון בדיקות, כך שפחות משנה הכלי.

לאן זה הולך? אולי AI יתחיל לכתוב בדיקות בשבילנו. כנראה שלא.

ToCode

08 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-js-nostalgie

ToCode

07 Nov, 05:14


ומגלה שכל לחיצה על כל משבצת גורמת לקומפוננטה להתעדכן. בגלל שהקומפוננטה גדולה עדכון שלה אומר להריץ מחדש את שתי הלולאות.

בנו קומפוננטה בשם Square שאחראית על הצגת תוכן משבצת בודדת, וקומפוננטה נוספת בשם Player שאחראית על הצגת השחקן הנוכחי - וגם בשתיהן הוסיפו את קטע הקוד שמקשיב ל updated ומדפיס כשיש עדכון לקומפוננטה.

עכשיו מצאו דרך לעדכן את הקוד כך שלחיצה על משבצת תגרום לעדכון רק של הקומפוננטות Square ו Player אבל לא לעדכון של הקומפוננטה הראשית TicTacToeGame.

ToCode

07 Nov, 05:14


דוגמת ויו: משחק איקס עיגול
לפני כמה שנים כתבתי מדריך על Vue ואחד הדברים שאז לא עבדו מספיק טוב היה מנגנון הריאקטיביות. רוב הזמן זה היה בסדר אבל היה יחסית קל להגיע לקצוות מבלבלים. לאחרונה אני מרענן את החומרים על Vue לקראת קורס שאני מעביר בנושא ושמח לגלות שהריאקטיביות עובדת הרבה יותר טוב היום. הנה דוגמה קצרה לפיתוח משחק איקס עיגול ריאקטיבי ב Vue, עם כל הפינוקים של הפרדה בין קובץ לוגיקה לקומפוננטות.

לוגיקת המשחק
הדרך הסטנדרטית ב Vue לתקשר בין לוגיקה לקומפוננטות היא ריאקטיביות. הקסם של Vue (בניגוד לריאקט), הוא שאפשר ליצור משתנים ריאקטיביים בכל מקום ולא רק בתוך קומפוננטות וכך אפשר לבנות לוגיקה שכוללת State מחוץ לקומפוננטות. בדוגמה שלנו זה הקוד של משחק איקס עיגול שמחזיק סטייט ריאקטיבי:

import {ref, Ref} from 'vue';

export default class ReactiveTicTacToe {
board: Ref<Array<Array<'O'|'X'|'.'>>>;
winner: Ref<undefined|'O'|'X'>;
currentPlayer: Ref<'O'|'X'>;

constructor() {
this.board = ref([
['.', '.', '.'],
['.', '.', '.'],
['.', '.', '.']
]);

this.currentPlayer = ref('X');
this.winner = ref();
}

play(row: number, column: number) {
if ((this.winner.value) || (this.board.value[row][column] !== '.')) {
return;
}

this.board.value[row][column] = this.currentPlayer.value;
if (this.checkWinner(this.currentPlayer.value)) {
this.winner.value = this.currentPlayer.value;
}
this.currentPlayer.value = this.nextPlayer();
}

nextPlayer() {
if (this.currentPlayer.value === 'X') {
return 'O'
} else {
return 'X'
}
}

checkWinner(player: 'X' | 'O') {
const winningCoordinates = [
[[0, 0], [0, 1], [0, 2]],
[[1, 0], [1, 1], [1, 2]],
[[2, 0], [2, 1], [2, 2]],
[[0, 0], [1, 0], [2, 0]],
[[0, 1], [1, 1], [2, 1]],
[[0, 2], [1, 2], [2, 2]],
[[0, 0], [1, 1], [2, 2]],
[[0, 2], [1, 1], [2, 0]]
]
if (winningCoordinates.some(triplet =>
triplet.every(([row, column]) => (
this.board.value[row][column] === player
))
)) {
return true;
}
return false;
}
}


זה קוד שקל לעבוד איתו, קל לבדוק אותו בבדיקות יחידה או לשלב אותו יחד עם קבצי לוגיקה אחרים. אפשר להגיד שקוד כזה היה כל מה שתמיד רציתי מ mobx, אבל דברים שם אף פעם לא עבדו מספיק טוב.

הקומפוננטה
ויו מעודד כתיבת לוגיקה פשוטה של קומפוננטה בתוך סקריפט האיתחול שלה, אבל כמובן שדברים יותר מורכבים עדיף לכתוב בקובץ נפרד. אחרי שכתבנו את קובץ הלוגיקה נוכל להשתמש בו מתוך קומפוננטה באופן הבא:

<script setup lang="ts">
import ReactiveTicTacToe from '../game/logic';

const {game} = defineProps<{game: ReactiveTicTacToe}>()

</script>

<template>
<div>
<h1 v-if="game.winner.value">Bravo! {{ game.winner }} won</h1>
<h1 v-else>Next player = {{ game.currentPlayer }}</h1>
<div v-for="(_, row) in 3" :style="{display: 'flex'}">
<div v-for="(_, column) in 3" :style="{flex: 1, cursor: 'pointer'}">
<div
@click="game.play(row, column)"
>
{{ game.board.value[row][column] }}
</div>
</div>
</div>
</div>
</template>


ובטח שכיף להשתמש בקיצורי הדרך של vue במיוחד באפשרות של לולאה על מספר בתוך ה v-for ובאפשרות לרשום ב @click משהו שנראה כמו הפעלה של פונקציה ו vue כבר יהפוך את זה לפונקציה.

לסיום בשביל להשתמש במשחק נוכל להפעיל אותו באופן הבא:

<script setup lang="ts">
import TicTacToeGame from './components/TicTacToeGame.vue';
import ReactiveTicTacToe from './game/logic';
</script>

<template>
<TicTacToeGame :game="new ReactiveTicTacToe()"/>
<TicTacToeGame :game="new ReactiveTicTacToe()"/>
<TicTacToeGame :game="new ReactiveTicTacToe()"/>
</template>


עכשיו אתם
ולסיום תרגיל לאלה מכם שרוצים לצלול קצת יותר לעומק הריאקטיביות ב Vue. אני מוסיף את הקוד הבא ל script setup של הקומפוננטה TicTacToeGame.vue:

  onUpdated(() => {
console.count(\TicTacToeGame::updated\);
})

ToCode

07 Nov, 05:14


https://www.tocode.co.il/blog/2024-11-vue-tictactoe-reactive-demo

ToCode

06 Nov, 05:15


איזה סוג של מכוער
כשאנחנו אומרים שמצאנו פיתרון מכוער לבעיה אנחנו מתכוונים שאנחנו לא מאוד מרוצים מהפיתרון, אבל עוד לא אמרנו מה בדיוק אנחנו לא אוהבים. כל אחת מהאפשרויות האלה יכולות לקבל את התווית "פיתרון מכוער":

1. מצאתי באינטרנט דרך שפותרת את הבעיה והעתקתי בלי להבין.

2. כתבתי קוד שפותר את הבעיה בכמה מקומות שונים, אבל לא הצלחתי למצוא דרך לאחד אותם למקום אחד.

3. היו לי כמה רעיונות איך לפתור את הבעיה ובחרתי בפיתרון שפוגע בביצועים של המערכת כי היה יותר קל לממש אותו.

4. הפיתרון שלי מטפל רק ב 90% מהמקרים.

5. פתרתי את הבעיה בצורה שיש סיכוי טוב שתישבר כשיהיו עדכונים לפלטפורמה או לספריות אחרות.

6. פתרתי את הבעיה בצורה שתפריע לכל הוספת פיצ'ר חדש למערכת.

7. פתרתי את הבעיה בצורה שמוסיפה אילוץ חדש למערכת שלא היה שם קודם.

כשאנחנו ברורים לגבי הבעיות בפיתרונות שלנו הרבה יותר קל לנו להתקדם לפיתרונות טובים יותר.

ToCode

06 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-kind-of-ugly

ToCode

05 Nov, 05:15


טיפ נקסט: שימו לב ל import-ים
ב next.js בגירסה ה Page Router יש לנו שלוש אפשרויות לטעון ספריות חיצוניות, ואני חושש שההבדל ביניהן לא תמיד מספיק ברור.

שימוש בספריה חיצונית בקובץ נפרד
דרך ראשונה היא לקרוא ל import מתוך קובץ של Page, לדוגמה בפרויקט next חדש אני כותב בקובץ index.tsx את הקוד הבא:

import _ from 'lodash';
import moment from 'moment';

// export const getServerSideProps = (async () => {
// const _ = (await import('lodash')).default;
// return { props: { name: foo(), number: _.random(100) } }
// });


export default function Home(props: any) {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div
className={\${styles.page} ${geistSans.variable} ${geistMono.variable}\}
>
<main className={styles.main}>
<p>Hello {_.head([1, 2, 3])}</p>
<p>{moment("20111031", "YYYYMMDD").fromNow()}</p>
</main>
</div>
</>
);
}


בבנייה של הקוד עם npm run build אני מקבל בתיקיית chunks את:

$ ls -1 .next/static/chunks
29107295-e679c2f4ae05922d.js
75fc9c18-25d3a9414ec027b7.js
framework-88d0cc4abe8a5763.js
main-22f485a16e76a198.js
pages
polyfills-42372ed130431b0a.js
webpack-69db33922baa0ba7.js


שימו לב לשני הקבצים ששמותיהם הם מספרים, אלה הספריות lodash ו moment. הם נכנסו לפרויקט בתור קבצים נפרדים, וזה נחמד כי דפדפן יכול לשמור כל אחד מהם בנפרד ב cache, ולטעון אותם רק מהדפים שבאמת צריכים אותם.

שימוש בספריה חיצונית מתוך app
בפרויקט next יש גם קובץ כללי בשם _app.tsx. ספריות שאנחנו טוענים ממנו יאוחדו לקובץ אחד שייטען בכל אחד מהדפים ביישום. אני מחזיר את index.tsx למצבו ההתחלתי ומעדכן הפעם את _app.tsx לתוכן הבא:

import "@/styles/globals.css";
import type { AppProps } from "next/app";
import _ from 'lodash';
import moment from 'moment';

export default function App({ Component, pageProps }: AppProps) {
return <div>
<p>Hello {_.head([1, 2, 3])}</p>
<p>{moment("20111031", "YYYYMMDD").fromNow()}</p>
<Component {...pageProps} />;
</div>

}



מבחינת העמוד דברים נראים אותו דבר יש לי את אותם טקסטים בדיוק (כן במקום אחר אבל זה לא חשוב כרגע), אבל מה שכן חשוב זה ה JavaScript שנוצר:

$ ls -l .next/static/chunks
total 816
-rw-r--r-- 1 ynonp staff 181258 נוב 4 19:42 framework-88d0cc4abe8a5763.js
-rw-r--r-- 1 ynonp staff 113610 נוב 4 19:42 main-22f485a16e76a198.js
drwxr-xr-x 5 ynonp staff 160 נוב 4 19:42 pages
-rw-r--r-- 1 ynonp staff 112594 נוב 4 19:42 polyfills-42372ed130431b0a.js
-rw-r--r-- 1 ynonp staff 1553 נוב 4 19:42 webpack-69db33922baa0ba7.js


הפעם אין צ'אנקים עבור הספריות החיצוניות ובמקומים בתוך תיקיית pages אני מקבל:

$ ls -l .next/static/chunks/pages
total 272
-rw-r--r-- 1 ynonp staff 129509 נוב 4 19:42 _app-852f41acee199ae3.js
-rw-r--r-- 1 ynonp staff 232 נוב 4 19:42 _error-8c2b6ff87cd513a2.js
-rw-r--r-- 1 ynonp staff 1385 נוב 4 19:42 index-de2181fd4b2adca1.js


כלומר קובץ אחד בשם _app-852f41acee199ae3.js שמכיל את שתי הספריות. הקובץ הזה ייטען בכל אחד מהדפים ביישום ודפדפן לא יוכל לשמור אותו ב cache, כי כל שינוי באחד המרכיבים שלו יגרום ליצירה מחדש של כל הקובץ.

שימוש בספריה חיצונית רק מתוך קוד צד שרת
אופציה שלישית היא לטעון את הספריה החיצונית ולהשתמש בה רק בפונקציה getServerSideProps מתוך אחד הדפים. אני מחזיר את _app.tsx לצורתו המקורית וחוזר ל index.tsx ועכשיו זה יהיה תוכנו:

import _ from 'lodash';
import moment from 'moment';

export const getServerSideProps = (async () => {
return {
props: {
one: _.head([1, 2, 3]),
two: moment("20111031", "YYYYMMDD").fromNow(),
}
}
});


export default function Home(props: any) {
return (
<>
<Head>
<title>Create Next App</title>

ToCode

05 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-next-imports

ToCode

05 Nov, 05:15


<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div
className={\${styles.page} ${geistSans.variable} ${geistMono.variable}\}
>
<main className={styles.main}>
<p>{props.one}</p>
<p>{props.two}</p>
</main>
</div>
</>
);
}


הפעם אלה הצ'אנקים שנוצרו בבנייה:

ynonp@Ynons-MacBook-Air ~/tmp/perf (main*?) $ ls -l .next/static/chunks
total 816
-rw-r--r-- 1 ynonp staff 181258 נוב 4 19:46 framework-88d0cc4abe8a5763.js
-rw-r--r-- 1 ynonp staff 113610 נוב 4 19:46 main-22f485a16e76a198.js
drwxr-xr-x 5 ynonp staff 160 נוב 4 19:46 pages
-rw-r--r-- 1 ynonp staff 112594 נוב 4 19:46 polyfills-42372ed130431b0a.js
-rw-r--r-- 1 ynonp staff 1459 נוב 4 19:46 webpack-59041051e025bed2.js
ynonp@Ynons-MacBook-Air ~/tmp/perf (main*?) $ ls -l .next/static/chunks/pages
total 24
-rw-r--r-- 1 ynonp staff 400 נוב 4 19:46 _app-f45d290ef4eb0435.js
-rw-r--r-- 1 ynonp staff 232 נוב 4 19:46 _error-8c2b6ff87cd513a2.js
-rw-r--r-- 1 ynonp staff 1480 נוב 4 19:46 index-f051a589b07bc05e.js


הפעם אף קובץ בתיקיית chunks לא כולל את ספריית moment או את ספריית lodash. ספריות אלה נטענות ונמצאות בשימוש רק בצד השרת, והגודל הכולל של כל ה JavaScript שנשלח לדפדפן הוא כ 20% פחות.

שורה תחתונה בעבודה עם next גם בגירסת ה Pages Router אנחנו יכולים להוריד משמעותית את כמות ה JavaScript שנשלחת לדפדפן באמצעות העברת הקוד לצד שרת, או להשתמש נכון יותר ב Cache באמצעות העברת הספריות לקבצים של הדפים. הקובץ app צריך להיות ממש המוצא האחרון כיוון שקוד שאני כותב בו יישלח לכל הדפים במרוכז.

ToCode

04 Nov, 05:16


לפעמים כפל קוד דווקא יכול להיות יותר קריא
בואו נראה דוגמה פשוטה מ vue לצמצום כפל קוד ואז ננסה לראות מה דעתנו על השינוי. אני מתחיל עם משתנה בשם seconds ושני משתנים מחושבים בשם minutes ו hours:

const seconds = ref(0);
const minutes = computed(() => {
get() { return seconds / 60},
set(minutes) { seconds.value = minutes * 60}
});
const hours = computed(() => {
get() { return hours / 3600},
set(hours) {seconds.value = hours * 3600}
})


ואז אני שם לב ש minutes ו hours בעצם חושבו באותה צורה אז אני מארגן מחדש את הקוד:

function deriveTime(start: Ref<number>, factor: number) {
return computed({
get() { return Number((start.value / factor).toFixed(2)) },
set(newValue) { start.value = newValue * factor }
})
}

const seconds = ref(0);
const minutes = deriveTime(seconds, 60);
const hours = deriveTime(seconds, 3600);


יותר מזה, מאחר ו deriveTime לא באמת קשורה לקומפוננטה אני יכול להעביר אותה לקובץ אחר ואז יש לי רק:

const seconds = ref(0);
const minutes = deriveTime(seconds, 60);
const hours = deriveTime(seconds, 3600);


והשאלה - איזה API טוב יותר? כמה מחשבות:

1. הפונקציה deriveTime משנה התנהגות. בזה שהיא מחביאה קריאה ל computed היא מחזיקה בתוכה מטען סמנטי משמעותי. כל מי שיקרא את הקוד יצטרך לדעת שפונקציה זו מחזירה ערך מחושב ושומרת על ריאקטיביות, ושאת הערך המחושב הזה אפשר גם לשנות.

2. במילים אחרות החוזה של פונקציית deriveTime עם הקוד החיצוני כולל יותר התחייבויות ממה שרואים ברשימת הפרמטרים.

3. אם הפונקציה נמצאת בשימוש בהרבה מקומות בקוד המערכת יש הגיון להוסיף לה את המטען הסמנטי הזה. ממילא כל מי שיקרא את הקוד צפוי להכיר אותה. ככל שהיא פחות נמצאת בשימוש היא עלולה לבלבל.

4. פיתרון טוב יהיה להוסיף תיעוד באתר הקריאה שמסביר מה קורה פה, משהו כזה:

const seconds = ref(0);
// create reactive modifiable computed refs for other time units:
const minutes = deriveTime(seconds, 60);
const hours = deriveTime(seconds, 3600);


מצד שני ככל שההסבר בתיעוד יותר ארוך אולי עדיף להישאר עם הקוד המקורי.

פיתרון ביניים נוסף שיכול לעזור כאן הוא להעביר רק את יצירת האוביקט לפונקציה אבל להשאיר את הקריאה ל computed באתר הקריאה כלומר:

const seconds = ref(0);
const minutes = computed(deriveTime(seconds, 60));
const hours = computed(deriveTime(seconds, 3600));


כתיבה כזאת משאירה את "מילת המפתח" computed במקום בולט וכך אנחנו יודעים מה אנחנו יוצרים. עדיין גם גירסה זאת מסתירה את העובדה שהאוביקט כולל גם getter וגם setter ולכן אפשר לשנות את הדקות והשעות.

הוצאת קוד לפונקציה כדי למנוע חזרה היא הרבה פעמים רעיון טוב. היא מאפשרת תחזוקה טובה יותר של הקוד ותיקון בעיות רק במקום אחד. מצד שני כשאנחנו מסתירים קריאה לפונקציות ליבה של הפריימוורק בתוך פונקציית עזר שלנו התוצאה עלולה להיות קוד מבלבל יותר ורק להקשות על התחזוקה.

מה דעתכם? במצב כזה הייתם משתמשים בפונקציית עזר, נשארים עם הקוד הכפול או בוחרים פיתרון אחר?

ToCode

04 Nov, 05:16


https://www.tocode.co.il/blog/2024-11-dry-framework-keywords

ToCode

03 Nov, 05:14


משחקים עם Embeddings
חוץ משיחות, ChatGPT גם יודע לחשב משהו שנקרא Embedding לכל טקסט שניתן לו. אמבדינג זה בעצם וקטור של מספרים שמייצג איפה במוח של ChatGPT הטקסט הזה יושב, כי אפשר לדמיין את המוח של ChatGPT בתור מרחב רב מימדי של טקסטים. בעזרת וקטור כזה אפשר לפעמים למצוא דברים שקשורים אחד לשני, כי הם כנראה יהיו "קרובים" באותו מרחב רב מימדי.

בפוסט היום הלכתי לנסות לחשב Embeddings ולראות אם זה יעזור לי לזהות פוסטים מהבלוג שקשורים לפוסט מסוים. המסקנה אינה חד משמעית אבל אני מקווה שהדרך תהיה מעניינת.

איך מחשבים Embedding
הרעיון הראשון והוא מאוד פשוט זה שבשביל לחשב Embedding רק צריך לשלוח טקסט למודל שפה גדול, במקרה שלנו ל ChatGPT. אני ברובי אז לקחתי ספריה שנקראת ruby-openai והשתמשתי בקוד שנראה ככה:

response = client.embeddings(
parameters: {
model: "text-embedding-3-large",
input: "The food was delicious and the waiter..."
}
)

puts response.dig("data", 0, "embedding")


בשביל לחשב Embeddings על פוסטים הפעלתי את הקוד הבא על 100 הפוסטים האחרונים שפרסמתי:

sources = blogPost.last(100).map(&:markdown_source)

embeddings2 = sources.map do |s|
client.embeddings(parameters: {model: "text-embedding-3-large", input: s })
end


ואחרי המתנה קצרה קיבלתי מערך של 100 אמבדינגס עבור 100 פוסטים אחרונים מהבלוג.

מרחק בין Embeddings
ראיתי ברשת כל מיני דרכים לחשב מרחקים בין Embeddings אבל נשמע שהפופולרית ביותר למצוא דברים שקשורים אחד לשני נקראת cosine similarity. הלכתי ל ChatGPT וביקשתי ממנו שיכתוב לי פונקציה ברובי לחשב את זה בין שני וקטורים:

def cosine_similarity(vec1, vec2)
dot_product = vec1.zip(vec2).map { |a, b| a * b }.sum
magnitude1 = Math.sqrt(vec1.map { |v| v**2 }.sum)
magnitude2 = Math.sqrt(vec2.map { |v| v**2 }.sum)

dot_product / (magnitude1 * magnitude2)
end


התוצאות
עכשיו נשאר רק לשחק עם המספרים. לדוגמה לקחתי את הפוסט האחרון שפירסמתי (ניסוי כתיבת Directives בריאקט) וחיפשתי איזה Embeddings היו יחסית קרובים אליו, כלומר במרחק גדול מ 0.5:

distances = embeddings2.map {|i| cosine_similarity(embeddings2[-1]["data"][0]["embedding"], i["data"][0]["embedding"])
}

distances.each_index.select {|i| distances[i] > 0.5 }


התוצאה כללה די הרבה פוסטים:

1. איך עובד פרויקט Rails עם ESBuild
2. אז OpenAI עברו לרמיקס
3. חמש דקות עם flet
4. ניסוי דינו - מה אפשר לעשות היום עם Deno ו next.js
5. ניסוי ריילס: משחק איקס עיגול
6. השתמשו באיזה ספריות שתרצו
7. טיפ LLM - איך זה עובד ב X ?
8. ריאקט או Vue - הדגמה דרך הוק קצר
9. ואז זה נגמר
10. ניסוי React - ואם היו Directives?

אם מחפשים רק פוסטים שהמרחק שלהם גדול מ 0.6 מקבלים רק את הפוסט המקורי ואת "ריאקט או Vue - הדגמה דרך הוק קצר". שזה יותר הגיוני כי מדובר על שני פוסטים שמשווים בין ריאקט ל Vue מזוויות שונות.

אחרי זה לקחתי את הפוסט "ניסוי דינו - מה אפשר לעשות היום עם Deno ו next.js" וחיפשתי באותה שיטה פוסטים קשורים. הגעתי ל:

1. ה Killer Feature החדש של node.js (או: האם זה הסוף של דינו?)
2. הונו הוא כל מה שרציתי מ express
3. אז OpenAI עברו לרמיקס
4. דוגמת דינו: שמירת תמונות מויקיפדיה

שזה כבר די מוצלח.

הצעד הבא
עכשיו שהתשתית במקום אפילו אם לא מושלמת אני מקווה לקחת את זה צעד נוסף קדימה ולהתחיל לחשב Embeddings של פוסטים כשאני מעלה אותם לאתר ולשמור ב DB, וגם להריץ חישוב מרוכז על כל הפוסטים שכבר פירסמתי (לפחות בשנה האחרונה). אחרי שזה יהיה במקום אפשר יהיה להוסיף קופסת "פוסטים קשורים". עדיין לא ברור אם זה יעבוד, אבל הניסוי היום הראה שיש סיכוי.

ToCode

03 Nov, 05:14


https://www.tocode.co.il/blog/2024-11-embeddings

ToCode

02 Nov, 05:14


ניסוי React - ואם היו Directives?
אחד המנגנונים החמודים של Vue הוא ה Directives שמאפשרים לכתוב פחות קוד בקוד התבנית ולקחת חלקים של קוד שמשפיעים על ה DOM כדי להשתמש בהם בכמה מקומות. בשביל המשחק בואו ננסה לבנות משהו דומה בריאקט.

מה זה Directives
אחת התכונות שאנשים אוהבים ב Vue היא ה Directives. בקצרה זאת היכולת לכתוב מאפיין בתוך אלמנט ולקבל התנהגות מסוימת עבור המאפיין הזה. לדוגמה:

<p v-if="seen">Now you see me</p>


המאפיין v-if הוא מאפיין מיוחד מסוג זה. הוא כולל "קוד" שרץ כל פעם שצריכים לרנדר את האלמנט והקוד הזה יכול לשנות את האלמנט או אפילו להוציא אותו לגמרי מהעץ. בדוגמה של v-if אם המשתנה seen הוא אמיתי האלמנט יופיע ואם הוא "שקר" אז האלמנט לא יופיע. הם עושים המון דברים עם Directives ב Vue אבל מה שחשוב לקחת מכאן זה:

1. דירקטיבס מאפשרים להשפיע על קומפוננטה מבחוץ, בלי קשר לקוד של הקומפוננטה עצמה (לא הייתי צריך לגעת בקוד של p בשביל להיות מסוגל להוסיף לו v-if).

2. אפשר להוסיף דירקטיבס לכל קומפוננטה.

3. דירקטיב הוא חלק מקוד הקומפוננטה שלא כתוב בתוכה ומתווסף או מוסר בצורה של הרכבה.

חלומות בריאקט
אז הלכתי לנסות לכתוב משהו דומה בריאקט, לפחות את הרעיון. התחלתי מהקוד הבא לקומפוננטה הראשית App:

function App() {
const [count, setCount] = useState(0)

return (
<>
<div>
<Text v-if={count % 2 == 0}></Text>
<Text v-highlight={true} v-if={count % 2 == 0}></Text>
<button onClick={() => setCount(c => c+1)}>{count}</button>
</div>
</>
)
}


וכבר פה אני מבין את המגבלות שלי. אין לי איך להשפיע על קומפוננטות כמו p או div או כל קומפוננטה מובנית אחרת בריאקט. הדברים היחידים שאני יכול להשפיע עליהם הם קומפוננטות שאני בונה, ולכן בשביל לנסות לכתוב Directives יצרתי קומפוננטה בשם Text שמציגה טקסט.

אני גם יודע שבשביל להוסיף תמיכה ל Directives צריך להיכנס לקוד הקומפוננטה, אבל אני לא רוצה לעשות את זה לכל קומפוננטה במערכת לכן יצרתי פונקציה שעוטפת הגדרה של קומפוננטות:

const Text = directify((props) => {
return <p>Hello World</p>
});


כל עוד אני מגדיר קומפוננטות עם הפונקציה directify אפשר יהיה להוסיף להן Directives.

קוד הפונקציה הוא די פשוט כי זה רק פוסט בבלוג, כמובן שאם תרצו לקחת את הרעיון הזה למנגנון אמיתי תצטרכו לחשוב קצת יותר לעומק על סדר פעולות ומתי מפעילים את קוד ה Directive. בינתיים כתבתי את זה:

export const Directives = {};

export function directify(c) {
return (props) => {
let el = c(props);
for (const [directiveName, directive] of Object.entries(Directives)) {
if (directiveName in props) {
if (el) {
el = directive(el, props[directiveName]);
} else return el;
}
}
return el;
}
}


הפונקציה מקבלת קומפוננטה, מפעילה אותה ומעבירה את התוצאה ל Directives. ליד הפונקציה יש אובייקט גלובאלי של Directives.

ה Directives עצמן באובייקט הגלובאלי נראות כך:

Directives['v-if'] = (el, value) => {
if (value) {
return el;
} else {
return false;
}
}

Directives['v-highlight'] = (el, value) => {
return cloneElement(el, {
style: {
...(el.props.style),
backgroundColor: 'yellow',
}
})
}


אז כן זה עובד, אבל צריך עוד הרבה עבודה. אפילו בדוגמה הקטנה של ה v-if צריך להבין ש v-if אמור לרוץ לפני שמרנדרים את האלמנט ואם הוא מחזיר false אז לא צריך לרנדר את האלמנט בכלל. מצד שני v-highlight עובד יחסית בסדר. אני לא בטוח איך לדמיין את v-for אבל זה יכול להיות תרגיל מעניין.

החיסרון הכי גדול הוא שאי אפשר להוסיף Directives בדרך הזאת לאלמנטים המובנים בריאקט, לכן נצטרך גם ליצור גירסה "תומכת Directives" של כל הקומפוננטות המובנות.

ספריה מעניינת שהלכה בכיוון הזה היא react-directive. שם הם התמקדו בהוספת תמיכה ב Directives לאלמנט div יחיד ומימוש ה Directives המובנים בלי לבנות תשתית להרחבה ל Directives שלנו.

ToCode

02 Nov, 05:14


https://www.tocode.co.il/blog/2024-11-react-directives

ToCode

01 Nov, 05:15


https://www.tocode.co.il/blog/2024-11-and-then-its-over

ToCode

01 Nov, 05:15


ואז זה נגמר
מישהו ברדיט שאל- יש לנו אלפי בדיקות אנזים לפרויקט ריאקט ואני רואה שאנזים כבר לא נתמך ורק בגלל הבדיקות אנחנו תקועים עם גירסה ישנה של ריאקט. מה עושים?

ואותה שאלה חוזרת כל הזמן ובאינסוף גירסאות ... מה עושים עם אפליקציית אנגולר1? מה יקרה עם אפליקציית ה next.js שלי במעבר ל App Router? מה עושים עם כל הקלאסים כשיצאו React Hooks? מה עושים עם אתר הפלאש? הסילברלייט? ה Java Applet?

ובדיקות זה עוד החלק הקל. לא נעים אבל אפשר למחוק את כל הבדיקות ולהתחיל לכתוב מחדש ב React Testing Framework. לפעמים צריך לעדכן קוד ולפעמים לכתוב מוצר חדש מאפס.

יש שיגידו ששינויים כאלה הם הסיבה לכתוב Micro Services. אני לא בגישה הזאת. שינויים דרסטיים (פריימוורק שלם לא עובד יותר) אינם פתאומיים, וכמעט תמיד אפשר לשכתב דברים בהדרגה או רק חלק מהמוצר. בסוף אנחנו חיים בעולם דינמי וכדאי לזכור החדשים נוצרו מסיבה - הרבה פעמים כתיבה מחדש של אותו קוד עם הכלים החדשים תהיה הרבה יותר מהירה מהכתיבה בפעם הראשונה, גם בגלל שאנחנו יודעים יותר טוב מה לעשות וגם בגלל שהכלים החדשים פשוטים וטובים יותר.

ToCode

31 Oct, 05:16


ריאקט או Vue - הדגמה דרך הוק קצר
הרבה אנשים מדברים על עקומת הלמידה של ריאקט בהשוואה ל Vue ויש בזה משהו. נכון לריאקט 18 (כלומר לפני ש Suspense יהיה דבר גדול ולפני use והבעיות שלו), האתגר הכי גדול עם ריאקט היה useEffect. בואו נראה דוגמה קצרה של אפקט בהשוואה בין שתי הספריות כדי להבין קצת את הרוח והייחודיות של כל אחת.

ריאקט: הקשבה לתנועת עכבר
בדוגמה אני כותב Hook שמקשיב לתנועת עכבר כדי להציג את המיקום הנוכחי של העכבר באמצע המסך. זה הקוד של ה Hook והקומפוננטה שמשתמשת בו, בשביל הפשטות באותו קובץ:


import { useEffect, useState } from 'react'
import './App.css'

function useMousePosition() {
const [position, setPosition] = useState([0, 0]);

useEffect(() => {
function handleMove(ev: MouseEvent) {
setPosition([ev.clientX, ev.clientY]);
}

window.addEventListener('mousemove', handleMove);

return () => {
window.removeEventListener('mousemove', handleMove);
}
}, [])

return position;
}

function App() {
const position = useMousePosition();

return (
<h1>Position: {position[0]}, {position[1]}</h1>
)
}

export default App


מבחינת הקומפוננטה השימוש ב Custom Hook נראה מאוד פשוט, והקומפוננטה כוללת בסך הכל את שורת הגדרת המשתנה ואת התבנית. בזכות טייפסקריפט אנחנו יודעים ש position מורכב משני מספרים, וגם ב Hook-ים יותר מסובכים טייפסקריפט יכול לעזור לזכור מה בדיוק חוזר מהפונקציה.

מבחינת קוד ה Hook יש פה קצת יותר אתגר בגלל המבנה של useState, useEffect והקשר ביניהם:

1. פונקציית useState מחזירה משתנה ופונקציית עדכון לאותו משתנה. במקרה של מערכים זה מבלבל וצריך לזכור שאי אפשר פשוט לשנות ערך באחד התאים של המערך, אלא תמיד לקרוא לפונקציית העדכון.

2. יותר מבלבל הוא הממשק של useEffect, בגלל שהוא מחולק ל-3 - הפונקציה הראשונה נקראת כדי ליצור את האפקט, הפונקציה השנייה (שחוזרת מ useEffect) צריכה לנקות את האפקט ומערך התלויות שקובע מתי ליצור מחדש את האפקט. כל אחד מהחלקים הכרחי אבל ניתן להשמטה וכל חלק ששוכחים לכתוב יכול להביא לתוצאות מוזרות.

גירסת Vue
אותה דוגמה בגירסת Vue לא נראית יותר מדי שונה, וזו התחלה טובה:


<script setup lang="ts">

import { shallowRef, onMounted, onUnmounted } from 'vue'

function useMouse() {
const position = shallowRef([0, 0])

function update(event: MouseEvent) {
position.value = [event.clientX, event.clientY];
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))

return position;
}

const position = useMouse();
</script>

<template>
<h1>{{ position[0] }}, {{ position[1] }}</h1>
</template>


הקומפוננטה מגדירה את position בתור shallowRef במקום בתור useState, והעדכון שלו מתבצע על ידי השמה למאפיין .value. גם כאן אי אפשר לשנות רק תא אחד במערך וזה יכול להיות מבלבל.
בניגוד לריאקט, ל Vue יש עוד אופציות להגדיר משתנים ריאקטיביים כמו ref ו reactive שכן מאפשרות מעקב גם לעומק האוביקט

השינוי היותר גדול הוא הממשק של ה Hook - הפעם במקום להשתמש בפונקציה אחת אנחנו קוראים ל-2, הפונקציה onMounted ו onUnmounted, ופה גם המפתח להבין את ההבדלים בין הספריות. בעוד שריאקט הכניסה את המילה Effect בתור אוסף של 3 חלקים, ב Vue הלכו על גישה יותר Low Level בה אנחנו פשוט מגדירים איזה קוד להפעיל מתי. הייתרון בגישה הוא הפשטות ועקומת הלימוד היותר קלה, כי לא צריך לחשוב מה זה אפקט ומתי טוב או לא טוב להשתמש בו, אבל החיסרון הוא שיש יותר מקום לטעויות כשצריך לחשוב למשל באיזה מצבים רוצים ליצור מחדש את ה Event Handler. הקשר בין onMounted ל onUpdated יכול להיות מסורבל לכתיבה ולכן בריאקט עברו ממודל של Lifecycle למודל של אפקטים, אבל מצד שני המודל החדש בריאקט יצר עקומת לימוד הרבה יותר משמעותית ובפרספקטיבה של השנים שעברו מאז יצירת המודל אני לא בטוח שזה היה רעיון טוב.

מה דעתכם? איזה מהגישות אהבתם יותר? ואיזה מאפיינים ייחודיים של כל ספריה אתם אוהבים?

ToCode

31 Oct, 05:16


https://www.tocode.co.il/blog/2024-10-react-vue-mousemove

ToCode

30 Oct, 05:15


טיפ CSS: כותרות דביקות לטבלה
הערך sticky של מאפיין position ב CSS יכול לחסוך הרבה כאב ראש כשרוצים לבנות טבלה עם כותרות דביקות - כלומר טבלה עם פסי גלילה בה שורת הכותרת נשארת תמיד למעלה (כמו באקסל) כשגוללים למטה כדי שאפשר יהיה לראות את שמות העמודות, ובאותו אופן העמודה הראשונה תישאר תמיד על המסך כשנגלול הצידה. זה הקוד בקצרה:

thead {
position: sticky;
top: 0;
z-index: 2;
}

th:first-child, td:first-child {
position: sticky;
left: 0;
}

td:first-child {
background-color: white;
}

th {
background-color: #f2f2f2;
}


בואו נעבור על כל המאפיינים:

1. המאפיין position: sticky גורם לאלמנט "להידבק" למיקום מסוים אם גלילה תגרום לו לצאת מהמסך. זה בדיוק האפקט שאנחנו רוצים לכותרות של הטבלה. ל thead הגדרתי שיידבק לחלק העליון בעזרת top וכל td שהוא ילד ראשון, כלומר כל העמודה הראשונה, נדבקת לצד שמאל.

2. הגדרת z-index על thead קבעה ששורת הכותרת העליונה תופיע מעל עמודת הכותרת וכך שם העמודה הראשונה לא מוסתר בגלילה למטה על ידי הערכים של העמודה.

3. אלמנטים שמוגדרים בתור sticky יופיעו "מעל" אלמנטים אחרים ולכן צריכים הגדרת צבע רקע כי אחרת נוכל לראות את הטקסט של השורות כשהן עוברות "מתחת" לשורת הכותרת או של העמודות כשהן עוברות "מתחת" לעמודת הכותרת.

העליתי לקודפן את הקוד המלא עם כל ה CSS וה HTML של הטבלה מוזמנים לשחק עם זה בקישור:
https://codepen.io/ynonp/pen/zYgWKOM

או מוטמע כאן:

<iframe height="400" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/zYgWKOM?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/zYgWKOM">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>

ToCode

30 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-position-sticky-table

ToCode

29 Oct, 05:15


סטראבה
כואב לשים לב שהרבה בעיות אבטחת מידע הן בעצם בעיות תיאום ציפיות. היה מספיק שסטראבה היתה מציגה פופ-אפ לפני שיתוף מסלול שאומר משהו כמו-

> אתה עומד לשתף מידע על מסלול ריצה שביצעת עם האינטרנט. שים לב: כל בן אדם אחר שרץ את אותו מסלול יוכל לראות את הפרטים שלך ואת כל המסלולים האחרים בהם רצת. האם אתה בטוח שאתה רוצה להמשיך?

ואולי בעצם איש ה Product שהיה מנסח כזאת הודעה היה מבין ששיתוף כזה הוא שבור וכולל בעיית אבטחה מובנית וכך היה נחסך מאיתנו פיצ'ר ריגול מיותר.

ToCode

29 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-strava

ToCode

28 Oct, 05:16


טיפ לדיבוג בעיות קונפיגורציה של Apache
אני יודע אתם כבר לא משתמשים ב Apache, יש לכם nginx או שאתם שמים את הפרויקטים שלכם על vercel או AWS ומי בכלל מתחזק שרתים בימינו. נו, אני צוחק. המון אנשים מתחזקים שרתים, ובהרבה מהם רץ עדיין Apache. ואחת הבעיות עם Apache היא שהודעות השגיאה שלו לא תמיד קיימות או לא מדויקות.

אז אם לדוגמה יש לי קובץ קונפיגורציה ל Site עם טעות, לפעמים אפאצ'י יסרב לעלות. לפעמים הוא יספר פרטים על הטעות ולפעמים הוא פשוט יעביר את התנועה שנשלחה ל Site הזה ל Site אחר עם קונפיגורציה יותר גנרית.

דרך אחת לקבל יותר אינפורמציה על Apache היא הפקודה: apache2ctl -S. הפלט מהשרת שלי נראה בערך כך:

AH00548: NameVirtualHost has no effect and will be removed in the next release /etc/apache2/sites-enabled/002-tocode.co.il.conf:1
VirtualHost configuration:
*:443 is a NameVirtualHost
default server tocode.co.il (/etc/apache2/sites-enabled/002-tocode.co.il.conf:8)
port 443 namevhost tocode.co.il (/etc/apache2/sites-enabled/002-tocode.co.il.conf:8)
port 443 namevhost www.tocode.co.il (/etc/apache2/sites-enabled/002-tocode.co.il.conf:27)
*:80 is a NameVirtualHost
default server tocode.co.il (/etc/apache2/sites-enabled/002-tocode.co.il.conf:3)
port 80 namevhost tocode.co.il (/etc/apache2/sites-enabled/002-tocode.co.il.conf:3)


וזה מראה לנו איזה Host-ים יש לנו על השרת, באיזה שמות מטפל כל Host והכי חשוב באיזה קובץ קונפיגורציה כל Host הוגדר. המשך הפלט מראה עוד נתוני debug שלי היו פחות חשובים. נסו את זה על השרתים שלכם וספרו אם למדתם משהו חדש או מצאתם משהו מיותר.

ToCode

28 Oct, 05:16


https://www.tocode.co.il/blog/2024-10-apache-debug-conf

ToCode

27 Oct, 05:15


מבחינת קוד העבודה עם דינו היתה מאוד פשוטה בזכות התמיכה בסטנדרטים המתקדמים של פיתוח רשת, כלומר אפשר להשתמש ב fetch כדי למשוך מידע מ URL מרוחק, Promise.all כדי לחכות לכמה בקשות רשת במקביל ויש להם פונקציה בשם Deno.writeFile כדי לכתוב לקובץ. בגדול מבחינת קוד אין שם שום דבר ש Chat GPT לא יכל היה לכתוב. בעיה אחת שכן היתה לי עם הקוד היא הלולאה:

  for (let i=0; i < names.length; i++) {
const fixed = await modifyImage(buffers[i], 512, 512);
await Deno.writeFile(path.join(dir, names[i]), fixed);
}


הלולאה מפעילה את modifyImage בצורה סדרתית תמונה אחרי תמונה. כשניסיתי להפעיל את הפונקציה במקביל על כל התמונות עם Promise.all קיבלתי תוצאות שגויות. לא הצלחתי להבין למה.

בעיה נוספת עם הקוד היתה ש Deno לא הצליח לפענח את קובץ ה index.d.ts של ספריית wikipedia מ npm, ולכן אי אפשר היה להשתמש בספריה מקובץ TypeScript ועברתי ל JavaScript בקובץ הלוגיקה.

דוקר
הקובץ האחרון הוא ה Dockerfile וגם פה יש כבר פיתרון טוב לדינו זה נראה ככה:

FROM denoland/deno:2.0.3

* The port that your application listens to. *
EXPOSE 8000

WORKDIR /app
RUN bash -c "mkdir -p /app/files && chown -R deno /app/files"

* Prefer not to run as root. *
USER deno

* These steps will be re-run upon each file change in your working directory: *
COPY . .
* Compile the main app so that it doesn't need to be compiled each startup/entry. *
RUN deno cache main.ts

CMD ["run", "-A", "main.ts"]


האימג' deno מ Denoland היא אימג' בסיסי להרצת תוכנית דינו וצריך להוסיף עליה רק את הקבצים שלנו ולהריץ.

בעיה אחת שהיתה לי עם ה Dockerfile הזה היא שהתקנת התלויות קורית בשורה:

RUN deno cache main.ts


מה שאומר שבשביל להתקין את התלויות אני צריך להעתיק לאימג' את כל הקבצים של הפרויקט ולכן קורה רק אחרי העתקתם. בדוקר זה אומר שאי אפשר להשתמש בתלויות בתור שכבה נפרדת ולכן כל שינוי בקוד בבנייה הבאה הוא יצטרך להתקין מחדש את כל התלויות. ב node אני מזכיר שהיה לנו את:

COPY package*.json ./
RUN npm install


לפני העתקת שאר הפרויקט וכך שינויים בקוד נכנסו בשכבה עליונה יותר מהתלויות. אני חושב שיש דרך לעשות משהו דומה גם ב Deno אבל עדיין לא מצאתי.

שורה תחתונה העבודה עם דינו לא היתה רעה ויש הרבה דברים שעבדו לא פחות טוב ממה שהייתי מקבל עם node.js, אבל עדיין קשה להתיחס לזה כקפיצת מדרגה. האקוסיסטם עדיין מבולגן והתמיכה במודולים מ npm חלקית, כולל תמיכה חלקית בהגדרות הטיפוסים ששם מה שיכול להוביל לקושי בפרויקטים גדולים יותר.

האתגר הבא מבחינת דינו נשאר לדעתי החיבור ל npm - או על ידי שיפור התמיכה במודולים מ npm והפיכתם ל First Class Citizens באקוסיסטם או על ידי יבוא מאוד מאסיבי של מודולים מ npm ל JSR.

ToCode

26 Oct, 05:14


https://www.tocode.co.il/blog/2024-10-typescript-productivity

ToCode

26 Oct, 05:14


פרודוקטיביות (או: מתי לוותר על טייפסקריפט)
הפינוקים של טייפסקריפט באמת עוזרים לכתוב קוד מהר יותר ולעדכן אותו עם פחות טעויות. השלמה אוטומטית חוסכת לעבור לחלון של התיעוד. בדיקת טיפוסים עוזרת לשים לב לטעויות בזמן הכתיבה. ההיכרות של ה IDE עם הקוד מאפשרת שינוי שמות במהירות ובביטחון.

הבעיה היא שעל העלות של טייפסקריפט יותר קשה לדבר כי היא תלוית פרויקט.

בפרויקט שמשתמש במעט ספריות או שהספריות כתובות בעצמן בטייפסקריפט ומגיעות עם הגדרות טיפוסים המחיר יהיה מאוד נמוך. כן קצת Setup ראשוני ומדי פעם להיעזר ב ChatGPT כדי לכתוב הגדרות טיפוסים למשהו מסובך, אבל הרבה אנשים יגידו שזה חלק מתהליך הפיתוח ובכלל המחשבה על הטיפוסים עוזרת להתמקד בקוד שאנחנו צריכים.

בפרויקט יותר ישן או שמשתמש בהרבה ספריות חיצוניות, חלקן ישנות, הסיפור עשוי להיות יותר מסובך. האם כדאי לעשות fork לספריה חיצונית רק בשביל לתקן את הגדרות הטיפוסים שבה? האם עליי להישאר עם גירסה ישנה של טייפסקריפט רק בגלל שאיזה ספריה חיצונית לא תומכת בגירסה החדשה? וכמה זמן צפוי להתבזבז על קריאת הודעות שגיאה קריפטיות בגלל הגדרות טיפוסים רקורסיביות?

העוינות שיש לאנשים לגבי טייפסקריפט יכולה בחלקה להיות מוסברת על ידי פערים בציפיות ובעלויות, ובניסיון העבר שלנו עם השפה. יש אנשים שעבדו שנים עם טייפסקריפט על פרויקטים מסוג מסוים ושילמו עלות נמוכה מאוד עבור הערך שקיבלו מבחינת פרודוקטיביות, ולעומתם אנשים שעבדו על פרויקטים מסוג אחר משלמים מחירים עצומים ולא רואים את הערך (אולי כי הפרויקט קטן או דורש מעט שינויים). טייפסקריפט לא מתאימה לכל פרויקט וזה בסדר לוותר עליה בשביל להרוויח פרודוקטיביות בפרויקטים מסוימים. האתגר הוא להשאיר בראש את התמונה הגדולה. לראות את ההשלכות גם לטווח הרחוק ולקבל החלטה חכמה - האם טייפסקריפט באמת לא שווה את המחיר, או שאני רק מתעצל עכשיו ואשלם על זה בעתיד.

ToCode

25 Oct, 05:16


https://www.tocode.co.il/blog/2024-10-chatgpt-wikipedia-images

ToCode

25 Oct, 05:16


שיחות עם ChatGPT
- הי אני צריך קוד סקאלה שיוציא את כל התמונות מפוסט בויקיפדיה
- אין בעיה אתה יכול להשתמש ב REST API של ויקיפדיה. הנה הקוד (זורק מלא קוד על המסך).
- שמע זה עובד אבל מדפיס רק את התמונה הראשית של העמוד.
- נכון! באמת לקחתי רק את התמונה הראשונה ברשימה. הנה גירסה נוספת של הקוד שעוברת בלולאה על כל התמונות. (זורק גירסה חדשה של הקוד שלא מתקמפלת).

פה אני יכול לבחור אם להתעקש איתו כדי לקבל גירסה שכן מתקמפלת של הקוד או לעצור ולחשוב. הלכתי לקרוא את הקוד במקום רק להעתיק ושמתי לב שאפילו אם הלולאה שלו היתה נכונה הנתונים שמגיעים מה API כוללים רק תמונה אחת. חזרה ל ChatGPT.

- שומע אני חושב שיש בעיה ב URL שבחרת, יש שם רק את התמונה הראשית מהעמוד.
- וואו כל הכבוד ששמת לב! באמת יש URL אחר שנותן את המידע על כל התמונות, אבל בלי ה URL-ים של התמונות עצמן. זה לא נורא כי אני יכול לפנות לעוד Endpoint עם רשימת התמונות ולקבל רשימה של URL-ים. (זורק עוד מלא קוד שלא מתקמפל).

הייתי רוצה לספר לכם שפה היה לי את השכל לעצור ולהיפרד מה ChatGPT אבל האמת שזה לקח לי עוד דקות ארוכות בהן תיקנתי את הקוד שהוא כתב עד שהבנתי שהוא לגמרי לא בכיוון. כן אפשר לגרום לזה לעבוד אבל חייבת להיות דרך טובה יותר. אז עברתי לתיעוד של ויקיפדיה, מצאתי את ה Endpoint הנכון וחזרתי ל Chat GPT לעמת אותו עם המציאות.

- שומע לויקיפדיה יש URL שנותן את כל המידע של התמונות ואפשר עם String Manipulation פשוט לקבל את ה URL-ים של כל התמונות. (מדביק קוד קצר שעובד).
- אתה ממש צודק ואני שמח שמצאת את זה. (זורק סיכום והסבר ארוכים של הקוד שלי).

נ.ב. אם יום אחד תצטרכו ה URL הזה נתן לי את כל השמות של התמונות בפוסט מסוים:

https://en.wikipedia.org/w/api.php?action=query&titles=table&generator=images&format=json

המידע שמגיע הוא מערך של אוביקטים ולכל אוביקט יש title שהוא שם התמונה, לדוגמה File:Disambig gray.svg. בשביל להפוך את שם התמונה ל URL בונים URL מיוחד לפי התבנית:

https://en.wikipedia.org/wiki/Special:FilePath/file/{title}


בדוגמה שלנו הקישור לתמונה הוא:

https://upload.wikimedia.org/wikipedia/en/5/5f/Disambig_gray.svg

ToCode

24 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-always-worked-before

ToCode

24 Oct, 05:15


אבל תמיד זה עבד
מיכה שטרית שר-

> פעם יכולתי לבלוע כדור ארץ שלם.
> רכבתי על אופניים כשהידיים באויר.
> פעם הייתי חיה ואת היית מותק.
> פעם חייתי באמת.

פעם אולי הצלחת בקלות למצוא עבודה בפיתוח PHP. היום אולי כבר צריך לדעת ריאקט בשביל אותה משרה.

פעם אולי אף אחד לא היה צריך אתר שיעבוד גם במצב Offline, או יתחשב בכלל ברוחב הפס של הגולש. היום הציפיות הן שונות.

פעם חשבנו ששפות דינמיות הן ה-דבר, וש JavaScript רק צריכה להיות יותר דינמית. היום קשה למצוא מפתחים שיוותרו על ה Static Typing של TypeScript. וכן זה משפיע על המערכות שנעבוד עליהן וסוגי הבעיות שנצטרך לפתור.

רק בגלל שפעם שיטה מסוימת עבדה לא אומר שהיא טובה או שהיא תמשיך לעבוד. השאלה היא לא מה עבד פעם ואפילו לא למה זה כבר לא עובד. יש דברים שעבדו ועכשיו הם כבר לא עובדים. עכשיו בואו נתקדם ונראה מה עושים עם זה.

ToCode

23 Oct, 05:15


ראספברי פי או VPS או אולי בכלל Serverless - איפה לארח פרויקט צד?
העלייה של פיתרונות Serverless בשנים האחרונות הביאה לשינוי מעניין בהרגלי פרויקטי הצד שלנו. מצד אחד מאוד קל להעלות פרויקט ל Deno deploy או Next ולקבל איחסון בחינם, מצד שני VPS-ים עולים גרושים ובסכומים של 5-10 דולר לחודש אפשר לקבל שרת שיריץ כמעט כל פרויקט צד שתחשבו עליו, ומצד שלישי מחשבים ביתיים רק הופכים לקטנים וחזקים כשאפילו מכשיר כמו Raspberry Pi מגיע כבר עם 8 ג'יגה זיכרון ומעבד 4 ליבות. הנה כמה שיקולים שאני לוקח בחשבון בפרויקטים קטנים:

העלאה לפלטפורמות Serverless
נתחיל עם האופציה הכי פופולרית שהיא העלאה לפלטפורמות Serverless. פלטפורמות אלה מגיעות בשיטת "מתחיל זול ממשיך יקר" ונותנות לכם להעלות פרויקט לאוויר בלחיצת כפתור ובחינם, אבל כשיתחילו להגיע המשתמשים המחיר יעלה.

יתרונות:

1. קל מאוד להתחיל, במיוחד אם הפלטפורמה מסונכרנת עם סביבת הפיתוח שלכם (למשל Vercel לפרויקט next.js).

2. קל מאוד להגדיל כח חישוב כשמגיעים עוד משתמשים, וכך אם במקרה המוצר שלכם הופך ויראלי האתר לא יתרסק.

3. מנגנון CI/CD מובנה שהרבה פעמים יכריח אתכם לעבוד נכון יותר.

חסרונות:

1. על איחסון פרויקט לא מאוד קטן נשלם הרבה יותר מאשר בכל אופציה אחרת.

2. חלק מהעניין בפרויקט צד זה ללמוד, ושימוש בפלטפורמה יכול לפגוע בהיבט הלימודי כי אנחנו מתעסקים בפחות דברים.

3. לפעמים נדרשת יותר עבודה כדי לעשות דברים בצורה שונה ממה שנתמך בפלטפורמה.

העלאה ל VPS
חברות כמו Linode ו Digital Ocean מציעות שרתי VPS במחירים מאוד זולים. באופציה זו אנחנו מקבלים מכונה וירטואלית שכוללת רק מערכת הפעלה ואנחנו צריכים להתקין את כל השאר.

יתרונות:

1. התקנה של שרת היא אחת הדרכים הכי טובות ללמוד איך מערכות אינטרנט עובדות.

2. אנחנו יודעים בדיוק מה מקבלים וכמה זה עולה, ויכולים להשתמש במשאבי המחשוב של השרת לכל צורך שיהיה.

חסרונות:

1. הקמת סביבת CI/CD ומערכת על סביבת פרודקשן לוקחת זמן והשקעה, ולפעמים אנחנו רק רוצים שדברים יעבדו במיוחד בפרויקט צד כשהזמן הוא משאב מוגבל.

2. הקמה עצמאית של כל הסביבה עלולה לחשוף אותנו לבעיות אבטחה בגלל טעויות בקונפיגורציה.

3. אם פתאום המוצר הופך ויראלי ולא התכוננו למצב זה מראש אנו עלולים להסתבך בעת הגדלת הסביבה (הוספת שרתים וחיבור Load Balancer).

ראספברי פי בארון
אופציה שלישית ולא רעה בכלל עם השיפור בביצועים, בצריכת החשמל ובשקט של מחשבים ביתיים היא להריץ את הפרויקט על Raspberry PI שיושב באיזה ארון.

יתרונות:

1. מבחינת לימוד הקמת הסביבה מספקת הכי הרבה הזדמנויות ללמוד איך דברים עובדים. המחיר הנמוך אומר שאנחנו יכולים לארגן Cluster של מספר מכונות, ואפילו להפעיל כלים מתוחכמים כמו קוברנטיס וכמעט בלי עלויות.

2. אין תשלום חודשי על ה"שרת". אחרי ההקמה הפרויקט ממשיך לרוץ לנצח.

חסרונות:

1. אם יום אחד הפרויקט יצליח אנחנו בצרה. נצטרך להוציא את כל המידע ששמור על ה Pi ולהעלות אותו לאחסון אמיתי.

2. ההתעסקות עם הקמת כתובת IP סטטית וחיבור דומיין ל Raspberry Pi שיושב מאחורי Router אצלנו בבית יכולה להיות מייאשת.

3. אין גיבויים ו Raspberry PI יכול פתאום להפסיק לעבוד כי SD Card.

4. הפסקות חשמל וניתוקי רשת הרבה יותר נפוצים אצלך בבית מאשר בחוות שרתים של VPS-ים או פלטפורמות Serverless.

סיכום והמלצות
אז איפה שמים את הפרויקט הבא? בואו נסכם:

1. אם יש לכם פרויקט שמתאים בשפת התכנות למשהו שנתמך על ידי פלטפורמת Serverless לכו על הפלטפורמה.

2. אם יש לכם זמן פנוי ואתם אוהבים שליטה על כל מה שקורה בפרויקט שלכם העלאה ל VPS היא הפיתרון הטוב ביותר.

3. אם אתם אוהבים ללמוד איך דברים עובדים ולהקים לעצמכם סביבות Deployment יצירתיות ובעלות נמוכה, ולא כזה חשוב לכם שהפרויקט יעבוד בצורה אמינה, Raspberry Pi יהיה הפיתרון הטוב ביותר.

ToCode

23 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-deployment-for-sideprojects

ToCode

22 Oct, 05:15


ניסוי Vue - משיכת מידע מקומפוננטה אסינכרונית
בדומה לריאקט גם ויו כבר תומך בקומפוננטות אסינכרוניות ואצלם כרגיל התחביר יותר פשוט ממה שאנחנו מכירים מריאקט. במקום להוסיף פונקציה סודית שמקבלת Promise, הם פשוט מאפשרים לכתוב את setup בצורה אסינכרונית. עשיתי ניסוי עם קומפוננטה פשוטה כדי לראות איך זה עובד. התוצאה בלינק:

https://asyncvue-nr26xan9r48q.deno.dev/

קוד הקומפוננטה
קוד הקומפוננטה יצא מאוד פשוט - יש לייצא אוביקט שמגדיר פונקציית setup אסינכרונית שמייצאת את אוביקט המידע שיגיע ל template. באופן אוטומטי ויו יפעיל את הפונקציה ורק כשהמידע יהיה מוכן ירנדר את התבנית. זה הקוד:

<template>
<h2>({{ person.id }}) {{ person.name }}</h2>
<p>{{ person.url }}</p>
</template>

<script>
import { ref } from 'vue'

export default {
async setup() {
const person = ref({
id: 2,
name: '',
url: ''
})

try {
await fetch(\https://swapi.dev/api/people/${person.value.id}\)
.then(response => response.json())
.then(data => {
person.value.name = data.name
person.value.url = data.url
})
} catch (err) {
console.error(err);
}


return { person }
}
}
</script>


הקוד שמפעיל
בקומפוננטה שקוראת לקוד האסינכרוני לא צריך בכלל להתייחס לסיפור האסינכרוני בזכות קומפוננטת Suspense, וכן פה זה כבר ממש כמו בריאקט:

<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import Person from './components/Person.vue'
</script>

<template>
<h1>Header</h1>
<suspense>
<person />
</suspense>
<h1>Footer</h1>
</template>


נשים לב שקומפוננטה אסינכרונית יכולה להופיע רק בתוך קומפוננטת suspense, וזה הגיוני כי ה suspense בעצם "מחכה" שה Promise תתבצע כדי להציג את המידע.


מה עוד נשאר
הקוד עובד אבל יש עוד לאן להמשיך בניסוי הזה, לדוגמה אפשר לקבל את ה id מהקומפוננטה החיצונית ולשלוח בקשת רשת נוספת כל פעם שה id משתנה, אבל אם אנחנו כבר עושים את הצעד אולי נרצה להשתמש בספריה כמו vue-query שעושה את זה אוטומטית וגם שומרת cache של בקשות ישנות.

ToCode

22 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-vue-async-fetch

ToCode

21 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-chatgpt-read-open-source

ToCode

21 Oct, 05:15


טיפ LLM - איך זה עובד ב X ?
אחד הקשיים בכניסה לפרויקט קוד פתוח, בין אם בשביל לתרום קוד או בשביל לדבג בעיה נקודתית בשימוש בספריה, הוא שספריות וותיקות הן פשוט ענקיות ולכן יכול לקחת המון זמן להבין איזה חלק בקוד הספריה בכלל קשור לאתגר שלנו. מנועי חיפוש לא ממש יעילים פה, כולל החיפוש המובנה בגיטהאב, כי בעבודה כזאת אין לנו באמת מושג מה מחפשים.

מסתבר שגם כאן Chat GPT וחבריו יודעים לעבוד כמו מנוע חיפוש משודרג ולכוון אותנו לפיתרון. אני ניסיתי את זה עם קלוז'ר ועם ספרייה בשם Reagent. רציתי להבין למה כשאני כותב:

[:p :foo/bar]


ה HTML שנוצר משמיט את התחילית foo ומראה לי על המסך רק את הטקסט bar. רק בשביל קונטקסט באופן כללי בקלוז'ר אפשר להשתמש בפונקציה str כדי להפוך דברים למחרוזות וכתיב כזה:

[:p (str :foo/bar)]


באמת גורם ל Reagent להציג את הטקסט המלא :foo/bar.

וכך הגעתי לקוד של Reagent ולשאלה פשוטה - איפה בתוך ערימת הקוד שם נמצאת הפונקציה שמכינה את האלמנטים לריאקט? ועם זה הלכתי ל Chat GPT ושאלתי את השאלה הבאה:

given reagent code
https://github.com/reagent-project/reagent

what part of the code causes reagent to convert the element's content to HTML, assuming the content is a keyword

that is what part converts the expression [:p :foo/bar] to React's createElement ?


התשובה כללה הפנייה מדויקת לפונקציה שאני צריך שנקראת as-element ולקובץ הרלוונטי שנקרא reagent.impl.template.

אחרי שמצאנו איפה הקוד המעניין אפשר להמשיך לעוד כמה שאילתות כדי להרחיב את הסיפור:

1. Check the commit history of the project and list all commit messages related to this line

2. Check the issues of the project and find me all issues that might have led to this line

ככל שנשתמש ב Chat GPT כמנוע חיפוש נוכל להגיע למידע מעניין ולחסוך זמן יקר, בלי שהוא ינסה כל הזמן לענות בעצמו על השאלות וישקע בהזיות.

ToCode

20 Oct, 05:14


https://www.tocode.co.il/blog/2024-10-problem-solving

ToCode

20 Oct, 05:14


שלושה צעדים לפיתרון בעיה

1. להשתכנע שיש בעיה ולמצוא את הקריטריון לפיתרון - כלומר להבין איך צריך להיראות העולם כשהבעיה פתורה.

2. להבין מה גורם לבעיה.

3. להציע שינויים שיביאו אותנו לפיתרון ולבחור את הפיתרון המתאים ביותר.

דוגמה קלה - יש לי אתר שנטען לאט. צעד ראשון הוא להבין איך אני מודד את זמן הטעינה של האתר, מה זמן הטעינה הנוכחי ומה זמן הטעינה הדרוש. צעד שני הוא להבין איזה חלקים בקוד האתר גורמים לטעינה האיטית והצעד השלישי הוא לשנות אותם כדי שהאתר ייטען במהירות הדרושה. בכל שינוי אני מודד מחדש את זמן הטעינה וכך אני מבין איזה שינויים רלוונטיים ויכול לבחור את השינוי הנכון למקרה שלי.

דוגמה יותר קשה - אף אחד לא מחזיר לי טלפון כשאני שולח קורות חיים למשרות. צעד ראשון הוא להבין מה מודדים פה. זה יותר קשה כי "אף אחד" זה כנראה סובייקטיבי ותלוי לאיזה אנשים שלחת מראש את הקורות חיים. צריך למצוא דרך לאפיין איזה משרה בדיוק אני מחפש, לארגן רשימה של כמה עשרות משרות פוטנציאליות ואז לשלוח לקבוצה של 5 משרות ולראות כמה מהם יחזרו אליי. אחרי שהשתכנעתי שאנשים רלוונטיים לא חוזרים אליי אני יכול לעצור ולהבין, מה האנשים האלה מחפשים, מה הם מצפים למצוא בקורות חיים של מועמדים. אפשר לדבר עם חברים שעובדים בתחום בתור מגייסים או עם נציגי חברות השמה ולהבין מה הקריטריונים שלהם בסינון. אחרי שהבנו את זה יש לנו רשימה של פערים ואפשר לנסות לתקן את הקורות חיים כדי לסגור את הפערים, תוך המשך מדידה תוך כדי התיקון. בדיוק כמו בסיפור של זמן טעינה, יש שינויים שאפשר לעשות יחסית מהר, ויש שינויים שדורשים השקעה גדולה.

הרבה פעמים הבעיות שגורמות לנו הכי הרבה סבל הן בכלל לא בעיות שאפשר לפתור. נסו את המשחק הזה עם "אין לי מספיק כסף בחשבון בנק", "אין לי כח לקום לעבודה" או "אין לי זמן לעשות את הדברים שאני אוהבת" ותראו שמאוד קשה למצוא קריטריון הצלחה או להבין בצורה אובייקטיבית מה גורם לבעיה. אולי הדבר הכי טוב לעשות עם התחושות האלה הוא להבין איזה בעיות אפשר לפתור שגורמות לנו להרגיש כך.

ToCode

19 Oct, 05:14


דברים טובים קורים כשמחכים (נוד 23 ו await)
יש לנו הרגל מוזר בתוכנה, כשאין פיתרון טוב לבעיה אנחנו כותבים קוד שעושה משהו אחר ועל הדרך גם פותר את הבעיה שלנו. רוב הזמן אנחנו גם משלמים על זה מחיר מתישהו בעתיד, אבל לא תמיד יש אופציה טובה יותר.

וכך אני גם קורא את השורה:

await "good things come to those who await"


שעשויה להופיע בתור השורה הראשונה בקובץ JavaScript בתקופה הקרובה. מה שקורה זה ש node 23 יודע לטעון קבצי JavaScript שמשתמשים בכתיב ה import/export בעזרת require, כלומר כשאני כותב בקוד:

const utils = require('./utils.js');


זה הולך לטעון מעכשיו את הקובץ utils.js בין אם הוא משתמש בכתיב import/export או אם הוא משתמש בכתיב CommonJS. נו זה מצוין רק הבעיה שקובץ שכתוב בכתיב ESM עלול לכלול קוד אסינכרוני ברמה העליונה ביותר, ו require לא יכול להריץ קוד אסינכרוני. מה עושים? פה הסיפור קצת מסתבך ובעצם הקוד שכתבתי יטען את utils.js רק אם הוא קובץ CommonJS או אם הוא קובץ ESM שלא מכיל קריאה ל await ברמה העליונה ביותר.

עכשיו נמשיך ונדמיין ספריה חיצונית שכתובה בכתיב ESM ולא מכילה await ברמה העליונה ביותר ונניח שאני טוען אותה מתוך קוד שלי בעזרת require. בעוד כמה זמן כותב הספריה מוציא עדכון שכולל קריאה ל await ברמה העליונה ביותר של אחד הקבצים בספריה או באחת התלויות שלה. בום. הקוד שלי מפסיק לעבוד אחרי שידרוג כי require זורק שגיאה אם הוא מנסה לטעון קובץ ESM שמכיל await ברמה העליונה ביותר.

לכן כותבי ספריות שלא רוצים שיטענו את הספריות שלהם עם require (כדי שאנשים לא יתעצבנו כשתצא גירסה חדשה שכוללת await ברמה העליונה ביותר), יוכלו להוסיף await כזה מזויף, כלומר:

// prevent people from loading this with "require"
// so their code won't break if I add await in some later time
await "good things come to those who await"

export function twice(x) {
return x * 2;
}


ההערה מעל אגב היא ניסוח שלי.

ToCode

19 Oct, 05:14


https://www.tocode.co.il/blog/2024-10-node-require-top-level-await

ToCode

18 Oct, 05:15


כמה מילים על חיבור חברתי ומסך כניסה חדש לאתר
העליתי אתמול גירסה חדשה של מסך הכניסה לאתר עם אפשרות לכניסה דרך גיטהאב או לינקדאין. מוזמנים לנסות את זה כאן (לא כזה מלהיב, אבל עובד):
https://www.tocode.co.il/login

אני משתף כמה הערות טכניות לגבי המימוש:

1. בכל המדריכים שראיתי הסבירו שצריך להוסיף לטבלת המשתמשים עמודה של "provider" ועמודה של "identifier", וכך כשמשתמש נוצר דרך לוגין חיצוני אפשר למלא את הערכים מתוך המידע שקיבלנו מאותו לוגין חיצוני, כדי שבלוגין הבא דרך אותו שירות נוכל לחבר את הפעולות לאותו משתמש. אבל השיטה הזאת לא מספיק טובה כי בעולם שלנו יש כבר משתמשים קיימים באתר והם ישתמשו בלוגין החברתי כדי להתחבר לחשבון הקיים שלהם, וגם אפשר לדמיין משתמשים שיתחברו פעם מגיטהאב ופעם מלינקדאין. לכן במקום להוסיף עמודות יצרתי טבלה חדשה של "זהויות" וכל פעם שמשתמש מתחבר דרך לוגין חיצוני אני מוסיף שורה לטבלת הזהויות עם הפרטים. כל שורה בטבלת הזהויות מחוברת לשורה בטבלת המשתמשים, וכך למשתמש אחד יכולות להיות מספר זהויות.

2. הקוד הבא מופעל אחרי שמשתמש עשה לוגין דרך שירות חיצוני כדי להבין מי המשתמש שהגיע:

def self.create_from_provider_data(auth)
authorization = Authorization.find_by(provider: auth.provider, uid: auth.uid)
return authorization.user if authorization

user = User.find_or_create_by(email: auth.info.email) do |user|
user.name = auth.info.name || auth.info.full_name
user.password = Devise.friendly_token[0, 20]
end

user.authorizations.create(provider: auth.provider, uid: auth.uid)

return user
end


הקוד מחפש שורה בטבלת הזהויות, אם הוא מוצא אז הכל קל ומחזירים את המשתמש שמחובר לאותה שורה. אם לא מצא אז הוא מחפש (או יוצר) משתמש עם האימייל שקיבלנו, יוצר שורה חדשה בטבלת הזהויות ומחבר בין השורה החדשה בטבלת הזהויות למשתמש.

3. שימו לב שהקוד יוצר סיסמה לכל משתמש. זה מעניין כי משתמש שהגיע דרך לוגין חיצוני לא נרשם עם סיסמה. הסיפור כאן הוא של תאימות לאחור, כי בטבלת המשתמשים העמודה של סיסמה אינה יכולה להיות ריקה. זה לא נורא כי זו סיסמה אקראית שאף אחד ממילא לא יודע. אם המשתמש ירצה להתחבר יום אחד עם שם וסיסמה הוא יוכל ללחוץ "שכחתי סיסמה" ולאפס את הסיסמה. אם הייתי מתחיל מאפס את הפיתוח היום כנראה שגם את האפשרות לחיבור עם אימייל וסיסמה הייתי שם בטבלת הזהויות.

4. החיבור עצמו בריילס ללוגינים חברתיים כמעט לא דורש קוד ומבוצע דרך ג'ם שנקרא omniauth.

5. במקור רציתי לפתוח לוגין מגוגל וגיטהאב, אבל גוגל החליטו שאני צריך לאמת את זהותי כדי להשתמש בלוגין שלהם וביקשו מכתב הסבר בצירוף וידאו שאמור להראות מה בדיוק אני עושה עם האימייל שיגיע אליי. בינתיים ויתרתי על התענוג. לינקדאין וגיטהאב אפשרו להגדיר לוגין חברתי בלחיצת כפתור.

6. ובאמת מילה על אתרי הלוגין החברתי - הסיפור זהה בכולם כי כולם משתמשים באותו פרוטוקול: פותחים אפליקציה, מקבלים client_id ו client_secret ומכניסים אותם ליישום שלנו. כן חשוב להגדיר מה כתובת האתר שלנו ביצירת האפליקציה כי הם מוכנים לשים מסך לוגין חברתי רק לאנשים שהגיעו מהאתר שמתאים לאפליקציה שפתחתם. בכל אתר דף ההגדרות בו יוצרים אפליקציה מתחבא במקום אחר, בגיטהאב זה היה ב Developer Settings ובלינקדאין יש פורטל של מפתחים ובו יוצרים אפליקציה. מה שעוד היה מבלבל באתר של לינקדאין זה שצריך לבחור בטאב Products את המוצר Sign In with LinkedIn using OpenID Connect בשביל שהלוגין יעבוד.

סך הכל בזכות omniauth כל החיבורים עבדו יחסית חלק ובזכות טיילווינד הארגון מחדש של מסך הכניסה גם היה פשוט.

ToCode

18 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-new-login-page

ToCode

17 Oct, 05:15


השתמשו באיזה ספריות שתרצו
ראיתי את המשפט הזה באיזה משימת בית בריאקט שמישהו פרסם, משהו כמו "כתבו קומפוננטה שמציגה רשימה של נתונים מחולקים לעמודים וכפתורים לדפדף קדימה ואחורה. השתמשו באיזה ספריות שתרצו".

הבעיה עם "איזה ספריות שתרצו" היא שזו הסחת דעת. יש אנשים שמכירים ספריה או שתיים שבדיוק פותרות את הבעיה ויסיימו מהר את המשימה; יש אחרים שמכירים ספריות שלא הכי מתאימות אבל יעשו מאמץ להתאים אותן למשימה וייכשלו או יסתבכו; אחרים יחפשו בגוגל ספריות ויבזבזו את כל זמן המשימה על חיפוש הספריה הטובה ביותר ועוד אנשים יפספסו לגמרי את הרעיון של ספריות ויכתבו הכל בריאקט בלי ספריות הרחבה, מה שיגרום לקוד שלהם להיראות יותר ארוך או שהמשימה תיקח להם יותר זמן.

שתי אפשרויות יותר טובות לדעתי למשימות בית:

1. אין להשתמש בספריות הרחבה - תכתבו את קוד התקשורת לבד בריאקט ואת העיצוב ב CSS או style. ברור שייקח יותר זמן ולא בטוח שנראה את המוצר הכי מעוצב, אבל אולי נרוויח שיחה מעניינת על הקוד.

2. השתמשו רק בספריות X, Y ו Z - פה יש סוג של ייתרון לאנשים שכבר מכירים מראש את הספריות שבחרנו למשימה, אבל רוב הזמן בפרונט אנד קל לאנשים להשתמש בספריות רלוונטיות גם אם לא עבדו איתן בעבר. כלומר מי שעבד עם react-query יצליח להסתדר מהר עם swr. מי שעבד עם emotion יצליח להסתדר עם styled components וכו. ברור אל תבחרו פה רידאקס או ספריות שקשה ללמוד, אלא אם כן אתם ספציפית מחפשים לגייס אנשים שמכירים ספריות אלה.

יותר מדי גמישות יכולה לעבוד לרעתכם, גם במשימות בית.

ToCode

17 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-use-any-libraries-you-want

ToCode

16 Oct, 05:15


היום למדתי: מערכת הקבצים הסודית של הדפדפנים
נמאס לכם מהמגבלה של 5 מגה של local storage? רוצים לכתוב ולקרוא מהר לקבצים שיישמרו אבל אתם תקועים בתוך דפדפן? מסתבר שיש פיתרון יחסית חדש ולא מסובך שנקרא Origin private file system או בקיצור OPFS. מנגנון זה מספק לנו משהו שעובד בדיוק כמו מערכת קבצים אבל סגור בתוך הדפדפן. כרום אצלי על המחשב נתן לי Quota של 500 ג'יגה אפילו בלי לבקש רשות.

דוגמה 1 - כתיבת קובץ
הממשק זמין דרך האוביקט navigator.storage, ובשביל לראות איך זה עובד הלכתי ל ChatGPT וביקשתי שתי דוגמאות. דוגמה ראשונה כותבת קובץ לדיסק:

async function create1MBFileInOPFS() {
// Request a handle to the OPFS root directory
const rootDir = await navigator.storage.getDirectory();

// Create a new file in OPFS
const fileHandle = await rootDir.getFileHandle('1MB_text_file.txt', { create: true });

// Create a writable stream
const writableStream = await fileHandle.createWritable();

// Generate 1MB of text data (1 character = 1 byte for plain text)
const sizeInBytes = 1024 * 1024; // 1MB
const largeText = 'A'.repeat(sizeInBytes); // A string with 1MB of 'A' characters

// Write the text data to the file
await writableStream.write(largeText);

// Close the writable stream to save the file
await writableStream.close();

console.log('1 MB text file created in OPFS');
}


נקרא את זה יחד -

1. הפקודה getDirectory נותנת לי נקודת כניסה ל storage.

2. הפקודה getFileHandle מחברת אותי לקובץ, והאופציה create אומרת אם ליצור את הקובץ במידה ולא קיים.

3. הפקודה createWritable מחזירה זרם לכתיבה לקובץ.

4. הפקודה write של זרם הכתיבה מקבלת מחרוזת וכותבת אותה לקובץ.

5. בסוף מפעילים close כדי לסיים את העבודה עם הקובץ.

כל הפונקציות הן אסינכרוניות ועובדות גם מ Web Worker.

דוגמה 2 - הצגת רשימת קבצים
הדוגמה השניה מציגה את רשימת הקבצים:

async function listFilesInOPFS() {
// Request a handle to the OPFS root directory
const rootDir = await navigator.storage.getDirectory();

// Iterate over the entries in the root directory
for await (const [name, handle] of rootDir) {
if (handle.kind === 'file') {
console.log(\File: ${name}\);
} else if (handle.kind === 'directory') {
console.log(\Directory: ${name}\);
}
}
}

// Call the function to list files in the root directory
listFilesInOPFS();


שוב משתמשים ב getDirectory כדי להיכנס לתיקייה ואז אפשר לרוץ בלולאה על אותה התיקיה כדי לקבל את הקבצים והתיקיות שבתוכה. כל דבר שהוא מסוג directory מאפשר איטרציה עם for כך שאפשר היה להמשיך רקורסיבית ולהדפיס את כל הקבצים והתיקיות על הדיסק הוירטואלי.

נשים לב-

1. הדיסק הוירטואלי מהיר ומספר המון מקום עבודה. זה טוב אם אתם בונים מערכת ווב וצריכים לשמור הרבה מידע שיהיה זמין אופליין.

2. הדיסק הוירטואלי ספציפי לדומיין, בדיוק כמו עוגיות ו local storage.

3. אחריות שלכם לנקות את הקבצים עם פקודת removeEntry כשאתם כבר לא צריכים אותם.

4. אפשר לראות את הגודל שתופסים כל הקבצים במסך כלי הפיתוח בטאב application. לא מצאתי במסך כלי הפיתוח דרך לשוטט בתיקיות הוירטואליות או בתוכן הקבצים עצמם. כן אפשר לעשות את זה מתוך קוד JavaScript כל עוד אתם בדומיין שיצר את הקבצים.

ToCode

16 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-opfs

ToCode

15 Oct, 05:15


ה vimrc שלי לפיתוח ריילס
הרומן שלי עם vim הוא מחזורי - פעם בכמה זמן אני מרוקן כמעט לגמרי את ההגדרות, לאט לאט אני מוסיף פלאגינים והגדרות עד שהוא מתנפח כל כך שאני לא מצליח להשתמש בו ואז אני מרוקן שוב כדי לחזור לבייסיקס. השבוע חזרתי לבייסיקס וזה קובץ ה vimrc הכי קטן שבניתי שעדיין מספיק לי בשביל לכתוב ריילס:

call plug#begin()

" List your plugins here
Plug 'tpope/vim-sensible'
Plug 'tpope/vim-rails'
Plug 'ctrlpvim/ctrlp.vim'
Plug 'preservim/nerdtree'
Plug 'nanotech/jellybeans.vim'

call plug#end()

let mapleader = ","

syntax on
filetype on
set number

colo jellybeans

set hidden
set shiftwidth=2
set expandtab
set tabstop=2
set wildmenu

set incsearch
set hlsearch
set ruler
set smartindent

nnoremap <silent> <C-l> :noh<cr>

let g:ctrlp_custom_ignore = '\v[\/]\.(git|hg|svn)$|node_modules\/'

nnoremap <Leader>n :NERDTreeToggle<cr>


הסבר בקצרה:

1. הפלאגין vim-rails הוא קסום ונותן המון קיצורי מקלדת לעבודה עם ריילס. לקח לי שנים להכיר אותו ואני לא מוותר עליו.

2. פלאגין ctrl-p מחפש בקבצים מהר, ו nerdtree פותח עץ תיקיות. כן יש יותר חדשים מהם אבל שניהם עובדים לי טוב. שימו לב שלקראת סוף הקובץ אני אומר ל ctrl-p ממה להתעלם וממפה את ההפעלה המהירה של Nerdtree.

3. ג'ליבינס זו ערכת צבעים מוצלחת.

4. כפתור הלידר הוא פסיק, כי אני רגיל.

5. חיפוש אינקרמנטלי עם צבעים מגיע מ incsearch ו hlsearch והמיפוי של Ctrl L עוזר לנקות את ההדגשה של החיפוש.

אין השלמות אוטומטיות או לינטינג בינתיים אבל בטח אוסיף בהמשך. התקנת פלאגינים מבוצעת עם פלאג אותו התקנתי בנפרד מכאן:

https://github.com/junegunn/vim-plug

יש לכם טיפים והגדרות וים שאתם לא יכולים לחיות בלי? שתפו בתגובות אולי אוכל לאמץ כמה רעיונות.

ToCode

15 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-basic-vimrc

ToCode

14 Oct, 05:15


https://www.tocode.co.il/blog/2024-10-ai-mistakes

ToCode

14 Oct, 05:15


ואם מחשבון היה טועה?
ישבתי לכתוב קטע קוד היום וראיתי שאני מסתבך אז קראתי לחבר טלפוני, כלומר ל Chat GPT. מהצד שלי זה נראה ככה-

1. הי AI אני צריך קוד שעושה X

2. תודה! אבל הקוד שהדפסת לא נראה יעיל במיוחד. יכול להציע 5 רעיונות אחרים?

3. מספר שלוש נראה טוב זה בגדול הכיוון שרציתי לקחת. בבקשה תתקדם איתו.

4. שמע זה כמעט עבד אבל הראה תוצאה לא נכונה. הנה ה Data עליו הרצתי. רואה את הבעיה?

5. תודה! זה עובד מעולה.

מצד אחד יצאתי עם קוד שנראה עובד. מצד שני ואולי היותר חשוב, הפסדתי את ההזדמנות לפתור את הבעיה בעצמי, להיתקע וללמוד. אבל רגע, מה בעצם ההבדל בין זה לבין מחשבון? אתה לא מציע כאן לבצע חישובים מסובכים על דף רק בשביל ה"חוויה" או ה"מיומנות" של פיתרון תרגילי חילוק ארוך?

ובאמת הפוסט הזה הוא יותר שאלה מתשובה. ה AI בכל מקום, אני יכול להריץ אותו מקומית ולגמרי בחינם, והוא חוסך המון המון זמן. מצד שני ובניגוד למחשבון התוצאה שמקבלים מ AI לא תמיד נכונה, ואפילו כשהיא עובדת לא ברור עד כמה היא טובה. בכתיבת קוד אנחנו עדיין הרבה יותר מודעים לכל סימן מאשר בקריאה והעתקה.

אולי הפיתרון הכי טוב כרגע הוא לדבר עם ה AI ואז ללכת לכתוב את הקוד מאפס ב IDE. ואולי מה שצריך זה להתרגל לדבג הרבה יותר, כי לאורך זמן אותם קודים שרוב הזמן עובדים הולכים להרכיב את רוב המערכות שלנו בעתיד.