Face up to the layout

I want to start by asking you a question:

“Do you think there’s something wrong with the layouts in your current Vue project?”

You might reply:

“There is no problem. Because it’s simply a Layout component on top of it.”

I think it must be something like this:

<template>
  <MyLayout>
    <h1>Here is my page content</h1>
  </MyLayout>
</template>

<script>
import MyLayout from '@/layouts/MyLayout.vue'
export default {
  name: 'MyPage',
  components: { MyLayout }
}
</script>
Copy the code

Or, for example, the Layout of the project where the melon is located:

  • If you’ve used Vue-element-admin, you’ll be familiar with routing Settings like this (the business component is a child of the Layout component).
const AdminLayout = () => import('@/views/admin/homepage/layout.vue') const OrgList = () => import('@/views/admin/admOrg/orgList.vue') const OrgDetail = () => import('@/views/admin/admOrg/orgDetail.vue') export Const admOrgRouters = {path: '/orgManage', Component: AdminLayout, Redirect: '/orgList', Name: 'Organization ', iconClass: 'roles: ['isAdmin', 'ordinaryAdmin'], title:' roles: ['isAdmin', 'ordinaryAdmin']Copy the code

Nice! If you have a project like this, you can keep reading ~ (otherwise, click 👍 and close ❎ to exit 😄)

Find pain points

This doesn’t seem like a bad idea, but let’s think about it for a second:

  1. We need to import layouts on different pages, and “reimporting” is always one of the most annoying things programmers do;
  2. We have to have a Layout wrap around our content, which is somewhat limited;
  3. Doing so makes our code heavier and forces the component to take responsibility for rendering the layout (the component and layout are not split apart enough);

Although these are not big deal points, we decided to carry out breaking change to change this situation because we were inspired by NuxtJS.

So what does NuxtJS inspire? In short, that is:

You can define a layout as a property of a component, rather than setting individual layout parent components into your application.

Add: nuxtjs – layouts

Let’s see how we can implement this inspiration in our Vue project.

Project preparation

We still use Vue CLI to build our project quickly:

vue create vue-layouts
Copy the code

You can choose Vue2+ or Vue3+, both of which are covered here.

Let’s clean up some of the unnecessary files from initialization, such as helloworld.vue, and create a new file to this directory:

--src
----views
------About.vue
------Contacts.vue
------Home.vue
----App.vue
----main.js
----router.js
Copy the code

Then in app.vue the code looks like this:

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/contacts">Contacts</router-link>
    </div>
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#nav {
  padding: 30px;
}
#nav a {
  font-weight: bold;
  color: #2c3e50;
}
#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
Copy the code

The code of home.vue is as follows:

<template>
  <div>
    <h1>This is a home page</h1>
  </div>
</template>

<script>
export default {
  name: 'Home'
}
</script>
Copy the code

About. Vue code is as follows:

<template>
  <div>
    <h1>This is an about page</h1>
  </div>
</template>

<script>
export default {
  name: 'About'
}
</script>
Copy the code

The contacts. vue code is as follows:

<template>
  <div>
    <h1>This is a contacts page</h1>
  </div>
</template>

<script>
export default {
  name: "Contacts"
}
</script>

Copy the code

The router.js code is as follows:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  },
  {
    path: '/contacts',
    name: 'Contacts',
    component: () => import('@/views/Contacts.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
Copy the code

Then call router in main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router: router
}).$mount('#app')

Copy the code

All right, the preparations have been made. We can get an interface like this:

Three simple route hops and component switches. I’m sure I’ll have no problem getting here

Structure layout

Focus on spicy!

We’re about to create a new layouts/ applayout.vue component.

  • Vue2
<template>
  <component :is="layout">
    <slot />
  </component>
</template>

<script>
const defaultLayout = 'AppLayoutDefault'
export default {
  name: "AppLayout",
  computed: {
    layout() {
      const layout = this.$route.meta.layout || defaultLayout
      return () => import(`@/layouts/${layout}.vue`)
    }
  }
}
</script>
Copy the code

This piece of code looks simple, but it is the core of our new layout system!

In this template, we added the dynamic component, and we added a calculation property called “Layout” for it.

In the calculated properties we can see that it will return the layout component and load it into the dynamic component, otherwise it will enable the defaultLayout defaultLayout.

  • In Vue3 the code looks like this:
