توثيق مكونات React Native مع Expo

دليل شامل للمكونات والواجهات البرمجية وأفضل الممارسات

البدء مع React Native و Expo

React Native هو إطار عمل لبناء تطبيقات الجوال باستخدام JavaScript و React. يتيح لك Expo بناء تطبيقات React Native بشكل أسرع وأسهل من خلال توفير مجموعة من الأدوات والمكتبات الجاهزة للاستخدام.

المتطلبات المسبقة:

  • Node.js (الإصدار 12 أو أحدث)
  • npm أو Yarn
  • تطبيق Expo Go على جهازك المحمول للاختبار

تثبيت الأدوات:


# تثبيت 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

مكون 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

مكون 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

مكون 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

مكون 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

مكون 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,
                  },
                });
                                        

Button

مكون Button هو عنصر واجهة المستخدم الأساسي للأزرار في React Native. يتيح للمستخدمين التفاعل مع التطبيق من خلال الضغط على زر.

الاستخدام الأساسي:


                import React from 'react';
                import { View, Button, Alert, StyleSheet } from 'react-native';
                
                export default function ButtonExample() {
                  return (
                    <View style={styles.container}>
                      <Button
                        title="اضغط هنا"
                        onPress={() => Alert.alert('تم الضغط على الزر')}
                        color="#4f46e5"
                      />
                      
                      <View style={styles.spacer} />
                      
                      <Button
                        title="زر معطل"
                        disabled
                        onPress={() => Alert.alert('لن يتم تنفيذ هذا')}
                        color="#9ca3af"
                      />
                    </View>
                  );
                }
                
                const styles = StyleSheet.create({
                  container: {
                    flex: 1,
                    justifyContent: 'center',
                    padding: 16,
                  },
                  spacer: {
                    height: 20,
                  }
                });
                                        

الخصائص الشائعة:

  • title: النص الذي يظهر داخل الزر
  • onPress: دالة تستدعى عند الضغط على الزر
  • color: لون الزر
  • disabled: تعطيل الزر
  • accessibilityLabel: نص وصفي للقراء الشاشة

ملاحظة مهمة:

مكون Button لا يدعم التخصيص المتقدم. إذا كنت بحاجة إلى مزيد من التخصيص، فكر في استخدام مكونات Touchable مثل TouchableOpacity أو Pressable.

إنشاء زر مخصص:


                import React from 'react';
                import { TouchableOpacity, Text, StyleSheet } from 'react-native';
                
                export default function CustomButton({ title, onPress, disabled }) {
                  return (
                    <TouchableOpacity
                      style={[
                        styles.button,
                        disabled ? styles.buttonDisabled : null
                      ]}
                      onPress={onPress}
                      disabled={disabled}
                    >
                      <Text style={[
                        styles.text,
                        disabled ? styles.textDisabled : null
                      ]}>
                        {title}
                      </Text>
                    </TouchableOpacity>
                  );
                }
                
                const styles = StyleSheet.create({
                  button: {
                    backgroundColor: '#4f46e5',
                    paddingVertical: 12,
                    paddingHorizontal: 24,
                    borderRadius: 8,
                    alignItems: 'center',
                    justifyContent: 'center',
                  },
                  buttonDisabled: {
                    backgroundColor: '#9ca3af',
                  },
                  text: {
                    color: 'white',
                    fontSize: 16,
                    fontWeight: 'bold',
                  },
                  textDisabled: {
                    color: '#e5e7eb',
                  },
                });
                                        

Touchables

مكونات Touchable توفر طرقًا مختلفة لإنشاء عناصر قابلة للنقر في React Native. وهي أكثر مرونة من المكون Button وتتيح تخصيصًا أكبر.

الأنواع الرئيسية:

  • TouchableOpacity: يضيف تأثير شفافية عند الضغط
  • TouchableHighlight: يضيف تأثير تظليل عند الضغط
  • TouchableWithoutFeedback: لا يضيف أي تأثير مرئي
  • Pressable: مكون حديث وأكثر مرونة (الإصدار 0.63 وما بعده)

