Skip to content

开发技巧

介绍 Teek 开发路程的一些技巧。

规范

Teek 建议在进行项目开发时,一个文件的代码行数推荐 300 行以下,最好不超过 500 行,禁止超过 1000 行。

如果超过 300+ 行,应该考虑下是否可以拆分为多个文件,这是一个良好的 结构化思维和分治思维

提示

Teek 建议您在开发前先思考有哪些模块,然后分别创建模块文件,而不是先在一个文件写完,再拆分。

举个例子:

代码合在一个文件:

html
<div>
  <div class="header">
    <img src="logo.png" />
    <h1>网站名称</h1>
  </div>
  <div class="main-content">
    <div class="banner">
      <ul>
        <li><img src="banner1.png" /></li>
        <!--   省略n行代码      -->
      </ul>
    </div>
    <div class="post-list">
      <!--   省略n行代码      -->
    </div>
    <!--   省略n行代码      -->
  </div>
</div>

将代码进行模块化,根据功能/布局/逻辑等进行拆分:

html
<div>
  <header />
  <main>
    <Banner />
    <PostList />
    <Card />
    <AboutMe />
  </main>
  <footer />
</div>

假设您没有参与过该项目开发,现在由您来开始开发 PostList 模块的功能,我相信您更喜欢第二种,因为它已经明确的在向您挥手。

SSR 兼容

在 VitePress 主题开发时,请考虑 SSR 兼容性。否则在构建项目时,报错:window/document is not defined

关于 SSR 兼容性,VitePress 官方提供了 SSR 兼容性 的文档可以参考,文档里介绍了几个方式如何兼容 SSR。

但是 Teek 在这里提供一个 VitePress 官方没有 直接 说明的一种方式,这也是 Teek 兼容 SSR 的方式,即:

在任何访问浏览器或调用 DOM API 的代码前加上 SSR 环境校验

首先自定义一个 SSR 环境检验变量:

ts
const isClient = typeof window !== "undefined" && typeof document !== "undefined";

然后在使用 DOM API 之前加上这个校验,这样就防止构建时报错:

vue
<script setup lang="ts">
// Teek 已经内置了 isClient 函数
import { isClient } from "vitepress-theme-teek";

const init = () => {
  if (!isClient) return;

  const xxDom = document.querySelector(".xx");
  // ...
};

init();
</script>

<template></template>

如果您的代码在 Vue 组件的 beforeMountmounted 钩子中执行,则无需考虑 SSR,Vue 已经处理了。

利用对象/数组减少 HTML 编写

对象形式

templateifif-else 判断

vue
<script setup lang="ts">
import { A, B, C, D } from "./components";
import { useData } from "vitepress";

const { theme } = useData();
const provider = theme.value.provider;
</script>

<template>
  <template v-if="provider === 'a'" name="a"><A /></template>
  <template v-else-if="provider === 'b'" name="b"><B /></template>
  <template v-else-if="provider === 'd'" name="c"><C /></template>
  <template v-else-if="provider === 'd'" name="d"><D /></template>
</template>

可以将其转为对象:

vue
<script setup lang="ts">
import { A, B, C, D } from "./components";
import { useData } from "vitepress";

const { theme } = useData();
const provider = theme.value.provider;

const providerMap = {
  a: { el: A, props: { name: "a" } },
  b: { el: B, props: { name: "b" } },
  c: { el: C, props: { name: "c" } },
  d: { el: D, props: { name: "d" } },
};
</script>

<template>
  <component v-if="provider" :is="providerMap[provider]?.el" v-bind="providerMap[provider]?.props" />
</template>

可以在组件 Layout 的评论区相关代码或者 HomeBanner 查看具体使用。

数组形式

template 编写类似的重复 HTML

vue
<script setup lang="ts"></script>

<template>
  <div>
    <div>
      <span class="title">A</span>
      <span class="desc">测试 A</span>
    </div>
    <div>
      <a class="link" href="/b">B</a>
      <span class="desc">测试 B</span>
    </div>
    <div>
      <img class="link" src="/c.png" />
      <span class="desc">测试 C</span>
    </div>
  </div>
</template>

可以将其转为数组:

vue
<script setup lang="ts">
const list = [
  { title: "A", desc: "测试 A", className: "title" },
  { title: "B", desc: "测试 B", isLink: true, className: "link", link: "/b" },
  { desc: "测试 C", isImg: true, className: "img", link: "/c.png" },
];
</script>

<template>
  <div>
    <div v-for="item in list" :key="item.title">
      <a v-if="item.isLink" :class="item.className" :href="item.link">{{ item.title }}</a>
      <img v-else-if="item.isImg" :class="item.className" :src="item.link" />
      <span v-else :class="item.className">{{ item.title }}</span>

      <span class="desc">{{ item.desc }}</span>
    </div>
  </div>
</template>

仅限于重复度接近 90% 以上或者简单的 HTML,否则不建议使用数组 + for 循环,可读性会变差。

可以在组件 ArticleInfo 查看具体使用。

配置项支持方式

config 配置

如果配置项仅支持在 .vitepress/config.mts 配置:

在组件里这样使用:

vue
<script setup lang="ts">
import { useData } from "vitepress";

const { theme } = useData();

// 赋予默认值
const { enabled = true, name = "", obj = {}, arr = [] } = { ...theme.xxx };
// 或者
// const { enabled = true, name = "", obj = {}, arr = [] } = theme.xxx || {};
</script>

<template></template>

这样避免了获取 theme.xxx 里的属性时报 undefined(没配置 xxx),同时如果 theme.xxx 里的某些属性没有配置,则赋予默认值。

config 和 frontmatter 配置

配置项同时支持在 .vitepress/config.mts 和 Markdown 的 frontmatter 配置,当两种都配置,则以 frontmatter 为准。

在组件里这样使用:

vue
<script setup lang="ts">
import { computed } from "vue";
import { useData } from "vitepress";

const { theme, frontmatter } = useData();

const themeConfig = computed(() => ({
  enabled: true,
  name: "",
  obj: {},
  arr: [],
  ...theme.xxx,
  ...frontmatter.value.xxx,
  ...frontmatter.value.tk.xxx,
}));

// 使用
console.log(themeConfig.value.xxx);
</script>

<template></template>

frontmatter.value.tk.xxx 是在首页 index.md 配置 frontmatter 时,额外添加了 tk,这是为了避免与 VitePress 自带配置的命名冲突。

支持 frontmatter 配置时,一定要用 computed 监听 frontmatter 变化,因为不同 Markdown 的 frontmatter 有可能不一样,如果没有监听 frontmatter 变化,会导致切换 Markdown 文章后,新文章的配置不会重新生效。

如:

ts
// .vitepress/config.mts
import { defineTeekConfig } from "vitepress-theme-teek/config";

const teekConfig = defineTeekConfig({
 comment: {
    provider: "giscus",
    options: {
      repo: "your repo",
      repoId: "your repoId",
      category: "your category",
      categoryId: "your categoryId",
    }
 };
});
yaml
---
tk:
  comment:
    provider: "giscus"
    options:
      repo: "your repo"
      repoId: "your repoId"
      category: "your category"
      categoryId: "your categoryId"
---
yaml
---
comment:
  provider: "giscus"
  options:
    repo: "your repo"
    repoId: "your repoId"
    category: "your category"
    categoryId: "your categoryId"
---
最近更新