Vue 项目中如何重构“布局”?

正视布局

开篇想先问你一个问题:

“你认为你目前所在 Vue 项目中的 layouts 设置有什么问题吗?”

Vue 项目中如何重构“布局”?

你可能会回答:

“没有啥问题啊。因为不就是简单的在外层套一个 Layout 组件吗?”

我想一定类似这样吧:

<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>

或者这样,以本瓜所在项目举例,Layout 设置:

  • 如果你用过 vue-element-admin 一定很熟悉这样的路由设置(业务组件是 Layout 组件的子组件)
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: '组织管理',
iconClass: 'orgManage',
children: [
orgList,
orgDetail,
],
meta: {
roles: ['isAdmin', 'ordinaryAdmin'],
title: '组织管理'
}
}

nice!如果你的项目也类似这样的话,就可以继续看后文了~(不然就点赞再关闭退出啦)

发现痛点

这样在外层包裹设置 Layout 似乎也没什么不对,但是我们细想一下:

  1. 我们需要在不同的页面 import Layout,而“重复引入”永远是程序员最讨厌的事之一;
  2. 我们必须使 Layout 包裹着我们的内容,这多少会有限制;
  3. 这样做使我们的代码更重并且迫使组件担负起渲染布局的责任(组件和布局没有拆分的够开);

虽然这些其实也并不是一些什么大不了的点,但是由于受到 NuxtJS 的启发,所以咱们决定进行 breaking change,改变这一现状。

那 NuxtJS 究竟启发了些什么呢?简而言之,即:

你可以定义【布局】为组件的一个【属性】,而不是设置一个个布局父组件到你的应用中。

附:​​nuxtjs-layouts​

让我们看看在我们的 Vue 项目中如何实现这一启发?

项目准备

我们依然用 Vue CLI 来快速构建我们的项目:

vue create vue-layouts

你可以选择 Vue2+ 或 Vue3+,本篇都会作相应介绍。

我们清除一些初始化带来的不必要的文件,比如 ​​HelloWorld.vue​​,然后新建新文件,得此目录:

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

然后在 App.vue 中代码如下:

<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>

Home.vue 代码如下:

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

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

About.vue 代码如下:

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

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

Contacts.vue 代码如下:

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

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

router.js 代码如下:

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

再在 main.js 调用 router

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')

好了,准备工作已经做好了。我们可以得到这样的一个界面:

Vue 项目中如何重构“布局”?

就是三个简单的路由跳转,组件切换。到这里肯定没啥问题~

构造布局

重点来辣!

我们新建一个 layouts/AppLayout.vue 组件。

  • 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>

这一段代码看似简单,却是我们新布局系统的核心

在这个模板中,我们加入了​动态组件​ ,并且为之加入了名为 “layout” 的计算属性。

在计算属性中我们可以看到它会【根据路由】返回【对应的布局组件】并加载到【动态组件】中去,否则就启用默认布局 —— defaultLayout。

  • 在 Vue3 中代码如下:
<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>

Vue 3 Composition API 的实现:

<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>

多种布局

有了上一节的精髓,再看我们如何完善我们的布局系统~

还记得我们在初始化时准备的三个核心组件:​​Home​​、​​About​​、​​Contacts​​,为此我们准备创建三种布局方式。(当然,你可以自定义任意多种布局方式,随你喜欢。)

在此之前我们对 App.vue 进行一个小重构:

新建一个文件:layouts/AppLayoutLinks.vue,把 App.vue 代码抽到此处,留下一个干净的 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>

// 干净的 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>

然后新建三种布局方式文件~

  • 布局方式一: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>
  • 布局方式二: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>
  • 布局方式三: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>

这里 demo 布局方式就简单的颜色区别作以区分,主要是“会意”。默认布局就不作颜色更改,不做赘述。

配置路由

如果你有仔细看 构造布局 这一精髓,你肯定看到了 this.$route.meta.layout 这一调用。所以我们需要返回路由进行设置,代码如下:

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

打完收工

上面的都设置好了,最后我们需要将 AppLayout.vue 绑定在 App.vue 上:

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

打完收工!

你可以把项目 download 到本地跑跑看:​​github 地址​

Vue 项目中如何重构“布局”?

如此,我们便实现了一个新的布局系统。灵感来自沙宣美发系列,哦,不,灵感来自 NuxtJS~你感受到了吗?

综上:我们以往的布局就是包裹在组件里面,或者包裹在路由里面,往往都需要多处引用。如果存在多种布局方式,就更为繁琐,没有一个抽离的布局系统作清晰的划分。本文通过 “动态组件”+“监听属性”+“路由配置”+“全局挂载” 的方式将布局系统抽离,免去多处引入,免去不清晰的目录结构。在构建项目初期,就搭建出这一套布局,会是相当明智的做法!如果是老项目,存在多种布局,有空也可以重构一下,感受感受。为什么不呢?

文章版权声明

 1 原创文章作者:、Mushie,如若转载,请注明出处: https://www.52hwl.com/36250.html

 2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈

 3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)

 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年7月15日 上午9:59
下一篇 2023年7月15日 上午10:00