اذهب إلى المحتوى

أساسيات React Native


ابراهيم الخضور

الآن وقد انتهينا من إعداد بيئة التطوير، سنتعرف على أساسيات React Native وسنبدأ بتطوير تطبيقنا. سنتعلم في هذا الفصل كيف سنبني واجهة مستخدم بالاستعانة بمكوّنات React Native البنيوية، وكيف سننسق مظهر هذه المكونات البنيوية، وكيف سننتقل من واجهة عرض إلى أخرى، كما سنتعلم كيف سندير حالة النماذج بفعالية.

المكونات البنيوية

تعلمنا في الأقسام السابقة طريقة استخدام React في تعريف المكوّنات كدوال تتلقى الخصائص كوسطاء وتعيد شجرة من عناصر React. تُمثَّل هذه الشجرة عادةً باستخدام شيفرة JSX. كما رأينا كيفية استخدام المكتبة ReactDOM في بيئة المتصفح من أجل تحويل المكوّنات إلى شجرة DOM قابلة للتصيير ضمن المتصفح. لاحظ الشيفرة التالية التي تمثل مكوّنًا بسيطًا:

import React from 'react';

const HelloWorld = props => {
  return <div>Hello world!</div>;
};

يعيد المكوّن HelloWorld عنصر <div> أُنشئ باستخدام عبارة JSX. وتذكر أن عبارة JSX ستُصرّف إلى استدعاءات للتابع React.createElement كالتالي:

React.createElement('div', null, 'Hello world!');

سيُنشئ سطر الشيفرة السابق عنصر <div> دون أية خصائص props وله عنصر ابن وحيد هو النص "Hello World". وعندما يُصيَّر هذا المكوّن إلى عنصر DOM جذري بتطبيق التابع ReactDOM.render، سيُصيَّر العنصر <div> إلى عنصر DOM المقابل.

وهكذا نرى أن React لا ترتبط ببيئة محددة كبيئة المتصفح، لكن وبالاستعانة بمكتبات مثل ReactDOM ستتمكن من تصيير مجموعة من المكوّنات المعرّفة مسبقًا كعناصر DOM في بيئة محددة. ندعو هذه المكوّنات المعرّفة مسبقًا في React Native بالمكوّنات البنيوية core components.

فالمكوّنات البنيوية هي مجموعة مكوّنات تؤمنها React Native قادرة على استخدام المكونات الأصلية لمنصة خلف الستار. لننفذ المثال السابق باستخدام React Native:

import React from 'react';
import { Text } from 'react-native';
const HelloWorld = props => {
  return <Text>Hello world!</Text>;};

لاحظ كيف أدرجنا المكوّن Text من React Native، واستبدلنا العنصر<div> بالعنصر <text>. ستجد الكثير من عناصر DOM المقابلة لعناصر React Native، وسنستعرض بعض الأمثلة التي استقيناها من توثيق المكونات البنيوية:

  • المكوّن Text: ويعتبر المكون الوحيد من مكونات React Native والذي يمتلك أبناء على شكل قيم نصية، ويشابه العناصر <h1> و<strong>.
  • المكوّن View: ويمثل وحدات البناء الرئيسية لواجهة المستخدم، ويشابه العنصر <div>.
  • المكوّن TextInput: وهو مكوّن لحقل نصي، ويشابه العنصر <input>.
  • المكوّن TouchableWithoutFeedback: وغيره من المكونات القابلة للمس Touchable ويستخدم لالتقاط مختلف حوادث الضغط والنقر، ويشابه العنصر <button>.

هنالك بعض الاختلافات الواضحة بين المكوّنات البنيوية وعناصر DOM. يظهر الاختلاف الأول بأن المكوّن Text هو المكوّن الوحيد الذي يمتلك أبناء على شكل قيم نصية في React Native. وبمعنًى آخر، لن تستطيع استبدال هذا المكوّن بالمكوّن View مثلًا في المثال الذي أوردناه سابقًا.

يتعلق الاختلاف الثاني بمعالجات الأحداث. فقد اعتدنا عند العمل مع عناصر DOM على إضافة معالج أحداث مثل onClick إلى أية عناصر مثل <div> و<btton>. لكن عليك قراءة توثيق الواجهة البرمجية في React Native لمعرفة أية معالجات وأية خصائص أخرى سيقبلها المكوّن الذي تستخدمه. إذ تؤمن -على سبيل المثال- عائلة مكوّنات اللمس Touchable components القدرة على التقاط الحركات التي تُرسم على الشاشة، كما يمكنها إظهار استجابات محددة عند تمييز هذه الحركات. ومن هذه المكوّنات أيضًا TouchableWithoutFeedback الذي يقبل الخاصية onPress:

import React from 'react';
import { Text, TouchableWithoutFeedback, Alert } from 'react-native';

const TouchableText = props => {
  return (
    <TouchableWithoutFeedback
      onPress={() => Alert.alert('You pressed the text!')}
    >
      <Text>You can press me</Text>
    </TouchableWithoutFeedback>
  );
};

سنبدأ الآن في هيكلة مشروعنا بعد أن اطلعنا على أساسيات المكونات البنيوية. لننشئ مجلدًا باسم "src" في المجلد الجذري للمشروع، ولننشئ ضمنه مجلدًا آخر باسم "components". أنشئ ملفًا باسم "Main.js" وضعه في المجلد "components"، على أن يحتوي هذا الملف الشيفرة التالية:

import React from 'react';
import Constants from 'expo-constants';
import { Text, StyleSheet, View } from 'react-native';

const styles = StyleSheet.create({
  container: {
    marginTop: Constants.statusBarHeight,
    flexGrow: 1,
    flexShrink: 1,
  },
});

const Main = () => {
  return (
    <View style={styles.container}>
      <Text>Rate Repository Application</Text>
    </View>
  );
};

export default Main;

لنستخدم الآن المكوّن Main ضمن المكوّن App الموجود في الملف "App.js" والذي يقع بدوره في المجلد الجذري للمشروع. استبدل محتويات الملف السابق بالشيفرة التالية:

import React from 'react';

import Main from './src/components/Main';

const App = () => {
  return <Main />;
};

export default App;

إعادة تحميل التطبيق يدويا

رأينا كيف يعيد Expo تحميل التطبيق تلقائيًا عندما نجري تغييرات على الشيفرة. لكن قد تفشل عملية إعادة التحميل تلقائيًا في حالات معينة، وسنضطر إلى إعادة تحميل التطبيق يدويًا. تُنجز هذه العملية من خلال قائمة المطوّر التي يمكن الوصول إليها بهزّ جهازك أو باختيار الرابط "Shake Gesture" ضمن قائمة العتاد الصلب في محاكي iOS. كما يمكن أن تستخدم أيضًا الاختصار "D⌘" عندما يعمل تطبيقك على محاكي iOS، أو الاختصار "M⌘" عندما يعمل التطبيق على مقلّد Android في نظام macOS، والاختصار "Ctrl+M" لمقلد Android في نظامي Linux وWindows.

اضغط على "Reload" عندما تفتح قائمة المطوّر لإعادة تحميل التطبيق. لن تحتاج بعد ذلك إلى إعادة تحميل التطبيق، بل ستحدث العملية تلقائيًا.

التمرين 10.3

قائمة المستودعات المقيمة

سننجز في هذه التمارين النسخة الأولى من تطبيق قائمة المستودعات المُقيَّمة. ينبغي أن تتضمن القائمة الاسم الكامل للمستودع ووصفه ولغته وعدد التشعبات وعدد النجمات ومعدل التقييم وعدد التقييمات. ولحسن الحظ، تؤمن مكوّنًا مفيدًا لعرض قائمة من البيانات وهو FlatList.

انجز المكوّنين RepositoryList وRepositoryItem في الملفين "RepositoryList.jsx" و"RepositoryItem.jsx" وضعهما في المجلد "componenets". ينبغي أن يصيّر المكوّن RepositoryList المكوّن البنيوي FlatList وأن يصيّر المكوّن RepositoryItem عنصرًا واحدًا من القائمة

اقتباس

تلميح: استخدم الخاصية العائدة للمكون FlatList.

استخدم ذلك كأساس للملف "RepositoryItem.jsx":

import React from 'react';
import { FlatList, View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  separator: {
    height: 10,
  },
});

const repositories = [
  {
    id: 'jaredpalmer.formik',
    fullName: 'jaredpalmer/formik',
    description: 'Build forms in React, without the tears',
    language: 'TypeScript',
    forksCount: 1589,
    stargazersCount: 21553,
    ratingAverage: 88,
    reviewCount: 4,
    ownerAvatarUrl: 'https://avatars2.githubusercontent.com/u/4060187?v=4',
  },
  {
    id: 'rails.rails',
    fullName: 'rails/rails',
    description: 'Ruby on Rails',
    language: 'Ruby',
    forksCount: 18349,
    stargazersCount: 45377,
    ratingAverage: 100,
    reviewCount: 2,
    ownerAvatarUrl: 'https://avatars1.githubusercontent.com/u/4223?v=4',
  },
  {
    id: 'django.django',
    fullName: 'django/django',
    description: 'The Web framework for perfectionists with deadlines.',
    language: 'Python',
    forksCount: 21015,
    stargazersCount: 48496,
    ratingAverage: 73,
    reviewCount: 5,
    ownerAvatarUrl: 'https://avatars2.githubusercontent.com/u/27804?v=4',
  },
  {
    id: 'reduxjs.redux',
    fullName: 'reduxjs/redux',
    description: 'Predictable state container for JavaScript apps',
    language: 'TypeScript',
    forksCount: 13902,
    stargazersCount: 52869,
    ratingAverage: 0,
    reviewCount: 0,
    ownerAvatarUrl: 'https://avatars3.githubusercontent.com/u/13142323?v=4',
  },
];

const ItemSeparator = () => <View style={styles.separator} />;

const RepositoryList = () => {
  return (
    <FlatList
      data={repositories}
      ItemSeparatorComponent={ItemSeparator}
      // other props
    />
  );
};

export default RepositoryList;

لا تبدّل محتويات المتغيّر repositories، فمن المفترض أن يحتوي على كل ما تحتاج إليه لإكمال التمرين. صيّر المكوّن ReositoryList ضمن المكوّن Main الذي أضفناه سابقّا إلى الملف "Main.jsx". ستبدو قائمة الكتب المُقَّيمة مشابهةً للتالي:

reviewed_repo_list_01.png

تنسيق التطبيق

بعد أن تعرفنا على آلية عمل المكوّنات، وكيفية استخدامها في بناء واجهة مستخدم بسيطة، لا بد من الالتفات إلى طرق تنسيق مظهر هذه التطبيقات. لقد رأينا في القسم 2 أنه من الممكن في بيئة المتصفح تعريف خصائص لتنسيق المكوّنات باستخدام CSS. كما رأينا أن هناك طريقتان لإنجاز ذلك: إما التنسيق ضمن السياق inline باستخدام الخاصية style، أو عن طريق محددات التنسيق المناسبة selectors المكتوبة في ملف CSS منفصل.

ستجد تشابهًا واضحًا في الطريقة التي ترتبط بها خصائص التنسيق بمكونات React Native البنيوية والطريقة التي ترتبط بها هذه الخصائص بعناصر DOM. فمعظم مكوّنات React Native البنيوية تقبل الخاصية style، وتقبل هذه الخاصية بدورها كائنًا يحمل خصائص تنسيقٍ مع قيمها. وتتطابق في أغلب الأحيان خصائص التنسيق هذه مع مقابلاتها في CSS، مع اختلاف بسيط وهو كتابة أسماء هذه الصفات بطريقة سنام الجمل camelCase. أي ستكتب خصائص مثل "padding-top" على الشكل "paddingTop". وسيوضح المثال التالي طريقة استخدام الخاصية style:

import React from 'react';
import { Text, View } from 'react-native';

const BigBlueText = () => {
  return (
    <View style={{ padding: 20 }}>
      <Text style={{
                   color: 'blue',
                   fontSize: 24,
                   fontWeight: '700'
                  }}>
        Big blue text
      </Text>
    </View>
  );
};

بالإضافة إلى أسماء الخصائص، ستلاحظ اختلافًا آخر في المثال السابق. إذ ترتبط بخصائص CSS التي تقبل قيمًا عددية واحدات مثل px أو % أو em أو rem. بينما لا تمتلك الخصائص التي تتعلق بالأبعاد مثل width وheight وpadding وكذلك font-size في React Native أية واحدات. تمثل هذه القيم العددية بيكسلات مستقلة عن الكثافة density-independent pixels. ولو تساءلت عن خصائص التنسيق المتاحة لمكوّن بنيوي محدد، ستجد الجواب في أوراق التنسيق الزائفة Styling Cheat Sheet لإطار عمل React Native.

