[Swap(), 그 기록] 5. 게시물 필터 (feat. Firebase)

Soye Park·2023년 2월 8일
0
post-thumbnail

이 포스팅은 사이드 프로젝트를 진행하며 생겼던 여러 일들에 대해서 기록을 겸하여 작성한 글입니다. 주기적으로 작성하고자 노력은 하지만 블로그 포스팅 한번 하려고 하면 머릿속에서 나만의 언어로 정리도 하면서 작성하다가 몇시간은 사용하는 지라 주기적이지 않을 가능성이 농후합니다. 아자아자...

🚨내용추가🚨 데이터 불러오는 단계에서 밸류값만 배열화시키는 메소드가 따로 있었음.

export const data = selector({
  key: 'defaultData',
  get: async () => {
    const postRef = await collection(db, 'posts');
    const q = query(postRef);
    const querySnapShot = await getDocs(q);
    const dataArr: DataTypes[] = [];

    querySnapShot.forEach((item: any) => {
      const returnDoc = item.data() as DataTypes; // 여기
      dataArr.push(returnDoc);
    });

    return dataArr;
  },
});

위 코드 10번째 줄의 data() 메소드를 통해 받아온 데이터를 일반적인 형태의 데이터로 가지고 올 수 있음
아 대대적인 공사를 해야겟꾸만.. 공식문서를 꼼꼼히 봅시다!

게시물 필터 적용하기

들어가면서...

필터 기능을 구현해본 것은 굉장히 단순한 구조의 데이터를 다룰 때 였는데 데이터 규모가 커져도 크게 로직이 바뀌는 것은 아니다 보니 경험을 토대로 조금은 머릿속에 그려지는 것들이 생기더라 신기.

Firebase Query?

파이어베이스에서 제공하는 쿼리 기능에는 이번에 구현한 최신 순, 오래된 순과 같은 간단한 필터부터 다양한 조건을 필요로 하는 복잡한 필터까지 쉽게 구현할 수 있도록 기능이 구현되어있다.

기본적인 사용법이 상당히 간단한데

  1. collection 메소드로 Ref 생성
  2. query 메소드로 어떤 ref를 참조할 것이며 어떤 필터를 걸어 결과값을 반환할 건지

이 두 과정으로 쉽게 정렬된 데이터를 받아올 수 있다. 코드로 표현하면 아래와 같은데

const filter = type => {
  const postRef = collection(db, 'posts');
  const q = query(postRef, orderBy('convertDate', type));
};

나는 type 매개변수를 받아와 과거순, 최신순 필터가 따로 분리되어 작성되지 않게 재사용성을 고려해 작성하였다.
아마 default 값들을 주게 되면 다른 필터들 역시 매개변수로 받아와 재사용성 좋게 활용할 수 있을 듯 하다.


🚨 만약 두가지 이상의 조건을 사용할 시에는 orderBy가 아닌 색인을 추가해야한다. 안그러면 파이어베이스가 에러를 막 뱉는다.

      • 색인 추가 예시
const q = query(
 postRef,
 orderBy('convertDate', type),
 orderBy('name', type),
 limit(8),
)


정렬된 데이터에서 필요한 데이터 가지고 오기

console.log(q)로 정렬된 데이터를 확인하게 되면 이게 무슨 데이터인지도 모르겠다 싶은 데이터가 확인된다.

이렇게 query 메소드로 정렬된 데이터에서 실제 (필요한) 데이터만 가지고 오려면 과정을 더 거쳐야하는데

getDocs 메소드를 통해 복잡한 구조의 쿼리를 데이터에 접근할 수 있도록 껍질을 벗겨줘야한다. (추상적 표현 야호. 근데 생각이 안남 ㅠ)