TouchableOpacity مثال:


                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',
                  },
                });
                                        

Pressable مثال (الأحدث والأكثر مرونة):


                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

مكون 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: لون نص الـ placeholder
  • keyboardType: نوع لوحة المفاتيح (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

مكون 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)

مكتبة 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)

مكتبة 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)

مكتبة 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)

مكتبة 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',
  },
});
        

Stack Navigation

مكتبة React Navigation توفر حلاً شاملاً للتنقل بين الشاشات في تطبيقات React Native. Stack Navigation هو أبسط أشكال التنقل، ويشبه تكديس الشاشات فوق بعضها البعض.

التثبيت:


# تثبيت المكتبة الرئيسية
npx expo install @react-navigation/native

# تثبيت مكتبات التبعية
npx expo install react-native-screens react-native-safe-area-context

# تثبيت Stack Navigator
npx expo install @react-navigation/native-stack
        

إعداد التنقل الأساسي:


import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button, StyleSheet } from 'react-native';

// إنشاء مكون Stack
const Stack = createNativeStackNavigator();

// الشاشة الرئيسية
function HomeScreen({ navigation }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الشاشة الرئيسية</Text>
      <Button
        title="الذهاب إلى شاشة التفاصيل"
        onPress={() => navigation.navigate('Details', { itemId: 86, otherParam: 'شيء ما' })}
      />
    </View>
  );
}

// شاشة التفاصيل
function DetailsScreen({ route, navigation }) {
  // استخراج المعلمات
  const { itemId, otherParam } = route.params;

  return (
    <View style={styles.screen}>
      <Text style={styles.title}>شاشة التفاصيل</Text>
      <Text style={styles.text}>
        معرف العنصر: {itemId}
      </Text>
      <Text style={styles.text}>
        معلمة أخرى: {otherParam}
      </Text>
      <Button
        title="الذهاب إلى التفاصيل مرة أخرى"
        onPress={() => 
          navigation.push('Details', {
            itemId: Math.floor(Math.random() * 100),
            otherParam: 'معلمة جديدة',
          })
        }
      />
      <Button title="العودة" onPress={() => navigation.goBack()} />
      <Button title="العودة إلى البداية" onPress={() => navigation.popToTop()} />
    </View>
  );
}

// التطبيق الرئيسي مع إعداد التنقل
export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator 
        initialRouteName="Home"
        screenOptions={{
          headerStyle: {
            backgroundColor: '#4f46e5',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
          headerTitleAlign: 'center',
        }}
      >
        <Stack.Screen 
          name="Home" 
          component={HomeScreen} 
          options={{ title: 'الرئيسية' }} 
        />
        <Stack.Screen 
          name="Details" 
          component={DetailsScreen} 
          options={({ route }) => ({ 
            title: `التفاصيل ${route.params.itemId}`,
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 16,
    marginBottom: 10,
  },
});
        

أساليب التنقل الرئيسية:

  • navigation.navigate('RouteName'): الانتقال إلى شاشة محددة
  • navigation.push('RouteName'): إضافة شاشة جديدة للمكدس، حتى لو كانت الشاشة الحالية
  • navigation.goBack(): العودة إلى الشاشة السابقة
  • navigation.popToTop(): العودة إلى أول شاشة في المكدس
  • navigation.replace('RouteName'): استبدال الشاشة الحالية بشاشة أخرى

نصيحة:

يمكنك استخدام navigation.setOptions() لتغيير خيارات الشاشة ديناميكيًا بعد تحميلها.

Tab Navigation

Tab Navigation يتيح للمستخدمين التنقل بين مختلف الشاشات باستخدام شريط تبويب، وهو نمط شائع في تطبيقات الهاتف المحمول.

التثبيت:


# بالإضافة إلى التثبيتات السابقة، قم بتثبيت
npx expo install @react-navigation/bottom-tabs
        

إعداد التنقل بالتبويبات:


import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { View, Text, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

// إنشاء Tab Navigator
const Tab = createBottomTabNavigator();

// شاشة الرئيسية
function HomeScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الرئيسية</Text>
      <Text style={styles.text}>هذه هي الشاشة الرئيسية للتطبيق.</Text>
    </View>
  );
}

