الوحدة 2 · التخطيط والتنسيق
Flexbox بإعدادات الجوّال الافتراضية
20 دقراءة 4 داملأ الفراغات
الدرس في لمحة
رتّب صفوف القوائم والشاشات في Trailhead باستخدام flexbox، مع معرفة القيم الافتراضية على الجوّال (الاتجاه العمودي، وعدم الانكماش، والتمدّد) التي تفسّر معظم التخطيطات المنضغطة أو الفائضة.
- 1الـ flexbox نفسه، بقيم افتراضية مختلفة
- 2flex: 1، الخاصية التي ستكتبها أكثر من غيرها
- 3بناء صفّ الرحلة
- 4التموضع المطلق والالتفاف
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
هذا خطأ تخطيط يواجهه كل مطوّر ويب في أسبوعه الأول مع React Native: صفّ فيه أيقونة وعنوان وسهم يبدو مثاليًا مع “Ridge Loop”، وينكسر مع “Old Mill Creek Trail to Eagle Point Lookout”. يختفي السهم خلف الحافة اليمنى. لا عيب في معرفتك بالـ flexbox؛ كل ما في الأمر أن flexbox في React Native يبدأ من قيم افتراضية مختلفة.
الـ flexbox نفسه، بقيم افتراضية مختلفة
يرتّب React Native كل View باستخدام flexbox (عبر Yoga، محرّك تخطيط يطبّق المواصفة). لا يوجد display: block ولا inline؛ فكل View حاوية flex. أربع قيم افتراضية تختلف عن الويب:
| الخاصية | الافتراضي على الويب | الافتراضي في React Native |
|---|---|---|
flexDirection |
row |
column |
flexShrink |
1 |
0 |
alignContent |
normal |
flex-start |
flex |
اختصار لعدّة خصائص | رقم واحد |
أول اثنتين تفسّران معظم المفاجآت. الأبناء يتكدّسون عموديًا ما لم تقل flexDirection: 'row'، ولا ينكمشون حين تنفد المساحة، ولهذا دفع ذلك العنوان الطويل السهمَ بعيدًا.
القواعد التي تعرفها ما زالت سارية: justifyContent يعمل على المحور الرئيسي، وalignItems على المحور العرضي. في العمود (الافتراضي)، يوسّط justifyContent: 'center' عموديًا، ويوسّط alignItems: 'center' أفقيًا. انتقل إلى صف فتتبادل الخاصيتان دوريهما.
القيمة الافتراضية لـ alignItems هي stretch، فالابن في عمود يملأ العرض كاملًا ما لم تعطه عرضًا أو تغيّر alignItems. ولهذا يمتد Text له لون خلفية في عمود عادي من الحافة إلى الحافة.
flex: 1، الخاصية التي ستكتبها أكثر من غيرها
في React Native، يأخذ flex رقمًا واحدًا. وflex: 1 تعني «اكبر لتملأ المساحة المتبقية على المحور الرئيسي، ويُسمح لك بالانكماش». مكانان لا يكادان يستغنيان عنها أبدًا:
- الـ View الجذري في كل شاشة، ليملأ الشاشة بدلًا من أن يلتصق بمحتواه.
- كل أب لـ ScrollView أو FlatList، لأن حاوية التمرير تتمرّر داخل الارتفاع الذي يُعطى لها. فإن لم يحدّد أي سلف هذا الارتفاع، تكبر بحجم محتواها كله، ويُقطع الجزء السفلي دون أن تتمرّر أبدًا.
حين تظهر الشاشة كشريط رفيع في الأعلى، أو حين لا تتمرّر قائمة، اصعد في الشجرة وابحث عن الـ flex: 1 المفقود.
بناء صفّ الرحلة
هذا هو الصف الذي انكسر، بعد إصلاحه:
import { Image, StyleSheet, Text, View } from 'react-native';
export function HikeRow({ hike }: { hike: { name: string; distanceKm: number; date: string; thumbUrl: string } }) {
return (
<View style={styles.row}>
<Image source={{ uri: hike.thumbUrl }} style={styles.thumb} />
<View style={styles.body}>
<Text style={styles.title} numberOfLines={1}>{hike.name}</Text>
<Text style={styles.meta}>{hike.distanceKm} km · {hike.date}</Text>
</View>
<Text style={styles.chevron}>›</Text>
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 },
thumb: { width: 56, height: 56, borderRadius: 8 },
body: { flex: 1 },
title: { fontSize: 17, fontWeight: '600' },
meta: { fontSize: 14, color: '#5c6b62', marginTop: 2 },
chevron: { fontSize: 24, color: '#9aa79f' },
});اقرأه محورًا محورًا. المحور الرئيسي للصف أفقي؛ وalignItems: 'center' يوسّط الأبناء الثلاثة عموديًا بالنسبة إلى أطولهم (الصورة المصغّرة). للصورة المصغّرة والسهم أحجام ثابتة. أما الجسم فعليه flex: 1، فيأخذ أي عرض يتبقّى، وnumberOfLines={1} يقتطع الاسم الطويل بعلامة حذف بدلًا من دفع السهم خارج الشاشة.
أداتان أخريان تُكملان التخطيط اليومي. يسمح alignSelf لابن واحد بالخروج عن alignItems الخاصة بأبيه: زرّ “Log a hike” في عمود متمدّد يمكنه استخدام alignSelf: 'flex-end' ليستقرّ على اليمين بعرضه الطبيعي. أما aspectRatio فيحدّد حجم الصندوق انطلاقًا من بُعد واحد، وهذا مثالي للصور: أعطِ صورة الدرب width: '100%' وaspectRatio: 4 / 3، فيتبع ارتفاعها عرض الشاشة على كل هاتف دون أن تحسب أي شيء.
وحين يسيء تخطيطٌ التصرّف، تبقى أسرع حيلة لتصحيحه هي الأقدم: أعطِ الـ Views المشتبه بها لون خلفية فاقعًا مؤقتًا. سترى فورًا أي صندوق أصغر من اللازم، وأيها تمدّد، وأيها لم يحصل قط على flex: 1.
التموضع المطلق والالتفاف
كل View يكون position: 'relative' افتراضيًا، لذا يضع position: 'absolute' الابن بالنسبة إلى أبيه. هكذا تضع شارة “New” على زاوية صورة مصغّرة:
<View>
<Image source={{ uri: hike.thumbUrl }} style={styles.thumb} />
<View style={{ position: 'absolute', top: -4, right: -4, backgroundColor: '#e8590c', borderRadius: 8, paddingHorizontal: 6 }}>
<Text style={{ color: 'white', fontSize: 11, fontWeight: '700' }}>NEW</Text>
</View>
</View>ولصفّ من الوسوم (“steep”، “lake”، “dog-friendly”) يجب أن ينتقل إلى سطر ثانٍ حين تنفد المساحة، اجمع بين flexDirection: 'row' وflexWrap: 'wrap' وgap: 8. التموضع المطلق يُخرج الابن من تدفّق الـ flex كليًا، لذا استخدمه للعناصر العائمة فقط، ولا تستخدمه أبدًا لتزييف تخطيط يستطيع الـ flex التعبير عنه.
في الدرس التالي ستجعل هذه التخطيطات تصمد أمام أحجام الهواتف المختلفة، والنتوءات (notches)، والزوايا المستديرة، والفروق بين المنصّتين.
أهم النقاط
- كل View حاوية flex، والمحور الرئيسي يمتد من الأعلى إلى الأسفل افتراضيًا (
flexDirection: 'column'). justifyContentيحدّد مواضع الأبناء على المحور الرئيسي؛ وalignItemsيحدّدها على المحور العرضي، وقيمته الافتراضيةstretch.- الأبناء لا ينكمشون افتراضيًا في React Native، لذا يدفع العنوان الطويل إخوته خارج الشاشة حتى تعطيه
flex: 1. flex: 1تعني «خذ المساحة المتبقية»؛ والـ View الجذري في كل شاشة وكل أب لـ ScrollView يحتاجان إليها.- استخدم
position: 'absolute'للعناصر العائمة مثل الشارات، وflexWrap: 'wrap'معgapلصفوف الوسوم.