본문 바로가기

모바일 개발/React Native-이론

React Navigation Docs 정리(Getting Started ~ Header bar 설정)

1. React Navigation 설치 

Getting started | React Navigation

 

React Navigation

 

reactnavigation.org

2. React Navigation에 대해, 

Hello React Navigation | React Navigation

 

React Navigation

 

reactnavigation.org

우리는 먼저 가장 쉽고 기본적인 Navigator인 Stack Navigator를 통해 화면 이동하는 방법을 습득하고, 다른 종류의 네비게이터를 알아보겠다. 

네비게이터는 RN 내장 라이브러리가 아니라서 따로 설치해서 써야 한다. 각 Navigator 마다 고유 라이브러리가 다 따로 존재하니 참고 바란다. 

(1) Browser와 StackNavigator의 공통점, 차이점 

Browser의 화면 이동 원리는 Stack 자료 구조의 원리와 똑같다. <a> 태그를 통해 다른 화면으로 이동하고, 이동한 화면이 기존 화면 위에 Stack으로 쌓인다. 우리는 이 Stack에서 맨 겉의 층만 화면으로  볼 수 있다. 

위와 같이 Stack 형태로 page들이 쌓여있기 때문에, 뒤로 가기 버튼을 누르면 바로 직전의 page를 열 수 있는 것 이다.

Stack Navigator는 App 안에서 화면 이동 시 Browser와 같은 원리로 작동한다. 화면 이동을 할 때마다 이동한 화면이 기존 화면 위로 쌓인다. 그래서 뒤로 가기 버튼을 누르면 Stack 맨 겉의 층 화면이 Pop out 되고, 직전 화면으로 돌아갈 수 있는 것이다. 

 

StackNavigator와 Browser의 차이점은 Stack Navigator는 화면 이동을 할 때 쓰는 제스쳐나 애니메이션 옵션도 제공한다는 것이다. 

(2)Stack Navigator 용어 설명

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

먼저 위와 같이 네비게이션 콘테이너와 createNativeStackNavigator 함수를 라이브러리에서 수입해 와야 한다. 

const Stack = createNativeStackNavigator();

우리는 해당 함수를 Stack이라는 변수에 임의로 할당하겠다. (함수 표현식)

 createNativeStackNavigator 함수는 두 가지 속성을 반환한다. 첫째는 Navigator이며 둘째는 Screen이다. 

    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>

둘 다 react Component이고, 화면이동에 대한 환경 설정을 하는데 사용된다. 

 

먼저 NavigationContainer란 녀석으로 전체를 감싼 것을 알 수 있다. 네비게이션 콘테이너는 전체 네비게이터의 구조를 관리하며, 네비게이터의 상태를 가지고 있는 컴포넌트이다. 우리가 화면 이동을 설정할려면, 해당 Navigation Container가 모든 Navigator를 감싸고 있어야 한다. (아니면 에러남.) 그래서 사람들은 보통 화면 이동에서 가장 뿌리가 되는 js 파일 (예를 들어 App.js 나 index.js)에 NavigationContainer를 사용해 모든 Navigator들을 묶어 놓는다. 

 

첫 번째로 Navigator는 화면 이동을 담당하는 컴포넌트이다.

Navigator는 자식 컴포넌트로 무조건 Screen 컴포넌트를 하나 이상 가지고 있어야 한다.

Navigator로 감싼 자식 Screen들 사이에서만 이동이 가능하다.

 

initialRouteName - 여기다가 특정 스크린 컴포넌트의 name을 기록하면, 앱을 랜더할 때 처음으로 뜨는 화면이 해당 Screen이 된다. 해당 속성을 따로 설정해두지 않으면, Navigator에서 첫 번째로 쓰여진 Screen이 첫 화면이 된다. 

 

두 번쨰로 Screen 컴포넌트이다. 해당 컴포넌트는 이동할 화면에 대한 명세를 담당한다. route라고도 불린다. 

