TIL 및 깃허브를 참고해주시면 감사하겠습니다. TIL 모음집이라 생각하고 작성하겠습니다.
(chatting.jsx : 채팅 주요 로직)
const sock = new SockJS("http://3.38.228.74:8080/ws/chat");
const ws = webstomp.over(sock);
const dispatch = useDispatch();
const listReducer = useSelector((state) => state.chatting.chatList);
console.log(listReducer);
// let postId = Number(id) : 임의값이라 일단 주석처리
useEffect(() => {
dispatch(__getinitialChatList({
postId:10,
roomId:1
}));
// 임의의 유저와 룸넘버
return () => {
onbeforeunloda()
};
}, []);
useEffect(() => {
wsConnectSubscribe()
// return () => {
// console.log("???????")
// onbeforeunloda()
// };
}, [listReducer.id]);
const [chatBody, setChatBody] = useState("");
const content = {
sender: "보내는이",
message:chatBody
// 임의의 센더와 useState값의 챗바디
};
let headers = {
Access_Token: localStorage.getItem('Access_Token')
};
// 토큰값이 있어야 채팅이 가능하게 함
function wsConnectSubscribe() {
try {
ws.connect(
headers,(frame) => {
ws.subscribe(
`/sub/1`,
// 데스티네이션
(response) => {
console.log("어떻게 나오는지",response)
let data = JSON.parse(response.body)
dispatch(chatList(data))
// 값이 어떻게 넘어오는지 보는 로직
}
);
},
);
} catch (error) {
}
}
function waitForConnection(ws, callback) {
setTimeout(
function () {
// 연결되었을 때 콜백함수 실행
if (ws.ws.readyState === 1) {
callback();
// 연결이 안 되었으면 재호출
} else {
waitForConnection(ws, callback);
}
},
1 // 밀리초 간격으로 실행
);
}
//stomp 메시지 에러 waitForConnection함수로 해결
const onbeforeunloda = () =>{
try {
ws.disconnect(
()=>{
ws.unsubscribe("sub-0");
clearTimeout(
waitForConnection
)
},
{Access_Token: localStorage.getItem('Access_Token')}
)
}catch (e){
console.log("연결구독해체 에러",e)
}
}
const inputHandler = (e) =>{
setChatBody(e.target.value)
}
// 연결 해제 시 로직
//onSubmitHandler
const onSubmitHandler = (event) =>{
//event.preventDefault()
// if (chatBody=== "" || chatBody === " ") {
// return alert("내용을 입력해주세요.");
// }
waitForConnection(ws,function() {
ws.send(
`/pub/1`,
JSON.stringify(content),
{
Access_Token: localStorage.getItem("Access_Token")
},
)})
setChatBody("")
}
const appKeyPress = (e) => {
if (e.key === 'Enter') {
onSubmitHandler()
setChatBody("")
}
}
//enter시 메시지 보냄
const scrollRef= useRef();
useEffect(() => {
if (scrollRef) {
scrollRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest",
});
}
}, [listReducer]);
(postDetail.jsx)
// 채팅방 개설
useEffect(()=>{
localStorage.setItem("roomId", postChat)
}, [postChat])
// postChat 선언한 것이 변할 때마다 셋아이템 유즈이펙트
const onClickChatting = () =>{
dispatch(__CreateRoom({
postId:post.postId,
// 페이로드로 넘겨주는 건 postId만
}));
setTimeout(
function () {
// 만들어진 채팅방으로 이동하는 로직 => localStorage 활용한 방법 이용
// localStorage에 저장해서 데스티네이션, url 등을 맞춰서 방 개설하는 방법 활용
// 연결되었을 때 콜백함수 실행
navigate(`/chatting/${localStorage.getItem("roomId")}`);
},
300 // 밀리초 간격으로 실행
);
}
트러블슈팅 : 각종 아이콘(svg) 이동, 말풍선 좌우 정렬 이슈가 있었다.
1) 말풍선 좌우 정렬 : 우측으로 본인 글 옮기는 로직
(Chatting.jsx)
{listReducer.joinUserNickname === item.sender ?
(<ChatStyle key={uuidv4()} style={{textAlign:"right"}}>
{/* 인라인 요소로 바로 우측 정렬 해결 */}
<TimeDiv>
<TimeSpan>{item.sendDate}</TimeSpan>
<JoinUserNickname>{item.message}</JoinUserNickname>
</TimeDiv>
</ChatStyle>) :
(<ChatStyle key={uuidv4()}>
<TimeDiv>
<PostUserNickname>{item.message}</PostUserNickname>
<TimeSpan>{item.sendDate}</TimeSpan></TimeDiv>
</ChatStyle>)
}
2) 종이비행기 아이콘 정렬하기
<InputDiv>
<Input value={chatBody} onChange={inputHandler} onKeyPress={appKeyPress} placeholder="댓글을 입력하세요"/>
{/* value를 줘야 사라진다 */}
<Send onClick={onSubmitHandler} style={{position:"fixed", left:"calc(50% + 140px)", transform:"translateX(-50%)", bottom:25}}/>
{/* calc로 계산해서 반응형에 맞춰도 움직이지 않는 그림 만든다. svg는 컴포넌트처럼 임포트가 가능하다 */}
</InputDiv>
⇒ 조건부 렌더링으로 length > 0일 때 보이게 하고 아닐 때(채팅방이 정상적이지 않을 때) 아예 null 처리를 해주는 방식으로 설정***```javascript
(chatList.jsx)
// 채팅방 list
<Div onClick={(e) => e.stopPropagation()}>
{Room !== undefined &&
Room !== [] &&
Room.map((item, i) => {
return (
<div key={i}>
{item.chatList.length > 0 ? (<>
// 조건이 0보다 커야만, 즉 채팅이 있어야만 채팅방이 개설되는 로직
<RoomList onClick={() => onClickChatting(item)}>
<Profile>
<div><img src={item.postUserAvatarUrl} style={{ width: 44, height: 44, borderRadius:"50%"}}/></div>
<div>
<div>{item.postUserNickname} <Time>{item.chatList[item.chatList.length-1].sendDate}</Time></div>
<Message>
{item.chatList[item.chatList.length-1].message}
// 스몰 이슈:
{/* 대화의 맨 마지막만 가지고 오는 로직이면 위와 같이 진행하면 됨 */}
</Message>
</div>
</Profile>
<SellImg>
{item.image !== undefined && (
<>
<img src={item.image.imgUrl} style={{ width: 44, height: 44}} />
</>)}
</SellImg>
</RoomList>
</>): null}
</div>
);
})}
</Div>
(부분 코드)
(PostList.jsx)
{posts.length > 0 &&
// 전체 조건부 렌더링
posts.map((post, index) => {
return (
<div
className="bg-white flex p-[18px] border-b-[0.5px] border-D9"
key={index}
onClick={() => {
onClickHandler(post.postId);
}}
>
<img
className="object-cover min-w-[84px] w-[84px] h-[84px] rounded"
src={post?.images[0]?.imgUrl}
/>
// 요소별 옵셔널 체이닝
<div className=" w-full flex-col ml-3 font-medium">
<div>
<label>{post?.title}</label>
</div>
<div className=" mt-1 text-base font-semibold">
<label>{post?.userPrice.toLocaleString("ko-KR")}원</label>
</div>
<div className="flex justify-between mt-4 text-xs font-normal">
<div>{post?.createdAt}</div>
<div className="flex">
<img src={mainHeart} />
<div>{post?.likeCnt}</div>
</div>
</div>
</div>
</div>
);
})}
(PostsSlice.js)
//__addPost
[__addPost.pending]: (state) => {
state.isLoading = true;
},
[__addPost.fulfilled]: (state, action) => {
state.isLoading = false;
state.posts.push(action.payload);
window.location.replace("/postread/all")
// replace 활용한 새로고침 및 렌더링
},
[__addPost.rejected]: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},