// شاشة البحث
function SearchScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>البحث</Text>
      <Text style={styles.text}>هنا يمكنك البحث عن العناصر.</Text>
    </View>
  );
}

// شاشة الإشعارات
function NotificationsScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الإشعارات</Text>
      <Text style={styles.text}>هنا تظهر إشعاراتك الجديدة.</Text>
    </View>
  );
}

// شاشة الملف الشخصي
function ProfileScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الملف الشخصي</Text>
      <Text style={styles.text}>هنا يمكنك عرض وتعديل ملفك الشخصي.</Text>
    </View>
  );
}

// التطبيق الرئيسي مع شريط التبويبات
export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            let iconName;

            if (route.name === 'Home') {
              iconName = focused ? 'home' : 'home-outline';
            } else if (route.name === 'Search') {
              iconName = focused ? 'search' : 'search-outline';
            } else if (route.name === 'Notifications') {
              iconName = focused ? 'notifications' : 'notifications-outline';
            } else if (route.name === 'Profile') {
              iconName = focused ? 'person' : 'person-outline';
            }

            return <Ionicons name={iconName} size={size} color={color} />;
          },
          tabBarActiveTintColor: '#4f46e5',
          tabBarInactiveTintColor: 'gray',
          tabBarLabelStyle: {
            fontSize: 12,
          },
          headerTitleAlign: 'center',
          headerStyle: {
            backgroundColor: '#4f46e5',
          },
          headerTintColor: '#fff',
        })}
      >
        <Tab.Screen name="Home" component={HomeScreen} options={{ title: 'الرئيسية' }} />
        <Tab.Screen name="Search" component={SearchScreen} options={{ title: 'البحث' }} />
        <Tab.Screen name="Notifications" component={NotificationsScreen} options={{ title: 'الإشعارات' }} />
        <Tab.Screen name="Profile" component={ProfileScreen} options={{ title: 'الملف الشخصي' }} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 16,
    textAlign: 'center',
  },
});
        

تخصيص شريط التبويبات:


import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';

const Tab = createBottomTabNavigator();

