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

Vue3中类内部修改响应式变量后DOM不更新如何解决?

问题

我有一个Vue 3.x单文件组件(SFC)和自定义TypeScript类Client,该类负责与服务器建立WebSocket连接,接收包含用户数字符串的消息。

在组件的mounted()钩子中直接修改this.client.userCount = "something"时,页面能正常响应更新;但当WebSocket收到消息后,在Client类内部修改this.userCount时,变量值确实变了,但页面没有重新渲染。

需要调整哪些设置,才能让类内部对变量的修改被Vue响应式系统识别,触发DOM更新?

组件代码:

<script setup>
  import { Client } from '@/path/to/client';
</script>

<template>
  <span>{{ users }}</span> online
</template>

<script>
  export default {
    data() {
      return {
        client: new Client(),
      }
    },
    computed: {
      users() {
        return this.client.userCount;
      },
    },
  }
</script>

Client.ts代码:

export class Client {

  // 用户计数器
  public userCount: string = "?";
  public connected: boolean = false;

  constructor() {
        this.websocket = new WebSocket("MYWEBSITE");

        this.websocket.onopen = () => {
            console.log("客户端连接已打开");
            this.connected = true;
        }

        // 处理收到的WebSocket数据
        this.websocket.onmessage = (event: any) => {
            try {
                let data = JSON.parse(event.data);
                console.log('收到消息', data);
                switch (data.type) {
                    case 'users':
                        this.userCount = data.count.toString();
                        break;
                    default:
                        console.error("不支持的事件类型", data);
                }
            } catch (err) {
                console.log("处理消息时出错:", err);
                console.log(event);
            }
        };

        this.websocket.onclose = () => {
            this.connected = false;
            console.log('客户端连接已关闭');
        }
    }
}
解决方案

Vue的响应式系统只会追踪它自己创建的响应式对象的属性变化。你直接实例化的Client类对象不是响应式的,所以类内部修改属性时,Vue无法感知到变化。下面是几种可行的解决方法:

方法1:将Client实例转为响应式对象

在组件的data中,用Vue的reactive()函数包裹Client实例,让整个对象变成响应式的:

修改组件的<script>部分:

<script>
import { reactive } from 'vue';
import { Client } from '@/path/to/client';

export default {
  data() {
    return {
      // 用reactive包裹Client实例,使其成为响应式对象
      client: reactive(new Client()),
    }
  },
  computed: {
    users() {
      return this.client.userCount;
    },
  },
}
</script>

这样,不管是外部还是Client类内部修改userCount,Vue都能追踪到变化并更新DOM。

方法2:在Client类内部使用ref管理响应式属性

如果不想把整个Client实例转为响应式,可以在类内部用Vue的ref()来管理需要响应式的属性:

修改Client.ts:

import { ref } from 'vue';

export class Client {
  // 用ref包裹userCount和connected,使其成为响应式引用
  public userCount = ref<string>("?");
  public connected = ref<boolean>(false);

  constructor() {
        this.websocket = new WebSocket("MYWEBSITE");

        this.websocket.onopen = () => {
            console.log("客户端连接已打开");
            this.connected.value = true; // 修改时需要访问.value
        }

        this.websocket.onmessage = (event: any) => {
            try {
                let data = JSON.parse(event.data);
                console.log('收到消息', data);
                switch (data.type) {
                    case 'users':
                        this.userCount.value = data.count.toString();
                        break;
                    default:
                        console.error("不支持的事件类型", data);
                }
            } catch (err) {
                console.log("处理消息时出错:", err);
                console.log(event);
            }
        };

        this.websocket.onclose = () => {
            this.connected.value = false;
            console.log('客户端连接已关闭');
        }
    }
}

然后组件的计算属性要访问ref的.value:

<script>
import { Client } from '@/path/to/client';

export default {
  data() {
    return {
      client: new Client(),
    }
  },
  computed: {
    users() {
      return this.client.userCount.value; // 访问ref的value属性
    },
  },
}
</script>

方法3:通过事件通知组件更新

如果不想依赖Vue的响应式API修改Client类,可以在类内部触发自定义事件,让组件监听事件并更新本地响应式变量:

修改Client.ts,添加事件触发逻辑:

export class Client {
  public userCount: string = "?";
  public connected: boolean = false;
  private listeners: Record<string, Function[]> = {};

  constructor() {
        this.websocket = new WebSocket("MYWEBSITE");

        this.websocket.onopen = () => {
            console.log("客户端连接已打开");
            this.connected = true;
            this.emit('connected', true);
        }

        this.websocket.onmessage = (event: any) => {
            try {
                let data = JSON.parse(event.data);
                console.log('收到消息', data);
                switch (data.type) {
                    case 'users':
                        this.userCount = data.count.toString();
                        this.emit('userCountUpdated', this.userCount); // 触发事件
                        break;
                    default:
                        console.error("不支持的事件类型", data);
                }
            } catch (err) {
                console.log("处理消息时出错:", err);
                console.log(event);
            }
        };

        this.websocket.onclose = () => {
            this.connected = false;
            this.emit('connected', false);
            console.log('客户端连接已关闭');
        }
    }

    // 自定义事件监听方法
    on(event: string, callback: Function) {
        if (!this.listeners[event]) {
            this.listeners[event] = [];
        }
        this.listeners[event].push(callback);
    }

    // 自定义事件触发方法
    emit(event: string, ...args: any[]) {
        if (this.listeners[event]) {
            this.listeners[event].forEach(callback => callback(...args));
        }
    }
}

然后组件中监听事件,更新本地响应式变量:

<script>
import { Client } from '@/path/to/client';

export default {
  data() {
    return {
      client: new Client(),
      userCount: "?", // 本地响应式变量
    }
  },
  mounted() {
    // 监听userCount更新事件
    this.client.on('userCountUpdated', (count) => {
      this.userCount = count;
    });
    // 初始化同步一次值
    this.userCount = this.client.userCount;
  },
  computed: {
    users() {
      return this.userCount;
    },
  },
}
</script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 03:25:19