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

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

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

אבל מה אם הייתם יכולים להביא את המידע עבור הדפים הכבדים הללו מראש, כך שבזמן שהמשתמש מגיע לדף, הוא יוכל לתקשר עם הדף מיידית?

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

תוכן העניינים

פריצה לפיתוח כפתור פתרונות

פה הגירסה המועדפת עם רק התיקון באופן גרמי וביצועים:

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

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

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

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

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

כיצד פריפטצ'ינג משפר את חוויית המשתמש

בואו נבחן כמה תרחישים שבהם פריפטצ'ינג מועיל:

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

  2. פריפטצ'ינג של נתוני טבלאות לדפים מאוחרים.

  3. טעינת נתונים מרכיב אב והעברתם לרכיב ילד.

  4. טעינת נתונים מראש עבור תצוגה בחלונית פופ-אובר.

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

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

הבנת הבעיה

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

  1. יש לכם רכיב שמציג מידע מסוים.

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

  3. הפופ-אובר טוען נתונים כאשר הוא נטען.

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

התרחיש ייראה כך:

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

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

פתרון #1: לפיץ מידע ברשת ברכיב הורא

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

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

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

פתרון #2: לפיץ מידע בזמן הטעיית הדף

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

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

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

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

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

כאן, אנו עושים את הדברים הבאים:

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

  • זה נותן לנו מספיק זמן לשלוף את הנתונים הראשיים.

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

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

איך ליישם Prefetching עם React

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

הגדרת הפרוייקט

כדי להתחיל ביצירת האפליקצייה העלולה לאפסייה, עשה את התהליך הבא:

אתה יכול להשתמש בvitejs (זה מה שאני השתמשתי בו) או בcreate-react-app כדי ליצור את האפליקצייה שלך. שירת את הפקט הבא בתוך הטרמינל שלך:

yarn create vite prefetch-example --template react-ts

אחרי שהאפליקצייה נוצרת, תצטרך לקבל את המבנה הבא כשאתה פתחת את תיבתprefetch-exampleעם VS Code.

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

רכבים

בדוגמה הזו, אנחנו נהשתמש ב-3 רכבים:

  • PopoverExample

  • UserProfile

  • UserProfileWithFetching

PopoverExample רכב

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

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

הנה הקוד לרכיב:

import { useState } from "react";
import { useFloating, useHover, useInteractions } from "@floating-ui/react";
import ContentLoader from "react-content-loader";
import UserProfile from "./UserProfile";
import UserProfileWithFetching from "./UserProfileWithFetching";

export const MyLoader = () => (
    <ContentLoader
        speed={2}
        width={340}
        height={84}
        viewBox="0 0 340 84"
        backgroundColor="#d1d1d1"
        foregroundColor="#fafafa"
    >
        <rect x="0" y="0" rx="3" ry="3" width="67" height="11" />
        <rect x="76" y="0" rx="3" ry="3" width="140" height="11" />
        <rect x="127" y="48" rx="3" ry="3" width="53" height="11" />
        <rect x="187" y="48" rx="3" ry="3" width="72" height="11" />
        <rect x="18" y="48" rx="3" ry="3" width="100" height="11" />
        <rect x="0" y="71" rx="3" ry="3" width="37" height="11" />
        <rect x="18" y="23" rx="3" ry="3" width="140" height="11" />
        <rect x="166" y="23" rx="3" ry="3" width="173" height="11" />
    </ContentLoader>
);
export default function PopoverExample() {
    const [isOpen, setIsOpen] = useState(false);
    const [isLoading, setIsLoading] = useState(false);
    const [data, setData] = useState({});

    const { refs, floatingStyles, context } = useFloating({
        open: isOpen,
        onOpenChange: setIsOpen,
        placement: "top",
    });

    const hover = useHover(context);

    const { getReferenceProps, getFloatingProps } = useInteractions([hover]);

    const handleMouseEnter = () => {
        if (Object.keys(data).length === 0) {
            setIsLoading(true);
            fetch("https://jsonplaceholder.typicode.com/users/1")
                .then((resp) => resp.json())
                .then((data) => {
                    setData(data);
                    setIsLoading(false);
                });
        }
    };

    return (
        <div
            id="hover-example"
            style={{
                display: "flex",
                flexDirection: "row",
                alignItems: "center",
                textAlign: "left",
            }}
            onMouseEnter={handleMouseEnter}
        >
            <span
                style={{
                    padding: "1rem",
                }}
            >
                <img
                    ref={refs.setReference}
                    {...getReferenceProps()}
                    style={{
                        borderRadius: "50%",
                    }}
                    src="https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_5.png"
                />
            </span>
            <p>
                Lorem Ipsum is simply dummy text of the printing and typesetting
                industry. Lorem Ipsum has been the industry's standard dummy text ever
                since the 1500s, when an unknown printer took a galley of type and
                scrambled it to make a type specimen book. It has survived not only five
                centuries, but also the leap into electronic typesetting, remaining
                essentially unchanged. It was popularised in the 1960s with the release
                of Letraset sheets containing Lorem Ipsum passages, and more recently
                with desktop publishing software like Aldus PageMaker including versions
                of Lorem Ipsum.
            </p>
            {isOpen && (
                <div
                    className="floating"
                    ref={refs.setFloating}
                    style={{
                        ...floatingStyles,
                        backgroundColor: "white",
                        color: "black",
                        padding: "1rem",
                        fontSize: "1rem",
                    }}
                    {...getFloatingProps()}
                >
                    {isLoading ? (
                        <MyLoader />
                    ) : (
                        <UserProfile hasAdditionalDetails {...data} />
                    )}
                    {/* <UserProfileWithFetching /> */}
                </div>
            )}
        </div>
    );
}

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

  • יש לנו תיבה הורית div בשם hover-example שכוללת תמונה וכמה טקסט.

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

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

  • השתמשנו בספריית react-content-loader במולך MyLoader. לספריית זו גם יש אתר אינטרנט שעוזר לך ליצור מטענים, אתה יכול לבדוק אתה כאן.

