Skip to content

Commit

Permalink
refactor: 렌더링 최적화
Browse files Browse the repository at this point in the history
  • Loading branch information
Cllaude99 committed Dec 4, 2024
1 parent 60270a4 commit c4fba47
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 166 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Button from '@/components/common/Button';
import { memo } from 'react';

interface IVotingPhaseProps {
userId: string;
Expand All @@ -9,7 +10,33 @@ interface IVotingPhaseProps {
handleVote: () => void;
}

export default function Voting({
const VoteButton = memo(
({
userId,
isSelected,
isVoteSubmitted,
onClick,
}: {
userId: string;
isSelected: boolean;
isVoteSubmitted: boolean;
onClick: () => void;
}) => (
<button
onClick={onClick}
disabled={isVoteSubmitted}
className={`w-full p-4 text-lg font-medium transition-colors rounded-lg ${
isSelected
? 'bg-green-default text-white-default'
: 'bg-white text-gray-800 hover:bg-gray-100'
} ${isVoteSubmitted && 'opacity-60 cursor-not-allowed'}`}
>
{userId}
</button>
),
);

const Voting = memo(function Voting({
userId,
allUsers,
selectedVote,
Expand All @@ -30,19 +57,14 @@ export default function Voting({
<div className="flex flex-col items-center justify-center w-full h-full space-y-6">
<h2 className="text-2xl font-bold text-white-default">피노코를 지목해주세요!</h2>
<div className="flex flex-col w-full max-w-md space-y-3">
{Array.from(allUsers).map((userId: string) => (
<button
key={userId}
onClick={() => !isVoteSubmitted && setSelectedVote(userId)}
disabled={isVoteSubmitted}
className={`w-full p-4 text-lg font-medium transition-colors rounded-lg ${
selectedVote === userId
? 'bg-green-default text-white-default'
: 'bg-white text-gray-800 hover:bg-gray-100'
} ${isVoteSubmitted && 'opacity-60 cursor-not-allowed'}`}
>
{userId}
</button>
{Array.from(allUsers).map((voterId: string) => (
<VoteButton
key={voterId}
userId={voterId}
isSelected={selectedVote === voterId}
isVoteSubmitted={isVoteSubmitted}
onClick={() => !isVoteSubmitted && setSelectedVote(voterId)}
/>
))}
</div>
{isVoteSubmitted ? (
Expand All @@ -64,4 +86,6 @@ export default function Voting({
)}
</div>
);
}
});

export default Voting;
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { useState } from 'react';
import { useState, useCallback, memo } from 'react';

interface IGuessInputProps {
onSubmitGuess: (word: string) => void;
}

export default function GuessInput({ onSubmitGuess }: IGuessInputProps) {
const GuessInput = memo(function GuessInput({ onSubmitGuess }: IGuessInputProps) {
const [inputValue, setInputValue] = useState('');

const handleSubmit = () => {
const handleSubmit = useCallback(() => {
if (!inputValue.trim()) return;
onSubmitGuess(inputValue);
setInputValue('');
};
}, [inputValue, onSubmitGuess]);

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSubmit();
}
};
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSubmit();
}
},
[handleSubmit],
);

const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
}, []);

return (
<div className="flex flex-col items-center justify-center space-y-4">
Expand All @@ -26,7 +33,7 @@ export default function GuessInput({ onSubmitGuess }: IGuessInputProps) {
type="text"
placeholder="제시어 입력"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onChange={handleChange}
onKeyDown={handleKeyDown}
className="p-2 text-lg rounded-md bg-gray-800 text-white-default outline-none"
/>
Expand All @@ -38,4 +45,6 @@ export default function GuessInput({ onSubmitGuess }: IGuessInputProps) {
</button>
</div>
);
}
});

export default GuessInput;
153 changes: 90 additions & 63 deletions packages/frontend/src/components/gamePage/leftSection/MainDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback, useMemo, memo } from 'react';
import { useRoomStore } from '@/store/roomStore';
import StartButton from './GameButtons/StartButton';
import ReadyButton from './GameButtons/ReadyButton';
Expand All @@ -20,7 +20,7 @@ import VideoStream from '@/components/gamePage/stream/VideoStream';
import { useLocalStreamStore } from '@/store/localStreamStore';
import { useSpeakingControl } from '@/hooks/useSpeakingControl';

export default function MainDisplay() {
const MainDisplay = memo(function MainDisplay() {
const { userId } = useAuthStore();
const { isHost, isPinoco, allUsers } = useRoomStore();
const [gamePhase, setGamePhase] = useState<GamePhase>(GAME_PHASE.WAITING);
Expand All @@ -38,13 +38,21 @@ export default function MainDisplay() {
setGamePhase,
setSelectedVote,
);
function getCurrentStream() {
const localStream = useLocalStreamStore.getState().localStream;
const remoteStreams = usePeerConnectionStore.getState().remoteStreams;
if (currentSpeaker === userId) return localStream;
const currentStream = remoteStreams.get(currentSpeaker || '');
return currentStream;
}

const handleCountdownEnd = useCallback(() => {
setGamePhase(GAME_PHASE.WORD_REVEAL);
const timer = setTimeout(() => {
setGamePhase(GAME_PHASE.SPEAKING);
}, 3000);
return () => clearTimeout(timer);
}, []);

const handleVote = useCallback(() => {
if (!isVoteSubmitted) {
votePinoco(selectedVote ?? '');
setIsVoteSubmitted(true);
}
}, [isVoteSubmitted, selectedVote, votePinoco]);

useEffect(() => {
if (gameStartData) {
Expand All @@ -54,23 +62,76 @@ export default function MainDisplay() {
}
}, [gameStartData]);

const handleCountdownEnd = () => {
setGamePhase(GAME_PHASE.WORD_REVEAL);
setTimeout(() => {
setGamePhase(GAME_PHASE.SPEAKING);
}, 3000);
};
const currentStreamData = useMemo(() => {
if (currentSpeaker === userId) {
return useLocalStreamStore.getState().localStream;
}
return usePeerConnectionStore.getState().remoteStreams.get(currentSpeaker || '');
}, [currentSpeaker, userId]);

const handleVote = () => {
if (!isVoteSubmitted) {
if (selectedVote === null) {
votePinoco('');
} else {
votePinoco(selectedVote);
}
setIsVoteSubmitted(true);
const renderGamePhaseContent = useMemo(() => {
switch (gamePhase) {
case GAME_PHASE.WAITING:
return (
<div className="flex flex-col items-center justify-center h-full">
{isHost ? <StartButton /> : <ReadyButton />}
</div>
);
case GAME_PHASE.COUNTDOWN:
return <Countdown onCountdownEnd={handleCountdownEnd} />;
case GAME_PHASE.VOTING:
return (
<Voting
userId={userId!}
allUsers={allUsers}
selectedVote={selectedVote}
isVoteSubmitted={isVoteSubmitted}
setSelectedVote={setSelectedVote}
handleVote={handleVote}
/>
);
case GAME_PHASE.VOTING_RESULT:
return (
<VoteResult
deadPerson={deadPerson ?? ''}
voteResult={voteResult}
isDeadPersonPinoco={isDeadPersonPinoco ?? null}
/>
);
case GAME_PHASE.GUESSING:
return (
<div className="flex flex-col items-center justify-center h-full">
{isPinoco ? (
<GuessInput onSubmitGuess={submitGuess} />
) : (
<p className="text-xl text-center text-white-default">
피노코가 제시어를 추측 중입니다 🤔
</p>
)}
</div>
);
case GAME_PHASE.ENDING:
return <EndingResult endingResult={endingResult} />;
default:
return null;
}
};
}, [
gamePhase,
isHost,
userId,
allUsers,
selectedVote,
isVoteSubmitted,
handleVote,
handleCountdownEnd,
endingResult,
deadPerson,
voteResult,
isDeadPersonPinoco,
isPinoco,
submitGuess,
]);

return (
<div
className={`relative flex-1 flex flex-col w-full p-4 mt-4 rounded-lg overflow-hidden ${
Expand All @@ -80,7 +141,7 @@ export default function MainDisplay() {
{gamePhase === GAME_PHASE.SPEAKING && (
<div className="absolute inset-0">
<VideoStream
stream={getCurrentStream() || null}
stream={currentStreamData || null}
userName={currentSpeaker}
isLocal={true}
height="h-full"
Expand All @@ -105,43 +166,7 @@ export default function MainDisplay() {
)}
</div>

<div className="flex-1">
{gamePhase === GAME_PHASE.WAITING && (
<div className="flex flex-col items-center justify-center h-full">
{isHost ? <StartButton /> : <ReadyButton />}
</div>
)}
{gamePhase === GAME_PHASE.COUNTDOWN && <Countdown onCountdownEnd={handleCountdownEnd} />}
{gamePhase === GAME_PHASE.VOTING && (
<Voting
userId={userId!}
allUsers={allUsers}
selectedVote={selectedVote}
isVoteSubmitted={isVoteSubmitted}
setSelectedVote={setSelectedVote}
handleVote={handleVote}
/>
)}
{gamePhase === GAME_PHASE.VOTING_RESULT && (
<VoteResult
deadPerson={deadPerson ?? ''}
voteResult={voteResult}
isDeadPersonPinoco={isDeadPersonPinoco ?? null}
/>
)}
{gamePhase === GAME_PHASE.GUESSING && (
<div className="flex flex-col items-center justify-center h-full">
{isPinoco ? (
<GuessInput onSubmitGuess={submitGuess} />
) : (
<p className="text-xl text-center text-white-default">
피노코가 제시어를 추측 중입니다 🤔
</p>
)}
</div>
)}
{gamePhase === GAME_PHASE.ENDING && <EndingResult endingResult={endingResult} />}
</div>
<div className="flex-1">{renderGamePhaseContent}</div>

<div className="relative mt-auto">
{gamePhase === GAME_PHASE.SPEAKING && currentSpeaker === userId && (
Expand Down Expand Up @@ -170,4 +195,6 @@ export default function MainDisplay() {
</div>
</div>
);
}
});

export default MainDisplay;
Loading

0 comments on commit c4fba47

Please sign in to comment.