14 - Vue3 UI Framework - 代碼優化

回頭看下整個項目,代碼冗餘非常嚴重,所以接下來我們把代碼重新梳理優化一下

返回閱讀列表點擊 這裏

全局設定

爲了後續查閱和管理的方便,我們對全局的用例進行一個簡單的設定

我們在 src 目錄下創建一個 Global.cs 文件,代碼如下:

export const components = {
  'Button': {
    name: 'Button', title: 'Button 按鈕'
  },
  'Card': {
    name: 'Card', title: 'Card 卡片'
  },
  'Dialog': {
    name: 'Dialog', title: 'Dialog 對話框'
  },
  'Input': {
    name: 'Input', title: 'Input 輸入框'
  },
  'Switch': {
    name: 'Switch', title: 'Switch 開關'
  },
  'Table': {
    name: 'Table', title: 'Table 表格'
  },
  'Tabs': {
    name: 'Tabs', title: 'Tabs 標籤頁'
  },
}
export const guidances = {
  'introduction': { path: "introduction", title: "介紹" },
  'install': { path: "install", title: "安裝" },
  'start': { path: "start", title: "快速上手" }
}

注意

通過這個文件, 我們能夠清楚的知道本項目的主要文檔頁的信息

樣式簡化

每個 src/component 下的組件文檔頁都引用 ./example 下對應的案例, 所以我們考慮將引用放在一起,然後再暴露給組件的文檔頁

接下來我們再新建一個 src/component/contents 文件夾,爲每個組件新建對應的樣例管理文件,並且該文件需要包含如下特性:

  1. 所有樣例的引入
  2. 該樣例專屬的參數列表

下面我們以 JeremyInput 組件爲例子說明一下:

首先我們在 contents 文件夾下創建 Input.ts 文件,內容如下:

import JeremyComponent1 from "../examples/Input/Input1.example.vue";
import JeremyComponent2 from "../examples/Input/Input2.example.vue";
import JeremyComponent3 from "../examples/Input/Input3.example.vue";
import JeremyComponent4 from "../examples/Input/Input4.example.vue";

export default {
  components: [
    JeremyComponent1,
    JeremyComponent2,
    JeremyComponent3,
    JeremyComponent4,
  ],
  attributes: [
    { attr: 'value', desp: '綁定值', type: 'string', values: '字符串', default: '必填' },
    { attr: 'theme', desp: '類型', type: 'string', values: 'input / textarea', default: 'input' },
    { attr: 'rows ', desp: '行高,但當 theme 爲 input 時值恆爲1', type: 'number', values: '正整數', default: '5' },
    { attr: 'color', desp: '外邊框顏色', type: 'string', values: '任意合法顏色值', default: '#8c6fef' }
  ]
};

另外,我們需要將模板設置成遍歷的方式,顯然,每個組件文檔頁,都具有與 Button 組件文檔頁類似的結構

那麼我們可以把這個結構抽取出來,然後按照需要進行引入

先抽取結構,我選擇在 src/views 下新建 Content.vue 來承載這個結構

組件列表和參數表通過 src/components/contents 下的彙總來引入

引入後,根據字段名的不同,製作哈希表 LabyMap

再要求用戶傳入參數 props,通過 name 來指定選擇要顯示哪個組件文檔頁,通過 title 來指示現在的組件文檔頁的標題

