본문 바로가기
프로그래밍/REACT-NATIVE

EXPO INSTAGRAM CLONE 만들기 (3)

by Lihano 2021. 8. 4.
반응형

서론

연속으로 업로드하는 인스타그램 클론 만들기 게시물!

아침에 배운 내용에 이어 낮에 배운 내용을 추가하도록 한다.

 

유저 검색하기

user를 검색하는 방법에 대해서 알아보자.

먼저 user의 정보는 두 군데에 저장되고 있다는 사실을 이해하자.

 

1. database 2. store

 

database는 내가 앱을 꺼도 유지되는 정보들이지만,

store는 내가 앱을 끄면 삭제되는 정보들이다.

 

그렇다면 우리는 당연히 로그인할 때 데이터베이스에서 user 정보를 얻어올 것이다.

그런 처리를 하는 action 함수가 fetchUser다.

 

export function fetchUser() {
  return (disaptch) => {
    firebase
    .firestore()
    .collection("users")
    .doc(firebase.auth().currentUser.uid)
    .get()
    .then(snapshot => {
      if (snapshot.exists) {
        dispatch({ type : USER_STATE_CHANGE, currentUser : snapshot.data() })
      } else {
        console.log("dose not exist")
      }
    })
  }
}

이게 action 페이지에서 로그인 함수를 처리하는 action 함수다.

참고로 action 함수에서 이런 비동기 작업이 가능한 이유는 redux-thunk를 통해 미들웨어를 생성했기 때문이다.

 

위의 액션 생성 함수가 하는 일은 결국 간단하다.

disaptch에 action을 보내는 일.

 

action이란 Typepaylaod를 포함한 객체다.

type이란 명령의 종류이며, paylaod란 명령을 처리하는 데 필요한 data를 의미한다. 보통은.

 

위의 액션 생성 함수는

  1. firebase의 auth 서비스에게 현재 로그인 처리된 계정의 uid를 요청해서
  2. 그 uid를 이용해 database에 저장된 계정 정보를 얻어온 뒤
  3. action을 생성해 store에 그 계정 정보를 저장한 것이다

복잡해 보이지만 아주 간단한 작업이다.

 

그리고 우리가 구성할 프로필 화면은 해당 유저가 업로드한 사진들을 갤러리처럼 나열할 것이다.

그러기 위해서는 역시 database에 저장된 사진을 store에 받아올 필요가 있다.

즉, 또 action 생성 함수가 필요하다.

 

