You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:17:49