full_stack_101 مكونات الأصناف ومواضيع أخرى مهمة في بناء تطبيقات React


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

مكونات الأصناف

استخدمنا حتى اللحظة في المنهاج مكوّنات React التي عرفناها على شكل دوال JavaScript. ولم يكن هذا ممكنًا لولا الوظائف التي أمنتها الخطافات التي أتت مع النسخة 16.8 من React. فلقد كان على المطوّر أن يستخدم العبارة Class عندما يعرّف مكونًا له حالة.

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

لنتعرّف إذًا على الميزات الرئيسية لمكوّنات الأصناف، وذلك باستخدامها مع تطبيق " الطرائف" الذي أصبح مألوفًا. سنخزّن الطرائف في الملف "db.json" مستخدمين خادم JSON. يمكن نسخ محتويات الملف من GitHub

تبدو النسخة الأساسية من مكوّن الأصناف كالتالي:

import React from 'react'

class App extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <h1>anecdote of the day</h1>
      </div>
    )
  }
}

export default App

يمتلك المكوّن الآن دالة بانية، والتي لا تقوم بأي عمل حاليًا، كما يتضمن أيضًا التابع render. سيعرّف التابع render كما توقعنا، كيف وماذا سيُعرض على الشاشة.

لنعرّف حالة لقائمة الطرائف وللطرفة التي تُعرض حاليًا. بالمقارنة مع استخدام الخطاف useState فمكوّن الأصناف يحتوي فقط حالة واحدة. فإن تكوّنت الحالة من عدة أجزاء يجب حفظها كخصائص للحالة. سنهيّئ الحالة ضمن البانية:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
        anecdotes: [],
        current: 0
    }
  }

  render() {
    if (this.state.anecdotes.length === 0 ) {
        return <div>no anecdotes...</div>
    }

    return (
      <div>
        <h1>anecdote of the day</h1>
        <div>
          {this.state.anecdotes[this.state.current].content}
    </div>
        <button>next</button>
      </div>
    )
  }
}

تخزّن حالة المكوّن في المتغيّر this.state. وستكون الحالة عبارة عن كائن بخاصيتين هما this.state.anecdotes وتمثل قائمة الطرائف وthis.state.current والتي تخزن رقم الطرفة المعروضة.

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

تقدّم توابع دورة العمل في مكوّنات الأصناف وظائف مقابلة. وسيكون المكان الأمثل لإحضار البيانات من الخادم داخل تابع دور العمل componentDidMount، والذي يُنفَّذ مرة واحدة عند كل تصيير للمكوّن:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      anecdotes: [],
      current: 0
    }
  }

  componentDidMount = () => {    
      axios.get('http://localhost:3001/anecdotes')
           .then(response => {
          this.setState({ anecdotes: response.data })
      })
  }
  // ...
}

تُحدِّث دالة استدعاء طلب HTTP المكوّن باستخدام التابع setState. يؤثر التابع فقط على المفاتيح التي عُرّفت في الكائن الذي سنمرّره إلى التابع كمعامل. وستبقى قيمة المفتاح current كما هي. كما يفعّل استدعاء التابع setState تابع التصيير render.

سنكمل عملنا على المكوّن بإضافة إمكانية تغيير الطرفة المعروضة. تمثل الشيفرة التالية المكوّن بالكامل، ولوِّنت الشيفرة التي تنفذ الإضافة السابقة:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      anecdotes: [],
      current: 0
    }
  }

  componentDidMount = () => {
    axios.get('http://localhost:3001/anecdotes').then(response => {
      this.setState({ anecdotes: response.data })
    })
  }

  handleClick = () => {    const current = Math.floor(
      Math.random() * this.state.anecdotes.length
  )    
  this.setState({ current })
                      }
  render() {
    if (this.state.anecdotes.length === 0 ) {
      return <div>no anecdotes...</div>
    }

    return (
      <div>
        <h1>anecdote of the day</h1>
        <div>{this.state.anecdotes[this.state.current].content}</div>
        <button onClick={this.handleClick}>next</button>
</div>
    )
  }
}

