full_stack_101 تصيير مجموعة بيانات ومفهوم الوحدات في React


ابراهيم الخضور

لنلقي نظرة سريعة على المواضيع التي بدت صعبة في النسخة السابقة من المنهاج قبل أن ننتقل إلى موضوع جديد.

التعليمة console.log

اقتباس

ما الفرق بين مبرمج JavaScript محترف وآخر هاوٍ؟ يستعمل المبرمج المحترف التعليمة console.log من 10 إلى 100 مرة أكثر.

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

تذكر لا تضم ما تريد إظهاره على الطرفية مستخدمًا إشارة الجمع (+) كأسلوب Java:

console.log('props value is' + props)

بل افصل الأشياء التي تريد طباعتها بفاصلة (,):

console.log('props value is', props)

إن استخدمت أسلوب Java فلن تحصل على النتيجة المطلوبة:

props value is [Object object]

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

نصيحة للاحتراف: استخدام مقاطع شيفرة Visual Studio
يمكن بسهولة إنشاء مقاطع شيفرة باستخدام Visual studio، حيث تعتبر هذه المقاطع أسطرًا برمجية قابلة لإعادة الاستخدام. وتشابه في عملها أسطر sout في برنامج Netbeans. يمكنك إيجاد المزيد من الإرشادات حول إنشاء واستخدام المقاطع من خلال الإنترنت. كما يمكنك البحث عن مقاطع جاهزة على شكل إضافات لبرنامج Visual Studio مثل الإضافة xabikos.ReactSnippets.

يعتبر المقطع الذي ينفذ الأمر ()console.log leg في الشيفرة التالية الأكثر أهمية حيث يوضح الأمرclog:

{
 "console.log": {
    "prefix": "clog",
    "body": [
      "console.log('$1')",
    ],
    "description": "Log output to console"
  }
}

مصفوفات JavaScript

انطلاقًا من هذه النقطة سنستخدم توابعًا للتعامل مع المصفوفات في JavaScript وفق أسلوب البرمجة بالدوال مثل التوابع find وfilter وmap. تشابه هذه التوابع في مبدأ عملها "المجاري (Streams)" في Java 8 التي اعتُمدت خلال السنوات القليلة الماضية في مقررات قسم علوم الحاسب في جامعة هلسنكي. إن بدا لك غريبًا أسلوب البرمجة بالدوال مع المصفوفات، يمكنك متابعة مقاطع الفيديو الأجنبية التالية:

معالجات الأحداث: نظرة ثانية

أثبت مفهوم معالجة الأحداث أنه صعب استنادًا إلى تقييمنا لمنهاج العام الفائت. لذا من الأجدى أن تلقي نظرة ثانية على هذا الموضوع الذي ناقشناه في الفصل السابق ضمن فقرة "عودة إلى معالجة الأحداث" إذا أردت إنعاش معلوماتك. كما برزت تساؤلات عدة حول تمرير معالجات الأحداث إلى المكوِّن الابن للمكوِّن الذي أنشأناه سابقًا App، لذلك يفضل مراجعة هذا الموضوع.

تصيير مجموعات البيانات

سنعمل الآن على الواجهة الأمامية للتطبيقات (frontend) أو ما يعرف بكتابة شيفرة التطبيق من جهة المتصفح. لنبدأ بتطبيق React مشابه للمثال النموذجي الذي رأيناه في مقال أساسيات بناء تطبيقات الويب:

import React from 'react'
import ReactDOM from 'react-dom'

const notes = [
  {
    id: 1,
    content: 'HTML is easy',
    date: '2019-05-30T17:30:31.098Z',
    important: true
  },
  {
    id: 2,
    content: 'Browser can execute only Javascript',
    date: '2019-05-30T18:39:34.091Z',
    important: false
  },
  {
    id: 3,
    content: 'GET and POST are the most important methods of HTTP protocol',
    date: '2019-05-30T19:20:14.298Z',
    important: true
  }
]

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        <li>{notes[0].content}</li>
        <li>{notes[1].content}</li>
        <li>{notes[2].content}</li>
      </ul>
    </div>
  )
}

