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

