full_stack_101 أهمية مصفوفة المكونات الأبناء props.Children والحزمة proptypes في عملية تسجيل الدخول في تطبيق React


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

عرض واجهة تسجيل الدخول في الحالات الملائمة

لنعدل التطبيق بحيث لا تظهر واجهة تسجيل الدخول بشكل افتراضي:

ogin_show_01.png

ستظهر الواجهة عندما ينقر المستخدم على الزر "Login":

login_button_02.png

يمكن للمستخدم إغلاق الواجهة أيضًا بالنقر على الزر "Cancel".

سنبدأ بنقل نموذج تسجيل الدخول إلى المكوِّن الخاص به:

import React from 'react'

const LoginForm = ({
   handleSubmit,
   handleUsernameChange,
   handlePasswordChange,
   username,
   password
  }) => {
  return (
    <div>
      <h2>Login</h2>

      <form onSubmit={handleSubmit}>
        <div>
          username
          <input
            value={username}
            onChange={handleUsernameChange}
          />
        </div>
        <div>
          password
          <input
            type="password"
            value={password}
            onChange={handlePasswordChange}
          />
      </div>
        <button type="submit">login</button>
      </form>
    </div>
  )
}

export default LoginForm

عُرّفت الحالة وكل الدوال المتعلقة بالنموذج خارج المكوِّن وتُمرر إليه كخصائص.

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

pre widget

const LoginForm = (props) => {
  return (
    <div>
      <h2>Login</h2>
      <form onSubmit={props.handleSubmit}>
        <div>
          username
          <input
            value={props.username}
            onChange={props.handleChange}
            name="username"
          />
        </div>
        // ...
        <button type="submit">login</button>
      </form>
    </div>
  )
}

حيث نحصل فيها على خصائص الكائنprop باستعمال التابع prop.handleSubmit، سنسند الخصائص مباشرة إلى متغيراتها.

إحدى الطرق السريعة في إضافة الوظيفة، هو تعديل الدالة loginForm العائدة للمكوِّن App كالتالي:

const App = () => {
  const [loginVisible, setLoginVisible] = useState(false)
  // ...

  const loginForm = () => {
    const hideWhenVisible = { display: loginVisible ? 'none' : '' }
    const showWhenVisible = { display: loginVisible ? '' : 'none' }

    return (
      <div>
        <div style={hideWhenVisible}>
          <button onClick={() => setLoginVisible(true)}>log in</button>
        </div>
        <div style={showWhenVisible}>
          <LoginForm
            username={username}
            password={password}
            handleUsernameChange={({ target }) => setUsername(target.value)}
            handlePasswordChange={({ target }) => setPassword(target.value)}
            handleSubmit={handleLogin}
          />
          <button onClick={() => setLoginVisible(false)}>cancel</button>
        </div>
      </div>
    )
  }

  // ...
}

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

<button onClick={() => setLoginVisible(true)}>log in</button>

<button onClick={() => setLoginVisible(false)}>cancel</button>

تُحدَّد إمكانية ظهور المكوِّن بتنسيقه ضمن السياق، حيث سيختفي المكوًن إن كانت قيمة الخاصية display هي (none).

const hideWhenVisible = { display: loginVisible ? 'none' : '' }
const showWhenVisible = { display: loginVisible ? '' : 'none' }

<div style={hideWhenVisible}>
  // button
</div>

<div style={showWhenVisible}>
  // button
</div>

لاحظ أننا استخدمنا مجددًا العامل"؟" ثلاثي المعاملات. إن كان المتغير loginVisible "صحيحًا"، ستحمل قاعدة CSS للمكوِّن القيمة التالية:

display: 'none';

وإن كان loginVisible "خاطئًا"، لن تحمل الخاصية display أية قيمة تتعلق بعرض المكوِّن.

المكونات الأبناء (أو المصفوفة props.children)

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

هدفنا حاليًا إضافة مكوِّن متبدّل (Togglable) يُستخدم بالطريقة التالية:

