لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/28/21 في كل الموقع
-
لقد وجدت نوع من انواع GANs يعمل على إنشاء صور و تصنيفات وهو ما يسمي ب Conditional Generative Adversarial Netowrks او cGANs، البرنامج السابق يستخدم فقط shallow GANs وهي أنواع الخوارزميات التي تختص فقط بإنشاء صور جديدة. Answering this in case anyone else falls into the same trap3 نقاط
-
2 نقاط
-
كيف يمكننا زيادة سطوع الصورة brightness؟1 نقطة
-
هل من الافضل استخدام الtriggers ام تنفيذ الكود من خﻻل السيرفر؟1 نقطة
-
1 نقطة
-
يمكنك أن تستخدم أكثر من عنصر tbody كما وضح المدرب أسامة، ويمكنك أن تستخدم الصفوف فقط للقيام بهذه المهمة أيضًا، على النحو التالي: <table border="2" width="100%"> <thead> <tr> <th>Group</th> <th>Avatar</th> <th>Name</th> <th>Email</th> <th>Character</th> <th>Profile</th> </tr> </thead> <tbody> <!-- المجموعة الأولى --> <tr> <td rowspan="3">Ninja</td> <td rowspan="2"><img src="https://via.placeholder.com/40/f00" alt=""></td> <td rowspan="2">Osama mohamed</td> <td>o1@nn.sa</td> <td rowspan="2">@</td> <td rowspan="2"><a href="#">Profile</a></td> </tr> <tr> <td>o2@nn.sa</td> </tr> <tr> <td><img src="https://via.placeholder.com/40/00f" alt=""></td> <td>Saed Zareef</td> <td>S@nn.ss</td> <td>™</td> <td><a href="#">Profile</a></td> </tr> <!-- المجموعة الثانية --> <tr> <td>Monsters</td> <td><img src="https://via.placeholder.com/40/000" alt=""></td> <td>Mohamed Hany</td> <td>m@nn.sa</td> <td>®</td> <td><a href="#">Profile</a></td> </tr> </tbody> <tfoot> <tr> <td colspan="5">Total members </td> <td>3</td> </tr> </tfoot> </table> لاحظ كيف أن كل صف tr في المجموعة الأولى يحتوي على عدد معين من عناصر td، وذلك للسماح للخلايا بالتوسع وأخذ مساحة أكثر من صف rowspan.1 نقطة
-
المشكلة لديك في ترتيب الأكواد التالية <tr> <td rowspan="2">Monsters</td> <td rowspan="2"><img src="https://via.placeholder.com/40/000"></td> <td rowspan="2">Mohamed Hany</td> <td rowspan="2">m@nn.sa</td> <td rowspan="2">®</td> <td rowspan="2"><a href="#">Profile</td> </tr> لقد عالجت لك المشكلة في وضع أكواد الصف الثاني داخل وسم tbody آخر ليظهر بالشكل المطلوب تنفيذه ، بهذا الشكل <tbody> <tr> <td rowspan="2">Monsters</td> <td rowspan="2"><img src="https://via.placeholder.com/40/000"></td> <td rowspan="2">Mohamed Hany</td> <td rowspan="2">m@nn.sa</td> <td rowspan="2">®</td> <td rowspan="2"><a href="#">Profile</td> </tr> </tbody> حاول دائماً ترتيب الأكواد ليسهل عليك فهم المشكلة ، يمكنك الاطلاع على النتيجة أرفقت لك الملف بعد التعديل index.html .1 نقطة
-
السلام عليكم أحد الأخوة الأفاضل بموقعكم الجميل قدم لي هذا الكود الذي يحذف الصور من مجلد التجميل التي ليس لها اسم مخزن في القاعدة بصيغة jpg لكم اريد اضافة امتدادات اخرى للكود مثل png , gif الخ كيف يكون ذلك <?php // حذف صور البروفايل القديمة ولا فائدة منها $sql = 'SELECT image_profile_path from accounts'; $result = mysqli_query($db,$sql); $db_imgs = []; while($row = mysqli_fetch_array($result)) { $db_imgs[] = $row['image_profile_path']; } $db_imgs = preg_filter('/^/','uploads/profile/',$db_imgs); $targetDirectory = "uploads/profile/"; $stored_imgs =glob($targetDirectory."*.jpg"); $targetImages = array_merge(array_diff($db_imgs,$stored_imgs),array_diff($stored_imgs,$db_imgs)); $targetImages = array_filter($targetImages); foreach($targetImages as $image) { unlink($image); } ?>1 نقطة
-
يمكنك أن تقوم بتعديل الدالة globl لكي تقوم بالبحث عن كل الملفات التي لديها الصيغة jpg أو png أو gif كالتالي: // غير السطر التالي $stored_imgs =glob($targetDirectory."*.jpg"); // إلى هذا السطر $stored_imgs = glob($targetDirectory."*.{jpg,png,gif}", GLOB_BRACE); بهذه الطريقة سوف يتم التطبيق على كل الصور التي لديها الصيغة jpg أو png أو gif.1 نقطة
-
كيف يمكننا إظهار الرسوم البيانية بعد أن نقوم بإنشائها؟1 نقطة
-
بشكل عام هي حسب الاستعلامات لديك والحقول في الجدول، لنفرض أنك تقوم باستعلام يبحث عن العمر و رقم الهاتف فنقوم بعمل فهرسة مزدوجة أي فهرس لحقلين معاً. فإذا كان لديك استعلام بالشكل التالي: SELECT * FROM USERS WHERE age > 17 AND phone = xxxxxxxxx سيكون الفهرس المناسب بهذا الشكل، مثال: Create Index My_IndexName On TableName_USERS (phone Asc, age Asc) فنقوم بإنشاء فهرس لأكثر من عمود Multi-column Indexes كما أن ترتيب الأعمدة يهم في الفهرس فوضع رقم الهاتف أولاً أفضل من العمر لأنه قيمة غير مكررة فرضاً بينما ملايين الأشخاص لهم نفس العمر (ضمن مشروعك مثلاً كل عمر له مئة مشترك.. فالفهرسة للرقم أسرع لأنه على الأغلب فريد ) يمكن الاستفادة من السؤال:1 نقطة
-
لعمل نسخ احتياطي لحجم تخزين نستخدم العلم volumes-from-- مثال لإنشاء مساحة تخزين باسم my_application_backup: docker run -v /dbdata --name my_application_backup ubuntu /bin/bash مثال لعمل نسخة احتياطية: docker run --rm --volumes-from my_application_backup -v $(pwd):/backup ubuntu tar cvf /backup/my_application_backup.tar /dbdata لاسترجاع البيانانت restore docker run --rm --volumes-from my_application_backup -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/my_application_backup.tar --strip 1" كما يوجد العديد من الإضافات التي يمكن استخدامها لإدارة حجوم البيانات data volume في حاويات وتقنيات Docker ويمكن الوصول لها من البحث عن Docker volume plugins التي يمكن تثبيتهم من خلال Docker يمكن تثبيت الإضافة من خلال: docker plugin install --grant-all-permissions xxx/yyy اسم الإضافة ثم اتباع التوثيق الخاص بهم لتعلم استخدامهم.1 نقطة
-
يمكن تثبيت التعديلات التي قمنا بها ضمن الحاوية إلى صورة جديدة باستعمال commit docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] مثال: docker commit c3f2xxxd17e0a my_application/test_3_image:version3 ينتج لنا صورة جديدة مع التعديلات new image. لتصدير حاوية لملف مضغوط نستخدم الأمر export الشكل العام docker export [OPTIONS] CONTAINER التصدير لملف docker export my_application > my_application_bk.tar أو docker export --output="my_application_bk.tar" my_application لاستيراد حاوية من ملف مضغوط نستخدم import الشكل العام docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] من الويب docker import https://example.com/my_application_bk.tar من ملف cat my_application_bk.tar | docker import - my_application:new1 نقطة
-
هذا ليس خطأ و إنما رسالة تحذير بسيطة يتم إخبارك فيها أنه لا يمكنك محو مجلد , و يحدث هذا لأن بعض عناصر المصفوفتين تحوي سلاسل نصية كالتالي : " " , أي فراغ . يمكنك تجاهلها بكل حال من الأحوال كون الشيفرة قابلة للتنفيذ مرة واحدة . لا تنسى ضبط السطر التالي : $targetImages = array_diff($stored_imgs,$db_imgs); و ذلك وفق ما هو موضح سابقا :1 نقطة
-
لعمل نسخ احتياطي لصورة docker image نستخدم الأمر save docker save [OPTIONS] IMAGE [IMAGE...] مثال: docker save my_application > my_application_bk.tar لتحميل صورة نستخدم load docker load [OPTIONS] مثال: docker load < my_application_bk1.tar في حال الرغبة بعمل ضغط للملفات باستخدام خوارزيمة gzip ننفذ الأمر التالي docker save my_application:latest | gzip > my_application_bk.tar.gz docker save my_application > my_application_bk.tar1 نقطة
-
docker image هو عبارة عن مجموعة ملفات تحوي التعليمات المسؤولة عن تشغيل الحاوية وليس لديها أي حالة، تحوي التعليمات البرمجية والملفات المصدرية والمكتبات والتبعيات والأدوات والملفات الأخرى اللازمة لتشغيل التطبيق/الحاوية .. source code, libraries, dependencies, tools.. وهو ملف غير قابل للتعديل فقط للقراءة. docker container الحاوية هي بيئة تشغيل التطبيق virtualized run-time environment أي هي الكائن النشط الذي ينتج عن docker image بعد تشغيلها. يمكن ل docker image واحد أن نقوم بإنشاء أكثر من نسخة نشطة منه التي هي حاوية docker container وتمثل كل منها عملية ضمن نظام التشغيل process صورة توضيحية: يتم بناء docker image عن طريق الأمر build للملف Dockerfile # تحميل اوبنتو FROM ubuntu:18.04 # نسخ ملفات COPY . /app # إنشاء مجلد RUN make /app # تشغيل بايثون CMD python /app/app.py أمر بناء: docker build -t my_image dir_with_dockerfile my_image اسم صورة التطبيق dir_with_dockerfile مسار الملف يتم تشغيل docker container بالأمر run مثل docker run image_name docker run my_image docker run hello-world لتوضيح المفهوم برمجياً، docker image تمثل Class بينما docker container تمثل Object. docker image مثل ملفات نظام التشغيل قبل تشغيل تثبيته وتشغيله ليصبح بعد التشغيل مثل docker container1 نقطة
-
يمكنك القيام بذلك بالشكل التالي: import numpy as np import matplotlib.pyplot as plt from PIL import Image # مسار الصورة path = r"/content/test.jpg" # PIL فتح الصورة من خلال الحزمة image = Image.open(path) # عرضها plt.imshow(image) plt.show() الآن لعرض صورة بالتدرج الرمادي نفتح الصورة باستخدام الدالة open من الحزمة PIL ونقوم بتحويلها إلى الوضع L من خلال الدالة convert وهذا يعني أنها صورة ذات قناة واحدة أي تفسر على أنها تدرج رمادي. يخزن فقط التدرج الرمادي وليس اللون. ثم نقوم برسم الصورة من خلال الدالة imshow مع ضبط الخريطة اللونية cmap على "gray": import numpy as np import matplotlib.pyplot as plt from PIL import Image # مسار الصورة path = r"/content/test.jpg" # PIL فتح الصورة من خلال الحزمة image = Image.open(path).convert("L") # عرضها بالصيغة الرمادية plt.imshow(image, cmap='gray') plt.show() وإذا كنت تريد عرض عكس التدرج الرمادي ، فقم بضبط cmap على gray_r.1 نقطة
-
بشكل عام يمكن اعتبار الفرق بين الأثنين أقرب لكونه طريقة كتابة فقط الا أنه هناك بعض الفروقات الصغيرة بينهما. ON هو الأكثر عمومية . يمكن استخدامها لضم merge الجداول على عمود مشترك ، أو مجموعة من الأعمدة أو حتى شرط كالمثال التالي: SELECT * FROM world.City JOIN world.Country ON (City.Code = Country.Code) بينما يكون USING مفيدًا عندما يشترك كلا الجدولين في عمود يحمل نفس الاسم الذي ينضمون إليه. في هذه الحالة كمثال: SELECT ... FROM Country JOIN world USING (country_id) WHERE ... كذلك من الأشياء الرائعة الإضافية أن المرء لا يحتاج إلى تأهيل الأعمدة للدمج بشكل كامل هكذا: SELECT film.title, film_id FROM film JOIN film_actor USING (film_id) WHERE ... عوضا عن كتابتها باستخدام ON كاملة هكذا: SELECT film.title, film.film_id FROM film JOIN film_actor ON (film.film_id = film_actor.film_id) WHERE ...1 نقطة
-
ليس تماماً , فإن الجافاسكريبت على عكس اللغات التي تعمل في المستوي المنخفض (low level languages) التي مكنك بإستخدامها إختيار ماإن كنت تريد إرسال البيانات بالمرجعية أم بالقيمة, فإن الأمر في الجافا سكريبت مختلف قليلاً, حيث يتم تلقائياً إرسال البيانات البدائية (primitive data types) بالقيمة , وأما الكائنات فيتم إرسال نسخة من المرجعية(copy of reference). حيث ﻻ يمكنك تغيير الكائن الذي يتم تمريره إلى الدالة ولكن يمكنك تغيير قيمته, لنأخذ مثالاً لتوضيح الأمر: function test(obj){ obj={}; } هذا السطر لن يؤثر على الكائن obj خارج الدالة حيث أنك تحاول تغيير مرجعية الكائن نفسه والذي هو غير مقبول في الجافا سكريبت function test(obj){ obj.a="hello khaled"; } بينما هذا السطر سيقوم بتغيير قيمة الخاصية a لدي الكائن obj حيث أنك تحاول فقط تغيير خواص الكائن والذي يُعد مقبولاً في الجافاسكريبت فلو قمنا بكتابة شفرة برمجية لتجربة الدوال بالأعلى سنجد قيمة الطباعة كما في الشكل بالأدنى function test(obj){ obj={}; } let a={ name:"sharaf" } test(a) console.log(a)/////////سنﻻحظ أن قيمة الكائن لن تتغير على الإطﻻق //////////////////////////////////// function test(obj){ obj.a="test" } let a={ name:"sharaf } test(a) console.log(a)///////////سنﻻحظ هنا تغيير في قيم الكائن1 نقطة
-
انصب تركيزنا حتى هذه اللحظة على كتابة الشيفرة للعمل مع الواجهة الأمامية (العمل من جهة المستخدم أو المتصفح)، وسنبدأ العمل على الواجهة الخلفية (جهة الخادم) عند الوصول إلى الفصل الثالث. مع ذلك سنخطو بداية خطواتنا بتعلم طريقة التواصل بين الشيفرة المكتوبة للمتصفح مع الخادم. سنستعمل عند تطويرنا التطبيقات أداة تدعى JSON Server لتأمين خادم افتراضي على جهازك. أنشئ ملفًا باسم db.json في المجلد الجذري للمشروع يحوي ما يلي: { "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 } ] } يمكن تثبيت كامل بيئة JSON على جهازك بتنفيذ الأمر install -g json-server. ويتطلب ذلك امتلاك امتيازات مدير النظام والخبرة بالتأكيد. لا يتطلب الأمر عادة تثبيتًا كاملًا، فيمكنك تثبيت ما يلزم في المجلد الجذري للمشروع بتنفيذ الأمر npx json-server: npx json-server --port 3001 --watch db.json يعمل JSON-server افتراضيًا على المنفذ 3000، ونظرًا لاستخدامنا create-react-app الذي يعمل أيضًا عند نفس المنفذ، لابد من تحديد منفذ جديد للخادم مثل 3001. توجه إلى العنوان http://localhost:3001/notes عبر متصفحك، ستلاحظ كيف سيعرض خادم JSON البيانات التي كتبناها سابقًا في ملف JSON. ثبت ملحقًا (plugin) إلى متصفحك لإظهار البيانات المكتوبة بصيغة JSON مثل JSONView، إن لم يتمكن من عرضها بشكل صحيح. لاحقًا، ستكون المهمة حفظ البيانات على الخادم. طبعًا خادم JSON في حالتنا. حيث تحضر شيفرة React الملاحظات ثم تصيّرها على الشاشة. وكذلك سترسل أية ملاحظات جديدة إلى الخادم لتجعلها "محفورة" في الذاكرة. يخزن خادم JSON البيانات في الملف db.json. في الواقع العملي، ستُخزَّن البيانات ضمن قواعد البيانات، لكن سيبقى هذا الخادم أداة مفيدة في متناول اليد لتجريب العمل مع الواجهة الخلفية خلال مرحلة التطوير دون عناء برمجة أي شيء عليه. سنتعلم المزيد حول مبادئ تطوير التطبيقات للتعامل مع الواجهة الخلفية بتفاصيل أكثر في القسم 3. دور المتصفح كبيئة تشغيل تقتضي مهمتنا الأولى إحضار الملاحظات الموجودة إلى تطبيقنا من العنوان http://localhost:3001/notes. ولقد تعلمنا بالفعل طريقةً لإحضار البيانات من الخادم باستخدام JavaScript في القسم 0. حيث استخدمنا XMLHttpRequest في إحضار البيانات من الخادم وهو مايعرف بطلب HTTP باستخدام الكائن XHR. قُدّمت هذه التقنية عام 1999 وتدعمها حتى اللحظة جميع المتصفحات. لكن لا ينصح حاليًا باستخدام هذا الأسلوب، بل استخدام التابع fetch الذي تدعمه المتصفحات بشكل واسع. وتعتمد طريقة عمله على مايسمى وعودًا promises، بدلًا من الأسلوب المقاد بالأحداث والذي يستخدمه XHR. كتذكرة من القسم 0 (لا يجب استخدام XHR إلا إن كنت بحاجة ماسة لذلك) سنحضر البيانات باستخدام هذا الأسلوب كالتالي: const xhttp = new XMLHttpRequest() xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { const data = JSON.parse(this.responseText) // Data يعالج الاستجابة التي أسندت إلى المتغيّر } } xhttp.open('GET', '/data.json', true) xhttp.send() صرحنا في البداية عن معالج حدث للكائن xhttp الذي يمثل طلب HTML. يُستدعى هذا المعالج من قبل بيئة تشغيل JavaScript عندما تتغير حالة الكائن xhttp. ستُعالج طبعًا البيانات التي أُحضرت بشكل مناسب إذا تغيرت الحالة نتيجة لتلقي استجابة الخادم. ولهذا لا قيمة لأية شيفرة قد نضعها في معالج الحدث قبل إرسال الطلب إلى الخادم. لكن بالطبع سيتم تنفيذها في وقت لاحق. إذًا لا تُنفَّذ الشيفرة بشكل متزامن من الأعلى للأسفل، بل بشكل غير متزامن، وستستدعي JavaScript المعالج المصرح عنه سابقًا في مرحلة ما. تعبر البرمجة بالطريقة غير المتزامنة شائعة الاستخدام في Java، يوضح ذلك المثال التالي (انتبه إلى أن الشيفرة في المثال ليس شيفرة Java قابلة للتنفيذ): HTTPRequest request = new HTTPRequest(); String url = "https://fullstack-exampleapp.herokuapp.com/data.json"; List<Note> notes = request.get(url); notes.forEach(m => { System.out.println(m.content); }); ُتنفَّذ الشيفرة في Java سطرًا تلو الآخر وتتوقف بانتظار نتيجة طلب HTTP، أي إتمام تنفيذ الأمر ()request.get. تُخزَّن بعدها البيانات التي يرسلها الخادم -وهي في حالتنا هذه "الملاحظات"- ليتم التعامل معها بالطريقة المطلوبة. تتبع بيئة تشغيل JavaScript أو محرك JavaScript بالمقابل الأسلوب غير المتزامن. ويتطلب ذلك من حيث المبدأ ألا تعيق عمليات الدخل والخرج IO-operations عملية التنفيذ (مع بعض الاستثناءات). أي ينبغي أن تُستأنف عملية تنفيذ الشيفرة مباشرة بعد استدعاء دالة (الدخل/الخرج) دون انتظار الاستجابة. وعندما تكتمل العملية السابقة أو بالأحرى خلال مرحلة ما بعد اكتمالها، يستدعي محرك JavaScript معالج الحدث المعَّرف سابقًا. تعتبر محركات JavaScript حاليًا أحادية المسلك أو الخيط SingleThreaded، أي أنها لا تستطيع تنفيذ الشيفرات على التوازي. لذلك يفضل عمليًا اعتماد نموذج (دخل/خرج) لا يعيق تنفيذ الشيفرة، وإلا سيجمد المتصفح عند إحضار البيانات من الخادم على سبيل المثال. ومن التبعات الأخرى لاستخدام المسلك الأحادي للمحركات، هو توقف المتصفح عند تنفيذ شيفرات تستغرق وقتًا طويلًا خلال فترة التنفيذ. لو وضعنا الشيفرة التالية في أعلى التطبيق: setTimeout(() => { console.log('loop..') let i = 0 while (i < 50000000000) { i++ } console.log('end') }, 5000) سيعمل التطبيق بشكل طبيعي لمدة خمس ثوان (وهو مُعامل الدالة setTimeOut) قبل أن يدخل في تنفيذ الحلقة الطويلة. حيث سيتوقف المتصفح عن العمل ولن نتمكن حتى من إغلاق المتصفح (على الأقل لا يمكننا ذلك في Chrome). وليبقى المتصفح متجاوبًا بشكل مستمر مع أفعال المستخدم وبسرعة كافية، لابد من اعتماد منطق لا يسمح بحسابات طويلة عند كتابة الشيفرة. ستجد على شبكة الإنترنت العديد من المواد المتعلقة بهذا الموضوع، ونخص بالذكر منها الملاحظات المفتاحية التي قدمها فيليب روبرتس بعنوان What the heck is the event loop anyway والتي تمثل عرضًا واضحًا للموضوع. يمكن تنفيذ الشيفرة على التوازي في المتصفحات الحديثة باستخدام ما يسمى عمال الويب web workers. لكن تنفيذ حلقة الأحداث في كل نافذة على حدى سيبقى بأسلوب المسلك الأحادي npm (مدير الحزم في Node.js) لنعد إلى إحضار البيانات من الخادم. يمكن أن نستخدم -كما أشرنا سابقًا- الدالة fetch التي تعتمد على مفهوم الوعود. وتعتبر هذه الدالة من الأدوات المميزة والمعيارية التي تدعمها كل المتصفحات الحديثة. لكننا مع ذلك سنستخدم دوال المكتبة axios بدلًا منها للتواصل بين المتصفح والخادم. حيث تعمل دوال هذه المكتبة بشكل مشابه للدالة fetch لكن التعامل معها أسهل. والسبب المهم الآخر هو تعلم إضافة مكتبات خارجية والمعروفة باسم حزم npm ضمن مشاريع React. تُعرّف حاليًا كل مشاريع JavaScript باستخدام npm، وكذلك تطبيقات React المبنية باستخدام create-react-app. ويشير وجود الملف package.json في المجلد الجذري للمشروع على استخدام npm. { "name": "notes", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^7.2.1", "react": "^16.12.0", "react-dom": "^16.12.0", "react-scripts": "3.3.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } نشير هنا إلى الجزء الأهم من الملف package.json وهو ملفات الارتباط dependencies أو المكتبات الخارجية التي يعتمدها المشروع. سنبدأ الآن باستخدام axios. يمكننا تعريف هذه المكتبة مباشرة في الملف package.jaon، لكن من الأفضل أن نثبتها باستخدام الأمر: npm install axios --save ملاحظة: تنفذ جميع أوامر npm في المجلد الجذري للمشروع، وهو المكان الذي تجد فيه الملف package.jsons. بعد تثبيتها ستظهر axios ضمن ملفات الارتباط: { "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^7.2.1", "axios": "^0.19.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-scripts": "3.3.0" }, // ... } سُينزِّل أمر التثبيت السابق بالإضافة إلى المكتبة axios شيفرة المكتبة. وكغيرها من المكتبات ستجد شيفراتها ضمن المجلد nodemodules الموجود في المجلد الجذري للمشروع. يمكنك أن تلاحظ ان المجلد nodemodules يضم كميةً لا بأس بها من الأشياء المهمة. لنضف أمرًا آخر. ثبت json-server كملف ارتباط (وهذا فقط أثناء التطوير) باستخدام الأمر: npm install json-server --save-dev ثم عدّل قليلًا القسم Scripts من الملف package.json كما يلي: { // ... "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "server": "json-server -p3001 --watch db.json" }, } يمكننا الآن تشغيل خادم json -وبلا تعريف لأية مُعاملات جديدة- من المجلد الجذري للمشروع باستخدام الأمر: npm run server سنطلع أكثر على الأداة npm في القسم الثالث من منهاجنا. ملاحظة: يجب إيقاف الخادم الذي شغلناه باستخدام الأمر السابق قبل تشغيل خادم جديد وإلا ستنتظرك المشاكل: تخبرنا رسالة الخطأ المطبوعة باللون الآخر عن المشكلة التالية: لا يمكن الارتباط بالمنفذ 3001. اختر رجاء رقمًا آخر للمنفذ من خلال التعليمة port-- أو من خلال ملف التهيئة لخادم json. والسبب أن المنفذ 3001 قد شُغِل من قبل الخادم الذي أنشئ أولًا ولم يتم إيقافه، وبالتالي لم يستطع التطبيق الارتباط بهذا المنفذ. لاحظ أن هناك فرقًا بسيطًا بين أمري التثبيت التاليين: npm install axios --save npm install json-server --save-dev ثُبِّتت المكتبة axios كملف ارتباط (save--) للتطبيق، لأن تنفيذه يتطلب وجود هذه المكتبة. بالمقابل ثُبِّت خادم jason كملف ارتباط للتطوير (save-dev--) لأن البرنامج لا يحتاجه فعلًا حتى يُنفّذ، بل سيساعد فقط خلال عملية تطوير التطبيق. سنتحدث أكثر عن موضوع ملفات الارتباط في القسم التالي. مكتبة Axios والوعود نحن الآن مستعدين للعمل مع axios، وسنفترض أن خادم json يعمل على المنفذ 3001. ملاحظة: لتشغل كل من jason و create-react-ap معًا عليك أن تفتح نافذتين من الطرفية node.js، كل نافذة لبرنامج. يمكن إدراج المكتبة ضمن التطبيق بالطريقة التي أدرجت فيها React وهي استعمال العبارة import. أضف مايلي إلى الملف index.js: import axios from 'axios' const promise = axios.get('http://localhost:3001/notes') console.log(promise) const promise2 = axios.get('http://localhost:3001/foobar') console.log(promise2) من المفترض أن يطبع ما يلي على الشاشة: يعيد التابع get وعدًا. ستجد في التوثيق على موقع موسوعة حسوب حول موضوع الوعود مايلي: وبكلمات أخرى، هو كائن يمثل عملية غير متزامنة يمكن أن يأخذ إحدى الحالات الثلاث التالية: الوعد طور التنفيذ (pending): أي أن القيمة النهائية (والتي تمثلها إحدى الحالتين التاليتين) غير جاهزة حتى اللحظة. الوعد قد تحقق (fulfilled): إي أن العملية قد اكتملت والقيمة النهائية جاهزة. ويعني هذا عمومًا أن العملية قد نجحت. وقد يشار إلى هذه الحالة أحيانًا على أنها منجزة Resolved. الوعد قد رفض (rejected): أي أن خطأً قد منع تحديد القيمة النهائية. ويعني هذا عمومًا إخفاق العملية. تَحقَّق الوعد الأول في مثالنا السابق، ويمثل نجاح الطلب (axios.get(http://localhost:3001/notes، بينما رفض الوعد الثاني. وتُبلغنا الطرفية سبب الرفض بأنها تبدو كمحاولة لإرسال طلب HTTP-GET إلى عنوان غير موجود. إذا أردنا أن نعرف كيف وأين سنحصل على نتيجة العملية التي يمثلها الوعد، لابد من التصريح عن معالج حدث لهذا الوعد. ويتم ذلك باستخدام تابع axios يدعى then: const promise = axios.get('http://localhost:3001/notes') promise.then(response => { console.log(response) }) ستظهر على شاشة الطرفية المعلومات التالية: تستدعي بيئة تشغيل JavaScript الدالة المصرح عنها داخل then وتمرر إليها الكائن response كمُعامل. يحوي المُعامل response كل البيانات الأساسية المتعلقة باستجابة الخادم على طلب HTTP-GET، وهي البيانات المعادة ورمز الحالة والترويسات. لا داعٍ لإسناد كائن الوعد إلى متغير، بل تُستخدم عادة طريقة الاستدعاء المتسلسل للتوابع كالتالي: axios.get('http://localhost:3001/notes').then(response => { const notes = response.data console.log(notes) }) حيث تستخلص دالة الاستدعاء البيانات من الاستجابة التي تحصل عليها، ومن ثم تحفظها في متغير وتطبعها على شاشة الطرفية. ومن المفضل كتابة توابع الاستدعاء المتسلسل بحيث يقع كل تابع في سطر جديد وذلك لتسهيل قراءة الشيفرة: axios .get('http://localhost:3001/notes') .then(response => { const notes = response.data console.log(notes) }) يعيد الخادم البيانات المطلوبة على شكل سلسلة نصية طويلة غير منسقة بعد. ثم تحول axios البيانات إلى مصفوفة JavaScript لأن الخادم قد حدد نمط البيانات المرسلة على أنها application/json بنمط محارف utf-8 وذلك ضمن ترويسة نوع المحتوى content-type. وبهذا سنصبح قادرين أخيرًا على استخدام البيانات القادمة من الخادم. سنحاول الآن طلب الملاحظات من الخادم المحلي ثم تصييرها على شكل مكوِّن App. لكن تذكر أن هذه المقاربة ستعرضك لمشاكل عدة لأننا سنصيّر المكوِّن App عندما يستجيب الخادم فقط: import React from 'react' import ReactDOM from 'react-dom' import App from './App' import axios from 'axios' axios.get('http://localhost:3001/notes').then(response => { const notes = response.data ReactDOM.render( <App notes={notes} />, document.getElementById('root') ) }) يمكن أن نستخدم الطريقة السابقة في ظروف معينة، لكنها سبب للعديد من المشاكل. لنحاول بدلًا من ذلك أن نضع أمر إحضار البيانات داخل المكوِّنApp بدلًا من أن يكون جزءًا من دالة الاستجابة. لكن ما ليس واضحًا تمامًا هو المكان الذي سنضع فيه الأمر داخل المكوِّن. خطافات التأثير تعرفنا سابقًا على خطافات الحالة التي ظهرت مع الإصدار 16.8.0 من React، والتي تمنح دالة المكوِّن إمكانية تحديد حالته الراهنة. كما ظهرت في نفس النسخة خطافات التأثير effect hooks كميزة جديدة، وقد وثّقت كالتالي: إذًا فخطافات التأثير هو ما نحتاجه تمامًا لإحضار البيانات من الخادم. لنحذف أمر إحضار البيانات من ملف index.js، ولن نحتاج أيضًا إلى استخدام الخصائص لتمرير الملاحظات إلى المكوِّن، طالما أننا سنحضرها مباشرة من الخادم. وبالتالي سيبدو الملف index.js كالتالي: ReactDOM.render(<App />, document.getElementById('root')) سيتغير المكوِّن App ليصبح كالتالي: import React, { useState, useEffect } from 'react' import axios from 'axios' import Note from './components/Note' const App = () => { const [notes, setNotes] = useState([]) const [newNote, setNewNote] = useState('') const [showAll, setShowAll] = useState(true) useEffect(() => { console.log('effect') axio.get('http://localhost:3001/notes').then(response => { console.log('promise fulfilled') setNotes(response.data) }) }, []) console.log('render', notes.length, 'notes') // ... } كما أضفنا عدة أوامر للطباعة على الطرفية بغية توضيح مسار العملية، وستظهر عند التنفيذ كالتالي: render 0 notes effect promise fulfilled render 3 notes تنفذ في البداية الشيفرة الموجودة داخل جسم دالة المكوِّن، وتصيَّر النتيجة للمرة الأولى. وعندها ستُطبع العبارة render 0 notes على الطرفية إشارة إلى أن البيانات لم تحضر بعد من الخادم. ستُنفَّذ الدالة أو (التأثير) التالي مباشرة بعد أول عملية تصيير: () => { console.log('effect') axios .get('http://localhost:3001/notes') .then(response => { console.log('promise fulfilled') setNotes(response.data) }) } يؤدي تنفيذ التأثير السابق إلى طباعة العبارة effect على الطرفية، ويهيئ الأمر axios.get لعملية إحضار البيانات من الخادم ويُعرِّف في نفس الوقت معالج الحدث التالي للعملية: response => { console.log('promise fulfilled') setNotes(response.data) }) بمجرد وصول البيانات من الخادم، تستدعي بيئة تشغيل JavaScript معالج الحدث المرتبط بالعملية والذي يطبع بدوره العبارة fulfilled على الطرفية، ويخزّن الملاحظات القادمة من الخادم ضمن حالة المكوِّن باستخدام الدالة (setNote(response.data. وكما هو الحال دائمًا ستسبب تحديث حالة التطبيق إعادة تصيير المكوِّن، وستظهر ثلاث ملاحظات على الطرفية ومن ثم ستصيّر من جديد على الشاشة. لنلقي في النهاية نظرة شاملة على خطاف التأثير الذي استخدمناه: useEffect(() => { console.log('effect') axios .get('http://localhost:3001/notes').then(response => { console.log('promise fulfilled') setNotes(response.data) }) }, []) لنُعِد كتابة الشيفرة بشكل مختلف قليلًا: const hook = () => { console.log('effect') axios .get('http://localhost:3001/notes') .then(response => { console.log('promise fulfilled') setNotes(response.data) }) } useEffect(hook, []) سنرى الآن بوضوح أن الدالة useEffect تأخذ في الواقع مُعاملين اثنين. الأول هو الدالة effect وتمثل التأثير ذاته وفقًا للتوثيق: يتم تنفيذ دالة التأثير افتراضيًا بعد أن يتم تصيير المكوِّن. لكننا نريد في حالتنا هذه أن ينفّذ فقط مع أول تصيير. أما المُعامل الثاني فيستخدم لتحديد كم مرة سيُنفَّذ التأثير الجانبي. فإذا وضعنا مصفوفة فارغة [ ] كقيمة له فإن التأثير سينفذ مرة واحدة عند أول تصيير للمكوِّن. يمكن أن نستخدم خطافات التأثير في أمور عدة بعد إحضار البيانات من الخادم، لكننا سنكتفي حاليًا بهذا الاستخدام. عليك أن تفكر مليًا بتسلسل الأحداث التي ناقشناها. أي جزء من الشيفرة نُفِّذ أولًا، وبأي ترتيب، وكم مرة، لأن فهم ترتيب الأحداث أمر حيوي جدًا. لاحظ أنه كان بالإمكان كتابة شيفرة دالة التأثير على النحو التالي: useEffect(() => { console.log('effect') const eventHandler = response => { console.log('promise fulfilled') setNotes(response.data) } const promise = axios.get('http://localhost:3001/notes') promise.then(eventHandler) }, []) أُسند مرجعٌ لدالة معالج الحدث إلى المتغيّرeventHandler. وخُزّن الوعد الذي يعيده التابع get في المتغيّر promise. يعرّف الاستدعاء (إعادة النداء) بجعل المتغير eventHandler كمُعامل للتابع then العائد إلى الوعد. فمن الضروري إسناد الدوال والوعود إلى متغيرات، ويفضل كتابة الأشياء بشكل مختصر: useEffect(() => { console.log('effect') axios .get('http://localhost:3001/notes') .then(response => { console.log('promise fulfilled') setNotes(response.data) }) }, []) تبقى لدينا مشكلة في التطبيق، فالملاحظات الجديدة التي ننشئها لن تخزن على الخادم. ستجد نسخة عن التطبيق حتى هذه المرحلة على github في الفرع part2-4. بيئة التشغيل الخاصة بالتطوير ستغدو تهيئة التطبيق بشكل كامل أصعب شيئًا فشيئًا. لنراجع إذًا ما الذي يحدث وأين سيحدث. تُنفَّذ شيفرة JavaScript التي توصّف التطبيق على المتصفح. حيث يحضر المتصفح هذه الشيفرة من خادم تطوير React. وهذا الأخير عبارة عن تطبيق يعمل مباشرة بعد تنفيذ الأمر npm start. يحول خادم التطوير dev-server شيفرة JavaScript إلى صيغة يفهمها المتصفح، كما يقوم بعدة أشياء أخرى منها تجميع شيفرة JavaScript من عدة ملفات في ملف واحد. سنطّلع على تفاصيل أكثر حول خادم التطوير في القسم 7. يحضر تطبيق React -الذي يعمل على المتصفح- البيانات بصيغة JSON من خادم JSON الذي يَشغُل المنفذ 3001 من جهازك. يحصل خادم JSON بدوره على البيانات المطلوبة من الملف db.json. تتواجد كل أقسام التطبيق حتى هذه المرحلة من مراحل التطوير ضمن آلة تطوير البرنامج Software Developer 's Machine. والتي تُعرف بالخادم المحلي. سيتغير الوضع حالما تنشر التطبيق على الإنترنت، وهذا ما سنراه في القسم 3. التمارين 2.11 -2.14 2.11 دليل الهاتف: الخطوة 6 خزن المعلومات الأولية للتطبيق في الملف db.json وضعه في المجلد الجذري للمشروع: { "persons":[ { "name": "Arto Hellas", "number": "040-123456", "id": 1 }, { "name": "Ada Lovelace", "number": "39-44-5323523", "id": 2 }, { "name": "Dan Abramov", "number": "12-43-234345", "id": 3 }, { "name": "Mary Poppendieck", "number": "39-23-6423122", "id": 4 } ] } شغل خادم JSON على المنفذ 3001 وتأكد من أن الخادم سيعيد إليك قائمة الأشخاص السابقة عند طلب العنوان http://localhost:3001/persons من المتصفح. إن تلقيت رسالة الخطأ التالية: events.js:182 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE 0.0.0.0:3001 at Object._errnoException (util.js:1019:11) at _exceptionWithHostPort (util.js:1041:20) سيعني ذلك بأن المنفذ 3001 محجوز من قبل تطبيقٍ آخر كخادم JSON آخر. أغلق كل التطبيقات الأخرى أو غيّر رقم المنفذ. عدّل التطبيق ليحضر لك بيانات الأشخاص من الخادم باستخدام المكتبة axios. ثم أكمل عملية إحضار البيانات باستخدام خطاف التأثير. 2.12 معلومات عن البلدان: الخطوة 1 * تزودك الواجهة البرمجية https://restcountries.eu بمعلومات عن بلدان مختلفة بصيغة تفهمها آلة التطوير، والتي تدعى REST API. أنشئ تطبيقًا يمكننا من خلاله الاطلاع على معلوماتٍ من بعض الدول. من المفترض أن يحصل تطبيقك على البيانات من نقطة الاتصال all. واجهة المستخدم بسيطة جدًا، فالبحث عن الدولة يكون بكتابة اسمها في حقل البحث. وإن ظهرت العديد من النتائج المطابقة للبحث (أكثر من 10) تظهر رسالة تنبيه للمستخدم أن يحدد بحثه بشكل أدق. وإن ظهرت عدة نتائج للبحث، لكنها أقل من 10 ستظهر جميع النتائج على الشاشة أما إن كانت نتيجة البحث دولة واحدة، ستظهر بعض المعلومات الأولية عنها مع علمها واللغات المحكية فيها. ملاحظة: يكفي أن يعمل التطبيق لأغلبية بلدان العالم. لأن بعض الدول مثل السودان ستربك التطبيق كون كلمة السودان هي أيضًا جزء من اسم دولة أخرى هي جنوب السودان. لا تلق بالًا لهذا الموضوع الآن. تحذير: تنشئ الأداة create-react-app مستودع git محلي يحتوي المشروع، إلا إن كان في المجلد مستودع محلي سابق. من المرجح أنك لا تريد أن يغدو المشروع مستودعًا، لهذا نفذ الأمر التاليrm -rf .git في مسار المشروع. 2.13 معلومات عن البلدان: الخطوة 2 * هناك الكثير من التمارين التي ينبغي إنجازها، لا تبقى طويلًا في هذا التمرين! (معلّم على أنه غير أساسي) حسِّن التطبيق بحيث يعرض زرًا بجانب الدول التي تظهر كنتيجة للبحث، يعطينا بالنقر عليه المعلومات التي ذكرناها سابقًا عن هذه الدولة. يكفي هنا أيضًا أن يعمل التطبيق بالنسبة لأغلبية البلدان، تجاهل الحالة التي أشرنا إليها سابقًا. 2.14 معلومات عن البلدان: الخطوة 3 * هناك الكثير من التمارين التي ينبغي إنجازها لا تبقى طويلًا في هذا التمرين! (معلّم على أنه غير أساسي) أضف إلى المعلومات التي تظهر عندما تكون نتيجة البحث دولة واحدة فقط، معلومات عن حالة الطقس في العاصمة. ستجد الكثير من الواجهات التي تزودك بأحوال الطقس مثل https://weatherstack.com. ملاحظة: ستحتاج إلى مفتاح لأي واجهة برمجية لخدمات الطقس. لا تحتفظ بالمفتاح ضمن عنصر التحكم ولا تبقه كما هو ضمن الشيفرة المصدرية لتطبيقك. استخدم بدلًا من ذلك متغيرات البيئة لحفظ المفتاح. فلو افترضنا أن مفتاح الواجهة هو 0p53cr3t4p1k3yv4lu3، فعندما يُقلع التطبيق بالشكل التالي: REACT_APP_API_KEY='t0p53cr3t4p1k3yv4lu3' npm start // لملف إقلاع لينوكس وماكنتوش ($env:REACT_APP_API_KEY='t0p53cr3t4p1k3yv4lu3') -and (npm start) // powershell ويندوز set REACT_APP_API_KEY='t0p53cr3t4p1k3yv4lu3' && npm start // سطر أوامر ويندوز يمكنك الوصول إلى قيمة المفتاح من خلال الكائن process.env كالتالي: const api_key = process.env.REACT_APP_API_KEY //يمتلك التطبيق الآن قيمة المفتاح الذي وضعناه عند الإقلاع انتبه: إن أنشأت التطبيق باستخدام الأمر npx create-react-app وأردت أن تطلق اسمًا آخر على متغيّر البيئة يجب أن يبدأ الاسم بالعبارة _REACT_APP. كما يمكنك إنشاء ملف لاحقته env. في المجلد الجذري للمشروع وتضع فيه الشيفرة التالية: # .env REACT_APP_API_KEY=t0p53cr3t4p1k3yv4lu3 بدلًا من تعريف المتغير من خلال سطر الأوامر command line كل مرة. انتبه: عليك إعادة تشغيل الخادم من جديد حتى تُطبّق التغييرات. ترجمة -وبتصرف- للفصل Getting Data from Server](https://fullstackopen.com/en/part2/gettingdatafrom_server) من سلسلة Deep Dive Into Modern Web Development1 نقطة