سنبدأ الآن التعرّف على ما هو على الأرجح أهم موضوع في هذه الدورة، ألا وهو مكتبة React. لنبدأ بإنشاء تطبيق React بسيط والتعرّف على المفاهيم الأساسية لـ React.

أسهل طريقة للبدء بفارق كبير هي استخدام أداة تُسمى Vite.

لننشئ تطبيقًا جديدًا باستخدام أداة create-vite:

npm create vite@latest

لنجب عن الأسئلة التي تعرضها الأداة كما يلي:

عرض اختيارات أداة create-vite، حيث يُسمّى المشروع part1، وإطار العمل React، والصيغة JavaScript، وتُجاب جميع الأسئلة الأخرى بـ No

لقد أنشأنا الآن تطبيقًا اسمه part1. كان بإمكان الأداة أيضًا تثبيت الاعتماديات المطلوبة وبدء التطبيق تلقائيًا لو أجبنا بـ "Yes" عن السؤال "Install with npm and start now?". لكننا سننفّذ هذه الخطوات يدويًا حتى نرى كيف تُنجَز.

بعد ذلك، لننتقل إلى مجلد التطبيق ونثبّت المكتبات المطلوبة:

cd part1
npm install

يُبدأ التطبيق كما يلي:

npm run dev

تقول وحدة التحكم إن التطبيق بدأ على منفذ localhost رقم 5173، أي العنوان http://localhost:5173/:

لقطة شاشة لوحدة التحكم تشغّل vite على localhost 5173

يبدأ Vite التطبيق افتراضيًا على المنفذ 5173. وإذا لم يكن هذا المنفذ متاحًا، يستخدم Vite رقم المنفذ الحر التالي.

افتح المتصفح ومحرر نصوص لتتمكن من عرض الشيفرة وصفحة الويب في الوقت نفسه على الشاشة:

لقطة شاشة لصفحة vite الأولية وبنية الملفات في vs code

تقع شيفرة التطبيق في مجلد src. لنبسّط الشيفرة الافتراضية بحيث يصبح محتوى الملف main.jsx كما يلي:

import ReactDOM from 'react-dom/client'

import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

ويصبح الملف App.jsx كما يلي

const App = () => {
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}

export default App

يمكن حذف الملفين App.css وindex.css والمجلد assets لأننا لا نحتاج إليها في تطبيقنا الآن.

مكوّن

يعرّف الملف App.jsx الآن مكوّن React باسم App. والأمر الموجود في السطر الأخير من الملف main.jsx

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

يعرض محتواه داخل عنصر div المعرّف في الملف index.html والذي يحمل قيمة id تساوي 'root'.

افتراضيًا، لا يحتوي الملف index.html على أي ترميز HTML مرئي لنا في المتصفح:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>part1</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

يمكنك أن تجرّب إضافة بعض HTML إلى الملف هناك. لكن عند استخدام React، يُعرَّف عادةً كل المحتوى الذي يجب عرضه كمكوّنات React.

لنلقِ نظرة أقرب على الشيفرة التي تعرّف المكوّن:

const App = () => (
  <div>
    <p>Hello world</p>
  </div>
)

كما خمّنت على الأرجح، سيُعرض المكوّن كوسم div يحيط بوسم p يحتوي على النص Hello world.

تقنيًا، يُعرَّف المكوّن كدالة JavaScript. ما يلي دالة (لا تستقبل أي معاملات):

() => (
  <div>
    <p>Hello world</p>
  </div>
)

ثم تُسنَد الدالة إلى متغير ثابت App:

const App = ...

توجد عدة طرق لتعريف الدوال في JavaScript. سنستخدم هنا دوال السهم، وهي موصوفة في نسخة من JavaScript تُعرف باسم ECMAScript 6، وتُسمى أيضًا ES6.

ولأن الدالة تتكوّن من تعبير واحد فقط، استخدمنا صيغة مختصرة تمثّل قطعة الشيفرة هذه:

const App = () => {
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}

بعبارة أخرى، تُعيد الدالة قيمة التعبير.

يمكن أن تحتوي الدالة التي تعرّف المكوّن على أي نوع من شيفرة JavaScript. عدّل مكوّنك ليصبح كما يلي:

const App = () => {
  console.log('Hello from component')
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}

export default App

ولاحظ ما يحدث في وحدة تحكم المتصفح

وحدة تحكم المتصفح تعرض console log مع سهم يشير إلى "Hello from component"

