Vue-Element-Admin Layout Component Hierarchy: How Navbar, Sidebar, AppMain, and TagsView Share State
The layout components in vue-element-admin communicate exclusively through Vuex store modules, using mapState and mapGetters to read shared state and dispatching actions to mutate sidebar visibility, visited tags, and cached views.
The vue-element-admin template organizes its interface through a hierarchical layout system where Navbar, Sidebar, AppMain, and TagsView operate as independent visual components. Rather than passing props or emitting events through the component tree, this layout component hierarchy relies on a centralized state management pattern that ensures perfect synchronization across all UI sections.
Centralized State Architecture
All shared data lives in dedicated Vuex modules within src/store/modules/. The layout component hierarchy consumes three primary modules:
app: Controls sidebar collapse state (sidebar.opened), device type detection (device), and UI size settingstagsView: MaintainsvisitedViewsarrays andcachedViewsfor<keep-alive>persistencesettings: Stores UI configuration flags includingtagsViewvisibility,fixedHeader, andshowSettings
Source references: src/store/modules/app.js defines toggleSideBar and closeSideBar actions, while src/store/modules/tagsView.js exports addView, delView, and delAllViews for tag management.
Layout Wrapper Coordination
The root layout component at src/layout/index.vue serves as the orchestration layer. It maps global state to local computed properties using mapState:
computed: {
...mapState({
sidebar: state => state.app.sidebar,
device: state => state.app.device,
showSettings: state => state.settings.showSettings,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
})
}
When users click the mobile overlay to close the sidebar, the wrapper dispatches the app/closeSideBar action without props drilling, ensuring the Sidebar component reacts immediately to the state change.
Navbar State Management
The src/layout/components/Navbar.vue component demonstrates the read-write pattern. It accesses sidebar status and user avatar through mapGetters:
computed: {
...mapGetters(['sidebar', 'avatar', 'device'])
}
When the hamburger icon triggers a toggle, Navbar dispatches the centralized action rather than emitting events upward:
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
async logout() {
await this.$store.dispatch('user/logout')
// ...
}
}
This ensures that Sidebar and any other components watching sidebar state update simultaneously.
Sidebar Reactive Rendering
The src/layout/components/Sidebar/index.vue remains a pure consumer of Vuex state. It retrieves accessible routes and sidebar collapse status via mapGetters:
computed: {
...mapGetters(['permission_routes', 'sidebar'])
}
The component renders the menu based on permission_routes and applies CSS classes according to sidebar.opened. It never mutates the store directly; it relies on Navbar or the ResizeHandler mixin to dispatch app/closeSideBar or app/toggleSideBar when the viewport changes.
TagsView Synchronization
The src/layout/components/TagsView/index.vue coordinates with the router to maintain visited tags. It reads from the tagsView module:
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.permission.routes
}
}
When the route changes, the component dispatches tagsView/addView:
watch: {
$route() {
this.addTags()
this.moveToCurrentTag()
}
},
methods: {
addTags() {
if (this.$route.name) {
this.$store.dispatch('tagsView/addView', this.$route)
}
}
}
All tag mutations—adding, deleting, refreshing, or closing all—flow through Vuex actions such as tagsView/delView and tagsView/delAllViews, ensuring AppMain's <keep-alive> stays synchronized via the cachedViews array.
AppMain and Keep-Alive Integration
The src/layout/components/AppMain.vue connects the tag system to Vue's component caching. It accesses cachedViews from the store:
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
}
}
This computed property feeds the <keep-alive> include attribute:
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
When TagsView commits a mutation adding a route to cachedViews (typically when meta.noCache is false), AppMain automatically includes that component in the keep-alive cache without direct component-to-component communication.
Global Resize Handling
The src/layout/mixin/ResizeHandler.js provides cross-cutting responsive behavior. It dispatches Vuex actions when viewport dimensions change:
methods: {
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile()
this.$store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
}
This ensures the layout component hierarchy remains synchronized when users resize their browser or switch devices, automatically closing the sidebar on mobile without requiring individual components to handle resize logic.
Summary
- Vuex as single source of truth: All layout state resides in
app,tagsView,settings, andpermissionmodules, eliminating prop drilling. - Read via getters: Components use
mapStateandmapGettersto access sidebar status, visited tags, cached views, and device type. - Write via actions: Mutations occur exclusively through dispatched actions like
app/toggleSideBar,tagsView/addView, andtagsView/delView. - Automatic synchronization: Changes propagate reactively to all dependent components, keeping Sidebar, Navbar, TagsView, and AppMain perfectly aligned without direct references.
- Cross-component coordination: Actions in one component (e.g., Navbar toggling the sidebar) instantly affect the entire layout hierarchy through shared Vuex state.
Frequently Asked Questions
How does the Sidebar know when to collapse without receiving props from Navbar?
The Sidebar component in src/layout/components/Sidebar/index.vue reads the collapse state directly from Vuex using mapGetters(['sidebar']). When Navbar dispatches app/toggleSideBar, the Vuex mutation updates the state, and Sidebar's computed property reacts automatically. This eliminates the need for prop passing or event emitting between siblings.
Why does TagsView use $store.state directly instead of mapGetters?
While mapGetters would work, TagsView in src/layout/components/TagsView/index.vue accesses visitedViews and routes via direct state references (this.$store.state.tagsView.visitedViews) to illustrate explicit dependency on the raw state structure. Both approaches are valid Vuex patterns; the project uses mapGetters in components like Navbar and Sidebar for brevity, while TagsView demonstrates direct store access for clarity in tracking visited routes.
How does AppMain keep router views cached without communicating with TagsView?
AppMain in src/layout/components/AppMain.vue binds the <keep-alive> include attribute to this.$store.state.tagsView.cachedViews. When TagsView dispatches tagsView/addView for a route with meta.noCache !== true, the Vuex module commits the route name to cachedViews. AppMain's computed property detects this change and updates the keep-alive include list, caching the component instance without direct component interaction.
What triggers the sidebar to close automatically on mobile devices?
The src/layout/mixin/ResizeHandler.js mixin dispatches app/toggleDevice and app/closeSideBar when the viewport width falls below a mobile threshold (typically 992px). This global handler ensures the sidebar closes without animation on mobile devices, maintaining responsive behavior across the entire layout component hierarchy. The Sidebar component reacts to this state change through its sidebar computed getter, applying CSS classes to hide or show the menu.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →