دليل شامل: بناء معاينة Markdown تفاعلية باستخدام React.js
مقدمة: بناء معاينة Markdown باستخدام React.js
يُعد بناء المشاريع الفعلية من أفضل الطرق لتعلم مكتبة React.js وترسيخ مبادئها الأساسية. في هذا المقال، سنقوم ببناء تطبيق بسيط لمعاينة نصوص Markdown، شبيه بما تراه في العديد من المحررات. سيكون هذا التطبيق عبارة عن واجهة تفاعلية تضم مربع نص (textarea) لإدخال نصوص Markdown، وعلامة تبويب للمعاينة حيث سيظهر النص المحول.
إذا كنت ترغب في الانتقال مباشرةً إلى الكود المصدري، يمكنك الاطلاع على مستودع GitHub هنا: https://github.com/lelouchB/markdown-previewer/tree/master
وإليك رابط للنسخة المنشورة من التطبيق: https://markdown-previewer.lelouch-b.now.sh/
الآن، دعنا نبدأ.
المتطلبات الأساسية
لتحقيق أقصى استفادة من هذا الدليل، يُفضل أن تكون لديك المعرفة الأساسية بالمفاهيم التالية:
- معرفة جيدة بـ
HTML،CSS،JavaScript، وBootstrap. - مبادئ
Reactالأساسية. - بيئة
Node.jsوnpm(أوyarn) مثبتة على جهازك. - محرر أكواد من اختيارك (مثل
VS Code).
إذا كنت تشعر أنك بحاجة إلى تعزيز معرفتك بأي من هذه المواضيع، فإن منصات مثل freeCodeCamp.org تقدم موارد تعليمية ممتازة لمساعدتك على البدء بسرعة.
إعداد بيئة المشروع
سنقوم بإنشاء هذا التطبيق باستخدام أداة npx create-react-app، وهي الطريقة المدعومة رسميًا لإنشاء تطبيقات React ذات الصفحة الواحدة (SPA). توفر هذه الأداة بيئة تطوير حديثة وجاهزة للعمل دون الحاجة إلى إعدادات معقدة.
للبدء، افتح الطرفية (terminal) في مجلد مشروعك المفضل ونفّذ الأوامر التالية:
npx create-react-app markdown-previewer
cd markdown-previewer
npm start
بعد ذلك، افتح المتصفح على العنوان http://localhost:3000/ لمشاهدة تطبيقك. سيبدو كالتالي:

هيكل المشروع الأولي
الآن، دعنا نلقي نظرة على هيكل المشروع الأولي الذي تم إنشاؤه:
markdown-previewer
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
تتميز هذه الأداة بتوفير بنية ملفات نظيفة وخالية من التعقيدات، مما يتيح لك التركيز على بناء تطبيقك مباشرةً.
تنظيف الملفات الأساسية
قبل المتابعة، سنقوم ببعض التعديلات لتنظيف الملفات الأساسية:
- احذف الملفين
index.cssوApp.css. - نظرًا لحذف ملفات
CSS، قم بإزالة أسطر الاستيرادimport './index.css';وimport './App.css';من الملفينindex.jsوApp.jsعلى التوالي. - احذف الملف
logo.svgوقم بإزالة سطر الاستيرادimport logo from './logo.svg';من الملفApp.js. - داخل الملف
App.js، قم بإزالة الدالةApp(). سنقوم بتصدير مكون من نوع فئة (class component) بدلاً من مكون دالي (functional component). لذا، عدّل محتوىApp.jsليبدو كما يلي:
import React from 'react';
export default class App extends React.Component {
render(){
return (
<div className = "App" >
</ div >
);
}
}
الآن، عند زيارة http://localhost:3000، ستجد الصفحة فارغة، وهذا هو المطلوب.
تصميم واجهة المستخدم وتخطيط المكونات
قبل الغوص في كتابة الأكواد، من الضروري دائمًا وضع خطة واضحة لما ستقوم ببنائه، خاصة عند تصميم واجهات المستخدم باستخدام React. يساعدنا هذا التخطيط على تحديد المكونات اللازمة والبيانات التي سيكون كل مكون مسؤولاً عنها.
لقد قمت برسم تخطيط سريع لتطبيق معاينة Markdown، مع تحديد المكونات الأساسية التي سنحتاجها:

المكونات الرئيسية
بناءً على هذا التصميم، سنحتاج إلى بناء ثلاثة مكونات رئيسية:
- العنوان والعناوين الفرعية (
Title and SubHeading): لعرض العناوين الرئيسية والفرعية للتطبيق. - مربع إدخال
Markdown(Markdown Input TextArea): وهو مربع النص الذي سنكتب فيه نصوصMarkdownللمعاينة. - معاينة
Markdown(Markdown Preview): وهو حاوية بخلفية رمادية لعرض النص المحول.
ملاحظات هامة حول البنية والتصميم
- سيكون لدينا مكون رئيسي واحد يسمى
Appيحتوي على جميع المكونات الأخرى. نظرًا لأن هذا المشروع صغير، فمن السهل إدارة جميع المكونات في ملف واحد. ولكن في المشاريع الأكبر (على سبيل المثال، عند بناء منصة للتجارة الإلكترونية)، يُفضل فصل المكونات إلى ملفات ومجلدات منفصلة بناءً على وظائفها. - بما أن هذا المقال لا يركز على
CSSوالتصميم بشكل معمق، فسوف نستخدم مكتبةReact-Bootstrapوالأنماط المضمنة (Inline Styles). سيتم الإشارة إليهما باختصار.
الأنماط المضمنة (Inline Styles) في React
عند استخدام الأنماط المضمنة في React، يتم تمرير خصائص CSS ككائن (Object) مباشرةً إلى العنصر، بدلاً من استخدام ملفات CSS منفصلة. على سبيل المثال:
var divStyle = {
color: 'white',
backgroundImage: 'url(' + imgUrl + ')',
WebkitTransition: 'all', // note the capital 'W' here
msTransition: 'all' // 'ms' is the only lowercase vendor prefix
};
ReactDOM.render(
<div style = {divStyle} > Hello World! </ div >,
document.getElementById("root")
);
مفاتيح الأنماط (Style keys) تُكتب بصيغة camelCase لتتوافق مع طريقة الوصول إلى خصائص عناصر DOM من خلال JavaScript (على سبيل المثال node.style.backgroundImage). يجب أن تبدأ البادئات الخاصة بالمتصفحات (Vendor prefixes)، باستثناء ms، بحرف كبير. لهذا السبب، تُكتب WebkitTransition بحرف W كبير. يتم تمرير كائن الأنماط إلى مكون DOM باستخدام الأقواس المعقوفة {}. يمكننا أيضًا تشغيل كود JavaScript داخل الدالة return باستخدام {}.
تطبيق الوظائف البرمجية
الآن حان الوقت للبدء في كتابة الكود الفعلي! إذا واجهتك أي صعوبة في أي وقت، فلا تتردد في الرجوع إلى التطبيق النهائي على GitHub أو النسخة المنشورة.
تثبيت المكتبات الضرورية (Dependencies)
لنبدأ بتثبيت المكتبات التي يحتاجها مشروعنا. داخل مجلد المشروع، نفّذ الأوامر التالية في الطرفية:
npm install react-bootstrap bootstrap
npm install marked
دعنا نوضح وظيفة كل مكتبة:
- الأمر الأول يقوم بتثبيت مكتبتي
React-BootstrapوBootstrap، واللتين سنستخدمهما لتصميم وتنسيق واجهة تطبيقنا. - الأمر الثاني يقوم بتثبيت مكتبة
Marked.js، وهي مترجمMarkdownمنخفض المستوى مصمم لتحليل نصوصMarkdownبكفاءة دون تخزين مؤقت أو حظر لفترات طويلة. هذه المكتبة هي التي ستنفذ المنطق الفعلي لتحويل نصوصMarkdown.
قبل البدء في استخدام React-Bootstrap داخل مشروعنا، يجب علينا إضافة ملف CSS المضغوط الخاص بـ Bootstrap إلى ملف index.js:
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
بهذا، تكون المكتبات قد تم تثبيتها وجاهزة للاستخدام.
إضافة العنوان الرئيسي

مهمتنا الأولى هي إضافة عنوان رئيسي لتطبيق React الخاص بنا وتوسيطه. لتحقيق ذلك، سنستخدم مكون Badge من مكتبة React-Bootstrap. اتبع الخطوات التالية:
- استورد
Badgeإلى ملفApp.js. أضف السطر التالي داخلApp.js:
import Badge from "react-bootstrap/Badge";
- داخل الدالة
render()فيApp.js، وتحت العنصرdivالذي يحملclassName="App"، أضف عنصرdivآخر يحملclassName="container":
import React from "react";
import Badge from "react-bootstrap/Badge";
export default class App extends React.Component {
render() {
return (
<div className = "App" >
<div className = "container" >
</ div >
</ div >
);
}
}
- بعد ذلك، داخل العنصر
divالذي يحملclassName="container"، سنضيف العنوان الرئيسي كما يلي:
<h1>
<Badge className = "text-align-center" variant = "light" > Markdown Previewer </ Badge >
</h1>
الآن، يمكنك رؤية العنوان في http://localhost:3000، لكنه ليس متمركزًا. لتوسيط العنوان، سنستخدم فئات Bootstrap ونحيط كتلة الكود أعلاه بعنصرين div:
<div className= "row mt-4" >
<div className = "col text-center" >
<h1>
<Badge className = "text-align-center" variant = "light" > Markdown Previewer </ Badge >
</h1>
</div>
</div>
بهذا، نكون قد أضفنا عنوانًا رئيسيًا لتطبيقنا وقمنا بتوسيطه.
إضافة العناوين الفرعية والأعمدة
بالنظر إلى التصميم الذي ناقشناه سابقًا، فإن الخطوة التالية هي إضافة عمودين يحملان العناوين الفرعية “إدخال Markdown” و “معاينة”. سيحتوي أحدهما على مربع إدخال النص، والآخر على منطقة المعاينة.
أولاً، سنقوم بإنشاء عمودين متجاورين داخل تطبيقنا باستخدام Bootstrap. داخل العنصر div الذي يحمل className="container"، أضف الكود التالي:
<div className= "row mt-4" >
<div className = "col-md-6" >
<h2> Lorem Ipsum </h2>
</div>
<div className = "col-md-6" >
<h2> Lorem Ipsum </h2>
</div>
</div>;
لقد استخدمنا نص Lorem Ipsum مؤقتًا، وسنقوم بتعديله في الخطوة التالية. تُنشأ الأعمدة باستخدام فئات Bootstrap. العنصر div الأول الذي يحمل className="row mt-4" ينشئ صفًا (row). يشير الحرف m إلى margin (الهامش)، والحرف t إلى top (الأعلى). أما العنصران div الآخران اللذان يحملان className="col-md-6" فينشئان عمودين، حيث يشغل كل منهما نصف عرض الشاشة على الأجهزة المتوسطة والكبيرة.
سيبدو التطبيق الآن بهذا الشكل:

الخطوة التالية هي إضافة العناوين الفعلية لهذه الأعمدة وتوسيطها. يمكن تحقيق ذلك بإضافة عنصر div يحمل className="col text-center" داخل كل من العنصرين div اللذين يحملان className="col-md-6"، ثم وضع مكون Badge داخلهما:
<div className= "col text-center" >
<h1>
<Badge className = "text-align-center" variant = "light" > // العنوان الفرعي الفعلي: هذا الكود سيكون نفسه لكلا العمودين </ Badge >
</h1>
</div>
بعد هذه التعديلات، سيبدو ملف App.js الخاص بك كالتالي:

إضافة مربع إدخال النص (TextArea)
الآن سنقوم بإضافة مربع إدخال النص (TextArea) إلى تطبيقنا، وذلك باستخدام وسم <textarea> البسيط من HTML.
داخل العمود الأول (الخاص بـ “إدخال Markdown“)، أضف عنصر div جديد يحمل className="mark-input"، وداخله أضف وسم <textarea> يحمل className="input":
<div className= "mark-input" >
<textarea className = "input" >
</textarea>
</div>;
دعنا نخصص مربع النص قليلاً. كما ناقشنا سابقًا، سنستخدم الأنماط المضمنة (Inline Styles). لهذا الغرض، سنقوم بتهيئة كائن أنماط (Object). سيتم تعريف جميع المتغيرات داخل الدالة render() ولكن خارج جملة return. على سبيل المثال:
render(){
var variableOne = "Lorem Ipsum"
var variableTwo = "Lorem Ipsum"
return (
// Code
)
}
إليك كائن الأنماط inputStyle الذي سنستخدمه:
var inputStyle = {
width: "400px",
height: "50vh",
marginLeft: "auto",
marginRight: "auto",
padding: "10px"
};
الآن، لنطبق كائن inputStyle على مربع النص textarea الخاص بنا:
<div className= "mark-input" style={inputStyle}>
<textarea className = "input" style = {inputStyle} >
</textarea>
</div>
بهذا، نكون قد أضفنا مربع إدخال النص إلى تطبيقنا، وسيبدو كالتالي:

إضافة منطقة المعاينة
لفصل منطقة المعاينة عن بقية التطبيق ووضعها داخل حاوية خاصة بها، سنتبع نفس العملية السابقة. سنقوم بإنشاء عنصر div داخل العمود الثاني (الخاص بـ “معاينة”) ونضيف إليه كائن أنماط خاص به.
إليك كائن الأنماط outputStyle:
var outputStyle = {
width: "400px",
height: "50vh",
backgroundColor: "#DCDCDC",
marginLeft: "auto",
marginRight: "auto",
padding: "10px"
};
الآن، أضف هذا الكائن إلى العنصر div داخل العمود الثاني:
<div className= "col-md-6" >
<div className = "col text-center" >
<h4>
<Badge className = "text-align-center" variant = "secondary" > Preview </ Badge >
</h4>
</div>
<div style = {outputStyle} >
</div>
</div>
سيبدو التطبيق الآن بهذا الشكل:

لقد أكملنا الآن تصميم واجهة تطبيقنا، لكنه يفتقر إلى الوظائف الأساسية. لذا، دعنا نضيفها. يمكن تقسيم هذه العملية إلى ثلاث خطوات رئيسية:
- الحصول على المدخلات من مربع النص (
TextArea). - تمرير هذه المدخلات إلى مكتبة
Marked.js. - عرض البيانات المعالجة في عمود المعاينة.
الحصول على المدخلات من مربع النص (TextArea)
سنستخدم كائن الحالة (state) في React. في React، state هو كائن يمثل الأجزاء المتغيرة من التطبيق. يمكن لكل مكون الاحتفاظ بحالته الخاصة، والتي توجد في كائن يسمى this.state. ببساطة، إذا كنت ترغب في أن يقوم تطبيقك بأي شيء تفاعلي – مثل إضافة أو حذف عناصر، تسجيل الدخول أو الخروج – فسيشمل ذلك استخدام الحالة.
هنا، قيمة مربع النص تتغير، وبالتالي ستتغير حالتنا معها. سنضيف كائن الحالة داخل مكون الفئة App، فوق الدالة render(). من أفضل الممارسات تهيئة الحالة داخل الدالة البانية (constructor). يمكن أن يعمل بدون constructor أيضًا، ولكن يُنصح بتجنب ذلك.
إليك كيفية تهيئة الحالة بخاصية markdown، والتي ستحتوي مبدئيًا على سلسلة نصية فارغة:
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
markdown: "",
};
}
render() {
// method and other code
}
}
في هذا المشروع أو أي مشاريع React أخرى، قم دائمًا بتهيئة state داخل constructor(props) وتحت super(props).
عادةً، تُستخدم الدوال البانية (constructors) في React لغرضين فقط:
- تهيئة الحالة المحلية بتعيين كائن إلى
this.state. - ربط دوال معالجة الأحداث (
event handler methods) بمثيل المكون.
تذكر أن constructor هو المكان الوحيد الذي يجب أن تقوم فيه بتعيين this.state مباشرةً. في جميع الدوال الأخرى، يجب عليك استخدام this.setState() بدلاً من ذلك.
تغييرات الحالة غير متزامنة (asynchronous). لتحسين الأداء المتصور، قد يؤخر React التحديثات ثم يقوم بتحديث عدة مكونات في تمريرة واحدة. لا يضمن React تطبيق تغييرات الحالة فورًا.
كما ناقشنا أعلاه، لا يمكننا تغيير الحالة مباشرةً. بدلاً من ذلك، يجب علينا استخدام this.setState(). لذا، دعنا ننشئ دالة تقوم بذلك ويتم استدعاؤها في كل مرة تتغير فيها قيمة مربع النص:
updateMarkdown(markdown) {
this.setState({ markdown });
}
يتم إنشاء هذه الدالة داخل مكون الفئة ولكن فوق الدالة render().
الآن، سنقوم أولاً بتعيين قيمة مربع النص إلى خاصية markdown في الحالة:
<textarea className= "input" style={inputStyle} value={ this.state.markdown}></textarea>
يمكننا الآن إضافة الدالة updateMarkdown() إلى حدث onChange() داخل وسم <textarea> واستدعائها باستخدام this.updateMarkdown():
<textarea className= "input" style={inputStyle} value={ this.state.markdown} onChange={ ( e ) => {
this.updateMarkdown(e.target.value);
}} ></textarea>;
للتحقق مما إذا كانت الحالة تُعين بشكل صحيح، يمكننا تمرير كود JavaScript واستخدام console.log() لعرض حالتنا:
<textarea className= "input" style={inputStyle} value={ this.state.markdown} onChange={ ( e ) => {
this.updateMarkdown(e.target.value);
}} > { console.log( this.state.markdown)} </textarea>;
الآن، افتح وحدة تحكم المتصفح (console) وحاول الكتابة داخل مربع النص. نأمل أن ترى نفس النص يظهر في وحدة التحكم.

بهذا، نكون قد قمنا بنجاح بتعيين مدخلات مربع النص إلى خاصية markdown في حالتنا.
دمج مكتبة Marked.js
تُعد مكتبة Marked.js العقل المدبر وراء عملية تحويل Markdown، وهي سهلة الاستخدام للغاية.
لاستيراد marked، أضف السطر التالي مباشرةً بعد سطر استيراد Badge من react-bootstrap/Badge:
let marked = require("marked");
لاستخدام مكتبة Marked.js، ما علينا سوى تمرير السلسلة النصية المراد تحويلها إلى الدالة marked(). لدينا بالفعل البيانات مخزنة ديناميكيًا داخل كائن الحالة، لذا سيتم ذلك كالتالي:
marked( this.state.markdown)
الخطوة التالية هي عرض البيانات المحولة فعليًا على صفحة الويب. لتحقيق ذلك، سنستخدم خاصية dangerouslySetInnerHTML، وهي سمة خاصة بعناصر DOM في React.
وفقًا للوثائق الرسمية، dangerouslySetInnerHTML هي بديل React لاستخدام innerHTML في DOM المتصفح. هذا يعني أنه إذا كان عليك في React تعيين HTML برمجيًا أو من مصدر خارجي، فسيتعين عليك استخدام dangerouslySetInnerHTML بدلاً من innerHTML التقليدي في JavaScript.
بكلمات بسيطة، باستخدام dangerouslySetInnerHTML، يمكنك تعيين محتوى HTML مباشرةً من React.
عند استخدام dangerouslySetInnerHTML، سيتعين عليك تمرير كائن يحتوي على مفتاح __html (لاحظ أن المفتاح يتكون من شرطتين سفليتين). إليك كيفية القيام بذلك:
<div style={outputStyle} dangerouslySetInnerHTML={{ __html: marked( this.state.markdown) }} >
</div>
بهذا، نكون قد أكملنا مشروعنا، والآن سترى معاينة Markdown الخاصة بك تعمل بكفاءة!
الكود الكامل لملف App.js
بعد جميع الخطوات السابقة، سيبدو ملف App.js النهائي كالتالي:
import React from "react";
import Badge from "react-bootstrap/Badge";
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
let marked = require("marked");
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
markdown: ""
};
}
updateMarkdown(markdown) {
this.setState({ markdown });
}
render() {
var inputStyle = {
width: "400px",
height: "50vh",
marginLeft: "auto",
marginRight: "auto",
padding: "10px"
};
var outputStyle = {
width: "400px",
height: "50vh",
backgroundColor: "#DCDCDC",
marginLeft: "auto",
marginRight: "auto",
padding: "10px"
};
return (
<div className="App">
<div className="container">
<div className="row mt-4">
<div className="col text-center">
<h1>
<Badge className="text-align-center" variant="light">
Markdown Previewer
</Badge>
</h1>
</div>
</div>
<div className="row mt-4">
<div className="col-md-6">
<div className="col text-center">
<h4>
<Badge className="text-align-center" variant="light">
Markdown Input
</Badge>
</h4>
</div>
<div className="mark-input" style={inputStyle}>
<textarea
className="input"
style={inputStyle}
value={this.state.markdown}
onChange={(e) => {
this.updateMarkdown(e.target.value);
}}
></textarea>
</div>
</div>
<div className="col-md-6">
<div className="col text-center">
<h4>
<Badge className="text-align-center" variant="secondary">
Preview
</Badge>
</h4>
</div>
<div
style={outputStyle}
dangerouslySetInnerHTML={{ __html: marked(this.state.markdown) }}
></div>
</div>
</div>
</div>
</div>
);
}
}
تهانينا على بناء معاينة Markdown هذه باستخدام React.
الخطوات التالية والمزيد من التخصيص
لقد نجحنا في بناء تطبيق معاينة Markdown وظيفي بالكامل. ولكن ماذا بعد؟ المستقبل بين يديك!
يمكنك تطوير نسختك الخاصة من معاينة Markdown عن طريق إضافة تصميمات مختلفة، تخطيطات فريدة، ووظائف مخصصة. على سبيل المثال، يمكنك إضافة زر “إعادة تعيين” لمسح مربع النص، أو دمج ميزات إضافية مثل حفظ المحتوى أو تصديره. حدود الإبداع هي خيالك فقط!
نأمل أن تكون قد استمتعت بمسار البرمجة هذا. إذا كان لديك أي اقتراحات لمشاريع أو دروس تعليمية أخرى تود رؤيتها، فلا تتردد في التواصل. وإذا قمت بإضافة ميزات جديدة إلى مشروعك، يسعدنا أن تشاركها معنا!
الخلاصة التقنية
في هذا الدليل، استعرضنا عملية بناء تطبيق معاينة Markdown تفاعلي باستخدام React.js. بدأنا بإعداد بيئة المشروع باستخدام create-react-app، ثم قمنا بتنظيف البنية الأولية للملفات. ركزنا على تصميم الواجهة وتحديد المكونات الرئيسية، مع الاستفادة من مكتبة React-Bootstrap للأنماط المضمنة. كان الجزء الأهم هو إضافة الوظائف، حيث تعلمنا كيفية إدارة حالة المكونات (state) لالتقاط مدخلات المستخدم، وكيفية استخدام مكتبة Marked.js لتحويل نصوص Markdown. أخيرًا، استخدمنا خاصية dangerouslySetInnerHTML لعرض المحتوى المحول بأمان. يوضح هذا المشروع قوة React في بناء واجهات مستخدم ديناميكية وفعالة، ويوفر أساسًا ممتازًا لتطوير تطبيقات ويب أكثر تعقيدًا.