الوضع المتزامن في React: تكييف تطبيقات الويب للأجهزة وسرعة الإنترنت

في هذه المقالة ، سأقدم الوضع المتزامن في React. دعنا نتعرف على ما هو: ما هي الميزات والأدوات الجديدة التي ظهرت وكيفية تحسين تشغيل تطبيقات الويب بمساعدتهم بحيث يمكن لكل شيء أن يطير للمستخدمين. الوضع المتزامن هو ميزة جديدة في React. وتتمثل مهمتها في تكييف التطبيق مع الأجهزة المختلفة وسرعات الشبكة. حتى الآن ، يعد الوضع المتزامن تجربة يمكن لمطوري المكتبة تغييرها ، مما يعني عدم وجود أدوات جديدة في الإسطبل. لقد حذرتك ، والآن دعنا نذهب.



حاليًا ، هناك نوعان من القيود لتقديم المكونات: طاقة المعالج ومعدل نقل بيانات الشبكة. عندما يحتاج المستخدم إلى عرض شيء ما ، يحاول الإصدار الحالي من React عرض كل مكون من البداية إلى النهاية. لا يهم ما إذا كانت الواجهة قد تتجمد لبضع ثوان. إنها نفس القصة مع نقل البيانات. ستنتظر React جميع البيانات التي يحتاجها المكون تمامًا ، بدلاً من رسمها قطعة قطعة.







النظام التنافسي يحل هذه المشاكل. باستخدامه ، يمكن لـ React إيقاف العمليات التي كانت محظورة سابقًا وتحديد أولوياتها وحتى إلغائها ، لذلك في الوضع المتزامن يمكنك البدء في عرض المكونات بغض النظر عما إذا تم استلام جميع البيانات أو جزء منها فقط.



الوضع المتزامن هو هندسة الألياف



الوضع التنافسي ليس شيئًا جديدًا قرر المطورون إضافته فجأة ، وعمل كل شيء هناك. مستعدة لإطلاق سراحه مقدما. في الإصدار 16 ، تم تحويل محرك React إلى بنية الألياف ، والتي تشبه من حيث المبدأ برنامج جدولة المهام في نظام التشغيل. المجدول يوزع الموارد الحسابية بين العمليات. إنه قادر على التبديل في أي وقت ، لذلك يتوهم المستخدم أن العمليات تعمل بالتوازي.



تقوم بنية الألياف بنفس الشيء ، ولكن مع المكونات. على الرغم من حقيقة أنه موجود بالفعل في React ، يبدو أن بنية الألياف الضوئية معلقة في الرسوم المتحركة ولا تستخدم قدراتها إلى أقصى حد. سيعمل الوضع التنافسي على تشغيله بكامل طاقته.



عند تحديث أحد المكونات في الوضع العادي ، يجب عليك رسم إطار جديد بالكامل على الشاشة. حتى اكتمال التحديث ، لن يرى المستخدم أي شيء. في هذه الحالة ، تعمل React بشكل متزامن. تستخدم الألياف مفهومًا مختلفًا. كل 16 مللي ثانية ، هناك مقاطعة وفحص: هل تغيرت الشجرة الافتراضية ، هل ظهرت بيانات جديدة؟ إذا كان الأمر كذلك ، فسيراهم المستخدم على الفور.



لماذا 16 مللي ثانية؟ يسعى مطورو React إلى إعادة رسم الشاشة بسرعة تقترب من 60 إطارًا في الثانية. لتلائم 60 تحديثًا في 1000 مللي ثانية ، عليك إجراؤها كل 16 مللي ثانية تقريبًا. ومن هنا الرقم. يخرج الوضع التنافسي من الصندوق ويضيف أدوات جديدة تجعل الحياة الأمامية أفضل. سأخبرك عن كل منها بالتفصيل.



تشويق



تم تقديم التعليق في React 16.6 كآلية لتحميل المكونات ديناميكيًا. في الوضع المتزامن ، يتم الاحتفاظ بهذا المنطق ، لكن تظهر فرص إضافية. يصبح التشويق آلية تعمل جنبًا إلى جنب مع مكتبة تحميل البيانات. نطلب موردًا خاصًا من خلال المكتبة ونقرأ البيانات منه.



