Navigation

נדבר על ניווט שנותן לנו אפשרות לעבור בין מסכים.

אחרי הפתיחה של הפרוייקט נתקין את react navigation.

npm install @react-navigation/native

נתקין 2 ספריות נוספות:

npx expo install react-native-screens react-native-safe-area-context

כדי לעבוד עם react navigation צריך קומפוננטה שעוטפת את כל האפליקציה. נשים את קוד הבסיס בקומפוננטת הפתיחה, index.jsx.

import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer children={undefined}>
    {/* Rest of your app code */}
    </NavigationContainer>
  );
}

בתוך NavigationContainer נוכל לשים קטעי קוד שמורכבים מנתיבי ניווט שונים.

Stack Navigation

העקרון של Stack Navigation הוא שכל מסך מונח על הקודם לו כמו ערימת קלפים. כשמנווטים למסך חדש, קלף חדש מונח בראש הערימה ואם חוזרים אחורה, הקף מוסר ומגלה את זה שהיה קודם בערימה. זה מאפשר למשתמשים להכנס למסך של פרטים ולחזור אחורה בקלות.

השיטה יעילה במיוחד באפליקציה עם מבנה לינארי של מסכים, למשל כשי שרשימה של פרטים ולחיצה על פריט לעוד פרטים.

יש סוגים שונים של ספריות ניווט, אנחנו נראה את Native Stack Navigator.

התקנה:

npm install @react-navigation/native-stack

נייבא את הספריה לקובץ שלנו.

import { createNativeStackNavigator } from '@react-navigation/native-stack';

ניצור מופע של createNativeStackNavigator ובתוך קומפוננטת הניווט נחבר בין הנתיבים לקומפוננטות שלהם.

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen'
import ProfileScreen from './screens/ProfileScreen'

const Stack = createNativeStackNavigator();

export default function App() {
  return (
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
  );
}

בתיקיית app ניצור תיקיית screens ובתוכה שני קבצים: HomeScreen.jsx, ProfileScreen.jsx במבנה בסיסי.

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

export default function HomeScreen({ navigation, route }) {
  return (
    <View>
      <Text>Home Screen</Text>
    </View>
  );
}

נייבא את הקומפוננטות לקובץ index. אם נריץ את האפליקציה נראה את המסך של Home. המסך שנראה הוא המסך הראשון ברשימה. אפשר לשנות את זה עם initialRouteName.

<Stack.Navigator initialRouteName='Profile'>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>

Navigation between Screens

יש לנו שני מסכים, איך עוברים ביניהם?

נוסיף כפתור במסך הבית ובלחיצה עליו נשתמש ב-navigation prop כדי לעבור לעמוד שאנחנו רוצים.

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

export default function HomeScreen({ navigation}) {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Home Screen</Text>
      <Button
        title="Go to Profile"
        onPress={() => navigation.navigate("Profile")}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  text: {
    fontSize: 24,
    fontWeight: "bold",
    marginBottom: 16,
  },
});

אפשר להשתמש ב-hooks כדי לנווט.

import { useNavigation } from "@react-navigation/native";
import React from "react";
import { View, Text, Button, StyleSheet } from "react-native";

export default function HomeScreen() {
  const navigation = useNavigation();

  return (
    <View style={styles.container}>
      <Text style={styles.text}>Home Screen</Text>
      <Button
        title="Go to Profile"
        onPress={() => navigation.navigate("Profile")}
      />
      {/* <Text style={styles.text}>Result: {route.params?.result}</Text> */}
    </View>
  );
}

השימוש ב-navigation prop כמו בדוגמא הראשונה קל ואין צורך בייבוא נוסף, אבל אפשר להשתמש בו מתוך קומפוננטת screen. השימוש ב-hook אפשרי מכל קומפוננטה ומקל על הניווט כשיש קומפוננטות מקוננות. כדאי להשתמש ב-hook רק כשיש צורך בכך.

אנחנו רואים שאחרי הניווט יש לנו חץ למעלה שמחזיר אותנו למסך הקודם.

Passing Data between Screens

נראה איך מעבירים מידע בין המסכים. navigation.navigate מקבלת פרמטרים שהיא מעבירה עם הניווט. כדי לקבל את הפרמטרים במסך שמגיעים אליו נשתמש ב-route prop.