القاعدة الأولى في تطوير الويب من جهة الواجهة الأمامية:

أبقِ وحدة التحكم مفتوحة طوال الوقت

لنكرّر هذا معًا: أَعِدُ بإبقاء وحدة التحكم مفتوحة طوال الوقت خلال هذه الدورة، وطوال بقية حياتي عندما أعمل في تطوير الويب.

من الممكن أيضًا عرض محتوى ديناميكي داخل المكوّن.

عدّل المكوّن كما يلي:

const App = () => {
  const now = new Date()
  const a = 10
  const b = 20
  console.log(now, a+b)

  return (
    <div>
      <p>Hello world, it is {now.toString()}</p>
      <p>
        {a} plus {b} is {a + b}
      </p>
    </div>
  )
}

تُقيَّم أي شيفرة JavaScript داخل الأقواس المعقوفة، وتُدمج نتيجة هذا التقييم في المكان المحدد في HTML الذي ينتجه المكوّن.

لاحظ أنه يجب ألا تحذف السطر الموجود في أسفل المكوّن

export default App

لا يظهر export في معظم أمثلة مادة الدورة. وبدون export، ينهار المكوّن والتطبيق بأكمله.

هل تذكرت وعدك بإبقاء وحدة التحكم مفتوحة؟ ما الذي طُبع هناك؟

JSX

يبدو أن مكوّنات React تُعيد ترميز HTML. لكن الأمر ليس كذلك. فغالبًا ما يُكتب تخطيط مكوّنات React باستخدام JSX. فرغم أن JSX يشبه HTML، فإننا نتعامل مع طريقة لكتابة JavaScript. ففي الخفاء، تُترجم JSX التي تُعيدها مكوّنات React إلى JavaScript.

بعد الترجمة، يبدو تطبيقنا هكذا:

const App = () => {
  const now = new Date()
  const a = 10
  const b = 20
  return React.createElement(
    'div',
    null,
    React.createElement(
      'p', null, 'Hello world, it is ', now.toString()
    ),
    React.createElement(
      'p', null, a, ' plus ', b, ' is ', a + b
    )
  )
}

تتولى Babel عملية الترجمة. والمشاريع المنشأة باستخدام Vite مهيأة للترجمة تلقائيًا. سنتعلم المزيد عن هذا الموضوع في الجزء 7 من هذه الدورة.

من الممكن أيضًا كتابة React كـ "JavaScript خالص" دون استخدام JSX. لكن لن يفعل ذلك أي شخص يتمتع بعقل سليم.

عمليًا، يشبه JSX لغة HTML كثيرًا، مع فارق أنه يتيح لك تضمين محتوى ديناميكي بسهولة عبر كتابة شيفرة JavaScript المناسبة داخل الأقواس المعقوفة. وفكرة JSX شبيهة جدًا بالعديد من لغات القوالب، مثل Thymeleaf المستخدمة مع Java Spring، والتي تُستخدم على الخوادم.

JSX "شبيه بـ XML"، وهذا يعني أن كل وسم يحتاج إلى إغلاق. على سبيل المثال، السطر الجديد عنصر فارغ، ويمكن كتابته في HTML كما يلي:

<br>

لكن عند كتابة JSX، يجب إغلاق الوسم:

<br />

مكوّنات متعددة

لنعدّل الملف App.jsx كما يلي:

// highlight-start
const Hello = () => {
  return (
    <div>
      <p>Hello world</p>
    </div>
  )
}
// highlight-end

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello />
    </div>
  )
}

عرّفنا مكوّنًا جديدًا Hello واستخدمناه داخل المكوّن App. وبطبيعة الحال، يمكن استخدام المكوّن عدة مرات:

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello />
      // highlight-start
      <Hello />
      <Hello />
      // highlight-end
    </div>
  )
}

ملاحظة: يُحذف export في الأسفل في هذه الأمثلة، الآن وفي المستقبل. لكنه ما زال ضروريًا لتعمل الشيفرة

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

وهناك عُرف قوي آخر هو فكرة المكوّن الجذري المسمى App في قمة شجرة مكوّنات التطبيق. ومع ذلك، كما سنتعلم في الجزء 6، هناك حالات لا يكون فيها المكوّن App هو الجذر تمامًا، بل يكون ملفوفًا داخل مكوّن مساعد مناسب.

props: تمرير البيانات إلى المكوّنات

