最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue 魔法师 —— 重构“布局”

    正文概述 掘金(掘金安东尼)   2021-03-18   488

    正视布局

    开篇想先问你一个问题:

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

    你可能会回答:

    我想一定类似这样吧:

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

    多种布局

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

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

    在此之前我们对 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~你感受到了吗?

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

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


    起源地下载网 » Vue 魔法师 —— 重构“布局”

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元