وبشكل عام، لا يعتبر تنسيق المكوًنات باستخدام الخاصية style مباشرة فكرة جيدة، لأنها ستجعل شيفرة المكونات غير واضحة. ويفضّل بدلًا عن ذلك، تعريف التنسيق خارج دالة المكوّن باستخدام التابع StyleSheet.create. يقبل هذا التابع وسيطًا واحدًا على شكل كائن يتألف بحد ذاته من كائنات تنسيق مسماة ومحددة القيم، كما ينشئ مرجعًا للمكوّن البنيوي StyleSheet من هذا الكائن. سنستعرض فيما يلي طريقة إعادة كتابة المثال السابق باستخدام التابع StyleSheet.create:

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
    container: {
        padding: 20,
    },
    text: {
        color: 'blue',    
        fontSize: 24,    
        fontWeight: '700',  
    },});
const BigBlueText = () => {
  return (
    <View style={styles.container}>      
      <Text style={styles.text}>        
          Big blue text
      <Text>
    </View>
  );
};

أنشأنا في الشيفرة السابقة كائني تنسيق مسمّيين هما: styles.container وstyles.text. يمكننا الوصول ضمن مكوّن إلى كائن تنسيق محدد بنفس الطريقة التي نصل بها إلى كائن صرف.

تقبل الخاصية style مصفوفة من الكائنات بالإضافة إلى الكائنات المفردة. وتتالى كائنات التنسيق في المصفوفة من اليسار إلى اليمين، وبالتالي سيكون للتنسيقات التي تظهر لاحقًا أولوية التنفيذ. كما يمكن تطبيق ذلك بشكل عودي recursive، إذ يمكن على سبيل المثال، أن تحتوي مصفوفة على مصفوفة أخرى لكائنات التنسيق وهكذا. إن كانت إحدى قيم المصفوفة غير صالحة أو كانت "null" أو "undefined"، سيتم تجاهل هذه القيم. وهكذا يصبح من السهل تعريف تنسيقات شرطية كتلك التي تُبنى على أساس قيمة خاصية مثلًا، وإليكم مثالًا عن ذلك:

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  text: {
    color: 'grey',
    fontSize: 14,
  },
  blueText: {
    color: 'blue',
  },
  bigText: {
    fontSize: 24,
    fontWeight: '700',
  },
});

const FancyText = ({ isBlue, isBig, children }) => {
  const textStyles = [
    styles.text,
    isBlue && styles.blueText,
    isBig && styles.bigText,
  ];

  return <Text style={textStyles}>{children}</Text>;
};

const Main = () => {
  return (
    <>
      <FancyText>Simple text</FancyText>
      <FancyText isBlue>Blue text</FancyText>
      <FancyText isBig>Big text</FancyText>
      <FancyText isBig isBlue>
        Big blue text
      </FancyText>
    </>
  );
};

استخدمنا في الشيفرة السابقة العامل && في العبارة condition && exprIfTrue. سيعطي تنفيذ هذه العبارة قيمة "experIfTrue" إن كانت قيمة "condition" هي "true"، وإلا سيعطي القيمة "condition" والتي ستأخذ في هذه الحالة القيمة "false". هذه الطريقة المختصرة شائعة جدًا ومفيدة جدًا. كما يمكنك استخدام العامل الشرطي الثلاثي ":?" كالتالي: condition ? exprIfTrue : exprIfFalse

سمات بواجهة مستخدم متناسقة

لنبقى في مجال تنسيق التطبيقات لكن من منظورٍ أوسع قليلًا. لقد استخدم معظمنا تطبيقات متنوعة، وربما سنتفق أن ما بجعل واجهة المستخدم جيدة هو تناسق العناصر. فمظهر مكوّنات واجهة المستخدم كحجم الخط وعائلته ولونه ستتبع أسلوبًا متناسقًا. ولإنجاز ذلك لابد من ربط قيم خصائص التنسيق المختلفة بمعاملات، وهذا ما يعرف ببناء السمات theming.

ربما يكون مصطلح "بناء السمات" مألوفًا لمستخدمي مكتبات بناء واجهات المستخدم المشهورة مثل Bootstrap وMaterial UI. وعلى الرغم من اختلاف طرق بناء السمات، إلا أنّ الفكرة الرئيسية ستبقى دائمًا استخدام المتغيرات مثل colors.primary بدلًا من الأرقام السحرية مثل 0366dd# عند تعريف التنسيق، وهذا ما سيقود إلى زيادة في الاتساق والمرونة.

دعونا نلقي نظرة على طريقة التنفيذ العملي لبناء السمات في تطبيقنا. سنستخدم في عملنا العديد من النصوص وبتنسيقات مختلفة مثل حجم الخط ولونه. وطالما أنّ React Native لا تدعم التنسيق العام، لا بد من إنشاء مكوّنات Text خاصة بنا لنبقي المحتوى النصي متسقًا. لنبدأ إذًا بإضافة شيفرة كائن "تهيئة التنسيق" التالية إلى الملف "theme.js" الموجود في المجلد "src":

const theme = {
  colors: {
    textPrimary: '#24292e',
    textSecondary: '#586069',
    primary: '#0366d6',
  },
  fontSizes: {
    body: 14,
    subheading: 16,
  },
  fonts: {
    main: 'System',
  },
  fontWeights: {
    normal: '400',
    bold: '700',
  },
};

export default theme;

علينا الآن إنشاء الكائن Text الفعلي الذي يستخدم قواعد تهيئة التنسيق. لننشئ إذًا الملف "Text.jsx" في المجلد "components" الذي يحتوي ملفات بقية المكوّنات، ولنضع فيه الشيفرة التالية:

import React from 'react';
import { Text as NativeText, StyleSheet } from 'react-native';

import theme from '../theme';

const styles = StyleSheet.create({
  text: {
    color: theme.colors.textPrimary,
    fontSize: theme.fontSizes.body,
    fontFamily: theme.fonts.main,
    fontWeight: theme.fontWeights.normal,
  },
  colorTextSecondary: {
    color: theme.colors.textSecondary,
  },
  colorPrimary: {
    color: theme.colors.primary,
  },
  fontSizeSubheading: {
    fontSize: theme.fontSizes.subheading,
  },
  fontWeightBold: {
    fontWeight: theme.fontWeights.bold,
  },
});

const Text = ({ color, fontSize, fontWeight, style, ...props }) => {
  const textStyle = [
    styles.text,
    color === 'textSecondary' && styles.colorTextSecondary,
    color === 'primary' && styles.colorPrimary,
    fontSize === 'subheading' && styles.fontSizeSubheading,
    fontWeight === 'bold' && styles.fontWeightBold,
    style,
  ];

  return <NativeText style={textStyle} {...props} />;
};

