我如何用 4 行 JSON 让我的作品集实现无限可扩展
Source: Dev.to
向我的作品集添加一个新项目只需 30 秒。无需更改组件。无需新增路由。无需布局调整。只需在文件夹中放入 4 行 JSON 和一张图片。
我已经厌倦了典型的作品集维护循环:先做一个酷炫的项目,然后花一个小时把它接入作品集网站,调整布局,确保新卡片不会破坏网格。因此,我从一开始就把我的作品集设计成完全数据驱动的。
下面是它的工作原理。
Source: …
单一真实来源
我作品集中的每个项目都存放在同一个文件中:projects.js。每个项目都是一个仅包含 4 个字段的对象:
{
id: 'crystal-index',
title: 'Crystal Index',
techStack: 'TypeScript, Next.js, Prisma, SQL, GPT4, React 3 Fiber',
info: 'Custom CMS for cataloguing crystals with structured filters for colour, chakra, and properties, and GPT-4-generated descriptions.',
}
仅此而已。四行代码。整个作品集就是从这些对象组成的数组渲染出来的。
ID 的三重职责
id 字段是设计的有趣之处。它不仅仅是一个标识符;它同时承担三种功能:
-
GitHub 链接路径 – 作品集通过将 ID 追加到基础 GitHub URL 来构建仓库链接。
crystal-index→https://github.com/sammii-hk/crystal-index. -
图片文件名查找 – 自动生成的图片映射将 ID 解析为对应的图片文件及其扩展名。
crystal-index→/assets/images/crystal-index.jpg. -
React 键 – 在遍历数组时,ID 充当唯一键。
一个字段,三项任务。这消除了冗余数据,并确保 GitHub 链接、图片和 React 键始终保持同步。
对于属于 GitHub 组织的项目,ID 包含组织路径,例如 unicorn-poo/succulent。图片工具会在 / 上拆分,并使用最后一个段作为文件名查找,而完整路径则用于构建正确的 GitHub URL。
自动生成的图片映射
我不想手动跟踪每个项目截图是 .jpg 还是 .png。于是我写了一个构建脚本,扫描 images 目录并生成 JSON 映射:
import { readdir, writeFile } from 'fs/promises';
import { join } from 'path';
const imagesDir = join(process.cwd(), 'public', 'assets', 'images');
async function generateImageMap() {
const files = await readdir(imagesDir);
const imageMap = {};
files.forEach(file => {
if (file === 'sammii.png') return;
const name = file.replace(/\.(jpg|png)$/, '');
const ext = file.endsWith('.png') ? 'png' : 'jpg';
if (!imageMap[name]) imageMap[name] = ext;
});
const outputPath = join(process.cwd(), 'app', 'common', 'utils', 'image-map.json');
await writeFile(outputPath, JSON.stringify(imageMap, null, 2));
}
generateImageMap();
输出是一个简单的查找表:
{
"crystal-index": "jpg",
"lunary": "png",
"succulent": "png",
"day-lite": "jpg"
}
一个实用函数可以将任意项目 ID 解析为完整的图片路径:
import imageMapData from './image-map.json';
const imageMap = imageMapData;
export const getImagePath = (projectId) => {
const projectBaseId = projectId.split('/').pop() || projectId;
const extension = imageMap[projectBaseId] || 'jpg';
return `/assets/images/${projectBaseId}.${extension}`;
};
把图片放进文件夹,运行脚本,作品集就会自动识别并使用它们。
零组件更改
该作品集拥有两种完全不同的视图模式——响应式网格(点击展开模态框)和全屏垂直轮播。两者都使用完全相同的 projects 数组。
网格视图 对数组进行映射并渲染卡片:
{projects.map(project => (
setSelectedProject(project)}>
))}
轮播视图 对同一数组进行映射并渲染全宽幻灯片:
(
)}
/>
ProjectItem 接收一个 isGrid 属性,用于在紧凑卡片布局和展开布局之间切换。相同的组件、相同的数据、两种呈现方式。向数组中添加项目后,它会自动出现在两种视图中,无需额外工作。
30秒工作流
当我完成一个新项目时,我会执行以下操作:
- 截取屏幕截图。
- 将其放入
/public/assets/images/,命名为project-name.png。 - 运行
node scripts/generate-image-map.mjs。 - 在
projects.js中添加 4 行代码。 - 推送到 GitHub。
作品集会重新构建,新项目会出现在两个视图中,显示正确的图片、正确的 GitHub 链接以及正确的布局。五步、三十秒,未触及任何组件文件。
Source:
背后的哲学
这不仅仅是为了在作品集中节省时间。这是我在各处使用的模式。
我的占星应用 Lunary 拥有一个包含超过 2,000 篇文章的法典。它们以结构化数据存储,并通过共享组件渲染。添加关于水晶或塔罗牌的新文章时,无需触碰任何 UI 代码。
我的发布工具 Spellcast 管理多个社交媒体账号和平台。账号配置是数据对象。添加新平台只需在配置中加入,而不是重建界面。
原则始终如一:将数据与表现层分离。让数据结构承担大部分工作。保持组件足够通用,以至于它们永远不需要了解具体内容。
如果你在为作品集接线新内容上花的时间比构建项目本身还多,那说明你的架构方向弄反了。让你的作品集为你服务,而不是相反。
我是 Sammii,Lunary 的创始人,这是一款真正教你阅读自己出生图的占星应用。我会写关于作为独立开发者构建产品的技术决策。关注我的 Dev.to 或在 GitHub 查看代码。