من الممكن تمرير البيانات إلى المكوّنات باستخدام ما يُسمى props.

لنعدّل المكوّن Hello كما يلي:

const Hello = (props) => {
  return (
    <div>
      <p>Hello {props.name}</p>
    </div>
  )
}

الآن أصبح للدالة التي تعرّف المكوّن معامل props. ويتلقى هذا المعامل كوسيط كائنًا يحتوي على حقول تقابل كل "props" يحددها مستخدم المكوّن.

وتُعرَّف الـ props كما يلي:

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello name='George' />
      <Hello name='Daisy' />
    </div>
  )
}

يمكن أن يكون هناك عدد اعتباطي من الـ props، ويمكن أن تكون قيمها نصوصًا "مكتوبة مباشرة" أو نتائج تعبيرات JavaScript. وإذا حُصل على قيمة prop باستخدام JavaScript فيجب لفّها بأقواس معقوفة.

لنعدّل الشيفرة بحيث يستخدم المكوّن Hello خاصيتي props:

const Hello = (props) => {
  console.log(props)
  return (
    <div>
      <p>
        Hello {props.name}, you are {props.age} years old
      </p>
    </div>
  )
}

const App = () => {
  const name = 'Peter'
  const age = 10

  return (
    <div>
      <h1>Greetings</h1>
      <Hello name='Maya' age={26 + 10} />
      <Hello name={name} age={age} />
    </div>
  )
}

الـ props التي يرسلها المكوّن App هي قيم المتغيرات، ونتيجة تقييم تعبير الجمع، ونص عادي.

كما يسجّل المكوّن Hello قيمة الكائن props في وحدة التحكم.

آمل حقًا أن تكون وحدة التحكم لديك مفتوحة. وإن لم تكن كذلك، فتذكّر ما وعدت به:

أَعِدُ بإبقاء وحدة التحكم مفتوحة طوال الوقت خلال هذه الدورة، وطوال بقية حياتي عندما أعمل في تطوير الويب

تطوير البرمجيات صعب. ويصبح أصعب إذا لم يستخدم المرء كل الأدوات المتاحة الممكنة مثل وحدة تحكم الويب والطباعة التصحيحية باستخدام console.log. فالمحترفون يستخدمون كليهما طوال الوقت، ولا يوجد سبب واحد يمنع المبتدئ من تبنّي استخدام هذه الأساليب المساعدة الرائعة التي ستجعل حياته أسهل بكثير.

رسالة خطأ محتملة

إذا كان مشروعك يستخدم إصدار React 18 أو أقدم، فقد تتلقى رسالة الخطأ التالية في هذه المرحلة:

لقطة شاشة لـ vs code تعرض خطأ eslint: "name is missing in props validation"

إنه ليس خطأ فعليًا، بل تحذير تسببه أداة ESLint. يمكنك إسكات تحذير react/prop-types بإضافة السطر التالي إلى الملف eslint.config.js

export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    settings: { react: { version: '18.3' } },
    plugins: {
      react,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...react.configs['jsx-runtime'].rules,
      ...reactHooks.configs.recommended.rules,
      'react/jsx-no-target-blank': 'off',
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      'react/prop-types': 0,
    },
  },
]

سنتعرّف على ESLint بمزيد من التفصيل في الجزء 3.

بعض الملاحظات

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

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

لقطة شاشة لخطأ prop غير معرّف

كما ذكرنا سابقًا، عند البرمجة باستخدام React، من الممكن بل ويستحق الأمر كتابة أوامر console.log() (التي تطبع في وحدة التحكم) داخل شيفرتك.

وتذكّر أيضًا أن الحرف الأول من أسماء مكوّنات React يجب أن يكون كبيرًا. إذا حاولت تعريف مكوّن كما يلي:

const footer = () => {
  return (
    <div>
      greeting app created by <a href='https://github.com/mluukkai'>mluukkai</a>
    </div>
  )
}

واستخدمته هكذا

const App = () => {
  return (
    <div>
      <h1>Greetings</h1>
      <Hello name='Maya' age={26 + 10} />
      <footer />
    </div>
  )
}

فلن تعرض الصفحة المحتوى المعرّف داخل مكوّن footer، وبدلًا من ذلك ينشئ React فقط عنصر footer فارغًا، أي عنصر HTML المدمج بدلًا من عنصر React المخصص الذي يحمل الاسم نفسه. وإذا غيّرت الحرف الأول من اسم المكوّن إلى حرف كبير، فإن React ينشئ عنصر div معرّفًا في مكوّن Footer، ويُعرض على الصفحة.