ReactDOM.render(
  <App notes={notes} />,
  document.getElementById('root')
)

تحتوي كل ملاحظة على قيمة نصية وعبارة زمنية وقيمة منطقية (Boolean) تحدد إذا ما كانت الملاحظة هامة أم لا وكذلك قيمة فريدة (unique value) لتمييز الملاحظة تدعى id. يعمل التطبيق السابق على مبدأ وجود ثلاث ملاحظات فقط في المصفوفة، وتصيّر كل ملاحظة باستخدام القيمة id مباشرة:

<li>{notes[1].content}</li>

لكن من الأفضل أن نظهر المعلومات المستخلصة من المصفوفة على شكل عنصر React باستخدام الدالة map:

notes.map(note => <li>{note.content}</li>)

ستكون النتيجة مصفوفة من العناصر li:

[
  <li>HTML is easy</li>,
  <li>Browser can execute only Javascript</li>,
  <li>GET and POST are the most important methods of HTTP protocol</li>,
]

يمكن إضافتها إلى القائمة ul:

const App = (props) => {
  const { notes } = props
  return (
    <div>
      <h1>Notes</h1>
      <ul>
      {notes.map(note => <li>{note.content}</li>)}
      </ul>
    </div>
  )
}

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

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>

     <ul>
       {notes.map(note => <li>{note.content}</li>)}
     </ul>
   </div>
  )
}

الصفات المفتاحية key-attribute

على الرغم من أن التطبيق سيعمل بشكل طبيعي، إلّا أن تحذيرًا مزعجًا يظهر على الطرفية:

unique_value_01.png

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

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li key={note.id}>{note.content}</li>        
         )}
      </ul>
    </div>
  )
}

وهكذا ستختفي رسالة التحذير. تستخدم React الصفات المفتاحية لتقرر الآلية التي تحدّث فيها مظهر التطبيق عندما يعاد تصيير مكوِّن. يمكنك دومًا استخدام وثائق React لمعرفة المزيد.

التابع Map

إنّ فهمك لآلية عمل التابع map عند التعامل مع المصفوفات أمر حيوي سيلزمك دائمًا. يحتوي التطبيق على مصفوفة تدعى notes:

const notes = [
  {
    id: 1,
    content: 'HTML is easy',
    date: '2019-05-30T17:30:31.098Z',
    important: true
  },
  {
    id: 2,
    content: 'Browser can execute only Javascript',
    date: '2019-05-30T18:39:34.091Z',
    important: false
  },
  {
    id: 3,
    content: 'GET and POST are the most important methods of HTTP protocol',
    date: '2019-05-30T19:20:14.298Z',
    important: true
  }
]

لنتوقف قليلًا ونرى كيف يعمل map. لنقل أن الشيفرة التالية قد أضيفت إلى نهاية الملف:

const result = notes.map(note => note.id)
console.log(result)

سينشئ التابع map مصفوفة جديدة قيمها [1,2,3] انطلاقًا من عناصر المصفوفة الأصلية. ويستخدم دالة كمُعامل له. ولهذه الدالة الشكل التالي:

note => note.id

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

(note) => {
  return note.id
}

تقبل الدالة السابقة الكائن note كمُعامل، وتعيد قيمة الحقل id منه. لكن إن غيرنا الشيفرة لتصبح:

const result = notes.map(note => note.content)

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

notes.map(note =>
  <li key={note.id}>{note.content}</li>
)

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

note => <li key={note.id}>{note.content}</li>

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

استخدام مصفوفة القرائن كمفاتيح: الأسلوب المعاكس

كان بإمكاننا إخفاء رسالة الخطأ السابقة باستخدام مصفوفة القرائن (Array indexes) كمفاتيح، حيث نستخلص هذه القرائن بتمرير معامل آخر إلى دالة الاستدعاء التي استخدمناها كمعامل للتابع map:

notes.map((note, i) => ...)

عندما نستدعي الدالة بالشكل السابق ستأخذ i قيمة القرينة التي تشير إلى موقع الملاحظة في الكائن note، وبهذا الأسلوب سنعرّف طريقةً لإنشاء المطلوب دون أخطاء:

<ul>
  {notes.map((note, i) => 
    <li key={i}>
      {note.content}
    </li>
  )}
</ul>

على الرغم من ذلك لا نوصي باتباع هذا الأسلوب نظرًا للمشاكل غير المتوقعة التي يمكن أن تحدث حتى لو بدا أنّ التطبيق يعمل بشكل جيد. يمكنك الاطلاع أكثر بقراءة مقالات تتعلق بالموضوع.

إعادة تكوين الوحدات

لندعّم الشيفرة التي كتبناها قليلًا. طالما أن اهتمامنا ينحصر في الحقلnotes فقط من خصائص المكوِّن، لنستخلص قيمة الحقل المطلوب مباشرة بطريقة التفكيك:

const App = ({ notes }) => {  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li key={note.id}>
            {note.content}
          </li>
        )}
      </ul>
    </div>
  )
}

وإن أردت تذكّر معلوماتك عن موضوع التفكيك راجع فقرة التفكيك في القسم السابق. سنعرف الآن مكوِّنًا جديدًا Note لعرض الملاحظة:

const Note = ({ note }) => {  return (<li>{note.content}</li>  )}
const App = ({ notes }) => {
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => <Note key={note.id} note={note} /> )}
      </ul>
    </div>
  )
}

لاحظ أن الصفة المفتاحية ارتبطت الآن بالمكوِّن Note وليس بالعنصر li.

يمكن كتابة تطبيق React بأكمله ضمن ملف واحد. لكن جرت العادة على كتابة كل مكوِّن في ملف خاص على شكل وحدة ES6، على الرغم من أن هذا الأسلوب ليس عمليًا جدًا. لقد استخدمنا الوحدات منذ البداية في شيفرتنا كما فعلنا في أول سطرين من الملف:

import React from 'react'
import ReactDOM from 'react-dom'

فلقد أدرجنا وحدتين لاستخدامهما في الملف. حيث أسندت الوحدة react إلى متغيّر يدعى React وكذلك أسندت الوحدة react-dom إلى المتغيّر ReactDOM. لننقل الآن المكوِّن Note إلى وحدته الخاصة. توضع المكوِّنات عادة في مجلد يدعى components ضمن المجلد src الموجود في المجلد الرئيسي للتطبيق، ويفضل تسمية الملف باسم المكوِّن. دعونا ننفذ ما ذكرناه ونضع المكوِّن Note في ملف جديد يدعى Note.js كالتالي:

import React from 'react'

const Note = ({ note }) => {
  return (
    <li>{note.content}</li>
  )
}

export default Note

لاحظ أننا أدرجنا React في السطر الأول لأن المكوِّن هو مكوِّن React. سيصدّر السطر الأخير الوحدة الجديدة التي عرفناها باسم Note لكي تستخدم في مكان آخر. سندرج الآن الوحدة الجديدة في الملف index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import Note from './components/Note'
const App = ({ notes }) => {
  // ...
}

وهكذا يمكن استخدام الوحدة الجديدة بإسنادها إلى المتغيّر Note. وتذكر كتابة عنوان الوحدة بطريقة العنوان النسبي عند إدراجها.

'./components/Note'

تشير النقطة (.) في بداية العنوان إلى المسار الحالي وبالتالي فإن الملف Note.js موجود ضمن المجلد الفرعي component والموجود ضمن المسار الحالي (المجلد الذي يحوي التطبيق).

ملاحظة: يمكن إغفال كتابة اللاحقة js .

لنصرح الآن عن المكوِّن App أيضًا في وحدة خاصة به. وطالما أنه المكوِّن الجذري سنضعه مباشرة في المجلد src:

import React from 'react'
import Note from './components/Note'

