$\colorbox{#dc645b}{\textsf{\textbf{\color{white}GitHub}}}$

https://github.com/thelight0804/subak

https://github.com/s-minii/subak

$\colorbox{#dc645b}{\textsf{\textbf{\color{white}Japanese page}}}$

Subak Market

$\colorbox{#dc645b}{\textsf{\textbf{\color{white}Reference}}}$

React Native

🥕 당근

$\colorbox{#dc645b}{\textsf{\textbf{\color{white}Process}}}$

수박마켓 process

$$ \huge\textsf{\textbf{\color{#dc645b}수박마켓 (당근마켓 클론코딩)}} $$

Untitled

<aside> <img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0fb2db70-d069-479e-8cd8-3f44fba669d3/blank.png" alt="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0fb2db70-d069-479e-8cd8-3f44fba669d3/blank.png" width="40px" /> 당근마켓 서비스를 클론코딩한 수박마켓 프로젝트입니다.

</aside>

<aside> 👉 日本語はこちらをご覧ください。

</aside>

https://github.com/thelight0804/subak/releases

🛠️ 기술 스택


Backend Frontend
Java 11 JavaScript
Spring Boot 2.7.17 React Native
Spring Data JPA Axios
Swagger Redux
MariaDB React Navigation
Cloudinary Async Storage
StyleSheet

React Native

React를 공부하다가 앱 개발에도 관심이 생겨 React Native를 사용했습니다. React Native는 JavaScript로 개발이 가능하며 Android 와 iOS 모바일 앱을 동시에 개발할 수 있습니다. 또한, React 개발자라면 누구든지 쉽게 앱을 개발할 수 있다는 장점이 있습니다.

예시 코드

<View style={shared.container}>
  //...
  <View style={styles.content}>
    <Image
      style={styles.image}
      source={require('../../assets/image/launch_screen.png')}
    />
    <View>
      <Text style={[styles.text, styles.title]}>당신 근처의 수박</Text>
      <Text style={styles.text}>동네라서 가능한 모든 것</Text>
      <Text style={styles.text}>지금 내 동네를 선택하고 시작해보세요!</Text>
      <TouchableOpacity>
		//...
    </View>
  </View>
  //...
</View>

React Native는 React와 달리 HTML 문법을 사용하지 않고, 대신 다양한 핵심 컴포넌트를 활용한다는 점을 배웠습니다. 예를 들어, <View>는 레이아웃을 구성하는 컨테이너로서, HTML에서 <div>와 유사한 기능을 수행합니다. <Text>는 텍스트를 표현하고 스타일링하는 데 사용되며, 이는 HTML의 <p> 태그와 비슷한 역할을 합니다.

Axios

Axios는 HTTP 요청을 전송하는 데 쓰이는 라이브러리로, fetch와 달리 다양한 브라우저에서 호환되며 데이터를 자동으로 JSON 형식으로 변환해줍니다. 또한, 페이지를 새로고침하지 않고도 데이터를 주고받을 수 있습니다. 메인 페이지에서 사용자가 화면을 아래로 스크롤할 때 추가로 게시글 데이터를 받아오는 기능을 구현하기 위해서 Axios를 사용했습니다.

예시 코드

axios.get(`http://${Config.DB_IP}/posts?offset=${start*10}&limit=10`, {
  headers: {
    Authorization: `Bearer ${userToken}`,
  },
  timeout: 2000
})
  .then(response => {
    // 요청이 성공적으로 완료되었을 때
  })
  .catch(error => {
    // 요청 중에 오류가 발생했을 때
  });

Axios를 활용하여 서버로부터 게시물 데이터를 가져옵니다. headers 객체에서 사용자 인증을 거쳐 게시물 데이터를 안전하게 불러올 수 있습니다. 또한, 데이터를 성공적으로 가져온 경우와 오류가 발생한 경우를 구분하여 처리하였습니다.

Redux

React Redux는 여러 컴포넌트 간의 props를 효율적으로 관리하는 데 사용하는 라이브러리입니다. 사용자 데이터를 여러 컴포넌트에서 공유하고 관리하기 위해 Redux를 활용했습니다.

예시 코드

const initialState = {
  name: '', // 이름
  id: '', // 사용자 고유 id
  phone: '', // 전화번호
  // ...
};

const userData = createSlice({
  name : 'userData',
  initialState,
  reducers: {
    login(state, action) { // 로그인 정보를 저장
      return action.payload;
    },
    logout(state) { // 초기 상태로 설정
      return initialState;
    },
  }
})

initialState에서 사용자 정보의 초기 상태를 정의합니다. 그 후, createSlice 함수를 사용해 사용자 정보와 관련된 Reducer를 생성합니다. reducers에서는 login, logout 두 가지의 액션을 처리합니다. 이렇게 생성된 userData 슬라이스는 Redux store에 등록되어, 다양한 컴포넌트에서 사용자 데이터를 효과적으로 관리할 수 있게 됩니다.

React Navigation

React에서 페이지 전환 시 Router를 활용했습니다. 반면, React Native에서는 React Navigation 라이브러리를 통해 다른 페이지로의 이동이 가능합니다. 이를 통해, Android와 iOS에서 사용하는 제스처와 애니메이션을 사용할 수 있습니다.

예시 코드

const PostStack = () => {
  const Stack = createNativeStackNavigator(); //React navigation stack

  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      <Stack.Screen name="PostDetail" component={PostDetail}/>
      <Stack.Screen name="NewPost" component={NewPost}/>
      //...
    </Stack.Navigator>
  )
}

React Navigation을 이용해 네이티브 스택 내비게이터를 구성했습니다. 이를 통해 각의 화면은 스택에 순차적으로 쌓이며, 사용자는 앱 내에서 앞뒤로 화면을 이동할 수 있습니다.

<TouchableOpacity
  onPress={() =>
    navigation.navigate('PostStack', {
      screen: 'PostDetail',
      params: {postId: item.id},
    })
}>

TouchableOpacity 컴포넌트를 누르면 'PostDetail' 넘어가는 기능을 구현하였습니다. 이 과정에서 params를 사용해 게시글의 고유 ID(item.id)를 파라미터로 전달합니다.

Async Storage

Async Storage를 사용하면 데이터를 클라이언트 기기에 Key-Value 형태로 저장할 수 있습니다. Redux와 달리 앱이 종료되어도 저장된 데이터가 유지되므로, 자동 로그인 기능 구현에 사용했습니다.

예시 코드

const setStorageData = async (data, key) => {
  try {
    const jsonValue = JSON.stringify(data);
    await AsyncStorage.setItem(key, jsonValue); // 데이터를 JSON으로 저장합니다.
  } catch (e) {
    // 오류가 발생했을 때
  }
};

const getStorageData = async (key) => {
  try {
    const jsonValue = await AsyncStorage.getItem(key); // 데이터를 비동기로 가져옵니다
    return jsonValue != null ? JSON.parse(jsonValue) : null; // 가져온 JSON 데이터를 JavaSCript 객체로 변경하여 반환합니다
  } catch (e) {
    // 오류가 발생했을 때
  }
};

setStorageData 함수는 데이터를 로컬 저장소에 저장하는 기능을 수행합니다. getStorageData 함수는 로컬 저장소에 저장된 데이터를 불러오는 기능을 담당합니다. 이 과정을 통해 사용자가 앱을 종료한 후 다시 열었을 때 로그인 상태를 유지할 수 있습니다.

🖥️ 아키텍처


Untitled

📜 API 명세서


Name Method URL Tag
회원가입 POST /user 회원
로그인 POST /user/sign-in 회원
회원 프로필 수정 PUT /user/{userId}/profile 회원
회원 이메일 찾기 POST /user/email 회원
회원 비밀번호 재설정 POST /user/password 회원
회원 탈퇴 PATCH /user/withdraw 회원
게시글 생성 POST /post 게시글
게시글 수정 GUT /post/{postId} 게시글
게시글 삭제 DELETE /post/{postId} 게시글
전체 게시글 조회 GET /posts 게시글
게시글 상세 페이지 조회 GET /posts/{postId} 게시글
상품 상태 수정 PATCH /post/{postId}/product-status 게시글
게시글 상태 수정 PATCH /post/{postId}/status 게시글
게시글 좋아요, 취소 POST /post/{postId}/hearts 게시글
끌어올리기 PUT /post/{postId}/recent 게시글
판매중 게시글 조회 GET /posts/selling 게시글
숨김 게시글 조회 GET /posts/hide 게시글
판매완료 게시글 조회 GET /posts/completed 게시글
구매완료 게시글 조회 GET /posts/purchased 게시글
관심 게시글 조회 GET /posts/likedBy 게시글
게시글 검색 GET /posts/search 게시글
카테고리별 게시글 검색 GET /posts/category/{category} 게시글
판매중 게시글 개수 조회 GET /posts/selling/count 게시글
숨김 게시글 개수 조회 GET /posts/hide/count 게시글
판매완료 게시글 개수 조회 GET /posts/completed/count 게시글
판매하기 POST /posts/{postId}/sell 게시글
댓글 추가 POST /post/{postId}/comments 댓글
댓글 수정 PUT /post/{postId}/comments/{commentId} 댓글
댓글 삭제 DELETE /post/{postId}/comments/{commentId} 댓글
후기 추가 POST /review/{postId} 후기
후기 조회 GET /review/{postId} 후기
구매자 후기 작성 여부 조회 GET /reviews/{postId}/buyer-status 후기
판매자 후기 작성 여부 조회 GET /reviews/{postId}/seller-status 후기

💡 기능


게시글

Untitled

목록

Untitled

글 작성

Untitled

상세 페이지

조회

댓글

유저 관리

판매 및 구매

👥 팀원