Navigator는 Screen 컴포넌트에 적힌 내용을 읽고 번지 수를 찾아 올바른 화면을 디바이스에 띄운다는 점에서 route라는 이름도 어울린다. 

(주의점! Screen과 Screen 컴포넌트를 구분하자. Screen은 우리가 랜더할 화면 자체를 말하고, Screen 컴포넌트는 해당 화면에 대한 명세이다.)

위에 적힌 속성들은 필수로 기입해야 하는 것들이다. 

name - 우리가 갈 화면의 이름이다. 이는 Component 속에 적힐 내용물과 이름이 달라도 된다. name은 주소로서의 역할을 한다. 후에 배우겠지만 화면 이동을 할 때는 Navigator란 네비게이션 내장 함수를 써야하는데. 이때 Navigator("Screen 컴포넌트의 name")을 치면 해당 화면으로 갈 수 있다. 

 component - name을 통해 갈 화면, 즉 내용물을 여기 기입한다. 

        <Stack.Screen name="Home" component={HomeScreen} />

이 예에서는 HomeScreen이라는 함수형 컴포넌트가 따로 존재하는 것이다. 

(3) 각 Screen에 Option 주기 (선택사항)

Option이라는 Screen 컴포넌트의 속성을 이용해,  해당 Screen으로 데이터를 추가로 보낼 수 있다. 

Options의 내용물은 객체 형태로 보내줘야 한다. 예를 들어 options = {{title : "OverView"}}는 해당 스크린의 헤더 이름을 바꾼다. default로는 Screen의 name이 헤더의 이름이었다. 해당 Options는 위의 title 처럼 약속된 key값에 대해서만 가능하다. 우리가 따로 설정하지 않아도, title에 값을 넣으면 자동으로 Screen의 헤더 text가 바뀐다. 

 

만약 데이터를 원하는 곳에 보내고 싶다면 1번 React-Context를 이용하던지, (나중에 공부 예정) 아니면 Screen 명세 시 아래와 같은 방법을 써야 한다. 

