开发技巧
介绍 Teek 开发路程的一些技巧。
规范
Teek 建议在进行项目开发时,一个文件的代码行数推荐 300 行以下,最好不超过 500 行,禁止超过 1000 行。
如果超过 300+ 行,应该考虑下是否可以拆分为多个文件,这是一个良好的 结构化思维和分治思维。
提示
Teek 建议您在开发前先思考有哪些模块,然后分别创建模块文件,而不是先在一个文件写完,再拆分。
举个例子:
代码合在一个文件:
<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>
将代码进行模块化,根据功能/布局/逻辑等进行拆分:
<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 环境检验变量:
const isClient = typeof window !== "undefined" && typeof document !== "undefined";
然后在使用 DOM API 之前加上这个校验,这样就防止构建时报错:
<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 组件的 beforeMount
或 mounted
钩子中执行,则无需考虑 SSR,Vue 已经处理了。
利用对象/数组减少 HTML 编写
对象形式
在 template
用 if
、if-else
判断。
<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>
可以将其转为对象:
<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。
<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>
可以将其转为数组:
<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
配置:
在组件里这样使用:
<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
为准。
在组件里这样使用:
<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 文章后,新文章的配置不会重新生效。
如:
// .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",
}
};
});
---
tk:
comment:
provider: "giscus"
options:
repo: "your repo"
repoId: "your repoId"
category: "your category"
categoryId: "your categoryId"
---
---
comment:
provider: "giscus"
options:
repo: "your repo"
repoId: "your repoId"
category: "your category"
categoryId: "your categoryId"
---