- isInteger
- 탭바네비게이션
- repeat
- padStart(n 0)
- localeCompare
- react navigation
- medisharp
- Math.sqrt
- Math.max 반대
- javascript
- toString(2)
- Number.isInteger
- 최대공약수
- tab navigation
- 이중map
- 최고득점자
- Filter
- livub
- %와 재귀 사용하기
- GCD
- indexOF("") === 0
- 리액트 네비게이션
- map
- a.localeCompare(b)
- Math.min
- Number()
- 이중 map
- while(num)
- while과 reduce
- drawer navigation
moon palace
[Retrospect] 스택, 스크린 그리고 네비게이션 이벤트들(2) 본문
생소하기만 했던 리액트 내비게이션에 적응했다고 생각했을 무렵, 얕은 이해도가 바닥을 보이기 시작했고 나는 화면 구성을 처음부터 다시 하지 않을 수 없었다. 물론 보기에는 똑같아 보이지만 나름의 기준으로 스택 간의 기준들을 설정했고, 다행스럽게도 그 방법대로 작 적용하여 지금까지 오게 되었다.
1. 문제의 시작 - 의미없이 쌓인 스택은 코딩을 더 어렵게 만든다.
createBottomTabNavigator를 통해 하단 내비게이션 바를 만들고 나서 한 고비 넘겼다고 생각한 것일까. 굳이 핑계를 대자면 Calendar와 TimePicker 등 처음으로 시도해보려는 것들이 있고, 리액트 네이티브도 적응이 채 안되었기에 얼른 넘어가고 싶었던 것일지도 모른다. 기어이 적절한 핑계를 찾은 나의 외면은 결국 파국을 맞이했다. 온전치 못한 이해 수준으로 단순히 스크린을 추가할 때마다 createStackNavigator를 남발했고 어째 저째 작동은 했지만 결국은 화면 전환과 파라미터 전송에서 매끄럽지 못한 상황을 맞이했다.
화면 구성이 엉키자마자 디테일한 서비스 간의 전환이 매끄럽지 못했고, 어떤 화면에서는 하단 내비게이터 바가 보였다가 또 어떤 화면에서는 사라졌다. 또 어떤 화면에서는 헤더 바를 숨기는 속성이 잘 먹히다가 다음 화면에서는 컨트롤이 불가능하기도 했다. 이 모든 문제들이 '적절한 기준에 따라 나뉘지 못한 스택들의 엉킴'에 원인을 두고 있다고 생각했고 더 늦기 전에 다시 정리해야 한다는 결론에 이르렀다. 팀원들에게 빠르게 나의 실수를 인정하고 수정을 약속했고, React Navigation 공식 문서를 정독했다.
2. 해결의 실마리 - 적절한 기준에 따른 스택들의 Clustering
약올림 앱을 구성하는 스택들과 그 기준
- AppStack : 앱이 실행되면 가장 먼저 보여줄 기본 스택
- TabNavigator : 하단 탭 내비게이션이 보여야 하는 화면들로 구성된 스택
- CalendarStack : 캘린더 기능에서 파생된 스택
- AlarmStack : 알람 등록과 관련된 스택
- MedicineBoxStack : 약통 기능과 연결된 스택
- MypageStack : 개인정보를 확인하고 수정할 수 있는 스택
각 내비게이션 바는 "홈 / 캘린더 / 알람 등록 / 약통 / 마이페이지" 순으로 구성되어있다. 각 버튼들은 사용자가 경험할 수 있는 약올림의 기능들을 나타내고 있고, 그에 따라 스택을 분류해서 만들어두었다. 기능에 따라 스택들을 분류하고 나서 의외의 부분에서도 매우 큰 장점을 발견했는데, 바로 시각적인 요소를 스택 단위로 동시에 컨트롤할 수 있다는 것이었다. 물론 지금 생각해보면 한 덩어리 안에 있으니 당연한 소리지만 그때는 몸으로 먼저 부딪히고 깨우치던 순서라 그런지 그런 발견이 뒤따라 오는 것도 하나의 큰 즐거움이었다.
3. 문제에 적용 - 모든 문제가 사라졌을까?
하지만 아직도 해결하지 못한 문제가 남아있다. 스택은 말끔히 정리가 되었는데 파라미터 전달이 매끄럽지 못했다. 모든 문제가 다 해결될 거라고 생각했는데 한가지를 놓친 것이다. 공식문서대로 navigation.navigator()와 getParam()를 통해 파라미터를 받아왔으나 제대로 값이 반영되지 못했다. 한참을 고민하다가 새로고침이 되면서 값이 반영되는 것을 보았는데, 불현듯 '라이프사이클의 문제일까?' 하는 생각이 들었다. 다시 공식문서로 달려갔고 해답은 역시 공식문서에 있었다.
위 링크는 내비게이션의 라이프사이클에 대한 설명이다.(사용법도 적혀있다. 공식문서 만세!) 앱에서는 웹에서의 것과 다르다. 앱에서는 총 네 타이밍으로 구분한 라이프 사이클이 존재한다. 아래와 같다.
- willFocus : 화면이 곧 넘어온다.
- willBlur : 화면이 곧 넘어간다.
- didFocus : 화면이 막 넘어왔다.
- didBlur : 화면이 막 넘어갔다.
다행스럽게도 Mount처럼 생소한 단어는 아니다. 아이폰 유저라면 매우 직관적인 이름이라고 느낄 것 같다. 화면을 쓸어 올리면 현재 앱 말고 실행되고 있는 다른 백그라운드 앱들은 모두 블러 처리되어있는 것을 알 수 있다. 이것과 같은 개념으로 라이프 사이클을 이해하면 된다.
약올림의 경우에서 파라미터를 받아오지 못하는 문제는 didFocus를 통해 해결했다. 다음 코드와 같이 최상단의 <View> 다음에 <NavigationEvents> 태그의 속성으로 라이프사이클을 써주면 된다!
...
render() {
return (
<View
style={{
backgroundColor: 'white',
paddingTop: getStatusBarHeight() + verticalMargin,
height: window.height * 0.9,
}}
>
<NavigationEvents
onDidFocus={(payload) => {
const resultArr = this.state.alarmMedicine;
let alarmMedicineGetParam = this.props.navigation.getParam('alarmMedicine');
alarmMedicineGetParam === undefined
? this.state.alarmMedicine
: resultArr.push(alarmMedicineGetParam);
this.setState({ alarmMedicine: resultArr });
}}
/>
...
</View>
)
}
}
4. 결과 - 그래서 모든 문제가 사라졌냐고
사실 아직도 살짝의 보완이 더 필요하다. onDidFocus만 되면 getParam()을 실행하는 탓에 잠시 다른 화면으로 옮겼다가 돌아오기만 하면 파라미터를 자꾸 추가한다. 위의 코드가 작성된 스크린은 알람을 등록하는 스크린이다. 카메라에서 약을 인식한 후 돌아왔을 때 약의 데이터를 가지고 오는 코드인데, 알람을 작성하다가 취소하고 다시 돌아오면 약의 데이터를 다시 가져와 버린다. 이 문제는 웹에서 unMount에 해당하는 라이프 사이클이 앱에 없기 때문에 나타나는 현상이다. 추후 리팩터링과 버그 픽스에서 수정하려고 한다. 두 가지 방안을 생각하고 있는데 다음과 같다.
- 스택을 수정 => 이는 최초 알람 등록과 카메라에서 넘어오는 알람 등록을 다른 스택으로 구분해서 구현하는 방법이다. 스택 꼼꼼히 정리하지 않으면 스택이 다시 꼬일지도 모른다는 위험성이 있다.
- navigation.navigator()가 아니라 navigation.push()를 사용 => 카메라 스크린 외의 화면 전환에서 navigator()가 아니라 push()를 사용해서 화면을 전환하는 방법이다. push는 navigator와 달리 기존 스택에 스크린이 존재하면 해당 스크린을 강제로 종료하고 새로운 렌더링 한다. 하지만 내비게이션 바를 통해 이동하는 경우에 대해 컨트롤이 어렵다는 문제가 남는다.
아직 완전히 능숙하게 React Navigation을 이해하고 다루고 있다고 할 수는 없다. 하지만 처음 마주하는 개념에 대해서 여러 가지 방법으로 고민하고 우직하게 헤딩도 해보면서 많이 성장했음을 느낀다. 아직 해결해야 할 부분들과 문제를 해결하면서 느꼈던 희열이 함께 남아있다.