React组件间调用Spotify Player实例方法的Context API问题
问题根源
你遇到的togglePlay is not a function错误,核心原因是你把Spotify Player实例转换成了普通对象,丢失了原型链上的方法:
在player_state_changed监听中,你使用setSpotifyPlayer({ ...playerInstance })展开实例,这种操作只会复制实例自身的属性,而Player的方法(比如togglePlay)是定义在其原型链上的,展开后这些方法会丢失,导致后续调用失败。
另外,直接给Player实例添加device_id和playback属性的做法也不够合理,容易破坏实例的原始结构。
修复方案
我们可以将Player实例和播放状态分开存储,既保留实例的完整方法,又能跟踪播放状态:
1. 修改SpotifyPlayerContext.jsx
import React, { createContext, useEffect, useState } from "react" import { useSelector } from "react-redux" export const SpotifyPlayerContext = createContext() export const SpotifyPlayerProvider = ({ children }) => { const user = useSelector((state) => state.user.value) const [spotifyPlayer, setSpotifyPlayer] = useState(null) // 单独存储播放状态,不修改Player实例 const [playbackState, setPlaybackState] = useState(null) const [deviceId, setDeviceId] = useState(null) const initSpotifyPlayer = () => { const script = document.createElement("script") script.src = "https://sdk.scdn.co/spotify-player.js" script.async = true document.body.appendChild(script) window.onSpotifyWebPlaybackSDKReady = () => { const playerInstance = new window.Spotify.Player({ name: "Figure Out The Lyrics", getOAuthToken: (cb) => { cb(user.token) }, }) playerInstance.connect() playerInstance.addListener("ready", ({ device_id }) => { console.log("Device ID", device_id) setDeviceId(device_id) setSpotifyPlayer(playerInstance) // 直接存储完整实例 }) playerInstance.addListener("player_state_changed", (state) => { console.log("player state changed", state) setPlaybackState(state) // 单独更新播放状态 }) } } // 把实例、状态、初始化方法都放入Context const value = { spotifyPlayer, playbackState, deviceId, initSpotifyPlayer } return ( <SpotifyPlayerContext.Provider value={value}> {children} </SpotifyPlayerContext.Provider> ) } export default SpotifyPlayerContext
2. 修改Player.jsx
import React, { useContext, useEffect } from "react" import SpotifyPlayerContext from "../SpotifyPlayerContext" const Player = ({ spotifyAPI }) => { const { spotifyPlayer, playbackState, deviceId } = useContext(SpotifyPlayerContext) useEffect(() => { if (!spotifyPlayer || !deviceId) return if (!playbackState) { spotifyAPI.transferMyPlayback([deviceId]) } console.log("useEffect in Player", spotifyPlayer) }, [spotifyPlayer, deviceId, playbackState]) return playbackState ? ( <button onClick={() => { console.log("spotifyPlayer", spotifyPlayer) spotifyPlayer.togglePlay() // 现在实例完整,方法可正常调用 }} > toggle </button> ) : ( <div>loading</div> ) } export default Player
Context API是否适用?
完全适用。Context API的核心场景之一就是跨组件共享带有行为的实例或工具类,相比Redux(更适合全局状态管理),直接用Context传递Player实例更直接高效,因为你需要的是调用实例的方法,而不是管理可序列化的状态。当然如果后续有更多和播放器相关的状态需要全局共享,也可以结合Redux一起使用,但当前场景下Context是最优解。
内容的提问来源于stack exchange,提问作者szym
相关产品推荐
相关产品推荐

