피드관리 - Part 2 피드조회
ㆍProject Diary/React + MariaDB (PicShare WebApp)
사용자가 피드를 필터링하여 조회할 수 있는 기능을 구현합니다. 이 글에서는 모든 피드와 사용자가 팔로우한 사용자의 피드만을 필터링하여 조회하는 방법과 필터링된 피드를 가져와 화면에 표시하는 과정을 다룹니다.
프론트엔드
1. 피드 필터링: MainFeedCategorySection
모든 피드(All)와 팔로잉한 사용자의 피드(Following)만 필터링하여 조회합니다.
// 피드 필터링 컴포넌트
const MainFeedCategorySection = ({ setFilter, filter }) => {
const dispatch = useDispatch();
const currentUser = useSelector((state) => state.members.user);
// 필터 버튼 클릭 핸들러
const handleFilterClick = async (filterType) => {
if (filterType === "all") {
// 모든 피드 필터 설정
setFilter({ type: "all" });
} else if (filterType === "following" && currentUser) {
// 팔로잉 피드 필터 설정
try {
const response = await dispatch(fetchFollowingList(currentUser.userNo));
const followingUserNos = response.map((user) => user.userNo);
setFilter({ type: "following", userNos: followingUserNos });
} catch (error) {
console.error("팔로잉 필터 실패:", error);
}
}
};
return (
<div>
<button onClick={() => handleFilterClick("all")}>All</button>
<button onClick={() => handleFilterClick("following")}>Following Feed</button>
</div>
);
};
export default MainFeedCategorySection;
- handleFilterClick 함수는 필터 타입에 따라 모든 피드 또는 팔로잉 피드를 설정합니다.
- "all"일 때는 모든 피드를, "following"일 때는 현재 사용자가 팔로우하는 사용자의 피드만 필터링합니다.
2. 피드 데이터 가져오기 및 처리: MainFeedSection
// 필터링된 피드 데이터를 가져와 화면에 표시하는 컴포넌트
const MainFeedSection = ({ filter }) => {
const dispatch = useDispatch();
const { feeds, loading, error } = useSelector((state) => state.feeds);
// 필터가 변경될 때마다 피드 데이터를 가져옴
useEffect(() => {
if (filter.type === "all") {
dispatch(fetchAllFeed());
} else if (filter.type === "following") {
dispatch(fetchAllFeed(filter));
}
}, [filter, dispatch]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{feeds.map((post, index) => (
<div key={post.postId}>
<div>{post.userNickname}</div>
<div>{post.content}</div>
{/* 이미지와 해시태그를 표시하는 로직 */}
</div>
))}
</div>
);
};
export default MainFeedSection;
필터링된 피드를 feeds 상태로 받아와 화면에 렌더링합니다.
3. Redux Slice: 피드 상태 관리
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const serverUrl = import.meta.env.VITE_API_URL;
// Redux Slice 생성
const feedSlice = createSlice({
name: "feed",
initialState: {
feeds: [],
loading: false,
error: null,
},
reducers: {
initFeedStart(state) {
state.loading = true;
state.error = null;
},
initFeedSuccess(state, action) {
state.feeds = action.payload;
state.loading = false;
},
initFeedFail(state, action) {
state.error = action.payload;
state.loading = false;
},
},
});
export const { initFeedStart, initFeedSuccess, initFeedFail } = feedSlice.actions;
// 모든 피드를 가져오는 Thunk 함수
export const fetchAllFeed = (filter) => async (dispatch) => {
dispatch(initFeedStart());
try {
let url = `${serverUrl}/feed/all`;
if (filter && filter.type === "following") {
url += `?userNos=${filter.userNos.join(",")}`;
}
const response = await axios.get(url);
dispatch(initFeedSuccess(response.data));
} catch (error) {
dispatch(initFeedFail(error.toString()));
}
};
export default feedSlice.reducer;
- fetchAllFeed 함수로 필터 조건에 따라 모든 피드 또는 팔로잉 피드를 가져옵니다.
백엔드
1. 피드 데이터 조회 API 엔드포인트
데이터베이스에서 피드 데이터를 조회하여 필터링된 피드를 반환합니다.
import express from "express";
import { db } from "../db.js";
const feedRouter = express.Router();
// 모든 피드를 가져오는 API 엔드포인트
feedRouter.get("/all", (req, res) => {
const { userNos } = req.query;
let query = `SELECT p.*, u.userNickname, u.profilePicture
FROM posts p
JOIN users u ON p.userNo = u.userNo`;
if (userNos) {
query += ` WHERE p.userNo IN (${userNos.split(",").map(Number).join(",")})`;
}
query += ` ORDER BY p.created_at DESC`;
db.query(query, (err, posts) => {
if (err) {
console.log(err);
return res.json({
message: "서버 오류가 발생했습니다. 다시 시도해주세요.",
});
}
const postIds = posts.map((post) => post.postId);
// 피드 이미지 가져옴
db.query(
`SELECT i.* FROM images i WHERE i.postId IN (?)`,
[postIds],
(err, imagesData) => {
if (err) {
console.log(err);
return res.json({
message: "피드의 이미지를 가져오던 중 오류가 발생했습니다.",
});
}
// 해시태그 데이터 가져옴
db.query(
`SELECT ph.postId, h.tag
FROM post_hashtags ph
JOIN hashtags h ON ph.hashtagId = h.hashtagId
WHERE ph.postId IN (?)`,
[postIds],
(err, hashtagsData) => {
if (err) {
console.log(err);
return res.json({
message: "피드의 해시태그를 가져오던 중 오류가 발생했습니다.",
});
}
// 각각의 이미지를 해당 포스트에 매핑
const imagesByPostId = imagesData.reduce((acc, image) => {
if (!acc[image.postId]) acc[image.postId] = [];
acc[image.postId].push(image);
return acc;
}, {});
// 각각의 해시태그를 해당 포스트에 매핑
const hashtagsByPostId = hashtagsData.reduce((acc, hashtag) => {
if (!acc[hashtag.postId]) acc[hashtag.postId] = [];
acc[hashtag.postId].push(hashtag.tag);
return acc;
}, {});
// 결과를 최종 포스트 데이터에 합침
const result = posts.map((post) => ({
...post,
feedImages: imagesByPostId[post.postId] || [],
feedHashtags: hashtagsByPostId[post.postId] || [],
}));
console.log("서버에서 보내주는 올피드데이터", result);
res.send(result);
}
);
}
);
});
});
export default feedRouter;
sql 쿼리 : 기본쿼리
SELECT p.*, u.userNickname, u.profilePicture FROM posts p JOIN users u ON p.userNo = u.userNo
- 모든 포스트와 해당 포스트를 작성한 사용자의 닉네임과 프로필 사진을 가져옵니다.
필터링 조건
(${userNos.split(",").map(Number).join(",")})
- 특정 사용자(userNos)가 작성한 포스트만 필터링합니다.
sql 쿼리 : 이미지 데이터 조회
SELECT i.* FROM images i WHERE i.postId IN (?)
- 특정 포스트의 이미지를 가져옵니다.
sql 쿼리 : 해시태그 데이터 조회
SELECT ph.postId, h.tag FROM post_hashtags ph JOIN hashtags h ON ph.hashtagId = h.hashtagId WHERE ph.postId IN (?)
- 특정 포스트의 해시태그를 가져옵니다.