يقرأ التعليق بشكل متزامن البيانات غير الجاهزة. كيف؟ نحن نطلب البيانات ، وحتى تصل بالكامل ، بدأنا بالفعل في قراءتها في أجزاء صغيرة. أروع شيء للمطورين هو إدارة الترتيب الذي يتم به عرض البيانات المحملة. يسمح لك التعليق بعرض مكونات الصفحة بشكل متزامن ومستقل عن بعضها البعض. يجعل الكود مباشرًا: عليك فقط إلقاء نظرة على بنية التشويق لمعرفة الترتيب المطلوب البيانات فيه.



الحل النموذجي لتحميل الصفحات في React "القديمة" هو Fetch-On-Render. في هذه الحالة ، نطلب البيانات بعد التصيير داخل useEffect أو componentDidMount. هذا هو المنطق القياسي عندما لا يكون هناك إعادة أو طبقة بيانات أخرى. على سبيل المثال ، نريد رسم مكونين ، يحتاج كل منهما إلى بيانات:



  • طلب المكون 1
  • توقع…
  • الحصول على البيانات -> تقديم المكون 1
  • طلب المكون 2
  • توقع…
  • الحصول على البيانات -> تقديم المكون 2


في هذا النهج ، لا يُطلب المكون التالي إلا بعد تقديم الأول. إنه طويل وغير مريح.



لنفكر بطريقة أخرى ، Fetch-Then-Render: أولاً نطلب جميع البيانات ، ثم نرسم الصفحة.



  • طلب المكون 1
  • طلب المكون 2
  • توقع…
  • الحصول على المكون 1
  • الحصول على المكون 2
  • تقديم المكون


في هذه الحالة ، نقوم بنقل حالة الطلب إلى مكان ما إلى الأعلى - نقوم بتفويضه إلى المكتبة للتعامل مع البيانات. الطريقة تعمل بشكل رائع ، ولكن هناك فارق بسيط. إذا استغرق تحميل أحد المكونات وقتًا أطول بكثير من الآخر ، فلن يرى المستخدم أي شيء ، على الرغم من أننا قد نعرض له شيئًا بالفعل. لنلقِ نظرة على نموذج تعليمة برمجية من العرض التوضيحي مع مكونين: المستخدم والمشاركات. نلف المكونات في التشويق:



const resource = fetchData() // -    React
function Page({ resource }) {
    return (
        <Suspense fallback={<h1>Loading user...</h1>}>
            <User resource={resource} />
            <Suspense fallback={<h1>Loading posts...</h1>}>
                <Posts resource={resource} />
            </Suspense>
        </Suspense>
    )
}


قد يبدو أن هذا النهج قريب من Fetch-On-Render ، عندما طلبنا البيانات بعد تقديم المكون الأول. ولكن في الواقع ، سيؤدي استخدام Suspense إلى الحصول على البيانات بشكل أسرع. هذا يرجع إلى حقيقة أن كلا الطلبين يتم إرسالهما بالتوازي.



في Suspense ، يمكنك تحديد العنصر الاحتياطي ، والمكون الذي تريد عرضه ، وتمرير المورد الذي تم تنفيذه بواسطة مكتبة استرداد البيانات داخل المكون. نحن نستخدمه كما هو. داخل المكونات ، نطلب البيانات من المورد ونستدعى طريقة القراءة. هذا وعد تقدمه لنا المكتبة. سيتفهم التشويق ما إذا كانت البيانات قد تم تحميلها ، وإذا كان الأمر كذلك ، فسوف يعرضها.



لاحظ أن المكونات تحاول قراءة البيانات التي لا تزال قيد الاستلام:



function User() {
    const user = resource.user.read()
    return <h1>{user.name}</h1>
}
function Posts() {
    const posts = resource.posts.read()
    return //  
}


في العروض التوضيحية الحالية لدان أبراموف ، يتم استخدام مثل هذا الشيء كعنصر أساسي لمورد .



read() {
    if (status === 'pending') {
        throw suspender
    } else if (status === 'error') {
        throw result
    } else if (status === 'success') {
        return result
    }
}