function MyTabBar({ state, descriptors, navigation }) {
  return (
    <View style={styles.tabBar}>
      {state.routes.map((route, index) => {
        const { options } = descriptors[route.key];
        const label =
          options.tabBarLabel !== undefined
            ? options.tabBarLabel
            : options.title !== undefined
            ? options.title
            : route.name;

        const isFocused = state.index === index;

        const onPress = () => {
          const event = navigation.emit({
            type: 'tabPress',
            target: route.key,
            canPreventDefault: true,
          });

          if (!isFocused && !event.defaultPrevented) {
            navigation.navigate(route.name);
          }
        };

        // اختيار الأيقونة المناسبة
        let iconName;
        if (route.name === 'Home') {
          iconName = isFocused ? 'home' : 'home-outline';
        } else if (route.name === 'Settings') {
          iconName = isFocused ? 'settings' : 'settings-outline';
        }

        return (
          <TouchableOpacity
            key={index}
            accessibilityRole="button"
            accessibilityState={isFocused ? { selected: true } : {}}
            accessibilityLabel={options.tabBarAccessibilityLabel}
            testID={options.tabBarTestID}
            onPress={onPress}
            style={styles.tabButton}
          >
            <Ionicons 
              name={iconName} 
              size={24} 
              color={isFocused ? '#4f46e5' : '#888'} 
            />
            <Text style={[
              styles.tabLabel,
              { color: isFocused ? '#4f46e5' : '#888' }
            ]}>
              {label}
            </Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
}

function HomeScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الشاشة الرئيسية</Text>
    </View>
  );
}

function SettingsScreen() {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>شاشة الإعدادات</Text>
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        tabBar={props => <MyTabBar {...props} />}
        screenOptions={{
          headerStyle: {
            backgroundColor: '#4f46e5',
          },
          headerTintColor: '#fff',
          headerTitleAlign: 'center',
        }}
      >
        <Tab.Screen 
          name="Home" 
          component={HomeScreen} 
          options={{ title: 'الرئيسية' }} 
        />
        <Tab.Screen 
          name="Settings" 
          component={SettingsScreen} 
          options={{ title: 'الإعدادات' }} 
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
  tabBar: {
    flexDirection: 'row',
    backgroundColor: 'white',
    height: 60,
    borderTopWidth: 1,
    borderTopColor: '#e5e7eb',
    elevation: 8,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: -2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  tabButton: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  tabLabel: {
    fontSize: 12,
    marginTop: 4,
  },
});
        

Drawer Navigation

Drawer Navigation يوفر قائمة جانبية منزلقة يمكن استخدامها للتنقل بين شاشات التطبيق المختلفة، وهي مفيدة للتطبيقات ذات العديد من الوجهات.

التثبيت:


# بالإضافة إلى التثبيتات السابقة، قم بتثبيت
npx expo install @react-navigation/drawer
npx expo install react-native-gesture-handler react-native-reanimated
        

يجب تحديث ملف babel.config.js لدعم مكتبة Reanimated:


module.exports = {
  presets: ['babel-preset-expo'],
  plugins: ['react-native-reanimated/plugin'],
};
        

إعداد التنقل بالقائمة الجانبية:


import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons';

// إنشاء Drawer Navigator
const Drawer = createDrawerNavigator();

// شاشة الرئيسية
function HomeScreen({ navigation }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الشاشة الرئيسية</Text>
      <Button
        title="فتح القائمة"
        onPress={() => navigation.openDrawer()}
      />
    </View>
  );
}

// شاشة الإشعارات
function NotificationsScreen({ navigation }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الإشعارات</Text>
      <Button
        title="فتح القائمة"
        onPress={() => navigation.openDrawer()}
      />
    </View>
  );
}

// شاشة الإعدادات
function SettingsScreen({ navigation }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الإعدادات</Text>
      <Button
        title="فتح القائمة"
        onPress={() => navigation.openDrawer()}
      />
    </View>
  );
}

// شاشة الملف الشخصي
function ProfileScreen({ navigation }) {
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>الملف الشخصي</Text>
      <Button
        title="فتح القائمة"
        onPress={() => navigation.openDrawer()}
      />
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator
        initialRouteName="Home"
        screenOptions={{
          drawerPosition: 'right', // لدعم اللغة العربية (RTL)
          drawerType: 'front',
          drawerStyle: {
            backgroundColor: '#f5f5f5',
            width: 240,
          },
          drawerActiveTintColor: '#4f46e5',
          drawerInactiveTintColor: '#666',
          headerStyle: {
            backgroundColor: '#4f46e5',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
          headerTitleAlign: 'center',
        }}
      >
        <Drawer.Screen 
          name="Home" 
          component={HomeScreen} 
          options={{
            title: 'الرئيسية',
            drawerIcon: ({ color, size }) => (
              <Ionicons name="home-outline" size={size} color={color} />
            ),
          }} 
        />
        <Drawer.Screen 
          name="Notifications" 
          component={NotificationsScreen} 
          options={{
            title: 'الإشعارات',
            drawerIcon: ({ color, size }) => (
              <Ionicons name="notifications-outline" size={size} color={color} />
            ),
          }} 
        />
        <Drawer.Screen 
          name="Settings" 
          component={SettingsScreen} 
          options={{
            title: 'الإعدادات',
            drawerIcon: ({ color, size }) => (
              <Ionicons name="settings-outline" size={size} color={color} />
            ),
          }} 
        />
        <Drawer.Screen 
          name="Profile" 
          component={ProfileScreen} 
          options={{
            title: 'الملف الشخصي',
            drawerIcon: ({ color, size }) => (
              <Ionicons name="person-outline" size={size} color={color} />
            ),
          }} 
        />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
});
        

الدمج بين أنواع التنقل المختلفة:

يمكنك دمج Stack و Tab و Drawer معًا لإنشاء تجربة تنقل متكاملة:


import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