export default Text;

وهكذا نكون قد أنجزنا مكوّن النص الخاص بنا من حيث اللون وحجم الخط وكثافته لنستخدمه في أي مكان من تطبيقنا. يمكننا الحصول على تغييرات مختلفة في هذا المكوّن باستخدام خصائص مختلفة كالتالي:

import React from 'react';

import Text from './Text';

const Main = () => {
  return (
    <>
      <Text>Simple text</Text>
      <Text style={{ paddingBottom: 10 }}>Text with custom style</Text>
      <Text fontWeight="bold" fontSize="subheading">
        Bold subheading
      </Text>
      <Text color="textSecondary">Text with secondary color</Text>
    </>
  );
};

export default Main;

لك كامل الحرية في توسيع أو تعديل هذا المكوّن إن أردت. وقد يكون إنشاء مكوّنات نصية قابلة لإعادة الاستخدام تستخدم المكوّن Text مثل Subheading فكرة جيدة. حاول أيضًا أن توسع أو تعدل قواعد تهيئة السمة كلما تقدمنا في بناء تطبيقنا.

استخدام flexbox في تصميم واجهة التطبيق

سنعرّج أخيرًا في عرضنا لمواضيع تنسيق التطبيقات على تصميم الواجهات باستخدام flexbox. يعرف المطورون الذين ألفوا اللغة CSS أنّ flexbox لا يتعلق فقط بإطار العمل React Native، فقد يستعمل في حالات عدة لتطوير صفحات الويب أيضًا. وعمليًا لن يتعلم المطورون الذين يعرفون آلية عمل flexbox الكثير من هذه الفقرة، لكن دعونا على الأقل نراجع أو نتعلم أساسيات flexbox.

flexbox هو كيان لتصميم الواجهات يتألف من مكونين منفصلين هما: حاوية flex وضمنها مجموعة من عناصر flex. تمتلك حاوية flex مجموعة من الخصائص التي تتحكم بتوزيع العناصر. ولجعل مكوّن ما حاوية flex، لا بد من أن يمتلك خاصية التنسيق display وقد أسندت لها القيمة "flex" وهي القيمة الافتراضية لهذه الخاصية. تظهر الشيفرة التالية طريقة تعريف حاوية flex:

import React from 'react';
import { View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  flexContainer: {
    flexDirection: 'row',
  },
});

const FlexboxExample = () => {
  return <View style={styles.flexContainer}>{/* ... */}</View>;
};

ربما تكون الخصائص التالية أهم خصائص حاوية flex:

  • الخاصية flexDirection: وتتحكم بالاتجاه الذي ستأخذه عناصر flex عند ترتيبها ضمن الحاوية. وتأخذ هذه الخاصية القيم row وrow-reverse وcoulmn وcolumn-reverse. سيرتب row عناصر flex من اليسار إلى اليمين، بينما سيرتبها column من الأعلى للأسفل (القيمة الافتراضية). وستعكس القيم ذات اللاحقة reverse- اتجاه ترتيب العناصر.

  • الخاصية justifyContent: وتتحكم بمحاذاة عناصر flex على طول المحور الرئيسي للترتيب الذي تحدده الخاصية السابقة. تأخذ هذه الخاصية القيم flex-start (القيمة الافتراضية) وflex-end وcenter وspace-between وspace-around وspace-evenly.

  • الخاصية alignItems: وتقوم بوظيفة الخاصية السابقة لكن على المحور الآخر لترتيب العناصر. تأخذ هذه الخاصية القيم flex-start وflex-end وcenter وbaseline و stretch (القيمة الافتراضية).

لننتقل الآن إلى عناصر flex والتي أشرنا سابقًا على أنها محتواة ضمن حاوية flex. تمتلك هذه العناصر خصائص تتحكم بتنسيق وسلوك هذه العناصر بالنسبة لبعضها في نفس الحاوية. ولكي يكون المكوّن عنصر flex يكفي أن نجعله ابنا مباشرًا لحاوية flex:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  flexContainer: {
    display: 'flex',
  },
  flexItemA: {
    flexGrow: 0,
    backgroundColor: 'green',
  },
  flexItemB: {
    flexGrow: 1,
    backgroundColor: 'blue',
  },
});

const FlexboxExample = () => {
  return (
    <View style={styles.flexContainer}>
      <View style={styles.flexItemA}>
        <Text>Flex item A</Text>
      </View>
      <View style={styles.flexItemB}>
        <Text>Flex item B</Text>
      </View>
    </View>
  );
};

إنّ أكثر خصائص العناصر استخدامًا هي flexGrow. حيث تقبل هذه الخاصية قيمة تحدد قدرة العنصر على النمو إن دعت الحاجة. فلو كانت قيمة هذه الخاصية لكل عناصر flex في حاوية هي 1، ستتقاسم هذه العناصر المساحة المتاحة للحاوية بالتساوي. بينما لو كانت قيمتها 0 لعنصر ما، فسيأخذ المساحة التي يتطلبها محتواه فقط، وسيترك المساحة الباقية لبقية العناصر.

ننصحك بدراسة المثال النموذجي Flexbox example، والذي يشكّل مثالًا رئيسًا أكثر تفاعلية لاستخدام flexbox في تنفيذ بطاقة بسيطة لها حاشية علوية وجسم وحاشية سفلية.

إقرأ تاليًا المقالة تعرف على CSS Flexbox وأساسيات استعماله لهيكلة صفحات الويب التي تحتوي على أمثلة مصوّرة شاملة عن استخدام flex. ومن الجيد أيضًا أن تجرب خصائص flexbox ضمن أرضية العمل Flexbox Playground لترى كيفية تأثير الخصائص على تصميم الواجهة. وتذكر أنّ أسماء الخصائص في React Native هي نفسها في CSS ما عدا نقطة التسمية بأسلوب سنام الجمل، إلا أن قيم هذه الخاصيات مثل flex-start وflex-end ستبقى نفسها.

اقتباس

ملاحظة: تختلف React Native عن CSS في بعض النواحي المتعلقة باستخدام flexbox. فالإختلاف الأهم هو أنّ القيمة الافتراضية للخاصية flexDirection في React Native هي column. كما يجدر الإشارة إلى أنّ اختصارات flex لا تقبل قيمًا متعددة في React Native. لتطلع أكثر على استخدام flexbox في تطبيقات React Native راجع توثيق React Native.

التمارين 10.4 - 10.5

10.4 شريط تنقل للتطبيق

سنحتاج قريبًا إلى التنقل بين الواجهات المختلفة للتطبيق، لذلك سنحتاج إلى شريط تنقل بين هذه الواجهات. أنشئ الملف "AppBar.jsx" وضعه في المجلد "components" ثم ضع الشيفرة التالية ضمنه:

import React from 'react';
import { View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';

const styles = StyleSheet.create({
  container: {
    paddingTop: Constants.statusBarHeight,
    // ...
  },
  // ...
});

const AppBar = () => {
  return <View style={styles.container}>{/* ... */}</View>;
};

export default AppBar;

وبما أنّ المكوّن AppBar سيمنع شريط الحالة من تغطية المحتوى، يمكن إزالة خاصية التنسيق marginTop التي وضعناها للمكوّنMain في الملف "Main.jsx". ينبغي أن يضم المكوّن AppBar نافذة عنوانها "Repositories". اجعل النافذة مستجيبة للمس touchable باستخدام المكوّن TouchableWithoutFeedback، لكن لا حاجة الآن لتعالج الحدث onPress. أضف المكوّن AppBar إلى المكوّن Main ، بحيث يبقى المكوّن الأعلى في الواجهة. من المفترض أن يبدو المكوّن AppBar شبيهًا للتالي:

appbar_component_02.png

إن لون خلفية التطبيق كما تظهره الصورة السابقة هو 24292e#، لكن يمكنك استخدام اللون الذي تشاء. يمكنك أيضًا تهيئة لون خلفية التطبيق ضمن قواعد تهيئة السمة، وسيصبح تغيير اللون أمرًا سهلًا إن احتجت لذلك. ومن الجيد أيضًا فصل شيفرة النافذة الموجودة في المكوّن AppBar لتصبح ضمن مكوّن مستقل يمكن أن تسميه AppBarTab، سيسهّل ذلك إضافة نوافذ أخرى مستقبلًا.

10.5 قائمة محسنة المظهر للمستودعات المقيمة

تبدو النسخة الحالية من تطبيق قائمة المستودعات المقيَّمة كئيبة المظهر. عدّل المكوّن RepositoryListItem ليعرض أيضًا الصورة التمثيلية لمؤلف المستودع. ويمكنك إنجاز ذلك باستخدام المكوّن Image. ينبغي إظهار القيم العددية كعدد النجمات وعدد التشعبات التي تزيد عن 1000 برقم مرتبة الآلاف وبدقة فاصلة عشرية واحدة فقط تليها اللاحقة "k". فلو كان مثلًا عدد التشعبات هو 8439، ستُعرض على الشاشة القيمة "8.4k". حسِّن أيضًا المظهر الكلي للمكوّن بحيث تبدو قائمة المستودعات مشابهة للتالي:

polished_repo_list_03.png

إنّ لون الخلفية للمكوّن Main في الصورة السابقة هو e1e4e8# بينما ستظهر خلفية للمكوّن RepositoryListItem باللون الأبيض. كما يأخذ لون خلفية محدد اللغة القيمة 0366d6 #، وهي نفسها قيمة المتغيّر colors.primary في إعدادات تهيئة السمة. لا تنس الاستفادة من المكوّن Text الذي أنجزناه سابقًا. افصل المكوّن RepositoryListItem -إن دعت الحاجة- إلى مكوّنات أصغر.

مسارات التنقل في React Native

سرعان ما سنحتاج إلى آلية للانتقال بين مختلف واجهات العرض في التطبيق كواجهة المستودعات وواجهة تسجيل الدخول، وذلك عندما نبدأ بتوسيعه. وكنا قد تعلمنا في القسم 7 استخدام المكتبة React router لتنفيذ مهمة التنقل والتوجه في تطبيقات الويب.

تختلف آلية التنقل في React Native قليلًا عن التنقل في تطبيقات الويب. ويظهر الاختلاف الرئيسي في عدم القدرة على الإشارة المرجعية للصفحات من خلال العناوين "URL" التي نكتبها في شريط تنقل المتصفح، ولا يمكننا كذلك التنقل بين الصفحات التالية والسابقة اعتمادًا على الواجهة البرمجية لتاريخ تنقلات المستخدم history API في المتصفح. لكن تبقى هذه الإشكالية مسألة تتعلق بواجهة التنقل التي نستخدمها.

يمكننا في ReactNative استخدام متحكمات المسار البنيوية React Router's core بالكامل كالخطافات والمكوّنات. لكن علينا استبدال BrowserRouter الذي يستخدم في بيئة المتصفح بمقابله NativeRouter الملائم لإطار عمل React Native والذي تؤمنه المكتبة react-router-native. لنثبّت إذًا هذه المكتبة:

npm install react-router-native

سيؤدي استخدام المكتبة react-router-native إلى توقف عرض التطبيق ضمن متصفح ويب المنصة Expo، بينما ستعمل بقية طرق عرض التطبيق بشكل جيد. يمكن إصلاح هذه المشكلة بتوسيع إعدادات تهيئة المجمع webpack للمنصة Expo بحيث ينقل transpile الشيفرة المصدرية للمكتبة باستخدام Babel. ولتوسيع إعدادات التهيئة، لابدّ من تثبيت المكتبة expo/webpack-config@:

npm install @expo/webpack-config --save-dev

سننشئ بعد ذلك الملف "webpack.config.js" في المجلد الجذري للمشروع وضمنه الشيفرة التالية:

const path = require('path');
const createExpoWebpackConfigAsync = require('@expo/webpack-config');

module.exports = async function(env, argv) {
  const config = await createExpoWebpackConfigAsync(env, argv);

  config.module.rules.push({
    test: /\.js$/,
    loader: 'babel-loader',
    include: [path.join(__dirname, 'node_modules/react-router-native')],
  });

  return config;
};

أعد تشغيل أدوات تطوير لكي تُطبق إعدادات تهيئة webpack الجديدة، وستجد أن المشكلة قد حُلّت. افتح الآن الملف "App.js" وأضف المكوّن NativeRouter إلى المكوّن App:

import React from 'react';
import { NativeRouter } from 'react-router-native';
import Main from './src/components/Main';

const App = () => {
  return (
    <NativeRouter>
     <Main />
    </NativeRouter>  );
};

export default App;

بعد أن أنشأنا متحكمًا بالمسار، سنضيف أول مسار تنقل (وجهة- Route) في المكوّن Main، وذلك ضمن الملف "Main.jsx":

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Route, Switch, Redirect } from 'react-router-native';
import RepositoryList from './RepositoryList';
import AppBar from './AppBar';
import theme from '../theme';

