الغوص في React Native لتطوير Android
نشرت: 2022-03-11قبل بضع سنوات ، أخبرني أحد زملائي في العمل عن React Native. كنت متشككا جدا. لقد جادلت بأنه مجرد إطار عمل آخر مشترك بين الأنظمة الأساسية والذي لن يعمل أبدًا في الحياة الواقعية - لم أكن أعرف كثيرًا كم كنت مخطئًا.
مرت السنوات وأصبحت مهارات React Native مطلوبة بشدة. منذ أن تعلمت شيئًا جديدًا منذ فترة ، فكرت لماذا لا أجربه؟ اليوم ، أنا مدافع ضخم عن React Native.
سلبيات:
- لا يمكنك استخدام Android Studio بعد الآن
- لا يمكن استخدام React Native لكل تطبيق أو ميزة
- React Native هو إطار عمل جديد ويمكن أن يكون للتحديثات تأثير سلبي على قاعدة التعليمات البرمجية الحالية
- جافا سكريبت ليست لغة مكتوبة بدقة
- يتطلب React Native تشغيل محرك JavaScript ، مما قد يجعله أقل أداءً
الايجابيات:
- سهل التعلم
- قاعدة بيانات مشتركة بين تطبيقات Android و iOS ، مع تعديلات طفيفة فقط مطلوبة لمطابقة تجارب النظام الأساسي
- إعادة التحميل المباشر والساخن ، مما يعني عدم وجود المزيد من أوقات الإنشاء اللانهائية
- المكونات الأصلية لكلا النظامين الأساسيين
- يتحسن باستمرار
- المجتمع المتزايد بنشاط
- عدد ضخم من المكتبات
- يلغي Expo الحاجة إلى امتلاك جهاز Mac لتطويره لنظام iOS
- انخفاض في موارد العمالة - بينما قد لا تزال بحاجة إلى بعض التطوير الأصلي لنظام Android / iOS ، إلا أنه نادر الحدوث.
يمكنني المضي قدمًا ، لكن دعنا نتوقف هنا وننتقل إلى موضوع منشور المدونة هذا. في هذا المنشور ، سأقوم بإنشاء أربعة تطبيقات تعمل بنظام التشغيل Android من React Native:
- عداد أساسي بأزرار لزيادة العداد وإنقاصه
- تطبيق للبحث في r / pics subreddit
- صفحة تسجيل دخول عامة
- تطبيق لتصفح subreddit r / pics
IDE
كما ذكرت أعلاه ، لا توجد طريقة يمكننا من خلالها استخدام Android Studio لتطوير React Native. نحن بحاجة إلى بديل. يمكن تطوير React Native على الأرجح في أي محرر نصوص حديث متاح هناك (Atom ، VS Code ، Sublime Text ، Brackets ، إلخ.) ولكن نظرًا لأننا نأتي بتجربة Android Studio ، فإن المفضل لدي هو WebStorm الذي تم إنشاؤه بواسطة نفس الشركة. على الرغم من أن WebStorm عبارة عن تطبيق مدفوع (129 دولارًا سنويًا) ، يمكنك تثبيت إصدار الوصول المبكر منه. EAP build of WebStorm مجاني ومستقر تمامًا. إذا كنت تفضل محررًا مجانيًا تمامًا ، فانتقل إلى VS Code. حتى أن Microsoft طورت مكونًا إضافيًا مدهشًا من React Native له وهو يعمل بشكل جيد للغاية.
إنشاء مشروع جديد
المتطلبات الأساسية: تثبيت Android SDK و Node و React Native على جهاز الكمبيوتر الخاص بك.
هناك طريقتان لإنشاء مشروع React Native جديد.
- الطريقة التقليدية. إما باستخدام واجهة المستخدم الرسومية لـ WebStorm أو باستخدام الأمر الطرفي:
react-native init AwesomeToptalProject
- أسهل طريقة "إنشاء تطبيق React Native".
create-react-native-app AwesomeToptalProject
إذا كنت تستخدم create-react-native-app
، فسيتم تمهيد المشروع الذي تم إنشاؤه باستخدام expo. لن أخوض في التفاصيل ، ولكن هذا يعني بشكل أساسي أنك لست بحاجة إلى تثبيت Xcode لتشغيل التطبيق على iOS. من الأسهل أيضًا أن يكون العميل دائمًا على اطلاع دائم من خلال وظائف expo.io وبعض الميزات الأخرى. لكن لا يمكنك إضافة رمز أصلي. وبالتالي ، إذا كنت تطور ميزة معينة ، فقد تحتاج إلى إخراج تطبيق من المعرض واستخدام مشروع React Native عادي بدلاً من ذلك.
سأستخدم الطريقة الأولى.
لندير المشروع. أولاً ، افتح برنامج محاكاة أو قم بتوصيل الجهاز. إذا قمت بإنشاء المشروع باستخدام WebStorm GUI ، فكل ما عليك فعله هو اختيار التكوين. في الزاوية العلوية اليمنى من WebStorm ، انقر فوق القائمة المنسدلة الموجودة على يسار الزر "تشغيل" ، واختر Android ، وانقر فوق "تشغيل" أو "تصحيح الأخطاء". إذا أنشأت المشروع باستخدام Terminal ، فيمكنك إما إضافة تكوين React Native جديد أو تشغيله باستخدام Terminal:
cd AwesomeToptalProject react-native run-android
إذا سارت الأمور على ما يرام ، فسيتم استقبالك بالشاشة التالية:

الهيكل والإعداد الأساسي
العناصر البارزة داخل المشروع هي:
- android - مشروع Android Studio مهيأ مسبقًا بدعم React Native.
- مشروع ios - Xcode مهيأ مسبقًا بدعم React Native.
- node_modules - مجلد يحتوي على إطار عمل React Native ومكتبات جافا سكريبت أخرى.
- index.js - نقطة دخول لتطبيقنا.
- App.js - تم تحميل المكون الأولي.
لنقم بإنشاء مجلد "src" داخل جذر المشروع ونقل App.js هناك. سيتعين عليك تحديث عمليات استيراد index.js لمطابقة موقع App.js الجديد.
import App from './src/App';
احذف كل شيء داخل App.js والصق هذا الرمز:
import React from 'react'; import {Text} from 'react-native'; export default class App extends React.Component { render() { return ( <Text>Hello TopTal</Text> ); } }
الكود الذي قمنا بلصقه واضح ومباشر. أنشأنا React.Component
App
يتجاوز طريقة العرض render()
ويعيد مكون Text
. React.Component
هي الفئة الأساسية لبناء واجهة المستخدم باستخدام JSX. يجعل المعدل export default
الفئة public
.
نحن الآن جاهزون لبدء تصميم مخططنا.
تخطيط مع Flexbox
إن Flexbox
مشابه لـ LinearLayout
، لكن Flexbox
يتجاوز قدرات LinearLayout
.
هذا المقتطف من JSX:
<View style={{ flex: 1, flexDirection: 'row' }}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD' }}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2' }}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7' }}/> </View>
يعرض هذا التخطيط:
بينما هذا XML:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
يجعل هذا:
كود JSX يبدو مألوفًا ، أليس كذلك ؟! دعنا ننشئ "قاموسًا" (أو ورقة غش) للتخطيطات التي تستخدم الشكل المتشابه في JSX و Android XML.
يرجى ملاحظة أن الوظائف ليست بالضرورة متساوية. أحاول مساعدة المبتدئين في React Native لفهم فكرة نظام التخطيط في React Native. يرجى الرجوع إلى الدليل الرسمي للحصول على معلومات مفصلة.
ضع في اعتبارك خاصية JSX هذه:
flex: 1
إنه يعادل هذا:
android:layout_width="match_parent" android:layout_height="match_parent"
هذا المقتطف من JSX:
<View style={{flex: 1, flexDirection: 'row'}}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7'}}/> </View>
وهذا XML:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
كلاهما يولد هذا الناتج:
وبالمثل ، فإن JSX هذا:
<View style={{flex: 1, flexDirection: 'column'}}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7'}}/> </View>
وهذا XML:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
قم بتوليد هذا:
لتحقيق الموضع الصحيح داخل الحاوية ، سنستخدم بشكل شائع مزيجًا من خصائص flexDirection
و alignItems
و justifyContent
.
هذا JSX:
<View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7'}}/> </View>
وهذا XML:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
سينتج هذا التخطيط:
هذا JSX:
<View style={{flex: 1, flexDirection: 'column', justifyContent: 'center'}}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7'}}/> </View>
وهذا XML
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="vertical"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
سينتج هذا التخطيط:
هذا JSX:
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'center'}}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7'}}/> </View>
وهذا XML:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="horizontal"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
سينتج هذا التخطيط:
هذا JSX:
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center'}}> <View style={{ width: 100, height: 100, backgroundColor: '#9575CD'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#7E57C2'}}/> <View style={{ width: 100, height: 100, backgroundColor: '#673AB7'}}/> </View>
وهذا XML:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#9575CD" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#7E57C2" /> <View android:layout_width="100dp" android:layout_height="100dp" android:background="#673AB7" /> </LinearLayout>
سينتج هذا التخطيط:
الدرس الذي يجب تعلمه: إذا كان لدينا flexDirection: row',
فستعمل alignItems works on Y axis and
بضبط المحتوى works on X axis. Everything is mirrored for
works on X axis. Everything is mirrored for
flexDirection: العمود '- يؤثر justifyContent
على المحور Y وتؤثر alignItems
على المحور Y.
justifyContent: "بداية مرنة" | الجاذبية = "ابدأ | يسار" |
محاذاة العناصر: "بداية مرنة" | الجاذبية = "ابدأ | يسار" |
justifyContent: "نهاية مرنة" | الجاذبية = "النهاية | اليمين" |
محاذاة العناصر: "نهاية مرنة" | الجاذبية = "النهاية | اليمين" |
جربها بنفسك. اضبط قيمة justifyContent
على space-around
، space-between
، space-evenly
.
إدارة الدولة
لتحديث حالة التطبيق ، ستستخدم متغير state
React. عندما يتم تحديث state
، يتم استدعاء render()
.
انسخ الرمز أدناه إلى تطبيقك:
import React from 'react'; import {Button, Text, View} from 'react-native'; export default class App extends React.Component { /* Initialize state object with variable 'number' set to 0 and variable name with value of empty string */ state = {number: 0}; render() { return ( <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', flex: 1, padding: 20 }}> <Button title='Decrement' color='#e57373' onPress={() => this.decrement()}/> <Text> {/* Text will be automatically updated whenever state.number has changed value */} Value = {this.state.number} </Text> <Button title='Increment' color='#64B5F6' {/* Set listener for click */} onPress={() => this.increment()}/> </View> ); } //Declaration of decrement function decrement() { //To update the state we need invoke this.setState //with new value for variable 'number' this.setState({number: this.state.number - 1}); } increment() { this.setState({number: this.state.number + 1}); } }

إذا قمت بالنقر فوق الزرين DECREMENT و INCREMENT ، فسترى أنه يتم تحديث النص تلقائيًا من أجلك. ليست هناك حاجة لاستخدام textView.setText("Value " + number)
.

تأتي وظائف الحالة في متناول اليد لأسباب متعددة:
- سهولة الحصول على القيمة - تعرف دائمًا أين وكيف تحصل على قيمة متغير معين.
- البيانات ليست مرتبطة بأدوات محددة.
- وجود أدوات متعددة تعتمد على تغيير القيمة المشتركة.
إنشاء تطبيق بحث عن / r / pics
الآن بعد أن تعاملنا مع الأساسيات ، فلنقم بإنشاء شيء أكثر تعقيدًا: تطبيق بحث عن / r / pics. يوفر Reddit نقطة نهاية JSON API مباشرة ، لذلك لن نضطر إلى القيام بمهام جانبية للحصول على المصادقة حتى تعمل بشكل صحيح.
يوفر React Native واجهة برمجة تطبيقات إحضار مضمنة. نظرًا لأن معظمنا معتاد على الأرجح على التعديل التحديثي وسهولة استخدامه ، فسنستخدم أكسيوس . يمكنك تثبيت axios من خلال أمر طرفي
باستخدام yarn
(الطريقة المفضلة لدي):
yarn add axios
أو باستخدام npm
:
npm install axios
الواردات:
import React from 'react'; import { TextInput, View, Text, Image, ActivityIndicator, Platform, StyleSheet } from 'react-native'; import axios from 'axios'; TextInput = EditText, ActivityIndicator = ProgressBar Platform - Platform detecting module StyleSheet - Module for creating stylesheets and moving them away from JSX
أنشئ الفصل:
export default class App extends React.Component { }
لتهيئة الحالة. سنحتاج:
- تحميل - لإظهار شريط التقدم.
- خطأ - لإظهار ما إذا كان هناك خطأ ما قد حدث عند تقديم طلب REST API.
- imgUrl - لمعاينة الصورة التي تم البحث عنها.
- نص - استعلام البحث.
state = {text: '', loading: false, error: null, imgUrl: null};
أضف كود JSX. لدينا تخطيط عمودي مع مكونات TextInput
و Image
.
render() { return ( //Predefined style. See below <View style={styles.containerStyle}> {/* returnKeyType ~ imeOptions onSubmitEditing ~ et.OnEditorActionListener */} <TextInput style={styles.textInputStyle} placeholder="Enter text to search an image" returnKeyType='search' autoFocus={true} onChangeText={(text) => this.setState({text})} onSubmitEditing={() => this.searchPicture()}/> {/* Render error Image component if this.state.imgUrl is not equal to null */} { this.state.imgUrl && <Image source={{uri: this.state.imgUrl}} style={{flex: 1}}/> } </View> ); }
أشياء جديدة:
onChangeText={(text) => this.setState({text})} onSubmitEditing={() => this.searchPicture()} { this.state.imgUrl && <Image source={{uri: this.state.imgUrl}} style={{flex: 1}}/> }
الطريقة الأولى تقوم بعمل مشابه لـ EditText
باستخدام مكون TextWatcher
. لنكن صادقين ، إنه أجمل بكثير في React Native.
يتم استدعاء الطريقة الثانية عند الضغط على مفتاح الإرجاع على لوحة المفاتيح (وآخرون ، محرر عمل ، et.OnEditorActionListener
) بعد أن يقوم بتشغيل searchPicture()
.
يتم تقديم الصورة عندما لا تكون imgUrl
خالية أو غير محددة نظرًا لأن عامل التشغيل "&&" لا يتحقق من وجود وسيطة ثانية إذا كانت الأولى خاطئة بالفعل.
قد تتساءل عن سبب this.state.imgUrl
. حسنًا ، عند استخدام عوامل التشغيل المنطقية في JavaScript ، فإن أي شيء باستثناء "" (سلسلة فارغة) ، 0
، false
، null
، أو undefined
يكون صحيحًا. ليست هناك حاجة لفحص محدد.
searchPicture() { //Default state this.setState({loading: true, error: null, imgUrl: null}); axios.get('https://www.reddit.com/r/pics/search.json', { params: { //the get param map restrict_sr: 'on', //search only /r/pics limit: 1, //limit to one search item sort: 'new', //sort by creation date q: this.state.text //our search query } }).then(response => { //promise is resolved and 'then' block is triggered //set state with new values this.setState({ imgUrl: response.data.data.children[0] .data.preview.images[0].source.url, error: null, loading: false }) }).catch(error => {//Some error occurred //set error this.setState({error: error.message, loading: false, imgUrl: null}) }) }
ها نحن. يجب أن يعمل التطبيق كما هو متوقع الآن. أدخل سلسلة بحث واضغط على رجوع.

نظرًا لأن تطبيقنا جاهز أيضًا لعرض ActivityIndicator
والأخطاء ، فنحن بحاجة إلى إضافة المزيد من التعليمات البرمجية بعد مكون Image
:
{ //Separate method this.renderProgress() } {/* Render error Text component if this.state.error is not equal to null */} { this.state.error && <Text style={{margin: 16, color: 'red'}}> {this.state.error} </Text> }
يمكنك نقل مكونات العرض خارج طريقة render()
أيضًا:
renderProgress() { //If this.state.loading is true //return View containing a progressbar //View takes style array if (this.state.loading === true) { return ( <View style={ [styles.containerStyle, {justifyContent: 'center'}]}> <ActivityIndicator color='#e57373'/> </View> ); } }
كل ما تبقى هو الأنماط. ضعها خارج فئة App
.
const styles = StyleSheet.create({ containerStyle: { flexDirection: 'column', flex: 1, //Since React Native is cross platform //let's handle both platforms. //Add top margin to fix status bar overlap marginTop: Platform.OS === 'ios' ? 20 : 0, }, textInputStyle: { marginLeft: 16, marginRight: 16, height: Platform.OS === 'ios' ? 30 : undefined } });
يمكننا الآن إضافة المزيد من التعديلات مثل فتح لوحة المفاتيح المرنة تلقائيًا عند بدء تشغيل التطبيق.
يرجى ملاحظة أن هناك طريقة أسهل لجعل TextInput
يركز تلقائيًا ( autoFocus={true} prop
) ، ولكن من أجل هذا المثال ، لن نستخدمه.
أضف مرجعًا إلى إدخال TextInput
مع الخاصية:
ref={ref => this.searchInput = ref}
وتجاوز طريقة دورة حياة componentDidMount()
مثل هذا:
componentDidMount(){ this.searchInput.focus(); }
أعد تحميل التطبيق وستفتح لنا لوحة المفاتيح تلقائيًا.
طرق دورة حياة المكون
لقد أنشأنا مكونًا بالفعل ، لكن دعنا ننتقل إلى حياة المكون.
إليك تدفق دورة حياة React:
-
constructor()
- يتم استدعاء المُنشئ دائمًا عند بدء تشغيل التطبيق -
static _getDerivedStateFromProps_(props, state)
- يتم استدعاؤها قبل التقديم وبعد التحديث. إرجاع الكائن لتحديث الحالة. العودة فارغة لتحديث أي شيء. -
render()
- التصيير مطلوب لكل صنف من مكونات React. يتم استخدامه لتقديم العرض. -
componentDidMount()
- يتم استدعاؤه بعد تقديم المكون وتركيبه في شجرة العرض. -
shouldComponentUpdate(nextProps, nextState)
- يُستدعى بعد تغيير الحالة أو الخاصيات. العودة الافتراضية إلى true بعد كل تحديث للحالة. يستدعيrender()
إذا كانت النتائج صحيحة. -
getSnapshotBeforeUpdate(prevProps, prevState)
- يتم استدعاؤه قبل الالتزام بالإخراج الذي تم تصييره مباشرةً. -
componentDidUpdate(prevProps, prevState, snapshot)
- يتم استدعاؤه بعد تقديم التحديث الجديد. لا يتم استدعاؤه بعدrender()
. -
componentWillUnmount()
- يتم استدعاؤه قبل إلغاء تحميل العنصر وتدميره.
مكونات قابلة لإعادة الاستخدام
نحتاج غالبًا إلى إنشاء مكونات قابلة لإعادة الاستخدام عند العمل في المشروع. هناك طريقتان لإنشاء المكون:
- إنشاء صنف يمتد إلى
React.Component
. يجب استخدام هذه الطريقة إذا كنا بحاجة إلى طرق دورة الحياة. - عن طريق كتابة دالة تقوم بإرجاع View من أجل بناء جملة أبسط.
نظرًا لأننا أنشأنا بالفعل فئات المكون ، فلنقم بإنشاء وظيفة لهذا المثال.
افترض أننا بحاجة إلى تناظرية لـ <CardView>
. قم بإنشاء مجلد "عام" ضمن دليل ./src
.
قم CardView.js
.
import React from "react"; import {View} from "react-native"; export default CardView = (props) => { return ( //Style will be merged from default containerStyle //and props.style. props.style attributes will override //values if parameters are same. <View style={{...styles.containerStyle, ...props.style}}> {/* props.children contain subviews add this line if the component is container */} {props.children} </View> ); }; const styles = { containerStyle: { borderRadius: 4, margin: 5, padding: 5, elevation: 5, shadowColor: 'black', shadowRadius: 5, shadowOpacity: 0.5, shadowOffset: {width: 0, height: 3}, backgroundColor: 'white' } };
نموذج باستخدام تخطيط LoginForm
الجديد الخاص CardView
:
import React from "react"; import {TextInput, Platform, Button, StyleSheet} from "react-native"; import CardView from "../common/components/CardView"; export default class LoginForm extends React._Component _{ render() { return ( //Override default style <CardView style={{ borderRadius: 4, backgroundColor: '#fff' }}> <TextInput placeholder="Email" style={styles.textInputStyle}/> <TextInput placeholder="Password" style={styles.textInputStyle} secureTextEntry={true}/> <Button color="#841584" title="Login" onPress={() => console.log("onLoginPress")} buttonStyle={styles.buttonStyle}/> </CardView> ); } } const styles = StyleSheet.create({ buttonStyle: { elevation: 5, height: 40 }, textInputStyle: { padding: 10, //Additional params to make //iOS inputs prettier ...Platform.select({ ios: { borderRadius: 2, marginTop: 5, backgroundColor: '#eeeeee' } }) } });
قم باستيراد فئة LoginForm
في فئة App
ولفها باستخدام View
<View style={{flex: 1, justifyContent: 'center'}}> <LoginForm/> </View>
إذا قمت بتعديل المعلمات في الأنماط ، يمكنك الحصول على شيء يبدو أجمل بكثير.
التنقل
يعد التنقل إلى المشاهد المختلفة جزءًا أساسيًا لمعظم التطبيقات. سننشئ تطبيق متصفح Reddit / r / pics.
يعد إنشاء التنقل في React Native أمرًا سهلاً إلى حد ما.
المتطلبات الأساسية
- تثبيت
react-navigation
معyarn
أوnpm
- تثبيت
npm
معyarn
أوaxios
لنبدأ بإنشاء مكونين مختلفين.
ملاحظة: يجب أن يكون معظم الكود أدناه مألوفًا لك بالفعل. سوف ألصق الفصل بأكمله.
PictureList.js:
import React from 'react'; import { ActivityIndicator, FlatList, Image, Text, TouchableHighlight, View } from "react-native"; import axios from "axios"; import CardView from "../common/CardView"; export default class PictureList extends React.Component { state = {loading: true, error: null, posts: null}; componentDidMount() { axios.get('https://www.reddit.com/r/pics.json') .then(response => { this.setState({ posts: response.data.data.children, loading: false }) }).catch(error => { this.setState({ error: error.message, loading: false }) }) } render() { return ( <View style={{flex: 1, justifyContent: 'center'}}> // FlatList ~ ListView // data - DataSource for the List // renderItem - function returns View item // keyExtractor - Unique id for items {this.state.posts && <FlatList data={this.state.posts} renderItem={this.renderItem.bind(this)} keyExtractor={(item) => (item.data.id + '')}/>} {this.state.loading && <ActivityIndicator size="large" color="#f4511e"/>} </View> ); } navigateToPicture(title, url) { this.props.navigation.navigate('PicturePreview', { 'title': title, 'url': url }) } renderItem(item) { //Destructuring values from item //Read more 'ES6 destructuring' const {data} = item.item; const {title} = data; const {url} = data.preview.images[0].source; return ( //Clickable view <TouchableHighlight onPress={() => this.navigateToPicture(title, url)}> {/Reusing our CardView/} <CardView> <Image style={{height: 150}} source={{uri: url}}/> <Text style={{padding: 5}}>{title}</Text> </CardView> </TouchableHighlight> ) } }
PicturePreview.js
:
import React from 'react'; import {Image} from "react-native"; export default class PicturePreview extends React.Component { //Destructure navigation //Set title to header static _navigationOptions = ({navigation}) => ({ title: navigation.state.params.title }); render() { const {url} = this.props.navigation.state.params; return (<Image style={{flex: 1}} source={{uri: url}}/>) } }
سيتم استدعاء خيارات navigationOptions
تلقائيًا بواسطة React-Navigation.
الآن دعنا ننتقل إلى App.js
ملاحظة: هناك العديد من أنواع التنقل في React-Navigation. اليوم ، سنركز على StackNavigation
. يرجى الرجوع إلى الموقع الرسمي للحصول على معلومات مفصلة.
import React from 'react'; import {createStackNavigator} from "react-navigation"; import PictureList from "./components/PictureList"; import PicturePreview from "./components/PicturePreview"; export default class App extends React.Component { render() { return ( <Router/> ); } } //Customize the header_ const NavigationOptions = { headerTintColor: '#fff', headerStyle: { backgroundColor: '#f4511e', } }; //Create the router. const Router = createStackNavigator({ //Name the screen 'PictureList': { //Link the Component screen: PictureList, //Additional navigation options navigationOptions: { title: '/r/pics Browser', ...NavigationOptions } }, 'PicturePreview': { screen: PicturePreview, navigationOptions: NavigationOptions } }, { //Root initialRouterName: 'PictureList' } );
كما ترى ، كل ما نحتاج إليه هو إنشاء موجه تنقل وجعل التطبيق يعرضه. إذا سارت الأمور على ما يرام ، فسنحصل على تطبيق متصفح Reddit / r / pics.
ذكري المظهر:
iOS:
رد الفعل الأصلي مدهش!
منذ أن بدأت البرمجة ، كانت لديّ تجارب تطوير محمولة بحتة. لكن يمكنني الآن كتابة أي شيء باستخدام React: الهاتف المحمول وسطح المكتب والويب.
إذا قررت البدء في تطوير تطبيقك المدهش التالي باستخدام React Native ، فستجد أنه يحتوي على المراوغات وبعض الأخطاء هنا وهناك ، ولكن React Native وظيفي للغاية ومثالي لمعظم المشاريع.