// إنشاء المتنقلات
const Drawer = createDrawerNavigator();
const Tab = createBottomTabNavigator();
const HomeStack = createNativeStackNavigator();
const SettingsStack = createNativeStackNavigator();

// مكونات شاشات التبويب الرئيسية
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 24, marginBottom: 20 }}>الشاشة الرئيسية</Text>
      <Button
        title="عرض التفاصيل"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

function DetailsScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 24 }}>شاشة التفاصيل</Text>
    </View>
  );
}

function SettingsScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 24, marginBottom: 20 }}>شاشة الإعدادات</Text>
      <Button
        title="عرض الملف الشخصي"
        onPress={() => navigation.navigate('Profile')}
      />
    </View>
  );
}

function ProfileScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 24 }}>شاشة الملف الشخصي</Text>
    </View>
  );
}

// مكدس الشاشة الرئيسية
function HomeStackScreen() {
  return (
    <HomeStack.Navigator>
      <HomeStack.Screen 
        name="HomeMain" 
        component={HomeScreen} 
        options={{ title: 'الرئيسية', headerShown: false }} 
      />
      <HomeStack.Screen 
        name="Details" 
        component={DetailsScreen} 
        options={{ title: 'التفاصيل' }} 
      />
    </HomeStack.Navigator>
  );
}

// مكدس شاشة الإعدادات
function SettingsStackScreen() {
  return (
    <SettingsStack.Navigator>
      <SettingsStack.Screen 
        name="SettingsMain" 
        component={SettingsScreen} 
        options={{ title: 'الإعدادات', headerShown: false }} 
      />
      <SettingsStack.Screen 
        name="Profile" 
        component={ProfileScreen} 
        options={{ title: 'الملف الشخصي' }} 
      />
    </SettingsStack.Navigator>
  );
}

// مجموعة التبويبات
function TabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;
          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          } else if (route.name === 'Settings') {
            iconName = focused ? 'settings' : 'settings-outline';
          }
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#4f46e5',
        tabBarInactiveTintColor: 'gray',
        headerShown: false,
      })}
    >
      <Tab.Screen name="Home" component={HomeStackScreen} options={{ title: 'الرئيسية' }} />
      <Tab.Screen name="Settings" component={SettingsStackScreen} options={{ title: 'الإعدادات' }} />
    </Tab.Navigator>
  );
}

// التطبيق الرئيسي مع القائمة الجانبية
export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator
        screenOptions={{
          drawerPosition: 'right',
          drawerActiveTintColor: '#4f46e5',
          headerStyle: {
            backgroundColor: '#4f46e5',
          },
          headerTintColor: '#fff',
          headerTitleAlign: 'center',
        }}
      >
        <Drawer.Screen 
          name="TabHome" 
          component={TabNavigator} 
          options={{ 
            title: 'الرئيسية',
            drawerIcon: ({ color, size }) => (
              <Ionicons name="home-outline" size={size} color={color} />
            ),
          }} 
        />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}
        

React Hooks

تمثل React Hooks طريقة لاستخدام حالة ودورة حياة المكونات في المكونات الوظيفية (Functional Components). تسمح لك باستخدام ميزات React دون الحاجة إلى كتابة مكونات صنفية (Class Components).

useState:

يستخدم لإدارة حالة المكون:


    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,
    },
    });
    

useEffect:

يستخدم لتنفيذ الآثار الجانبية (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,
    },
    });
    

useRef:

يسمح بالاحتفاظ بمرجع قابل للتغيير يستمر بين عمليات إعادة التقديم:


    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,
    },
    });
    

useMemo و useCallback:

تستخدم هذه الخطافات لتحسين الأداء عن طريق منع عمليات إعادة الحساب غير الضرورية:


    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 للاحتفاظ بقيم قابلة للتغيير بين عمليات إعادة التقديم أو للوصول إلى مكونات DOM
  • استخدم useMemo لتخزين نتائج العمليات المكلفة لتجنب إعادة الحساب غير الضروري
  • استخدم useCallback لمنع إعادة إنشاء الدوال بين عمليات إعادة التقديم، خاصة عند تمريرها إلى المكونات الفرعية

Context API

