[React] 트위터 클론 (+ Firebase) - Notice, Bookmark

jjjjj·2022년 10월 5일
1

트위터 클론

목록 보기
5/6

👀 깃허브 링크
😎 클론 사이트 바로가기


⚠ 아직 부족한 점이 많아 정보 및 코드가 올바르지 않을 수 있습니다. 이 점 양해 부탁드리며 수정이 필요한 부분은 피드백 주시면 감사하겠습니다!

이슈가 있었던 부분은 제목 옆에 ❗를 붙였습니다.


➕ 기능 및 특징

Notice / Bookmark

  • 카테고리를 세분화 및 각각 정보들이 업데이트 될 때마다 실시간으로 확인 가능

  • 라우터 내의 각 탭에서 렌더링 되는 정보들은 하나의 컴포넌트로 만들어서 재사용

  • 노출되는 목록 오브젝트 클릭 시 라우터 이동과 시간 노출 부분은 따로 custom hooks로 빼내어 사용
      1. 내용을 감싸고 있는 태그와 내용들(이미지, 닉네임)의 라우터가 다름
      1. 감싸고 있는 태그에 <Link>를 주게 될 시 내용들을 클릭할 때마다 원하는 라우터가 아닌 감싸진 태그의 라우터로 이동됨
      1. ref로 if문을 작성해 useHistory()로 구현


✨ Notice

  • 리트윗 탭에서는 원글, 답글에 리트윗 된 것들을 같이 볼 수 있게 했습니다.
    • 원글과 답글의 리트윗 정보들을 한 번에 받아오기 하기 위해서 FirebasereNweets 컬렉션에 같이 추가 되도록 했고, 원글·답글 리트윗 정보의 Firebase 필드값이 다른 부분이 있기에 삼항 연산자로 원하는 부분을 가져올 수 있게 했습니다.

  • 재사용 컴포넌트에서는 useLocation(), pathname, includes()을 사용해서 해당 주소일 때 원하는 값을 나타날 수 있게 했습니다.

└ 부모 Notice 컴포넌트의 리트윗 관련 부분

// 리트윗 가져오기
useEffect(() => {
  const q = query(
    collection(dbService, "reNweets"),
    orderBy("reNweetAt", "desc")
  );

  const unsubscribe = onSnapshot(q, (snapshot) => {
    const reNweetArray = snapshot.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));

    const filter = reNweetArray.filter(
      (obj) =>
      obj.email !== userObj.email &&
      (obj?.replyEmail ? obj?.replyEmail : obj?.parentEmail) ===
       userObj.email
      );

      setReNweets(filter);
    setLoading((prev) => ({ ...prev, reNweets: true }));
  });

  return () => unsubscribe();
}, [userObj.email]);

└ 리트윗 컴포넌트 컨테이너 (답글, 팔로우도 비슷함)

// reNweetsObj = 부모 컴포넌트의 reNweets 값

export const NoticeReNweet = ({ reNweetsObj, userObj }) => {
  const [creatorInfo, setCreatorInfo] = useState([]);
  const [nweets, setNweets] = useState([]);
  const [loading, setLoading] = useState(false);

  // 정보 가져오기
  useEffect(() => {
    const unsubscribe = onSnapshot(
      doc(dbService, "users", reNweetsObj.email),
      (doc) => {
        setCreatorInfo(doc.data());
        setLoading(true);
      }
    );

    return () => unsubscribe();
  }, [reNweetsObj, userObj.email]);

  // 트윗 가져오기
  useEffect(() => {
    const unsubscribe = onSnapshot(
      doc(dbService, "nweets", reNweetsObj.parent),
      (doc) => {
        setNweets(doc.data());
      }
    );

    return () => unsubscribe();
  }, [reNweetsObj]);

  return (
    <>
      {loading && (
        <NoticeInnerContents
          creatorInfo={creatorInfo}
          noticeUser={reNweetsObj}
          nweets={nweets}
          text={
            reNweetsObj?.replyId
              ? "답글에 리트윗을 했습니다."
              : "글에 리트윗을 했습니다."
          }
        />
      )}
    </>
  );
};

└ 재사용 컴포넌트

