ToCode
رفتن به کانال در Telegram
1 419
مشترکین
+124 ساعت
اطلاعاتی وجود ندارد7 روز
اطلاعاتی وجود ندارد30 روز
آرشیو پست ها
1 421
# שימוש חוזר בקוד פייתון באמצעות Decorator
השבוע בשיעור שבכלל לא דיבר על Decorators יצא לי לכתוב עם התלמידים קוד עבור מפענח שקורא הוראות מקלט חיצוני ומבצע אותן לפי הסדר. המפענח שלנו החזיק "זיכרון" וכל הוראה יכלה לקבל פרמטר מספרי או פרמטר שהוא אות, כאשר אם קיבלנו אות היינו צריכים לקרוא את הערך שמתאים לאות זו מהזיכרון.
לדוגמא המפענח תומך בפעולת set ששומרת ערך לזיכרון. אפשרות אחת היא להעביר ל set מספר:
set a 10
ואז בתא a בזיכרון נשמור את המספר 10. אפשרות שניה היא להעביר ל set אות ואז נצטרך לקרוא מהזיכרון את הערך בתא המתאים, לדוגמא:
set a 10
set b a
ישמור בתא a את הערך 10 ובתא b את הערך ששמור עכשיו ב a, כלומר גם 10.
בקיצור יצא שכתבנו קוד עם אלמנט חזרתי שנראה בערך כך:
class MyParser:
# ...
def set(self, reg, val):
ival = self.read_register_or_value(val)
self.memory[reg] = val
def sub(self, reg, val):
ival = self.read_register_or_value(val)
self.memory[reg] -= ival
def mul(self, reg, val):
ival = self.read_register_or_value(val)
self.memory[reg] *= ival
כל פונקציה משתמשת בפונקציית עזר כדי לבדוק אם הערך שקיבלה הוא מספר או אות, ואם אות אז לקרוא מהזיכרון את הערך המתאים.
הקוד עובד והשימוש החוזר בפונקציית העזר באמת משפר את המצב בהשוואה לכתיבת הלוגיקה המלאה שלוש פעמים אבל עדיין משהו כאן צורם: כל פונקציה צריכה להגדיר משתנה חדש מה שמבזבז לנו שם, והמבנה של שורה ראשונה בפונקציה שמוקדשת להכנת המשתנים עלול לשעמם כשרואים אותו שוב ושוב.
דרך אחת להפוך את הקוד לקצת יותר נעים לעין היא להמיר את הקריאה היזומה בקריאה ל Decorator:
class MyParser:
@read_value_from_register
def set(self, reg, val):
self.memory[reg] = val
@read_value_from_register
def sub(self, reg, val):
self.memory[reg] -= val
@read_value_from_register
def mul(self, reg, val):
self.memory[reg] *= val
כל מה שצריך בשביל המעבר למבנה החדש הוא להגדיר את הפונקציה read_value_from_register מחוץ למחלקה בתור Decorator:
def read_value_from_register(f):
def inner(self, reg, val):
ival = self.read_register_or_value(val)
f(self, reg, ival)
return inner
מעניין לשים לב שבמקרה כזה הפונקציה f שעוברת כפרמטר היא עדיין לא Object Method אלא פונקציה פשוטה. היא עדיין לא קשורה לאוביקט מאחר וה Decorator נקרא לפני שנוצרו אוביקטים מסוג MyParser. מסיבה זו בהפעלת הפונקציה אנחנו חייבים להעביר את self בצורה יזומה בתור הפרמטר הראשון. מלבד תשומת הלב המיוחדת ל self, הגדרת Decorator למתודה (שנמצאת בתוך מחלקה) זהה לחלוטין להגדרת Decorator לפונקציה רגילה.1 421
# שלוש דוגמאות קטנות לעבודה עם בסיסי נתונים בפייתון
בפייתון PEP 249 מאפיין את הממשק הרצוי לדרייברים לבסיסי הנתונים השונים בפייתון. בואו נראה 3 דוגמאות לעבודה עם בסיס נתונים SQLite כדי לקבל מושג איך הממשק הזה נראה.
## יצירת בסיס נתונים וטבלאות
התוכנית הראשונה יוצרת בסיס נתונים מסוג SQLite ובתוכו יוצרת שתי טבלאות:
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('CREATE TABLE people (name text, email text)')
c.execute('''CREATE TABLE stocks
(date text, trans text, symbol text, qty real, price real)''')
conn.close()
הפקודה sqlite3.connect מתחברת לבסיס נתונים והיא הפקודה שתיראה שונה בין בסיסי נתונים שונים: בעבודה עם sqlite אנחנו מפעילים אותה מהמודול של sqlite ומעבירים את הפרמטרים שרלוונטים למודול זה, ובעבודה עם בסיסי נתונים אחרים נעביר פרמטרים אחרים. רוב הפקודות האחרות יעבדו אותו הדבר בין בסיסי נתונים.
הפקודה cursor יוצרת צינור (נקרא cursor) לבסיס הנתונים דרכו אפשר להריץ שאילתות, לבצע פעולות ולקבל את התשובות שלהן. בתוכנית הפעלתי שתי פקודות יצירת טבלא באמצעות execute ובסוף סגרתי את החיבור.
## הכנסת מידע לבסיס הנתונים
התוכנית הבאה מכניסה שלוש שורות לבסיס הנתונים לטבלת People:
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
# Larger example that inserts many records at a time
people = [
('one', 'one@gmail.com'),
('two', 'two@gmail.com'),
('three', 'three@gmail.com')
]
c.executemany("INSERT INTO people VALUES(?, ?)", people)
conn.commit()
c.close()
conn.close()
שתי הפקודות החדשות בתוכנית הן executemany ו commit. הפקודה executemany מקבלת שאילתה ומערך של "ערכים למילוי" ומפעילה את השאילתה עם כל אחד מערכי המילוי מהמערך. אפשר להשתמש גם ב execute בשביל להכניס מידע לבסיס הנתונים ואז במקום להעביר מערך נעביר Tuple יחיד של ערכי המילוי. הפקודה commit היא ששומרת בסוף את המידע וחייבים לקרוא לה לפני שסוגרים את החיבור.
## שליפת מידע מבסיס הנתונים
התוכנית השלישית שולפת את המידע ופשוט מדפיסה אותו על המסך:
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
name = 'one'
query = c.execute("SELECT * FROM people WHERE name = ?", (name,))
results = query.fetchall()
for row in results:
print(row)
c.close()
conn.close()
הפקודה execute כמו שכבר הבנתם מקבלת Tuple עם ערכים להחלפה (במקום הסימן שאלה), והפקודה fetchall מפעילה את השאילתה ומחזירה לנו אוביקט תוצאות. בעזרת אוביקט התוצאות נוכל לעבור שורה שורה על התוצאות ולהדפיס כל אחת. בעבודה עם סטים גדולים של תוצאות הרבה פעמים נרצה להשתמש ב fetchmany ולקבוע את גודל המנה. זה יראה כך:
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
name = 'one'
query = c.execute("SELECT * FROM people")
while True:
results = query.fetchmany(2)
if len(results) == 0: break
for row in results:
print(row)
c.close()
conn.close()
לסיכום פייתון מספקת חיבור נוח וטבעי לבסיסי נתונים שונים בממשק אחיד שלהרבה תוכניות יהיה מספיק טוב. לפרטים נוספים שווה לקרוא את PEP 249 עצמו או את דף התיעוד על המודול sqlite3 שמגיע יחד עם פייתון.1 421
# פרילאנסרים וכלכלת החלטורה
כולם במדיה אוהבים לדבר על הקשיים בכלכלה החדשה ובפרט בתחום שנקרא "כלכלת החלטורה". כלכלת החלטורה זה מושג שמתאר את הסביבה הכלכלית שמחברת בין לקוח לנותן שירות באמצעות אפליקציה או פלטפורמה מרכזית, והיא מתאפיינת בכך שנותני השירות מקבלים יחס של "בורג במערכת" שאפשר תמיד להחליפו באחר.
כך כשנזמין מונית דרך Get Taxi אין לנו דרך (וגם לא ממש מעניין אותנו) לבחור מי יהיה הנהג בנסיעה הבאה ובאופן אוטומטי האלגוריתם מחבר לנו נהג שרוצה את הנסיעה ונמצא באזור. כשאנחנו מזמינים אוכל מ-וולט לא ממש מעניין אותנו מי השליח ובאופן אוטומטי האלגוריתם מחבר את השליח לנסיעה. גם בעסקים גדולים יותר כמו חיפוש פרילאנסר ב XPlace הרבה פעמים אנחנו מחפשים פרילאנסר עבור עבודה מסוימת וההעדפה שלנו נקבעת לפי מערכת הדירוגים של הפלטפורמה ואלגוריתם החיפוש שלה. בצורה כזו פרילאנסרים מתקשים לבלוט ומקיימים סוג של מירוץ לתחתית: מי יהיה הפרילאנסר שייתן את הפיתרון במחיר הנמוך ביותר. והבעיה במירוץ לתחתית היא שאתה עלול לנצח, או יותר גרוע עלול להגיע שני.
פירסומים רבים בעיתונות מחברים בין פרילאנס לכלכלת החלטורה באופן שעלול לתת לקורא את הרושם שזו הדרך היחידה או המרכזית להתפרנס בתור פרילאנס. הנה אחד מתוך גלובס:
> במסגרת כלכלת החלטורה, בקרוב מאוד, מרבית האוכלוסייה הכשירה להימנות עם שוק העבודה המודרני, תקיים שוק שברובו מושתת על עובדים זמניים, עובדים במשרה חלקית או עצמאיים.
ומתוך דה מרקר:
> "כלכלת החלטורה" שצוברת תאוצה מאז המשבר העולמי, מבוססת על נותני שירותים שאינם זוכים לקבל את מלוא הזכויות התעסוקתיות, כך שלא נשמרים עקרונות יסוד עליהם מושתת משפט העבודה המגן בישראל. כפועל יוצא, עשויה כלכלת החלטורה לערער את הביטחון הסוציאלי והתעסוקתי של אוכלוסיות שלא היו חשופות לסיכונים אלה בעבר.
וכך מתכנתים רבים שרוצים לעבור להיות פרילאנסרים יתחילו בפתיחת פרופיל ב XPlace, וגם אני כשרציתי להתחיל לעבוד כפרילאנס חיפשתי עבודה בתור פרילאנסר שעובד תחת תיווך של חברת ייעוץ או חברת הדרכה גדולה. כשאנחנו רק מתחילים העולם האמיתי נראה גדול ומלחיץ ורישום לפלטפורמה קיימת נראה כמו הזדמנות טובה להתחיל לקבל עבודות. הבעיה בעבודה עם פלטפורמות היא שאנחנו ממשיכים להרוויח מיום ליום ולאורך זמן לא בונים את הנכסים שיעזרו לנו להשתפר ולהתקדם בתור פרילאנסרים.
הדרך החוצה ממירוץ הפלטפורמה היא לא לקבל דירוג טוב יותר בפלטפורמה - אלא היא לבנות את הנכס או הפלטפורמה שלכם: ספריית קוד פתוח פופולרית שתביא לכם לקוחות, קשרים עם בכירים בתעשיה שסומכים עליכם וישמחו לתת לכם פרויקטים איכותיים, מוצר תוכנה שמכניס לכם קצת כסף ומביא איתו חשיפה שתעזור לפתוח דלתות בעתיד. ככל שנשקיע יותר בבניית נכסים כך נוכל לקצור את הפירות לאורך זמן בשדות שלנו.
1 421
# ביטויים רגולאריים ואנאגרמות... אוי ויי
לעולם לא אשכח את ההתרגשות שלי ביום ה 4/12/2017 כאשר בשעה שבע בבוקר אריק ווסטל פתח את החידה הרביעית של Advent Of Code אותה שנה. הסיבה למסיבה היתה תוכן החידה, שבמבט ראשון נראה לי כמו הזדמנות נפלאה לכתוב ביטויים רגולאריים ולהיכנס לרשימת הפותרים המהירים של התחרות.
החידה נשמעת בערך כך - בהינתן רשימת שורות קלט ספרו כמה שורות אינן כוללות מילה שמופיעה פעמיים. עכשיו אני שומע מילה שמופיעה פעמיים וישר מבין שסחבק מדבר על הביטוי הרגולארי:
\b(\w+)\b.*\b\1\b
שזה בעברית "תמצא מילה, שמור אותה בצד, תמשיך קדימה ותחפש את אותה מילה שוב". שורות הקלט שמתאימות לביטוי זה מכילות את אותה מילה פעמיים ואנחנו רק רוצים לספור את שורות הקלט שלא מתאימות לביטוי. בפייתון הקוד לפיתרון יראה כך:
import fileinput, re
count = 0
pat = re.compile(r'\b(\w+)\b.*\b\1\b')
with fileinput.input() as file:
for line in file:
if not pat.search(line):
count += 1
print(count)
זה לקח שלוש דקות לכתוב ואני ממשיך בריצה לחלק השני של החידה, ושם - בום.
מסתבר שעכשיו אריק רוצה שנספור כמה מהשורות לא כוללות שתי מילים שהן אנאגרמה, כלומר שתי מילים שמורכבות מאותן אותיות.
ובאותו 4 לדצמבר 2017 הרגשתי שאין סיכוי להיכנס לרשימת הפותרים המהירים. שהנה אני צריך למחוק את הכל ולשנות גישה, שהרי ביטויים רגולאריים לא יודעים לחפש אנאגרמות... נכון?
זה לקח לי כמה ימים להבין שאין סיבה למחוק את כל הקוד ושאפשר בשינוי ממש קטן בתוכנית לקבל גם את מספר השורות בהן אין שתי מילים שהן אנאגרמה אחת של השניה. כל מה שצריך לעשות זה לסדר את האותיות בכל מילה לפי סדר האלף-בית כך שאם בשורה היה כתוב המשפט:
dad can add
אנחנו נשנה את המשפט ל:
add acn add
שינוי כזה של האותיות בכל מילה יאפשר לנו להשתמש שוב בביטוי הרגולרי שכבר כתבנו. ובגלל שאנחנו בפייתון ומדברים על ביטויים רגולאריים - עדכון הקוד כך שיחליף את סדר האותיות בכל מילה יכול להתבצע גם הוא באמצעות ביטוי רגולארי, הפעם עם החלפה:
import fileinput, re
count1 = 0
count2 = 0
pat = re.compile(r'\b(\w+)\b.*\b\1\b')
with fileinput.input() as file:
for line in file:
if not pat.search(line):
count1 += 1
line2 = re.sub(
r'\b(\w+)\b',
lambda m: ''.join(sorted(m.group(1))),
line)
if not pat.search(line2):
count2 += 1
print(count1)
print(count2)
אגב תחרויות במקור זה לקח לי רבע שעה להבין, לפתור ולהגיש את החלק הראשון ואחריה עוד שעה להתאושש מההלם של החלק השני ולפתור אותו. החבר'ה הרציניים של AoC פתרו את שני החלקים תוך דקות ורשימת 100 הפותרים הראשונים נסגרה אחרי 3 דקות וארבעים שניות. מזל שאני ממילא לא טיפוס תחרותי.
אה וכמובן עוד שבוע וחצי יש סידרת חידות חדשה עם AoC 2019. אני אנסה לעמוד בקצב ולשתף חידות ופיתרונות מעניינים מהסידרה החדשה במהלך דצמבר.1 421
# שעה ביום
לרוב האנשים קל למצוא 5 דקות ביום, עשר בלחץ, אבל שעה ביום!? זה המון. קל ליפול למלכודת ולהסתכל על אנשים שכן מצליחים להשקיע בדבר הזה שאנחנו רוצים כאילו אם רק הייתי כמוהם הייתי מוצא את הזמן-
רק אנשים ספורטיביים מצליחים למצוא את הזמן לעשות כושר.
רק מתכנתים שעובדים בחברות רציניות מצליחים למצוא את הזמן לכתוב בדיקות יחידה.
רק ל CTO יש זמן ללמוד טכנולוגיות חדשות כל היום.
רק יועצים טכנולוגיים מצליחים למצוא זמן לכתוב בלוג מקצועי.
אבל האמת שיותר מועיל לחשוב על זה הפוך: כשאצליח למצוא את הזמן ללמוד שעה ביום Python, לאורך זמן אפתח את הראש לשיטות עבודה חדשות וכך לאורך זמן אהיה מתכנת טוב יותר ותהיה לי יותר גמישות להמשיך וללמוד בזמן שלי. ואם אתם מוכנים להאמין לי על זה, כל מה שנשאר בשביל להיות הדבר שאתם רוצים להיות זה למצוא את השעה האחת הזאת ביום.
1 421
# בואו נלמד שפה חדשה עם Python
הכי כיף לשלב בין הידע שלכם בתכנות לחיים האמיתיים ולכתוב כלים קטנים שעוזרים בכל מיני משימות של היום יום. בפוסט היום נכתוב כלי קטן של כרטיסיות מילים שיקרא רשימת כרטיסיות מילים מקובץ ויבקש מאתנו לתרגם מילה בין שתי שפות. בדרך נלמד גם קצת פייתון.
## מבנה הקלט
קובץ כרטיסיות המילים צריך להיות פשוט. בעתיד אולי אפילו ניצור אחד באופן אוטומטי עם גוגל תרגום, אבל בינתיים פשוט לקחתי כמה מילים באנגלית וצרפתית ויצרתי מהן קובץ טקסט שנראה כך:
mom / maman
dad / papa
hello / salut
island / île
בגלל שאנחנו בעסקי השפות חשבתי שיהיה נחמד לצאת מעולם ה ascii ובגלל זה המילה "אי" נכנסה לרשימה. בכל מקרה אתם יכולים לשחק עם זה ולבחור את המילים שאתם רוצים להתאמן עליהן בשפה שאתם לומדים. הסימן / כדי להפריד בין המילה לתרגום שלה עדיף על רווח כי לפעמים נרצה להתאמן על תרגום משפטים של כמה מילים.
## קריאת הקלט למילון ב Python
עם המודול fileinput אפשר יהיה לקרוא את הקלט למילון בזיכרון יחסית בקלות - המודול ייתן לנו לרוץ על כל השורות בקובץ, ובעזרת split אשבור כל שורה למילים באנגלית ובצרפתית. הדבר היחיד שחשוב לזכור כאן הוא לקרוא ל strip כדי לא להיתקע עם רווחים בתוך המילה:
# Create the data
with fileinput.input("flashcards.txt") as f:
for line in f:
[english, french] = [w.strip() for w in line.split('/')]
words[english] = french
## בחירת מילה אקראית מהמילון
עכשיו שכל המילים בזיכרון אפשר להמשיך לבחור מילה אקראית בשביל המשחק שלנו - הפונקציה random.choice מחזירה אלמנט אקראי מתוך סידרה. הבעיה שהיא לא עובדת על מילונים ולכן עלינו לקחת את word.items, להפוך אותו לרשימה ואותה לשלוח ל random.choice. אני רוצה לתמוך גם בתרגום מאנגלית לצרפתית וגם מצרפתית לאנגלית לכן אני מוסיף עוד משתנה בוליאני שיגדיר את סוג המשחק:
# Show a flashcard
word, translation = random.choice(list(words.items()))
reverse = random.choice([True, False])
if reverse:
word, translation = translation, word
## הצגת המילה אות-אחרי-אות בהמתנה לתרגום
עכשיו אפשר לצאת לדרך - נציג את המילה לתרגום ונחכה שהמשתמש יכתוב את התרגום שלה. אם הוא טעה נציג לו אות אחת מהמילה כדי להזכיר, בטעות הבאה נוסיף עוד אות וכך עד שנציג בסוף את כל המילה. בגלל שכל פעם יש הזדמנות להצליח, אני יכול להשתמש במבנה נחמד של Python של else על לולאה: במבנה כזה ה else יופעל רק אם לא היה break בתוך הלולאה, כלומר רק אם המשתמש לא הצליח לנחש בצורה נכונה את המילה.
החלק האחרון של התוכנית לכן נראה כך:
print(word)
for i in range(1, len(translation) + 1):
guess = input('> ')
if guess == translation:
print('Bravo!')
break
print(translation[0:i])
else:
print("Sorry, the answer was", translation)
ובפחות מ-30 שורות קיבלתם משחק זיכרון למילים שיעזור לכם ללמוד כל שפה בעולם.1 421
# זה עוד רחוק?
לימודים מסודרים עובדים בגלל שיש להם מנגנון של וודאות: תסיים בית ספר בציונים גבוהים ויקחו אותך לאוניברסיטה; תסיים אוניברסיטה בציונים גבוהים ויתנו לך עבודה; מדעי המחשב לוקח שלוש שנים ללמוד, הנדסה ייקח 4 ורפואה 7. אם את בשנה שניה במדעי המחשב יש לך עוד שנה של לימודים עד שתמצאי עבודה. זה נפלא וגורם לאנשים להשקיע.
ואנחנו משקיעים אפילו שאנחנו יודעים שזה לא ממש עובד, או לא עובד יותר, או לא עובד כמו בסיפורים (או שאולי אף פעם לא עבד). אפילו שאנחנו מכירים המון אנשים שסיימו אוניברסיטה ולא מצאו עבודה או שהתקשו למצוא, ואני לא מדבר בכלל על סיום של קורסים מקצועיים או מכללות שם הכל אפילו יותר קשה. כשאתה בתוך התהליך הוודאות שהנה עוד חצי שנה נגמר הקורס או עוד שנה נגמר התואר היא משהו שכיף להיאחז בו, והיא גורמת לך לעבוד יותר קשה ולהתמיד.
המעבר ללימוד עצמי הוא קשה כי בלימוד עצמי אף אחד לא מבטיח לי שום דבר. אולי ייקח לי חודש ללמוד פייתון, אולי חודשיים ואולי שנה. זאת תחושה שקשה מאוד לחיות איתה.
ולכן המפתח להצלחה בלימוד עצמי נקרא אמון. לא אמון במערכת (שבינינו מצליחה לקיים רק במקרים נדירים), אלא אמון בעצמי. אמון שעוזר להירגע ולשים בצד את השאלות כמו "זה עוד רחוק?", "מתי זה יצליח?", "מתי אמצא כבר עבודה?" או "מתי אנשים יתחילו לקנות את המוצר שלי?".
כי כל השאלות האלה מסיחות את הדעת מהדבר האמיתי שצריך לעשות עכשיו - שזה להמשיך לכתוב ולמחוק קוד עד שהוא יעבוד.
1 421
# איך לשמור מערך בסטייט של React Component
אנחנו יודעים ש useState מקבל ערך ראשוני של סטייט ואחרי זה בכל קריאה נוספת מחזיר את הערך המעודכן ביותר. הבעיה ש useState מתנהג קצת מצחיק כששומרים בו דברים כמו מערך או אוביקט, דברים שיכולים להשתנות. בואו נראה מה הבעיות ואיך בכל זאת לשמור מערך ב State.
## למה שנרצה לשמור מערך בסטייט
הרבה פעמים אפשר להתחמק משמירת מערך ופשוט לשמור כמה שדות שונים, אבל זה לא תמיד הגיוני. בואו ניקח כדוגמא רשימה של אלמנטים שמאפשרת לבחור ולסמן קבוצה של אלמנטים מהרשימה. אולי אפילו יהיה כפתור של מחיקה כדי למחוק את כל מה שבחרנו. בכל מקרה בשלב ראשון אסתפק ברשימה שתציג לי למטה איזה אלמנטים נבחרו.
הנה נקודת ההתחלה עם פקד שמציג רשימת פריטים שאפשר לבחור ממנה:
function SelectableList(props) {
const { items } = props;
return (
<ul>
{items.map(item => (
<li key={item} style={{ direction: "rtl" }}>
<label>
{item}
<input type="checkbox" />
</label>
</li>
))}
</ul>
);
}
הרשימה עובדת ועכשיו אני רוצה להוסיף למטה את רשימת כל הפריטים שבחרנו. פה החיים מתחילים להסתבך.
## הבעיה עם שמירת מערך בסטייט
נוסיף לסטייט מערך של הפריטים שנבחרו ונעדכן את המערך כל פעם שמשתמש בוחר או מבטל בחירה. ניסיון ראשון עשוי להיראות ככה אבל זה לא עובד כלל:
<iframe
src="https://codesandbox.io/embed/zealous-rhodes-hjrdy?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="zealous-rhodes-hjrdy"
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
></iframe>
הפונקציה selectItem נקראת ומעדכנת את המערך - וכאן בדיוק הבעיה. אחרי העדכון אנחנו קוראים ל setSelectedItems עם אותו מערך בדיוק. במילים אחרות למרות שתוכן המערך השתנה, אוביקט המערך עצמו שנשמר בתוך הסטייט לא השתנה ולכן ריאקט מניח שמותר לדלג על ה render הזה.
## איך בכל זאת לשמור מערך בסטייט
המסקנה שכדאי להתיחס למידע שאנחנו שומרים בסטייט בתור Immutable כלומר מידע שאסור לשנות את התוכן שלו ורק להחליף אותו כשצריך משהו חדש. מחרוזות ומספרים הם כאלה באופן טבעי ב JavaScript, אבל בעבודה עם סוגי מידע אחרים נדרשת זהירות נוספת כדי לא לשנות אותם בטעות.
למזלנו ברגע שאנחנו מבינים את הבעיה קל מאוד לתקן אותה - פשוט יוצרים מערך חדש במקום לעדכן את הקיים (ובאופן כללי תמיד נבחר ליצור אוביקט חדש במקום לעדכן את הקיים כשמדובר במשתנים ששמורים בסטייט).
בעבודה עם מערכים הפונקציות concat ו filter יצרו מערך חדש, בעוד הפונקציות splice ו push יעדכנו את הקיים. הנה הקוד המעודכן עם concat ו filter:
<iframe
src="https://codesandbox.io/embed/amazing-euler-odg52?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="amazing-euler-odg52"
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
></iframe>1 421
# דוגמת ריפקטורינג ב React עם Custom React Hook
הסיבה המרכזית לדעתי לעבור ל React Hooks היא הפשטות בהוצאת קוד משותף לפונקציה חיצונית וביצוע שימוש חוזר בקוד בין פקדים או אפילו בתוך אותו פקד. כל פונקציה יכולה להפעיל פונקציות Hook ומספיק לכתוב פונקציה שמפעילה Hook-ים אחרים בשביל לקבל Custom Hook. הטרמינולוגיה המסורבלת הזאת באה לתת שם לתופעה מאוד פשוטה שאומרת שכשיש לנו קוד משותף אנחנו יכולים להוציא אותו החוצה לפונקציה.
דוגמת הריפקטורינג שאני רוצה להראות היתה קצת מפתיעה לחלק מהאנשים בקורס ריאקט שאני מעביר. בואו נראה את הקוד קודם כל לפני ה Refactoring. נתון הקוד הבא לפקד שמשתמש ב-4 משתנים ב State ועוד שני אפקטים, והכל כדי לטעון מידע משרת חיצוני:
import React from 'react';
import ReactDOM from 'react-dom';
import { useState, useEffect } from 'react';
import _ from 'lodash';
import $ from 'jquery';
const App = () => {
const [id, setId] = useState(1);
const [character, setCharacter] = useState({});
const [filmId, setFilmId] = useState(1);
const [film, setFilm] = useState({});
useEffect(function() {
setFilm({});
const req = $.getJSON(`https://swapi.co/api/films/${filmId}/`, function(data) {
setFilm(data);
});
return function cancel() {
req.abort();
}
}, [filmId]);
useEffect(function() {
setCharacter({});
const req = $.getJSON(`https://swapi.co/api/people/${id}/`, function(data) {
setCharacter(data);
});
return function cancel() {
req.abort();
}
}, [id]);
const characterNameText = (character.name == null ?
'[Loading please wait]' :
character.name);
const filmNameText = (film.title == null ?
'[Loading please wait]' :
film.title);
const filmIds = (character.films != null ?
character.films.map(f => f.match(/(\d+)\/$/)[1]) :
_.range(1, 8));
return (
<div>
<h1>ID: {id}</h1>
Character ID:
<select onChange={(e) => setId(e.target.value)}>
{_.range(1, 11).map(id => (
<option value={id}>{id}</option>
))}
</select>
Film ID :
<select onChange={(e) => setFilmId(e.target.value)}>
{filmIds.map(id => (
<option value={id}>{id}</option>
))}
</select>
<hr />
<h2>Character Name: {characterNameText}</h2>
<h2>Film Name: {filmNameText}</h2>
</div>
)
};
const root = document.querySelector('main');
ReactDOM.render(<App />, root);
קל לראות שהגדרת האפקט פעמיים היא כפילות מיותרת - עבור כל זוג משתנים ב State יש לנו הגדרת אפקט שמופעל כשהמשתנה ID מקבל ערך חדש, ומכניס את התוכן למשתנה ה Data. הפקד משתמש בשתי נקודות קצה ב API ולכן האפקט מופיע פעמיים ואם היינו רוצים להוסיף נקודת קצה נוספת היינו צריכים לשכפל את הקוד פעם שלישית.
איך מתקנים? כל מה שצריך הוא להוציא את הקוד הכפול לפונקציה:
export function useRemoteData(endpoint) {
const [id, setId] = useState(1);
const [data, setData] = useState({});
useEffect(function() {
setData({});
const req = $.getJSON(`https://swapi.co/api/${endpoint}/${id}/`, function(data) {
setData(data);
});
return function cancel() {
req.abort();
}
}, [id]);
return [id, setId, data];
}
ועכשיו אפשר להפעיל אותה בפקד הראשי שלנו במקום כל הבלאגן:
const App = () => {
const [characterId, setCharacterId, character] = useRemoteData('people');
const [filmId, setFilmId, film] = useRemoteData('films');
// ...
וכן לריאקט ממש לא אכפת שאתם קוראים מפונקציית הפקד לפונקציה נוספת והיא זו שקוראת בפועל ל useEffect ו useState. למעשה האפשרות הזו להוציא קוד החוצה היא הסיבה המרכזית למעבר ל React Hooks. הפונקציה useRemoteData נקראת Custom Hook וזה כל מה שצריך לדעת בשביל לשתף קוד מבוסס סטייט בין קומפוננטות שונות או לבצע שימוש חוזר בקוד כזה בתוך אותה קומפוננטה.