الفرق بين المراجعتين ل"React/code splitting"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:تقسيم الشيفرة}}</noinclude>')
 
(تحديث)
 
(8 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:تقسيم الشيفرة}}</noinclude>
+
<noinclude>{{DISPLAYTITLE:تقسيم الشيفرة في React}}</noinclude>
 +
 
 +
== التحزيم (Bundling) ==
 +
تكون معظم الملفّات في تطبيقات React مُحزَّمة باستخدام أدوات مثل [https://webpack.js.org/ Webpack] أو [https://rollupjs.org/ Rollup] أو [http://browserify.org/ Browserify]. التحزيم هو عملية تتبّع الملفّات المستوردة ودمجها في ملف واحد وهو الحزمة (Bundle). يُمكِن بعدها تضمين هذه الحزمة في صفحة ويب لتحميل كامل التطبيق دفعة واحدة.
 +
 
 +
=== مثال ===
 +
شيفرة التطبيق هي:<syntaxhighlight lang="javascript">
 +
// app.js
 +
import { add } from './math.js';
 +
 
 +
console.log(add(16, 26)); // 42
 +
 
 +
</syntaxhighlight><syntaxhighlight lang="javascript">
 +
// math.js
 +
export function add(a, b) {
 +
  return a + b;
 +
}
 +
</syntaxhighlight>شيفرة الحزمة هي:<syntaxhighlight lang="javascript">
 +
function add(a, b) {
 +
  return a + b;
 +
}
 +
 
 +
console.log(add(16, 26)); // 42
 +
 
 +
</syntaxhighlight>'''ملاحظة:''' سينتهي مظهر الحزم لديك إلى شكلٍ مختلف عن هذا.
 +
 
 +
إن كنت تستخدم <code>[https://github.com/facebookincubator/create-react-app create-react-app]</code>، أو [https://github.com/zeit/next.js/ Next.js]، أو [https://www.gatsbyjs.org/ Gatsby]، أو أي أداة مشابهة، فسيكون لديك إعداد Webpack جاهز لتحزيم تطبيقك.
 +
 
 +
أمّا إن لم تكن تستخدم أي من هذه الأدوات فستحتاج إلى إعداد التحزيم بنفسك. انظر إلى دليل [https://webpack.js.org/guides/installation/ التثبيت] [https://webpack.js.org/guides/getting-started/ ودليل البدء] في توثيق Webpack.
 +
 
 +
== تقسيم الشيفرة ==
 +
يكون التحزيم رائعًا، ولكن عندما يكبر تطبيقك ستكبر الحزمة لديك أيضًا، خاصّة إن كنت تُضمِّن مكتبات طرف ثالث كبيرة الحجم. يجب الانتباه إلى الشيفرة التي تُضمِّنها في حزمتك لكي لا تجعلها كبيرة من غير قصد لدرجة أن يستغرق تطبيقك زمنًا طويلًا للتحميل.
 +
 
 +
لتجنّب الحصول على حزمة كبيرة من الأفضل استباق حصول المشكلة والبدء في تقسيم حزمتك. [https://webpack.js.org/guides/code-splitting/ تقسيم الشيفرة (Code-Splitting)] هو ميّزة مدعومة من قبل المُحزِّمات مثل Webpack و Browserify (عبر <code>[https://github.com/browserify/factor-bundle factor-bundle]</code>) والتي تستطيع إنشاء حزم متعددة يُمكِن تحميلها بشكل ديناميكي في زمن التنفيذ.
 +
 
 +
يُساعدك تقسيم شيفرة تطبيقك على إجراء تحميل مُتأخّر (Lazy Load) للأشياء التي يحتاجها المستخدم حاليًّا فقط، ممّا يُحسِّن بشكل كبير أداء تطبيقك. وفي حين أنّك لم تُقلِّل الحجم الكلي لشيفرة تطبيقك، فقد تجنّبت تحميل شيفرة قد لا يحتاجها المستخدم أبدًا وقلّلتَ حجم الشيفرة التي تحتاج إلى تحميلها في البداية.
 +
 
 +
== <code>import()‎</code> ==
 +
أفضل طريقة لتقديم تقسيم الشيفرة إلى تطبيقك هي عبر استخدام صياغة <code>import()‎</code> الديناميكية.
 +
 
 +
الشيفرة قبل الاستخدام:<syntaxhighlight lang="javascript">
 +
import { add } from './math';
 +
 
 +
console.log(add(16, 26));
 +
 
 +
</syntaxhighlight>الشيفرة بعد الاستخدام:<syntaxhighlight lang="javascript">
 +
import("./math").then(math => {
 +
  console.log(math.add(16, 26));
 +
});
 +
 
 +
</syntaxhighlight>عندما يأتي Webpack على هذه الصياغة سيبدأ بتقسيم شيفرة تطبيقك تلقائيًّا. إن كنت تستخدم <code>create-react-app</code> فهذا الإعداد موجود لديك مُسبقًا و[https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#code-splitting تستطيع استخدامه مباشرةً]. وهو أيضًا موجود بشكل جاهز عندما تستخدم [https://github.com/zeit/next.js/#dynamic-import Next.js].
 +
 
 +
إن كنت تُعِد Webpack بنفسك فستحتاج لقراءة [https://webpack.js.org/guides/code-splitting/ دليل Webpack حول تقسيم الشيفرة]. يجب أن تبدو إعدادات Webpack لديك مثل [https://gist.github.com/gaearon/ca6e803f5c604d37468b0091d9959269 هذا].
 +
 
 +
عند استخدامك Babel يجب أن تتأكد من قدرته على تصريف صياغة الاستيراد (<code>import()</code>‎) الديناميكية بدون تحويلها. ستحتاج من أجل ذلك إلى هذه الإضافة <code>[https://yarnpkg.com/en/package/babel-plugin-syntax-dynamic-import babel-plugin-syntax-dynamic-import]</code>.
 +
 
 +
== الدالة <code>React.lazy</code> ==
 +
'''ملاحظة''': <code>React.lazy</code> و <code>Suspense</code> غير متاحين للتصيير من طرف الخادم. إن أردت تنفيذ تقسيم للشيفرة في تطبيق مصيَّر من طرف الخادم، ننصح باستعمال المكتبة [https://github.com/smooth-code/loadable-components Loadable Components] إذ تملك [https://github.com/smooth-code/loadable-components/blob/master/packages/server/README.md دليلًا جيدًا لتحزيم عملية التقسيم مع التصيير من طرف الخادم].
 +
 
 +
تمكِّنك الدالة <code>React.lazy</code> من تصيير استيراد ديناميكي على أنَّه مكون عادي.
 +
 
 +
قبل استعمال <code>React.lazy</code>:<syntaxhighlight lang="javascript">
 +
import OtherComponent from './OtherComponent';
 +
 
 +
</syntaxhighlight>بعد استعمال <code>React.lazy</code>:<syntaxhighlight lang="javascript">
 +
const OtherComponent = React.lazy(() => import('./OtherComponent'));
 +
 
 +
</syntaxhighlight>ستحمِّل هذه الشيفرة الحزمة تلقائيًّا التي تحوي المكون <code>OtherComponent</code> عند تصييره.
 +
 
 +
تأخذ الدالة <code>React.lazy</code> دالةً يجب عليها أن استدعاء استيراد ديناميكي عبر <code>import()‎</code>. هذا يجب أن يعيد وعدًا (أي <code>[[JavaScript/Promise|Promise]]</code>) يُستبيَن إلى وحدةٍ مع تصدير افتراضي <code>default</code> يحوي مكون React.
 +
 
 +
ينبغي تصيير المكون الكسول عقب ذلك داخل مكون <code>Suspense</code>.والذي يتيح لنا إظهار المحتوى التراجعي (fallback content) في أثناء انتظارنا تحميلها.<syntaxhighlight lang="javascript">
 +
import React, { Suspense } from 'react';
 +
 
 +
const OtherComponent = React.lazy(() => import('./OtherComponent'));
 +
 
 +
function MyComponent() {
 +
  return (
 +
    <div>
 +
      <Suspense fallback={<div>Loading...</div>}>
 +
        <OtherComponent />
 +
      </Suspense>
 +
    </div>
 +
  );
 +
}
 +
</syntaxhighlight>تقبل الخاصية <code>fallback</code> أي عناصر React تريد تصييرها أثناء انتظار المكون لتحمليه. يمكنك وضع المكون <code>Suspense</code> في أي مكان فوق المكون الكسول. يمكنك حتى تغليف مكونات كسولة مع مكون <code>Suspense</code> وحيد.<syntaxhighlight lang="javascript">
 +
import React, { Suspense } from 'react';
 +
 
 +
const OtherComponent = React.lazy(() => import('./OtherComponent'));
 +
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
 +
 
 +
function MyComponent() {
 +
  return (
 +
    <div>
 +
      <Suspense fallback={<div>Loading...</div>}>
 +
        <section>
 +
          <OtherComponent />
 +
          <AnotherComponent />
 +
        </section>
 +
      </Suspense>
 +
    </div>
 +
  );
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== حدود الخطأ ===
 +
إن فشل تحميل الوحدة الأخرى (نتيجة فشل الشبكة مثلًا)، فسيُطلَق خطأ. يمكنك معالجة هذه الأخطاء لتحسين تجربة المستخدم وإدارة عملية الإصلاح عبر [[React/error boundaries|حد الأخطاء]]. متى ما أنشأت حدًا للخطأ، يمكنك استعماله في أي مكان فوق مكوناتك الكسولة لعرض حالة الخطأ عندما يكون هنالك خطأ في الشبكة.<syntaxhighlight lang="javascript">
 +
import React, { Suspense } from 'react';
 +
import MyErrorBoundary from './MyErrorBoundary';
 +
 
 +
const OtherComponent = React.lazy(() => import('./OtherComponent'));
 +
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
 +
 
 +
const MyComponent = () => (
 +
  <div>
 +
    <MyErrorBoundary>
 +
      <Suspense fallback={<div>Loading...</div>}>
 +
        <section>
 +
          <OtherComponent />
 +
          <AnotherComponent />
 +
        </section>
 +
      </Suspense>
 +
    </MyErrorBoundary>
 +
  </div>
 +
);
 +
</syntaxhighlight>
 +
 
 +
== تقسيم الشيفرة المعتمد على الطريق (Route) ==
 +
قد يكون من الصعب تحديد موضع تقسيم الشيفرة في تطبيقك. يجب عليك أن تتأكد من اختيار أماكن تُقسِّم الشيفرة بشكلٍ متساوٍ ولكن من دون الضرر بتجربة المستخدم.
 +
 
 +
المكان الجيد للبدء بذلك هو الطرق (routes). اعتاد معظم مستخدمي الويب على أنّ أزرار التقليب بين الصفحات تستغرق وقتًا لتحميلها. تميل حينها أيضًا إلى إعادة تصيير كامل الصفحة مرة واحدة لذا من غير المحتمل أن يكون المستخدم يحاول التفاعل مع الصفحة في نفس الوقت.
 +
 
 +
هذا مثال حول إعداد تقسيم الشيفرة اعتمادًا على الطريق في تطبيقك باستخدام مكتبات مثل [https://reacttraining.com/react-router/ React Router] و <code>React.lazy</code>:<syntaxhighlight lang="javascript">
 +
import React, { Suspense, lazy } from 'react';
 +
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
 +
 
 +
const Home = lazy(() => import('./routes/Home'));
 +
const About = lazy(() => import('./routes/About'));
 +
 
 +
const App = () => (
 +
  <Router>
 +
    <Suspense fallback={<div>Loading...</div>}>
 +
      <Switch>
 +
        <Route exact path="/" component={Home}/>
 +
        <Route path="/about" component={About}/>
 +
      </Switch>
 +
    </Suspense>
 +
  </Router>
 +
);
 +
</syntaxhighlight>
 +
 
 +
== التصديرات المسماة ==
 +
تدعم الدالة <code>React.lazy</code> حاليًا التصديرات الافتراضية (default exports). إن كانت الوحدة التي تريد استيرادها تستعمل التصديرات المسماة (named exports)، تستطيع إنشاء وحدة وسيطة تعيد تصديرها بالشكل الافتراضي. يضمن ذلك استمرار التحقق من عدم وجود شيفرات «ميتة» وعدم استيراد المكونات غير المستعملة.<syntaxhighlight lang="javascript">
 +
// ManyComponents.js
 +
export const MyComponent = /* ... */;
 +
export const MyUnusedComponent = /* ... */;
 +
</syntaxhighlight><syntaxhighlight lang="javascript">
 +
// MyComponent.js
 +
export { MyComponent as default } from "./ManyComponents.js";
 +
</syntaxhighlight><syntaxhighlight lang="javascript">
 +
// MyApp.js
 +
import React, { lazy } from 'react';
 +
const MyComponent = lazy(() => import("./MyComponent.js"));
 +
</syntaxhighlight>
 +
 
 +
== انظر أيضًا ==
 +
* [[React/jsx in depth|شرح JSX بالتفصيل]]
 +
* [[React/static type checking|التحقق من الأنواع الثابتة]]
 +
* [[React/typechecking with proptypes|التحقق من الأنواع باستخدام PropTypes]]
 +
* [[React/refs and the dom|استخدام المراجع مع DOM]]
 +
* [[React/uncontrolled components|المكونات غير المضبوطة]]
 +
* [[React/optimizing performance|تحسين الأداء]]
 +
* [[React/react without es6|React بدون ES6]]
 +
* [[React/react without jsx|React بدون JSX]]
 +
* [[React/reconciliation|المطابقة (Reconciliation)]]
 +
* [[React/context|استخدام السياق (Context) في React]]
 +
* [[React/fragments|استخدام الأجزاء (Fragments) في React]]
 +
* [[React/portals|المداخل (Portals) في React]]
 +
* [[React/error boundaries|حدود الأخطاء]]
 +
* [[React/web components|مكونات الويب]]
 +
* [[React/higher order components|المكونات ذات الترتيب الأعلى]]
 +
* [[React/forwarding refs|تمرير المراجع]]
 +
* [[React/render props|خاصيات التصيير]]
 +
* [[React/integrating with other libraries|تكامل React مع المكتبات الأخرى]]
 +
* [[React/accessibility|سهولة الوصول]]
 +
* [[React/strict mode|الوضع الصارم (Strict Mode)]]
 +
== مصادر==
 +
*[https://reactjs.org/docs/code-splitting.html صفحة تقسيم الشيفرة في توثيق React الرسمي].
 +
[[تصنيف:React]]
 +
[[تصنيف:React Advanced Guides]]

المراجعة الحالية بتاريخ 09:51، 3 نوفمبر 2020


التحزيم (Bundling)

تكون معظم الملفّات في تطبيقات React مُحزَّمة باستخدام أدوات مثل Webpack أو Rollup أو Browserify. التحزيم هو عملية تتبّع الملفّات المستوردة ودمجها في ملف واحد وهو الحزمة (Bundle). يُمكِن بعدها تضمين هذه الحزمة في صفحة ويب لتحميل كامل التطبيق دفعة واحدة.

مثال

شيفرة التطبيق هي:

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

شيفرة الحزمة هي:

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

ملاحظة: سينتهي مظهر الحزم لديك إلى شكلٍ مختلف عن هذا.

إن كنت تستخدم create-react-app، أو Next.js، أو Gatsby، أو أي أداة مشابهة، فسيكون لديك إعداد Webpack جاهز لتحزيم تطبيقك.

أمّا إن لم تكن تستخدم أي من هذه الأدوات فستحتاج إلى إعداد التحزيم بنفسك. انظر إلى دليل التثبيت ودليل البدء في توثيق Webpack.

تقسيم الشيفرة

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

لتجنّب الحصول على حزمة كبيرة من الأفضل استباق حصول المشكلة والبدء في تقسيم حزمتك. تقسيم الشيفرة (Code-Splitting) هو ميّزة مدعومة من قبل المُحزِّمات مثل Webpack و Browserify (عبر factor-bundle) والتي تستطيع إنشاء حزم متعددة يُمكِن تحميلها بشكل ديناميكي في زمن التنفيذ.

يُساعدك تقسيم شيفرة تطبيقك على إجراء تحميل مُتأخّر (Lazy Load) للأشياء التي يحتاجها المستخدم حاليًّا فقط، ممّا يُحسِّن بشكل كبير أداء تطبيقك. وفي حين أنّك لم تُقلِّل الحجم الكلي لشيفرة تطبيقك، فقد تجنّبت تحميل شيفرة قد لا يحتاجها المستخدم أبدًا وقلّلتَ حجم الشيفرة التي تحتاج إلى تحميلها في البداية.

import()‎

أفضل طريقة لتقديم تقسيم الشيفرة إلى تطبيقك هي عبر استخدام صياغة import()‎ الديناميكية.

الشيفرة قبل الاستخدام:

import { add } from './math';

console.log(add(16, 26));

الشيفرة بعد الاستخدام:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

عندما يأتي Webpack على هذه الصياغة سيبدأ بتقسيم شيفرة تطبيقك تلقائيًّا. إن كنت تستخدم create-react-app فهذا الإعداد موجود لديك مُسبقًا وتستطيع استخدامه مباشرةً. وهو أيضًا موجود بشكل جاهز عندما تستخدم Next.js.

إن كنت تُعِد Webpack بنفسك فستحتاج لقراءة دليل Webpack حول تقسيم الشيفرة. يجب أن تبدو إعدادات Webpack لديك مثل هذا.

عند استخدامك Babel يجب أن تتأكد من قدرته على تصريف صياغة الاستيراد (import()‎) الديناميكية بدون تحويلها. ستحتاج من أجل ذلك إلى هذه الإضافة babel-plugin-syntax-dynamic-import.

الدالة React.lazy

ملاحظة: React.lazy و Suspense غير متاحين للتصيير من طرف الخادم. إن أردت تنفيذ تقسيم للشيفرة في تطبيق مصيَّر من طرف الخادم، ننصح باستعمال المكتبة Loadable Components إذ تملك دليلًا جيدًا لتحزيم عملية التقسيم مع التصيير من طرف الخادم.

تمكِّنك الدالة React.lazy من تصيير استيراد ديناميكي على أنَّه مكون عادي.

قبل استعمال React.lazy:

import OtherComponent from './OtherComponent';

بعد استعمال React.lazy:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

ستحمِّل هذه الشيفرة الحزمة تلقائيًّا التي تحوي المكون OtherComponent عند تصييره.

تأخذ الدالة React.lazy دالةً يجب عليها أن استدعاء استيراد ديناميكي عبر import()‎. هذا يجب أن يعيد وعدًا (أي Promise) يُستبيَن إلى وحدةٍ مع تصدير افتراضي default يحوي مكون React.

ينبغي تصيير المكون الكسول عقب ذلك داخل مكون Suspense.والذي يتيح لنا إظهار المحتوى التراجعي (fallback content) في أثناء انتظارنا تحميلها.

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

تقبل الخاصية fallback أي عناصر React تريد تصييرها أثناء انتظار المكون لتحمليه. يمكنك وضع المكون Suspense في أي مكان فوق المكون الكسول. يمكنك حتى تغليف مكونات كسولة مع مكون Suspense وحيد.

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

حدود الخطأ

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

import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

تقسيم الشيفرة المعتمد على الطريق (Route)

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

المكان الجيد للبدء بذلك هو الطرق (routes). اعتاد معظم مستخدمي الويب على أنّ أزرار التقليب بين الصفحات تستغرق وقتًا لتحميلها. تميل حينها أيضًا إلى إعادة تصيير كامل الصفحة مرة واحدة لذا من غير المحتمل أن يكون المستخدم يحاول التفاعل مع الصفحة في نفس الوقت.

هذا مثال حول إعداد تقسيم الشيفرة اعتمادًا على الطريق في تطبيقك باستخدام مكتبات مثل React Router و React.lazy:

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

التصديرات المسماة

تدعم الدالة React.lazy حاليًا التصديرات الافتراضية (default exports). إن كانت الوحدة التي تريد استيرادها تستعمل التصديرات المسماة (named exports)، تستطيع إنشاء وحدة وسيطة تعيد تصديرها بالشكل الافتراضي. يضمن ذلك استمرار التحقق من عدم وجود شيفرات «ميتة» وعدم استيراد المكونات غير المستعملة.

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

انظر أيضًا

 مصادر