在Laravel与Vue.js中实现WebSocket以实现实时数据更新
用WebSocket替代Laravel+Vue/Quasar应用的轮询方案
背景
我的应用后端基于Laravel,前端采用Vue.js和Quasar框架。原本Vue视图每5秒发送一次请求做轮询,现在已经完成Pusher与Laravel Echo的配置,想要用WebSocket替代轮询,实现监听数据表的所有更新,避免频繁请求和加载状态的频繁触发。
目前前端表格的分页逻辑由前端实现,通过请求后端获取数据展示;加载逻辑为发起请求时显示加载状态,请求完成后隐藏,轮询时每5秒会触发一次加载。
当前轮询代码片段
初始化轮询的init函数
async init() { this.onRequest({ pagination: this.pagination, filter: undefined }) this.timeout = setTimeout(this.init, 5000); }
请求数据的onRequest函数
onRequest(props) { const { page, rowsPerPage } = props.pagination const { search } = this this.loading = true DataService.index({ params: { page, rowsPerPage, search } }).then((payrolls) => { this.data = payrolls.data this.pagination.rowsPerPage = payrolls.per_page this.pagination.page = payrolls.current_page this.pagination.rowsNumber = payrolls.total this.loading = false }).catch(() => { this.loading = false }) }
实现WebSocket监听数据表更新的步骤
1. 后端配置:触发数据表更新事件
在Laravel的模型中,利用模型事件(如created/updated/deleted)或者观察者,当数据表有更新时,通过Pusher发送事件。
示例:模型事件触发广播
比如在Payroll模型的boot方法中添加:
protected static function boot() { parent::boot(); // 监听创建、更新、删除事件 static::created(function ($payroll) { broadcast(new PayrollUpdated($payroll, 'created'))->toOthers(); }); static::updated(function ($payroll) { broadcast(new PayrollUpdated($payroll, 'updated'))->toOthers(); }); static::deleted(function ($payroll) { broadcast(new PayrollUpdated($payroll, 'deleted'))->toOthers(); }); }
创建广播事件
生成PayrollUpdated事件:
php artisan make:event PayrollUpdated
在事件类中实现ShouldBroadcast接口,并定义广播频道和数据:
namespace App\Events; use App\Models\Payroll; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class PayrollUpdated implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $payroll; public $action; /** * 创建事件实例 */ public function __construct(Payroll $payroll, string $action) { $this->payroll = $payroll; $this->action = $action; } /** * 指定事件广播的频道 */ public function broadcastOn(): array { // 根据需求选择公共频道或私有频道 return [ new Channel('payrolls'), ]; } // 自定义广播数据(可选) public function broadcastWith(): array { return [ 'data' => $this->payroll->toArray(), 'action' => $this->action, 'timestamp' => now()->toDateTimeString() ]; } }
2. 前端配置:监听WebSocket事件并更新数据
在Vue组件中,通过Laravel Echo监听后端广播的PayrollUpdated事件,根据事件类型(创建/更新/删除)更新前端表格数据,无需重新发起请求。
步骤1:移除轮询逻辑
删除init函数中的定时器,停止轮询:
// 移除定时器 clearTimeout(this.timeout); // 不再调用setTimeout(this.init, 5000)
步骤2:监听WebSocket事件
在组件的mounted钩子中初始化Laravel Echo监听:
mounted() { // 首次加载数据(保留原有分页逻辑) this.onRequest({ pagination: this.pagination, filter: undefined }); // 监听payrolls频道的PayrollUpdated事件 window.Echo.channel('payrolls') .listen('PayrollUpdated', (e) => { const { data, action } = e; switch(action) { case 'created': this.handleCreated(data); break; case 'updated': this.handleUpdated(data); break; case 'deleted': this.handleDeleted(data); break; } }); }, beforeUnmount() { // 组件销毁时取消监听 window.Echo.channel('payrolls').stopListening('PayrollUpdated'); clearTimeout(this.timeout); }
步骤3:实现数据更新逻辑
根据事件类型更新前端数据,同时处理分页统计:
methods: { // 处理新增数据 handleCreated(newPayroll) { // 如果当前页是第一页且数据未达每页条数,直接添加到列表 if (this.pagination.page === 1 && this.data.length < this.pagination.rowsPerPage) { this.data.unshift(newPayroll); } // 更新总条数 this.pagination.rowsNumber += 1; }, // 处理更新数据 handleUpdated(updatedPayroll) { // 找到对应数据并替换 const index = this.data.findIndex(item => item.id === updatedPayroll.id); if (index !== -1) { this.data.splice(index, 1, updatedPayroll); } }, // 处理删除数据 handleDeleted(deletedPayroll) { // 移除对应数据 this.data = this.data.filter(item => item.id !== deletedPayroll.id); // 更新总条数 this.pagination.rowsNumber -= 1; // 如果当前页数据为空且不是第一页,自动跳转到上一页 if (this.data.length === 0 && this.pagination.page > 1) { this.pagination.page -= 1; this.onRequest({ pagination: this.pagination, filter: undefined }); } }, // 保留原有的onRequest函数用于分页和搜索 onRequest(props) { const { page, rowsPerPage } = props.pagination const { search } = this this.loading = true DataService.index({ params: { page, rowsPerPage, search } }).then((payrolls) => { this.data = payrolls.data this.pagination.rowsPerPage = payrolls.per_page this.pagination.page = payrolls.current_page this.pagination.rowsNumber = payrolls.total this.loading = false }).catch(() => { this.loading = false }) } }
3. 注意事项
- 频道权限:如果需要私有频道(比如只允许特定用户查看数据),可以使用
PrivateChannel,并在routes/channels.php中定义授权逻辑。 - 数据一致性:多用户同时操作时,WebSocket事件能保证数据实时同步,需注意处理并发场景(如同一数据被多次更新)。
- 首次加载:保留
onRequest的首次调用用于加载初始分页数据,后续数据更新通过WebSocket事件处理,无需轮询。 - 错误处理:可给Echo添加错误监听,处理WebSocket连接断开的情况,比如自动重连或 fallback 到轮询(可选)。
内容的提问来源于stack exchange,提问作者Orlando Correa Jiménez
相关产品推荐
相关产品推荐