وعلى سبيل المقارنة، فالشيفرة التالية هي الشيفرة المقابلة لمكوّنات الدوال:

const App = () => {
  const [anecdotes, setAnecdotes] = useState([])
  const [current, setCurrent] = useState(0)

  useEffect(() =>{
    axios.get('http://localhost:3001/anecdotes').then(response => {
      setAnecdotes(response.data)
    })
  },[])

  const handleClick = () => {
    setCurrent(Math.round(Math.random() * (anecdotes.length - 1)))
  }

  if (anecdotes.length === 0) {
    return <div>no anecdotes...</div>
  }

  return (
    <div>
      <h1>anecdote of the day</h1>
      <div>{anecdotes[current].content}</div>
      <button onClick={handleClick}>next</button>
    </div>
  )
}

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

وعلى مستويات مقارنة أعلى، سيقدم خطاف التأثير آلية تحكم أفضل بالتأثيرات الجانبية مقارنة بتوابع دورة العمل التي تأتي مع مكوّنات الأصناف. والفائدة الملحوظة باستخدام مكوّنات الدوال تكمن في انعدام الحاجة لاستخدام المؤشر الذاتي this الذي تستخدمه أصناف JavaScript.

لا توفر مكوّنات الأصناف بناء على رأي الكثيرين أية ميزات تفوق بها مكوّنات الدوال المدعّمة بالخطافات، ما عدا آلية محيط الخطأ (error boundary) والتي لم تستخدم حتى الآن من قبل مكوّنات الدوال.

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

تنظيم الشيفرة في تطبيقات React

لقد اتبعنا في معظم التطبيقات المبدأ التالي في التنظيم: حيث وضعنا المكوّنات في المجلد "components"، ووضعنا دوال الاختزال في المجلد"reducers"، وضعنا الشيفرة المسؤولة عن الاتصال مع الخادم في المجلد "services". تلائم الطريقة السابقة تنظيم التطبيقات الصغيرة. لكن عند زيادة عدد المكوّنات، سيتطلب الأمر حلولًا أفضل. ليست هنالك طريقة واحدة صحيحة لتنظيم المشروع، لكن قد تقدم لك المقالة التي تحمل العنوان The 100% correct way to structure a React app (or why there’s no such thing) بعض الإضاءات بهذا الشأن.

الواجهة الخلفية والأمامية في المجلد نفسه

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

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

يمكن الاطلاع على طريقة لتنظيم الأمور تستخدمها كنقطة انطلاق على GitHub.

التغييرات على الخادم

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

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

ومقابس الويب هي واجهات برمجية يزودنا بها المتصفح، لكنها غير مدعومة بشكل كامل من قبل جميع المتصفحات:

web_socket_01.png

ينصح باستخدام المكتبة Socket.io بدلًا من الاستخدام المباشر لواجهة مقبس الويب البرمجية. تؤمن هذه المكتبة عدة خيارات للتراجع إن لم يدعم المتصفح المقابس بشكل كامل.

سنتعرف في القسم 8 على GraphQL والتي ستزودنا بآلية جيدة لتنبيه الزبون بأية تغييرات في الواجهة الخلفية.

DOM افتراضية

يظهر موضوع DOM الافتراضية عندما نناقش تقنيات React. ماذا تعنيه تلك العبارة؟ كما ذكرنا في القسم 0، تزودنا المتصفحات بواجهة برمجية لنموذج DOM، والذي يمكِّن شيفرة JavaScript العاملة ضمن المتصفح من تغيير العناصر التي تحدد مظهر الصفحة.

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

const element = <h1>Hello, world</h1>

كما أنها أيضًا عناصر React مبنية على شيفرة JavaScript في صميمها.

تُشكّل عناصر React التي تحدد مظهر المكونات في التطبيق DOM الافتراضية، والتي تُخزَّن في ذاكرة النظام أثناء تشغيل التطبيق.

