- 코드의 재사용이 가능하도록
- 버튼, App.js 등도 컴포넌트
- App.js에서 return (<MainPage />); ==> MainPage.js도 컴포넌트화
- 컴포넌트에 데이터를 전달하는 것
- key, value의 형태 ==> content = { content }
- numberOfLines, resizeMode ...
- 컴포넌트를 반복문으로 돌릴 때에는 컴포넌트마다 고유하다는 것을 표현하기 위해 map에서의 index를 key = { i } 속성 전달 형태로 꼭 넣어야 함.
- 비구조 할당 방식으로 내려받음.
- 컴포넌트마다 데이터를 보유하고 관리할 수 있음.
- 컴포넌트에서 보유/관리되는 데이터를 상태(State)라고 부름 ==> UI = component(state)
- 상태(State)는 useState 함수로 생성하고, setState 함수로 정의/변경할 수 있음.
- 화면이 그려진 다음 가장 먼저 실행되는 함수
- useEffect( ()=>{ 실행되는 코드 } , [])
- 보통 데이터를 준비할 때 사용. 데이터를 받은 후 상태(State)에 반영 ==>
화면이 그려짐 > useEffect가 데이터를 준비 > State update... > 화면이 다시 그려짐.
- 앱에 따라 화면 맨 위 상태 바의 모습을 지정
- 해당 라이브러리를 설치해주어야 한다.
- 공식 문서 : https://docs.expo.io/versions/latest/sdk/status-bar/
- 페이지 개념, 컴포넌트 들을 페이지화 시키고, 해당 페이지끼리 이동할 수 있도록 한다.
- 외부 라이브러리를 사용해야 한다.
- React-Navigation 공식 문서 : 바로가기
- 페이지화(Main.js 와 같은 컴포넌트를 페이지로) : Stack.Screen
- 책갈피(등록하여 페이지 이동 가능하게) : Stack.Navigator
Stack.Screen 에 등록된 모든 페이지 컴포넌트는 navigation과 route 라는 딕셔너리(객체)를 속성을 넘겨 받아 사용할 수 있음.
navigation 객체가 가지고 있는 두 함수 : setOptions, navigate
- 해당 페이지의 제목 설정 : navigation.setOptions({ title: '페이지 제목' })
- 데이터 전달 없이 name 속성으로 해당 페이지로 이동 :
navigation.navigate("DetailPage")
- name 속성 전달, 두번째 인잘 데이터를 전달. route 딕셔너리로 받을 수 있음. :
navigation.navigate( "DetailPage", { title: title } )
- React Native 에서 기본적으로 제공해주는 공유 기능
- 페이지에 링크 버튼을 추가하고자 한다면, Expo에서 도구를 가져와야 한다. 즉 설치해야 한다.
설치
- expo install expo-linking
MainPage.js에서 일부를 Card.js로...
MainPage.js 에서,
...
import data from '../data.json';
import Card from '../components/Card';
...
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
{
tip.map((content,i)=>{
return (<Card content={content} key={i}/>)
})
}
</View>
MainPage.js 에서 Card.js로 Props(속성)를 넘긴다(데이터를 전달한다).
Card.js 에서, 비구조 할당 방식으로 간단하게 키 값만 꺼내서 사용하고자 한다.
...
export default function Card({content}) {
return (<View style={styles.card}>
<Image style={styles.cardImage} source={{uri:content.image}}/>
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
<Text style={styles.cardDate}>{content.date}</Text>
</View>
</View>)
}
데이터 준비 중 잠시 지나가는 Loading.js가 있다고 하자.
MainPage.js에서,
...
import React, { useState, useEffect } from 'react';
import data from '../data.json';
import Card from '../components/Card';
import Loading from '../components/Loading';
...
export default function MainPage() {
...
const [state, setState] = useState([])
// setState = [] 와 같은 의미, 아무런 데이터도 준비되어 있지 않다...
// 데이터가 없기 때문에 오류 발생할 수 있다.
const [ready, setReady] = useState(true)
// 위 함수에서 오류가 발생할 수 있기 때문에 데이터 준비를 위한 setReady 함수가 준비
// setReady = true
useEffect(() => {
setTimeout(() => {
setState(data)
setReady(false)
}, 1000)
// 1000 숫자는 1초를 뜻함. 1초 뒤에 실행되는 코드들이 담겨 있다는 의미임.
}, [])
...
return ready ? <Loading /> : (
// ready ? true : false 와 같은 삼항 연산자
// setReady = true 가 우선 준비되었으므로, true에 해당하는(전항) Loading 컴포넌트가 실행
// 1초(1000) 뒤에 useEffect가 가장 먼저 실행되면서
// setState 함수의 변수인 state에 data를 준비하면서 setReady = false로 대입
// 이후 다시 만난 삼항 연산자에서는 ready = false가 되므로,
// false에 대항하는(후항) 엘리먼트들(MainPage 컴포넌트)이 실행됨.
...
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
{
tip.map((content, i) => {
return (<Card content={content} key={i} />)
})
}
</View>
...
MainPage.js에서
...
const [cateState, setCateState] = useState([])
// cateState 변수에 카테고리에 따라 다른 꿀팁 데이터를 그때그때 저장하기 위한 상태로 만들었다.
// useState에 의해 빈 리스트([])를 생성해 놓았다.
...
useEffect(() => {
setTimeout(() => {
// 꿀팁 데이터로 모두 초기화 준비
let tip = data.tip;
setState(tip)
setCateState(tip)
setReady(false)
// setState(tip)을 통해 전체 데이터를 넣어놓는다.
// 이는 "전체보기"를 눌렀을 때 전체 꿀팁이 나타나도록 하기 위함.
// setCateState(tip)은 카테고리에 따라 달라지는 tip 데이터를
// setState(tip)에서 꺼내서 사용하기 위함
}, 1000)
}, [])
...
const category = (cate) => {
if (cate == "전체보기") {
//전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
setCateState(state)
} else {
setCateState(state.filter((d) => {
return d.category == cate
}))
}
}
// category 함수 정의
// cate == "전체보기" 가 참이 되면, state에 들어가 있는 tip(= data.tip, 즉 전체 데이터)로 접근
// 즉, 초기화 상태와 같다.
// cate 에 다른 조건이 들어가면, 그 조건에 맞는 항목을 찾아냄. (state.filter 에 의해...)
...
<ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
<TouchableOpacity style={styles.middleButtonAll} onPress={() => { category('전체보기') }}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
<TouchableOpacity style={styles.middleButton01} onPress={() => { category('생활') }}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
{/* onPress = {} 로 key, value 형태의 Props...
그 안에 함수가 정의되면서 category 함수가 실행될 코드로 들어간다.
() => { category(d) }
d 값에 해당하는 항목(카테고리)를 tip (= data.tip)에서 찾아 화면에 그려준다. */}
...
MainPage.js에서
...
import { StatusBar } from 'expo-status-bar';
...
<ScrollView style={styles.container}>
<StatusBar style="black" />
{/* StatusBar의 style : auto, inverted, light, dark ... */}
<Text style={styles.title}>나만의 꿀팁</Text>
...
StackNavigator.js를 생성한다.
StackNavigator.js에서,
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
// 성치한 스택 네비게이션 라이브러리를 가져옴.
import DetailPage from '../pages/DetailPage';
import MainPage from '../pages/MainPage';
// 페이지화 한 컴포넌트를 불러옴.
// 페이지 : DetailPage, MainPage
const Stack = createStackNavigator();
const StackNavigator = () => {
return (
// 컴포넌트들을 페이지처럼 여기게 끔 해주는 기능을 하는 네비게이터 태그를 선언
// 위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용
// Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있다.
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: "white",
borderBottomColor: "white",
shadowColor: "white",
height:100
},
//헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정
headerTitleAlign:'left',
headerTintColor: "#000",
headerBackTitleVisible: false
}}
>
{/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/}
<Stack.Screen name="MainPage" component={MainPage}/>
<Stack.Screen name="DetailPage" component={DetailPage}/>
</Stack.Navigator>
)
}
export default StackNavigator;
MainPage.js에서는 title 삭제 (또는, title 부분 주석 처리 : {/ 나만의 꿀팁 /} )
App.js도 수정해야 한다.
import React from 'react';
//이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로
//컴포넌트를 더이상 불러오지 않아도 됩니다.
// import MainPage from './pages/MainPage';
// import DetailPage from './pages/DetailPage';
import { StatusBar } from 'expo-status-bar';
//메인에 세팅할 네비게이션 도구들을 가져옵니다.
import {NavigationContainer} from '@react-navigation/native';
import StackNavigator from './navigation/StackNavigator'
export default function App() {
console.disableYellowBox = true;
return (
<NavigationContainer>
<StatusBar style="black" />
<StackNavigator/>
</NavigationContainer>);
}
MainPage.js에서,
...
export default function MainPage({navigation,route}) {
...
useEffect(()=>{
setTimeout(()=>{
//헤더의 타이틀 변경
navigation.setOptions({
title:'나만의 꿀팁'
})
//꿀팁 데이터로 모두 초기화 준비
let tip = data.tip;
setState(tip)
setCateState(tip)
setReady(false)
},1000)
},[])
...
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
{
cateState.map((content,i)=>{
return (<Card content={content} key={i} navigation={navigation}/>)
})
}
</View>
...
비구조 할당 방식으로 수정되었으며, useEffect 부위에 navigation.setOptions를 통해 제목을 강제로 바꾸었다. cateState.map 부위에도 navigation = { navigation} 으로 Props를 주어 이동이 가능해진다.
Card.js도 수정되어야 한다. Card.js에서 태그로 감싸져 있던 부분을 로 바꾸었다. 해당 부분을 버튼처럼 사용하기 위해서이다. 그러면서 onPress = { } 로 Props를 주면서 그 안에 함수를 정의하여 navigation.navigate('DetailPage')} 함수를 실행 코드로 넣었다. 이 코드로 DetailPage로 이동한다.
즉, Card.js에서,
...
export default function Card({content,navigation}){
return(
//카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
<TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}>
<Image style={styles.cardImage} source={{uri:content.image}}/>
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
<Text style={styles.cardDate}>{content.date}</Text>
</View>
</TouchableOpacity>
)
}
...
위의 수정된 Card.js에서
DetailPage로 이동할 때, MainPage로부터 넘겨받은 content도 넘겨서 보려면 Card.js를 일부 수정해야 한다.
위 Card.js에서,
...
<TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',content)}}>
...
위 Card.js와 비교하면, navigation.navigate('DetailPage') 부분이 navigation.navigate('DetailPage', content)로 바뀌어 있다.
DetailPage.js도 수정되어야 한다.
DetailPage.js에서
...
export default function DetailPage({navigation,route}) {
...
const [tip, setTip] = useState({
"idx":9,
"category":"재테크",
"title":"렌탈 서비스 금액 비교해보기",
// 초기 데이터를 setTip 함수이 tip 변수에 넣어둔다.
// 초기 컴포넌트의 상태값을 설정(의미 없는 값이라도...)해 줘야 오류가 없다.
...
useEffect(()=>{
console.log(route)
navigation.setOptions({
title:route.params.title,
// Card.js에서 navigation.navigate 함수를 쓸 때 content를 넘겼다.
// content는 딕셔너리 그 자체이므로 route.params에 그대로 남겨온다.
// 즉, route.params 는 content 다.
headerStyle: {
backgroundColor: '#000',
shadowColor: "#000",
},
headerTintColor: "#fff",
})
setTip(route.params)
},[])
const popup = () => {
Alert.alert("팝업!!")
}
...
<TouchableOpacity style={styles.button} onPress={()=>popup()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
...
페이지 공유 기능을 위해서는 import { Share } from "react-native"; 와 같이 Share를 import 해야 한다.
DetailPage에서 공유 버튼을 추가하기 위해서는 DetailPage.js를 수정해야 한다.
DetailPage.js에서
...
import { StyleSheet, Text, View, Image, ScrollView, TouchableOpacity, Alert, Share } from 'react-native';
...
const share = () => {
Share.share({
message:`${tip.title} \n\n ${tip.desc} \n\n ${tip.image}`,
});
}
...
<TouchableOpacity style={styles.button} onPress={()=>share()}><Text style={styles.buttonText}>팁 공유하기</Text></TouchableOpacity>
...
DetailPage에 링크 버튼을 추가하기 위해서는 DetailPage.js를 수정해야 한다.
DetailPage.js에서,
...
import * as Linking from 'expo-linking';
...
const link = () => {
Linking.openURL("https://spartacodingclub.kr")
}
...
<TouchableOpacity style={styles.button} onPress={()=>link()}><Text style={styles.buttonText}>외부 링크</Text></TouchableOpacity>
...