[TIL] 내일배움캠프 React 과정 2023.01.03_React Navigation

Today I Learned

  • React Native 실무 심화 강의 수강

 


터미널에서 expo 셋업하기

 npx create-expo-app 폴더명

git bash에 입력

 

eas update:configure

vscode로 폴더 열고 터미널창에 입력 -> expo 홈페이지에도 프로젝트가 생성된 것을 볼 수 있음

 

eas update

배포하기

 

 

App icon 변경하기

  • icon.png -> IOS 아이콘
  • adaptive-icon -> Android 아이콘
  • splash -> 로딩 화면

Figma 링크에 들어가서 아이콘을 만들고 png 파일로 저장한 후에 assets 폴더에 동일한 파일명으로 png 파일을 교체한다.

공식문서 https://docs.expo.dev/guides/app-icons/

 

 

react-navigation이란?

react-router-dom과 비슷하게 react-navigation을 이용해서 페이지를 이동할 수 있다. 또한 기본적으로 제공하는 navigators 기능들이 다양하다.

 

설치하기

npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context

 

// navigators도 별도로 설치해야 함
npm install @react-navigation/native-stack

공식 문서 https://reactnavigation.org/

 

 

Native Stack Navigator 사용하기

<NavigationContainer>
  <Stack.Navigator>
    <Stack.Screen name="one" component={One} />
    <Stack.Screen name="two" component={Two} />
    <Stack.Screen name="three" component={Three} />
  </Stack.Navigator>
</NavigationContainer>

기본 구조. Sreen 컴포넌트의 name을 이용해서 페이지를 이동한다.

 

const One = ({ navigation: { navigate, goBack }}) => {
  return (
    <>
        <TouchableOpacity onPress={() => navigate("two")}>
          <Text>ONE</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => goBack()}>
          <Text>GoBackBtn</Text>
        </TouchableOpacity>
    </>
  )
};
const Three = ({ navigation: { reset }}) => {
  return (
    <>
      {/* 'one' 페이지로 이동하고 뒤로 가기 사라짐 */}
      <TouchableOpacity onPress={() => {
        reset({
          index: 0,
          routes: [{name: 'one'}]
        });
      }}>
        <Text>reset btn1</Text>
      </TouchableOpacity>

      {/* 버튼 클릭하면 'two'페이지로 이동하고
        거기서 뒤로 가기 클릭하면 'one' 페이지로 이동함  */}
      <TouchableOpacity onPress={() => {
        reset({
          index: 1, // index는 버튼 클릭했을 때 처음 나오는 화면이 무엇인지를 지정함
          routes: [{name: 'one'}, {name: 'two'}]
        });
      }}>
        <Text>reset btn2</Text>
      </TouchableOpacity>
    </>
  )
};
const Two = ({ navigation: { setOptions }}) => {
  return (
    <TouchableOpacity onPress={() => setOptions({
      title: "제목 바꾼다!"
    })}>
      <Text>setOptions</Text>
    </TouchableOpacity>
  )
};

 

강의 자료

더보기
// navigate : 지정한 스크린를 현재 스크린 앞으로 가져오기
navigation.navigate("screenName", { paramName: "paramValue" })

// goBack : 뒤로가기
navigation.goBack()

// reset : 지정한 대로 navigation state 초기화
navigation.reset({
  index: 1,
  routes: [
    {
      name: "Detail",
      params: { movieId: review.movieId },
    },
    {
      name: "Review",
      params: { review: { ...review, ...editingObj }, from },
    },
 ],
})

// setOptions: navigator의 title이나 버튼 등을 변경할 수 있음
navigation.setOptions({
	title: "변경된 제목!",
	headerBackTitle: "백버튼제목"
})

Navigation prop을 이용해서 원하는 기능을 이용할 수 있다.

  • navigate: 페이지 이동
  • goBack: 뒤로 가기
  • reset: 이전 페이지 없애고 임의로 페이지 경로 지정하기 (히스토리 초기화)
  • setOptions: 버튼 클릭 시 타이틀 변경 가능

 

<NavigationContainer>
      {/* 일괄적으로 옵션 지정 */}
      <Stack.Navigator initialRouteName='three' screenOptions={{
        headerTintColor: "blue",
        headerShown: false, // 헤더를 감춤-safeAreaVieW까지 풀린다
        headerBackTitle: "뒤로갈게요", // 뒤로가기 텍스트 수정
        presentation: "modal", // 스크린 이동방식을 카드 or 모달 방식 중 선택
        animation: "flip" // 스크린 이동 시 애니메이션 효과
      }}>
        {/* 해당 스크린에만 옵션 지정 */}
        <Stack.Screen options={{presentation: "modal",}} name="three" component={Three} />
      </Stack.Navigator>
