2024. 6. 12. 17:53ㆍ리액트 (React)/리액트 레시피 (React Recipt)
Video
이제 채팅으로 비디오를 받을 수 있다고 해봅니다.
이를 위해 Video 컴포넌트를 만들고
(loop 어트리뷰트는 비디오가 끝나면 다시 자동으로 시작하도록 하는 것입니다)
import "./Video.scss";
export default function Video({ src, type }: { src: string; type: string }) {
return (
<div className="video">
<video loop>
<source src={src} type={type} />
</video>
<i className="fa-solid fa-video" />
</div>
);
}
Box에 추가했습니다.
...
import Video from "./Video";
export default function Box({ name, onClickList }: { name: string; onClickList: () => void }) {
const [newMessages, setNewMessages] = useState<string[]>([]);
return (
<div className="layout">
<div className="box">
<Header name={name} onClickList={onClickList} />
<Screen>
<Message message="show something!" isOutgoing />
<Video src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" />
{newMessages.map((message) => (
<Message message={message} isOutgoing />
))}
</Screen>
<Input onSubmit={(message: string) => setNewMessages([...newMessages, message])} />
</div>
</div>
);
}
이제 비디오를 눌러서 실행하고 일시정지하는 기능이 필요합니다.
그러나 지금까지 배운 state를 사용하게 되면 누를 때마다 리렌더링 될 것 이므로 일시정지 될 것 같지 않습니다.
더군나나 mdn 문서를 보면, video 어트리뷰트에 이런 기능을 하는 게 없습니다.
바로 이벤트(event)에 있기 때문입니다.
그럼 DOM 객체를 핸들링하려면 어떻게 할까요?
Ref
바로 이럴 때 ref를 사용합니다.
useRef 훅을 사용하면 ref를 만들 수 있습니다. 한 번 video 태그에 적용해봅시다.
import { useRef } from "react";
import "./Video.scss";
export default function Video({ src, type }: { src: string; type: string }) {
const videoRef = useRef<HTMLVideoElement>(null);
return (
<div className="video">
<video ref={videoRef} loop>
<source src={src} type={type} />
</video>
<i className="fa-solid fa-video" />
</div>
);
비디오가 실행되거나 일시정지되는 상태가 필요하니, 간단하게 state도 하나 추가해줍니다.
const [isPlay, setIsPlay] = useState<boolean>(false);
이제 비디오를 누르면 실행되고, 다시 누르면 일시정지하고, ... 를 할 수 있도록 handleClick 함수를 구현해봅니다.
...
export default function Video({ src, type }: { src: string; type: string }) {
const videoRef = useRef<HTMLVideoElement>(null);
const [isPlay, setIsPlay] = useState<boolean>(false);
function handleClick() {
setIsPlay(!isPlay);
if (!videoRef.current) {
return;
}
if (!isPlay) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}
return (
<div className="video" onClick={handleClick}>
...
보기 쉽게 아이콘까지 넣어주면 완성입니다!
import { useRef, useState } from "react";
import "./Video.scss";
export default function Video({ src, type }: { src: string; type: string }) {
const videoRef = useRef<HTMLVideoElement>(null);
const [isPlay, setIsPlay] = useState<boolean>(false);
function handleClick() {
setIsPlay(!isPlay);
if (!videoRef.current) {
return;
}
if (!isPlay) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}
return (
<div className="video" onClick={handleClick}>
<video ref={videoRef} loop>
<source src={src} type={type} />
</video>
<i className="fa-solid fa-video" />
<i className={`fa-solid fa-${isPlay ? 'pause' : 'play'} control`} />
</div>
);
}
Ref vs State
react.dev에 방문해보면 ref와 state 간 차이점을 잘 정리해놓았습니다.
필자가 생각했을 때 가장 중요한 점은 바로 리렌더링 여부입니다.
state는 익히 봐왔듯, 업데이트되면 리렌더링을 수행합니다.
하지만 ref는 업데이트되도 리렌더링을 하지 않습니다.
예를 들어 비디오 옆에 좋아요 버튼을 하나 추가해봅니다.
코드에는 좋아요 수를 ref로 만들어서, 좋아요를 누를 때마다 1 씩 증가한다고 하면
export default function Video({ src, type }: { src: string; type: string }) {
const likeCountRef = useRef<number>(0);
...
return (
<div className="video-box">
...
<button
className="like"
onClick={() => {
likeCountRef.current += 1;
}}
>
<i className="fa-solid fa-thumbs-up" /> {likeCountRef.current}
</button>
</div>
);
좋아요를 아무리 눌러도 숫자가 증가해보이지 않습니다.
이는 ref 값을 업데이트 한다고 한들, 리렌더링이 되지 않기 때문입니다.
따라서 이런 상황에는 ref가 아닌, state를 써야 됩니다.
export default function Video({ src, type }: { src: string; type: string }) {
// const likeCountRef = useRef<number>(0); // not re-rendered!
const [likeCount, setLikeCount] = useState<number>(0);
...
return (
<div className="video-box">
...
<button
className="like"
onClick={() => {
setLikeCount(likeCount + 1);
}}
>
<i className="fa-solid fa-thumbs-up" /> {likeCount}
</button>
</div>
);
References
'리액트 (React) > 리액트 레시피 (React Recipt)' 카테고리의 다른 글
8. Context 전달하기 (0) | 2024.06.12 |
---|---|
7. State 보존하기 (0) | 2024.06.11 |
6. 인터렉티브한 Input 만들기 (0) | 2024.06.10 |
5. State로 리렌더링하기 (0) | 2024.06.10 |
4. CSS 입히기 (0) | 2024.06.10 |