تسمح Context API في React بمشاركة البيانات بين مكونات متعددة دون الحاجة إلى تمرير البيانات عبر الـ props في كل مستوى. هذا مفيد جدًا لإدارة الحالة العالمية في التطبيق مثل تفضيلات المستخدم، السمات، أو بيانات المصادقة.

إنشاء واستخدام Context:


    // 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;
    };
    

متى تستخدم Context API:

  • عندما تحتاج إلى مشاركة البيانات بين مكونات متعددة على مستويات مختلفة من شجرة المكونات
  • للبيانات العالمية مثل تفضيلات المستخدم، السمات، حالة المصادقة
  • لتجنب "تمرير البيانات عبر المستويات" (props drilling) في التطبيقات المعقدة
  • كبديل أبسط لمكتبات إدارة الحالة مثل Redux للتطبيقات الصغيرة إلى المتوسطة

AsyncStorage

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,
    },
    });
    

StyleSheets

يستخدم 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>
    );
    }
    

مزايا استخدام StyleSheet:

  • يقدم تحقق من الأخطاء أثناء عملية التطوير
  • يحسن الأداء من خلال تحويل التعريفات إلى أرقام معرفات فريدة
  • يمنع إعادة إنشاء كائنات الأنماط في كل رسم للمكون
  • يسهل تنظيم وإدارة الأنماط ومشاركتها عبر المكونات

Flexbox

تستخدم 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,
    },
    });
    

خصائص Flex المتقدمة:


    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',
    },
    });
    

الاختلافات بين Flexbox في React Native و CSS:

  • القيمة الافتراضية لـ flexDirection في React Native هي 'column' بدلاً من 'row' في CSS للويب
  • flex في React Native تستخدم عادة قيمًا رقمية فقط (مثل flex: 1) وليس تعبيرات معقدة كما في CSS
  • لا تدعم React Native جميع خصائص Flexbox الموجودة في CSS، مثل flex-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:

يساعد مكون 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:

قبل البناء، تحتاج إلى تكوين تطبيقك في ملف 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:

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:

يتيح لك ملف eas.json تكوين ملفات تعريف البناء المختلفة:


    {
    "build": {
    "development": {
    "developmentClient": true,
    "distribution": "internal"
    },
    "preview": {
    "distribution": "internal"
    },
    "production": {}
    }
    }
    

البناء المحلي (Expo Prebuild):

يمكنك أيضًا إنشاء مشروع 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
    

نصائح للبناء:

  • تأكد من تحديث جميع التبعيات إلى أحدث إصدار قبل البناء
  • اختبر دائمًا نسخة البناء المؤقتة (Preview build) قبل إنشاء نسخة الإنتاج
  • استخدم EAS Build لتجنب مشاكل التكوين المعقدة للبناء المحلي
  • تأكد من ضبط version و versionCode (Android) أو buildNumber (iOS) قبل كل بناء جديد
  • خصص رمز وشاشة البداية لتطبيقك لتحسين تجربة المستخدم

النشر على المتاجر

بعد بناء تطبيقك، الخطوة التالية هي نشره على متاجر التطبيقات مثل Google Play و App Store.

النشر على Google Play:

  1. إنشاء حساب مطور في Google Play Console (تكلفة التسجيل 25 دولارًا أمريكيًا لمرة واحدة)
  2. إنشاء تطبيق جديد وإدخال معلومات التطبيق (الاسم، الوصف، الصور، إلخ)
  3. تحديد تصنيف المحتوى للتطبيق
  4. إعداد صفحة معلومات المتجر (Store Listing)
  5. تحميل حزمة APK أو AAB (Android App Bundle) التي قمت ببنائها
  6. تعيين سعر التطبيق والدول التي سيتوفر فيها
  7. مراجعة ونشر التطبيق

النشر على App Store:

  1. إنشاء حساب في Apple Developer Program (تكلفة الاشتراك 99 دولارًا أمريكيًا سنويًا)
  2. إنشاء معرف تطبيق (App ID) وشهادات توقيع وملفات تعريف التوزيع
  3. استخدام Xcode أو App Store Connect لتحميل تطبيقك
  4. إدخال معلومات التطبيق والوصف والصور
  5. تعبئة معلومات التسعير والتوافر
  6. إرسال التطبيق للمراجعة
  7. انتظار موافقة Apple (قد تستغرق عدة أيام)