export function fetchUserPosts() {
  return (dispatch) => {
    firebase
    .collection("posts)
    .doc(firebase.auth().currentUser.uid)
    .collection("userPosts)
    .orderBy("creation", "asc")
    .get()
    .then(snapshot => {
      let posts = snaphsot.docs.map((doc) => {
        const data = doc.data();
        const id = doc.id;
        return {id, ...data};
      });
      dispatch({ type : USER_POSTS_STATE_CHANGE, posts })
    })
  }
}

이 액션 생성 함수의 동작도 로그인 액션 생성 함수와 거의 동일하다.

 

다만 한가지 추가 언급하고 싶은 건, 예전에 firebase의 database 서비스를 소개할 때 collection은 document로 이루어진 집합이라고 설명한 적이 있다.

틀린 말은 아니지만 위에 보다시피 document에 collection을 생성하는 것도 가능하다.

보면 posts라는 컬렉션 내부에는 유저들의 uid로 된 document가 존재하고 이 document에는 posts라는 collection이 또 존재한다.

 

이렇게 store에 현재 유저 정보와 유저의 사진 리스트를 저장하는 액션 생성 함수에 대해서 알아보았다.

 

그러면 실제로 검색 페이지를 구성해보자.

우리는 유저를 name을 통해서 검색할 것이다.

또한 엔터키가 아니라 자판을 누르자마자 즉시 검색이 되도록 할 것이다.

 

우선 검색된 유저들을 담을 변수를 생성한다.

이 정보들은 현재 컴포넌트 내부에서만 다룰거기 때문에 useState를 통해서 만들어준다.

const [users, setUsers] = useState([])

그리고 검색창을 만들어준다.

검색창은 간단하게 TextInput으로 구현하고, 입력된 텍스트들을 바로 함수를 돌려 검색을 실행할 것이다.

<TextInput placeholder="검색할 유저" onChangeText={(search) => fetchUUsers(search)} />

onChagneText는 입력된 text가 변할 때마다 바로 실행된다.

useState를 통해서 간접적으로 텍스트를 update 시킬 필요 없이 이렇게도 함수를 실행시킬 수 있다.

 

입력된 텍스트를 통해 user를 찾는 작업은 firebase의 database에서 이뤄져야 할 것이다.

현재 우리 앱의 store에는 내 계정 정보밖에 없기 때문이다.

그러니 그 외의 정보는 외부에서 찾아와야지.

const fetchUsers = (search) => {
  firebase
  .firestore()
  .collection("users")
  .where("name", ">=", search)
  .get()
  .then(snapshot => {
    let users = snapshot.docs.map(doc => {
      const data = doc.data();
      cosnt id = doc.id;
      return { id, ...data }
    })
    setUsers(users)
  })
}

firebase의 database의 document를 검색하는 쿼리를 볼 수 있다.

firebase의 database도 SQL처럼 where문을 사용할 수 있다. 그 방법에 차이는 있지만.

대부분 SQL에서 지원하는 쿼리문은 firebase의 database에서도 사용할 수 있다.

 

하지만 그럼에도 불구하고 파이어베이스의 쿼리문은 매우 단순하며 비효율적이라 document의 양이 늘어날 경우에는 좋은 효율을 끌어내지 못할 수 있다.

이게 파이어베이스로 대규모 작업을 추천하지 못하는 이유인 것 같다.

 

아무튼 나는 이렇게 입력한 텍스트 search로 name이 일치하는 정보 리스트인 users를 얻었다.

그럼 이 users를 가지고 뭘 하나?

이만큼 찾았습니다~ 하고 화면에 표시해야지.

 

내가 찾은 users는 가변적이고 개발자가 예측하지 못하는 데이터의 배열이다.

그런 종류의 데이터를 화면에 표시하는 데 좋은 컴포넌트가 FlatList라고 전에 언급한바 있다.

FlatList는 단순히 인피니트 스크롤을 구현하는데 사용되는 컴포넌트가 아니라 이런 경우에도 사용된다.

<FlatList
numColumns={1}
horizontal={false}
data={users}
renderItem={({item}) => (
  <TouchableOpacity onPress={()=>props.navigation.navigate("Profile", {uid: item.id})} >
    <Text>{item.name}</Text>
  </TouchableOpacity>
)}
/>

FlatList에 대해서는 전의 포스팅에 질릴 정도로 언급했으니 더이상은 설명하지 않는다.

중요한 건 렌더링하는 목록을 클릭하면 네비게이션 함수를 통해 Profile 화면으로 이동한다.

 

이 프로필 컴포넌트는 이동될 때 uid라는 props를 전달받는다.

이건 Main 화면에서 탭 버튼을 통해 프로필로 이동할 때도 마찬가지다.

 

Main 화면에서 버튼을 눌러 프로필 화면으로 이동할 때는 listener의 tabPress 이벤트 함수를 설정해 auth 서비스로부터 지금 로그인한 계정의 uid를 프로필 화면으로 보낸다.

 

하지만 Search 화면에서 FlatList의 목록을 눌러 프로필 화면으로 갈 때는 검색된 item의 uid를 프로필 화면으로 보낸다.

 

프로필 화면은 uid를 전달받을 때 우선 이게 지금 로그인 된 계정의 uid인지를 확인해야한다.

왜냐하면 지금 로그인 된 계정의 계정 정보와 사진 리스트는 전부 store에 저장되어 있으니까 굳이 한번 더 찾을 필요가 없기 때문이다.

반대로 지금 props로 전달받은 uid가 로그인 된 계정의 uid가 아닌 타계정의 uid라면 firebase의 database에 접근해 그 uid의 계정 정보사진 리스트를 받아와야 한다.

 

그 정보를 화면에 출력해주면 검색 작업은 끝이다.

 

팔로우 기능

내가 하려는 건 다음 두가지다.

  1. 타인의 프로필 화면을 들어갈 때 "팔로우" 버튼을 출력
  2. 팔로우가 되어있다면 언팔로우 버튼을, 안되어있다면 팔로우 버튼을. 서로 전환되도록 설정.

그러기 위해선 다음 작업이 필요할 것이다.

  • 데이터베이스의 계정 document에 팔로우 정보를 관리하는 Collection을 만든다.
  • store에 현재 계정의 팔로우 정보를 다루는 Reduce를 만든다
  • 프로필 화면에서 팔로우 정보를 업데이트하는 버튼과 함수를 만든다.

우선 첫번째는 리덕스다.

리듀스 함수를 만들어주자.

case USER_FOLLOWING_STATE_CHANGE :
  return {
    ...state,
    following : action.following
  }

리듀스 함수의 action type은 USER_FOLLOWING_STATE_CHANGE다.

그런 다음은 이 리듀스 함수를 실행하는 액션 생성 함수를 만든다.

액션 생성 함수의 기능은 데이터베이스에서 팔로우 정보를 가져오는 것이다.

export function fetchUserFollowing() {
  return (dispatch) => {
    firebase
    .firestore()
    .collection("following")
    .doc(firebase.auth().currentUser.uid)
    .collection("userFollowing")
    .onSnapshot((snapshot) => {
      let following = snapshot.docs.map((doc) => {
        const id = doc.id;
        return id;
      })
      dispatch({ type : USER_FOLLOWING_STATE_CHANGE, following })
    })
  }
}

주목할만한 점은 액션 생성 함수에서도 onSnapshot을 사용할 수 있다는 점이다.

onSnapshot은 데이터베이스를 모니터링하는 함수다.

read, write, update, delete가 실행되었을 때 실행되는 트리거 함수다.

 

이 액션 함수를 실행시키면 action 페이지에서 팔로우 컬렉션을 모니터링한다.

그러면서 팔로우에 변화가 일어나면 즉시 store를 업데이트 한다.

 

팔로우를 데이터베이스에서 조회하는 함수를 만들었으니,

팔로우를 데이터베이스에서 생성하는 함수를 만들어보자.

 

이 동작은 프로필 화면에서 일어난다.

우선 두 가지 함수를 작성한다.

  1. 팔로우 함수
  2. 언팔로우 함수

팔로우 함수는 지금 내 계정의 팔로우 컬렉션에 이 프로필 화면의 주인을 추가한다.

언팔로우 함수는 지금 내 계정의 팔로우 컬렉션에 이 프로필 화면의 주인을 제거한다.

const onFollow = () => {
  firebase
  .firestore()
  .collection("following)
  .doc(firebase.auth().currentUser.uid)
  .collection("userFollowing")
  .doc(props.route.params.uid)
  .set({})
}
const onUnfollow = () => {
  firebase
  .firestore()
  .collection("following")
  .doc(firebase.auth().currentUser.uid)
  .collection("userFollowing)
  .doc(props.route.params.uid)
  .delete();
}

여기서 질문.

 

※왜 userFollowing 컬렉션 내부에 uid 이름의 빈 document를 생성하는건가요?

→ 그건 uid의 리스트를 document 내부에서 관리했다간 지우고 업데이트하는 과정이 번거로워질 수 있기 때문이다.

document의 선에서 fetch하고 delete update하는 과정이 더 낫다고 판단했기 때문.

 

사실 이렇게 함수를 생성해준 것만으로도 팔로우 기능은 이미 끝이다.

남은 건 현재 프로필 화면이 타인의 것인가? 를 판단하여 팔로우 버튼을 띄우기.

그리고 팔로우 여부에 따라 팔로우 버튼과 언팔로우 버튼을 띄우기다.

 

이 과정은 굳이 설명할 필요가 없을 것 같다.

오늘은 검색과 팔로우 기능을 알아보았다.

블로그에 글을 업데이트 하느라 더 진도가 느린 것 같다.

하지만 블로그에 글을 쓰는 만큼 깊고 확실하게 알아가는 것 같기도 하다.

 

이 클론은 내일 아니면 내일 모래 끝내도록 하자.

그럼 이만🖐🖐🖐🖐🖐

반응형

댓글