إذا كان المورد لا يزال قيد التحميل ، فنحن نرمي كائن الوعد كاستثناء. يمسك التشويق بهذا الاستثناء ، ويدرك أنه وعد ، ويستمر في التحميل. إذا ، بدلاً من الوعد ، وصل استثناء مع أي كائن آخر ، فسوف يتضح أن الطلب انتهى بالخطأ. عندما يتم إرجاع النتيجة النهائية ، سيعرضها التشويق. من المهم بالنسبة لنا الحصول على مورد واستدعاء طريقة عليه. كيف يتم تنفيذه داخليا هو قرار من مطوري المكتبة ، الشيء الرئيسي هو أن Suspense يفهم تنفيذها.



متى تطلب البيانات؟ السؤال في الجزء العلوي من الشجرة ليس فكرة جيدة لأنه قد لا يكون مطلوبًا أبدًا. الخيار الأفضل هو القيام بذلك على الفور عند التنقل داخل معالجات الأحداث. على سبيل المثال ، احصل على الحالة الأولية من خلال رابط ، ثم اطلب الموارد بمجرد أن ينقر المستخدم على الزر.



هكذا سيبدو في الكود:



function App() {
    const [resource, setResource] = useState(initialResource)
    return (
        <>
            <Button text='' onClick={() => {
                setResource(fetchData())
            }}>
            <Page resource={resource} />
        </>
    );
}


التشويق مرن بشكل لا يصدق. يمكن استخدامه لعرض المكونات واحدًا تلو الآخر.



return (
    <Suspense fallback={<h1>Loading user...</h1>}>
        <User />
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
        </Suspense>
    </Suspense>
)


أو في نفس الوقت ، يجب أن يتم لف كلا المكونين في تعليق واحد.



return (
    <Suspense fallback={<h1>Loading user and posts...</h1>}>
        <User />
        <Posts />
    </Suspense>
)


أو قم بتحميل المكونات بشكل منفصل عن بعضها البعض عن طريق لفها في تعليق مستقل. سيتم تحميل المورد من خلال المكتبة. انه رائع جدا ومريح



return (
    <>
        <Suspense fallback={<h1>Loading user...</h1>}>
            <User />
        </Suspense>
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
        </Suspense>
    </>
)


أيضًا ، ستكتشف مكونات حدود الخطأ الأخطاء داخل التعليق. إذا حدث خطأ ما ، فيمكننا إظهار أن المستخدم قد قام بالتحميل ، لكن المنشورات لم يتم تحميلها ، ونعطي خطأ.



return (
    <Suspense fallback={<h1>Loading user...</h1>}>
        <User resource={resource} />
        <ErrorBoundary fallback={<h2>Could not fetch posts</h2>}>
            <Suspense fallback={<h1>Loading posts...</h1>}>
                <Posts resource={resource} />
            </Suspense>
        </ErrorBoundary>
    </Suspense>
)


لنلقِ الآن نظرة على الأدوات الأخرى التي يمكنها إطلاق العنان للمزايا الكاملة للنظام التنافسي.



قائمة التشويق



تساعد قائمة Suspense في نفس الوقت على التحكم في ترتيب تحميل Suspense. إذا احتجنا إلى تحميل العديد من المعلقين بشكل صارم واحدًا تلو الآخر بدونه ، فسيتعين عليهم التداخل داخل بعضهم البعض:



return (
    <Suspense fallback={<h1>Loading user...</h1>}>
        <User />
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
            <Suspense fallback={<h1>Loading facts...</h1>}>
                <Facts />
            </Suspense>
        </Suspense>
    </Suspense>
)


قائمة التشويق تجعل هذا أسهل بكثير:



return (
    <SuspenseList revealOrder="forwards" tail="collapsed">
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
        </Suspense>
        <Suspense fallback={<h1>Loading facts...</h1>}>
            <Facts />
        </Suspense>
    </Suspense>
)


مرونة قائمة التشويق مذهلة. يمكنك تضمين قائمة SuspenseList في بعضها كما تريد وتخصيص ترتيب التحميل بالداخل حيث سيكون مناسبًا لعرض عناصر واجهة المستخدم وأي مكونات أخرى.



useTransition



خطاف خاص يؤجل تحديث المكون حتى يصبح جاهزًا تمامًا ويزيل حالة التحميل الوسيطة. لما هذا؟ تسعى React جاهدة لجعل الانتقال أسرع ما يمكن عند تغيير الحالة. لكن في بعض الأحيان من المهم أن تأخذ وقتك. إذا تم تحميل جزء من البيانات بناءً على إجراء مستخدم ، فعادةً في وقت التحميل نعرض أداة تحميل أو هيكل عظمي. إذا وصلت البيانات بسرعة كبيرة ، فلن يكون لدى المُحمل الوقت لإكمال نصف دورة. سيومض ، ثم يختفي ، وسنرسم المكون المحدث. في مثل هذه الحالات ، من الحكمة عدم إظهار اللودر على الإطلاق.