<Togglable buttonLabel='login'>
  <LoginForm
    username={username}
    password={password}
    handleUsernameChange={({ target }) => setUsername(target.value)}
    handlePasswordChange={({ target }) => setPassword(target.value)}
    handleSubmit={handleLogin}
  />
</Togglable>

تختلف طريقة استخدام المكوِّن قليلًا عن المكوٍّنات السابقة. فللمكوّن معرفات بداية ونهاية تحيط بمكوّن LoginForm. تصطلح React على تسمية المكوًّن LoginForm بالمكوِّن الإبن للمكوِّن Togglable.

يمكننا إضافة عناصر React بين معرفي البداية والنهاية للمكوِّن Togglable كما في المثال التالي:

<Togglable buttonLabel="reveal">
  <p>this line is at start hidden</p>
  <p>also this is hidden</p>
</Togglable>

الشيفرة التالية هي شيفرة المكوِّن Togglable:

import React, { useState } from 'react'

const Togglable = (props) => {
  const [visible, setVisible] = useState(false)

  const hideWhenVisible = { display: visible ? 'none' : '' }
  const showWhenVisible = { display: visible ? '' : 'none' }

  const toggleVisibility = () => {
    setVisible(!visible)
  }

  return (
    <div>
      <div style={hideWhenVisible}>
        <button onClick={toggleVisibility}>{props.buttonLabel}</button>
      </div>
      <div style={showWhenVisible}>
        {props.children}
        <button onClick={toggleVisibility}>cancel</button>
      </div>
    </div>
  )
}

export default Togglable

إن الجزء الجديد والمهم في الشيفرة هي الخاصية props.children، والتي يستخدم في الإشارة إلى المكوِّنات الأبناء لمكوِّن، والتي تمثل عناصر React الموجودة ضمن معرفي بداية ونهاية المكوّن.

ستصيّر الآن المكونات الأبناء بالشيفرة ذاتها التي تصيّر المكون الأب:

<div style={showWhenVisible}>
  {props.children}
  <button onClick={toggleVisibility}>cancel</button>
</div>

وعلى خلاف الخصائص الاعتيادية التي رأيناها سابقًا يضاف "الأبناء" تلقائيًا من قبل React وتكون موجودة دائمًا.

لو عرّفنا مكوّنًا له معرف نهاية تلقائي "</" كالمكوِّن التالي:

<Note
  key={note.id}
  note={note}
  toggleImportance={() => toggleImportanceOf(note.id)}
/>

ستشكل الخاصية props.children مصفوفة فارغة.

يمكن إعادة استعمال المكوِّن togglable لإضافة خاصية العرض والإخفاء للنموذج الذي استخدمناه في إنشاء ملاحظة جديدة.

قبل أن نفعل ذلك، لننقل شيفرة نموذج إنشاء الملاحظات الجديدة إلى مكوِّن مستقل:

const NoteForm = ({ onSubmit, handleChange, value}) => {
  return (
    <div>
      <h2>Create a new note</h2>

      <form onSubmit={onSubmit}>
        <input
          value={value}
          onChange={handleChange}
        />
        <button type="submit">save</button>
      </form>
    </div>
  )
}

سنعرّف تاليًا مكوِّن النموذج داخل المكوّن Togglable:

<Togglable buttonLabel="new note">
  <NoteForm
    onSubmit={addNote}
    value={newNote}
    handleChange={handleNoteChange}
  />
</Togglable>

يمكنك إيجاد الشيفرة الكاملة لتطبيقنا الحالي ضمن الفرع part5-4 في المستودع المخصص للتطبيق على GitHub.

حالة النماذج

تتواجد حالة التطبيق في المكون الأعلى App. حيث ينص توثيق React على ما يلي بخصوص مكان تواجد الحالة:

اقتباس

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

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

سيتغيّر مكِّون الملاحظة إلى الشكل التالي:

import React, {useState} from 'react' 