const filter = async type => {
  const postRef = collection(db, 'posts');
  const q = query(postRef, orderBy('convertDate', type));
  const querySnapShot = await getDocs(q);

위의 코드와 같이 getDocs 통해 사용한 가능한 데이터로 만들어주고 console.log(querySnapShot)으로 데이터를 확인해보면

ㅓ...? 이거 query랑 똑같은 거 아니냐...? 어케 써..?

싶은 데이터구조가 나온다. 하지만 우리가 사용할 데이터가 query에서는 바로 안보였지만 getDocs로 추출된 데이테에는 보인다. 바로 이것.

이걸 열어보면

이런 각 데이터들이 쫘라락 50개 펼쳐지는데, 맞다 이 데이터를 forEach메소드를 사용해서 쓸 거다!


데이터를 배열로 넣기

이제 필요한 것들은 모두 준비가 되었고 배열로 데이터를 넣으면 된다.

const SalesProductListTap = () => {
  const [filterType, setFilterType] = useState('');
  const setFilterDataArr = useSetRecoilState(filterData);

  const filter = async type => {
  const postRef = collection(db, 'posts');
  const q = query(postRef, orderBy('convertDate', type));
  const dataArr = [];
  const querySnapShot = await getDocs(q);

  querySnapShot.forEach(item => {
    dataArr.push(item._document.data.value.mapValue.fields);
  });

  setFilterDataArr(dataArr);
};

위의 코드처럼 빈 배열을 하나 만들어주고 그 빈 배열에 querySnapShot.forEach()를 통해 하나씩 데이터를 집어넣어주면 되는데 이런 데이터들은 어떻게 서버에서 제공하느냐에 따라 데이터 구조가 다를 수 있기 때문에 따로 설명하지는 않을 것. 아무튼 필요한 데이터를 item에서 찾아서 쏙쏙 넣어주면 되는데 이렇게 만들어진 데이터는 setState 함수를 통해 state에 반영해준다. (selector를 써서 따로 데이터를 구분한 것은 또 다른 이유가 있긴 한데 그건 뒤에)


데이터를 렌더링할 차례

리코일의 활용

여기서 부터는 리팩토링이 될 여지가 있다- 그래도 정리

나는 컴포넌트를 잘게 쪼게 놓고 해서 이리저리 props가 넘나들면서 복잡스러워 질 것 같아 리코일을 활용해 전역상태로 관리해줬다.
전역관리로 받았을 때 한가지 문제가 있었는데, default 데이터가 부재하다는 것이었다. 물론 로드 될 때 데이터를 useSetRecoilState를 사용해 넣어버리는 방법이라던가 뭐 몇가지가 있었을 것 같긴한데, 나는 recoil의 selector를 사용해 초기 데이터를 만들고 recoil state에 default값으로 지정해주었다.

export const data = selector({
  key: 'defaultData',
  get: async () => {
    const postRef = collection(db, 'posts');
    const q = query(postRef);
    const querySnapShot = await getDocs(q);
    const dataArr = [];

    querySnapShot.forEach((item) => {
      dataArr.push(item._document.data.value.mapValue.fields);
    });
    return dataArr;
  },
});
export const filterData = atom({
  key: 'filterData',
  default: data,
});

일단 아톰을 활용해 위와 같은 식으로 default 데이터를 제공했고, 필터가 실행될 때마다 data의 값이 바뀌면서 필터링된 데이터가 렌더링되는 식이다.


데이터 렌더링----

아래 코드를 보면 구조가 좀 이상하다

const filteredData = useRecoilValue(filterData);

const contents = filteredData.map(content => {
  return {
    key: content.postId.stringValue,
    title: content.title.stringValue,
    content: content.content.stringValue,
    name: content.name.stringValue,
    date: content.date.stringValue,
    convertDate: content.convertDate.stringValue,
    productImgUrl:
      content.imgUrl.arrayValue.values[0].mapValue.fields.url.stringValue,
    profileImgUrl: content.profileImg.stringValue,
  };
});

그렇다. querySnapShot 내의 아이템들을 배열로 넣기는 했는데 구조가 타입이 하나씩 끼어있었다.
그런 이유로 default로 활용하던 데이터를 넣으면 파일의 구조가 달라 필터가 안먹거나 / 디폴트가 렌더안되거나 둘 중 하나의 양자택일의 상황이었다.
그래서 선택한 게 selector를 활용해 동일 구조의 데이터를 하나 생성했던 것.

암튼 데이터 렌더링이야 많이 해봤으니까 설명은 끝-


구현된 기능

화질구지여서 잘안보이지만.. 암튼 ..된다!


마치며

필터기능을 파이어베이스를 활용할 때 굉장히 쉽게 구현할 수 있다.
하지만 필터기능 자체의 컨셉이나 작동원리는 대부분 비슷하니까!! 좋은 경험이었다-

profile
응애FE개발자/ 블로그 이전 : https://soyeah-log.vercel.app/

0개의 댓글