首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Vue 魔法师 —— 重构“布局”

Vue 魔法师 —— 重构“布局”

作者头像
掘金安东尼
发布2022-09-22 12:16:21
发布2022-09-22 12:16:21
79800
代码可运行
举报
文章被收录于专栏:掘金安东尼掘金安东尼
运行总次数:0
代码可运行

title: Vue 魔法师 —— layouts date: 2021-03-17 tags:

  • vue categories:
  • 2021

正视布局

开篇想先问你一个问题:

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

你可能会回答:

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

我想一定类似这样吧:

代码语言:javascript
代码运行次数:0
运行
复制
<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: '组织管理' } }

代码语言:javascript
代码运行次数:0
运行
复制
nice!如果你的项目也类似这样的话,就可以继续看后文了~(不然就点赞👍再关闭❎退出啦😄)


## 发现痛点

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

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

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

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

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

附:[nuxtjs-layouts](https://zh.nuxtjs.org/examples/layouts/)

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

## 项目准备

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

vue create vue-layouts

代码语言:javascript
代码运行次数:0
运行
复制
你可以选择 Vue2+ 或 Vue3+,本篇都会作相应介绍。


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

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

代码语言:javascript
代码运行次数:0
运行
复制
然后在 App.vue 中代码如下:

#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; }

代码语言:javascript
代码运行次数:0
运行
复制
Home.vue 代码如下:

export default { name: 'Home' }

代码语言:javascript
代码运行次数:0
运行
复制
About.vue 代码如下:

export default { name: 'About' }

代码语言:javascript
代码运行次数:0
运行
复制
Contacts.vue 代码如下:

export default { name: "Contacts" }

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
再在 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')

代码语言:javascript
代码运行次数:0
运行
复制
好了,准备工作已经做好了。我们可以得到这样的一个界面:

![layouts1.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad6b5981cb904635b1bd5a93d45e1844~tplv-k3u1fbpfcp-watermark.image)

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

## 构造布局

重点来辣!

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

* Vue2

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

代码语言:javascript
代码运行次数:0
运行
复制
这一段代码看似简单,却是我们新布局系统的**核心**!

在这个模板中,我们加入了[**动态组件**](https://cn.vuejs.org/v2/guide/components-dynamic-async.html?) ,并且为之加入了名为 “layout” 的计算属性。

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

* 在 Vue3 中代码如下:

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

代码语言:javascript
代码运行次数:0
运行
复制
Vue 3 Composition API 的实现:

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

代码语言:javascript
代码运行次数:0
运行
复制
## 多种布局

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

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

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

新建一个文件:*layouts/AppLayoutLinks.vue*,把 App.vue 代码抽到此处,留下一个干净的 App.vue。

// 新建的 AppLayoutLinks.vue

export default { name: "AppLayoutLinks" } #nav { padding: 30px; } #nav a { font-weight: bold; color: #2c3e50; } #nav a.router-link-exact-active { color: #42b983; }

代码语言:javascript
代码运行次数:0
运行
复制
// 干净的 App.vue

#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; }

代码语言:javascript
代码运行次数:0
运行
复制
然后新建三种布局方式文件~

* 布局方式一:AppLayoutHome.vue

import AppLayoutLinks from '@/layouts/AppLayoutLinks' export default { name: "AppLayoutHome", components: { AppLayoutLinks } } .header { height: 5rem; background-color: green; }

代码语言:javascript
代码运行次数:0
运行
复制
* 布局方式二:AppLayoutAbout.vue

import AppLayoutLinks from '@/layouts/AppLayoutLinks' export default { name: "AppLayoutAbout", components: {AppLayoutLinks} } .header { height: 5rem; background-color: blue; }

代码语言:javascript
代码运行次数:0
运行
复制
* 布局方式三:AppLayoutContacts

import AppLayoutLinks from '@/layouts/AppLayoutLinks' export default { name: "AppLayoutContacts", components: { AppLayoutLinks } } .header { height: 5rem; background-color: red; }

代码语言:javascript
代码运行次数:0
运行
复制
这里 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

代码语言:javascript
代码运行次数:0
运行
复制
## 打完收工

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

```

打完收工!

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

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

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

撰文不易✍,点赞鼓励👍,关注我的公众号【掘金安东尼】😎,诚挚输出中......

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正视布局
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档