const NoteForm = ({ createNote }) => {
  const [newNote, setNewNote] = useState('') 

  const handleChange = (event) => {
    setNewNote(event.target.value)
  }

  const addNote = (event) => {
    event.preventDefault()
    createNote({
      content: newNote,
      important: Math.random() > 0.5,
    })

    setNewNote('')
  }

  return (
    <div>
      <h2>Create a new note</h2>

      <form onSubmit={addNote}>
        <input
          value={newNote}
          onChange={handleChange}
        />
        <button type="submit">save</button>
      </form>
    </div>
  )
}

export default NoteForm

نُقلت صفة الحالة newNote ومعالج الحدث المسؤول عن تغييرها من المكوِّن App إلى المكوِّن المسؤول عن نموذج الملاحظات. وبقيت فقط الخاصية createNote وهي دالة يستدعيها النموذج عندما تُنشأ ملاحظة جديدة.

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

const App = () => {
  // ...
  const addNote = (noteObject) => {
    noteService
      .create(noteObject)
      .then(returnedNote => {
        setNotes(notes.concat(returnedNote))
      })
  }
  // ...
  const noteForm = () => (
    <Togglable buttonLabel='new note'>
      <NoteForm createNote={addNote} />
    </Togglable>
  )

  // ...
}

يمكن أن نكرر هذه العملية على نموذج تسجيل الدخول، لكننا سنترك ذلك للتمارين الإختيارية.

يمكنك إيجاد شيفرة التطبيق في الفرع part5-5 على GitHub.

إنشاء مراجع إلى المكوِّنات باستعمال ref

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

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

هناك طرق عديدة لإغلاق النموذج من المكوّن الأب، لكننا سنستعمل آلية المرجع ref الخاصة بالمكتبة React والتي تؤمن مرجعًا إلى المكوِّن.

لنعدّل المكوّن App ليصبح كالتالي:

import React, { useState, useRef } from 'react'
const App = () => {
  // ...
  const noteFormRef = useRef()
  const noteForm = () => (
    <Togglable buttonLabel='new note' ref={noteFormRef}>
    <NoteForm createNote={addNote} />
    </Togglable>
  )

  // ...
}

يستخدم الخطاف useRef لإنشاء مرجع إلى المتغير noteFormRef ويسند إلى المكوّن Togglable الذي يحتوي نموذج إنشاء ملاحظة. وبالتالي سيعمل المتغير noteFormRef كمرجع إلى المكوّن. يضمن الخطاف أن يبقى المرجع كما هو عند إعادة تصيير المكوِّن.

عدّلنا أيضًا المكون Togglable ليصبح على الشكل التالي:

import React, { useState, useImperativeHandle } from 'react'
const Togglable = React.forwardRef((props, ref) => {  
  const [visible, setVisible] = useState(false)
  const hideWhenVisible = { display: visible ? 'none' : '' }
  const showWhenVisible = { display: visible ? '' : 'none' }
  const toggleVisibility = () => {
    setVisible(!visible)
  }

  useImperativeHandle(ref, () => {    
      return {      
          toggleVisibility    
      }
  })
  return (
    <div>
      <div style={hideWhenVisible}>
        <button onClick={toggleVisibility}>{props.buttonLabel}</button>
      </div>
      <div style={showWhenVisible}>
        {props.children}
        <button onClick={toggleVisibility}>cancel</button>
      </div>
    </div>
  )
})
export default Togglable

تُغلَّف الدالة التي تنشئ المكوّن داخل استدعاء الدالة forwardRef، وبالتالي سيتمكن المكوّن من الوصول إلى المرجع الذي أسند إليه. يستخدم المكوِّن الخطاف useImperativeHandle ليجعل الدالة toggleVisibility متاحة خارج إطار المكوِّن.

سنتمكن الآن من إخفاء النموذج بتنفيذ الأمر ()noteFormRef.current.toggleVisibility بعد أن تُنشأ الملاحظة الجديدة:

const App = () => {
  // ...
  const addNote = (noteObject) => {
    noteFormRef.current.toggleVisibility()
    noteService
      .create(noteObject)
      .then(returnedNote => {     
        setNotes(notes.concat(returnedNote))
      })
  }
  // ...
}

ولكي نلخص ما مضى، فإن الدالة useImperativeHandle هي خطاف يستخدم لتعريف دوال داخل المكوِّن يمكن استدعاؤها من خارجه.

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

تستخدم المراجع في عدة حالات أخرى مختلفة غير الوصول إلى مكوِّنات React.

يمكنك أن تجد شيفرة التطبيق بأكملها ضمن الفرع part5-6 في المستودع الخاص بالتطبيق على GitHub

نقطة أخرى حول المكوّنات

عندما نعرّف مكوّنًا في React كالتالي:

const Togglable = () => ...
  // ...
}

ونستخدمه بالشكل:

<div>
  <Togglable buttonLabel="1" ref={togglable1}>
    first
  </Togglable>

  <Togglable buttonLabel="2" ref={togglable2}>
    second
  </Togglable>

  <Togglable buttonLabel="3" ref={togglable3}>
    third
  </Togglable>
</div>

سنكون قد أنشأنا ثلاثة مكوّنات منفصلة ولكل منها حالته المستقلة:

component_states_03.png

وتستخدم الصفة ref لإسناد مرجع لكل مكوّن ضمن المتغيرات togglable1 وtogglable2 وtogglable3.

التمارين 5.5 - 5.10

5.5 واجهة أمامية لقائمة المدونات: الخطوة 5

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

يجب أن لا يظهر النموذج بشكل افتراضي:

form_invisible_04.png

يظهر النموذج بالنقر على الزر "new note":

form_visible_05..png

يختفي النموذج عند إنشاء مدونة جديدة.

5.6 واجهة أمامية لقائمة المدونات: الخطوة 6

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

يجب أن يعمل المكوِّن بشكل مشابه للمكوِّن NoteForm الذي أنشأناه سابقًا في هذا الفصل.

5.7 واجهة أمامية لقائمة المدونات: الخطوة 7 *

لنضف زرًا إلى كل مدونة ليتحكم بإظهار كل تفاصيل المدونة أو عدم إظهارها. حيث تظهر كل التفاصيل بالنقر على الزر

show_all_details_06.png

وتختفي التفاصيل بالنقر على الزر ثانيةً.

لا حاجة أن ينفذ الزر "like" أي شيء حاليًا.

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

const Blog = ({ blog }) => {
  const blogStyle = {
    paddingTop: 10,
    paddingLeft: 2,
    border: 'solid',
    borderWidth: 1,
    marginBottom: 5
  }

  return (
    <div style={blogStyle}>
      <div>
        {blog.title} {blog.author}
      </div>
      // ...
  </div>
)}

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

5.8 واجهة أمامية لقائمة المدونات: الخطوة 8 *

اضف وظيفةً لزر الاعجابات. يزداد عدد الإعجابات بإرسال طلب HTTP-PUT إلى عنوان المدونة الفريد في الواجهة الخلفية. وطالما أن العملية في الواجهة الخلفية ستستبدل المنشور كاملًا، عليك إرسال كل حقول المنشور ضمن جسم الطلب.

فلو أردت إضافة إعجاب إلى المنشور التالي:

{
  _id: "5a43fde2cbd20b12a2c34e91",
  user: {
    _id: "5a43e6b6c37f3d065eaaa581",
    username: "mluukkai",
    name: "Matti Luukkainen"
  },
  likes: 0,
  author: "Joel Spolsky",
  title: "The Joel Test: 12 Steps to Better Code",
  url: "https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/"
},

عليك إرسال طلب HTTP-PUT إلى العنوان api/blogs/5a43fde2cbd20b12a2c34e91/، يتضمن البيانات التالية:

