From 25281f4459635d3e57e31354887f5e4d23daa33a Mon Sep 17 00:00:00 2001 From: joyzhao Date: Thu, 19 Jun 2025 11:26:47 +0800 Subject: [PATCH] refactor(types): centralize type definitions in shared types file Move all interface and type definitions to src/types/index.ts to reduce code duplication and improve maintainability. This includes types for components, blog posts, personal info, and localization. --- src/components/AuthorCard.tsx | 15 +- src/components/BlogList.tsx | 25 +--- src/components/Footer.tsx | 5 +- src/components/GlassHeader.tsx | 5 +- src/components/LanguageSwitcher.tsx | 5 +- src/components/ShareButtons.tsx | 10 +- src/components/SkillsMarquee.tsx | 9 +- src/components/layout/BlogNavigation.astro | 2 + src/components/layout/TableOfContents.astro | 2 +- src/i18n/utils.ts | 8 +- src/layouts/AboutLayout.astro | 8 +- src/layouts/BlogPostLayout.astro | 14 +- src/lib/data.ts | 4 +- src/types/index.ts | 158 ++++++++++++++++++++ tsconfig.json | 1 + 15 files changed, 180 insertions(+), 91 deletions(-) create mode 100644 src/types/index.ts diff --git a/src/components/AuthorCard.tsx b/src/components/AuthorCard.tsx index fce7f36..1b751db 100644 --- a/src/components/AuthorCard.tsx +++ b/src/components/AuthorCard.tsx @@ -1,19 +1,6 @@ -import type { Lang } from '../i18n/utils'; +import { type AuthorCardProps } from '@/types'; import { personalInfo } from '@/lib/data'; -interface AuthorCardProps { - lang: Lang; - author?: { - name: string; - bio?: string; - avatar?: string; - website?: string; - twitter?: string; - github?: string; - linkedin?: string; - }; -} - export default function AuthorCard({ lang, author }: AuthorCardProps) { // Default author info based on personalInfo const defaultAuthor = { diff --git a/src/components/BlogList.tsx b/src/components/BlogList.tsx index 61b3fe9..d50624c 100644 --- a/src/components/BlogList.tsx +++ b/src/components/BlogList.tsx @@ -1,27 +1,6 @@ import React from 'react'; -import { useTranslations, type Lang } from '../i18n/utils'; - -/** - * Blog post interface definition - */ -interface BlogPost { - title: string; - description: string; - image: string; - slug: string; - tags: string[]; - date: string; - readTime: string; -} - -/** - * Props interface for BlogList component - */ -interface BlogListProps { - posts: BlogPost[]; - lang: Lang; - baseUrl?: string; // Base URL for blog posts, defaults to '/blog/posts/' -} +import { useTranslations, type Lang } from '@/i18n/utils'; +import { type BlogPost, type BlogListProps } from '@/types'; /** * Reusable blog list component that displays blog posts in a grid layout diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 2b28cf2..8e46652 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -3,10 +3,7 @@ import { personalInfo } from "@/lib/data"; import { motion } from "framer-motion"; import { useState, useEffect } from "react"; import { defaultLang } from "@/i18n/ui"; - -interface FooterProps { - lang?: Lang; -} +import { type FooterProps } from "@/types"; export default function Footer({ lang: propLang }: FooterProps) { // 优先使用props传入的语言,如果没有则尝试从HTML lang属性获取 diff --git a/src/components/GlassHeader.tsx b/src/components/GlassHeader.tsx index 32cb98f..8ecd821 100644 --- a/src/components/GlassHeader.tsx +++ b/src/components/GlassHeader.tsx @@ -3,13 +3,12 @@ import LanguageSwitcher from "./LanguageSwitcher"; import ThemeToggle from "./ui/theme-toggle"; import { useTranslations, getLocalizedPath, type Lang } from "@/i18n/utils"; import { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; import { Menu, X } from "lucide-react"; import { defaultLang } from "@/i18n/ui"; +import { type GlassHeaderProps } from "@/types"; // 从window.document.documentElement.lang获取当前语言 -interface GlassHeaderProps { - lang?: Lang; -} export default function GlassHeader({ lang: propLang }: GlassHeaderProps) { // 优先使用props传入的语言,如果没有则尝试从HTML lang属性获取 diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 12797f6..38a50b0 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -3,6 +3,7 @@ import { getLocalizedPath, type Lang } from "@/i18n/utils"; import { languages as i18nLanguages, defaultLang } from "@/i18n/ui"; import { Languages, Check, ChevronDown } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; +import { type LanguageSwitcherProps } from "@/types"; const availableLanguages = Object.entries(i18nLanguages).map(([code, name]) => ({ code: code as Lang, @@ -11,10 +12,6 @@ const availableLanguages = Object.entries(i18nLanguages).map(([code, name]) => ( icon: code === 'en' ? '🇬🇧' : code === 'zh' ? '🇨🇳' : '🌐' })); -interface LanguageSwitcherProps { - lang?: Lang; -} - export default function LanguageSwitcher({ lang: propLang }: LanguageSwitcherProps) { const [isOpen, setIsOpen] = useState(false); // 获取当前语言,优先使用props传入的语言 diff --git a/src/components/ShareButtons.tsx b/src/components/ShareButtons.tsx index 56ba202..5cfcb93 100644 --- a/src/components/ShareButtons.tsx +++ b/src/components/ShareButtons.tsx @@ -1,11 +1,5 @@ -import { useState } from 'react'; -import type { Lang } from '../i18n/utils'; - -interface ShareButtonsProps { - lang: Lang; - title: string; - url?: string; -} +import React, { useState } from 'react'; +import { type ShareButtonsProps } from '@/types'; export default function ShareButtons({ lang, title, url }: ShareButtonsProps) { const [copied, setCopied] = useState(false); diff --git a/src/components/SkillsMarquee.tsx b/src/components/SkillsMarquee.tsx index a9ade8a..66698f4 100644 --- a/src/components/SkillsMarquee.tsx +++ b/src/components/SkillsMarquee.tsx @@ -1,14 +1,7 @@ import { motion, useAnimation } from "framer-motion"; import { useState, useEffect } from "react"; import { useTranslations } from "@/i18n/utils"; - -/** - * Skill item interface for the marquee component - */ -interface SkillItem { - name: string; - icon: string; // skillicons icon name -} +import { type SkillItem } from "@/types"; /** * All skills data with corresponding skillicons names diff --git a/src/components/layout/BlogNavigation.astro b/src/components/layout/BlogNavigation.astro index 3546d42..65af95b 100644 --- a/src/components/layout/BlogNavigation.astro +++ b/src/components/layout/BlogNavigation.astro @@ -1,4 +1,6 @@ --- +import { type BlogPost as BaseBlogPost } from '@/types'; + const currentPath = Astro.url.pathname; const isZh = currentPath.includes('/zh/'); const lang = isZh ? 'zh' : 'en'; diff --git a/src/components/layout/TableOfContents.astro b/src/components/layout/TableOfContents.astro index 58a7442..d488220 100644 --- a/src/components/layout/TableOfContents.astro +++ b/src/components/layout/TableOfContents.astro @@ -1,5 +1,5 @@ --- -import type { Lang } from '../../i18n/utils'; +import { type Lang } from '@/types'; interface Props { lang: Lang; diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts index 126c58f..ede16d8 100644 --- a/src/i18n/utils.ts +++ b/src/i18n/utils.ts @@ -1,11 +1,9 @@ // src/i18n/utils.ts import { ui, defaultLang, languages } from './ui'; +import { type Lang, type UiKeys } from '@/types'; -export type Lang = keyof typeof languages; - -// 简化类型定义,直接使用字符串类型 -// 这样可以兼容点符号键,同时避免复杂的类型推导问题 -export type UiKeys = string; +// 重新导出类型,以保持向后兼容性 +export type { Lang, UiKeys }; export function useTranslations(lang: Lang | undefined) { diff --git a/src/layouts/AboutLayout.astro b/src/layouts/AboutLayout.astro index 380daea..c420a9a 100644 --- a/src/layouts/AboutLayout.astro +++ b/src/layouts/AboutLayout.astro @@ -1,17 +1,11 @@ --- import type { MarkdownLayoutProps } from 'astro'; -import { type Lang } from '@/i18n/utils'; +import { type Lang, type FrontmatterProps } from '@/types'; import { defaultLang } from '@/i18n/ui'; import GlassHeader from '@/components/GlassHeader'; import Footer from '@/components/Footer'; import "../styles/global.css"; -// Define the frontmatter structure -interface FrontmatterProps { - title: string; - description?: string; -} - // Use Astro's MarkdownLayoutProps for proper type safety export type Props = MarkdownLayoutProps; diff --git a/src/layouts/BlogPostLayout.astro b/src/layouts/BlogPostLayout.astro index b5e6d37..479405a 100644 --- a/src/layouts/BlogPostLayout.astro +++ b/src/layouts/BlogPostLayout.astro @@ -1,6 +1,7 @@ --- import type { MarkdownLayoutProps } from 'astro'; import { type Lang } from '@/i18n/utils'; +import { type FrontmatterProps } from '@/types'; import { defaultLang } from '@/i18n/ui'; import GlassHeader from '@/components/GlassHeader'; import Footer from '@/components/Footer'; @@ -11,19 +12,6 @@ import PostMeta from '@/components/blog/PostMeta.astro'; import "../styles/global.css"; -// Define the frontmatter structure -interface FrontmatterProps { - title: string; - description?: string; - publishDate?: string; - date?: string; // Alternative field name for publish date - author?: string; - tags?: string[]; - category?: string | string[]; - readingTime?: number; - readTime?: string; // Alternative field name for reading time -} - // Use Astro's MarkdownLayoutProps for proper type safety export type Props = MarkdownLayoutProps; diff --git a/src/lib/data.ts b/src/lib/data.ts index 9ad299c..c0b6bb3 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -1,4 +1,6 @@ -export const personalInfo = { +import type { PersonalInfo } from '@/types'; + +export const personalInfo: PersonalInfo = { name: "Joy Zhao", location: "China", avatar: "https://avatars.githubusercontent.com/u/24975063?v=4", diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..6697d26 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,158 @@ +/** + * 集中管理项目中的类型定义 + * 这个文件包含了项目中常用的接口和类型定义,减少代码冗余和提高可维护性 + */ + +import { languages } from '@/i18n/ui'; + +/** + * 语言类型定义 + */ +export type Lang = keyof typeof languages; + +/** + * 国际化文本键类型 + */ +export type UiKeys = string; + +/** + * 多语言文本类型 + */ +export type LocalizedText = { + [K in Lang]: string; +}; + +/** + * 作者信息接口 + */ +export interface Author { + name: string; + bio?: string; + avatar?: string; + website?: string; + twitter?: string; + github?: string; + linkedin?: string; +} + +/** + * 博客文章接口 + */ +export interface BlogPost { + title: string; + description: string; + image: string; + slug: string; + tags: string[]; + date: string; + readTime: string; + url?: string; +} + +/** + * 博客文章前置元数据接口 + */ +export interface FrontmatterProps { + title: string; + description?: string; + publishDate?: string; + date?: string; // Alternative field name for publish date + author?: string; + tags?: string[]; + category?: string | string[]; + readingTime?: number; + readTime?: string; // Alternative field name for reading time +} + +/** + * 技能项接口 + */ +export interface SkillItem { + name: string; + icon: string; // skillicons icon name +} + +/** + * 个人信息接口 + */ +export interface PersonalInfo { + name: string; + location: string; + avatar: string; + email: string; + github: string; + linkedin: string; + website: string; + twitter: string; + position: LocalizedText; + description: LocalizedText; + about: { + [K in Lang]: string[]; + }; + stats: { + repositories: number; + commits: string; + contributions: number; + }; + skills: { + frontend: string[]; + backend: string[]; + database: string[]; + devops: string[]; + mobile: string[]; + }; + terminal: { + username: string; + }; +} + +/** + * 组件 Props 接口 + */ + +/** + * 作者卡片组件 Props + */ +export interface AuthorCardProps { + lang: Lang; + author?: Author; +} + +/** + * 分享按钮组件 Props + */ +export interface ShareButtonsProps { + lang: Lang; + title: string; + url?: string; +} + +/** + * 页脚组件 Props + */ +export interface FooterProps { + lang?: Lang; +} + +/** + * 玻璃标题组件 Props + */ +export interface GlassHeaderProps { + lang: Lang; +} + +/** + * 博客列表组件 Props + */ +export interface BlogListProps { + posts: BlogPost[]; + lang: Lang; + baseUrl?: string; // Base URL for blog posts, defaults to '/blog/posts/' +} + +/** + * 语言切换器组件 Props + */ +export interface LanguageSwitcherProps { + lang: Lang; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7799e13..9ada815 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "include": [ ".astro/types.d.ts", "src/**/*.ts", + "src/**/*.tsx", "src/**/*.astro", ], "exclude": [