لاحظ أن محتوى مكوّن React يحتاج (عادةً) إلى أن يحتوي على عنصر جذر واحد. فإذا حاولنا، على سبيل المثال، تعريف المكوّن App دون عنصر div الخارجي:

const App = () => {
  return (
    <h1>Greetings</h1>
    <Hello name='Maya' age={26 + 10} />
    <Footer />
  )
}

فالنتيجة هي رسالة خطأ.

لقطة شاشة لخطأ عناصر جذر متعددة

استخدام عنصر جذر ليس الخيار الوحيد الذي يعمل. فـمصفوفة من المكوّنات حل صالح أيضًا:

const App = () => {
  return [
    <h1>Greetings</h1>,
    <Hello name='Maya' age={26 + 10} />,
    <Footer />
  ]
}

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

ولأن عنصر الجذر مطلوب، فإن لدينا عناصر div "إضافية" في شجرة DOM. ويمكن تجنّب ذلك باستخدام fragments، أي بلفّ العناصر التي سيعيدها المكوّن بعنصر فارغ:

const App = () => {
  const name = 'Peter'
  const age = 10

  return (
    <>
      <h1>Greetings</h1>
      <Hello name='Maya' age={26 + 10} />
      <Hello name={name} age={age} />
      <Footer />
    </>
  )
}

الآن تُترجم الشيفرة بنجاح، ولم يعد DOM الذي ينشئه React يحتوي على عنصر div الإضافي.

لا تعرض الكائنات

تأمّل تطبيقًا يطبع أسماء أصدقائنا وأعمارهم على الشاشة:

const App = () => {
  const friends = [
    { name: 'Peter', age: 4 },
    { name: 'Maya', age: 10 },
  ]

  return (
    <div>
      <p>{friends[0]}</p>
      <p>{friends[1]}</p>
    </div>
  )
}

export default App

لكن لا يظهر شيء على الشاشة. قضيت 15 دقيقة أحاول إيجاد مشكلة في الشيفرة، لكنني لم أستطع معرفة أين يمكن أن تكون المشكلة.

وأخيرًا أتذكر الوعد الذي قطعناه

أَعِدُ بإبقاء وحدة التحكم مفتوحة طوال الوقت خلال هذه الدورة، وطوال بقية حياتي عندما أعمل في تطوير الويب

تصرخ وحدة التحكم بالأحمر:

devtools تعرض خطأ مع إبراز حول "Objects are not valid as a React child"

جوهر المشكلة هو Objects are not valid as a React child، أي أن التطبيق يحاول عرض كائنات ويفشل مرة أخرى.

تحاول الشيفرة عرض معلومات أحد الأصدقاء كما يلي

<p>{friends[0]}</p>

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

{ name: 'Peter', age: 4 }

في React، يجب أن تكون العناصر الفردية المعروضة داخل الأقواس المعقوفة قيمًا أولية، مثل الأعداد أو النصوص.

الإصلاح كما يلي

const App = () => {
  const friends = [
    { name: 'Peter', age: 4 },
    { name: 'Maya', age: 10 },
  ]

  return (
    <div>
      <p>{friends[0].name} {friends[0].age}</p>
      <p>{friends[1].name} {friends[1].age}</p>
    </div>
  )
}

export default App

فالآن يُعرض اسم الصديق منفصلًا داخل الأقواس المعقوفة

{friends[0].name}

والعمر

{friends[0].age}

بعد تصحيح الخطأ، يجب أن تمسح رسائل الخطأ من وحدة التحكم بالضغط على 🚫 ثم تعيد تحميل محتوى الصفحة وتتأكد من عدم عرض أي رسائل خطأ.

ملاحظة إضافية صغيرة على ما سبق. يسمح React أيضًا بعرض المصفوفات إذا كانت المصفوفة تحتوي على قيم صالحة للعرض (مثل الأعداد أو النصوص). لذا سيعمل البرنامج التالي، وإن كانت النتيجة قد لا تكون ما نريد:

const App = () => {
  const friends = [ 'Peter', 'Maya']

  return (
    <div>
      <p>{friends}</p>
    </div>
  )
}

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

تمارين 1.1.-1.2.

تُسلَّم التمارين عبر GitHub، وبتعليم التمارين كمنجزة في تبويب «تسليماتي» (my submissions) في تطبيق التسليم.