هنا يأتي دور useTransition. كيف يعمل في الكود؟ نسمي الخطاف useTransition ونحدد المهلة بالمللي ثانية. إذا لم تأت البيانات في الوقت المحدد ، فسنستمر في عرض أداة التحميل. ولكن إذا حصلنا عليها بشكل أسرع ، فسيكون هناك انتقال فوري.



function App() {
    const [resource, setResource] = useState(initialResource)
    const [startTransition, isPending] = useTransition({ timeoutMs: 2000 })
    return <>
        <Button text='' disabled={isPending} onClick={() => {
            startTransition(() => {
                setResource(fetchData())
            })
        }}>
        <Page resource={resource} />
    </>
}


في بعض الأحيان لا نرغب في إظهار أداة التحميل عندما ننتقل إلى الصفحة ، لكننا ما زلنا بحاجة إلى تغيير شيء ما في الواجهة. على سبيل المثال ، طوال فترة الانتقال ، قم بحظر الزر. بعد ذلك ، ستكون خاصية isPending في متناول اليد - ستعلمك أننا في مرحلة الانتقال. بالنسبة للمستخدم ، سيكون التحديث فوريًا ، ولكن من المهم أن نلاحظ هنا أن useTransition magic يؤثر فقط على المكونات المغلفة في Suspense. UseTransition نفسها لن تعمل.



الانتقالات شائعة في الواجهات. سيكون المنطق المسؤول عن الانتقال رائعًا لخياطة الزر والاندماج في المكتبة. إذا كان هناك مكون مسؤول عن الانتقالات بين الصفحات ، فيمكنك لف onClick الذي يتم تمريره عبر الدعائم إلى الزر الموجود في handleClick وإظهار الحالة isDisabled.



function Button({ text, onClick }) {
    const [startTransition, isPending] = useTransition({ timeoutMs: 2000 })

    function handleClick() {
        startTransition(() => {
            onClick()
        })
    }

    return <button onClick={handleClick} disabled={isPending}>text</button>
}


useDefirmedValue



إذن ، هناك عنصر نجعل به انتقالات. أحيانًا يظهر الموقف التالي: يريد المستخدم الانتقال إلى صفحة أخرى ، وقد تلقينا بعض البيانات ونحن على استعداد لعرضها. في الوقت نفسه ، تختلف الصفحات قليلاً عن بعضها البعض. في هذه الحالة ، سيكون من المنطقي إظهار بيانات المستخدم القديمة حتى يتم تحميل كل شيء آخر.



الآن لا تعرف React كيف: في الإصدار الحالي ، يمكن فقط عرض البيانات من الحالة الحالية على شاشة المستخدم. لكن useDefirmedValue في الوضع المتزامن قادر على إرجاع نسخة مؤجلة من القيمة ، وإظهار البيانات التي لا معنى لها بدلاً من أداة التحميل الوامضة أو الرجوع في وقت التمهيد. يأخذ هذا الخطاف القيمة التي نريد الحصول على الإصدار المؤجل لها والتأخير بالمللي ثانية.



تصبح الواجهة فائقة الانسيابية. يمكن إجراء التحديثات بأقل قدر من البيانات ، ويتم تحميل كل شيء آخر تدريجيًا. انطباع المستخدم أن التطبيق سريع وسلس. في الواقع ، يبدو useDefirmedValue على النحو التالي:



function Page({ resource }) {
    const deferredResource = useDeferredValue(resource, { timeoutMs: 1000 })
    const isDeferred = resource !== deferredResource;
    return (
        <Suspense fallback={<h1>Loading user...</h1>}>
            <User resource={resource} />
            <Suspense fallback={<h1>Loading posts...</h1>}>
                <Posts resource={deferredResource} isDeferred={isDeferred}/>
            </Suspense>
        </Suspense>
    )
}


يمكنك مقارنة القيمة من الخاصيات بالقيمة التي تم الحصول عليها من خلال useDefirmedValue. إذا كانا مختلفين ، فلا تزال الصفحة قيد التحميل.



ومن المثير للاهتمام ، أن useDefirmedValue سيسمح لك بتكرار خدعة التحميل البطيء ، ليس فقط للبيانات التي يتم إرسالها عبر الشبكة ، ولكن أيضًا لإزالة تجميد الواجهة بسبب الحسابات الكبيرة.



لماذا هذا عظيم؟ تعمل الأجهزة المختلفة بشكل مختلف. إذا قمت بتشغيل تطبيق باستخدام useDefirmedValue على iPhone جديد ، فسيكون الانتقال من صفحة إلى صفحة فوريًا ، حتى لو كانت الصفحات ثقيلة. ولكن عند استخدام debuted ، سيظهر التأخير حتى على جهاز قوي. يتكيف وضع UseDefirmedValue والوضع المتزامن مع الأجهزة: إذا كان يعمل ببطء ، فسيظل الإدخال يطير ، وسيتم تحديث الصفحة نفسها وفقًا لما يسمح به الجهاز.



كيف يمكنني تبديل مشروع إلى الوضع المتزامن؟



الوضع التنافسي هو وضع ، لذلك تحتاج إلى تمكينه. مثل مفتاح التبديل الذي يجعل الألياف تعمل بكامل طاقتها. من أين تبدأ؟



نزيل الإرث. نتخلص من جميع الطرق القديمة في الكود ونتأكد من أنها ليست في المكتبات. إذا كان التطبيق يعمل بشكل جيد في React.StrictMode ، فسيكون كل شيء على ما يرام - ستكون الخطوة سهلة. المضاعفات المحتملة هي المشاكل داخل المكتبات. في هذه الحالة ، تحتاج إما إلى الترقية إلى إصدار جديد أو تغيير المكتبة. أو التخلي عن النظام التنافسي. بعد التخلص من الإرث ، كل ما تبقى هو تبديل الجذر.



مع وصول الوضع المتزامن ، ستتوفر ثلاثة أوضاع اتصال جذر:



  • سيتم

    ReactDOM.render(<App />, rootNode)

    إيقاف وضع العرض القديم بعد تحرير الوضع التنافسي.
  • وضع الحظر

    ReactDOM.createBlockingRoot(rootNode).render(<App />)

    كمرحلة وسيطة ، ستتم إضافة وضع الحجب ، والذي يتيح الوصول إلى بعض الفرص للوضع التنافسي في المشاريع التي توجد بها موروثات أو صعوبات أخرى في النقل.
  • الوضع التنافسي

    ReactDOM.createRoot(rootNode).render(<App />)

    إذا كان كل شيء على ما يرام ، فلا يوجد إرث ، ويمكن تبديل المشروع على الفور ، استبدل العرض في المشروع بـ createRoot - وأوقفه إلى مستقبل مشرق.


الاستنتاجات



يتم إجراء عمليات الحظر داخل React بشكل غير متزامن عن طريق التبديل إلى Fiber. تظهر أدوات جديدة تسهل تكييف التطبيق مع إمكانيات الجهاز وسرعة الشبكة:



  • تشويق ، بفضله يمكنك تحديد ترتيب تحميل البيانات.
  • قائمة التشويق ، والتي هي أكثر ملاءمة.
  • useTransition لإنشاء انتقالات سلسة بين المكونات المغلفة بالتعليق.
  • useDefirmedValue - لإظهار البيانات التي لا معنى لها أثناء إدخال / إخراج وتحديثات المكون


حاول تجربة الوضع المتزامن بينما لا يزال خارجًا. يتيح لك الوضع المتزامن تحقيق نتائج مبهرة: تحميل سريع وسلس للمكونات بأي ترتيب مناسب ، واجهة فائقة الانسيابية. التفاصيل موضحة في الوثائق ، وهناك عروض توضيحية بأمثلة تستحق الاستكشاف بنفسك. وإذا كنت مهتمًا بكيفية عمل هندسة الألياف ، فإليك رابطًا لمحادثة ممتعة.



قم بتقييم مشاريعك - ما الذي يمكن تحسينه باستخدام الأدوات الجديدة؟ وعندما يخرج الوضع التنافسي ، لا تتردد في التحرك. كل شيء سيكون رائعا!



All Articles