问题
There is a navigation bar where elements are displayed depending on the user's role:
<b-navbar-toggle
right
class="jh-navbar-toggler d-lg-none"
href="javascript:void(0);"
data-toggle="collapse"
target="header-tabs"
aria-expanded="false"
aria-label="Toggle navigation">
<font-awesome-icon icon="bars"/>
</b-navbar-toggle>
<b-collapse is-nav id="header-tabs">
<b-navbar-nav class="ml-auto">
<b-nav-item-dropdown
right
id="pass-menu"
v-if="hasAnyAuthority('ROLE_USER') && authenticated"
:class="{'router-link-active': subIsActive('/pass')}"
active-class="active"
class="pointer">
<span slot="button-content" class="navbar-dropdown-menu">
<font-awesome-icon icon="ticket-alt"/>
<span>First element</span>
</span>
</b-nav-item-dropdown>
<b-nav-item-dropdown
right
id="dictionaries-menu"
v-if="hasAnyAuthority('ROLE_ADMIN') && authenticated"
:class="{'router-link-active': subIsActive('/dictionary')}"
active-class="active"
class="pointer">
<span slot="button-content" class="navbar-dropdown-menu">
<font-awesome-icon icon="book"/>
<span>Second element</span>
</span>
</b-nav-item-dropdown>
</b-navbar-nav>
If I set the same roles for both elements, for example ROLE_USER
, everything works correctly. But when the roles are different for two elements, an infinite loop begins and everything freezes.
Component for checking roles:
@Component
export default class JhiNavbar extends Vue {
@Inject('loginService')
private loginService: () => LoginService;
@Inject('accountService') private accountService: () => AccountService;
public version = VERSION ? 'v' + VERSION : '';
private currentLanguage = this.$store.getters.currentLanguage;
private languages: any = this.$store.getters.languages;
private hasAnyAuthorityValue = false;
created() {}
public get authenticated(): boolean {
return this.$store.getters.authenticated;
}
public hasAnyAuthority(authorities: any): boolean {
this.accountService()
.hasAnyAuthorityAndCheckAuth(authorities)
.then(value => {
this.hasAnyAuthorityValue = value;
});
return this.hasAnyAuthorityValue;
}
}
Service for working with user account:
export default class AccountService {
constructor(private store: Store<any>, private router: VueRouter) {
this.init();
}
public init(): void {
this.retrieveProfiles();
}
public retrieveProfiles(): void {
axios.get('management/info').then(res => {
if (res.data && res.data.activeProfiles) {
this.store.commit('setRibbonOnProfiles', res.data['display-ribbon-on-profiles']);
this.store.commit('setActiveProfiles', res.data['activeProfiles']);
}
});
}
public retrieveAccount(): Promise<boolean> {
return new Promise(resolve => {
axios
.get('api/account')
.then(response => {
this.store.commit('authenticate');
const account = response.data;
if (account) {
this.store.commit('authenticated', account);
if (sessionStorage.getItem('requested-url')) {
this.router.replace(sessionStorage.getItem('requested-url'));
sessionStorage.removeItem('requested-url');
}
} else {
this.store.commit('logout');
this.router.push('/');
sessionStorage.removeItem('requested-url');
}
resolve(true);
})
.catch(() => {
this.store.commit('logout');
resolve(false);
});
});
}
public hasAnyAuthorityAndCheckAuth(authorities: any): Promise<boolean> {
if (typeof authorities === 'string') {
authorities = [authorities];
}
if (!this.authenticated || !this.userAuthorities) {
const token = localStorage.getItem('jhi-authenticationToken') || sessionStorage.getItem('jhi-authenticationToken');
if (!this.store.getters.account && !this.store.getters.logon && token) {
return this.retrieveAccount();
} else {
return new Promise(resolve => {
resolve(false);
});
}
}
for (let i = 0; i < authorities.length; i++) {
if (this.userAuthorities.includes(authorities[i])) {
return new Promise(resolve => {
resolve(true);
});
}
}
return new Promise(resolve => {
resolve(false);
});
}
public get authenticated(): boolean {
return this.store.getters.authenticated;
}
public get userAuthorities(): any {
return this.store.getters.account.authorities;
}
}
There is solution by @bigless:
...
private hasAdminAuthorityValue = false;
private hasUserAuthorityValue = false;
...
public hasAdminAuthority(): boolean {
this.accountService()
.hasAnyAuthorityAndCheckAuth('ROLE_ADMIN')
.then(value => {
this.hasAdminAuthorityValue = value;
});
return this.hasAdminAuthorityValue;
}
public hasUserAuthority(): boolean {
this.accountService()
.hasAnyAuthorityAndCheckAuth('ROLE_USER')
.then(value => {
this.hasUserAuthorityValue = value;
});
return this.hasUserAuthorityValue;
}
回答1:
This is caused by Vue's reactivity. You use async method in v-if
statement and it is mostly bad idea. Vue watches all reactive dependencies of this method and if any dependecy changes, it re-evaluates this statement again(which means calling that function again). Thats one part of the story.
Now why this happens only when roles are different. Because you use shared property hasAnyAuthorityValue
which is reactive dependency of the method mentioned above.
For example: it gets privileges for ROLE_USER, returns hasAnyAuthorityValue
immediately(sync), but later this value is fetched and updated in promise(async). It can work if async result doesn't change(one role), but when value of hasAnyAuthorityValue
is toggling between ROLE_USER and ROLE_ADMIN async results, it is trapped in endless loop.
There are many solutions. f.e. save results of both hasAnyAuthority(any)
as property value of data during create phase or use different property names for each role instead of shared one.
来源:https://stackoverflow.com/questions/62960887/infinite-loop-when-checking-roles-vuejs