إعداد المواد التسويقية:

قائمة المواد التي تحتاج إلى إعدادها:

  • أيقونة التطبيق (بأحجام مختلفة)
  • لقطات شاشة للتطبيق (للهواتف والأجهزة اللوحية)
  • صورة العرض (Feature Graphic) لـ Google Play
  • فيديو ترويجي (اختياري ولكنه موصى به)
  • وصف قصير للتطبيق (30-80 حرفًا)
  • وصف كامل للتطبيق (4000 حرف كحد أقصى)
  • كلمات مفتاحية لتحسين البحث
  • سياسة الخصوصية (مطلوبة)

التحديثات المستقبلية:


    تحديث إصدار التطبيق في ملف app.json
    بناء إصدار جديد
    eas build --platform android --auto-submit
    eas build --platform ios --auto-submit
    أو التقديم بشكل منفصل
    eas submit -p android --latest
    eas submit -p ios --latest
    

ملاحظات مهمة:

  • تستغرق عملية مراجعة التطبيق في App Store وقتًا أطول من Google Play، لذا خطط وفقًا لذلك
  • تأكد من اختبار التطبيق بشكل جيد قبل تقديمه، فقد يؤدي رفض التطبيق إلى تأخير إطلاقه
  • حافظ على تحديث سياسة الخصوصية وشروط الاستخدام
  • تذكر زيادة رقم الإصدار ورمز الإصدار مع كل تحديث
  • خطط لاستراتيجية تسعير مناسبة (مجاني، مدفوع، اشتراك، مشتريات داخل التطبيق)

خدمة EAS

EAS (Expo Application Services) هي مجموعة من الخدمات المقدمة من Expo لتسهيل بناء وتوزيع وتحديث تطبيقات React Native.

EAS Build:

خدمة سحابية لبناء تطبيقات React Native لنظامي iOS و Android:


    إنشاء بناء إنتاجي
    eas build --platform all
    إنشاء بناء داخلي للاختبار
    eas build --profile preview --platform all
    

EAS Submit:

يساعدك في تقديم تطبيقك إلى متاجر التطبيقات:


    تقديم آخر بناء إلى متجر 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
    

EAS Update:

يتيح لك تحديث JavaScript وبعض الأصول دون الحاجة إلى إعادة تقديم التطبيق إلى المتاجر:


    نشر تحديث للتطبيق
    eas update --branch production --message "إصلاح أخطاء وتحسينات الأداء"
    نشر تحديث لإصدار محدد
    eas update --branch production --message "إصلاح أخطاء" --target=1.2.3
    

تكوين EAS في ملف eas.json:


    {
    "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
    

مزايا استخدام EAS:

  • بناء سريع وموثوق في السحابة دون الحاجة إلى إعداد بيئة البناء المحلية
  • دعم لأنظمة iOS و Android في منصة واحدة
  • إدارة التوزيع للمختبرين الداخليين بسهولة
  • تحديثات OTA (Over-The-Air) لنشر التغييرات سريعًا دون انتظار موافقة المتاجر
  • تكامل مع أدوات CI/CD لأتمتة عمليات البناء والنشر
  • تسهيل التقديم إلى متاجر التطبيقات

خاتمة

في هذا الدليل الشامل، استعرضنا أساسيات وتقنيات متقدمة في تطوير تطبيقات React Native باستخدام Expo. بدءًا من إعداد المشروع ووصولاً إلى نشر تطبيقك على متاجر التطبيقات.

تذكر دائمًا أن الممارسة هي المفتاح للإتقان. استمر في تطوير المشاريع الصغيرة وتوسيعها تدريجيًا لتطوير مهاراتك في React Native و Expo. نتمنى لك التوفيق في رحلتك لتطوير تطبيقات الجوال!

حقوق النشر © 2023 API Club - جميع الحقوق محفوظة