</NavigationContainer>

Native Stack Navigator props

  • initialRouteName: 첫 화면을 결정
  • screenOptions: header의 속성을 지정할 수 있음

 

공식 문서

https://reactnavigation.org/docs/native-stack-navigator

https://reactnavigation.org/docs/navigation-prop

 

 

Bottom Tabs Navigator 사용하기

const Tabs = () => {
    return(
        <Tab.Navigator screenOptions={{
            tabBarLabelPosition: "beside-icon",
            headerTitleAlign: "center"
        }}>
            <Tab.Screen 
            options={{
                title: "영화",
                tabBarLabel: "Movies",
                tabBarIcon: ({color, size}) => (
                    <MaterialIcons name="movie-filter" size={size} color={color} />
                )
            }} 
            name="movies" 
            component={Movies}/>
        </Tab.Navigator>
    )
};

Bottom Tabs Navigator Props

  • initialRouteName: 첫 페이지 지정
  • screenOptions: 스크린 헤더, 탭의 속성을 지정함
  • sceneContainerStyle: 스크린 내용(페이지)의 속성을 지정함

 

Stack & Bottom-Tab Navigator 병합 사용하기

const Root = () => {
    return (
        <Stack.Navigator 
        screenOptions={{
            headerShown: false
        }}>
            <Stack.Screen name="Tabs" component={Tabs}/>
            <Stack.Screen name="Stacks" component={Stacks}/>
        </Stack.Navigator>
    )
};

Root.js 파일을 생성하고 Native Stack Navigator를 이용해서 Tabs와 Stacks를 스크린으로 연결한다. 이렇게 하면 헤더가 두 개가 되기 때문에 screenOptions를 이용해서 header를 하나 감춰준다.

 

const Movies = ({ navigation: { navigate }}) => {
    return(
        <View>
            <Text>무빗</Text>
            <TouchableOpacity onPress={() => navigate("Stacks", { screen: "one", params: { id: 123}})}>
                <Text>페이지 one으로 이동</Text>
            </TouchableOpacity>
        </View>
    )
};

navigate를 이용해서 페이지를 이동하거나 해당 스크린에 props를 넘길 수 있다. 오직 Screen 컴포넌트만 props로 navigation을 가질 수 있다. Screen 컴포넌트 외의 컴포넌트에서는 const navigation = useNavigation(); navigation.navigate("");을 사용한다.

 

const One = ({ route: { params }, navigation: { navigate }}) => {
    console.log(params) // { id: 123}
    return (
        <TouchableOpacity onPress={() => navigate("three")}>
            <Text>ONE</Text>
        </TouchableOpacity>
    )
};

다른 컴포넌트에서 넘긴 prams를 받을 때는 route: {params}를 이용한다.

 

 

react-navigation으로 페이지 이동하는 방법

// react-navigation (React Native 앱개발 시)
import { Button } from 'react-native';
import { Link } from "@react-navigation/native";

const Home = ({ navigation: { navigate } }) => {
	const goToAnother = () => {
		navigate("another", { paramName: "paramValue" } )
	}
	return (
		<Link to={{ screen: "another", params: { paramName: "paramValue" } }}>페이지 이동방법1</Link>
		<Button onPress={goToAnother}>페이지 이동방법2</button>
	)
}

 

 

useEffect vs useFocusEffect

useEffect

useEffect(()=>{
   // 한번만 실행
   return () => {
   // 언마운트 시 실행
   }
}, []) // dependency array가 빈배열이면 컴포넌트 마운트 후 한번만 실행

컴포넌트가 마운트(첫 렌더링)되었을 때, dependency가 변경되었을 때, 언마운트 되었을 때를 감지하여 실행. 언마운트를 하려면 navigation reset을 실행해야 한다.

 

useFocusEffect

useFocusEffect(useCallBack(()=>{
   // 컴포넌트가 현재 화면에 있으면 실행: Focus
   return () => {
   // 컴포넌트가 현재 화면에서 벗어나면 실행: Blur
   }
}, []))