{
  user: "5a43e6b6c37f3d065eaaa581",
  likes: 1,
  author: "Joel Spolsky",
  title: "The Joel Test: 12 Steps to Better Code",
  url: "https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/"
}

تحذير من جديد: إن لاحظت أنك تستخدم await/async مع then فتأكد أنك سترتكب خطأً ما. استخدم أحد الأسلوبين وليس كلاهما في الوقت ذاته.

5.9 واجهة أمامية لقائمة المدونات: الخطوة 9 *

عدّل التطبيق ليرتب المنشورات وفقًا لعدد الإعجابات التي تحملها. يمكن ترتيب المنشورات باستخدام تابع المصفوفات sort.

5.10 واجهة أمامية لقائمة المدونات: الخطوة 10 *

أضف زرًا لحذف منشور. وأضف أيضًا وسيلة لحذف المنشورات في الواجهة الخلفية.

قد يبدو تطبيقك مشابهًا للشكل التالي:

blog_delete_07.png

يمكنك إظهار رسالة لتأكيد الحذف باستخدام الدالة window.confirm.

أظهر زر حذف المنشور إذا كان المستخدم الحالي للتطبيق هو من أنشأ هذا المنشور.

الحزمة PropTypes والخصائص النمطية

يفترض المكوِّن Togglable أنه سيحصل على النص الذي سيظهر على الزر عبر الخاصية buttonLabel، فلو نسينا تحديد ذلك في المكوِّن:

<Togglable> buttonLabel forgotten... </Togglable>

سيعمل التطبيق، لكن سيصيّر المتصفح الزر الذي لا يحمل نصًا.

لذلك قد نرغب أن نجعل وجود قيمة للنص الذي سيظهر كعنوان على الزر أمرًا إجباريًا عند استخدام المكوِّن Togglable. يمكننا تحديد الخصائص المتوقعة والضرورية (الخصائص النمطية) باستخدام الحزمة prop-types. لنثبت الحزمة إذًا

npm install prop-types

يمكننا تحدد الخاصية buttonLabel كخاصية إجبارية أو كخاصية نصية لازمة كالتالي:

import PropTypes from 'prop-types'

const Togglable = React.forwardRef((props, ref) => {
  // ..
})

Togglable.propTypes = {
  buttonLabel: PropTypes.string.isRequired
}

ستعرض الطرفية رسالة الخطأ التالية إذا لم نحدد قيمة الخاصية:

console_prop_undef_08.png

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

لنعرِّف خصائص نمطية للمكوِّن LoginForm.

import PropTypes from 'prop-types'

const LoginForm = ({
   handleSubmit,
   handleUsernameChange,
   handlePasswordChange,
   username,
   password
  }) => {
    // ...
  }

LoginForm.propTypes = {
  handleSubmit: PropTypes.func.isRequired,
  handleUsernameChange: PropTypes.func.isRequired,
  handlePasswordChange: PropTypes.func.isRequired,
  username: PropTypes.string.isRequired,
  password: PropTypes.string.isRequired
}

إن كان نمط الخاصية الممررة خاطئًا، كأن نعرف مثلًا الخاصية handleSubmit كنص، سيتسبب ذلك برسالة التحذير التالية:

prop_undef_war_09.png

المدقق ESlint

هيئنا في القسم 3 مدقق تنسيق الشيفرة ESlint ليعمل مع الواجهة الخلفية. سنستخدمه الآن مع الواجهة الأمامية.

يثبت البرنامج Create-react-app المدقق ESlint تلقائيًا في المشروع، وكل ما يبقى علينا هو كتابة تعليمات التهيئة المطلوبة داخل الملف eslintrc.js.

ملاحظة: لا تنفذ الأمر eslint --init. سيثبت هذا الأمر آخر نسخة من ESlint والتي لن تتوافق مع ملف التهيئة الذي ينشئه createreactapp.

سنبدأ تاليًا باختبار الواجهة الأمامية. ولكي نتجنب أخطاء التدقيق غير المرغوبة أو التي لا تتعلق بشيفرتنا، سنثبت الحزمة eslint-jest-plugin:

npm add --save-dev eslint-plugin-jest

لننشئ الملف eslintrc.js الذي يحتوي الشيفرة التالية:

module.exports = {
  "env": {
      "browser": true,
      "es6": true,
      "jest/globals": true 
  },
  "extends": [ 
      "eslint:recommended",
      "plugin:react/recommended"
  ],
  "parserOptions": {
      "ecmaFeatures": {
          "jsx": true
      },
      "ecmaVersion": 2018,
      "sourceType": "module"
  },
  "plugins": [
      "react", "jest"
  ],
  "rules": {
      "indent": [
          "error",
          2  
      ],
      "linebreak-style": [
          "error",
          "unix"
      ],
      "quotes": [
          "error",
          "single"
      ],
      "semi": [
          "error",
          "never"
      ],
      "eqeqeq": "error",
      "no-trailing-spaces": "error",
      "object-curly-spacing": [
          "error", "always"
      ],
      "arrow-spacing": [
          "error", { "before": true, "after": true }
      ],
      "no-console": 0,
      "react/prop-types": 0
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

ملاحظة: إن كنت تستخدم ESlint كإضافة مع Visual Studio Code، قد تحتاج إلى إعدادات إضافية لفضاء العمل حتى يعمل. وإن كنت ترى الرسالة:

اقتباس

"Failed to load plugin react: Cannot find module 'eslint-plugin-react"

فهذا دليل على أنك تحتاج إلى مزيد من تعليمات التهيئة.

قد يؤدي إضافة الأمر "eslint.workingDirectories": [{ "mode": "auto" }] إلى الملف setting.json في فضاء العمل إلى حل المشكلة. يمكنك الاطلاع على المزيد حول الموضوع عبر الانترنت.

لننشئ ملف تجاهل قواعد eslint ذو اللاحقة eslintignore. بحيث يحتوي الشيفرة التالية:

node_modules
build

وهكذا لن تخضع المجلدات "build" و"node_modules" لعملية التدقيق.

لننشئ أيضًا سكربت npm لتشغيل المدقق:

{
  // ...
  {
    "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "server": "json-server -p3001 db.json",
    "eslint": "eslint ."  },
  // ...
}

سيسبب المكوِّن تحذيرًا مزعجًا: "لا يحمل تعريف المكوِّن اسمًا لعرضه" (Component definition is missing display name).

componnent_warn_display_name_10.png

وستكشف أداة تطوير React أيضًا أن المكوِّن لا يحمل اسمًا:

react_comp_no_name_11.png

لكن إصلاح ذلك أمر يسير.

import React, { useState, useImperativeHandle } from 'react'
import PropTypes from 'prop-types'

const Togglable = React.forwardRef((props, ref) => {
  // ...
})

Togglable.displayName = 'Togglable'
export default Togglable

يمكنك إيجاد شيفرة التطبيق بالكامل ضمن الفرع part5-7 في الستودع الخاص بالتطبيق على GitHub.

التمرينان 5.11 - 5.12

5.11 واجهة أمامية لقائمة المدونات: الخطوة 11

عرّف خاصة نمطية propTypes لأحد مكوِّنات التطبيق.

5.12 واجهة أمامية لقائمة المدونات: الخطوة 12

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

يثبت البرنامج create-react-app المدقق ESlint تلقائيًا في المشروع، وكل ما يبقى عليك هو كتابة تعليمات التهيئة المطلوبة داخل الملف eslintrc.js.

ملاحظة: لا تنفذ الأمر eslint --init. سيثبت هذا الأمر آخر نسخة من ESlint والتي لن تتوافق مع ملف التهيئة الذي ينشئه create-react-app.

ترجمة -وبتصرف- للفصل props.children, proptypes من سلسلة Deep Dive Into Modern Web Development





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


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



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

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

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


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

تسجيل الدخول

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


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