const styles = StyleSheet.create({
  container: {
    backgroundColor: theme.colors.mainBackground,
    flexGrow: 1,
    flexShrink: 1,
  },
});

const Main = () => {
  return (
    <View style={styles.container}>
      <AppBar />
      <Switch>        
      <Route path="/" exact>          
          <RepositoryList />        
          </Route>        
      <Redirect to="/" />      
     </Switch>    
   </View>
  );
};

export default Main;

التمرينان 10.6 - 10.7

10.6 واجهة عرض تسجيل الدخول

سننفذ قريبًا نموذجًا لكي يصبح المستخدم قادرًا على تسجيل دخوله إلى التطبيق. لكن علينا قبل ذلك أن ننشئ واجهة عرض يمكن الوصول إليها من شريط التطبيق. أنشئ الملف "Signin.jsx" في المجلد "components"، ثم ضع الشيفرة التالية ضمنه:

import React from 'react';

import Text from './Text';

const SignIn = () => {
  return <Text>The sign in view</Text>;
};

export default SignIn;

هيئ مسارًا وجهته المكوّن Signin وضعه في المكوًن Main. أضف بعد ذلك نافذة عنوانها النص "Sign in" في أعلى شريط التطبيق إلى جوار النافذة "Repositories". ينبغي أن يكون المستخدم قادرًا على التنقل بين النافذتين السابقتين بالضغط على عنوان كل نافذة

اقتباس

تلميح: استخدم المكوّن Link والخاصية component العائدة له.

10.7 شريط تطبيق قابل للتمرير scrollable

طالما أننا سنضيف نوافذ جديدة إلى شريط التطبيق، فمن الجيد أن يكون قابلًا للتمرير أفقيًا عندما لا تتسع الواجهة لكل النوافذ. سنستخدم في ذلك المكوّن الأنسب لأداء المهمة وهو ScrollView. ضع النوافذ الموجودة في المكوّن AppBar ضمن المكوّن ScrollView:

const AppBar = () => {
  return (
    <View style={styles.container}>
      <ScrollView horizontal>{/* ... */}</ScrollView>    </View>
  );
};

عندما تُسند القيمة "true" إلى الخاصية horizontal العائدة للمكون ScrollView، سيجعله قابلًا للتمرير أفقيًا عندما لا تتسع الشاشة للنوافذ التي يحتويها شريط التطبيق. وانتبه إلى ضرورة إضافة خصائص تنسيق ملائمة للمكوّن ScrollView، بحيث تُرتَّب النوافذ على شكل صف "row" ضمن حاوية flex. للتأكد من إنجاز المطلوب أضف نوافذ جديدة إلى الشريط حتى لا تتسع الشاشة لها جميعًا، ولا تنس إزالة هذه النوافذ التجريبية بعد التحقق من إنجاز المهمة.

إدارة حالة النموذج

بعد أن حصلنا على حاضنة مناسبة لواجهة عرض تسجيل الدخول، ستكون المهمة التالية إنجاز نموذج لتسجيل الدخول. لكن قبل الشروع في ذلك، سنتحدث قليلًا عن النماذج وبمنظور أوسع.

يعتمد تنفيذ النماذج بشدة على إدارة الحالة. فقد يقدم لنا الخطاف useState حلًا جيدًا للنماذج الصغيرة، لكن سيكون التعامل مع الحالة بنفس الأسلوب مرهقًا عندما يغدو النموذج أكثر تعقيدًا. ولحسن الحظ سنجد مكتبات تتعايش مع بيئة React لتسهيل عملية إدارة حالة النماذج ومنها المكتبة Formik.

تعتمد المكتبة Formik على مفهومي سياق العمل context والحقل field. يؤمن سياق العمل المكوّن Formik الذي يحتوي على حالة النموذج. وتتكون الحالة من معلومات عن حقول النموذج، وتتضمن هذه المعلومات مثلًا قيم الحقول والأخطاء الناتجة عن تقييم كلٍ منها. يمكن الإشارة إلى حالة الحقول باستخدام اسم الحقل عن طريق الخطاف useField أو المكوّن Field.

لنرى كيف سيعمل الأمر من خلال إنشاء نموذج لحساب "مؤشر كتلة الجسم" عند البشر:

import React from 'react';
import { Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
import { Formik, useField } from 'formik';

const initialValues = {
  mass: '',
  height: '',
};

const getBodyMassIndex = (mass, height) => {
  return Math.round(mass / Math.pow(height, 2));
};

const BodyMassIndexForm = ({ onSubmit }) => {
  const [massField, massMeta, massHelpers] = useField('mass');
  const [heightField, heightMeta, heightHelpers] = useField('height');

  return (
    <View>
      <TextInput
        placeholder="Weight (kg)"
        value={massField.value}
        onChangeText={text => massHelpers.setValue(text)}
      />
      <TextInput
        placeholder="Height (m)"
        value={heightField.value}
        onChangeText={text => heightHelpers.setValue(text)}
      />
      <TouchableWithoutFeedback onPress={onSubmit}>
        <Text>Calculate</Text>
      </TouchableWithoutFeedback>
    </View>
  );
};

const BodyMassIndexCalculator = () => {
  const onSubmit = values => {
    const mass = parseFloat(values.mass);
    const height = parseFloat(values.height);

    if (!isNaN(mass) && !isNaN(height) && height !== 0) {
      console.log(`Your body mass index is: ${getBodyMassIndex(mass, height)}`);
    }
  };

  return (
    <Formik initialValues={initialValues} onSubmit={onSubmit}>
      {({ handleSubmit }) => <BodyMassIndexForm onSubmit={handleSubmit} />}
    </Formik>
  );
};

لا يعتبر هذا المثال جزءًا من تطبيقنا، لذا لا حاجة لإضافة شيفرته إلى التطبيق. لكن يمكنك تجريب الشيفرة في Expo Snack مثلًا، وهو محرر لشيفرة React Native على شبكة الإنترنت على غرار JSFiddle وCodePen، ويمثل منصة مفيدة لتجريب الشيفرات بسرعة. كما يمكنك مشاركة Expo Snacks مع الآخرين بتحديد رابط إلى عملك أو تضمين الشيفرة على شكل مشغّل Snack ضمن أي صفحة ويب. ولربما قد صادفت مسبقًا مشغل Snack في مادة منهاجنا أو أثناء اطلاعك على توثيق React Native.

عرّفنا في المثال السابق سياق عمل Formik ضمن المكوّن BodyMassIndexCalculator، وزودناه بالقيم الأولية وباستدعاء لإرسال محتويات النموذج submit callback. كما استخدمنا الخاصية initialValues لتزويد السياق بالقيم الأولية على شكل كائنات مفاتيحها أسماء الحقول وقيمها هي القيم الأولية المقابلة. كما تزودنا الصفة onSubmit باستدعاء إرسال محتويات النموذج، حيث ينفَّذ هذا الاستدعاء عندما تُستدعى الدالة handleSubmit شريطة أن لا تظهر أية أخطاء تقييم لمحتويات الحقول. تُستدعى الدالة التي تمثل ابنا مباشرًا للمكوّن Formik من خلال الخصائص props التي تحتوي معلومات متعلقة بالحالة، كما تحتوي أفعالًا كالدالة handleSubmit.

يحتوي المكوّن BodyMassIndexForm ارتباطات الحالة بين السياق وعناصر الإدخال النصيّة. ويُستخدم الخطاف useField للحصول على قيمة حقل أو تعديل قيمته. يقبل useField وسيطًا واحدًا هو اسم الحقل ويعيد مصفوفة من ثلاث كائنات هي [الحقل field, البيانات الوصفية meta، المُساعدات helpers]. يحتوي الكائن field على قيمة الحقل، بينما يحتوي الكائن meta على المعلومات الوصفية للحقل كرسائل الخطأ التي قد يرميها. أما الكائن الأخير helpers، فيحتوي على الأفعال التي تُستخدم لتغيير حالة الحقل كالدالة setValue. وتجدر الإشارة إلى أنّ المكوّن الذي يستخدم الخطاف لابدّ أن يكون ضمن سياق Formik. أي يجب أن يكون هذا المكوّن من أبناء المكوّن Formik.

يمكنك الاطلاع على نسخة تفاعلية عن المثال السابق باسم Formik example على موقع Expo Snack.

لقد سبب استخدام الخطاف useField مع المكوّن TextInput شيفرة مكررة. لنعزل هذه الشيفرة ونضعها في مكوّن جديد باسم FormikTextInput، ولننشئ مكوّنًا مخصصًا باسم TextInput لإضافة مظهر مرئي أفضل لعنصر الإدخال النصي. لنثبّت أولًا Formik:

npm install formik

سننشئ الآن الملف "TextInput.jsx" في المجلد "components" ونضع فيه الشيفرة التالية:

import React from 'react';
import { TextInput as NativeTextInput, StyleSheet } from 'react-native';

const styles = StyleSheet.create({});

const TextInput = ({ style, error, ...props }) => {
  const textInputStyle = [style];

  return <NativeTextInput style={textInputStyle} 
    {...props} />;
};

export default TextInput;

لننتقل إلى المكوّن FormikTextInput الذي ينشئ رابطًا لحالة Formik بالمكوّن TextInput. سننشئ الآن الملف "FormikTextInput.jsx" في المجلد "components" ونضع فيه الشيفرة التالية:

import React from 'react';
import { StyleSheet } from 'react-native';
import { useField } from 'formik';

import TextInput from './TextInput';
import Text from './Text';

const styles = StyleSheet.create({
  errorText: {
    marginTop: 5,
  },
});

const FormikTextInput = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  const showError = meta.touched && meta.error;

  return (
    <>
      <TextInput
        onChangeText={value => helpers.setValue(value)}
        onBlur={() => helpers.setTouched(true)}
        value={field.value}
        error={showError}
        {...props}
      />
      {showError && <Text style={styles.errorText}>{meta.error}</Text>}
    </>
  );
};

export default FormikTextInput;

يمكننا بعد استخدام المكوّن FormikTextInput أن نعيد كتابة المكوّن BodyMassIndexForm في المثال السابق كالتالي:

const BodyMassIndexForm = ({ onSubmit }) => {
  return (
    <View>
      <FormikTextInput name="mass" placeholder="Weight (kg)" />      <FormikTextInput name="height" placeholder="Height (m)" />      <TouchableWithoutFeedback onPress={onSubmit}>
        <Text>Calculate</Text>
      </TouchableWithoutFeedback>
    </View>
  );
};

وكما نرى سيقلص إنشاء المكوّن FormikTextInput الذي يعالج ارتباطات حالة Formik بالمكوّن TextInput الشيفرة اللازمة. ومن الجيد أن تكرر نفس العملية إن استخدَمَت نماذج Formik التي لديك مكونات إدخال بيانات.

التمرين 10.8

10.8 نموذج تسجيل الدخول

أنجز نموذجًا لتسجيل الدخوّل يرتبط بالمكوّن SignIn الذي أضفناه سابقًا في الملف"SignIn.jsx". يجب أن يحتوي النموذج مربعي إدخال نصيين، أحدهما لاسم المستخدم والآخر لكلمة السر. كما يجب أن يحتوي زرًا لتسليم بيانات النموذج. لا حاجة لكتابة دالة الاستدعاء onSubmit، بل يكفي إظهار قيم الحقول باستخدام الأمر console.log عند تسليم البيانات:

const onSubmit = (values) => {
  console.log(values);
};

تذكّر أن تستخدم المكوّن FormikTextInput الذي أنشأناه سابقّا. كما يمكنك استخدام الخاصية secureTextEntry في المكوّن TextInput لحجب كلمة السر عند كتابتها.

سيبدو نموذج تسجيل الدخول مشابهًا للتالي:

signin_form_04.png

تقييم النموذج

تقدم Formik مقاربتين لتقييم النماذج: دالة تقييم أو تخطيط تقييم. فدالة التقييم: هي دالة تُمرر للمكون Formik كقيمة للخاصية validate. حيث تستقبل الدالة قيم حقول النموذج كوسطاء وتعيد كائنًا يحتوى الأخطاء المحتملة الخاصة بكل حقل. أما تخطيط التقييم فيمرر إلى المكوّن Formik كقيمة للخاصية validationSchema. ويمكن إنشاء هذا التخطيط باستخدام مكتبة تقييم تُدعى Yup. لنثبّت هذه المكتبة إذًا:

npm install yup

كمثال على ما أوردنا، سننشئ تاليًا تخطيط تقييم لنموذج "مؤشر كتلة الجسم" الذي أنجزناه سابقًا. نريد أن نتحقق أنّ قيمتي كتلة الجسم والطول موجودتان، وأنهما قيم عددية. كذلك نريد أن نتحقق أنّ كتلة الجسم أكبر أو تساوي1، وأنّ الطول أكبر أو يساوي 0.5. تمثل الشيفرة التالية طريقة تنفيذ التخطيط:

import React from 'react';
import * as yup from 'yup';
// ...

