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

React(Inertia)中Laravel-Echo广播出现异常结果求助

Laravel Echo + Inertia(React) 实时广播重复接收问题排查

用PHP/Blade结合Pusher CDN实现实时功能时,提交表单后广播事件正常,其他客户端能正确接收消息。但切换到Inertia(React)+Laravel-Echo实现时,出现单次点击后接收端内容多次渲染的异常:

  • 发起广播的客户端(Client1)同时收到自己的广播响应和接收端消息
  • 其他客户端(Client2)重复收到两次接收端消息

Blade + Pusher CDN 代码

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title inertia>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
        <script src="https://js.pusher.com/8.2.0/pusher.min.js"></script>
        <!-- Scripts -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    </head>
    <body class="font-sans antialiased">
        <div className="grid place-items-center">
            <h1 className="text-slate-800 text-center p-8 text-7xl font-bold uppercase">
                Test Page
            </h1>
            <p className="text-slate-500 px-8 text-center mt-3 text-2xl font-bold">
                This page is dedicated to test a Real Time Chat in React,
                using Laravel Echo and Pusher. Request will be done using
                Jquery
            </p>
            <form>
                <button
                    id='btn'
                    className="mt-8 text-center border border-slate-800 bg-slate-800 text-white font-bold text-2xl p-3 rounded"
                >
                    Broadcast
                </button>
            </form>
        </div>
        <div className="mt-48 grid place-items-center">
            <span id="placeholder"></span>
        </div>
    </body>
    <script>
        // Enable pusher logging - don't include this in production
        Pusher.logToConsole = true;
        const pusher = new Pusher("{{ config('broadcasting.connections.pusher.key') }}", {cluster: "eu"});
        const channel = pusher.subscribe('public');
        channel.bind('chat', function(data){
            $.post('/receive', {
                _token: '{{csrf_token()}}',
                message: "Hello, From JS",
            }).done(res => $("#placeholder").last().after(res));
        });

        $("form").submit(function(e){
            e.preventDefault();
            $.ajax({
                url: '/broadcast',
                method:"POST",
                headers: {
                    "X-Socket-Id": pusher.connection.socket_id
                },
                data: {
                    _token: "{{ csrf_token() }}",
                    message: "Hello,From JS"
                }
            }).done(res => $("#placeholder").last().after(res));
        })
    </script>
</html>

Inertia(React) + Laravel-Echo 代码

import React from "react";
import $ from "jquery";
import Echo from "laravel-echo";
import Pusher from "pusher-js";
import { CSRF_TOKEN } from "@/Utils/forms";

function Test() {
    window.Pusher = Pusher;
    window.Pusher.logToConsole = true;

    window.Echo = new Echo({
        broadcaster: "pusher",
        key: "c804af4b0e3beefe4880",
        cluster: "eu",
    });
    window.Echo.channel("public").listen(".chat", function (data) {
        $.post("/receive", {
            _token: `${CSRF_TOKEN}`,
            message: "Hello, From JS",
        }).done((res) => $("#placeholder").last().after(res));
    });

    const handleSubmit = (e) => {
        e.preventDefault();
        $.ajax({
            url: "/broadcast",
            method: "POST",
            headers: {
                "X-Socket-Id": window.Echo.socketId(),
            },
            data: {
                _token: CSRF_TOKEN,
                message: "Hello, From JS",
            },
        }).done((res) => $("#placeholder").last().after(res));
    };

    return (
        <>
            <div className="grid place-items-center">
                <h1 className="text-slate-500 text-center p-8 text-7xl font-bold uppercase">
                    Test Page
                </h1>
                <p className="text-slate-300 px-8 text-center mt-3 text-2xl font-bold">
                    This page is dedicated to test a Real Time Chat in React,
                    using Laravel Echo and Pusher. Request will be done using
                    Jquery
                </p>
                <form onSubmit={handleSubmit}>
                    <button
                        type="submit"
                        className="mt-8 text-center border border-slate-800 bg-slate-800 text-white font-bold text-2xl p-3 rounded"
                    >
                        Broadcast
                    </button>
                </form>
            </div>
            <div className="mt-48 grid place-items-center">
                <span id="placeholder"></span>
            </div>
        </>
    );
}