<template> <component :is="layout"> <slot /> </component> </template> <script> import AppLayoutDefault from './AppLayoutDefault' export default { name: "AppLayout", data: () => ({ layout: AppLayoutDefault }), watch: { $route: { immediate: true, async handler(route) { try { const component = await import(`@/layouts/${route.meta.layout}.vue`) this.layout = component? .default || AppLayoutDefault } catch (e) { this.layout = AppLayoutDefault } } } } } </script>Copy the code

Vue 3 Composition API implementation:

<template> <component :is="layout"> <slot /> </component> </template> <script> import AppLayoutDefault from './AppLayoutDefault' import { markRaw, watch } from 'vue' import { useRoute } from 'vue-router' export default { name: 'AppLayout', setup() { const layout = markRaw(AppLayoutDefault) const route = useRoute() watch( () => route.meta, async meta => { try { const component = await import(`@/layouts/${meta.layout}.vue`) layout.value = component? .default || AppLayoutDefault } catch (e) { layout.value = AppLayoutDefault } }, { immediate: true } ) return { layout } } } </script>Copy the code

A variety of layout

With that in mind, let’s look at how we can improve our layout system

Remember that we prepared the three core components at initialization: Home, About, and Contacts, and for that we are going to create three layouts. (Of course, you can customize as many layouts as you like.)

Before we do that we do a small refactoring of app.vue:

To create a new file: layouts/AppLayoutLinks vue, the App. The vue code to draw here, leaving a clean App. Vue.

// 新建的 AppLayoutLinks.vue

<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/contacts">Contacts</router-link>
  </div>
</template>

<script>
export default {
name: "AppLayoutLinks"
}
</script>

<style scoped>
#nav {
  padding: 30px;
}
#nav a {
  font-weight: bold;
  color: #2c3e50;
}
#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
Copy the code

// Clean app.vue

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>
Copy the code

Then create the three layout files ~

  • Layout method 1: applayouthome.vue
<template> <div> <header class="header" /> <AppLayoutLinks /> <slot /> </div> </template> <script> import AppLayoutLinks  from '@/layouts/AppLayoutLinks' export default { name: "AppLayoutHome", components: { AppLayoutLinks } } </script> <style scoped> .header { height: 5rem; background-color: green; } </style>Copy the code
  • Layout method 2: applayoutabout.vue
<template> <div> <header class="header" /> <AppLayoutLinks /> <slot /> </div> </template> <script> import AppLayoutLinks  from '@/layouts/AppLayoutLinks' export default { name: "AppLayoutAbout", components: {AppLayoutLinks} } </script> <style scoped> .header { height: 5rem; background-color: blue; } </style>Copy the code
  • Layout mode 3: AppLayoutContacts
<template> <div> <header class="header" /> <AppLayoutLinks /> <slot /> </div> </template> <script> import AppLayoutLinks  from '@/layouts/AppLayoutLinks' export default { name: "AppLayoutContacts", components: { AppLayoutLinks } } </script> <style scoped> .header { height: 5rem; background-color: red; } </style>Copy the code

Here demo layout on the simple color distinction to distinguish, mainly “understanding”. The default layout has no color changes and is not verbose.

Configure the routing

If you’ve looked closely at the essence of constructing layouts, you’ve definitely seen this.$route.meta. Layout. So we need to return the route to set, as follows:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: {
      layout: 'AppLayoutHome'
    }
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue'),
    meta: {
      layout: 'AppLayoutAbout'
    }
  },
  {
    path: '/contacts',
    name: 'Contacts',
    component: () => import('@/views/Contacts.vue'),
    meta: {
      layout: 'AppLayoutContacts'
    }
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
Copy the code

Finished work

Vue is bound to app.vue:

  <div id="app">
    <AppLayout>
      <router-view />
    </AppLayout>
  </div>
Copy the code

Call it a day!

You can download the project locally: Github address

With this, we have implemented a new layout system. Inspired by The Sassoon hair collection, oh no, inspired by NuxtJS~ did you feel it?

To summarize: Our previous layouts were wrapped in components, or wrapped in routes, often requiring multiple references. If there are multiple layout methods, it is more complicated, without a separate layout system for clear division. In this paper, the “dynamic component” + “listening attribute” + “route configuration” + “global mount” method is used to pull out the layout system, so as to avoid multiple introduction and avoid unclear directory structure. It would be wise to set up this layout early in the project! If it is an old project and there are multiple layouts, you can refactor it at your leisure and feel the feeling. Why not?

✍, write not easy, upward encourage 👍, pay attention to my public number [nuggets Anthony] 😎, sincere output in……