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는 또 왜 이렇게 많이 쓰는 거야~~ 그래도 캠프 기간 동안 네이티브를 배울 기회는 이번뿐이니까 최선을 다해서 배우기로 한다. 화이팅!!