REACT NATIVE/포트폴리오

[REACT NATIVE] 다이어리 프로젝트

예글 2022. 11. 8. 17:26

[기능 구현]

  • 글 작성
  • 글 목록 조회
  • 달력 보기
  • 검색 기능
  • 수정 하기, 삭제하기

0. 화면 설계

1.  react-navigation 적용하기

  • RootStack: 네이티브 스택 내비게이션 사용
  • MainTab: 하단 탭 내비게이션 사용
  • FeedsScreen: 작성한 글 목록 형태로 보여주는 화면
  • CalenderScreen: 작성한 글 달력 형태로 보여주는 화면
  • SearchScreen: 글 검색할 수 있는 화면
  • WriteScreen: 글 작성, 수정하는 화면 
    • MainTab 화면
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import React from 'react';
import CalendarScreen from './CalendarScreen';
import FeedsScreen from './FeedsScreen';
import SearchScreen from './SearchScreen';
import Icon from 'react-native-vector-icons/MaterialIcons'; // {Icon} XX

const Tab = createBottomTabNavigator();

function MainTab() {
  return (
    <Tab.Navigator
      screenOptions={{	// tabBarOptions XX
        showLabel: false,
        activeTintColor: '#009688',
      }}>
      <Tab.Screen
        name="Feeds"
        component={FeedsScreen}
        options={{
          tabBarIcon: ({color, size}) => (
            <Icon name="view-stream" size={size} color={color} />
          ),
        }}
      />
      <Tab.Screen
        name="Calendar"
        component={CalendarScreen}
        options={{
          tabBarIcon: ({color, size}) => (
            <Icon name="event" size={size} color={color} />
          ),
        }}
      />
      <Tab.Screen
        name="Search"
        component={SearchScreen}
        options={{
          tabBarIcon: ({color, size}) => (
            <Icon name="search" size={size} color={color} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}

export default MainTab;

app > build.gradle에 추가

project.ext.vectoricons = [
    iconFontNames: ['MaterialIcons.ttf']
]

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

2. Context API 사용하기(App, FeedScreen, CalendarScreen)

  • 리액트에 내장된 기능
  • Props를 사용하지 않아도 특정 값이 필요한 컴포넌트끼리 쉽게 값을 공유할 수 있게 해줌
  • createContext: 새로운 Context 만들 때
    • <LogContext.Provider> : Context 안에 있는 값을 사용할 컴포넌트들을 감싸주는 용도
      • value(Props) : Context를 통해 여러 컴포넌트에서 공유할 수 있는 값
      • 이 컴포넌트 내부에 선언 된 모든 컴포넌트에서 Context 안의 값을 사용할 수 있음
    • <LogContext.Consumer>: Context 안의 값을 사용할 때
      • Render Props: 컴포넌트 태그 사이에 함수 넣는 것/ 사용할 컴포넌트에서 특정 값을 밖으로 빼내와 사용할 수 있음
      • Context의 값을 읽는 부분이 JSX에 있기 때문에 컴포넌트 로직 작성이 까다로움
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import RootStack from './screens/RootStack';
import LogContext from './contexts/LogContext';

function App() {
  return (
    <NavigationContainer>
      <LogContext.Provider value='안녕하세요'>
        <RootStack />
      </LogContext.Provider>
    </NavigationContainer>
  );
}

export default App;
import React from 'react';
import {StyleSheet, View, Text} from 'react-native';
import LogContext from '../contexts/LogContext';

function FeedsScreen() {
  return (
  <View style={styles.block}>
    <LogContext.Consumer>
      {(value) => <Text>{value}</Text>} 
    </LogContext.Consumer>
  </View>
  );
}

const styles = StyleSheet.create({
  block: {},
});

export default FeedsScreen;
  • children Props: 컴포넌트 태그 사이에 넣어준 값
  • useContext Hook 함수: 컴포넌트에서 JSX를 반환하기 전에 값을 조회할 수 있기 때문에 컴포넌트 로직 작성이 편리함

3. 새 글 작성하기(WriteScreen)

  • FloatingWriteButton: FeedsScreen 우측 모서리에 나타나는 둥근 버튼
  • <Pressable> : TochableWithoutFeedback과 비슷 but 기능 더 많음
    • android_ripple(Props): 물결 효과
    • pressed(Style): 컴포넌트가 눌리면 동적인 스타일 적용
  • WriteScreen UI 준비
    • WriteHeader : 뒤로가기, 삭제, 완료 버튼
      • TransparentCircleButton({name, color, hasMarginRight, onPress})
    • WriteEditor : 제목, 내용 작성
      • Props 이름만 쓰고 값 지정 X --> 값이 true로 지정 
      • TextView 값이 true --> 여러 줄 작성 가능
  • useRef
    • 함수 컴포넌트에서 컴포넌트의 레퍼런스를 선택할 수 있게 하는 Hook
    • ref를 생성해 TextInput의 Prop로 지정 --> 원하는 컴포넌트의 레퍼런스 선택 가능
      • .focus() : TextInput에 포커스를 잡아줌
      • .blur() : TextInput에 포커스를 해제
      • .clear() : TextInput의 내용을 모두 비움
    • useReft로 서낵한 레퍼런스는 .current 값을 조회해 확인 가능
  • keyboardAvoidingView
    • 화면에서 기본적으로 보여줄 수 있는 줄 수를 초과 --> iOS에서 하단 내용 잘림
    • keyboardAvoidingView로 WriteScreen 감싸줘야 글 길어져도 문제없이 글 작성 가능
  • 제목, 내용 텍스트 상태 관리 --> useState 사용
  • 배열 상태 관리 --> LogContext
    • const [logs, setLogs] = useState([]);
    • logs 배열을 객체로 감싸서 value에 넣어줌 -- 객체로 감싸는 이유: logs 배열 외에도 상태에 변화 주는 함수를 같이 넣어줄 것이므로
    • uuid 라이브러리
      • 고유한 값 생성 
      • yarn add uuid
      • yarn add react-native-get-random-values
    • WriteScreen에서 useContext를 사용해 LogContext값 받아오고 onSave함수 만들기
    • onSave 함수 완성 --> WriteHeader에 Props로 넣기 --> 체크 버튼을 누를 때 함수 호출
    • FeedsScreen에서 useContext 사용해 LogContext 값 받아오기

4. 글 목록 보여주기

  • FeedListItem 컴포넌트 만들기
    • log 객체 Props로 받아와서 알맞은 위치에 정보 보여줌
    • truncate 함수: 줄 바꿈 문자 없애기, 글 길어지면 줄임표 붙이기
    • Pressable 컴포넌트: 글 선택하면 WriteScreen  띄워서 전체 내용 볼 수 있게 해줌 
  • FeedList 컴포넌트 만들기
    • logs 객체 Props로 받아와서 FlatList 통해 여러 항목 보여줌
  • FeedScreen에서 사용

5. date-fns로 날짜 포맷팅하기

  • 작성한 시간에 따라 방금 전, 3분전, 1시간 전, 3일 전, 2021년 8월 23일 07:00 이렇게 보이도록 하기
  • date-fns 라이브러리 사용 : yarn add date-fns p.334
    • formatDistanceToNow: 현재 시각을 기준으로 단어를 사용해 시간 나타냄
    • format: 다양한 형태로 날짜 포맷팅

6. Animated로 애니메이션 적용하기

  • 항목이 많아져서 스크롤해야 할 때 -> 우측 하단 버튼이 항목의 내용 가림 -> 버튼이 하단으로 사라지는 애니메이션을 적용해 자연스럽게 숨기기
  • Animated 객체 사용 - value 만들어야 함
    • function Sample() { const animation = useRef(new Animated.Value(1)).current; }
    • Value의 생성자 함수의 인자에는 초깃값 넣어주기
    • <Animated.컴포넌트 이름> 이런 형식으로 사용
    • .start()로 시작

  • 예외 처리

7. 작성한 글 WriteScreen으로 열기

  • FeedListItem 컴포넌트에서 Pressable에 onPress Props를 설정해 컴포넌트 눌렀을 때 Props로 받아온 log 객체를 WriteScreen의 파라미터로 넣어서 화면 열기
  • WriteScreen에서 log 파라미터 인식해 파라미터가 주어졌을 때 제목과 내용의 기본값 지정
    • 옵셔널 체이닝 문법: ?. -- null이거나 undefined일 수 있는 객체의 프로퍼티를 에러 없이 접근 가능
    • log?.body ?? '' ----> log?.body가 유효한 값이라면 해당 값 사용, 아니면 공백 문자열 사용

8. 수정 기능 구현

  • WriteScreen의 onSave 함수에서 log 라우트 파라미터가 유효하면 수정하는 함수 호출, 아니면 생성하는 함수 호출
  • LogContext에서 onModify 함수 구현
  • WriteScreen에서 onModify 함수 사용

9. 삭제 기능 구현

  • LogContext에서 onRemove 함수 구현
  • WriteScreen에서 onRemove 함수 사용
  • Alert.alert를 사용해 사용자에게 정말 삭제할 것인지 한 번 더 물어보기
  • isEditing 값 전달 --> WriteHeader에서 이 값 확인해 삭제 버튼 보여줄지 말지 결정
  • !!log : log가 유효한 값이면 true, 그렇지 않으면 false 값 전달
  • isEiditing Props가 true일 때만 삭제버튼 보여주고, 버튼 눌렀을 때 onAskRemove 호출하기

10. 검색 기능 구현

  • 검색 하려면 화면의 헤더 부분에 텍스트를 입력할 수 있어야 하므로, 헤더 커스터마이징 하기
  • SearchHeader 컴포넌트 만들기
  • MinTab 검색화면 설정 수정
  • 화면 크기 조회 - dp 단위로 가져오기 
    • Dimensions.get 사용
    • useWindowDimensions Hook 사용 - 값 바뀌면 컴포넌트에서 자동으로 반영 / 함수 컴포넌트 내부에서만 사용 가능 / 전체 화면의 크기 가져오는 기능은 없음
  • SearchHeader 컴포넌트 UI 구성
    • TextInput, 검색어 초기화하는 버튼 보여주기
  • SearchContext 만들기
    • 검색어 상태 관리
  • 검색어 필터링 후 FeedList 재사용하기
    • 배열 내장 함수 filter 사용 --> 검색어를 사용해 배열의 데이터 필터링 후 결과물을 FeedList 컴포넌트 재사용해 화면에 띄우기
  • 검색 결과가 없을 때 안내 문구 보여주는 EmptySearchResult 컴포넌트 만들기
    • 사용자가 검색어를 입력하지 않았을 때
    • 검색어와 일치하는 결과물이 없을 때