במסך השולח:

<Button
  title="Go to Profile"
  onPress={() => navigation.navigate("Profile", { name: "Keren" })}
/>

במסך המקבל:

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

export default function ProfileScreen({ route }) {
  const {name} = route.params;

  return (
    <View style={styles.container}>
      <Text style={styles.text}>{name}'s Profile</Text>
    </View>
  );
}

const styles = StyleSheet.create({
    container: {
      flex: 1,
      alignItems: "center",
      justifyContent: "center",
    },
    text: {
      fontSize: 24,
      fontWeight: "bold",
      marginBottom: 16,
    },
  });

אפשר לקבוע ברירת מחדל לפרמטרים בקובץ בו מוגדרים הנתיבים.

export default function App() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} 
        initialParams={{
          name: "Guest"
        }}
      />
    </Stack.Navigator>
  );
}

יש אפשרות לעדכן את הפרמטר שהגיע.

export default function ProfileScreen({ route, navigation }) {
  const {name} = route.params;

  return (
    <View style={styles.container}>
      <Text style={styles.text}>{name}'s Profile</Text>
      <Button
            title="Update the name"
            onPress={() => navigation.setParams({ name: "Epic Marketing" })}
      />
    </View>
  );
}

Stack Navigation Options

נראה חלק מהאפשרויות ש-Stack Navigation תומך בהן.

כותרת

לכל מסך יש כותרת שהוא מציג. כברירת מחדל השם שמוגדר ב-name הוא זה שיופיע בראש המסך. אפשר להתאים את זה.

<Stack.Screen name="Home" component={HomeScreen} options={{
  title: "Welcome Home"
}}/>

עיצוב הכותרת

לפעמים יש צורך בעיצוב שטח הכותרת כדי להתאים אותו לעיצוב האפליקציה.

<Stack.Screen name="Home" component={HomeScreen} options={{
  title: "Welcome Home",
  headerStyle: {
    backgroundColor: "green" 
  },
  headerTintColor: "#fff",
  headerTitleStyle: {
    fontWeight: "bold"
  }
}}/>

צדדי הכותרת

אפשר להוסיף קומפוננטות מתאמות לימין ולשמאל הכותרת.

<Stack.Screen name="Home" component={HomeScreen} options={{
  title: "Welcome Home",
  headerStyle: {
    backgroundColor: "green" 
  },
  headerTintColor: "#fff",
  headerTitleStyle: {
    fontWeight: "bold"
  },
  headerRight: () => (
    <Pressable onPress={() => alert("Menu Pressed")}>
      <Text style={{color: "#fff", fontSize: 16, paddingRight: 10}}>Menu</Text>
    </Pressable>
  )
}}/>

עיצוב התוכן

יש אפשרות לעצב את התוכן של המסך.

<Stack.Screen name="Home" component={HomeScreen} options={{
  title: "Welcome Home",
  headerStyle: {
    backgroundColor: "green" 
  },
  headerTintColor: "#fff",
  headerTitleStyle: {
    fontWeight: "bold"
  },
  headerRight: () => (
    <Pressable onPress={() => alert("Menu Pressed")}>
      <Text style={{color: "#fff", fontSize: 16, paddingRight: 10}}>Menu</Text>
    </Pressable>
  ),
  contentStyle: {
    backgroundColor: 'lightgreen'
  }
}}/>

כל ההגדרות האלה חלות על המסך הספציפי שבו הן מוגדרות. אם רוצים להחיל אותן על כל המסכים צריך להעלות אותן ל-Stack.Navigator ולהשתמש ב-screenOption.

Dynamic Stack Navigator Options

ראינו איך לשנות את ההגדרות של ה-stack. לפעמים נרצה לשלוח דינמית משתנים להגדרות האלה, למשל נרצה להוסיף את שם המשתמש בכותרת.

<Stack.Screen name="Profile" component={ProfileScreen} 
  initialParams={{
    name: "Guest"
  }}
  options={({route}) => ({
    title: route.params?.name
  })}
/>

דרך נוספת היא להשתמש ב-hook בקובץ עצמו, למשל פה בקובץ מסך profile.

...
import { useLayoutEffect } from "react";

export default function ProfileScreen({ route, navigation }) {
  const {name} = route.params;
  useLayoutEffect(() => {
    navigation.setOptions({
        title: name
    })
  }, [navigation, name]);
...

השימוש ב-useLayoutEffect ולא ב-useEffect הוא כדי לתת מעבר חלק יותר. השימוש ב-hook מתאים יותר כאשר השינוי נובע מפעולות בעמוד עצמו.

Drawer Navigation

Drawer Navigation מציג תפריט שיוצא מהצד של המסך.

נתחיל בהתקנה של הספריה.

npm install @react-navigation/drawer
npm install react-native-gesture-handler react-native-reanimated

בקובץ נייבא את הספרייה react-native-gesture-handler בשורה הכי עליונה ואחר כך את ספריות הניווט. ניצור מופע של DrawerNavigator.

import 'react-native-gesture-handler'
import { createDrawerNavigator } from "@react-navigation/drawer";

const Drawer = createDrawerNavigator();

נוסיף את NavigationContainer, בתוכו את Drawer.Navigator ובתוכו Drawer.Screen לכל מסך שנרצה להוסיף.

export default function App() {
    return (
        <NavigationContainer>
            <Drawer.Navigator>
                <Drawer.Screen />
            </Drawer.Navigator>
        </NavigationContainer>
    )     
}

נוסיף 2 קבצי מסכים נוספים בתיקיית screens: את Dashboard ואת Settings. בכל אחד מהם קוד בסיסי.

import { View, Text, StyleSheet, Button } from "react-native";

export default function DashboardScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>DashboardScreen</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  text: {
    fontSize: 24,
    fontWeight: "bold",
    marginBottom: 16,
  },
});

נצרף את המסכים לתפריט הניווט שיצרנו.

export default function App() {
    return (
        <Drawer.Navigator>
            <Drawer.Screen name="Dashboard" component={DashboardScreen}/>
            <Drawer.Screen name="Settings" component={SettingsScreen} />
        </Drawer.Navigator>
    )     
}

קבלנו מסך עם אייקון של תפריט, ואם פותחים אותו אפשר לעבור בין הדפים.

אפשר לפתוח את התפריט גם דרך הקוד.

export default function DashboardScreen({ navigation }){
  return (
    <View style={styles.container}>
      <Text style={styles.text}>DashboardScreen</Text>
      <Button title="Toggle drawer" onPress={() => navigation.toggleDrawer()} />
    </View>
  );
};

אפשר גם לעבור למסך אחר בלי צורך לעבור דרך התפריט.

export default function DashboardScreen({ navigation }){
  return (
    <View style={styles.container}>
      <Text style={styles.text}>DashboardScreen</Text>
      <Button title="Toggle drawer" onPress={() => navigation.toggleDrawer()} />
      <Button title="Settings" onPress={() => navigation.jumpTo("Settings")} />
    </View>
  );
};

Drawer Navigation Options

לניווט יש אפשרויות כמו שראינו קודם.

export default function App() {
    return (
        <Drawer.Navigator>
            <Drawer.Screen
          name="Dashboard"
          component={DashboardScreen}
          options={{
            title: "My dashboard",
            drawerLabel: "Dashboard label",
            drawerActiveTintColor: "#333",
            drawerActiveBackgroundColor: "lightblue",
            drawerContentStyle: {
              backgroundColor: "#c6cbef",
            },
          }}
        />
            <Drawer.Screen name="Settings" component={SettingsScreen} />
        </Drawer.Navigator>
    )     
}

Tab Navigation

Tab Navigation מאפשר ניווט על ידי לחיצה על טאבים שבדרך כלל מוצגים בתחתית המסך.

נייבא את createBottomTabNavigator, ניצור מופע ונכין Tab לכל אחד מהמסכים שרוצים.

import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import DashboardScreen from './screens/DashboardScreen'
import SettingsScreen from './screens/SettingsScreen'
import CoursesScreen from './screens/CoursesScreen'

const Tab = createBottomTabNavigator();