تُسلَّم التمارين جزءًا واحدًا في كل مرة. وعندما تسلّم تمارين جزء من الدورة، لم يعد بإمكانك تسليم تمارين غير منجزة للجزء نفسه.

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

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

من بنى الملفات العملية جدًا لمستودع التسليم ما يلي:

part0
part1
  courseinfo
  unicafe
  anecdotes
part2
  phonebook
  countries

انظر إلى مستودع التسليم النموذجي هذا!

لكل جزء من الدورة يوجد مجلد، يتفرّع بدوره إلى مجلدات تحتوي على سلسلة من التمارين، مثل "unicafe" للجزء 1.

تبني معظم تمارين الدورة تطبيقًا أكبر، مثل courseinfo وunicafe وanecdotes في هذا الجزء، شيئًا فشيئًا. ويكفي تسليم التطبيق المكتمل. ويمكنك إجراء commit بعد كل تمرين، لكن ذلك ليس إلزاميًا. فمثلًا يُبنى تطبيق معلومات الدورة في التمارين 1.1.-1.5. وما تحتاج إلى تسليمه هو النتيجة النهائية بعد 1.5 فقط!

لكل تطبيق ويب مخصص لسلسلة من التمارين، يُوصى بتسليم جميع الملفات المتعلقة بذلك التطبيق، باستثناء المجلد node_modules.

1.1: معلومات الدورة، الخطوة 1

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

استخدم Vite لتهيئة تطبيق جديد. عدّل main.jsx ليطابق ما يلي

import ReactDOM from 'react-dom/client'

import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

وApp.jsx ليطابق ما يلي

const App = () => {
  const course = 'Half Stack application development'
  const part1 = 'Fundamentals of React'
  const exercises1 = 10
  const part2 = 'Using props to pass data'
  const exercises2 = 7
  const part3 = 'State of a component'
  const exercises3 = 14

  return (
    <div>
      <h1>{course}</h1>
      <p>
        {part1} {exercises1}
      </p>
      <p>
        {part2} {exercises2}
      </p>
      <p>
        {part3} {exercises3}
      </p>
      <p>Number of exercises {exercises1 + exercises2 + exercises3}</p>
    </div>
  )
}

export default App

واحذف الملفين الإضافيين App.css وindex.css، واحذف أيضًا المجلد assets.

لسوء الحظ، التطبيق كله في المكوّن نفسه. أعد هيكلة الشيفرة بحيث تتكوّن من ثلاثة مكوّنات جديدة: Header وContent وTotal. تظل كل البيانات في المكوّن App، الذي يمرّر البيانات اللازمة إلى كل مكوّن باستخدام props. يتولى Header عرض اسم الدورة، ويعرض Content الأجزاء وعدد تمارينها، ويعرض Total العدد الإجمالي للتمارين.

عرّف المكوّنات الجديدة في الملف App.jsx.

سيكون جسم المكوّن App على النحو التالي تقريبًا:

const App = () => {
  // تعريفات الثوابت

  return (
    <div>
      <Header course={course} />
      <Content ... />
      <Total ... />
    </div>
  )
}

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

قد يبدو التقدّم الحذر بخطوات صغيرة بطيئًا، لكنه في الواقع أسرع طريقة للتقدّم بفارق كبير. وقد صرّح مطوّر البرمجيات الشهير Robert "Uncle Bob" Martin

"الطريقة الوحيدة للإسراع هي أن تسير جيدًا"

أي أنه وفقًا لـ Martin، فإن التقدّم الحذر بخطوات صغيرة هو حتى الطريقة الوحيدة لتكون سريعًا.

1.2: معلومات الدورة، الخطوة 2

أعد هيكلة المكوّن Content بحيث لا يعرض بنفسه أي أسماء للأجزاء أو أعداد تمارينها. وبدلًا من ذلك، يعرض فقط ثلاثة مكوّنات Part، يعرض كل منها اسم جزء وعدد تمارينه.

const Content = ... {
  return (
    <div>
      <Part .../>
      <Part .../>
      <Part .../>
    </div>
  )
}

يمرّر تطبيقنا المعلومات بطريقة بدائية جدًا في الوقت الحالي، لأنه يعتمد على متغيرات فردية. وسنصلح ذلك في الجزء 2، لكن قبل ذلك، لننتقل إلى part1b لتعلّم JavaScript.