React: مكونات الوظائف و«props»
آخر تحديث: 2026-08-26
مكونات الدوال تشبه مكعبات الليغو — فكل مكعب له شكله الخاص (الخصائص)، ويمكن تجميعها ودمجها بطرق متنوعة. والخصائص هي «واجهات» المكعبات: فأيًا كان اللون أو الحجم الذي تحددانه للمكعب، فسيظهر بهذه الصورة.
1. ما ستتعلمه
- تعريف قواعد تسمية مكونات الوظائف
- تمرير واستلام المقتنيات المسرحية
- أنواع المعلمات (سلسلة / عدد / قيمة منطقية / كائن)
- دعامة خاصة للأطفال
- استخدم TypeScript لتعريف أنواع الخصائص (props)
2. قصة حقيقية لفريق تطوير الواجهة الأمامية
تدفق البيانات بين المكونات والخصائص
flowchart TD
A[Parent Component] -->|"name='Alice'"| B[UserCard]
A -->|"items=[...]"| C[ProductList]
C -->|"item={...}"| D[ProductCard]
B -->|"children"| E[<Avatar />]
A -->|"onFollow callback"| B
style A fill:#e3f2fd,stroke:#1565c0
style B fill:#e8f5e9,stroke:#2e7d32
style C fill:#fff3e0,stroke:#e65100
style D fill:#fce4ec,stroke:#c62828
(1) المشكلة: زر يتعين عليك نسخه ولصقه 50 مرة
تشارلي هو المسؤول التقني لفريق الواجهة الأمامية. وقد اكتشف وجود أكثر من 50 زرًا مختلفًا موزعة على 20 صفحة في مستودع الكود:
// Page A:Red Button
<button style={{ backgroundColor: 'red', color: 'white' }}>
Delete
</button>
// Page B:Blue Button
<button style={{ backgroundColor: 'blue', color: 'white' }}>
Submit
</button>
// Page C:Small gray button
<button style={{ backgroundColor: 'gray', color: 'black', fontSize: '12px' }}>
Cancel
</button>
السؤال:
- تغييرات في التصميم: يجب أن تكون جميع الأزرار مستديرة؛ وهذا يتطلب إجراء تغييرات في 50 موضعًا.
- عدم الاتساق: بعض الأزرار تستخدم
red، وبعضها تستخدم#ff0000، وبعضها تستخدمrgb(255,0,0) - أخطاء متكررة: قام أحدهم بكتابة نمط العنصر بشكل خاطئ، مما تسبب في أن يصبح الزر بأكمله شفافًا
(2) نهج قائم على المكونات
الإدارة الموحدة باستخدام المكونات الوظيفية و«props»:
function Button({ variant = 'primary', size = 'medium', children }) {
const styles = {
primary: { backgroundColor: '#1890ff', color: 'white' },
danger: { backgroundColor: '#ff4d4f', color: 'white' },
default: { backgroundColor: '#f0f0f0', color: '#333' }
}
return (
<button style={{
...styles[variant],
padding: size === 'large' ? '12px 24px' : '8px 16px',
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
}}>
{children}
</button>
)
}
// Usage:Done in one line of code
<Button variant="danger">Delete</Button>
<Button variant="primary">Submit</Button>
<Button variant="default">Cancel</Button>
المزايا: تم تقليل عدد الأزرار من 50 جزءًا إلى مكون واحد؛ وأصبحت التغييرات في التصميم تتطلب تعديلاً واحدًا فقط، مما يضمن اتساق الأسلوب.
3. أساسيات مكونات الوظائف
(1) تعريف المكون
في React، يُعرف مكون الدالة بأنه دالة JavaScript تُرجع JSX.
// The Simplest Function Component
function Welcome() {
return <h1>Hello, React!</h1>
}
// How to Write Arrow Functions(More common)
const Welcome = () => {
return <h1>Hello, React!</h1>
}
// Implicit Return(Only one line JSX "when " can be omitted return)
const Welcome = () => <h1>Hello, React!</h1>
| القاعدة | الوصف |
|---|---|
| كتابة الحرف الأول بحرف كبير | Welcome ✅ welcome ❌ (يميز React بين المكونات وعلامات HTML) |
| إرجاع JSX | يجب إرجاع عنصر JSX (أو null) |
| الدوال النقية | يجب أن تُرجع نفس المدخلات (Props) نفس المخرجات (UI) |
| لا يمكن تعديل الخصائص | الخصائص هي للقراءة فقط ولا يمكن تعديلها داخل المكون |
(2) المعلمات هي معلمات الدالة
تُعرف «البروبس» (اختصار لـ «Properties») بأنها بيانات للقراءة فقط تُمرر من مكون أب إلى مكون تابع.
// ---- Define a Component ----
function Greeting(props) {
// props It is an object,Includes all incoming parameters
return <h1>Hello,{props.name}!This year, you {props.age} years old。</h1>
}
// ---- Using Components ----
<Greeting name="Alice" age={28} />
// Rendering Results:
// <h1>Hello,Alice!This year, you 28 years old。</h1>
(3) قواعد إزالة هيكلية الخصائص (Props)
وهناك نهج أكثر شيوعًا يُعرف بـ التعيين التفكيكي، والذي يستخرج المعلمات المطلوبة مباشرةً:
// Deconstructive Writing Style(Recommendations)
function Greeting({ name, age }) {
return <h1>Hello,{name}!This year, you {age} years old。</h1>
}
// equivalent to:
function Greeting(props) {
const { name, age } = props
return <h1>Hello,{name}!This year, you {age} years old。</h1>
}
4. أنواع الدعائم
| نوع الكائن | صيغة التمرير | مثال على الاستلام | ملاحظات |
|---|---|---|---|
| سلسلة | name="Alice" |
{ name } → "Alice" |
محاطة بعلامتي اقتباس مزدوجتين |
| الرقم | age={30} |
{ age } → 30 |
يجب استخدام {} |
| منطقية | active / active={true} |
{ active } → صحيح |
في حالة حذف القيمة، تكون القيمة الافتراضية هي «صحيح» |
| كائن | info={{ key: 'val' }} |
{ info } → { key: 'val' } |
أقواس معقوفة مزدوجة |
| مصفوفة | items={[1, 2, 3]} |
{ items } → [1,2,3] |
يجب استخدام {} |
| الوظيفة | onClick={handler} |
{ onClick } → fn |
معالج الاستدعاء/الحدث |
| المكون | icon={<Icon />} |
{ icon } → JSX |
مكون يتم عرضه ديناميكيًا |
يمكن أن تقبل العناصر المساعدة أي نوع من أنواع بيانات جافا سكريبت: السلاسل، والأرقام، والقيم المنطقية، والكائنات، والمصفوفات، والدوال، وحتى المكونات الأخرى.
(1) سلسلة
يمكن تمرير السلاسل مباشرةً باستخدام علامات الاقتباس المزدوجة (تمامًا مثل سمات HTML):
function UserCard({ name, role }) {
return (
<div className="card">
<h2>{name}</h2>
<p>Characters:{role}</p>
</div>
)
}
// Strings are enclosed in double quotes,For other types {} Package
<UserCard name="Alice" role="Administrator" />
(2) الأعداد والقيم المنطقية والكائنات
يجب أن تُحاط الأنواع غير السلسلية بعلامتي {}:
function Product({ name, price, inStock, details }) {
return (
<div className="product">
<h3>{name}</h3>
<p>Price:${price}</p>
{/* Boolean values are used for conditional rendering */}
{inStock ? <span>In stock ✅</span> : <span>Out of stock ❌</span>}
{/* Object:Accessible properties */}
<p>Category:{details.category} | Weight:{details.weight}g</p>
</div>
)
}
// Using Components
<Product
name="Wireless Mouse"
price={89}
inStock={true}
details={{ category: 'Peripherals', weight: 120 }}
/>
(3) جدول مرجعي سريع للطرق التي تستخدم التمرير بالقيمة
| النوع | مثال | الوصف |
|---|---|---|
| سلسلة | name="Alice" |
تُكتب بنفس طريقة كتابة سمات HTML |
| الرقم | count={42} |
يتطلب {} |
| القيمة المنطقية «true» | active أو active={true} |
إذا تم تحديد اسم الخاصية فقط، فإن القيمة الافتراضية هي «true» |
| القيمة المنطقية «false» | active={false} |
يتطلب {} |
| الكائن | data={{ x:1, y:2 }} |
الأقواس المزدوجة |
| مصفوفة | items={[1,2,3]} |
يتطلب {} |
| تعبير | result={a + b} |
أي تعبير JS |
| متغير | name={userName} |
متغير يتم تمريره بالقيمة |
▶ مثال: بطاقة معلومات المستخدم
// ============================================
// Example:use Props Build a User Information Card
// Features:Display Props Transmission of Various Types
// ============================================
function UserCard({ name, age, isOnline, avatar, tags }) {
return (
<div style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '20px',
maxWidth: '300px'
}}>
<img
src={avatar}
alt={name}
style={{ width: '80px', height: '80px', borderRadius: '50%' }}
/>
<h2>{name}</h2>
<p>Age:{age} years old</p>
<p>
Status:
<span style={{ color: isOnline ? '#52c41a' : '#999' }}>
{isOnline ? 'Online' : 'Offline'}
</span>
</p>
<div>
Tags:
{tags.map(tag => (
<span key={tag} style={{
background: '#f0f0f0',
padding: '2px 8px',
borderRadius: '4px',
margin: '0 4px',
fontSize: '12px'
}}>
{tag}
</span>
))}
</div>
</div>
)
}
// Using Components
<UserCard
name="Alice"
age={28}
isOnline={true}
avatar="https://i.pravatar.cc/80"
tags={['Front End', 'React', 'TypeScript']}
/>
5. العناصر التابعة والقيم الافتراضية
(1) children: محتوى متداخل داخل المكون
children هو عنصر خاص يمثل المحتوى الموجود بين علامتي component.
// Definition:use children Content between tags
function Card({ children, title }) {
return (
<div style={{ border: '1px solid #ddd', padding: '16px', borderRadius: '8px' }}>
<h3>{title}</h3>
<div>{children}</div> {/* The content between the tags is displayed here */}
</div>
)
}
// Usage:The content between the tags automatically becomes children
<Card title="Announcement">
<p>The system will go live tonight 22:00 Perform maintenance,Expected to continue 2 hours。</p>
<p>Please save your work in advance.,To prevent data loss。</p>
</Card>
// Rendering Results:
// <div class="card">
// <h3>Announcement</h3>
// <div>
// <p>The system will go live tonight 22:00 Perform maintenance...</p>
// <p>Please save your work in advance....</p>
// </div>
// </div>
(2) القيمة الافتراضية
استخدم صيغة تفكيك ES6 مع القيم الافتراضية لتعيين قيم افتراضية لـ props:
function Button({ text = 'Click', color = 'blue', size = 'medium' }) {
return (
<button style={{
backgroundColor: color,
padding: size === 'large' ? '12px 24px' : '8px 16px',
color: 'white',
border: 'none',
borderRadius: '4px'
}}>
{text}
</button>
)
}
// No parameters → Use the default values
<Button /> // Blue、medium、"Click"
// Pass some parameters → Override the default value
<Button text="Submit" /> // Blue、medium、"Submit"
<Button color="red" size="large" /> // Red、large、"Click"
6. تعليقات أنواع TypeScript (نظرة عامة)
باستخدام TypeScript، يمكنك تعريف أنواع صارمة لـ props، مما يمكّن المحرر من تقديم اقتراحات الإكمال التلقائي وإجراء عمليات التحقق من الأنواع:
// ---- Definition Props Type ----
interface ButtonProps {
text: string // Required:string
color?: string // Optional: add ? suffix
size?: 'small' | 'medium' | 'large' // Optional: limited to 3 values
onClick: () => void // Required:Function Types
disabled?: boolean // Optional: boolean, defaults to false
}
// ---- Using in a component ----
function Button({ text, color = 'blue', size = 'medium', onClick, disabled = false }: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
style={{
backgroundColor: color,
padding: size === 'large' ? '12px 24px' : '8px 16px',
opacity: disabled ? 0.5 : 1,
color: 'white',
border: 'none',
borderRadius: '4px'
}}
>
{text}
</button>
)
}
// Usage:The editor provides smart suggestions
<Button
text="Save"
color="#1890ff"
size="medium"
onClick={() => alert('Saved successfully!')}
/>
// ❌ Error:TypeScript It will throw an error
<Button text="Test" /> // Required field is missing onClick
<Button text="Test" size="xl" onClick={fn} /> // size Outside the permitted range
7. مثال كامل: مكون «بطاقة المنتج»
// ============================================
// Example:Product Card Component(Comprehensive Application Props Various Types)
// ============================================
// Type Definitions
interface Product {
id: number
name: string
price: number
image: string
category: string
tags: string[]
}
interface ProductCardProps {
product: Product
onAddToCart: (productId: number) => void
showDiscount?: boolean
size?: 'small' | 'large'
}
// Component Implementation
function ProductCard({
product,
onAddToCart,
showDiscount = false,
size = 'large'
}: ProductCardProps) {
return (
<div style={{
border: '1px solid #eee',
borderRadius: '8px',
padding: size === 'large' ? '16px' : '10px',
width: size === 'large' ? '280px' : '200px'
}}>
<img
src={product.image}
alt={product.name}
style={{ width: '100%', height: '150px', objectFit: 'cover' }}
/>
<h3>{product.name}</h3>
<p style={{ color: '#ff4d4f', fontSize: '20px', fontWeight: 'bold' }}>
${product.price}
{showDiscount && <span style={{ fontSize: '12px', color: '#999', textDecoration: 'line-through', marginLeft: '8px' }}>${(product.price * 1.2).toFixed(0)}</span>}
</p>
<p style={{ color: '#666', fontSize: '14px' }}>{product.category}</p>
<div>
{product.tags.map(tag => (
<span key={tag} style={{ background: '#f0f5ff', color: '#1890ff', padding: '2px 6px', borderRadius: '4px', margin: '0 2px', fontSize: '12px' }}>
{tag}
</span>
))}
</div>
<button
onClick={() => onAddToCart(product.id)}
style={{
width: '100%',
padding: '8px',
marginTop: '10px',
backgroundColor: '#1890ff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Add to Cart
</button>
</div>
)
}
// Using Components
const myProduct = {
id: 101,
name: 'Mechanical Keyboard',
price: 299,
image: 'https://via.placeholder.com/280x150',
category: 'Computer Peripherals',
tags: ['Hot Seller', 'New Products']
}
<ProductCard
product={myProduct}
onAddToCart={(id) => console.log('Add to Cart:', id)}
showDiscount={true}
/>
النتيجة المتوقعة: بطاقة منتج تعرض صورة لوحة مفاتيح، واسم «لوحة مفاتيح ميكانيكية»، والسعر 299 دولارًا (مع خط مشطوب يشير إلى السعر الأصلي البالغ 359 دولارًا)، والفئة «ملحقات الكمبيوتر»، والعلامات «منتج مبيعًا» و«وصول جديد»، مع زر أزرق «إضافة إلى سلة التسوق» أسفلها.
▶ المثال 2: الاستخدام الشامل لتمرير أنواع المعلمات والقيم الافتراضية
// ============================================
// Example:Profile Card——Props Combined Use of Types and Default Values
// Features:Display string、number、boolean、object、function etc. Props Type Passing
// ============================================
function ProfileCard({
name = 'Anonymous User',
age = 0,
isActive = false,
avatar = 'https://via.placeholder.com/60',
skills = [],
onGreet
}) {
return (
<div style={{
border: '1px solid #e8e8e8',
borderRadius: '12px',
padding: '20px',
maxWidth: '320px',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '16px' }}>
<img
src={avatar}
alt={name}
style={{ width: '60px', height: '60px', borderRadius: '50%', objectFit: 'cover' }}
/>
<div>
<h3 style={{ margin: 0 }}>{name}</h3>
<p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '14px' }}>
{age} years old · {isActive ? '🟢 Online' : '🔴 Offline'}
</p>
</div>
</div>
{skills.length > 0 && (
<div style={{ marginBottom: '12px' }}>
<p style={{ margin: '0 0 8px 0', fontWeight: 'bold', fontSize: '14px' }}>Skills:</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
{skills.map((skill, index) => (
<span key={index} style={{
backgroundColor: '#f0f5ff',
color: '#1890ff',
padding: '4px 10px',
borderRadius: '12px',
fontSize: '12px'
}}>
{skill}
</span>
))}
</div>
</div>
)}
{onGreet && (
<button
onClick={() => onGreet(name)}
style={{
width: '100%',
padding: '8px',
backgroundColor: '#1890ff',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px'
}}
>
Say hello to {name}
</button>
)}
</div>
)
}
// Usage:Pass all parameters
<ProfileCard
name="Alice"
age={28}
isActive={true}
avatar="https://i.pravatar.cc/60"
skills={['React', 'TypeScript', 'Node.js']}
onGreet={(name) => alert(`Hello,${name}!`)}
/>
// Usage:Pass only some parameters(Use the default values for the rest)
<ProfileCard
name="Bob"
skills={['Vue', 'Python']}
/>
// Usage:Do not pass any parameters(Use all default values)
<ProfileCard />
▶ المثال 3: خصائص استدعاء رد الفعل للأحداث — المكونات الفرعية تُخطر المكونات الرئيسية
function AddToCartButton({ productName, onAdd }) {
const [adding, setAdding] = useState(false)
async function handleClick() {
setAdding(true)
await onAdd(productName)
setAdding(false)
}
return (
<button onClick={handleClick} disabled={adding}
style={{ padding: '8px 16px', cursor: adding ? 'wait' : 'pointer' }}>
{adding ? 'Adding...' : `Add ${productName}`}
</button>
)
}
function ProductPage() {
const [cart, setCart] = useState([])
function handleAdd(name) {
setCart(prev => [...prev, name])
}
return (
<div>
<h2>Products</h2>
<AddToCartButton productName="Keyboard" onAdd={handleAdd} />
<AddToCartButton productName="Mouse" onAdd={handleAdd} />
<p>Cart: {cart.join(', ') || 'empty'}</p>
</div>
)
}
▶ المثال 4: العرض الشرطي لخصائص المكونات — التبديل الديناميكي بين المكونات
function StatusMessage({ status }) {
const config = {
loading: { icon: '⏳', color: '#1890ff', text: 'Loading...' },
success: { icon: '✅', color: '#52c41a', text: 'Success!' },
error: { icon: '❌', color: '#ff4d4f', text: 'Error occurred' },
empty: { icon: '📭', color: '#999', text: 'No data' },
}
const { icon, color, text } = config[status] || config.empty
return (
<div style={{ padding: 16, textAlign: 'center', color }}>
<span style={{ fontSize: 32 }}>{icon}</span>
<p>{text}</p>
</div>
)
}
function DataViewer() {
const [status, setStatus] = useState('loading')
return (
<div style={{ maxWidth: 300, margin: '0 auto' }}>
<StatusMessage status={status} />
<div style={{ display: 'flex', gap: 4 }}>
{['loading', 'success', 'error', 'empty'].map(s => (
<button key={s} onClick={() => setStatus(s)}
style={{ padding: '4px 8px', cursor: 'pointer', fontSize: 12 }}>
{s}
</button>
))}
</div>
</div>
)
}
▶ المثال 5: نمط renderProps — مكونات القوائم والعرض المخصص
function DataList({ items, renderItem, keyExtractor, emptyMessage = 'No items' }) {
if (!items || items.length === 0) {
return <p style={{ color: '#999', textAlign: 'center', padding: 20 }}>{emptyMessage}</p>
}
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map(item => (
<li key={keyExtractor(item)} style={{ borderBottom: '1px solid #f0f0f0', padding: 8 }}>
{renderItem(item)}
</li>
))}
</ul>
)
}
function App() {
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com', active: true },
{ id: 2, name: 'Bob', email: 'bob@example.com', active: false },
{ id: 3, name: 'Charlie', email: 'charlie@example.com', active: true },
]
const products = [
{ id: 'a', name: 'Keyboard', price: 79 },
{ id: 'b', name: 'Monitor', price: 399 },
]
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Users</h3>
<DataList
items={users}
keyExtractor={u => u.id}
renderItem={u => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{u.name} ({u.email})</span>
<span style={{ color: u.active ? '#52c41a' : '#999' }}>
{u.active ? 'Active' : 'Inactive'}
</span>
</div>
)}
/>
<h3>Products</h3>
<DataList
items={products}
keyExtractor={p => p.id}
emptyMessage="No products available"
renderItem={p => (
<span>{p.name} - ${p.price}</span>
)}
/>
</div>
)
}
❓ أسئلة شائعة
<div> هي علامة div في HTML؛ <Div> هو مكون مخصص. إذا كُتب اسم المكون بأحرف صغيرة، فسيتعامل React معه على أنه علامة HTML، مما يؤدي إلى فشل عملية العرض.function Card({ title = 'Untitled' }) {}؛ ② استخدام Card.defaultProps = { title: 'Untitled' }. لاحظ أن React لم تعد توصي باستخدام defaultProps (في مكونات الدالة)؛ ويُنصح باستخدام عملية التفكيك لتعيين القيم الافتراضية. في TypeScript، يمكنك أيضًا استخدام title?: string للإشارة إلى أن الخاصية اختيارية، مما يجعل الكود أكثر وضوحًا عند دمجه مع تفكيك المتغيرات لتعيين القيم الافتراضية.📖 ملخص
- مكون الدالة هو دالة في JavaScript تُرجع JSX؛ يجب أن يكون الحرف الأول كبيرًا
- «البيانات المساعدة» هي بيانات للقراءة فقط تُمرَّر من مكون أب إلى مكون تابع؛ ولا يمكن للمكون التابع تعديلها.
- يمكن أن تكون المعلمات من أي نوع: سلسلة، أو عدد، أو قيمة منطقية، أو كائن، أو مصفوفة، أو دالة
childrenهو عنصر خاص يمثل المحتوى الموجود بين علامتي المكون- استخدم صيغة تفكيك المتغيرات
function Comp({ prop1, prop2 })لجعل الكود أكثر وضوحًا - يمكن أن تسهم تعليقات الأنواع في TypeScript في تحسين تجربة التطوير ومتانة الكود بشكل كبير
📝 تمارين
- المشكلة الأساسية (مستوى الصعوبة ⭐): أنشئ مكونًا باسم
Avatarيقبل خاصيتين —name(سلسلة نصية) وsize(رقم، القيمة الافتراضية 50) — ويعرض الصورة الرمزية للمستخدم واسمه. - مشكلة متقدمة (درجة الصعوبة ⭐⭐): أنشئ مكونًا
TagListيقبل خاصيتين —tags(مصفوفة من السلاسل) وonRemove(دالة) — ويعرض قائمة من العلامات. يؤدي النقر على أي علامة إلى حذفها. - التحدي (الصعوبة ⭐⭐⭐): قم بإنشاء مكون
DataTableبلغة TypeScript يقبلcolumns(مصفوفة من تعريفات الأعمدة) وdata(مصفوفة من البيانات) ويعرض جدول بيانات بسيطًا. حاول إضافة خاصيةstriped(من نوع Boolean، اختيارية) للتحكم في نمط الزبرا.