وتصيّر DOM الافتراضية بمساعدة المكتبة ReactDOM إلى DOM الحقيقية التي يمكن للمتصفح عرضها باستخدام "DOM API".

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

عندما تتغير حالة التطبيق تُعرَّف DOM افتراضية جديدة بواسطة المكوّنات. تمتلك React النسخة السابقة منها في ذاكرة النظام، وبدلًا من تصيير النسخة الجديدة مباشرة باستخدام "DOM API"، تقدر React الطريقة المثلى لتحديث DOM (إزالة أو إضافة أو تعديل العناصر ضمن DOM). وهكذا ستعكس DOM النسخة الجديدة دائمًا.

دور React في التطبيقات

ربما لم نظهر بوضوح خلال تقدمنا في المنهاج أنّ React هي بشكل أساسي مكتبة لإدارة إنشاء واجهات عرض للتطبيقات. فلو نظرنا إلى النمط التقليدي للمتحكم بنموذج العرض(MVC-Model View Controller)، فسيكون نطاق استخدام React هو العرض(View). وللمكتبة React مجال تطبيق أضيق من Angular على سبيل المثال، والتي تمثل إطار عمل للتحكم بوحدة عرض الواجهات الأمامية. وهكذا لا تمثل React ما ندعوه "إطار عمل"، بل هي مكتبة.

تخزّن البيانات التي يتعامل معها تطبيق React في حالة مكوناته، فيمكننا إذًا التفكير بحالة التطبيق على أنها نموذج من معمارية MVC.

لا نشير عادة إلى معمارية MVC عند الحديث عن تطبيقات React. فلو استخدمنا Redux فسيتبع التطبيق معمارية Flux وسيركّز دور React هنا أكثر على إنشاء واجهات العرض. إذ ستعالج حالة Redux ومولدات الأفعال منطق التطبيق. أما عند استخدام redux thunk التي تعرفنا عليها في القسم 6، فسنجد أنّ منطق التطبيق سينفصل كليًا عن شيفرة React.

وطالما أن React وFlux من إنتاج Facebook، فيمكننا القول أنّ استخدام React كمكتبة للتعامل مع واجهة المستخدم (UI) هي الطريقة التي ينبغي استخدامها، كما أن توافقها مع معمارية Flux سيضيف ميزة إلى التطبيق. لكن لو تحدثنا عن التطبيقات الصغيرة أو النماذج الأولية سيكون من المفيد استعمال React بطريقة "خاطئة" غير التي صممت لأجلها، ذلك أنّ تجاوز الحدود التقنية نادرًا ما يثمر.

وكما أشرنا في نهاية الفصل الأخير من القسم 6، تقدم React الواجهة البرمجية والتي تمثل حلًا بديلًا لمركزية إدارة الحالة دون استخدام طرف ثالث مثل Redux. يمكنك الاطلاع على المقالة ?can't replace redux with hooks والمقالة ?how to usecontext with usereducer

أمن تطبيقات React/Node

لم نتطرق حتى الآن في مادة المنهاج إلى أمن المعلومات. وليس لدينا الوقت الكافي أيضًا. لكن لحسن الحظ قسم علوم الحاسب في جامعة هلسنكي يقدم المنهاج Securing Software لهذا الغرض.

ينشر المشروع المفتوح لأمن تطبيقات الويب OWASP قائمة سنوية بأكثر الأخطار الأمنية شيوعًا في تطبيقات الويب. ستجد اللائحة الحالية على الموقع https://owasp.org/www-project-top-ten. ويمكن أن تتكرر الأخطار ذاتها سنويًا.

ستتصدر القائمة أخطار الإدخالات المشبوهة (injection). ويعني ذلك تفسير النص الذي أرسل عبر استمارة تطبيق بشكل مختلف كليًا عما أراده المطوّر. وأكثر هذه الإدخالات شهرة هي إدخالات SQL

لنفترض على سبيل المثال، أنّ استعلام SQL التالي سيُنفّذ ضمن تطبيق قابل للاختراق:

let query = "SELECT * FROM Users WHERE name = '" + userName + "';"

لنفترض الآن أنّ المستخدم المشبوه Arto Hellas سيعرّف اسمه بالشكل التالي:

Arto Hell-as'; DROP TABLE Users; --

سيحوي الاسم علامة التنصيص المفردة ' والتي تمثل محرف بداية ونهاية السلسلة النصية في لغة SQL. وكنتيجة لتنفيذ عبارتي SQL السابقتين، ستدمر العبارة الثانية الجدول Users من قاعدة البيانات.

SELECT * FROM Users WHERE name = 'Arto Hell-as'; DROP TABLE Users; --'

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

يمكن أن يحصل هذا النوع من الهجوم على قواعد بيانات لا تستخدم SQL. فقاعدة البيانات Mongoose تحبط هذا الهجوم بعملية تطهير الاستعلام. يمكنك الاطلاع على المزيد حول هذا الموضوع على الموقع blog.websecurify.com/2014/08/hacking-nodejs-and-mongodb.html.

من الأخطار الأخرى هي السكربت العابرة للمواقع (Cross-sites scripting XSS). إذ يمكن لهذا الهجوم أن يدخل شيفرة JavaScript مشبوهة إلى تطبيق ويب شرعي. ستُنفَّذ عندها هذه الشيفرة في متصفح الضحية. فلو حاولنا إدخال الشيفرة التالية داخل تطبيق الملاحظات:

<script>
  alert('Evil XSS attack')
</script>

لن يتم تنفيذ الشيفرة بل ستصيّر على شكل نص على الصفحة:

xss_attack_note_app_02.png

ذلك أنّ React تُطهّر البيانات التي تحملها المتغيرات. لقد كانت بعض نسخ React عرضة لهجوم XSS. بالطبع تم سد هذه الثغرات، لكن لا أحد يضمن ما سيحدث.

على المطور أن يبقى حذرًا عند استخدام المكتبات، وعليه أن يحدّث مكتباته بشكل مستمر إن ظهرت لها تحديثات أمنية جديدة. ستجد التحديثات المتعلقة بالمكتبة Express ضمن توثيق المكتبة، بينما ستجد تحديثات Node ضمن المدونة https://nodejs.org/en/blog.

كما يمكنك التحقق من وضع اعتمادياتك باستخدام الأمر:

npm outdated --depth 0

لقد احتوت الحلول النموذجية لتمرينات القسم 4 بعض الاعتماديات التي احتاجت إلى تحديث العام الماضي:

outdate_depend_03.png

يمكن تحديث الاعتمادية بتحديث الملف "package.json" وتنفيذ الأمر npm install. وانتبه إلى أنّ النسخ القديمة من الاعتماديات ليست بالضرورة خطرًا أمنيًا.

يمكن أن تستخدم الأمر audit للتحقق من أمن الاعتماديات. حيث يقارن أرقام نسخة الاعتمادية في تطبيقك مع قائمة بأرقام النسخ التي ثَبُت احتواؤها على تهديد أمني ضمن قاعدة بيانات مركزية.

إنّ تنفيذ الأمر npm audit على تمرين من تمرينات القسم الرابع من منهاج العام الفائت سيطبع لك قائمة طويلة بالمشاكل وطريقة حلها، والتقرير التالي هو جزء من التقرير الكلي الناتج:

$ bloglist-backend npm audit

                       === npm audit security report ===

# Run  npm install --save-dev jest@25.1.0  to resolve 62 vulnerabilities
SEMVER WARNING: Recommended action is a potentially breaking change
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Low           │ Regular Expression Denial of Service                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ braces                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ jest [dev]                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ jest > jest-cli > jest-config > babel-jest >                 │
│               │ babel-plugin-istanbul > test-exclude > micromatch > braces   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/786                             │
└───────────────┴──────────────────────────────────────────────────────────────┘


┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Low           │ Regular Expression Denial of Service                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ braces                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ jest [dev]                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ jest > jest-cli > jest-runner > jest-config > babel-jest >   │
│               │ babel-plugin-istanbul > test-exclude > micromatch > braces   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/786                             │
└───────────────┴──────────────────────────────────────────────────────────────┘


┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Low           │ Regular Expression Denial of Service                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ braces                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ jest [dev]                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ jest > jest-cli > jest-runner > jest-runtime > jest-config > │
│               │ babel-jest > babel-plugin-istanbul > test-exclude >          │
│               │ micromatch > braces                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/786                             │
└───────────────┴──────────────────────────────────────────────────────────────┘

...


found 416 vulnerabilities (65 low, 2 moderate, 348 high, 1 critical) in 20047 scanned packages
  run `npm audit fix` to fix 354 of them.
  62 vulnerabilities require semver-major dependency updates.

فخلال عام فقط، امتلأ التطبيق بالتهديدات الأمنية الصغيرة. ولحسن الحظ لم يكن هنالك سوى تهديد خطير واحد. لننفذ الأمر npm audit fix كما يوصي التقرير:

$ bloglist-backend npm audit fix

+ mongoose@5.9.1
added 19 packages from 8 contributors, removed 8 packages and updated 15 packages in 7.325s
fixed 354 of 416 vulnerabilities in 20047 scanned packages
  1 package update for 62 vulns involved breaking changes
  (use `npm audit fix --force` to install breaking changes; or refer to `npm audit` for steps to fix these manually)

بقيت التهديدات لأن الإصلاح الافتراضي باستخدام audit لا يحدّث الاعتماديات إن زاد الرقم الأساسي لنسخها عن 62. وتحديث هذه الاعتماديات قد يسبب انهيارًا كاملًا في التطبيق. نتجت بقية التهديدات عن اعتمادية التطوير Jest. فرقم نسخة التطبيق هو 23.6.0 بينما تحمل النسخة الآمنة الرقم 25.0.1. وطالما أنها اعتمادية تطوير فلن يكون التهديد موجودًا أصلًا. مع ذلك سنحدّث المكتبة لنبقى في دائرة الأمان:

npm install --save-dev jest@25.1.0 

سيبدو الوضع جيدًا بعد التحديث:

 $ blogs-backend npm audit

                       === npm audit security report ===

found 0 vulnerabilities
 in 1204443 scanned packages

من التهديدات الأخرى المذكورة في قائمة OWASP، سنجد إخفاق التحقق (Broken Authentication) وإخفاق التحكم بالوصول (Broken Access Control). إنّ التحقق المبني على الشهادات الذي استخدمناه قوي بما يكفي إن استخدم مع بروتوكول النقل المشفّر HTTPS. لذلك عندما نضيف ميزة التحكم بالوصول إلى التطبيق، ينبغي أن لا نكتفي بالتحقق من هوية المستخدم على المتصفح، بل على الخادم أيضًا. فمن الأخطاء الأمنية، منع بعض الأفعال من الحدوث بإخفاء خيارات التنفيذ في الشيفرة التي ينفذها المتصفح فقط.

ستجد دليلًا جيدًا جدًا عن أمن مواقع الويب على موقع Mozilla's MDN، والذي سيثير الموضوع المهم التالي:

security_issue_04.png

يحتوي توثيق Express على فصل حول موضوع الأمن بعنوان Production Best Practices: Security من المفيد الاطلاع عليه. كما ننصحك بإضافة مكتبة تدعى Helmet إلى الواجهة الخلفية. حيث تحتوي هذه المكتبة على أدوات وسطية تزيل بعض الثغرات الأمنية في تطبيقات Express. كما تستحق الأداة security-plugin العائدة للمدقق ESlint التجربة.

المسارات الحالية للتطور

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

نسخ بمتغيرات نمطية من JavaScript

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

زاد الاهتمام مؤخرًا بالتحقق الساكن من الأنماط. وتعتبر نسخة المتغيرات النمطية من JavaScript التي قدمتها Microsoft باسم Typescript الأكثر شعبية، وسنتعرف عليها في القسم 9.

التصيير من جهة الخادم والتطبيقات الإيزومورفية والشيفرة الموّحدة

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

تدعى عملية التصيير على الخادم بالاسم "التصيير ضمن الخادم".

إنّ إحدى دوافع التصيير ضمن الخادم هو استمثال محركات البحث (SEO). فلطالما كانت محركات البحث سيئة في تمييز المحتوى الناتج عن تصيير شيفرة JavaScript. لكن على ما يبدو أنّ هذا الأمر في انحسار. يمكنك الاطلاع على ذلك بزيارة المقالين Will Google find your React content?‎ و SEO vs. React: Web Crawlers are Smarter Than You Think.

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

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

تمنحنا React وNode خيارات مرغوبة في كتابة تطبيقات ايزومورفية بشيفرة موحدة.

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

تطبيقات الويب العصرية

بدأ المطورون باستخدام مصطلح تطبيقات الويب العصرية (progressive web app - PWA) الذي أطلقه Google. ونتحدث باختصار عن تطبيقات الويب التي تعمل بأفضل شكل ممكن على كل المنصات مستفيدة من الميزات الأبرز لعناصر المنصة. ولا يجب أن يحد حجم شاشة الأجهزة النقالة من القدرة على استعمال هذه التطبيقات. كما يجب أن تعمل هذه التطبيقات بلا مشاكل دون اتصال أو مع الاتصالات البطيئة بالإنترنت. وينبغي أيضًا أن تُثبّت على الأجهزة النقالة كأي تطبيق آخر. وأخيرًا يجب أن تكون جميع البيانات المنقولة عبرها مشفّرة.

تعتبر التطبيقات التي تُنشئها الأداة create-react-app عصرية افتراضيًا. لكن سيتطلب جعل التطبيق الذي يستخدم بيانات مصدرها الخادم عصريًا جهدًا. كما تنجز وظيفة العمل دون اتصال بالاستفادة من الواجهة البرمجية service workers.

معمارية الخدمات الدقيقة

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

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

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

فيمكن لتطبيق قائمة المدونات على سبيل المثال أن يتكون من خدمتين: تتعامل الأولى مع المستخدمين والأخرى مع المدونات. وستكون مسؤولية الوظيفة الأولى تسجيل دخول المستخدم والتحقق منه، بينما ستهتم الثانية بمنطق التعامل مع المدونات.

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

microsevice_vs_momolithic_05.png

لا يختلف دور الواجهة الأمامية (محاطة بمربع في الشكل السابق) كثيرًا بين النموذجين. وقد يتواجد أحيانًا ما يسمى بوابة الواجهة البرمجية (API gateway) بين الخدمات الدقيقة والواجهة الأمامية، والتي تعطي انطباعًا بأنها بنية تقليدية، "حيث يوجد كل شيء على الخادم نفسه". تستخدم Netflix وغيرها مقاربة مماثلة

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

اقتباس

على جميع الفرق عرض بياناتها ووظائفها من خلال واجهات خدمية من الآن فصاعدًا.

على الفرق أن تتواصل مع بعضها من خلال هذه الواجهات.

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

لا يهم أية تقنيات ستستخدم.

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

لا استثناءات

سيُطرد كل موظف لم ينفذ المطلوب! أتمنى لكم نهارًا سعيدًا.

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

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

الاستقلال عن الخوادم

بدأت معالم مسار جديد في تطوير تطبيقات الويب بالتكوّن، بعد إصدار الخدمة السحابية lambda من قبل Amazon عام 2014. إنّ المعلم الرئيسي في lambda وفي الوظائف السحابية لشركة Google، وكذلك الوظائف السحابية على Azure أنها قادرة على تنفيذ وظائف فردية في السحابة. وقد كانت أصغر وحدة قابلة للتنفيذ على السحابة هي عملية مفردة، أي بيئة تشغيل تعتمد على Node كواجهة خلفية.

يمكن على سبيل المثال تصميم تطبيقات مستقلة عن الخادم (serverless) باستخدام بوابة الواجهة البرمجية التي تقدمها Amazon. حيث تستجيب الوظائف السحابية على الطلب المرسل إلى الواجهة البرمجية التي تدير طلبات HTTP. حيث تعمل الوظائف عادة باستخدام البيانات المخزّنة ضمن قاعدة بيانات الخدمة السحابية.

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

مكتبات مفيدة وروابط مهمة

أنتج مجتمع تطوير JavaScript عددًا ضخمًا من المكتبات المتنوعة المفيدة. فإن كنت بصدد تطوير أي شيء أكثر أهمية، تحقق من وجود حلول جاهزة متاحة، ويمكنك أن تجد الكثير من المكتبات في الموقع https://applibslist.xyz. وستجد في آخر الفقرة بعض المكتبات التي ينصح بها شركاء موثوقين.

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

إن كنت تتعامل مع الوقت والتاريخ فالمكتبة date-fns أداة جيدة.

تساعدك المكتبتان Formik وredux-form في معالجة النماذج بطريقة أسهل. وإن كنت ستستخدم الرسوميات في تطبيقك فهناك خيارات عدة. وننصحك باستخدام recharts وhighcharts.

تؤمن المكتبة immutable.js التي طورتها Facebook، وكما يوحي اسمها، إدراج بعض بنى البيانات الثابتة. إذ يمكن استخدام المكتبة عند استخدام Redux، لأنه وكما أشرنا في القسم 6، أن دوال الاختزال يجب أن تكون دوال نقية، بمعنى أنها لن تعدِّل حالة مخزن Redux، بل ستستبداله بآخر جديد عندما تحدث أية تغييرات. لقد أثر ظهور المكتبة Immer على شعبية immutable.js في السنة الماضية. فهي تؤمن نفس الوظائف لكن ضمن حزمة أسهل استخدامًا نوعًا ما.

تؤمن المكتبة Redux-saga طرقًا بديلة لإنشاء أفعال غير متزامنة للمكتبة redux thunk التي تعرفنا عليها في القسم 6.

إن جمع البيانات التحليلية للتفاعل بين الصفحة في تطبيقات الصفحة الواحدة وبين المستخدمين سيحمل قدرًا من التحدي. ستقدم لك المكتبة React Google Analytics حلًا لتطبيقات الويب التي تُحمِّل الصفحة بأكملها.

يمكنك أن تستفيد من خبرتك في React عند تطوير تطبيقات الهواتف النقالة باستخدام مكتبة Facebook المشهورة جدًا React Native.

تتقلب الأحوال كثيرًا في مجتمع تطوير JavaScript فيما يتعلق بإدارة تجميع وحزم الملفات المشاريع. وتتغير معايير الممارسة الأفضل بشكل سريع:

فقد البرنامج Hipsters لأهميته بعد أن سيطر Webpack على السوق. ثم بدأ البرنامج Parcel قبل عدة سنوات بكسب عدة جولات لصالحه في السوق، كأداة أبسط وأسرع من Webpack (فالبرنامج Webpack ليس بسيطًا على الإطلاق). لكن بعد انطلاقته الواعدة، لم يستطع البرنامج المنافسة أكثر، ويبدو أن Webpack سيبقى في رأس القائمة.

يزودك الموقع https://reactpatterns.com بقائمة تضم الممارسات الأفضل عند تطوير التطبيقات باستخدام React. وقد تعرفنا بالفعل على بعضها ضمن مادة المنهاج. وهنالك قائمة مشابهة لها هي react bits.

سيساعدك Reactiflux وهو مجتمع محادثات لمطوري React على Discord، في الحصول على الدعم بعد إكمالك للمنهاج. ستجد على سبيل المثال قنوات مستقلة للتحدث عن عدد هائل من المكتبات.

ترجمة -وبتصرف- للفصل Class components, Miscellaneous من سلسلة Deep Dive Into Modern Web Development





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


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



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

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

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


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

تسجيل الدخول

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


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