const App = ({ notes }) => {
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map((note) => 
          <Note key={note.id} note={note} />
        )}
      </ul>
    </div>
  )
}

export default App

ما بقي إذًا في الملف index.js هو التالي:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
const notes = [
  // ...
]

ReactDOM.render(
  <App notes={notes} />,
  document.getElementById('root')
)

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

app_add_github_002.png

قبل أن تشغّل التطبيق باستعمال الأمر npm start، نفذ أولًا الأمر npm install.

عندما ينهار التطبيق

سترى في بداياتك البرمجية أو حتى بعد 30 عامًا من كتابة الشيفرات أن التطبيق قد ينهار أحيانًا وبشكل مفاجئ. وسيكون الأمر أسوأ في اللغات التي يمكن كتابة تعليماتها بشكل ديناميكي أثناء التنفيذ مثل JavaScript. فلا يتاح عندها للمتصفح من التحقق مثلًا من نوع البيانات التي يحملها متغيّر أو التي تمرر لدالة.

يظهر الشكل التالي مثالًا عن انهيار تطبيق React:

app_break_003.png

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

const Course = ({ course }) => (
  <div>
   <Header course={course} />
  </div>
)

const App = () => {
  const course = {
    // ...
  }

  return (
    <div>
      <Course course={course} />
    </div>
  )
}

سنقتفي أثر الخطأ بإضافة التعليمة console.log إلى الشيفرة. وطالما أن المكوِّن App سيصيّر أولًا فمن المفيد وضع أول تعليمة طباعة ضمنه:

const App = () => {
  const course = {
    // ...
  }

  console.log('App works...')
  return (
    // ..
  )
}

عليك طبعًا النزول إلى الأسفل مرورًا بقائمة الأخطاء الطويلة حتى ترى نتيجة الطباعة.

using_console_log_004.png

حالما تتأكد من أن الأمور جيدة حتى نقطة معينة، عليك نقل تعليمة الطباعة إلى نقطة أعمق. وتذكر أن التصريح عن مكوِّن بعبارة واحدة أو عن دالة دون أن تعيد قيمة، سيجعل الطباعة على الطرفية أصعب.

const Course = ({ course }) => (
  <div>
   <Header course={course} />
  </div>
)

لذا غيًر تصريح المكوِّن إلى شكله النموذجي حتى تستطيع الطباعة:

const Course = ({ course }) => { 
  console.log(course)  return (
    <div>
    <Header course={course} />
    </div>
  )
}

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

const Course = (props) => {  console.log(props)  const { course } = props
  return (
    <div>
    <Header course={course} />
    </div>
  )
}

إن لم تُحل المشكلة، لا يمكنك عندها سوى اصطياد الأخطاء بزرع تعليمة الطباعة console.log في أماكن مختلفة من الشيفرة.

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

التمارين 2.1 - 2.5

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

تُسلَّم تمارين كل قسم دفعة واحدة، فلا يمكنك تسليم أية تمارين أنجزتها مؤخرًا من قسم ما إذا كنت قد سلمت غيرها من القسم ذاته.

يضم هذا القسم تمارين أكثر من القسمين السابقين، لا تسلم أيًا منها حتى تنجزها كلها.

تحذير: سيجعل create_react_app المشروع مستودع git إلا إذا أنشأت مشروعك داخل مستودع موجود مسبقًا. لن تريد فعل ذلك على الأغلب، لذا نفذ الأمر rm -rf .git عند جذر مشروعك.

2.1 معلومات عن المنهاج: الخطوة 6

سننهي في هذا التمرين تصيير محتوى المنهاج الذي استعرضناه في التمارين 1.1-1.5. يمكنك البدء من الشيفرة الموجودة في نماذج الأجوبة. ستجد نماذج أجوبة القسم 1 في منظومة تسليم الملفات. انقر على my submissions في الأعلى ثم انقر على show الموجودة ضمن العمود solutions في السطر المخصص للقسم 1. انقر على الملف index.js للاطلاع على حل تمرين "معلومات عن المنهاج course info" تحت عبارة kurssitiedot التي تعني (معلومات عن المنهاج).