UserProfile מולך

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

מולך זה נראה בתוך מולך הפופובר. המטרה של המולך היא לטעם את הפרטים name email phone website שנשלמים מ API המחליף JSON.

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

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

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

import { useEffect, useState } from "react";
import ContentLoader from "react-content-loader";

const MyLoader = () => (
    <ContentLoader
        speed={2}
        viewBox="0 0 476 124"
        backgroundColor="#d1d1d1"
        foregroundColor="#fafafa"
    >
        <rect x="4" y="43" rx="0" ry="0" width="98" height="30" />
    </ContentLoader>
);

export default function UserProfile(props: Record<string, string | boolean>) {
    const { name, email, phone, website, hasAdditionalDetails } = props;
    const [isLoading, setIsLoading] = useState(false);
    const [additionalData, setAdditionalData] = useState(0);

    useEffect(() => {
        if (hasAdditionalDetails) {
            setIsLoading(true);
            fetch("https://jsonplaceholder.typicode.com/albums")
                .then((resp) => resp.json())
                .then((data: Array<unknown>) => {
                    const albumCount = data.reduce((acc, curr) => {
                        if (curr.userId === 1) acc += 1;

                        return acc;
                    }, 0);
                    setAdditionalData(albumCount);
                })
                .finally(() => {
                    setIsLoading(false);
                });
        }
    }, [hasAdditionalDetails]);

    return (
        <div id="user-profile">
            <div id="user-name">name: {name}</div>
            <div id="user-email">email: {email}</div>
            <div id="user-phone">phone: {phone}</div>
            <div id="user-website">website: {website}</div>
            {hasAdditionalDetails && (
                <>
                    {isLoading ? (
                        <MyLoader />
                    ) : (
                        <div id="user-albums">Album Count: {additionalData}</div>
                    )}
                </>
            )}
        </div>
    );
}

הקוmpונט הזה משתמש בhasAdditionalDetails prop. המטרה של הprop הזו היא לטעון מידע נוסף כשהקוmpונט מוטעה. היא משאירה את הפתרון מספר 2 שציינתי למעלה.

UserProfileWithFetching קוmpונט

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

אז הקוmpונט הזה תמיד יטעה את המידע כשהקוmpונט מוטעה, שירתא את הלוח המעטפת.

פה הקוד:

import { useEffect, useState } from "react";
import { MyLoader } from "./PopoverExample";

export default function UserProfileWithFetching() {
    const [isLoading, setIsLoading] = useState(false);
    const [data, setData] = useState<Record<string, string>>({});

    useEffect(() => {
        setIsLoading(true);
        fetch("https://jsonplaceholder.typicode.com/users/1")
            .then((resp) => resp.json())
            .then((data) => {
                setData(data);
                setIsLoading(false);
            });
    }, []);

    if (isLoading) return <MyLoader />;

    return (
        <div id="user-profile">
            <div id="user-name">name: {data.name}</div>
            <div id="user-email">email: {data.email}</div>
            <div id="user-phone">phone: {data.phone}</div>
            <div id="user-website">website: {data.website}</div>
        </div>
    );
}

כל הקוד לאפליקציית הזו ניתן למציאה כאן.

יותר מדי טעינה גם יכולה לגרום לאטות.

דעת טובה, יות

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

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

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

סיכום

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

לקריאה נוספת, בבקשה פנו לכתבות הבאות:

עבור תוכן נוסף, אתה יכול לעקוב איתי בTwitter, GitHub, ובLinkedIn.