<Stack.Screen name="Home">
  {(props) => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>

위의 표현법은 Component 속성을 대신 해서 {}를 이용해 콜백함수를 사용했다. 이 방법을 통해 우리는 익숙한 길로 props들을 우리가 랜더할 화면 컴포넌트에 보낼 수 있게 되었다. (()안의 props들을 </> 안에서 속성으로 받는다. )

({item1, item2}) => <HomeScreen requirement1={item1} requirement2={item2}/>

 

 3. 화면 이동에 대해

Moving between screens | React Navigation

 

React Navigation

 

reactnavigation.org

/*HomeScreen과 DetailsScreen이 Stack.Navigator에 등록되어 있다고 가정*/

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

// ... other code from the previous section

우리는 navigation 속성의 navigate라는 함수를 이용하여 원하는 화면으로 이동할 수 있다. 

navigate("")의 괄호안에 가고 싶은 Screen의 name을 적기만 하면 된다. 

하지만 Screen 선언 부를 잘 보라! 우리는 Navigation이란 속성을 인수로 넣은 적이 없다.

      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>

하지만 Navigation이라는 속성을 쓸 수가 있다. 왜냐하면 Navigator에 소속된 Screen들에게는 자동으로 Navigation이란 속성이 들어가게 되기 때문이다. 

앞서도 말했듯이 우리는 Navigator안에 정의된 Screen들 사이에서만 이동이 가능하다. 만약 다른 컴포넌트 이름을 Navigate의 ()안에다 집어넣으면 에러가 뜰 것이다. 

 

a. 같은 화면을 Stack에 여러 번 쌓고 싶은 경우 

우리는 다른 데이터를 들고 같은 화면을 여러 번 방문 해야 하는 경우가 생길 수 있다. 일단 '다른 데이터를 들고'는 차치하고, 같은 화면을 여러 번 방문하는 방법에 대해서 알아보자. 

"어? 그냥 navigate("지금 있는 화면의 이름")으로 하면 되지 않나요? "

한번 해보라. 분명 details로 가는 버튼을 여러 번 누를 때 마다  Stack에 같은 화면이 여러 개 쌓였다면, 뒤로 가기를 눌렀을 때 details 화면이 복수로 나와야 한다. 

하지만 navigate로 작업 시, 뒤로 가기 버튼을 누르면 곧장 초기 화면인 Home으로 온다! 

 

이 이유는 다음과 같다. navigate의 대략의 뜻은 ()안의 스크린으로 이동하라이다. 따라서 이미 해당 화면에 와 있다면 해당 함수를 실행해도 작동하지 않는다! 또한 Stack에 해당 스크린이 이미 쌓여 있다면, 그 스크린위에 쌓인 화면들을 다 pop 하고 해당 스크린으로 이동할 뿐, 새로 쌓지 않는다.

 

만약 같은 화면을 여러번 방문하고 싶다면, Navigation.navigate() 대신에 Navigation.push()를 써야한다. push는 이름에서도 알 수 있듯이 Stack에다가 ()안의 화면을 쌓아라는 명령어 이다.

function DetailsScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Button
        title="Go to Details... again"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

b. 뒤로 가기 버튼/ 아예 처음으로 가기 버튼 

스크린이 2개 이상 쌓였을 시, StackNavigator는 자동으로 뒤로 가기 버튼을 제공한다. 하지만 어떤 경우에 우리는 내용물 안에 go back 버튼이 필요하기도 하고, 직전 화면 말고, 맨 처음으로 돌아가기 버튼이 필요하기도 하다. 

 

뒤로 가기 버튼을 만들고 싶다면, navigation.goBack()이라는 함수를 사용하면 된다. 해당 함수는 ()안에 뭐라고 적던 직전 화면으로 되돌아간다. 

 

만약 초기화면 (이름이 Home이라고 가정)으로 돌아가고 싶다면, 

navigation.navigate("Home")을 쓰거나, navigation.popToTop()을 쓰면 된다. 

두 명령어 다 쌓인 스택들을 다 제거하고, 맨 밑에 있는 초기화면으로 돌아가라는 뜻이다. 

 

4. 화면 이동 시 Params 보내기

Passing parameters to routes | React Navigation

 

React Navigation

 

reactnavigation.org

(1) 기본적인 절차

a. navigation.navigate()의 두 번째 인자로 객체형태로 데이터를 넣어보내면 된다. 

          navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          });

b. 우리가 두 번째 인자로 넣은 객체는 route란 객체의 params라는 멤버로 들어있다. 

(일반적인 부모 컴포넌트에서 자식 컴포넌트로 params 이동 시 props의 역할을 route.params가 대신 하는 것이다.)

function DetailsScreen({ route, navigation }) {
  /* 2. Get the param */
  const { itemId, otherParam } = route.params;
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Text>itemId: {JSON.stringify(itemId)}</Text>
      <Text>otherParam: {JSON.stringify(otherParam)}</Text>
      <Button
        title="Go to Details... again"
        onPress={() =>
          navigation.push('Details', {
            itemId: Math.floor(Math.random() * 100),
          })
        }
      />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

우리는 params를 구조 분해 할당 하여, 우리가 넣은 데이터들을 꺼내서 사용하면 된다.

route라는 속성 또한 navigation과 마찬가지로 해당 스크린이 네비게이터 안에 속해 있다면 자동으로 인수로 들어오는 속성이다. 

(2) 인수로 들어와야 하는 Params가 안 들어왔을 때 대비 (initialParams)

위의 예시에서 itemId와 OtherParams는 꼭 들어와야 한다. 왜냐하면 우리가 해당 인수를 꺼내서 <Text> 태그에서 사용하고 있기 때문이다. 없으면 에러난다. 

근데 깜빡하고 안 보내기 마련이다. 이런 경우를 대비해 해당 스크린의 부모 컴포넌트에서 속성으로 initialParams를 설정하면, 만약 자식 컴포넌트로 해당 인수가 들어오지 않을 경우 initialParams가 대신 쓰인다. 

<Stack.Screen
  name="Details"
  component={DetailsScreen}
  initialParams={{ itemId: 42 }}
/>

Details로 화면 이동 시 itemId가 들어오지 않으면 , 42라는 값이 대신 쓰인다. 

(3) Screen 자식 컴포넌트에서 Params의 값 update 하기

 우리가 이미 해당 화면에 와있는데, 특정 Params를 고쳐야 하는 순간이 있다. 주사위 던지기라고 생각해보자. 

다른 화면의 주사위던지기! 버튼을 누르고 주사위 던지는 화면으로 왔다. 화면은 이미 6이 띄워져 있지만, 해당 화면의 '주사위 한번 더 던지기' 버튼을 누르면 해당 화면의 값이 또 한번 바뀌어야 한다. 

이때 유용하게 쓸 수 있는 것이 navigation.setParams({바꿀 값의 이름: 바꾸는 내용}) 이다. 

function HomeScreen({ route, navigation }) {
  const { itemId } = route.params;
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Text>itemId: {JSON.stringify(itemId)}</Text>
      <Button
        title="Update param"
        onPress={() =>
          navigation.setParams({
            itemId: Math.floor(Math.random() * 100),
          })
        }
      />
    </View>
  );
}

이 함수는 홈 스크린에 표시되는 랜덤 수를 updateParam 버튼을 눌러 계속 바꿔주고 있다.

(4) 이전 화면으로 Params 보내는 절차 

언뜻 보면 어려운 로직이 들어갈 것 같지만, 제일 쉽다. navigate 함수 기억하는가? 우리가 초기 화면으로 돌아가고 싶었다면, navigate("Home")을 써주면 되었다. 이는 Home 위에 쌓여있던 다른 Screen들을 전부 pop하고 그 화면으로 돌아간다. 

우리는 이때 여기다가 params만 더해서 보내주면 된다. 

function CreatePostScreen({ navigation, route }) {
  const [postText, setPostText] = React.useState('');

  return (
    <>
      <TextInput
        multiline
        placeholder="What's on your mind?"
        style={{ height: 200, padding: 10, backgroundColor: 'white' }}
        value={postText}
        onChangeText={setPostText}
      />
      <Button
        title="Done"
        onPress={() => {
          // Pass and merge params back to home screen
          navigation.navigate({
            name: 'Home',
            params: { post: postText },
            merge: true,
          });
        }}
      />
    </>
  );
}

여기서는 navigate()의 ()안의 정보를 인자 하나에 다 쓰는 새로운 방법을 보여줬지만, 뜻하는 바는 같다. 

(5) Nested 된 Screen으로 params 보내기(네비게이터 속 네비게이터의 스크린으로)

Nested는 이중이라는 뜻이다. 여기서는 Navigator 속 Navigator가 하나 더 있는 것이다. 따라서 우리는 1층이 아니라 지하 1층에 존재하는 Screen에게 params를 보내고 싶은 것이다.

navigation.navigate('Account', {
  screen: 'Settings',
  params: { user: 'jane' },
});

여기서 Account는 Navigator 속의 Navigator이다. 두 번째 인자인 객체는 Account가 Params로 받을 객체이다. 

우리는 Account가 받을 Params안에 params를 또 써줬다. params도 nested 해서 보내주면 된다는 것이다. 

이때 Account 안의 Screen이 복수 일 수 있으므로 해당 params를 보낼 Screen의 이름도 같이 적어줘야 한다. 

** 참고 사항 TextInput 태그에 관하여**

textInput의 속성에 대한 설명
multiline -> default는 false인데, true로 변경하면, 여러 줄 입력 가능
onChangeText
-> 사용자의 입력이 바뀌면 호출 되고, 
사용자 입력으로 인해 바뀐 텍스트를 
하나의 문자열 단락으로 콜백 함수에게 보낸다. 
사용자가 칠 때마다 지금까지 쳤던 텍스트 전부를 인수로 보낸다. 
native value -> 기본값

value -> 기본값이 주어진 경우, 해당 값과 일치한 값을 가지게 된다.

 

5. 헤더 환경 설정

Configuring the header bar | React Navigation

(1) params를 이용해 Screen Header의 title 바꾸기 

a. 그냥 screen의 속성인 Options 사용하여 바꾸기 

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'My home' }}
      />
    </Stack.Navigator>
  );
}

b. params 이용하여 바꾸기. 

이는 화면 이동시 navigate 함수의 두 번째 인자로 들어온 값들을 부모 컴포넌트의 Options 속성이 받아서 이용하는 원리이다.

/*HomeScreen 내부*/
navigation.navigate("profile", {name : "It is a profile"})

/*-----------------------------------------------------------------*/

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'My home' }}
      />
      <Stack.Screen
        name="Profile"
        component={ProfileScreen}
        options={({ route }) => ({ title: route.params.name })}
      />
    </Stack.Navigator>
  );
}

이를 통해 Params로 보내는 값은 Screen의 자식 컴포넌트 부모 컴포넌트 둘 다 listening 하고 있음을 알 수 있다. 

(2) 만약 이미 스크린에 들어왔는데, 해당 스크린의 옵션을 바꾸고 싶다면? 

/* Inside of render() of React class */
<Button
  title="Update the title"
  onPress={() => navigation.setOptions({ title: 'Updated!' })}
/>

setParams와 마찬가지로 setOptions를 이용해 Screen의 Options 객체 내부 갑들을 update 할 수 있다. 

헷갈리지 말자! setParams는 이미 랜더된 화면에 들어온 데이터를 변경하는 것이고, setOptions는 랜더된 화면의 Screen Options를 바꾸는 것이다. 

(3) Hedaer의 스타일을 변경하는 Option의 key 값들 

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          title: 'My home',
          headerStyle: {
            backgroundColor: '#f4511e',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        }}
      />
    </Stack.Navigator>
  );
}

headerStyle은 스크린의 Header를 감싸고 있는 View에 적용되는 style 속성
headerTintColor: 
헤더 제목과 뒤로가기 버튼이 이거에 적용 받는다. 
헤더스타일 바깥에서 기술해줘야 한다. 
headerTitleStyle -> 해더 제목의 font, 폰트 굵기, 헤더 텍스트 스타일을 바꿀 수 있는 것 이것도 headerstyle 바깥에서 기술 해줘야함. 

(4) 모든 스크린에 동일한 옵션 주는 방법 

Navigator의 속성 중 하나인 ScreenOptions 안에 Option을 설정하면, 해당 네비게이터 내의 모든 Screen들에게 같은 Options이 적용된다. 

물론 특정 Screen에게는 맞춤형 Option을 적용할 수  있다. ScreenOptions와 Options 사이에서 Options가 더 우선 순위가 높기 때문에 스크린 컴포넌트에서 따로 Options를 정의하면 그게 우선 적용된다.

function StackScreen() {
  return (
    <Stack.Navigator
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}
    >
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'My home' }}
      />
    </Stack.Navigator>
  );
}

 

(5) 내가 커스터마이징한 헤더로 스크린 헤더 바꾸기 

function LogoTitle() {
  return (
    <Image
      style={{ width: 50, height: 50 }}
      source={require('@expo/snack-static/react-native-logo.png')}
    />
  );
}

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerTitle: (props) => <LogoTitle {...props} /> }}
      />
    </Stack.Navigator>
  );
}

Header로 쓸 컴포넌트를 작성하고, 해당 컴포넌트를 Screen 컴포넌트의 Options 속성 안에 headerTitle의 값으로 집어넣으면 된다. 

 

여기서 왜 title의 value가 아니라 headerTitle의 value로 집어넣는지 의문이 들 수 있다. 

그 이유는 headerTitle이 따로 정의가 안되었을 때 기본 값으로 하는 기능이 title에 정의된 text를 헤더 제목으로 띄우는 <Text> 태그를 랜더하는 것이다. 따라서 headerTitle이란 key값이 title보다 상위 개념임을 알 수 있다.