컴포넌트가 화면에 지금 보이는 지(Focus), 안보이는지(Blur)를 감지하여 실행.

  • useFocusEffect는 화면에 포커스가 잡혔을 때 특정 작업을 할 수 있게 하는 Hook입니다. 만약 HomeScreen에서 DetailScreen을 띄운다면 HomeScreen이 화면에서 사라지는 게 아니라, HomeScreen 위에 DetailScreen을 쌓아서 보여주는 것입니다. 그래서 useEffect Hook을 통해서 컴포넌트가 마운트 되거나 언마운트될 때 콘솔에 텍스트를 출력한다면 DetailScreen을 띄울 때 컴포넌트가 언마운트되지 않고, 또 뒤로가기하여 HomeScreen으로 돌아왔을 때 컴포넌트가 마운트되지 않는 것을 확인할 수 있습니다.
  • 출처 https://thebook.io/080236/ch05/04/03/

 

다크 모드 구현하는 3가지 방법

{
  "expo": {
    "userInterfaceStyle": "automatic"
  }
}

가장 먼저 app.json 코드 수정하기

 

// 현재 휴대폰 디스플레이 설정이 다크모드인지 라이트모드인지 확인할 수 있음
const isDark = useColorScheme() === "dark";

 

 NavigationContainer 속성 이용하기 

export default function App() {
  const isDark = useColorScheme() === "dark";
  return (
    <NavigationContainer theme={isDark ? DarkTheme : DefaultTheme}>
      <Root />
    </NavigationContainer>
  );
};

NavigationContainer 속성 중 theme에 리액트 네이티브가 자체적으로 제공하는 Theme을 할당한다.

 

 

 styled-components의 ThemeProvider 이용하기 

 

// App.js
export default function App() {
  const isDark = useColorScheme() === "dark";
  return (
    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
      <NavigationContainer>
        <Root />
      </NavigationContainer>
    </ThemeProvider>
  );
}
// Movies.jsx
const Movies = ({ navigation: { navigate }}) => {
    return(
        <View>
            <SectionTitle>영화 추천</SectionTitle>
        </View>
    )
};

export default Movies;

const SectionTitle = styled.Text`
    font-size: 30px;
    color: ${(props) => props.theme.title}
`
// theme.js
export const lightTheme = {
    title: BLUE_COLOR,
};

export const darkTheme = {
    title: ORANGE_COLOR,
};

styled-components의 ThemeProvider를 활용한 다크모드 컨트롤. styled-component에서 props.theme에 접근할 수 있다.

 

 

 컴포넌트 자체에 스타일 넣기

export default function Tabs() {
  const isDark = useColorScheme() === "dark";
  return (
    <Tab.Navigator
      sceneContainerStyle={{
        backgroundColor: isDark ? BLACK_COLOR : "white",
      }}
      screenOptions={{
        tabBarStyle: {
          backgroundColor: isDark ? BLACK_COLOR : "white",
        },
        tabBarActiveTintColor: isDark ? YELLOW_COLOR : PURPLE_COLOR,
        headerStyle: { backgroundColor: isDark ? BLACK_COLOR : GRAY_COLOR },
        headerTintColor: isDark ? YELLOW_COLOR : BLACK_COLOR,
      }}
    >
  	...생략...
    </Tab.Navigator>
  );
}

Navigator 또는 Screen 컴포넌트 자체에 Style을 넣을 수도 있다.

 

 

Text의 numberOfLines 속성

<Text numberOfLines={3}>호호</Text>

<Text>의 numberOfLines 속성을 이용하면 텍스트가 넘쳐나지 않고 자동으로 말줄임표가 생성되도록 할 수 있다.

 

 

ScrollView 가로로 적용하기

<ScrollView horizontal> </ScrollView>

ScrollView에는 horizontal이라는 속성이 있는데 default 값이 false이다. 그래서 ScrollView 안에 horizontal 속성만 추가해 주면 된다.

 


Programmers 문제 풀기

배열 원소의 길이

나의 풀이

function solution(strlist) {
    let array = [];
    for (const str of strlist) {
        array.push(str.length)
    }
    return array
}

 

다른 사람의 풀이

function solution(strlist) {
    return strlist.map((el) => el.length)
}

map이 원래 배열을 반환하는 애니까 바로 map을 돌리 버리면 되는 거였구나...

 


회고

리액트 네이티브는 UI만 구현하는데도 쉽지가 않다. 그리고 페이지 이동이 stack도 있고 tab도 있어서 너무 헷갈린다. props는 또 왜 이렇게 많이 쓰는 거야~~ 그래도 캠프 기간 동안 네이티브를 배울 기회는 이번뿐이니까 최선을 다해서 배우기로 한다. 화이팅!!