export const NoticeInnerContents = ({
  noticeUser,
  creatorInfo,
  text,
  nweets,
}) => {
  const imgRef = useRef();
  const nameRef = useRef();
  const location = useLocation();
  const [followTime, setFollowTime] = useState([]);

  // 팔로우 시간 정보 가져오기
  useEffect(() => {
    if (creatorInfo?.following) {
      creatorInfo?.following.map((follow) => setFollowTime(follow.followAt));
    }
  }, [creatorInfo?.following]);

  const { timeToString } = useTimeToString();

  return (
    <>
      <div className={styled.nweet}>
        <div className={styled.nweet__container}>
          <Link
            to={`/profile/mynweets/${noticeUser?.email}`}
            className={styled.nweet__profile}
            ref={imgRef}
          >
            <img
              src={creatorInfo?.photoURL || noticeUser?.photoURL}
              alt="profileImg"
              className={styled.profile__image}
            />
          </Link>
          <Link
            className={styled.nweet__contents}
            to={
              location.pathname.includes("followers")
                ? `/profile/mynweets/${noticeUser?.email}`
                : `/nweet/${noticeUser?.parent}`
            }
          >
            <div className={styled.reNweetBox}>
              <p>
                <span ref={nameRef}>
                  @{(noticeUser?.email || creatorInfo.email)?.split("@")[0]}
                </span>
                님이{" "}
                {location.pathname.includes("renweets") && (
                  <span className={styled.reNweet__name}>
                    "{noticeUser?.text ? noticeUser?.text : nweets?.text}"
                  </span>
                )}
                {location.pathname.includes("replies") && (
                  <span className={styled.reNweet__name}>"{nweets?.text}"</span>
                )}
                {location.pathname.includes("followers") && null}
                {text}
              </p>
            </div>
            <div
              style={{ marginLeft: "auto" }}
              className={styled.reNweet__time}
            >
              {location.pathname.includes("renweets") && (
                <p>{timeToString(noticeUser?.reNweetAt)}</p>
              )}
              {location.pathname.includes("replies") && (
                <p>{timeToString(noticeUser?.createdAt)}</p>
              )}
              {location.pathname.includes("followers") && (
                <p>{timeToString(followTime)}</p>
              )}
            </div>
          </Link>
        </div>
      </div>
    </>
  );
};


└ 경과 시간 custom hook

const timeToString = (timestamp) => {
  const today = new Date();
  const timeValue = new Date(timestamp);

  const betweenTime = Math.floor(
    (today.getTime() - timeValue.getTime()) / 1000 / 60
  );
  if (betweenTime < 1) return "방금 전";
  if (betweenTime < 60) {
    return `${betweenTime}분 전`;
  }

  const betweenTimeHour = Math.floor(betweenTime / 60);
  if (betweenTimeHour < 24) {
    return `${betweenTimeHour}시간 전`;
  }

  const betweenTimeDay = Math.floor(betweenTime / 60 / 24);
  if (betweenTimeDay < 8) {
    return `${betweenTimeDay}일 전`;
  }

  return `${Math.floor(betweenTimeDay / 7)}주 전`;
};


✨ Bookmark

  • 화면 사이즈에 따라 '북마크' 탭을 다른 라우터에서 노출시키게 했습니다.
    • resize 이벤트 리스너를 작성하여 윈도우의 사이즈를 체크하고 모바일 사이즈일 경우 프로필에 북마크 탭을 생성하고 해당 라우터로 이동시키게 했습니다.

└ 사이즈 체크

// 리사이징
useEffect(() => {
  // 렌더 시
  if (size < 500) {
    setResize(true);
    // LeftBar 컴포넌트 속 코드 -> 모바일 사이즈 시 Profile의 북마크 탭으로 이동
    history.push("/profile/bookmarknweets/" + userObj.email);
  } else if (size > 500) {
    setResize(false);
    // Profile 컴포넌트 속 코드 -> 모바일 사이즈가 아닐 시 Left Bar의 북마크 탭으로 이동
    history.push("/bookmark/nweets"); 
  }
  const Resize = () => {
    let innerSize = window.innerWidth;
    setSize(innerSize);
  };
  window.addEventListener("resize", Resize);
  return () => window.addEventListener("resize", Resize);
}, [history, size, userObj.email]);


📌 참고

- 라우터 이동 관련 글 1
- 라우터 이동 관련 글 2
- 글 작성 경과 시간 표시

profile
의미있게 하기~.~

0개의 댓글