const validationSchema = yup.object().shape({  
    mass: yup    
    .number()    
    .min(1, 'Weight must be greater or equal to 1')    
    .required('Weight is required'),  
    height: yup    
    .number()    
    .min(0.5, 'Height must be greater or equal to 0.5')      .required('Height is required'),});
const BodyMassIndexCalculator = () => {
  // ...

  return (
    <Formik
      initialValues={initialValues}
      onSubmit={onSubmit}
      validationSchema={validationSchema}    >
      {({ handleSubmit }) => <BodyMassIndexForm onSubmit={handleSubmit} />}
    </Formik>
  );
};

تجري عملية التحقق افتراضيًا عند حدوث أي تغيير في قيم حقول النموذج، وكذلك عند استدعاء الدالة handleSubmit. فإن أخفق التحقق، لن تُستدعى الدالة التي تمرر إلى الكائن Formik عبر الخاصية onSubmit

يعرض المكوًن FormikTextInput الذي أنشأناه سابقًا رسائل الخطأ المتعلقة بالحقل إن وقع الخطأ الموافق لها ولُمس الحقل. أي في الحالة التي يتلقى فيها الحقل تركيز الدخل ثم يفقده:

const FormikTextInput = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);

  // Check if the field is touched and the error message is present
  const showError = meta.touched && meta.error;

  return (
    <>
      <TextInput
        onChangeText={(value) => helpers.setValue(value)}
        onBlur={() => helpers.setTouched(true)}
        value={field.value}
        error={showError}
        {...props}
      />
      {/* Show the error message if the value of showError variable is true  */}
      {showError && <Text style={styles.errorText}>{meta.error}</Text>}
    </>
  );
};

التمرين 10.9

10.9 تقييم نموذج تسجيل الدخول

تحقق أنّ حقلي كلمة السر واسم المستخدم في نموذج تسجيل الدخول إجباريين. وتذكر أن لا تستدعي الدالة onSubmit التي أنجزناها في التمرين السابق إن أخفق تقييم النموذج.

سيعرض المكوّن FormikTextInput حاليًا رسالة خطأ إن حدث خطأ في الحقل الذي تم لمسه، عزز مظهر هذه الرسالة بجعل نصها أحمر اللون.

بالإضافة إلى رسالة الخطأ، إجعل إطار حقل الإدخال أحمر اللون ليشير ذلك إلى وقوع خطأ ضمنه. وتذكر أن المكوّن FormikTextInput سيعطي القيمة "true" للخاصية error العائدة للمكوّن TextInput عند حدوث أية أخطاء ضمن الحقل. يمكنك استعمال قيمة هذه الخاصية لإضافة تنسيق شرطي للمكوّن TextInput.

سيبدو شكل نموذج تسجيل الدخول عند حدوث خطأ في أحد الحقول قريبًا من الشكل التالي:

signin_form_field_error_05.png

اقتباس

ملاحظة: إن الرقم المرجعي للون الأحمر المستخدم هنا هو d73a4a#.

الشيفرة الخاصة بمنصة

من أعظم ميزات استخدام React Native هي أننا لن نهتم بالمنصة التي سيعمل عليها التطبيق سواء Android أو iOS. لكن، قد تصادفنا حالات نضطر فيها إلى تنفيذ شيفرة خاصة بمنصة محددة. من هذه الحالات مثلًا، كتابة مكوّنات بطرق مختلفة لتلائم منصات مختلفة.

يمكن الوصول إلى المنصة التي يستخدمها المستخدم من خلال الثابت Platform.OS:

import { React } from 'react';
import { Platform, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  text: {
    color: Platform.OS === 'android' ? 'green' : 'blue',
  },
});

const WhatIsMyPlatform = () => {
  return <Text style={styles.text}>Your platform is: 
    {Platform.OS}</Text>;
};

يأخذ هذا الثابت إحدى القيمتين: android أو ios. يمكن كتابة شيفرة مخصصة لمنصة أيضًا مستفيدين من التابع Platform.select. إذ يمرر للتابع كائن قد تأخذ مفاتيحه إحدى القيم التالية: ios أو android أو native أو default، ويعيد القيمة الأكثر ملائمة للمنصة التي يعمل عليها المستخدم. يمكننا إعادة كتابة المتغير styles في المثال السابق باستعمال التابع Platform.select كالتالي:

const styles = StyleSheet.create({
  text: {
    color: Platform.select({
      android: 'green',
      ios: 'blue',
      default: 'black',
    }),
  },
});

وبالإمكان أيضًا استخدام التابع Platform.select لطلب مكوّن خاص بمنصة محددة:

const MyComponent = Platform.select({
  ios: () => require('./MyIOSComponent'),
  android: () => require('./MyAndroidComponent'),
})();

<MyComponent />;

إنّ الطريقة الأكثر تقدّمًا لكتابة وإدراج مكوّنات خاصة بمنصة محددة أو أية أجزاء من الشيفرة، هي استخدام ملفات لها إحدى اللاحقتين "ios.jsx." أو "android.jsx.". وانتبه إلى إمكانية استخدام أية لاحقة يمكن للمجمع أن يميزها مثل "js.". إذ يمكن مثلًا أن ننشئ ملفات باسم "Button.ios.jsx" أو باسم "Button.android.jsx" بحيث نستطيع إدراجها في شيفرتنا كالتالي:

import React from 'react';

import Button from './Button';

const PlatformSpecificButton = () => {
  return <Button />;
};

وهكذا ستضم حزمة Android للتطبيق المكوّن المعرّف في الملف "Button.android.jsx"، بينما تضم حزمة iOS المكوّن المعرّف في الملف "Button.ios.jsx".

التمرين 10.10

10.10 خط خاص بمنصة محددة

حُددت عائلة الخط الذي نستخدمه حاليًا في تطبيقنا بأنها "System" ضمن إعدادات تهيئة السمة الموجودة في الملف "theme.js". استخدم بدلًا من الخط "system" خطًا من خطوط العائلة Sans-serif ليكون خطًا خاصًا بكل منصة. استخدم الخط "Roboto" في منصة Android والخط Arial في منصة iOS. يمكن أن تُبقي "System" مثل خط افتراضي.

هذا هو التمرين الأخير في هذا الفصل، وقد حان الوقت لتسليم التمارين إلى GitHub والإشارة إلى أنك أكملتها في منظومة تسليم التمارين. انتبه إلى وضع الحلول في القسم 2 ضمن المنظومة.

ترجمة -وبتصرف- للفصل React Native basics من سلسلة Deep Dive Into Modern Web Development.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...