<template>
  <h1>{{ title }}</h1>
  <br />
  <div
    class="container"
    v-for="({ ...component }, index) in components"
    :key="index"
  >
    <jeremy-card class="example">
      <h2>{{ component.__sourceCodeTitle }}</h2>
      <br />
      <component :is="component" />
      <br />
      <br />
      <code class="markdown-body">
        <pre
          v-if="visibility[index]"
          v-html="
            Prism.highlight(
              component.__sourceCode,
              Prism.languages.html,
              'html'
            )
          "
        ></pre>
      </code>

      <button class="toggle" @click="toggle(index)">
        <span class="close" v-if="visibility[index]">
          △
          <span class="desp">隱藏代碼</span>
        </span>
        <span class="open" v-else>
          ▽
          <span class="desp">顯示代碼</span>
        </span>
      </button>
    </jeremy-card>
    <br />
  </div>
  <jeremy-table bordered>
    <thead>
      <tr>
        <th v-for="(head, index) in heads" :key="index">{{ head.name }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(attribute, index) in attributes" :key="index">
        <td v-for="key in keys" :key="key" v-html="attribute[key]"></td>
      </tr>
    </tbody>
  </jeremy-table>
</template>

<script lang="ts">
import JeremyButtons from "../components/contents/Button";
import JeremyCards from "../components/contents/Card";
import JeremyDialogs from "../components/contents/Dialog";
import JeremyInputs from "../components/contents/Input";
import JeremySwitchs from "../components/contents/Switch";
import JeremyTables from "../components/contents/Table";
import JeremyTabss from "../components/contents/Tabs";

import { ref } from "vue";
import { JeremyCard, JeremyTable } from "jeremy-ui"
import "prismjs";
// import "prismjs/themes/prism.css";

const Prism = (window as any).Prism;

const JeremyMap = {
  Button: JeremyButtons,
  Card: JeremyCards,
  Dialog: JeremyDialogs,
  Input: JeremyInputs,
  Switch: JeremySwitchs,
  Table: JeremyTables,
  Tabs: JeremyTabss,
};

export default {
  props: {
    name: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
  },
  components: {
    JeremyCard,
    JeremyTable,
  },
  setup(props) {
    const { name, title } = props;
    const heads = [
      { name: "參數", identifier: "attr" },
      { name: "含義", identifier: "desp" },
      { name: "類型", identifier: "type" },
      { name: "可選值", identifier: "values" },
      { name: "默認值", identifier: "default" },
    ];
    const keys = heads.map((item: any) => item.identifier);

    const { components, attributes } = JeremyMap[name];
    const visibility = ref(components.map((item) => false));
    const toggle = (index) => {
      visibility.value[index] = !visibility.value[index];
    };
    return {
      title,
      Prism,
      heads,
      keys,
      components,
      attributes,
      visibility,
      toggle,
    };
  },
};
</script>
<style lang="scss" scoped>
$theme-color: #8c6fef;
.container {
  &:hover {
    > .example > .toggle > * > .desp {
      display: inline;
    }
  }
  > .example {
    > .toggle {
      display: block;
      width: 100%;
      height: 32px;
      border: none;
      transition: background-color 250ms;
      outline: none;
      &:focus {
        outline: none;
      }
      background: white;
      cursor: pointer;
      &:hover {
        background: fade-out($theme-color, 0.95);
      }
      > * > .desp {
        display: none;
      }
    }
  }
}
</style>

我們還可以抽取指南文檔頁的結構到 src/views/Guidance.vue

<template>
  <article class="markdown-body" v-html="md"></article>
</template>
<script>
import { ref } from "vue";
export default {
  props: {
    path: {
      type: String,
      required: true,
    },
  },
  setup(props) {
    const md = ref(null);
    import(`../markdown/${props.path}.md`).then(
      (res) => (md.value = res.default)
    );
    return { md };
  },
};
</script>

然後修改 router.ts,通過路由來傳遞參數

import { createWebHistory, createRouter } from 'vue-router'
import Home from './views/Home.vue'
import Document from './views/Document.vue'
import { guidances, components } from './Global'
import Guidance from './views/Guidance.vue'
import Content from './views/Content.vue'

function Route(path, component, props) {
  this.path = path
  this.component = component
  this.props = props
}

const guidancesRoutes = Object.keys(guidances).map(item => {
  return new Route(item, Guidance, guidances[item])
})
const componentsRoutes = Object.keys(components).map(item => {
  return new Route(item.toLowerCase(), Content, components[item])
})

const history = createWebHistory()
const router = createRouter({
  history,
  routes: [
    { path: '/', component: Home },
    {
      path: '/document', component: Document, children: [
        { path: '', redirect: '/document/introduction' },
        ...guidancesRoutes,
        ...componentsRoutes
      ]
    }
  ]
})
export default router

優化路由

這個 router.ts 看起來也還是很混亂,也需要再優化一下

注意到重複的部分出現在 children 字段下,而該字段的值是個數組,每個數組項都是一個包含 3 個字段的對象

那麼我們可以先定義這個對象:

function Route(path, component, props) {
    this.path = path
    this.component = component
    this.props = props
}

再觀察指南的路由,和組件的路由,有如下規律

  1. 指南的路由
    • path 值集合,與 Global.ts 中的 guidances 對象的 keys 是一致的
    • props 中傳入的鍵集合,與 Global.ts 中的 guidances 對象的 keys 是一致的
  2. 組件的路由
    • path 值集合,與 Global.ts 中的 components 對象的 keys.toLowerCase 是一致的
    • props 中傳入的鍵集合,與 Global.ts 中的 components 對象的 keys 是一致的

那麼容易得到指南路由的數組和組件路由的數組

const guidancesRoutes = Object.keys(guidances).map(item => {
    return new Route(item, Guidance, guidances[item])
})
const componentsRoutes = Object.keys(components).map(item => {
    return new Route(item.toLowerCase(), Content, components[item])
})

然後在路由配置的 children 字段下,使用 spread 語法展開這兩個數組即可

const router = createRouter({
    history,
    routes: [
        { path: '/', component: Home },
        {
            path: '/document', component: Document, children: [
                { path: '', redirect: '/document/introduction' },
                ...guidancesRoutes,
                ...componentsRoutes
            ]
        }
    ]
})

OK,現在的代碼,幾乎沒有冗餘了

項目地址 🎁

GitHub: https://github.com/JeremyWu917/jeremy-ui

官網地址 🌍

JeremyUI: https://ui.jeremywu.top

感謝閱讀 ☕

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章