Vue中作为根Props的外部JS类实例(如Keycloak)如何实现响应式?
问题根因
- Vue的响应式系统默认仅对纯JavaScript对象做深度代理,对于外部JS类的实例(比如Keycloak实例),出于性能和兼容性考虑,不会递归遍历其原型链、对所有属性做响应式劫持。你直接把类实例塞给
reactive,实际上只是代理了keycloak这个引用本身,类实例内部属性(比如token、tokenParsed)的更新不会被Vue的响应式系统捕获。 - 你写的
const props = reactive({ keycloak: keycloak })只是让props.keycloak这个引用变更能被监听,而定时器里更新的是keycloak实例内部的属性,引用本身没有变,自然触发不了响应式更新。
解决方案
方案1:抽离需要响应式的字段做手动同步(全版本兼容,最稳定)
这是兼容性最好、性能开销最小的方案,你不需要让整个Keycloak实例响应式,只需要把你用到的需要响应式的字段(比如token、用户信息)单独存到响应式对象里,每次Keycloak更新时手动同步值即可:
Vue3 实现示例
import { reactive, createApp, h } from 'vue' import App from './App.vue' // 单独定义需要响应式的字段 const keycloakState = reactive({ token: '', tokenParsed: {}, authenticated: false }) let initOptions = { url: 'http://127.0.0.1:8080/auth', realm: 'keycloak-demo', clientId: 'app-vue', onLoad: 'login-required' } let keycloak = Keycloak(initOptions); keycloak.init({ onLoad: initOptions.onLoad }).then((auth) => { if (!auth) { window.location.reload(); } else { console.info("Authenticated"); // 初始化同步字段 keycloakState.token = keycloak.token keycloakState.tokenParsed = keycloak.tokenParsed keycloakState.authenticated = auth createApp({ render () { // 把响应式的state传进去,或者把整个keycloak和state一起传 return h(App, { props: { keycloak, keycloakState } }) } }).mount('#app') } // Token Refresh setInterval(() => { keycloak.updateToken(70).then((refreshed) => { if (refreshed) { console.info('Token refreshed' + refreshed); // 刷新后手动同步字段 keycloakState.token = keycloak.token keycloakState.tokenParsed = keycloak.tokenParsed } else { console.warn('Token not refreshed, valid for ' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'); } }).catch(() => { console.error('Failed to refresh token'); }); }, 6000) }).catch(() => { console.error("Authenticated Failed"); });
Vue2 实现示例
和Vue3逻辑一致,只是把reactive换成Vue2的响应式定义即可:
// 把响应式字段挂在根实例的data上 new Vue({ el: '#app', data() { return { keycloakState: { token: '', tokenParsed: {}, authenticated: false } } }, render: h => h(App, { props: { keycloak: keycloak, keycloakState: this.keycloakState } }) }) // 同样在token刷新时手动更新keycloakState里的字段即可
方案2:强制对类实例做响应式转换(Vue3专属)
如果你确实需要整个类实例的所有属性都支持响应式,可以用Vue3 3.3以上版本提供的toReactive工具方法强制把类实例转换成纯对象的响应式代理:
import { reactive, toReactive } from 'vue' // 把类实例转成纯对象再包裹reactive const props = reactive({ keycloak: toReactive(keycloak) })
注意:该方案可能存在兼容性问题,比如类的原型方法调用异常,优先推荐方案1。
内容的提问来源于stack exchange,提问作者MickeyMouse
相关产品推荐
相关产品推荐

