بناء محرر كود الويب
نشرت: 2022-03-10يعد محرر أكواد الويب عبر الإنترنت مفيدًا للغاية عندما لا تتاح لك الفرصة لاستخدام تطبيق محرر التعليمات البرمجية ، أو عندما تريد تجربة شيء ما على الويب بسرعة باستخدام جهاز الكمبيوتر أو حتى الهاتف المحمول. يعد هذا أيضًا مشروعًا مثيرًا للاهتمام للعمل عليه لأن المعرفة بكيفية إنشاء محرر كود سيمنحك أفكارًا حول كيفية التعامل مع المشاريع الأخرى التي تتطلب منك دمج محرر كود لإظهار بعض الوظائف.
إليك بعض مفاهيم React التي ستحتاج إلى معرفتها للمتابعة في هذه المقالة:
- خطاف
- هيكل المكون ،
- المكونات الوظيفية ،
- الدعائم.
باستخدام CodeMirror
سنستخدم مكتبة تسمى CodeMirror لبناء محررنا. CodeMirror هو محرر نصوص متعدد الاستخدامات يتم تنفيذه في JavaScript للمتصفح. إنه مخصص لتحرير الكود ويأتي مع عدد من أوضاع اللغة والوظائف الإضافية لمزيد من وظائف التحرير المتقدمة.
تتوفر واجهة برمجة تطبيقات برمجة غنية ونظام سمات CSS لتخصيص CodeMirror لتلائم تطبيقك وتوسيعه بوظائف جديدة. يمنحنا وظيفة إنشاء محرر كود غني يعمل على الويب ويظهر لنا نتيجة الكود الخاص بنا في الوقت الفعلي.
في القسم التالي ، سنقوم بإعداد مشروع React الجديد الخاص بنا وتثبيت المكتبات التي نحتاجها لبناء تطبيق الويب الخاص بنا.
إنشاء مشروع React جديد
لنبدأ بإنشاء مشروع React جديد. في واجهة سطر الأوامر ، انتقل إلى الدليل الذي تريد إنشاء مشروعك فيه ، ودعنا ننشئ تطبيق React ونطلق عليه اسم code_editor
:
npx create-react-app code_editor
بعد إنشاء تطبيق React الجديد الخاص بنا ، دعنا ننتقل إلى دليل المشروع في واجهة سطر الأوامر:
cd code_editor
هناك مكتبتان نحتاج إلى تثبيتهما هنا: codemirror
و react-codemirror2
.
npm install codemirror react-codemirror2
بعد تثبيت المكتبات التي نحتاجها لهذا المشروع ، فلنقم بإنشاء علامات التبويب الخاصة بنا وتمكين التبديل بين علامات التبويب الثلاثة التي ستظهر في محررنا (لـ HTML و CSS و JavaScript).
مكون الزر
بدلاً من إنشاء أزرار فردية ، دعنا نجعل الزر مكونًا يمكن إعادة استخدامه. في مشروعنا ، سيكون للزر ثلاث حالات ، وفقًا لعلامات التبويب الثلاث التي نحتاجها.
قم بإنشاء مجلد يسمى components
في مجلد src
. في مجلد components
الجديد هذا ، قم بإنشاء ملف JSX باسم Button.jsx
.
إليك كل التعليمات البرمجية المطلوبة في مكون Button
:
import React from 'react' const Button = ({title, onClick}) => { return ( <div> <button style={{ maxWidth: "140px", minWidth: "80px", height: "30px", marginRight: "5px" }} onClick={onClick} > {title} </button> </div> ) } export default Button
فيما يلي شرح كامل لما فعلناه أعلاه:
- أنشأنا مكونًا وظيفيًا باسم
Button
، ثم قمنا بتصديره. - لقد دمرنا
title
وonClick
من الدعائم القادمة إلى المكون. هنا ، سيكونtitle
عبارة عن سلسلة نصية ، وستكونonClick
وظيفة يتم استدعاؤها عند النقر فوق الزر. - بعد ذلك ، استخدمنا عنصر
button
للإعلان عن الزر الخاص بنا ، واستخدمنا سماتstyle
لتصميم الزر ليبدو أنيقًا. - أضفنا السمة
onClick
وقمنا بتمرير دعامات وظيفةonClick
المدمرة لها. - آخر شيء ستلاحظ أننا فعلناه في هذا المكون هو تمرير
{title}
كمحتوى لعلامةbutton
. يتيح لنا ذلك عرض العنوان ديناميكيًا ، بناءً على الخاصية التي يتم تمريرها إلى مثيل مكون الزر عند استدعائه.
الآن بعد أن أنشأنا مكون زر قابل لإعادة الاستخدام ، دعنا ننتقل ونجلب المكون الخاص بنا إلى App.js.
انتقل إلى App.js
مكون الزر الذي تم إنشاؤه حديثًا:
import Button from './components/Button';
لتتبع علامة التبويب أو المحرر المفتوح ، نحتاج إلى حالة إعلان للاحتفاظ بقيمة المحرر المفتوح. باستخدام useState
، سنقوم بإعداد الحالة التي ستخزن اسم علامة تبويب المحرر المفتوحة حاليًا عند النقر على زر علامة التبويب تلك.
إليك كيف نفعل ذلك:
import React, { useState } from 'react'; import './App.css'; import Button from './components/Button'; function App() { const [openedEditor, setOpenedEditor] = useState('html'); return ( <div className="App"> </div> ); } export default App;
هنا أعلنا دولتنا. يأخذ اسم المحرر المفتوح حاليًا. نظرًا لتمرير القيمة html
كقيمة افتراضية للحالة ، سيكون محرر HTML هو علامة التبويب المفتوحة افتراضيًا.
دعنا ننتقل ونكتب الوظيفة التي ستستخدم setOpenedEditor
لتغيير قيمة الحالة عند النقر فوق زر علامة التبويب.
ملاحظة: قد لا يتم فتح علامتي تبويب في نفس الوقت ، لذلك يتعين علينا مراعاة ذلك عند كتابة وظيفتنا.
إليك ما تبدو وظيفتنا المسماة onTabClick
:
import React, { useState } from 'react'; import './App.css'; import Button from './components/Button'; function App() { ... const onTabClick = (editorName) => { setOpenedEditor(editorName); }; return ( <div className="App"> </div> ); } export default App;
هنا ، مررنا وسيطة دالة واحدة ، وهي اسم علامة التبويب المحددة حاليًا. سيتم توفير هذه الوسيطة في أي مكان يتم استدعاء الوظيفة فيه ، وسيتم تمرير الاسم ذي الصلة لعلامة التبويب هذه.
لنقم بإنشاء ثلاث مثيلات من Button
الخاص بنا لعلامات التبويب الثلاث التي نحتاجها:
<div className="App"> <p>Welcome to the editor!</p> <div className="tab-button-container"> <Button title="HTML" onClick={() => { onTabClick('html') }} /> <Button title="CSS" onClick={() => { onTabClick('css') }} /> <Button title="JavaScript" onClick={() => { onTabClick('js') }} /> </div> </div>
هنا ما فعلناه:
- لقد بدأنا بإضافة علامة
p
، بشكل أساسي فقط لإعطاء بعض السياق لما يدور حوله تطبيقنا. - استخدمنا علامة
div
لالتفاف أزرار علامة التبويب الخاصة بنا. تحمل علامةdiv
اسمclassName
الذي سنستخدمه لتصميم الأزرار في عرض شبكي في ملف CSS لاحقًا في هذا البرنامج التعليمي. - بعد ذلك ، أعلنا عن ثلاث مثيلات لمكون
Button
. إذا كنت تتذكر ، فإن مكونButton
يأخذ عنصرين من الدعائم ،title
وonClick
. في كل مثيل لمكونButton
، يتم توفير هاتين الخاصيتين. - يأخذ عنصر
title
عنوان علامة التبويب. - تأخذ خاصية
onClick
وظيفة ،onTabClick
، التي أنشأناها للتو والتي تأخذ وسيطة واحدة: اسم علامة التبويب المحددة.
استنادًا إلى علامة التبويب المحددة حاليًا ، سنستخدم عامل تشغيل JavaScript الثلاثي لعرض علامة التبويب بشكل مشروط. هذا يعني أنه إذا تم تعيين قيمة حالة openedEditor
على html
( setOpenedEditor('html')
) ، فإن علامة التبويب الخاصة بقسم HTML ستصبح علامة التبويب المرئية حاليًا. ستفهم هذا بشكل أفضل كما نفعله أدناه:
... return ( <div className="App"> ... <div className="editor-container"> { openedEditor === 'html' ? ( <p>The html editor is open</p> ) : openedEditor === 'css' ? ( <p>The CSS editor is open!!!!!!</p> ) : ( <p>the JavaScript editor is open</p> ) } </div> </div> ); ...
دعنا ننتقل إلى الكود أعلاه بلغة إنجليزية بسيطة. إذا كانت قيمة openedEditor
هي html
، فقم بعرض قسم HTML. وإلا ، إذا كانت قيمة openedEditor
هي css
، فقم بعرض قسم CSS. خلاف ذلك ، إذا كانت القيمة ليست html
أو css
، فهذا يعني أن القيمة يجب أن تكون js
، لأن لدينا ثلاث قيم ممكنة فقط لحالة openedEditor
؛ لذلك ، سنعرض علامة تبويب JavaScript.
استخدمنا علامات الفقرة ( p
) للأقسام المختلفة في شروط المشغل الثلاثي. مع تقدمنا ، سننشئ مكونات المحرر ونستبدل علامات p
بمكونات المحرر نفسها.
لقد قطعنا شوطا طويلا بالفعل! عندما يتم النقر فوق الزر ، فإنه يطلق الإجراء الذي يقوم بتعيين علامة التبويب التي يمثلها على القيمة true
، مما يجعل علامة التبويب هذه مرئية. إليك ما يبدو عليه تطبيقنا حاليًا:
دعنا نضيف القليل من CSS إلى حاوية div
التي تحتوي على الأزرار. نريد أن يتم عرض الأزرار في شبكة ، بدلاً من تكديسها عموديًا كما في الصورة أعلاه. انتقل إلى ملف App.css
وأضف الكود التالي:
.tab-button-container{ display: flex; }
تذكر أننا أضفنا className="tab-button-container"
كسمة في علامة div
التي تحمل أزرار علامات التبويب الثلاثة. هنا ، قمنا بتصميم تلك الحاوية ، باستخدام CSS لضبط عرضها على flex
. هذه هي النتيجة:
كن فخورًا بالمقدار الذي فعلته للوصول إلى هذه النقطة. في القسم التالي ، سننشئ برامج التحرير الخاصة بنا ، مع استبدال علامات p
بها.
خلق المحررين
نظرًا لأننا قمنا بالفعل بتثبيت المكتبات التي سنعمل عليها داخل محرر CodeMirror ، فلنقم بإنشاء ملف Editor.jsx
بنا في مجلد components
.
المكونات> Editor.jsx
بعد إنشاء ملفنا الجديد ، دعنا نكتب بعض التعليمات البرمجية الأولية فيه:
import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { return ( <div className="editor-container"> </div> ) } export default Editor
هذا ما فعلناه:
- قمنا باستيراد React بجانب الخطاف
useState
لأننا سنحتاجه. - لقد قمنا باستيراد ملف CodeMirror CSS (الذي يأتي من مكتبة CodeMirror التي قمنا بتثبيتها ، لذا لن تضطر إلى تثبيته بأي طريقة خاصة).
- لقد استوردنا
Controlled
منreact-codemirror2
، وأعدنا تسميته إلىControlledEditorComponent
لجعله أكثر وضوحًا. سوف نستخدم هذا قريبا. - بعد ذلك ، أعلنا عن المكون الوظيفي
Editor
، ولدينا تعليمة إرجاع تحتوي علىdiv
فارغ ، مع اسمclassName
في تعليمة الإرجاع في الوقت الحالي.
في مكوننا الوظيفي ، دمرنا بعض القيم من الخاصيات ، بما في ذلك language
value
و setEditorState
. سيتم توفير هذه العناصر الثلاثة في أي مثيل للمحرر عندما يتم استدعاؤه في App.js
دعنا نستخدم ControlledEditorComponent
لكتابة التعليمات البرمجية لمحررنا. إليك ما سنفعله:
import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { return ( <div className="editor-container"> <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, }} /> </div> ) } export default Editor
دعنا نتعرف على ما فعلناه هنا ، وشرح بعض مصطلحات CodeMirror.
تحدد أوضاع CodeMirror اللغة التي يقصد بها المحرر. قمنا باستيراد ثلاثة أوضاع لأن لدينا ثلاثة محررين لهذا المشروع:
- XML: هذا الوضع خاص بـ HTML. يستخدم مصطلح XML.
- JavaScript: هذا (
codemirror/mode/javascript/javascript
) يجلب وضع JavaScript. - CSS: يجلب (
codemirror/mode/css/css
) وضع CSS.
ملاحظة: نظرًا لأن المحرر مبني كمكون قابل لإعادة الاستخدام ، فلا يمكننا وضع وضع مباشر في المحرر. لذلك ، نوفر الوضع من خلال خاصية language
التي دمرناها. لكن هذا لا يغير حقيقة أن الأوضاع يجب أن يتم استيرادها لكي تعمل.
بعد ذلك ، دعنا نناقش الأشياء في ControlledEditorComponent
:
-
onBeforeChange
يسمى هذا في أي وقت تكتب فيه أو تزيله من المحرر. فكر في هذا مثل معالجonChange
الذي عادة ما يكون لديك في حقل إدخال لتتبع التغييرات. باستخدام هذا ، سنتمكن من الحصول على قيمة محررنا في أي وقت يكون هناك تغيير جديد وحفظه في حالة محررنا. سنكتب الدالة{handleChange}
أثناء تقدمنا. -
value = {value}
هذا فقط محتوى المحرر في أي وقت. لقد مررنا خاصيةvalue
مدمرة لهذه السمة. خاصياتvalue
هي الدولة التي تحتفظ بقيمة ذلك المحرر. سيتم توفير هذا من نسخة المحرر. -
className
="code-mirror-wrapper"
اسم الفصل هذا ليس أسلوبًا نصنعه بأنفسنا. يتم توفيره من ملف CSS الخاص بـ CodeMirror ، والذي قمنا باستيراده أعلاه. -
options
هذا كائن يأخذ الوظائف المختلفة التي نريد أن يمتلكها محررنا. هناك العديد من الخيارات الرائعة في CodeMirror. لنلقِ نظرة على تلك التي استخدمناها هنا:-
lineWrapping: true
هذا يعني أن الكود يجب أن يلتف إلى السطر التالي عندما يكون السطر ممتلئًا. -
lint: true
هذا يسمح linting. -
mode: language
هذا الوضع ، كما نوقش أعلاه ، يأخذ اللغة التي سيتم استخدام المحرر لها. تم استيراد اللغة أعلاه بالفعل ، لكن المحرر سيطبق لغة بناءً على قيمةlanguage
المقدمة للمحرر عبر الخاصية. -
lineNumbers: true
هذا يحدد أن المحرر يجب أن يكون لديه أرقام أسطر لكل سطر.
-
بعد ذلك ، يمكننا كتابة دالة handleChange
لمعالج onBeforeChange
:
const handleChange = (editor, data, value) => { setEditorState(value); }
يتيح لنا معالج onBeforeChange
الوصول إلى ثلاثة أشياء: editor, data, value
.
نحتاج فقط إلى value
لأنها ما نريد تمريره في setEditorState
بنا. تمثل setEditorState
القيمة المحددة لكل حالة أعلناها في App.js
، مع الاحتفاظ بالقيمة لكل محرر. بينما نمضي قدمًا ، سننظر في كيفية تمرير هذا كعنصر خاص إلى مكون Editor
.
بعد ذلك ، سنضيف قائمة منسدلة تسمح لنا بتحديد سمات مختلفة للمحرر. لذلك ، دعونا نلقي نظرة على السمات في CodeMirror.
سمات CodeMirror
يحتوي CodeMirror على موضوعات متعددة يمكننا الاختيار من بينها. قم بزيارة الموقع الرسمي لمشاهدة العروض التوضيحية للموضوعات المختلفة المتاحة. لنقم بعمل قائمة منسدلة بمواضيع مختلفة يمكن للمستخدم الاختيار من بينها في محررنا. في هذا البرنامج التعليمي ، سنضيف خمسة موضوعات ، ولكن يمكنك إضافة أكبر عدد تريده.
أولاً ، دعنا نستورد السمات الخاصة بنا في مكون Editor.js
:
import 'codemirror/theme/dracula.css'; import 'codemirror/theme/material.css'; import 'codemirror/theme/mdn-like.css'; import 'codemirror/theme/the-matrix.css'; import 'codemirror/theme/night.css';
بعد ذلك ، قم بإنشاء مجموعة من جميع السمات التي قمنا باستيرادها:
const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night']
دعنا نعلن عن خطاف useState
للاحتفاظ بقيمة السمة المحددة ، وتعيين السمة الافتراضية على أنها dracula
:
const [theme, setTheme] = useState("dracula")
لنقم بإنشاء القائمة المنسدلة:
... return ( <div className="editor-container"> <div style={{marginBottom: "10px"}}> <label for="cars">Choose a theme: </label> <select name="theme" onChange={(el) => { setTheme(el.target.value) }}> { themeArray.map( theme => ( <option value={theme}>{theme}</option> )) } </select> </div> // the rest of the code comes below... </div> ) ...
في الكود أعلاه ، استخدمنا علامة HTML label
لإضافة تسمية إلى القائمة المنسدلة ، ثم أضفنا علامة HTML select
لإنشاء القائمة المنسدلة الخاصة بنا. تحدد علامة option
في عنصر select
الخيارات المتاحة في القائمة المنسدلة.
نظرًا لأننا احتجنا إلى ملء القائمة المنسدلة بأسماء السمات في themeArray
الذي أنشأناه ، فقد استخدمنا طريقة مصفوفة .map
لتعيين themeArray
وعرض الأسماء بشكل فردي باستخدام علامة option
.
انتظر - لم ننتهي من شرح الكود أعلاه. في علامة select
الافتتاحية ، مررنا سمة onChange
لتتبع حالة theme
وتحديثها كلما تم تحديد قيمة جديدة في القائمة المنسدلة. عندما يتم تحديد خيار جديد في القائمة المنسدلة ، يتم الحصول على القيمة من الكائن الذي يتم إرجاعه إلينا. بعد ذلك ، نستخدم setTheme
من خطاف الحالة الخاص بنا لتعيين القيمة الجديدة لتكون القيمة التي تحتفظ بها الحالة.
في هذه المرحلة ، أنشأنا القائمة المنسدلة ، وقمنا بإعداد حالة موضوعنا ، وكتبنا وظيفتنا لضبط الحالة بالقيمة الجديدة. آخر شيء يتعين علينا القيام به لجعل CodeMirror تستخدم السمة الخاصة بنا هو تمرير السمة إلى كائن options
في ControlledEditorComponent
. في كائن options
، دعنا نضيف قيمة باسم theme
، ونضبط قيمتها على قيمة الحالة للموضوع المحدد ، المسمى أيضًا theme
.
إليك ما سيبدو عليه ControlledEditorComponent
الآن:
<ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} />
الآن ، قمنا بعمل قائمة منسدلة للقوالب المختلفة التي يمكن الاختيار من بينها في المحرر.
هذا ما تبدو عليه الشفرة الكاملة في Editor.js
في الوقت الحالي:
import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/dracula.css'; import 'codemirror/theme/material.css'; import 'codemirror/theme/mdn-like.css'; import 'codemirror/theme/the-matrix.css'; import 'codemirror/theme/night.css'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { const [theme, setTheme] = useState("dracula") const handleChange = (editor, data, value) => { setEditorState(value); } const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night'] return ( <div className="editor-container"> <div style={{marginBottom: "10px"}}> <label for="themes">Choose a theme: </label> <select name="theme" onChange={(el) => { setTheme(el.target.value) }}> { themeArray.map( theme => ( <option value={theme}>{theme}</option> )) } </select> </div> <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} /> </div> ) } export default Editor
هناك فئة واحدة فقط نحتاج إلى className
. انتقل إلى App.css
وأضف النمط التالي:
.editor-container{ padding-top: 0.4%; }
الآن بعد أن أصبح محررونا جاهزين ، دعنا نعود إلى App.js
هناك.
src> App.js
أول شيء يتعين علينا القيام به هو استيراد المكون Editor.js
هنا:
import Editor from './components/Editor';
في App.js
، دعنا نعلن عن الحالات التي ستحتوي على محتويات محررات HTML و CSS و JavaScript ، على التوالي.
const [html, setHtml] = useState(''); const [css, setCss] = useState(''); const [js, setJs] = useState('');
إذا كنت تتذكر ، فسنحتاج إلى استخدام هذه الحالات للاحتفاظ بمحتويات المحررين لدينا وتوفيرها.
بعد ذلك ، دعنا نستبدل علامات الفقرة ( p
) التي استخدمناها في HTML و CSS وجافا سكريبت في العروض الشرطية بمكونات المحرر التي أنشأناها للتو ، وسنمرر أيضًا الخاصية المناسبة لكل مثيل من المحرر عنصر:
function App() { ... return ( <div className="App"> <p>Welcome to the edior</p> // This is where the tab buttons container is... <div className="editor-container"> { htmlEditorIsOpen ? ( <Editor language="xml" value={html} setEditorState={setHtml} /> ) : cssEditorIsOpen ? ( <Editor language="css" value={css} setEditorState={setCss} /> ) : ( <Editor language="javascript" value={js} setEditorState={setJs} /> ) } </div> </div> ); } export default App;
إذا كنت تتابعها حتى الآن ، فستفهم ما فعلناه في مقطع التعليمات البرمجية أعلاه.
ها هو باللغة الإنجليزية البسيطة: لقد استبدلنا علامات p
(التي كانت موجودة كعناصر نائبة) بأمثلة لمكونات المحرر. بعد ذلك ، قمنا بتوفير عناصر language
value
و setEditorState
الخاصة بهم ، على التوالي ، لمطابقة حالاتهم المقابلة.
لقد قطعنا شوطا طويلا! هذا ما يبدو عليه تطبيقنا الآن:
مقدمة عن إطارات Iframes
سنستخدم الإطارات المضمنة (iframes) لعرض نتيجة الكود الذي تم إدخاله في المحرر.
وفقًا لـ MDN:
يمثل عنصر HTML Inline Frame (
<iframe>
) سياق تصفح متداخلًا ، يدمج صفحة HTML أخرى في الصفحة الحالية.
كيف تعمل إطارات Iframes في React
تُستخدم إطارات Iframe عادةً مع HTML عادي. لا يتطلب استخدام إطارات Iframes مع React العديد من التغييرات ، أهمها تحويل أسماء السمات إلى مجموعة أحرف. مثال على ذلك هو أن srcdoc
ستصبح srcDoc
.
مستقبل إطارات Iframes على الويب
لا تزال إطارات Iframe مفيدة حقًا في تطوير الويب. شيء ما قد ترغب في التحقق منه هو البوابات. كما يشرح دانيال برين:
"تقدم البوابات مجموعة جديدة قوية من الإمكانات في هذا المزيج. أصبح من الممكن الآن إنشاء شيء يبدو وكأنه إطار iframe ، يمكن تحريكه وتحويله بسهولة والاستيلاء على نافذة المتصفح الكاملة ".
أحد الأشياء التي تحاول البوابات حلها هي مشكلة شريط عنوان URL. عند استخدام iframe ، لا تحمل المكونات المعروضة في iframe عنوان URL فريدًا في شريط العناوين ؛ على هذا النحو ، قد لا يكون هذا رائعًا لتجربة المستخدم ، اعتمادًا على حالة الاستخدام. تستحق البوابات المراجعة ، وأود أن أقترح عليك القيام بذلك ، ولكن نظرًا لأنها ليست محور مقالتنا ، فهذا كل ما سأقوله عنها هنا.
إنشاء إطار Iframe لإبراز نتيجتنا
دعنا نمضي قدمًا في برنامجنا التعليمي من خلال إنشاء إطار iframe لإيواء نتيجة المحررين لدينا.
return ( <div className="App"> // ... <div> <iframe srcDoc={srcDoc} title="output" sandbox="allow-scripts" frameBorder="1" width="100%" height="100%" /> </div> </div> );
هنا ، أنشأنا iframe ووضعناه في علامة حاوية div
. في إطار iframe ، مررنا بعض السمات التي نحتاجها:
-
srcDoc
تمت كتابة السمةsrcDoc
في حالة الجمل لأن هذه هي كيفية كتابة سمات iframe في React. عند استخدام iframe ، يمكننا إما تضمين صفحة ويب خارجية على الصفحة أو تقديم محتوى HTML محدد. لتحميل صفحة خارجية وتضمينها ، سنستخدم خاصيةsrc
بدلاً من ذلك. في حالتنا ، نحن لا نقوم بتحميل صفحة خارجية ؛ بدلاً من ذلك ، نريد إنشاء مستند HTML داخلي جديد يضم نتائجنا ؛ لهذا ، نحتاج إلى سمةsrcDoc
. تأخذ هذه السمة مستند HTML الذي نريد تضمينه (لم نقم بإنشائه بعد ، لكننا سننشئه قريبًا). -
title
يتم استخدام سمة العنوان لوصف محتويات الإطار المضمن. -
sandbox
هذه الخاصية لها أغراض عديدة. في حالتنا ، نستخدمها للسماح بتشغيل البرامج النصية في إطار iframe الخاص بنا بقيمةallow-scripts
. نظرًا لأننا نعمل باستخدام محرر JavaScript ، فسيكون هذا مفيدًا بسرعة. -
frameBorder
يحدد هذا فقط سماكة إطار iframe. -
width
height
يحدد هذا عرض إطار iframe وارتفاعه.
يجب أن تكون هذه الشروط الآن أكثر منطقية بالنسبة لك. دعنا ننتقل ونعلن الحالة التي ستحتوي على مستند قالب HTML لـ srcDoc
. إذا نظرت عن كثب إلى مقطع التعليمات البرمجية أعلاه ، فسترى أننا مررنا قيمة إلى سمة srcDoc
: srcDoc
={srcDoc}
. دعنا نستخدم useState()
React للإعلان عن حالة srcDoc
. للقيام بذلك ، في ملف App.js
، انتقل إلى حيث حددنا الحالات الأخرى وأضف هذه الحالة:
const [srcDoc, setSrcDoc] = useState(` `);
الآن وقد أنشأنا الحالة ، فإن الشيء التالي الذي يجب فعله هو عرض النتيجة في الحالة كلما نكتب في محرر الكود. ولكن ما لا نريده هو إعادة تصيير المكون في كل ضغطة مفتاح. مع وضع ذلك في الاعتبار ، دعنا ننتقل.
تكوين إطار Iframe لعرض النتيجة
في كل مرة يحدث تغيير في أي من محرري HTML و CSS و JavaScript ، على التوالي ، نريد useEffect()
، وسيؤدي ذلك إلى عرض النتيجة المحدثة في iframe. useEffect()
للقيام بذلك في ملف App.js
:
أولاً ، قم باستيراد useEffect()
:
import React, { useState, useEffect } from 'react';
useEffect()
على النحو التالي:
useEffect(() => { const timeOut = setTimeout(() => { setSrcDoc( ` <html> <body>${html}</body> <style>${css}</style> <script>${js}</script> </html> ` ) }, 250); return () => clearTimeout(timeOut) }, [html, css, js])
هنا ، قمنا بكتابة useEffect()
والذي سيتم تشغيله دائمًا كلما تم تغيير أو تحديث القيمة التي أعلنا عنها لمحرري HTML و CSS و JavaScript.
لماذا احتجنا إلى استخدام setTimeout()
؟ حسنًا ، إذا كتبنا هذا بدونه ، فعندئذٍ في كل مرة يتم فيها الضغط على مفتاح واحد في محرر ، سيتم تحديث إطار iframe الخاص بنا ، وهذا ليس جيدًا للأداء بشكل عام. لذلك نستخدم setTimeout()
لتأخير التحديث لمدة 250 مللي ثانية ، مما يمنحنا وقتًا كافيًا لمعرفة ما إذا كان المستخدم لا يزال يكتب أم لا. أي أنه في كل مرة يضغط فيها المستخدم على مفتاح ، فإنه يعيد تشغيل العد ، لذلك لن يتم تحديث إطار iframe إلا عندما يكون المستخدم خاملاً (لا يكتب) لمدة 250 مللي ثانية. هذه طريقة رائعة لتجنب الاضطرار إلى تحديث إطار iframe في كل مرة يتم فيها الضغط على مفتاح.
كان الشيء التالي الذي فعلناه أعلاه هو تحديث srcDoc
بالتغييرات الجديدة. يعرض مكون srcDoc
، كما أوضحنا أعلاه ، محتوى HTML محددًا في iframe. في الكود الخاص بنا ، مررنا نموذج HTML ، مع أخذ حالة html
التي تحتوي على الكود الذي كتبه المستخدم في محرر HTML ووضعه بين علامات body
في القالب الخاص بنا. أخذنا أيضًا حالة css
التي تحتوي على الأنماط التي كتبها المستخدم في محرر CSS ، وقمنا بتمريرها بين علامات style
. أخيرًا ، أخذنا الحالة js
التي تحتوي على كود JavaScript الذي كتبه المستخدم في محرر JavaScript ، وقمنا بتمريره بين علامات script
.
لاحظ أنه في إعداد setSrcDoc
، استخدمنا backticks ( ` `
) بدلاً من علامات الاقتباس العادية ( ' '
). هذا لأن backticks تسمح لنا بتمرير قيم الحالة المقابلة ، كما فعلنا في الكود أعلاه.
عبارة return
في useEffect()
هي دالة تنظيف تقوم بمسح setTimeout()
عند اكتمالها ، لتجنب تسرب الذاكرة. تحتوي الوثائق على المزيد حول useEffect
.
إليك ما يبدو عليه مشروعنا في الوقت الحالي:
ملحقات CodeMirror
باستخدام الوظائف الإضافية CodeMirror ، يمكننا تحسين محررنا بمزيد من الوظائف التي قد نجدها في برامج تحرير الأكواد الأخرى. دعنا نتصفح مثالاً على علامات الإغلاق التي تتم إضافتها تلقائيًا عند كتابة علامة فتح ، ومثال آخر لقوس يتم إغلاقه تلقائيًا عند إدخال قوس الفتح:
أول شيء يجب فعله هو استيراد الملحق الخاص بهذا إلى ملف App.js
بنا:
import 'codemirror/addon/edit/closetag'; import 'codemirror/addon/edit/closebrackets';
دعنا نمررها في خيارات ControlledEditorComponent
:
<ControlledEditorComponent ... options={{ ... autoCloseTags: true, autoCloseBrackets: true, }} />
الآن هذا ما لدينا:
يمكنك إضافة الكثير من هذه الوظائف الإضافية إلى المحرر الخاص بك لمنحه ميزات أكثر ثراءً. لا يمكننا المرور بها جميعًا هنا.
الآن وقد انتهينا من ذلك ، دعونا نناقش بإيجاز الأشياء التي يمكننا القيام بها لتحسين إمكانية الوصول إلى تطبيقنا وأدائه.
الأداء وإمكانية الوصول إلى الحل
بالنظر إلى محرر كود الويب الخاص بنا ، يمكن بالتأكيد تحسين بعض الأشياء.
نظرًا لأننا أولينا اهتمامًا أساسيًا للوظائف ، فقد نكون قد أهملنا التصميم قليلاً. لإمكانية وصول أفضل ، إليك بعض الأشياء التي يمكنك القيام بها لتحسين هذا الحل:
- يمكنك تعيين فصل دراسي
active
على الزر للمحرر المفتوح حاليًا. سيؤدي تمييز الزر إلى تحسين إمكانية الوصول من خلال منح المستخدمين إشارة واضحة إلى المحرر الذي يعملون عليه حاليًا. - قد ترغب في أن يشغل المحرر مساحة شاشة أكبر مما لدينا هنا. شيء آخر يمكنك تجربته هو جعل إطار iframe ينبثق بنقرة زر مثبتة في مكان ما على الجانب. سيؤدي القيام بذلك إلى منح المحرر مساحة شاشة أكبر.
- سيكون هذا النوع من المحرر مفيدًا للأشخاص الذين يرغبون في إجراء تمرين سريع على أجهزتهم المحمولة ، لذا سيكون من الضروري تكييفه بالكامل مع الهاتف المحمول (ناهيك عن النقطتين حول الهاتف المحمول أعلاه).
- حاليًا ، يمكننا تبديل سمة مكون المحرر من بين السمات المتعددة التي قمنا بتحميلها ، لكن المظهر العام للصفحة يظل كما هو. يمكنك تمكين المستخدم من التبديل بين المظهر الداكن والفاتح للتخطيط بأكمله. سيكون هذا مفيدًا لإمكانية الوصول ، مما يخفف الضغط على أعين الناس من النظر إلى شاشة ساطعة لفترة طويلة.
- لم ننظر إلى مشكلات الأمان في إطار iframe الخاص بنا ، ويرجع ذلك أساسًا إلى أننا كنا نحمل مستند HTML داخليًا في iframe ، وليس مستندًا خارجيًا. لذلك لا نحتاج إلى التفكير في هذا الأمر بعناية شديدة لأن إطارات iframe مناسبة لحالة الاستخدام لدينا.
- مع إطارات iframe ، هناك اعتبار آخر يتمثل في وقت تحميل الصفحة ، لأن المحتوى الذي يتم تحميله في إطار iframe يكون عادةً خارج نطاق سيطرتك. في تطبيقنا ، هذه ليست مشكلة لأن محتوى iframe الخاص بنا ليس خارجيًا.
يستحق الأداء وإمكانية الوصول قدرًا كبيرًا من الاهتمام عند إنشاء أي تطبيق لأنهما سيحددان مدى فائدة تطبيقك وقابليته للاستخدام لمستخدميه.
قام Shedrack بعمل جيد في شرح طرق تحسين الأداء وتحسينه في تطبيقات React. يستحق التدقيق!
خاتمة
يساعدنا العمل من خلال مشاريع مختلفة على التعرف على مجموعة واسعة من الموضوعات. الآن بعد مراجعة هذه المقالة ، لا تتردد في توسيع نطاق تجربتك من خلال تجربة المزيد من الوظائف الإضافية لجعل محرر الكود أكثر ثراءً ، وتجديد واجهة المستخدم ، وإصلاح مشكلات الوصول والأداء الموضحة أعلاه.
- قاعدة التعليمات البرمجية الكاملة لهذا المشروع متاحة على GitHub.
هذا هو العرض التوضيحي على Codesandbox:
الروابط والمواد
- "بوابات Google Chrome: مثل Iframes ، ولكن أفضل ، وأسوأ" ، دانيال برين
- "تحسين الأداء" ، وثائق رد الفعل
- "دليل المستخدم والدليل المرجعي" ، وثائق CodeMirror