1. (클로닝) 프로젝트 대상 및 목표: "당근마켓 (앱)"
: React Native 기술을 활용하여 당근 마켓 앱을 클로닝
: 여러가지 상태 관리 Tools적용 및 비교(Context, MobX, Redux)
: 개선되거나 추가되었으면 하는 기능 추가아이디어 구현 (새상품 최저가 검색 기능)
: 전화번호 본인인증 및 위치기반 관련 기능 적용 및 구현2. 팀 구성 : 프론트 1명, 백엔드 1명
3. 프로젝트 기간: 3/17 ~ 4/2
4. 프로젝트 범위
- 진입화면(3)
- 휴대폰 본인인증 화면(4, 5) : firebase 연결
- 동네인증(6): 위치인식 및 키워드 검색 필터링, 주소 API 연결
- 메인화면(1): Top 메뉴 및 Bottom Navigation
- 상세화면(2): 이미지 슬라이드, 동적 라우팅
- 동네 선택 모달창(7)
- 키워드 검색 화면(8)
- 카테고리 선택 화면 (9)
- 채팅화면(10)
아래는 컴포넌트와 navigator들의 관계를 한눈에 파악할 수 있도록, 시각화 한 구조도(형광색 표시 부분이 navigator에서 routing 되는 지점)
+ 💡 추가 아이디어 화면 💡
아이디어 배경
: 당근마켓 앱을 사용하다보면, 중고제품의 가격이 적정 가격인지를 판단하기 위해서 보통 앱을 빠져나가 온라인 쇼핑몰에서 새상품 최저가 가격을 알아본 후, 중고제품의 가격 적정여부를 판단하고 구매결정하고 있음. 따라서, 이에 대한 기능을 앱 내에서 직접 제공한다면 사용자의 편의 향상에 도움될것
구현 내용
: 위 사진에서 보는 바와 같이, 매 상품박스 안에는 "새상품 최저가 검색"이라는 버튼이 있고, 이를 클릭하면, 제품명(모델명)기반 웹 크롤링 결과 중 최저가순 내림차순으로 2~3개 정도의 결과를 하단 공간이 열리면서 보여지도록 구현해보았음5. 업무진행 방법
- 백엔드-프론트엔드 간의 원활한 내용 공유 및 일정 관리를 위하여, Notion과 Slack을 적극 활용하였음
6. 사용기술 스택
- FrontEnd
:React Native
,Hooks
,State Management(Context, Redux, MobX)
,Navigatore
,Expo
- 기타 Tools
:Figma
,diagrams
,Notion
,Slack
SafeAreaView
- https://github.com/th3rdwave/react-native-safe-area-context#usage
<SafeAreaProvider>
는 앱의 최상단에 1개만 위치- 하위위 screen중에서 필요한 부분에
<SafeAreaView>
를 기재하면, 노치나 statusbar 등(insets)을 피해서 UI랜더링 (plus, useSafeAreaInsets(hook)을 사용할 수도 있음)- 현재 ios 11 이상에서만 적용가능함
React Native와 React 차이점 정리
Reactr Native Navigator의 LifeCycle
Navigation
- https://reactnavigation.org/docs/navigating
- navigation 안의 모든 screen(컴포넌트)으로 props 내에 1. navigation 과 2. route 항목이 전달되고, 1번은 screen 이동시 사용되는 navigation()를 포함하고 있고, route 안에는 param안에 전달 데이터를 가지고 있음.
디바이스 윈도우 크기 값 가져오기
https://docs.expo.io/versions/v40.0.0/react-native/usewindowdimensions/
Problem 1
: navigator에서 자동으로 screen에 내려주는 props내 route에는 실제 무엇이 담겨있을까?
Solution 1
여기서 console.log(route)를 찍어보니, 아래와 같이 나옴
=> navigator 하위 스크린의 속성을 모두 route 안에 담아서 보내고 있음
Problem 2
: mockData를 fetch로 가져오려고 했지만, 로컬 경로 설정이 까다로웠음.
이렇게 하면 못가져오는듯..Solution 2
: 백방으로 알아봤지만, React에서처럼 Fetch로 가져오는 것은 불가능한 것 같음
: 대신, 아래와 같이 require 로 읽어오니 해결const getListData = () => { const data = require('../../data/mock_productList.json'); setProductListData(data.data); };
Problem 3
: Screen 헤더와 백버튼 부분을 커스터 마이징하기 위해서는 보이지 않도록 처리해야하는 문제가 있었음
Solution 3
- 아래오 같이 screen의 headerShown 속성 값을 false로 하면 간단하게 해결 가능
<Stack.Screen name="productDetail" component={ProductDetail} options={{ headerShown: false, }} />
Problem 4
: 한참 레이아웃을 짜다보니, 하단의 탭바를 특정 스크린에서는 보이지 않도록 해야 했음
: 상세화면에서는 아래 네이게이션 바가 사라져야 하는데 잘 안됨..Solution 4
: 처음 네이게이션 구조는 Home 아래 ProductDetail 있었는데, 상위 네비게이션을 stack으로, 하위를 tab으로 바꾸면서(아래처럼) 조정하여 해결!
: 네비게이션 구조를 처음부터 잘 기획해야 할 듯...
Problem 5
: 화면 하단의 스크롤에 따라 영향을 받지 않고 고정된 부분을 만들어야 했음 ("채팅으로 거래하기" 버튼이 들어있는 영역)
Solution 5
.... </View> <SellingProductList /> </ScrollView> <DealFooter price={productDetailData.price} /> </View>
처음에 ScrollView 안에 있었던 DealFooter를 밖으로 빼고, 상위 View를 만든 후, flex:1을 입력하니 fixed footer 식으로 구현 완료 !
Problem 6 & Solution 6 - 전화번호 본인 인증 구현
- expo 에서 전화번호 본인 인증 예시 코드
https://docs.expo.io/versions/latest/sdk/firebase-recaptcha/
Problem 7 & Solution 7- Geolocation 및 주소 api 적용하기
- gelocation(위경도 가져오기)
https://docs.expo.io/versions/latest/sdk/location/#locationinstallwebgeolocationpolyfill- 위, 경도에서 주소로 변환(주소 API)
https://www.juso.go.kr/addrlink/main.do
Problem 7 - 갑자기 ionics가 적용이 안된다? why?
: 그 동안 감탄하면서 잘 썼던 icon이 어느 순간 적용이 되지 않고 깨져 보이는 현상 발생!
오류메세지는 아래와 같음.
Solution 7
: 와 이거 땜에 이틀은 날린듯...
(큰 깨달음) - expo install로 설치하면, RN 공식문서에 있는 라이브러리를 npm 명령어로 install 할때랑 다른 버전이 설치됨(가급적, expo 명령어로만 설치해야, 상호 충돌 등을 막을 수 있음)
- 따라서, React Native cli로 개발할 경우는, 해당 공식문서에 있는 라이브러리를 인스톨, expo로 개발하는 경우는 expo install 명령어로만 가급적 설치할 것!!
- 또한, npm intall과 yarn install 은 혼용하지 말것 !! (경고 메세지 뜸)
Problem 8 - header 버튼으로 navigation.navigate() 사용하기
: 해당 스크린 컴포넌트에서는 navigation을 props로 받아 사용 가능했지만, header options 속성 내에 들어가는 커스텀 헤더안의 버튼에서는 navigation 함수를 어떻게 사용하는지 몰라 헤맸음
Solution 8
- 결국에는 아래와 같이, options를 함수 형태로 변경하고, 인자로서 navigation을 전달해준다음 불러와서 사용할 수 있었음
options={({ navigation }) => ({ title: '전화번호 인증', headerBackTitleVisible: false, headerLeft: () => ( <TouchableOpacity onPress={() => { navigation.goBack(); }}> <Icon name="md-arrow-back" size={30} style={{ marginLeft: 20 }} /> </TouchableOpacity> ), })}
Problem 9 - Fetch 내 json() 실행관련 오류
: 주소 API를 연결하기 위해서 Fetch 함수를 썼는데 계속 오류메세지가 떴음
뭐가 잘 못된거지???Solution 9
- 계속 시도해봤지만 Fetch가 적용이 안되고, Axios로 해봤더니 작동하여 일단은 axios로 적용시켰음
- 추후, 원인은 분석해봐야겠지만, 아무래도 expo 환경과 관련있지 않을까 하는 추정..
Problem 10 - 모달창 배경 어둡게 하기 (overlay 추가)
: 동네 선택 화면을 모달창으로 구현해야 해서 배경을 어둡게 했어야 했는데, 전체 화면을 덮어버리는 overlay를 만들면 모달창 내에서 버튼이 클릭이 안되는 문제가 있었음
const [modalVisible, setModalVisible] = useState(false); return ( <> {modalVisible && ( <TouchableOpacity style={styles.overlay} onPress={() => console.log('click')} /> )} <Modal transparent={true} visible={modalVisible}> <View style={styles.modalView}> <Text style={styles.modalText}>남사면</Text> <Text style={styles.modalText}>대치 4동</Text> <Text style={styles.modalText}>내동네 설정하기</Text> </View> </Modal>
console 창에 찍어보지만.. 클릭이 안됨(참고, View는 onPress props이 없음)
Solution 10
: 아래처럼 TouchableOpacity를 overlay처럼 병렬로 배치하고 + 영역은 버튼 영역 아래를 차지하도록 설정하면, 버튼은 클릭되고, 그 밖(아래) 영역 클릭하면 모달창 사라짐
<Modal transparent={true} visible={modalVisible} animationType="fade"> <View style={styles.modalView}> <Text style={styles.modalText} onPress={() => console.log('1')}> 남사면 </Text> <Text style={styles.modalText}>대치4동</Text> <Text style={styles.modalText}>내 동네 설정하기</Text> </View> <TouchableOpacity style={{ width: 500, height: 600, top: 60, }} onPress={() => { setModalVisible(!modalVisible); }} /> </Modal>
Problem 11 & Solution 11 - sceen option 내에 들어가는 컴포넌트 안에서 navigation 함수 사용하기
- 아래와 같이 options를 함수형으로 수정하고, 인자로서 navigation을 전달한 후, 역시 함수형으로 된 headerTitle에서 props 인자로 TopMenus에 전달하여 TopMenus안에서 navigation 함수 사용가능
<Stack.Screen name="home" component={HomeTabs} options={({ navigation }) => ({ headerTitle: () => <TopMenus navigation={navigation} />, })} />
Problem 12 - createMaterialTopTabNavigator 설치하다가 생긴 오류
Reanimated 2 failed to create a worklet, maybe you forgot to add Reanimated's babel plugin?
: 이런 오류가 계속 뜸. 구글 찾아서 별거 다 해봤지만 안되길래..
Solution 12
: npm 이나 yarn 설치가 아닌 expo install 로 설치해봤음(https://docs.swmansion.com/react-native-reanimated/docs/installation/)
: 설치해서 보니 버전이 좀 낮은게 설치가되었고, 잘 작동됨(해결)
Problem 13 - FlatList로 mockdata 파일에서 이미지 소스(주소)가져오기
- 처음에 아래처럼, require(item.img)식으로 넣으려고만 했는데 되지 않음 ㅠ
const categoryRenderUnit = ({ item }) => { return ( <View style={styles.categoryUnit}> <Image style={styles.categoryImage} source={require(item.img)} /> <Text style={styles.categoryText}>{item.text}</Text> </View> ); };
Solution 13
- 한참을 헤메다가 겨우 찾아낸 원인은 require까지를 mock data 안에 넣었어야 했다는거 ! (아래처럼 하니 잘 불러옴)
const categoryRenderUnit = ({ item }) => { return ( <View style={styles.categoryUnit}> <Image style={styles.categoryImage} source={item.img} /> <Text style={styles.categoryText}>{item.text}</Text> </View> ); };
Problem 14 - 하위 스크린에서 상위 다른 스크린으로 전환하기
: 위에서, chatting에서 chattingRoom으로 이동하고 싶었음Solution 14
: 처음에 계속 안되서, navigator가 nesting 되어 있으면, navigation.navigate()로는 이동이 불가능하다고 오해했지만, 알고보니 ChattingStacks와 HomeTabs를 바꾸면서, App.js파일을 아래와 같이 수정하지 않아서 이동하지 못했다.
하나의<NaviatonConatiner>
안에만 들어가 있으면, 여러 navigator가 중첩되어 있더라도 navigation.navigate()로 자유롭게 이동 가능하다니~~!! 엄청 편리하다.
Problem 15 - 조건부 랜더링에서 오류..(React와 차이)
일반적으로 React에서 해왔듯 위처럼 조건부 랜더링 작성하면 오류가 뜸Solution 15
아래와 같이 작성하니 해결~!
Problem 16 - TouchableOpacity position:absolute 적용 후, onPress not working..
우 하단의 포스트 작성 버튼을 만들었지만 클릭이 안됨..
zIndex 값을 크게 줘봐도 안되었음
// 해당 부분 컴포넌트 <TouchableOpacity style={styles.createButton} // onPress={() => navigation.navigate('create')} onPress={() => console.log('clicked')} <Icon name="add" size={40} color={'white'} /> </TouchableOpacity>
//해당 부분 스타일 속성 createButton: { width: 50, height: 50, justifyContent: 'center', alignItems: 'center', position: 'absolute', bottom: 10, right: 20, backgroundColor: '#EF904F', borderRadius: 50, zIndex: 1000, },
Solution 16
- 우연히, stackoverflow에서 발견한 내용을 참고하여, TouchableOpacity의 임포트 소스를 아래처럼 변경해보았더니 잘 됨!
import { SafeAreaView, StyleSheet, Text, View, FlatList, Image, TouchableOpacity, } from 'react-native'; // import { TouchableOpacity } from 'react-native-gesture-handler';
- 자세한 내용은 여기에 => https://velog.io/@mementomori/React-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-Tool-%EC%82%AC%EC%9A%A9-%EB%B9%84%EA%B5%90-Redux-VS-MobX-VS-Context-API#%EA%B2%B0%EA%B3%BC-%ED%99%95%EC%9D%B8
- 느낀 점
: 전역(global)로 관리하는 자체가 상하위 컴포넌트간 props를 통한 데이터 전달에 비해 월등히 편의를 가져다 줄 뿐 아니라, 업데이트 되는 컴포넌트를 최소화 시켜 성능 향상에도 도움을 주기 때문에 거의 필수로 활용해애하는 Tool이라 느꼈음
context API
는 별도로 상태 값 별로 객체를 생성해서 각각 Provider를 생성해야 하는 점에서, 관리해야 하는 상태의 수가 많아지게 되면 비효율적인 방법인 것으로 보였음Redux
는 직접 세 가지를 적용해 봤을때 제일 깔끔하게 적용이 되긴 하였지만, Action과 Reducer를 따로 관리해야 하고, 관리대상 상태의 수가 많아졌을 때 컨트롤 해야하는 부분이 많아지게 될 것 같아 필요한 경우에 한하여 적절히 선택 적용함이 바람직하다고 판단되었음. 또한, 다양한 middleware 적용이 가능하고, timemachine등 history를 관리할 수 있는 강력한 기능을 갖고 있는 장점이 있어 보였음MobX
는 여러 강의 내용이나 사용 후기를 살펴본 결과, 최종적으로 가장 선호하고 추천하는 Tool이었음
실제로도 Context 및 Custom Hooks를 활용해보니, 제일 간단하게 적용이 가능했음 (별도 Provider도 불필요, 자동으로 observer가 감시하고 있는 컴포넌트 내의 observable이 변화되면 해당 컴포넌트만 리랜더 됨) 다만, 버전도 빠르게 바뀌고 있고, 적용하는 프로젝트도 다양한데다 함께 사용하는 decorator 등과 같은 Tool들이 있어, 학습하고 알아보기에는 가장 어렵고 시간이 소요되었음.