export default function App() {
    return (
        <Tab.Navigator>
            <Tab.Screen name="Dashboard" component={DashboardScreen} />
            <Tab.Screen name="Settings" component={SettingsScreen} />
            <Tab.Screen name="Courses" component={CoursesScreen} />
        </Tab.Navigator>
    )     
}

נקבל בתחתית המסך שורת ניווט.

Tab Navigation Options

הגדרות כלליות

נראה את האפשרויות לשינוי בסרגל הניווט. ברמה העליונה יש את screenOptions.

export default function App() {
    return (
        <Tab.Navigator
            screenOptions={{
                //   tabBarShowLabel: false,
                tabBarLabelPosition: "below-icon",
                tabBarActiveTintColor: "purple",
            }}
        >
            <Tab.Screen name="Dashboard" component={DashboardScreen} />
            <Tab.Screen name="Settings" component={SettingsScreen} />
            <Tab.Screen name="Courses" component={CoursesScreen} />
        </Tab.Navigator>
    )     
}

tabBarLabelPosition מאפשר לשים את הטקסט במיקום שונה ביחס לאייקון. במסך טאבלט למשל אפשר לשים את הטקסט ליד האייקון ולא מתחתיו.

tabBarShowLabel מאפשר להוריד לגמרי את התוית.

tabBarActiveTintColor משנה את הצבע של ה-tab הפעיל. tabBarInactiveTintColor משנה את הצבע של הטאבים הלא פעילים.

הגדרות לכל פריט ברשימה

tabBarLabel – משנה את הטקסט שמוצג בתפריט.

tabBarIcon – משנה את האייקון בתפריט. אייקונים נמצאים באתר האייקונים של expo. לאייקון אפשר לשנות צבע וגודל.

tabBarBadge מוסיף תג מעל לאייקון.

headerShown מסיר את כיתוב הכותרת של העמוד.

export default function App() {
    return (
        <Tab.Navigator
            screenOptions={{
                //   tabBarShowLabel: false,
                tabBarLabelPosition: "below-icon",
            }}
        >
            <Tab.Screen name="Dashboard" component={DashboardScreen} 
                options={{
                    tabBarIcon: () => <FontAwesome6 name="table" size={24} color="green" />,
                }}
            />
            <Tab.Screen name="Settings" component={SettingsScreen} 
                options={{
                    tabBarLabel: "My Settings",
                    tabBarIcon: () => <Ionicons name={"person"} size={20} />,
                    tabBarBadge: 3,
                }}
            />
            <Tab.Screen name="Courses" component={CoursesScreen} 
                options={{
                    headerShown: false,
                    tabBarIcon: () => <FontAwesome6 name="list" size={24} color="black" />,
                }}
            />
        </Tab.Navigator>
    )     
}

Nesting Navigators

שילוב של הניווטים. נוסיף Stack בתוך Tab.

נגדיר בקובץ נפרד את הניווט Stack ונייצא אותו.

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen'
import ProfileScreen from './screens/ProfileScreen'

const Stack = createNativeStackNavigator();
export const AboutStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </Stack.Navigator>
  )
}

export default function App() {
  return (
    <AboutStack />
  );
}

בקובץ הראשי, שבו כרגע מוגדר ניווט Tab ניצור טאב חדש עם הקומפוננטה שיצאנו עכשיו.

...
import AboutStack from './index_stack'

const Tab = createBottomTabNavigator();

export default function App() {
    return (
        <Tab.Navigator>
            <Tab.Screen name="Dashboard" component={DashboardScreen} 
                options={{
                    tabBarIcon: () => <FontAwesome6 name="table" size={24} color="green" />,
                }}
            />
            ...
            <Tab.Screen name="About Stack" component={AboutStack} />
        </Tab.Navigator>
    )     
}

עכשיו יש טאב רביעי שלחיצה עליו תציג את המסך הראשון של ה-Stack.

יש לנו שני headers. בדרך כלל נרצה שה-Stack ישלוט בכותרת כדי שתהיה אפשרות לחזור אחורה.

<Tab.Screen name="About Stack" component={AboutStack} 
  options={{
    headerShown: false
  }}
/>

ניווט במאמר

מאמרים אחרונים

Weekly Tutorial