export default Test;

预期结果

Client 1: Hello,From JS broadcast 
Client 2: Hello, From JS receiver

实际结果

Client 1:  Hello, From JS receiver; Hello,From JS broadcast
Client 2:  Hello, From JS receiver; Hello, From JS receiver

问题原因及解决方案

核心问题1:组件重复注册Echo监听

React组件每次渲染时都会执行函数内部代码,导致每次渲染都创建新的Echo实例并重复绑定chat事件,同一个事件被多次触发,进而重复渲染内容。

核心问题2:事件名称格式不一致

Blade代码绑定的是chat事件,React代码监听的是.chat(带前缀点号),Laravel Echo默认会给事件添加命名空间前缀,格式不匹配可能导致重复触发或事件识别异常。

解决方案步骤

  1. 用useEffect管理Echo生命周期
    仅在组件挂载时初始化一次Echo并绑定事件,卸载时清理监听,避免重复注册:

    import React, { useEffect } from "react";
    import $ from "jquery";
    import Echo from "laravel-echo";
    import Pusher from "pusher-js";
    import { CSRF_TOKEN } from "@/Utils/forms";
    
    function Test() {
        useEffect(() => {
            window.Pusher = Pusher;
            window.Pusher.logToConsole = true;
    
            const echoInstance = new Echo({
                broadcaster: "pusher",
                key: "c804af4b0e3beefe4880",
                cluster: "eu",
            });
    
            // 统一事件名称为chat,和Blade代码保持一致
            const listener = echoInstance.channel("public").listen("chat", function (data) {
                $.post("/receive", {
                    _token: CSRF_TOKEN,
                    message: "Hello, From JS",
                }).done((res) => $("#placeholder").last().after(res));
            });
    
            // 组件卸载时清理监听和Echo实例
            return () => {
                echoInstance.channel("public").stopListening("chat");
                echoInstance.disconnect();
            };
        }, []); // 空依赖数组确保只执行一次
    
        const handleSubmit = (e) => {
            e.preventDefault();
            // 从局部echoInstance获取socketId,避免全局变量问题
            const socketId = window.Echo?.socketId();
            $.ajax({
                url: "/broadcast",
                method: "POST",
                headers: socketId ? { "X-Socket-Id": socketId } : {},
                data: {
                    _token: CSRF_TOKEN,
                    message: "Hello, From JS",
                },
            }).done((res) => $("#placeholder").last().after(res));
        };
    
        return (
            <>
                {/* 页面结构保持不变 */}
                <div className="grid place-items-center">
                    <h1 className="text-slate-500 text-center p-8 text-7xl font-bold uppercase">
                        Test Page
                    </h1>
                    <p className="text-slate-300 px-8 text-center mt-3 text-2xl font-bold">
                        This page is dedicated to test a Real Time Chat in React,
                        using Laravel Echo and Pusher. Request will be done using
                        Jquery
                    </p>
                    <form onSubmit={handleSubmit}>
                        <button
                            type="submit"
                            className="mt-8 text-center border border-slate-800 bg-slate-800 text-white font-bold text-2xl p-3 rounded"
                        >
                            Broadcast
                        </button>
                    </form>
                </div>
                <div className="mt-48 grid place-items-center">
                    <span id="placeholder"></span>
                </div>
            </>
        );
    }
    
    export default Test;
    
  2. 统一事件名称
    将React中的监听事件改为chat,去掉前缀点号,和Blade代码、后端广播的事件名称保持一致。

  3. 正确传递X-Socket-Id
    确保X-Socket-Id正确传递,Laravel会自动排除当前客户端接收自己的广播,解决Client1收到自身消息的问题。


额外建议

  • 避免在React中用jQuery操作DOM,改用React状态管理(如useState)渲染消息,更符合React数据流模式,减少DOM操作引发的异常。
  • 检查后端广播代码,确保使用toOthers()方法排除当前客户端,同时确认事件名称与前端完全匹配。

内容的提问来源于stack exchange,提问作者Speed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 15:02:10