음악플레이어를 구현하면서 zustand를 사용하면서 만난 리렌더링 문제
import { create } from 'zustand'
const initialStore: NowPlayList = {
owner: undefined,
tracks: [],
currentTrack: null,
isPlaying: false,
playingPosition: 0
}
export const useNowPlayStore = create<NowPlayStore>(set => ({
...initialStore,
setCurrentTrack: (track: Track) =>
set(state => ({
...state,
currentTrack: track
})),
setPlayingPosition: playingPosition =>
set(state => {
return {
...state,
playingPosition: playingPosition
}
}),
...
}))
사용한 부분만 보다면 현재 재생목록과 관련된 store에는 현재 트랙을 의미하는 currentTrack과 이를 지정하는 setCurrentTrack이 있다. 또한 현재 재생된 분량을 뜻하는 playingPosition과 setPlayingPosition을 가지고 있다.
//BillPage.tsx
export const BillPage = () => {
const [analysisList, setAnalysis] = useState<any>([])
const numberOfObject = analysisList.length
...
const averageAnalysis = useMemo(
() =>
Object.fromEntries(
Object.entries(reduceAnalysisList).map(([key, value]) => [
key,
value / numberOfObject
])
),
[reduceAnalysisList]
)
const {currentTrack,setCurrentTrack } = useNowPlayStore()
return (
<>
{/* 음악 분석 그래프 */}
<BillGraph averageAnalysis={averageAnalysis} />
...
{/* 플레이 바 */}
{currentTrack && <PlayBar />}
</>
)
}
BillPage에서는 추천받은 음악의 분석결과를 보여주는 chart.js 그래프와 음악을 재생하고 현재 재생된 프로그래스바를 보여주는 playbar가 있다.
빌지의 음악목록 하나의 재생버튼을 눌러 현재 재생목록을 지정하고, 플레이바는 currentTrack이 있을때 보여져야하기 때문에 useStore를 사용하여
const {currentTrack,setCurrentTrack } = useNowPlayStore()
이런한 방식으로 들고왔으나, 이렇게 사용할 경우 useNowPlayStore 내부의 모든 상태를 반환한다.
const playAndPauseNowPlay = useCallback(() => {
toggleIsPlaying()
if (isPlaying) {
audioRef.current!.pause()
clearInterval(intervalIdRef.current!)
} else {
audioRef.current!.play()
intervalIdRef.current = setInterval(() => {
const { currentTrack } = useNowPlayStore.getState()
setPlayingPosition(audioRef.current?.currentTime)
//노래가 끝나면 다음곡으로 넘기기
if (audioRef.current!.ended) {
setCurrentTrack(tracks[tracks.indexOf(currentTrack!) + 1])
audioRef.current!.src = currentTrack?.preview_url!
audioRef.current!.currentTime = 0
setPlayingPosition(0)
}
}, 100)
}
}, [isPlaying, tracks, toggleIsPlaying, setPlayingPosition, setCurrentTrack])
그런데 여기서 playBar 컴포넌트내에서 음악이 재생된는 중에는 playingPosition 값이 setInterval의 콜백함수로 계속해서 바뀌고 있다.
따라서 앞의 방식으로 store의 저장된 값을 들고오면 위 gif처럼 음악 재생중에는 BillPage에 있는 모든 컴포넌트가 리렌더링 되어 <BillGraph />
는 currentTrack과 관련이 없는데에도 계속 리렌더링되어 애니메이션이 일어난다.
const currentTrack = useNowPlayStore(state => state.currentTrack)
const setCurrentTrack = useNowPlayStore(state => state.setCurrentTrack)
이런한 방식으로 들고올 경우에는 useNowPlayStore의 전체 state가 아닌 state.currentTrack
,state.setCurrentTrack
만 들고온다.
Object가 아닌, primitive type의 변수와 절대 바뀌지 않는 action 함수만을 들고 오기에, useNowPlayStore에 영향을 받지않는 <BillGraph />
는 리렌더링이 일어나지 않습니다.
위의 gif를 보면 그래프는 리렌더링되지않지만 <playbar/>
내부의 앨범 이미지와 버튼 svg가 계속 리렌더링되는데 프로그레스 바외에는 리렌더링되지않도록 하고, shallow를 사용하는 추가적 로직 수정이 필요하다.