تنبيه: إذا نسخت مشروعًا من مكان لآخر، سيتوجب عليك حذف المجلد node_modules وإعادة تثبيت ملفات الارتباط (Dependencies) مرة أخرى بتنفيذ الأمر npm install قبل أن تشغل التطبيق.

لا يفضل أن تنسخ المحتوى الكامل لتطبيق أو أن تضيف المجلد node_modules إلى منظومة التحكم بالإصدار أو أن تقوم بكلا العملين.

لنغيّر الآن المكوِّن App كما يلي:

const App = () => {
  const course = {
    id: 1,
    name: 'Half Stack application development',
    parts: [
      {
        name: 'Fundamentals of React',
        exercises: 10,
        id: 1
      },
      {
        name: 'Using props to pass data',
        exercises: 7,
        id: 2
      },
      {
        name: 'State of a component',
        exercises: 14,
        id: 3
      }
    ]
  }

  return <Course course={course} />
}

عّرف مكوِّنًا يدعى Course مسؤولًا عن تنسيق منهاج واحد. يمكن أن يتخذ التطبيق الهيكلية التالية:

App
  Course
    Header
    Content
      Part
      Part
      ...

سيضم المكوِّن Course كل المكوِّنات التي عرّفناها في القسم السابق والمسؤولة عن تصيير اسم المنهاج وأقسامه. ستبدو الصفحة بعد التصيير على الشكل التالي:

comp_course_render_005.png

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

2.2 معلومات عن المنهاج: الخطوة 7

أظهر عدد التمارين الكلية في المنهاج.

show_exe_sum_006.png

2.3 معلومات عن المنهاج: الخطوة 8 *

إن لم تجد طريقة لحل التمرين السابق، احسب العدد الكلي للتمارين باستخدام التابع reduce الذي يستعمل مع المصفوفات.

نصيحة للاحتراف 1: إن بدت شيفرتك بالشكل التالي:

const total = 
  parts.reduce((s, p) => someMagicHere)

ولم تعمل، يجدر بك استخدام تعليمة الطباعة على الطرفية، والتي تتطلب إعادة كتابة الدالة السهمية بشكلها الكامل:

const total = parts.reduce((s, p) => {
  console.log('what is happening', s, p)
  return someMagicHere 
})

نصيحة للاحتراف 2: يمكنك استخدام إضافة للمحرر VS يمكنها تحويل الدالة السهمية المختصرة إلى الكاملة وبالعكس.

vs_plugin_007.png

2.4: معلومات عن المنهاج: خطوة 9

وسّع التطبيق ليعمل مع أي عدد افتراضي من المناهج:

const App = () => {
  const courses = [
    {
      name: 'Half Stack application development',
      id: 1,
      parts: [
        {
          name: 'Fundamentals of React',
          exercises: 10,
          id: 1
        },
        {
          name: 'Using props to pass data',
          exercises: 7,
          id: 2
        },
        {
          name: 'State of a component',
          exercises: 14,
          id: 3
        },
        {
          name: 'Redux',
          exercises: 11,
          id: 4
        }
      ]
    }, 
    {
      name: 'Node.js',
      id: 2,
      parts: [
        {
          name: 'Routing',
          exercises: 3,
          id: 1
        },
        {
          name: 'Middlewares',
          exercises: 7,
          id: 2
        }
      ]
    }
  ]

  return (
    <div>
      // ...
    </div>
  )
}

يمكن أن يبدو التطبيق بالشكل التالي:

app_course_extended_008.png

2.5 وحدات منفصلة

صرح عن المكوِّن Course في وحدة منفصلة يمكن إدراجها في ملف المكوِّن App. يمكن أن تضع كل المكوِّنات الفرعية للمنهاج ضمن نفس الوحدة.

ترجمة -وبتصرف- للفصل Rendering a collection,modules من سلسلة Deep Dive Into Modern Web Development





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن