دليل شامل للمكونات والواجهات البرمجية وأفضل الممارسات
React Native هو إطار عمل لبناء تطبيقات الجوال باستخدام JavaScript و React. يتيح لك Expo بناء تطبيقات React Native بشكل أسرع وأسهل من خلال توفير مجموعة من الأدوات والمكتبات الجاهزة للاستخدام.
# تثبيت Expo CLI عالمياً
npm install -g expo-cli
# أو باستخدام Yarn
yarn global add expo-cli
يمكنك أيضًا استخدام أمر npx مباشرة دون الحاجة إلى التثبيت العالمي:
npx expo
إنشاء مشروع React Native جديد باستخدام Expo سهل للغاية. فيما يلي الخطوات اللازمة لإعداد مشروعك الأول.
# إنشاء مشروع جديد
npx create-expo-app MyAwesomeApp
# الانتقال إلى مجلد المشروع
cd MyAwesomeApp
# تشغيل المشروع
npx expo start
بعد تشغيل المشروع، سترى رمز QR يمكنك مسحه باستخدام تطبيق Expo Go على جهازك المحمول. يمكنك أيضًا تشغيل المشروع على محاكي iOS أو Android.
MyAwesomeApp/
├── .expo/ # ملفات تكوين Expo
├── assets/ # الصور والخطوط وملفات الوسائط الأخرى
├── node_modules/ # حزم npm المثبتة
├── .gitignore
├── App.js # نقطة الدخول الرئيسية للتطبيق
├── app.json # تكوين تطبيق Expo
├── babel.config.js # تكوين Babel
├── package.json # تبعيات المشروع ونصوص npm
└── README.md
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>مرحبًا بك في تطبيقي الأول!</Text>
<Text style={styles.paragraph}>هذا هو أول تطبيق React Native خاص بي</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
},
paragraph: {
fontSize: 16,
color: '#555',
},
});
مكون View هو العنصر الأساسي لبناء واجهة المستخدم في React Native. يمكن اعتباره مثل عنصر div في HTML، حيث يستخدم لتخطيط العناصر وتجميعها.
import React from 'react';
import { View, StyleSheet } from 'react-native';
export default function BasicView() {
return (
<View style={styles.container}>
<View style={styles.box} />
<View style={styles.box} />
<View style={styles.box} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
box: {
width: 80,
height: 80,
backgroundColor: '#5856D6',
borderRadius: 10,
},
});
style
: لتطبيق الأنماط على العنصرaccessibilityLabel
: نص وصفي للقراء الشاشةonLayout
: دالة تستدعى عند حساب أبعاد العنصرtestID
: معرف للاختبارات الآليةعلى عكس الويب، مكونات View في React Native لا تستخدم خاصية display: inline
افتراضيًا. بدلاً من ذلك، تستخدم Flexbox للتخطيط.
مكون Text هو العنصر الأساسي لعرض النصوص في React Native. جميع العناصر النصية يجب أن تكون داخل مكون Text.
import React from 'react';
import { Text, StyleSheet, View } from 'react-native';
export default function TextExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>مرحبًا بالعالم!</Text>
<Text style={styles.paragraph}>
يمكنك تنسيق النص بعدة طرق مختلفة.
<Text style={styles.bold}> هذا نص غامق </Text>
و<Text style={styles.italic}> هذا نص مائل</Text>.
</Text>
<Text style={styles.description} numberOfLines={2}>
هذا نص طويل سيتم اقتصاصه بعد سطرين من النص. وهذا لاختبار خاصية numberOfLines.
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 16,
textAlign: 'right',
},
paragraph: {
fontSize: 16,
lineHeight: 24,
color: '#555',
marginBottom: 12,
textAlign: 'right',
},
bold: {
fontWeight: 'bold',
color: '#000',
},
italic: {
fontStyle: 'italic',
color: '#0070e0',
},
description: {
fontSize: 14,
color: '#666',
textAlign: 'right',
}
});
style
: لتطبيق الأنماط على النصnumberOfLines
: يحدد عدد الأسطر قبل اقتصاص النصellipsizeMode
: كيفية اقتصاص النص (head، middle، tail، clip)onPress
: دالة تستدعى عند النقر على النصselectable
: للسماح بتحديد النصيمكن تداخل مكونات Text داخل بعضها البعض لتطبيق أنماط مختلفة على أجزاء محددة من النص، كما هو موضح في المثال أعلاه.
مكون Image هو العنصر المستخدم لعرض مختلف أنواع الصور في React Native، بما في ذلك الصور المحلية والصور عبر الإنترنت وحتى الصور المتحركة (GIF).
import React from 'react';
import { View, Image, StyleSheet } from 'react-native';
export default function ImageExample() {
return (
<View style={styles.container}>
{/* صورة محلية */}
<Image
source={require('./assets/local-image.png')}
style={styles.localImage}
/>
{/* صورة من الإنترنت */}
<Image
source={{ uri: 'https://example.com/remote-image.jpg' }}
style={styles.remoteImage}
/>
{/* صورة مع خلفية بديلة أثناء التحميل */}
<Image
source={{ uri: 'https://example.com/slow-image.jpg' }}
style={styles.remoteImage}
defaultSource={require('./assets/placeholder.png')}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
justifyContent: 'center',
},
localImage: {
width: 200,
height: 200,
marginBottom: 20,
borderRadius: 10,
},
remoteImage: {
width: 300,
height: 150,
marginBottom: 20,
borderRadius: 8,
resizeMode: 'cover',
},
});
source
: مصدر الصورة (وحدة محلية أو URI)style
: لتطبيق الأنماط على الصورةresizeMode
: كيفية تعديل حجم الصورة (cover، contain، stretch، repeat، center)defaultSource
: الصورة البديلة أثناء تحميل الصورة الأساسية (iOS فقط)onLoad
: دالة تستدعى عند اكتمال تحميل الصورةonError
: دالة تستدعى عند فشل تحميل الصورة
import React, { useState } from 'react';
import { View, Image, StyleSheet, ActivityIndicator } from 'react-native';
export default function EfficientImageLoading() {
const [isLoading, setIsLoading] = useState(true);
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
{isLoading && (
<ActivityIndicator
style={styles.activityIndicator}
size="large"
color="#0000ff"
/>
)}
<Image
source={{ uri: 'https://example.com/large-image.jpg' }}
style={styles.image}
onLoadStart={() => setIsLoading(true)}
onLoadEnd={() => setIsLoading(false)}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
imageContainer: {
width: 300,
height: 200,
position: 'relative',
},
image: {
width: '100%',
height: '100%',
borderRadius: 10,
},
activityIndicator: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
zIndex: 1,
},
});
مكون ScrollView هو حاوية قابلة للتمرير يمكنها احتواء عدة مكونات ومشاهد. يستخدم عندما تحتاج إلى عرض محتوى أكبر من مساحة الشاشة المتاحة.
import React from 'react';
import { ScrollView, View, Text, StyleSheet } from 'react-native';
export default function ScrollViewExample() {
return (
<ScrollView style={styles.container}>
{[...Array(20)].map((_, i) => (
<View key={i} style={styles.box}>
<Text style={styles.text}>عنصر {i + 1}</Text>
</View>
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
},
box: {
marginBottom: 10,
backgroundColor: '#e0f2fe',
padding: 20,
borderRadius: 8,
},
text: {
fontSize: 16,
fontWeight: '500',
textAlign: 'center',
},
});
horizontal
: تمكين التمرير الأفقيshowsVerticalScrollIndicator
: إظهار أو إخفاء مؤشر التمرير الرأسيshowsHorizontalScrollIndicator
: إظهار أو إخفاء مؤشر التمرير الأفقيonScroll
: دالة تستدعى عند التمريرrefreshControl
: إضافة تحكم السحب للتحديث
import React from 'react';
import { ScrollView, View, Text, StyleSheet, Dimensions } from 'react-native';
const { width } = Dimensions.get('window');
export default function HorizontalScrollView() {
return (
<ScrollView
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
style={styles.container}
>
{['الأول', 'الثاني', 'الثالث', 'الرابع'].map((item, index) => (
<View key={index} style={styles.page}>
<Text style={styles.pageText}>الصفحة {item}</Text>
</View>
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
page: {
width: width,
height: '100%',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#cffafe',
},
pageText: {
fontSize: 24,
fontWeight: 'bold',
},
});
ScrollView يقوم بتحميل جميع عناصره دفعة واحدة، حتى إذا كانت خارج الشاشة. لقوائم كبيرة، استخدم FlatList أو SectionList للحصول على أداء أفضل.
مكون FlatList هو عنصر قائمة ذات أداء عالي لعرض قوائم البيانات. يقوم بتحميل العناصر فقط عند ظهورها على الشاشة، مما يحسن الأداء بشكل كبير مقارنة بـ ScrollView.
import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
export default function FlatListExample() {
const data = [
{ id: '1', title: 'العنصر الأول' },
{ id: '2', title: 'العنصر الثاني' },
{ id: '3', title: 'العنصر الثالث' },
{ id: '4', title: 'العنصر الرابع' },
{ id: '5', title: 'العنصر الخامس' },
{ id: '6', title: 'العنصر السادس' },
{ id: '7', title: 'العنصر السابع' },
{ id: '8', title: 'العنصر الثامن' },
{ id: '9', title: 'العنصر التاسع' },
{ id: '10', title: 'العنصر العاشر' },
];
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
style={styles.container}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
},
item: {
backgroundColor: '#f0f9ff',
padding: 20,
marginVertical: 8,
borderRadius: 8,
},
title: {
fontSize: 16,
textAlign: 'right',
},
});
data
: مصفوفة البيانات التي ستعرض في القائمةrenderItem
: دالة تحدد كيفية عرض كل عنصرkeyExtractor
: دالة لاستخراج مفتاح فريد لكل عنصرhorizontal
: تمكين العرض الأفقي للقائمةItemSeparatorComponent
: مكون لعرضه بين العناصرListHeaderComponent
: مكون يعرض في أعلى القائمةListFooterComponent
: مكون يعرض في أسفل القائمةonRefresh
: دالة تستدعى عند السحب للتحديثrefreshing
: مؤشر ما إذا كانت القائمة تقوم بالتحديثonEndReached
: دالة تستدعى عند الوصول إلى نهاية القائمة
import React, { useState } from 'react';
import { FlatList, View, Text, StyleSheet, RefreshControl } from 'react-native';
export default function AdvancedFlatList() {
const [refreshing, setRefreshing] = useState(false);
const [data, setData] = useState([
{ id: '1', title: 'العنصر 1' },
{ id: '2', title: 'العنصر 2' },
{ id: '3', title: 'العنصر 3' },
// ... المزيد من العناصر
]);
const onRefresh = () => {
setRefreshing(true);
// محاكاة طلب شبكة
setTimeout(() => {
// تحديث البيانات
setData([
{ id: 'new-1', title: 'عنصر جديد 1' },
{ id: 'new-2', title: 'عنصر جديد 2' },
...data
]);
setRefreshing(false);
}, 2000);
};
return (
<FlatList
data={data}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id}
style={styles.container}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
ListHeaderComponent={
<Text style={styles.header}>قائمة العناصر</Text>
}
ListEmptyComponent={
<Text style={styles.empty}>لا توجد عناصر للعرض</Text>
}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#f0f9ff',
padding: 20,
marginHorizontal: 16,
},
title: {
fontSize: 16,
textAlign: 'right',
},
header: {
fontSize: 20,
fontWeight: 'bold',
padding: 16,
textAlign: 'center',
},
empty: {
padding: 30,
textAlign: 'center',
color: '#888',
fontSize: 18,
},
separator: {
height: 1,
backgroundColor: '#e5e7eb',
marginVertical: 8,
},
});
مكونات Touchable توفر طرقًا مختلفة لإنشاء عناصر قابلة للنقر في React Native. وهي أكثر مرونة من المكون Button وتتيح تخصيصًا أكبر.
TouchableOpacity
: يضيف تأثير شفافية عند الضغطTouchableHighlight
: يضيف تأثير تظليل عند الضغطTouchableWithoutFeedback
: لا يضيف أي تأثير مرئيPressable
: مكون حديث وأكثر مرونة (الإصدار 0.63 وما بعده)
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export default function TouchableOpacityExample() {
return (
<View style={styles.container}>
<TouchableOpacity
style={styles.button}
onPress={() => alert('تم الضغط!')}
activeOpacity={0.7}
>
<Text style={styles.buttonText}>اضغط هنا</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 16,
alignItems: 'center',
},
button: {
backgroundColor: '#4f46e5',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
width: 200,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
},
});
import React from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function PressableExample() {
return (
<View style={styles.container}>
<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
onPress={() => alert('تم الضغط!')}
>
{({ pressed }) => (
<Text style={[
styles.buttonText,
pressed && styles.buttonTextPressed
]}>
{pressed ? 'جاري الضغط' : 'اضغط هنا'}
</Text>
)}
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 16,
alignItems: 'center',
},
button: {
backgroundColor: '#4f46e5',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
width: 200,
},
buttonPressed: {
backgroundColor: '#312e81',
transform: [{ scale: 0.98 }],
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
},
buttonTextPressed: {
color: '#e5e7eb',
},
});
مكون TextInput يسمح للمستخدمين بإدخال النص في التطبيق. يمكن استخدامه لإنشاء نماذج وحقول بحث وغيرها.
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';
export default function TextInputExample() {
const [text, setText] = useState('');
return (
<View style={styles.container}>
<Text style={styles.label}>أدخل نصًا:</Text>
<TextInput
style={styles.input}
onChangeText={setText}
value={text}
placeholder="اكتب هنا..."
placeholderTextColor="#9ca3af"
/>
<Text style={styles.output}>النص المدخل: {text}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
label: {
fontSize: 16,
marginBottom: 8,
textAlign: 'right',
},
input: {
height: 50,
borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 8,
paddingHorizontal: 12,
fontSize: 16,
textAlign: 'right',
marginBottom: 16,
},
output: {
fontSize: 16,
color: '#4b5563',
textAlign: 'right',
},
});
value
: قيمة حقل الإدخالonChangeText
: دالة تستدعى عند تغيير النصplaceholder
: نص يظهر عندما يكون الحقل فارغًاplaceholderTextColor
: لون نص الـ placeholderkeyboardType
: نوع لوحة المفاتيح (default, number-pad, email-address, ...)secureTextEntry
: إخفاء النص المدخل (لكلمات المرور)multiline
: السماح بإدخال نص متعدد الأسطرautoCapitalize
: تحديد كيفية جعل الأحرف كبيرة (none, sentences, words, characters)editable
: ما إذا كان النص قابلاً للتحرير
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet, TouchableOpacity } from 'react-native';
export default function SimpleForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
});
const handleChange = (field, value) => {
setFormData({
...formData,
[field]: value,
});
};
const handleSubmit = () => {
alert(`تم إرسال البيانات: ${JSON.stringify(formData)}`);
};
return (
<View style={styles.container}>
<Text style={styles.title}>نموذج التسجيل</Text>
<Text style={styles.label}>الاسم</Text>
<TextInput
style={styles.input}
value={formData.name}
onChangeText={(text) => handleChange('name', text)}
placeholder="أدخل اسمك"
placeholderTextColor="#9ca3af"
/>
<Text style={styles.label}>البريد الإلكتروني</Text>
<TextInput
style={styles.input}
value={formData.email}
onChangeText={(text) => handleChange('email', text)}
placeholder="أدخل بريدك الإلكتروني"
placeholderTextColor="#9ca3af"
keyboardType="email-address"
autoCapitalize="none"
/>
<Text style={styles.label}>كلمة المرور</Text>
<TextInput
style={styles.input}
value={formData.password}
onChangeText={(text) => handleChange('password', text)}
placeholder="أدخل كلمة المرور"
placeholderTextColor="#9ca3af"
secureTextEntry
/>
<TouchableOpacity
style={styles.button}
onPress={handleSubmit}
>
<Text style={styles.buttonText}>تسجيل</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
},
label: {
fontSize: 16,
marginBottom: 8,
textAlign: 'right',
},
input: {
height: 50,
borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 8,
paddingHorizontal: 12,
fontSize: 16,
textAlign: 'right',
marginBottom: 16,
},
button: {
backgroundColor: '#4f46e5',
padding: 16,
borderRadius: 8,
alignItems: 'center',
marginTop: 16,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
مكون Switch هو عنصر واجهة المستخدم لتبديل الحالة بين تشغيل/إيقاف. يستخدم غالبًا للإعدادات أو تفعيل/تعطيل الميزات.
import React, { useState } from 'react';
import { View, Switch, Text, StyleSheet } from 'react-native';
export default function SwitchExample() {
const [isEnabled, setIsEnabled] = useState(false);
const toggleSwitch = () => setIsEnabled(previousState => !previousState);
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.label}>
{isEnabled ? 'تم التفعيل' : 'معطل'}
</Text>
<Switch
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={isEnabled ? "#4f46e5" : "#f4f3f4"}
ios_backgroundColor="#3e3e3e"
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
justifyContent: 'center',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
backgroundColor: '#f5f5f5',
borderRadius: 8,
},
label: {
fontSize: 16,
color: '#333',
},
});
value
: قيمة المفتاح (true للتشغيل، false للإيقاف)onValueChange
: دالة تستدعى عند تغيير قيمة المفتاحdisabled
: تعطيل المفتاحthumbColor
: لون الجزء المتحرك من المفتاحtrackColor
: لون المسار (يمكنك تحديد لون مختلف للحالة true والحالة false)ios_backgroundColor
: لون خلفية المفتاح في نظام iOS
import React, { useState } from 'react';
import { View, Switch, Text, StyleSheet } from 'react-native';
export default function SettingsScreen() {
const [settings, setSettings] = useState({
notifications: true,
darkMode: false,
location: true,
autoUpdate: false,
});
const toggleSetting = (key) => {
setSettings(prevSettings => ({
...prevSettings,
[key]: !prevSettings[key]
}));
};
return (
<View style={styles.container}>
<Text style={styles.title}>الإعدادات</Text>
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>الإشعارات</Text>
<Switch
value={settings.notifications}
onValueChange={() => toggleSetting('notifications')}
trackColor={{ false: "#767577", true: "#4f46e5" }}
thumbColor="#ffffff"
/>
</View>
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>الوضع الداكن</Text>
<Switch
value={settings.darkMode}
onValueChange={() => toggleSetting('darkMode')}
trackColor={{ false: "#767577", true: "#4f46e5" }}
thumbColor="#ffffff"
/>
</View>
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>مشاركة الموقع</Text>
<Switch
value={settings.location}
onValueChange={() => toggleSetting('location')}
trackColor={{ false: "#767577", true: "#4f46e5" }}
thumbColor="#ffffff"
/>
</View>
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>التحديث التلقائي</Text>
<Switch
value={settings.autoUpdate}
onValueChange={() => toggleSetting('autoUpdate')}
trackColor={{ false: "#767577", true: "#4f46e5" }}
thumbColor="#ffffff"
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
},
settingItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: '#e5e7eb',
},
settingLabel: {
fontSize: 16,
color: '#333',
},
});
مكتبة Camera من Expo تتيح لك الوصول إلى كاميرا الجهاز لالتقاط الصور وتسجيل الفيديو في تطبيقك.
npx expo install expo-camera
يجب طلب إذن استخدام الكاميرا قبل استخدامها:
import { Camera } from 'expo-camera';
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export default function CameraExample() {
const [hasPermission, setHasPermission] = useState(null);
useEffect(() => {
(async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
if (hasPermission === null) {
return <View><Text>طلب إذن الكاميرا...</Text></View>;
}
if (hasPermission === false) {
return <View><Text>لا يمكن الوصول إلى الكاميرا</Text></View>;
}
return (
<View style={styles.container}>
<Camera style={styles.camera} type={Camera.Constants.Type.back}>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={() => {
// التقاط صورة
}}>
<Text style={styles.text}>التقاط</Text>
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
camera: {
flex: 1,
},
buttonContainer: {
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
justifyContent: 'center',
margin: 20,
},
button: {
alignSelf: 'flex-end',
alignItems: 'center',
backgroundColor: 'white',
padding: 15,
borderRadius: 10,
},
text: {
fontSize: 16,
color: 'black',
},
});
import { Camera } from 'expo-camera';
import React, { useState, useRef } from 'react';
import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
export default function CameraWithCapture() {
const [photo, setPhoto] = useState(null);
const cameraRef = useRef(null);
const takePicture = async () => {
if (cameraRef.current) {
const options = { quality: 0.5, base64: true };
const data = await cameraRef.current.takePictureAsync(options);
setPhoto(data.uri);
}
};
if (photo) {
return (
<View style={styles.container}>
<Image source={{ uri: photo }} style={styles.preview} />
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={() => setPhoto(null)}>
<Text style={styles.text}>التقاط صورة جديدة</Text>
</TouchableOpacity>
</View>
</View>
);
}
return (
<View style={styles.container}>
<Camera
style={styles.camera}
type={Camera.Constants.Type.back}
ref={cameraRef}
>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={takePicture}>
<Text style={styles.text}>التقاط</Text>
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
camera: {
flex: 1,
},
buttonContainer: {
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
justifyContent: 'center',
margin: 20,
},
button: {
alignSelf: 'flex-end',
alignItems: 'center',
backgroundColor: 'white',
padding: 15,
borderRadius: 10,
},
text: {
fontSize: 16,
color: 'black',
},
preview: {
flex: 1,
width: '100%',
height: '100%',
},
});
لا تنس أن تضيف الأذونات المناسبة في ملف app.json أو app.config.js:
{
"expo": {
"plugins": [
[
"expo-camera",
{
"cameraPermission": "السماح للتطبيق باستخدام الكاميرا"
}
]
]
}
}
مكتبة Location من Expo تتيح لك الوصول إلى موقع الجهاز وتتبعه في تطبيقك.
npx expo install expo-location
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import * as Location from 'expo-location';
export default function CurrentLocation() {
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('تم رفض إذن الوصول إلى الموقع');
return;
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
})();
}, []);
let text = 'جاري تحديد الموقع...';
if (errorMsg) {
text = errorMsg;
} else if (location) {
text = `خط العرض: ${location.coords.latitude}, خط الطول: ${location.coords.longitude}`;
}
return (
<View style={styles.container}>
<Text style={styles.paragraph}>{text}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
paragraph: {
fontSize: 16,
textAlign: 'center',
},
});
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Switch } from 'react-native';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const LOCATION_TRACKING = 'location-tracking';
TaskManager.defineTask(LOCATION_TRACKING, async ({ data, error }) => {
if (error) {
console.error(error);
return;
}
if (data) {
const { locations } = data;
// يمكنك هنا تخزين المواقع أو إرسالها إلى خادم
console.log('المواقع في الخلفية:', locations);
}
});
export default function LocationTracking() {
const [locationStarted, setLocationStarted] = useState(false);
const startLocationTracking = async () => {
await Location.requestBackgroundPermissionsAsync();
await Location.startLocationUpdatesAsync(LOCATION_TRACKING, {
accuracy: Location.Accuracy.Balanced,
timeInterval: 10000,
distanceInterval: 100,
foregroundService: {
notificationTitle: "تتبع الموقع",
notificationBody: "نقوم حاليًا بتتبع موقعك",
}
});
setLocationStarted(true);
};
const stopLocationTracking = async () => {
await Location.stopLocationUpdatesAsync(LOCATION_TRACKING);
setLocationStarted(false);
};
return (
<View style={styles.container}>
<Text style={styles.title}>تتبع الموقع</Text>
<View style={styles.switchContainer}>
<Text style={styles.label}>
{locationStarted ? 'إيقاف التتبع' : 'بدء التتبع'}
</Text>
<Switch
onValueChange={locationStarted ? stopLocationTracking : startLocationTracking}
value={locationStarted}
trackColor={{ true: '#4f46e5' }}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 30,
},
switchContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '80%',
backgroundColor: '#f5f5f5',
padding: 16,
borderRadius: 8,
},
label: {
fontSize: 16,
},
});
لا تنس إضافة أذونات الموقع في ملف app.json:
{
"expo": {
"plugins": [
[
"expo-location",
{
"locationAlwaysAndWhenInUsePermission": "السماح للتطبيق باستخدام موقعك.",
"locationAlwaysPermission": "السماح للتطبيق باستخدام موقعك حتى عندما يكون في الخلفية."
}
]
]
}
}
مكتبة Notifications من Expo تتيح لك إرسال وتلقي الإشعارات المحلية والبعيدة في تطبيقك.
npx expo install expo-notifications
import React, { useEffect, useState } from 'react';
import { Text, View, Button, StyleSheet, Platform } from 'react-native';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
// إعداد كيفية معالجة الإشعارات عند ظهورها أثناء استخدام التطبيق
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export default function LocalNotification() {
const [expoPushToken, setExpoPushToken] = useState('');
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
}, []);
// إرسال إشعار محلي بسيط
const sendNotification = async () => {
await Notifications.scheduleNotificationAsync({
content: {
title: "إشعار جديد! 📬",
body: 'هذا هو محتوى الإشعار المحلي.',
data: { data: 'يمكنك وضع أي بيانات هنا' },
},
trigger: { seconds: 2 }, // يظهر بعد ثانيتين
});
};
return (
<View style={styles.container}>
<Text style={styles.title}>إشعارات محلية</Text>
<Button
title="إرسال إشعار بعد ثانيتين"
onPress={sendNotification}
/>
<Text style={styles.description}>
رمز التوكن:{'\n'}{expoPushToken}
</Text>
</View>
);
}
// دالة للتسجيل في خدمة إشعارات الدفع
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('فشل في الحصول على رمز توكن لإشعارات الدفع!');
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
} else {
alert('يجب استخدام جهاز فعلي لإشعارات الدفع');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 20,
},
description: {
marginTop: 20,
textAlign: 'center',
color: '#888',
fontSize: 12,
},
});
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Platform } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
import * as Notifications from 'expo-notifications';
export default function ScheduledNotification() {
const [date, setDate] = useState(new Date());
const [showDatePicker, setShowDatePicker] = useState(false);
const scheduleNotification = async () => {
await Notifications.scheduleNotificationAsync({
content: {
title: "تذكير مجدول! 🕒",
body: 'حان الوقت للقيام بمهمتك!',
sound: true,
},
trigger: {
date: date,
},
});
alert(`تم جدولة الإشعار في: ${date.toLocaleString()}`);
};
const onChange = (event, selectedDate) => {
const currentDate = selectedDate || date;
setShowDatePicker(Platform.OS === 'ios');
setDate(currentDate);
};
return (
<View style={styles.container}>
<Text style={styles.title}>جدولة إشعار</Text>
<TouchableOpacity
style={styles.dateButton}
onPress={() => setShowDatePicker(true)}
>
<Text style={styles.dateButtonText}>
اختر التاريخ والوقت: {date.toLocaleString()}
</Text>
</TouchableOpacity>
{showDatePicker && (
<DateTimePicker
value={date}
mode="datetime"
display="default"
onChange={onChange}
/>
)}
<TouchableOpacity
style={styles.scheduleButton}
onPress={scheduleNotification}
>
<Text style={styles.scheduleButtonText}>جدولة الإشعار</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 30,
},
dateButton: {
backgroundColor: '#e0e7ff',
padding: 15,
borderRadius: 8,
width: '100%',
alignItems: 'center',
marginBottom: 20,
},
dateButtonText: {
color: '#4338ca',
fontSize: 16,
},
scheduleButton: {
backgroundColor: '#4f46e5',
padding: 15,
borderRadius: 8,
width: '100%',
alignItems: 'center',
},
scheduleButtonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
لاستخدام إشعارات الدفع البعيدة (Push Notifications)، ستحتاج إلى خدمة مثل Firebase Cloud Messaging أو خدمة Expo Push Notifications. راجع الوثائق الرسمية للمزيد من التفاصيل.
مكتبة FileSystem من Expo توفر واجهة للوصول إلى نظام الملفات المحلي للجهاز. يمكنك استخدامها لقراءة وكتابة وحذف الملفات.
npx expo install expo-file-system
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity } from 'react-native';
import * as FileSystem from 'expo-file-system';
export default function FileSystemExample() {
const [text, setText] = useState('');
const [savedText, setSavedText] = useState('');
const [fileName, setFileName] = useState('myfile.txt');
const filePath = FileSystem.documentDirectory + fileName;
const saveFile = async () => {
try {
await FileSystem.writeAsStringAsync(filePath, text);
alert(`تم حفظ الملف بنجاح في: ${filePath}`);
} catch (error) {
alert(`خطأ في حفظ الملف: ${error.message}`);
}
};
const readFile = async () => {
try {
const content = await FileSystem.readAsStringAsync(filePath);
setSavedText(content);
} catch (error) {
alert(`خطأ في قراءة الملف: ${error.message}`);
setSavedText('');
}
};
const deleteFile = async () => {
try {
await FileSystem.deleteAsync(filePath);
alert('تم حذف الملف بنجاح');
setSavedText('');
} catch (error) {
alert(`خطأ في حذف الملف: ${error.message}`);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>عمليات على الملفات</Text>
<TextInput
style={styles.input}
placeholder="اسم الملف"
value={fileName}
onChangeText={setFileName}
/>
<TextInput
style={[styles.input, styles.textarea]}
placeholder="أدخل نصًا لحفظه في الملف"
multiline
numberOfLines={4}
value={text}
onChangeText={setText}
/>
<View style={styles.buttonRow}>
<TouchableOpacity style={styles.button} onPress={saveFile}>
<Text style={styles.buttonText}>حفظ</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.readButton]}
onPress={readFile}
>
<Text style={styles.buttonText}>قراءة</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.deleteButton]}
onPress={deleteFile}
>
<Text style={styles.buttonText}>حذف</Text>
</TouchableOpacity>
</View>
{savedText ? (
<View style={styles.resultContainer}>
<Text style={styles.resultLabel}>محتوى الملف:</Text>
<Text style={styles.resultText}>{savedText}</Text>
</View>
) : null}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
marginBottom: 16,
fontSize: 16,
backgroundColor: 'white',
textAlign: 'right',
},
textarea: {
height: 100,
textAlignVertical: 'top',
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
},
button: {
flex: 1,
backgroundColor: '#4f46e5',
padding: 12,
borderRadius: 8,
alignItems: 'center',
marginHorizontal: 4,
},
readButton: {
backgroundColor: '#0d9488',
},
deleteButton: {
backgroundColor: '#ef4444',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
resultContainer: {
backgroundColor: '#f8fafc',
padding: 16,
borderRadius: 8,
borderWidth: 1,
borderColor: '#e2e8f0',
},
resultLabel: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
color: '#334155',
},
resultText: {
fontSize: 16,
color: '#334155',
},
});
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Image, ActivityIndicator } from 'react-native';
import * as FileSystem from 'expo-file-system';
export default function DownloadExample() {
const [url, setUrl] = useState('https://example.com/image.jpg');
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState(0);
const [imagePath, setImagePath] = useState(null);
const downloadFile = async () => {
setDownloading(true);
setProgress(0);
const fileUri = FileSystem.documentDirectory + 'downloaded-image.jpg';
try {
const downloadResumable = FileSystem.createDownloadResumable(
url,
fileUri,
{},
(downloadProgress) => {
const progress = downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite;
setProgress(progress);
}
);
const { uri } = await downloadResumable.downloadAsync();
setImagePath(uri);
setDownloading(false);
} catch (error) {
console.error(error);
setDownloading(false);
alert('حدث خطأ أثناء التنزيل');
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>تنزيل ملف</Text>
<TextInput
style={styles.input}
placeholder="أدخل رابط الملف"
value={url}
onChangeText={setUrl}
/>
<TouchableOpacity
style={styles.button}
onPress={downloadFile}
disabled={downloading}
>
<Text style={styles.buttonText}>تنزيل الملف</Text>
</TouchableOpacity>
{downloading && (
<View style={styles.progressContainer}>
<ActivityIndicator size="small" color="#4f46e5" />
<Text style={styles.progressText}>
{Math.floor(progress * 100)}% تم التنزيل
</Text>
</View>
)}
{imagePath && (
<View style={styles.imageContainer}>
<Text style={styles.subtitle}>الصورة المنزلة:</Text>
<Image
source={{ uri: imagePath }}
style={styles.image}
resizeMode="cover"
/>
<Text style={styles.imagePath}>
المسار: {imagePath}
</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
marginBottom: 16,
fontSize: 16,
backgroundColor: 'white',
textAlign: 'right',
},
button: {
backgroundColor: '#4f46e5',
padding: 15,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
progressContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginTop: 20,
},
progressText: {
marginLeft: 10,
fontSize: 16,
color: '#4f46e5',
},
imageContainer: {
marginTop: 20,
alignItems: 'center',
},
subtitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
image: {
width: 300,
height: 200,
borderRadius: 8,
},
imagePath: {
marginTop: 10,
fontSize: 12,
color: '#666',
textAlign: 'center',
},
});
تمثل React Hooks طريقة لاستخدام حالة ودورة حياة المكونات في المكونات الوظيفية (Functional Components). تسمح لك باستخدام ميزات React دون الحاجة إلى كتابة مكونات صنفية (Class Components).
يستخدم لإدارة حالة المكون:
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
export default function CounterExample() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.count}>العدد: {count}</Text>
<View style={styles.buttonRow}>
<Button title="زيادة" onPress={() => setCount(count + 1)} />
<View style={styles.buttonSpacer} />
<Button title="إنقاص" onPress={() => setCount(count - 1)} />
</View>
<Button title="إعادة تعيين" onPress={() => setCount(0)} color="#ef4444" />
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
alignItems: 'center',
},
count: {
fontSize: 48,
fontWeight: 'bold',
marginBottom: 20,
},
buttonRow: {
flexDirection: 'row',
marginBottom: 20,
},
buttonSpacer: {
width: 20,
},
});
يستخدم لتنفيذ الآثار الجانبية (side effects) في المكونات الوظيفية:
import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
export default function DataFetchingExample() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// محاكاة طلب واجهة برمجة التطبيقات (API)
const fetchData = async () => {
try {
// جلب البيانات من الواجهة البرمجية
// هذا المثال يستخدم توقيت للمحاكاة فقط
setTimeout(() => {
setData({ name: 'أحمد', age: 28, job: 'مطور واجهات أمامية' });
setLoading(false);
}, 2000);
} catch (err) {
setError('حدث خطأ أثناء جلب البيانات');
setLoading(false);
}
};
fetchData();
// دالة التنظيف (تنفذ عند تفكيك المكون)
return () => {
console.log('تنظيف المكون');
// يمكن إلغاء الطلبات هنا أو تنظيف الموارد الأخرى
};
}, []); // المصفوفة الفارغة تعني أن هذا الأثر يعمل مرة واحدة فقط بعد التحميل
if (loading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color="#4f46e5" />
<Text style={styles.loadingText}>جاري التحميل...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centered}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>بيانات المستخدم</Text>
<View style={styles.userCard}>
<Text style={styles.userInfo}>الاسم: {data.name}</Text>
<Text style={styles.userInfo}>العمر: {data.age}</Text>
<Text style={styles.userInfo}>الوظيفة: {data.job}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: '#666',
},
errorText: {
color: '#ef4444',
fontSize: 16,
textAlign: 'center',
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 16,
textAlign: 'center',
},
userCard: {
backgroundColor: '#f3f4f6',
padding: 16,
borderRadius: 8,
},
userInfo: {
fontSize: 16,
marginBottom: 8,
},
});
يسمح بالاحتفاظ بمرجع قابل للتغيير يستمر بين عمليات إعادة التقديم:
import React, { useRef, useState } from 'react';
import { View, TextInput, Button, Text, StyleSheet } from 'react-native';
export default function TextInputFocusExample() {
const inputRef = useRef(null);
const [inputValue, setInputValue] = useState('');
const [submittedValue, setSubmittedValue] = useState('');
const focusInput = () => {
// استخدام useRef للوصول إلى مكون TextInput
inputRef.current.focus();
};
const handleSubmit = () => {
setSubmittedValue(inputValue);
setInputValue('');
// إخفاء لوحة المفاتيح بعد الإرسال
inputRef.current.blur();
};
return (
<View style={styles.container}>
<TextInput
ref={inputRef}
style={styles.input}
value={inputValue}
onChangeText={setInputValue}
placeholder="أدخل نصًا هنا"
placeholderTextColor="#888"
/>
<View style={styles.buttonRow}>
<Button title="التركيز" onPress={focusInput} />
<View style={styles.buttonSpacer} />
<Button title="إرسال" onPress={handleSubmit} />
</View>
{submittedValue ? (
<View style={styles.resultContainer}>
<Text style={styles.resultLabel}>النص المرسل:</Text>
<Text style={styles.resultValue}>{submittedValue}</Text>
</View>
) : null}
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
input: {
height: 50,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
fontSize: 16,
marginBottom: 20,
textAlign: 'right',
},
buttonRow: {
flexDirection: 'row',
marginBottom: 20,
},
buttonSpacer: {
width: 20,
},
resultContainer: {
backgroundColor: '#f3f4f6',
padding: 16,
borderRadius: 8,
},
resultLabel: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
resultValue: {
fontSize: 16,
},
});
تستخدم هذه الخطافات لتحسين الأداء عن طريق منع عمليات إعادة الحساب غير الضرورية:
import React, { useState, useMemo, useCallback } from 'react';
import { View, Text, Button, FlatList, StyleSheet } from 'react-native';
export default function PerformanceExample() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
// استخدام useMemo لحساب قيمة معقدة
const expensiveCalculation = useMemo(() => {
console.log("حساب القيمة المكلفة...");
// محاكاة عملية حسابية مكلفة
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
return result + count;
}, [count]); // يتم إعادة الحساب فقط عندما يتغير العداد
// استخدام useCallback للحفاظ على دالة بين عمليات إعادة التقديم
const addItem = useCallback(() => {
setItems(prevItems => [...prevItems, Math.floor(Math.random() * 100) + 1]);
}, []); // لا توجد تبعيات، لذا لن يتم إعادة إنشاء الدالة أبدًا
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.itemText}>{item}</Text>
</View>
);
return (
<View style={styles.container}>
<Text style={styles.title}>مثال على تحسين الأداء</Text>
<View style={styles.section}>
<Text style={styles.sectionTitle}>مثال useMemo</Text>
<Text style={styles.count}>العداد: {count}</Text>
<Text style={styles.calculationResult}>نتيجة الحساب: {expensiveCalculation}</Text>
<Button title="زيادة العداد" onPress={() => setCount(c => c + 1)} />
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>مثال useCallback</Text>
<Button title="إضافة عنصر عشوائي" onPress={addItem} />
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={(item, index) => item-${index}}
style={styles.list}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
section: {
marginBottom: 30,
backgroundColor: '#f3f4f6',
padding: 16,
borderRadius: 8,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 12,
},
count: {
fontSize: 16,
marginBottom: 8,
},
calculationResult: {
fontSize: 16,
marginBottom: 16,
color: '#4f46e5',
},
list: {
maxHeight: 200,
marginTop: 12,
},
item: {
backgroundColor: 'white',
padding: 12,
marginVertical: 4,
borderRadius: 4,
},
itemText: {
fontSize: 16,
},
});
useState
لإدارة حالة المكونuseEffect
للتعامل مع الآثار الجانبية مثل طلبات واجهة برمجة التطبيقاتuseRef
للاحتفاظ بقيم قابلة للتغيير بين عمليات إعادة التقديم أو للوصول إلى مكونات DOMuseMemo
لتخزين نتائج العمليات المكلفة لتجنب إعادة الحساب غير الضروريuseCallback
لمنع إعادة إنشاء الدوال بين عمليات إعادة التقديم، خاصة عند تمريرها إلى المكونات الفرعيةتسمح Context API في React بمشاركة البيانات بين مكونات متعددة دون الحاجة إلى تمرير البيانات عبر الـ props في كل مستوى. هذا مفيد جدًا لإدارة الحالة العالمية في التطبيق مثل تفضيلات المستخدم، السمات، أو بيانات المصادقة.
// ThemeContext.js - ملف منفصل للسياق
import React, { createContext, useState, useContext } from 'react';
// إنشاء السياق
const ThemeContext = createContext();
// إنشاء مزود السياق
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
// قيم السياق التي ستتاح للمكونات
const value = {
theme,
toggleTheme
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};
// خطاف مخصص لاستخدام السياق
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme يجب استخدامه داخل ThemeProvider');
}
return context;
};
// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { ThemeProvider } from './ThemeContext';
import HomeScreen from './HomeScreen';
import SettingsScreen from './SettingsScreen';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<ThemeProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
</ThemeProvider>
);
}
// HomeScreen.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useTheme } from './ThemeContext';
export default function HomeScreen({ navigation }) {
// استخدام خطاف السياق المخصص
const { theme, toggleTheme } = useTheme();
// تحديد الألوان بناءً على السمة
const themeStyles = {
backgroundColor: theme === 'light' ? '#ffffff' : '#333333',
textColor: theme === 'light' ? '#333333' : '#ffffff',
};
return (
<View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
<Text style={[styles.title, { color: themeStyles.textColor }]}>
الشاشة الرئيسية
</Text>
<Text style={[styles.text, { color: themeStyles.textColor }]}>
السمة الحالية: {theme}
</Text>
<TouchableOpacity
style={[styles.button, { backgroundColor: theme === 'light' ? '#4f46e5' : '#818cf8' }]}
onPress={toggleTheme}
>
<Text style={styles.buttonText}>
تبديل السمة
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, { backgroundColor: theme === 'light' ? '#4f46e5' : '#818cf8' }]}
onPress={() => navigation.navigate('Settings')}
>
<Text style={styles.buttonText}>
الذهاب إلى الإعدادات
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
text: {
fontSize: 16,
marginBottom: 20,
},
button: {
padding: 12,
borderRadius: 8,
marginVertical: 10,
width: '80%',
alignItems: 'center',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
// AuthContext.js
import React, { createContext, useState, useContext, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(true);
const [userToken, setUserToken] = useState(null);
const [userData, setUserData] = useState(null);
useEffect(() => {
// التحقق من توكن المستخدم عند بدء التطبيق
const bootstrapAsync = async () => {
try {
const token = await AsyncStorage.getItem('userToken');
const user = await AsyncStorage.getItem('userData');
if (token && user) {
setUserToken(token);
setUserData(JSON.parse(user));
}
} catch (e) {
console.error('فشل في استعادة بيانات المستخدم', e);
} finally {
setIsLoading(false);
}
};
bootstrapAsync();
}, []);
const signIn = async (email, password) => {
setIsLoading(true);
// هنا يجب استبدال هذا بطلب API حقيقي للمصادقة
// هذا مثال محاكاة فقط
setTimeout(async () => {
const fakeUserToken = 'fake-auth-token';
const fakeUserData = {
id: 1,
name: 'أحمد محمد',
email
};
try {
await AsyncStorage.setItem('userToken', fakeUserToken);
await AsyncStorage.setItem('userData', JSON.stringify(fakeUserData));
setUserToken(fakeUserToken);
setUserData(fakeUserData);
} catch (e) {
console.error('فشل في حفظ بيانات المستخدم', e);
} finally {
setIsLoading(false);
}
}, 1000);
};
const signOut = async () => {
setIsLoading(true);
try {
await AsyncStorage.removeItem('userToken');
await AsyncStorage.removeItem('userData');
setUserToken(null);
setUserData(null);
} catch (e) {
console.error('فشل في تسجيل الخروج', e);
} finally {
setIsLoading(false);
}
};
return (
<AuthContext.Provider
value={{
isLoading,
userToken,
userData,
signIn,
signOut,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth يجب استخدامه داخل AuthProvider');
}
return context;
};
AsyncStorage هو نظام تخزين غير متزامن، قائم على المفتاح والقيمة، ويعمل عبر الجلسات. يستخدم لتخزين البيانات التي يجب أن تستمر حتى بعد إغلاق التطبيق، مثل تفضيلات المستخدم أو بيانات الدخول.
npx expo install @react-native-async-storage/async-storage
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function AsyncStorageExample() {
const [name, setName] = useState('');
const [savedName, setSavedName] = useState('');
// استرجاع الاسم المحفوظ عند تحميل المكون
useEffect(() => {
const loadData = async () => {
try {
const value = await AsyncStorage.getItem('userName');
if (value !== null) {
setSavedName(value);
}
} catch (error) {
console.error('خطأ في استرجاع البيانات:', error);
}
};
loadData();
}, []);
// حفظ الاسم في AsyncStorage
const saveName = async () => {
try {
if (name) {
await AsyncStorage.setItem('userName', name);
setSavedName(name);
setName('');
alert('تم حفظ الاسم بنجاح!');
}
} catch (error) {
console.error('خطأ في حفظ البيانات:', error);
}
};
// حذف الاسم من AsyncStorage
const clearName = async () => {
try {
await AsyncStorage.removeItem('userName');
setSavedName('');
alert('تم حذف الاسم!');
} catch (error) {
console.error('خطأ في حذف البيانات:', error);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>مثال على AsyncStorage</Text>
<TextInput
style={styles.input}
placeholder="أدخل اسمك"
value={name}
onChangeText={setName}
/>
<View style={styles.buttonRow}>
<Button title="حفظ الاسم" onPress={saveName} />
<View style={styles.buttonSpacer} />
<Button
title="حذف الاسم"
onPress={clearName}
color="#ef4444"
disabled={!savedName}
/>
</View>
{savedName ? (
<View style={styles.savedDataContainer}>
<Text style={styles.savedDataLabel}>الاسم المحفوظ:</Text>
<Text style={styles.savedDataValue}>{savedName}</Text>
</View>
) : (
<Text style={styles.noDataText}>لا يوجد اسم محفوظ</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
input: {
height: 50,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
fontSize: 16,
marginBottom: 20,
textAlign: 'right',
},
buttonRow: {
flexDirection: 'row',
marginBottom: 20,
},
buttonSpacer: {
width: 10,
},
savedDataContainer: {
backgroundColor: '#f0f9ff',
padding: 16,
borderRadius: 8,
},
savedDataLabel: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
savedDataValue: {
fontSize: 16,
color: '#4f46e5',
},
noDataText: {
textAlign: 'center',
color: '#888',
marginTop: 10,
},
});
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function TodoListExample() {
const [todos, setTodos] = useState([]);
const [task, setTask] = useState('');
// استرجاع قائمة المهام عند تحميل المكون
useEffect(() => {
const loadTodos = async () => {
try {
const jsonValue = await AsyncStorage.getItem('todos');
if (jsonValue != null) {
setTodos(JSON.parse(jsonValue));
}
} catch (error) {
console.error('خطأ في استرجاع المهام:', error);
}
};
loadTodos();
}, []);
// حفظ قائمة المهام عند تغييرها
useEffect(() => {
const saveTodos = async () => {
try {
const jsonValue = JSON.stringify(todos);
await AsyncStorage.setItem('todos', jsonValue);
} catch (error) {
console.error('خطأ في حفظ المهام:', error);
}
};
if (todos.length > 0) {
saveTodos();
}
}, [todos]);
// إضافة مهمة جديدة
const addTodo = () => {
if (task) {
const newTodo = {
id: Date.now().toString(),
text: task,
completed: false,
};
setTodos([...todos, newTodo]);
setTask('');
}
};
// تبديل حالة الإكمال للمهمة
const toggleTodo = (id) => {
const updatedTodos = todos.map(todo => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
setTodos(updatedTodos);
};
// حذف مهمة
const deleteTodo = async (id) => {
const newTodos = todos.filter(todo => todo.id !== id);
setTodos(newTodos);
// إذا لم تعد هناك مهام، احذف المفتاح من التخزين
if (newTodos.length === 0) {
try {
await AsyncStorage.removeItem('todos');
} catch (error) {
console.error('خطأ في حذف المهام:', error);
}
}
};
// حذف جميع المهام
const clearAllTodos = async () => {
try {
await AsyncStorage.removeItem('todos');
setTodos([]);
} catch (error) {
console.error('خطأ في حذف جميع المهام:', error);
}
};
// تقديم عنصر المهمة
const renderItem = ({ item }) => (
<View style={styles.todoItem}>
<Text
style={[
styles.todoText,
item.completed && styles.todoCompleted
]}
onPress={() => toggleTodo(item.id)}
>
{item.text}
</Text>
<Button
title="حذف"
onPress={() => deleteTodo(item.id)}
color="#ef4444"
/>
</View>
);
return (
<View style={styles.container}>
<Text style={styles.title}>قائمة المهام</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="أدخل مهمة جديدة"
value={task}
onChangeText={setTask}
onSubmitEditing={addTodo}
/>
<Button title="إضافة" onPress={addTodo} />
</View>
{todos.length > 0 ? (
<View style={styles.listContainer}>
<FlatList
data={todos}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
<Button
title="حذف الكل"
onPress={clearAllTodos}
color="#ef4444"
style={styles.clearButton}
/>
</View>
) : (
<Text style={styles.emptyText}>لا توجد مهام. أضف مهمة جديدة للبدء!</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
inputContainer: {
flexDirection: 'row',
marginBottom: 20,
},
input: {
flex: 1,
height: 50,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
fontSize: 16,
marginRight: 10,
textAlign: 'right',
},
listContainer: {
marginTop: 10,
},
todoItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#f0f9ff',
padding: 12,
borderRadius: 8,
marginBottom: 10,
},
todoText: {
flex: 1,
fontSize: 16,
marginRight: 10,
},
todoCompleted: {
textDecorationLine: 'line-through',
color: '#9ca3af',
},
emptyText: {
textAlign: 'center',
color: '#888',
marginTop: 20,
fontSize: 16,
},
clearButton: {
marginTop: 20,
},
});
يستخدم React Native مكونات StyleSheet لتحديد أنماط التطبيق. على خلاف CSS في الويب، تستخدم React Native أسماء الخصائص بنمط camelCase وتعتمد على JavaScript للأنماط.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function StylesExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>عنوان كبير</Text>
<Text style={styles.subtitle}>عنوان فرعي</Text>
<View style={styles.card}>
<Text style={styles.cardTitle}>بطاقة معلومات</Text>
<Text style={styles.cardText}>هذا نص داخل بطاقة مع تنسيق مختلف.</Text>
</View>
<View style={[styles.box, styles.primaryBox]}>
<Text style={styles.boxText}>مربع أساسي</Text>
</View>
<View style={[styles.box, styles.secondaryBox]}>
<Text style={styles.boxText}>مربع ثانوي</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f9fafb',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#111827',
marginBottom: 8,
textAlign: 'right',
},
subtitle: {
fontSize: 18,
color: '#4b5563',
marginBottom: 20,
textAlign: 'right',
},
card: {
backgroundColor: 'white',
borderRadius: 8,
padding: 16,
marginBottom: 20,
// إضافة ظل للبطاقة
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // للأندرويد
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
color: '#111827',
textAlign: 'right',
},
cardText: {
fontSize: 16,
color: '#4b5563',
lineHeight: 24,
textAlign: 'right',
},
box: {
padding: 16,
borderRadius: 8,
marginBottom: 16,
alignItems: 'center',
},
primaryBox: {
backgroundColor: '#4f46e5',
},
secondaryBox: {
backgroundColor: '#0ea5e9',
},
boxText: {
color: 'white',
fontWeight: 'bold',
fontSize: 16,
},
});
يمكنك دمج الأنماط المختلفة باستخدام مصفوفة:
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export default function MultipleStylesExample() {
const [isActive, setIsActive] = useState(false);
return (
<View style={styles.container}>
<Text style={styles.title}>دمج الأنماط</Text>
{/* دمج الأنماط باستخدام مصفوفة /}
<TouchableOpacity
onPress={() => setIsActive(!isActive)}
style={[
styles.button,
isActive ? styles.activeButton : styles.inactiveButton
]}
>
<Text
style={[
styles.buttonText,
isActive ? styles.activeButtonText : styles.inactiveButtonText
]}
>
{isActive ? 'زر نشط' : 'زر غير نشط'}
</Text>
</TouchableOpacity>
{/* أنماط مضمنة مع أنماط StyleSheet /}
<View style={[
styles.box,
{
backgroundColor: isActive ? '#34d399' : '#f87171',
transform: [{ scale: isActive ? 1.1 : 1 }]
}
]}>
<Text style={styles.boxText}>
{isActive ? 'الحالة: نشط' : 'الحالة: غير نشط'}
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 30,
textAlign: 'center',
},
button: {
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
marginBottom: 30,
width: 200,
alignItems: 'center',
},
activeButton: {
backgroundColor: '#4f46e5',
},
inactiveButton: {
backgroundColor: '#e5e7eb',
},
buttonText: {
fontSize: 16,
fontWeight: 'bold',
},
activeButtonText: {
color: 'white',
},
inactiveButtonText: {
color: '#4b5563',
},
box: {
width: 200,
height: 100,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
// تأثير انتقالي (لا يعمل في React Native، هذا فقط للتوضيح)
// يجب استخدام Animated API للتأثيرات الانتقالية
},
boxText: {
color: 'white',
fontWeight: 'bold',
fontSize: 16,
},
});
للمشاريع الكبيرة، يمكنك تنظيم الأنماط في ملفات منفصلة:
// styles/colors.js - ملف الألوان
export default {
primary: '#4f46e5',
secondary: '#0ea5e9',
success: '#10b981',
danger: '#ef4444',
warning: '#f59e0b',
info: '#3b82f6',
light: '#f9fafb',
dark: '#111827',
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
}
};
// styles/typography.js - أنماط النصوص
import { StyleSheet } from 'react-native';
import colors from './colors';
export default StyleSheet.create({
h1: {
fontSize: 30,
fontWeight: 'bold',
color: colors.dark,
marginBottom: 10,
textAlign: 'right',
},
h2: {
fontSize: 24,
fontWeight: 'bold',
color: colors.dark,
marginBottom: 8,
textAlign: 'right',
},
h3: {
fontSize: 20,
fontWeight: 'bold',
color: colors.dark,
marginBottom: 8,
textAlign: 'right',
},
body: {
fontSize: 16,
color: colors.gray[600],
lineHeight: 24,
textAlign: 'right',
},
small: {
fontSize: 14,
color: colors.gray[500],
textAlign: 'right',
},
});
// استخدام الأنماط المنظمة
import React from 'react';
import { View, Text } from 'react-native';
import colors from './styles/colors';
import typography from './styles/typography';
export default function OrganizedStylesExample() {
return (
<View style={{ flex: 1, padding: 20, backgroundColor: colors.light }}>
<Text style={typography.h1}>عنوان رئيسي</Text>
<Text style={typography.h2}>عنوان فرعي</Text>
<Text style={typography.body}>
هذا مثال على استخدام الأنماط المنظمة. يساعد هذا النهج في الحفاظ على تناسق التصميم في جميع أنحاء التطبيق وتسهيل إجراء تغييرات على نطاق واسع.
</Text>
<Text style={typography.small}>تم التحديث: 2023</Text>
</View>
);
}
تستخدم React Native نظام Flexbox للتخطيط، والذي يسمح بتوزيع العناصر في الشاشة بشكل مرن. يختلف قليلاً عن Flexbox في CSS للويب، ولكنه يعمل بشكل مشابه.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function FlexboxBasics() {
return (
<View style={styles.container}>
<Text style={styles.title}>أساسيات Flexbox</Text>
<Text style={styles.sectionTitle}>flexDirection: 'row'</Text>
<View style={[styles.boxContainer, { flexDirection: 'row' }]}>
<View style={[styles.box, { backgroundColor: '#4f46e5' }]} />
<View style={[styles.box, { backgroundColor: '#0ea5e9' }]} />
<View style={[styles.box, { backgroundColor: '#10b981' }]} />
</View>
<Text style={styles.sectionTitle}>flexDirection: 'column'</Text>
<View style={[styles.boxContainer, { flexDirection: 'column' }]}>
<View style={[styles.box, { backgroundColor: '#4f46e5' }]} />
<View style={[styles.box, { backgroundColor: '#0ea5e9' }]} />
<View style={[styles.box, { backgroundColor: '#10b981' }]} />
</View>
<Text style={styles.sectionTitle}>justifyContent: 'space-between'</Text>
<View style={[styles.boxContainer, { justifyContent: 'space-between' }]}>
<View style={[styles.box, { backgroundColor: '#4f46e5' }]} />
<View style={[styles.box, { backgroundColor: '#0ea5e9' }]} />
<View style={[styles.box, { backgroundColor: '#10b981' }]} />
</View>
<Text style={styles.sectionTitle}>alignItems: 'center'</Text>
<View style={[styles.boxContainer, { height: 100, alignItems: 'center' }]}>
<View style={[styles.box, { backgroundColor: '#4f46e5' }]} />
<View style={[styles.box, { backgroundColor: '#0ea5e9', height: 60 }]} />
<View style={[styles.box, { backgroundColor: '#10b981' }]} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
sectionTitle: {
fontSize: 16,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
},
boxContainer: {
flexDirection: 'row',
marginBottom: 10,
backgroundColor: '#f3f4f6',
padding: 10,
borderRadius: 8,
},
box: {
width: 40,
height: 40,
margin: 5,
borderRadius: 4,
},
});
import React from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
export default function AdvancedFlexbox() {
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>خصائص Flex المتقدمة</Text>
<Text style={styles.sectionTitle}>flex: 1 (توزيع متساوٍ)</Text>
<View style={[styles.row, { height: 80 }]}>
<View style={[styles.flex1, { backgroundColor: '#4f46e5' }]}>
<Text style={styles.boxText}>flex: 1</Text>
</View>
<View style={[styles.flex1, { backgroundColor: '#0ea5e9' }]}>
<Text style={styles.boxText}>flex: 1</Text>
</View>
<View style={[styles.flex1, { backgroundColor: '#10b981' }]}>
<Text style={styles.boxText}>flex: 1</Text>
</View>
</View>
<Text style={styles.sectionTitle}>flex: 2 (نسبة مختلفة)</Text>
<View style={[styles.row, { height: 80 }]}>
<View style={[styles.flex1, { backgroundColor: '#4f46e5' }]}>
<Text style={styles.boxText}>flex: 1</Text>
</View>
<View style={[styles.flex2, { backgroundColor: '#0ea5e9' }]}>
<Text style={styles.boxText}>flex: 2</Text>
</View>
<View style={[styles.flex1, { backgroundColor: '#10b981' }]}>
<Text style={styles.boxText}>flex: 1</Text>
</View>
</View>
<Text style={styles.sectionTitle}>flexWrap: 'wrap'</Text>
<View style={[styles.row, { flexWrap: 'wrap' }]}>
{[...Array(9)].map((, i) => (
<View
key={i}
style={[
styles.smallBox,
{ backgroundColor: ['#4f46e5', '#0ea5e9', '#10b981'][i % 3] }
]}
/>
))}
</View>
<Text style={styles.sectionTitle}>alignSelf</Text>
<View style={[styles.row, { height: 120, alignItems: 'flex-start' }]}>
<View style={[styles.flex1, { backgroundColor: '#4f46e5' }]}>
<Text style={styles.boxText}>start</Text>
</View>
<View style={[styles.flex1, { backgroundColor: '#0ea5e9', alignSelf: 'center' }]}>
<Text style={styles.boxText}>center</Text>
</View>
<View style={[styles.flex1, { backgroundColor: '#10b981', alignSelf: 'flex-end' }]}>
<Text style={styles.boxText}>end</Text>
</View>
<View style={[styles.flex1, { backgroundColor: '#f59e0b', alignSelf: 'stretch' }]}>
<Text style={styles.boxText}>stretch</Text>
</View>
</View>
<Text style={styles.sectionTitle}>position: 'absolute'</Text>
<View style={styles.positionContainer}>
<View style={styles.basePositioned} />
<View style={[styles.positioned, { top: 10, left: 10 }]} />
<View style={[styles.positioned, { top: 10, right: 10 }]} />
<View style={[styles.positioned, { bottom: 10, left: 10 }]} />
<View style={[styles.positioned, { bottom: 10, right: 10 }]} />
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
sectionTitle: {
fontSize: 16,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
},
row: {
flexDirection: 'row',
backgroundColor: '#f3f4f6',
borderRadius: 8,
padding: 10,
marginBottom: 10,
},
flex1: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
margin: 5,
borderRadius: 4,
padding: 8,
},
flex2: {
flex: 2,
justifyContent: 'center',
alignItems: 'center',
margin: 5,
borderRadius: 4,
padding: 8,
},
smallBox: {
width: 60,
height: 60,
margin: 5,
borderRadius: 4,
},
boxText: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
positionContainer: {
height: 200,
backgroundColor: '#f3f4f6',
borderRadius: 8,
position: 'relative',
},
basePositioned: {
position: 'absolute',
top: '25%',
left: '25%',
right: '25%',
bottom: '25%',
backgroundColor: '#e5e7eb',
borderRadius: 8,
},
positioned: {
position: 'absolute',
width: 50,
height: 50,
backgroundColor: '#4f46e5',
borderRadius: 25,
},
});
import React from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
export default function CommonLayouts() {
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>تخطيطات شائعة</Text>
<Text style={styles.sectionTitle}>بطاقة معلومات</Text>
<View style={styles.card}>
<View style={styles.cardHeader}>
<View style={styles.avatar} />
<View style={styles.headerInfo}>
<Text style={styles.userName}>أحمد محمد</Text>
<Text style={styles.userRole}>مصمم واجهات المستخدم</Text>
</View>
</View>
<Text style={styles.cardContent}>
هذا مثال على تخطيط بطاقة معلومات شائعة في التطبيقات. يمكن استخدام Flexbox لإنشاء تخطيطات معقدة بشكل بسيط.
</Text>
<View style={styles.cardFooter}>
<View style={styles.footerButton}>
<Text style={styles.buttonText}>إعجاب</Text>
</View>
<View style={styles.footerButton}>
<Text style={styles.buttonText}>مشاركة</Text>
</View>
</View>
</View>
<Text style={styles.sectionTitle}>شريط التنقل</Text>
<View style={styles.navbar}>
<View style={styles.navItem}>
<View style={styles.iconPlaceholder} />
<Text style={styles.navText}>الرئيسية</Text>
</View>
<View style={styles.navItem}>
<View style={styles.iconPlaceholder} />
<Text style={styles.navText}>البحث</Text>
</View>
<View style={styles.navItem}>
<View style={styles.iconPlaceholder} />
<Text style={styles.navText}>الإشعارات</Text>
</View>
<View style={styles.navItem}>
<View style={styles.iconPlaceholder} />
<Text style={styles.navText}>الملف</Text>
</View>
</View>
<Text style={styles.sectionTitle}>شبكة العناصر</Text>
<View style={styles.grid}>
{[...Array(6)].map((, i) => (
<View key={i} style={styles.gridItem}>
<View style={styles.gridItemContent} />
<Text style={styles.gridItemText}>عنصر {i + 1}</Text>
</View>
))}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
},
// بطاقة معلومات
card: {
backgroundColor: 'white',
borderRadius: 8,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
cardHeader: {
flexDirection: 'row',
marginBottom: 16,
alignItems: 'center',
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: '#4f46e5',
marginLeft: 12,
},
headerInfo: {
flex: 1,
},
userName: {
fontSize: 16,
fontWeight: 'bold',
color: '#111827',
},
userRole: {
fontSize: 14,
color: '#6b7280',
},
cardContent: {
fontSize: 16,
color: '#4b5563',
lineHeight: 24,
marginBottom: 16,
textAlign: 'right',
},
cardFooter: {
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: '#e5e7eb',
paddingTop: 12,
},
footerButton: {
flex: 1,
alignItems: 'center',
padding: 8,
},
buttonText: {
fontSize: 14,
fontWeight: 'bold',
color: '#4f46e5',
},
// شريط التنقل
navbar: {
flexDirection: 'row',
backgroundColor: 'white',
borderRadius: 8,
padding: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
navItem: {
flex: 1,
alignItems: 'center',
},
iconPlaceholder: {
width: 24,
height: 24,
backgroundColor: '#4f46e5',
borderRadius: 12,
marginBottom: 4,
},
navText: {
fontSize: 12,
color: '#4b5563',
},
// شبكة العناصر
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginHorizontal: -8,
},
gridItem: {
width: '30%',
marginBottom: 16,
alignItems: 'center',
},
gridItemContent: {
width: 80,
height: 80,
backgroundColor: '#4f46e5',
borderRadius: 8,
marginBottom: 8,
},
gridItemText: {
fontSize: 14,
color: '#4b5563',
},
});
flexDirection
في React Native هي 'column'
بدلاً من 'row'
في CSS للويبflex
في React Native تستخدم عادة قيمًا رقمية فقط (مثل flex: 1
) وليس تعبيرات معقدة كما في CSSflex-flow
و align-content
width
أو height
، يكون لها أولوية على قيم flex
في React Nativeيتيح لك التصميم المتجاوب إنشاء واجهات تعمل بشكل جيد على مختلف أحجام الشاشات والاتجاهات. في React Native، هناك عدة تقنيات يمكن استخدامها لإنشاء تطبيقات متجاوبة.
import React from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';
// الحصول على أبعاد النافذة
const { width, height } = Dimensions.get('window');
export default function ScreenDimensionsExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>أبعاد الشاشة</Text>
<View style={styles.infoContainer}>
<Text style={styles.info}>عرض الشاشة: {width}px</Text>
<Text style={styles.info}>ارتفاع الشاشة: {height}px</Text>
<Text style={styles.info}>نسبة العرض إلى الارتفاع: {(width / height).toFixed(2)}</Text>
</View>
<Text style={styles.subtitle}>مربعات متجاوبة</Text>
<View style={styles.responsiveContainer}>
<View style={[styles.box, { width: width / 2 - 30 }]} />
<View style={[styles.box, { width: width / 2 - 30 }]} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
subtitle: {
fontSize: 18,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
},
infoContainer: {
backgroundColor: '#f3f4f6',
padding: 16,
borderRadius: 8,
},
info: {
fontSize: 16,
marginBottom: 8,
},
responsiveContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 10,
},
box: {
height: 100,
backgroundColor: '#4f46e5',
borderRadius: 8,
},
});
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';
export default function OrientationExample() {
const [dimensions, setDimensions] = useState({
window: Dimensions.get('window')
});
const [isPortrait, setIsPortrait] = useState(
dimensions.window.height > dimensions.window.width
);
useEffect(() => {
// إنشاء مستمع للتغييرات في أبعاد الشاشة
const subscription = Dimensions.addEventListener('change', ({ window }) => {
setDimensions({ window });
setIsPortrait(window.height > window.width);
});
// إزالة المستمع عند تفكيك المكون
return () => subscription.remove();
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>مثال على الاستجابة للاتجاه</Text>
<View style={styles.infoContainer}>
<Text style={styles.info}>
وضع الجهاز الحالي: {isPortrait ? 'عمودي (Portrait)' : 'أفقي (Landscape)'}
</Text>
<Text style={styles.info}>
العرض: {dimensions.window.width}px
</Text>
<Text style={styles.info}>
الارتفاع: {dimensions.window.height}px
</Text>
</View>
<View style={[
styles.responsiveBox,
isPortrait ? styles.portraitBox : styles.landscapeBox
]}>
<Text style={styles.boxText}>
هذا العنصر يتغير بناءً على اتجاه الجهاز
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
infoContainer: {
backgroundColor: '#f3f4f6',
padding: 16,
borderRadius: 8,
marginBottom: 20,
},
info: {
fontSize: 16,
marginBottom: 8,
},
responsiveBox: {
padding: 20,
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
},
portraitBox: {
backgroundColor: '#4f46e5',
height: 200,
},
landscapeBox: {
backgroundColor: '#10b981',
height: 120,
flexDirection: 'row',
},
boxText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
},
});
لا يمكن استخدام النسب المئوية مباشرة في React Native، ولكن يمكن حسابها باستخدام أبعاد الشاشة:
import React from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';
// الحصول على أبعاد الشاشة
const { width, height } = Dimensions.get('window');
// حساب النسب
const getWidthPercent = (percent) => (width * percent) / 100;
const getHeightPercent = (percent) => (height * percent) / 100;
export default function PercentageExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>استخدام النسب المئوية</Text>
<View style={styles.row}>
<View style={[
styles.box,
{ width: getWidthPercent(30), height: getHeightPercent(15) }
]}>
<Text style={styles.boxText}>30% × 15%</Text>
</View>
<View style={[
styles.box,
{ width: getWidthPercent(60), height: getHeightPercent(15) }
]}>
<Text style={styles.boxText}>60% × 15%</Text>
</View>
</View>
<View style={[
styles.box,
styles.centeredBox,
{ width: getWidthPercent(90), height: getHeightPercent(30) }
]}>
<Text style={styles.boxText}>90% × 30%</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
marginBottom: 20,
},
box: {
backgroundColor: '#4f46e5',
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
},
centeredBox: {
backgroundColor: '#10b981',
},
boxText: {
color: 'white',
fontWeight: 'bold',
},
});
يساعد مكون SafeAreaView في تجنب التداخل مع الشقوق (notches) وعناصر واجهة المستخدم الخاصة بالنظام:
import React from 'react';
import { SafeAreaView, View, Text, StyleSheet, Platform, StatusBar } from 'react-native';
export default function SafeAreaExample() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>شريط التنقل الآمن</Text>
</View>
<View style={styles.content}>
<Text style={styles.contentText}>
يتيح لك مكون SafeAreaView ضمان عرض المحتوى في المنطقة الآمنة من الشاشة،
بعيدًا عن الشقوق (notches) وشريط الحالة وشريط التنقل الخاص بالنظام.
</Text>
<Text style={styles.contentText}>
هذا مهم بشكل خاص للأجهزة الحديثة مثل iPhone X وما بعده، والتي تحتوي على شقوق
في الشاشة.
</Text>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>تذييل آمن</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
// إضافة paddingTop لنظام Android
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
},
header: {
height: 60,
backgroundColor: '#4f46e5',
justifyContent: 'center',
alignItems: 'center',
},
headerText: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
content: {
flex: 1,
padding: 20,
},
contentText: {
fontSize: 16,
lineHeight: 24,
marginBottom: 16,
textAlign: 'right',
},
footer: {
height: 60,
backgroundColor: '#4f46e5',
justifyContent: 'center',
alignItems: 'center',
},
footerText: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
});
flex: 1
بدلاً من الارتفاعات الثابتة حيثما أمكنflexDirection
و flexWrap
لإنشاء تخطيطات متجاوبةSafeAreaView
لتجنب التداخل مع عناصر واجهة المستخدم الخاصة بالنظامPixelRatio
للتعامل مع كثافات الشاشة المختلفةبعد انتهائك من تطوير تطبيقك باستخدام React Native و Expo، تحتاج إلى بناء التطبيق وإعداده للنشر. توفر Expo أدوات مختلفة لبناء تطبيقات Android و iOS.
قبل البناء، تحتاج إلى تكوين تطبيقك في ملف app.json
:
{
"expo": {
"name": "اسم التطبيق",
"slug": "app-slug",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"/"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourcompany.yourappname"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"package": "com.yourcompany.yourappname",
"permissions": ["CAMERA", "ACCESS_FINE_LOCATION"]
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
EAS Build هي خدمة سحابية من Expo لبناء تطبيقات React Native:
تثبيت EAS CLI
npm install -g eas-cli
تسجيل الدخول إلى حسابك في Expo
eas login
تهيئة مشروعك لـ EAS Build
eas build:configure
بناء تطبيق Android
eas build --platform android
بناء تطبيق iOS
eas build --platform ios
بناء لكلا المنصتين
eas build --platform all
يتيح لك ملف eas.json
تكوين ملفات تعريف البناء المختلفة:
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {}
}
}
يمكنك أيضًا إنشاء مشروع React Native أصلي باستخدام Expo Prebuild:
إنشاء بنية مشروع أصلية
npx expo prebuild
بناء تطبيق Android
cd android
./gradlew assembleRelease
بناء تطبيق iOS (يتطلب macOS وXcode)
cd ios
pod install
xcodebuild -workspace YourApp.xcworkspace -scheme YourApp -configuration Release
version
و versionCode
(Android) أو buildNumber
(iOS) قبل كل بناء جديدبعد بناء تطبيقك، الخطوة التالية هي نشره على متاجر التطبيقات مثل Google Play و App Store.
تحديث إصدار التطبيق في ملف app.json
بناء إصدار جديد
eas build --platform android --auto-submit
eas build --platform ios --auto-submit
أو التقديم بشكل منفصل
eas submit -p android --latest
eas submit -p ios --latest
EAS (Expo Application Services) هي مجموعة من الخدمات المقدمة من Expo لتسهيل بناء وتوزيع وتحديث تطبيقات React Native.
خدمة سحابية لبناء تطبيقات React Native لنظامي iOS و Android:
إنشاء بناء إنتاجي
eas build --platform all
إنشاء بناء داخلي للاختبار
eas build --profile preview --platform all
يساعدك في تقديم تطبيقك إلى متاجر التطبيقات:
تقديم آخر بناء إلى متجر Google Play
eas submit -p android --latest
تقديم آخر بناء إلى App Store
eas submit -p ios --latest
تقديم ملف معين
eas submit -p android --path=/path/to/build.aab
eas submit -p ios --path=/path/to/build.ipa
يتيح لك تحديث JavaScript وبعض الأصول دون الحاجة إلى إعادة تقديم التطبيق إلى المتاجر:
نشر تحديث للتطبيق
eas update --branch production --message "إصلاح أخطاء وتحسينات الأداء"
نشر تحديث لإصدار محدد
eas update --branch production --message "إصلاح أخطاء" --target=1.2.3
{
"cli": {
"version": ">= 0.52.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {}
},
"submit": {
"production": {
"android": {
"track": "production"
},
"ios": {
"appleId": "[email protected]",
"ascAppId": "123456789",
"appleTeamId": "AB12CD34EF"
}
}
}
}
إضافة مستخدم اختبار
eas user:invite [email protected]
إدارة الوصول إلى المشروع
eas project:permission:set [email protected] --role admin
في هذا الدليل الشامل، استعرضنا أساسيات وتقنيات متقدمة في تطوير تطبيقات React Native باستخدام Expo. بدءًا من إعداد المشروع ووصولاً إلى نشر تطبيقك على متاجر التطبيقات.
تذكر دائمًا أن الممارسة هي المفتاح للإتقان. استمر في تطوير المشاريع الصغيرة وتوسيعها تدريجيًا لتطوير مهاراتك في React Native و Expo. نتمنى لك التوفيق في رحلتك لتطوير تطبيقات الجوال!
حقوق النشر © 2023 API Club - جميع الحقوق محفوظة