diff --git a/package.json b/package.json index 2ca62a0..444f500 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "framer-motion": "^11.3.29", "lodash-es": "^4.17.21", "lucide-react": "^0.429.0", + "lxgw-wenkai-lite-webfont": "^1.7.0", "next": "^14.2.6", "next-mdx-remote": "^5.0.0", "next-themes": "^0.3.0", @@ -34,11 +35,11 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@next/bundle-analyzer": "^14.2.6", "@mdx-js/loader": "^3.0.1", + "@next/bundle-analyzer": "^14.2.6", "@next/mdx": "^14.2.6", - "@types/mdx": "^2.0.13", "@types/lodash-es": "^4.17.12", + "@types/mdx": "^2.0.13", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/src/app/blog/[id]/AnimatedBlogPost.tsx b/src/app/blog/[id]/AnimatedBlogPost.tsx index aedbca9..6f49671 100644 --- a/src/app/blog/[id]/AnimatedBlogPost.tsx +++ b/src/app/blog/[id]/AnimatedBlogPost.tsx @@ -50,7 +50,11 @@ export default function AnimatedBlogPost({ transition={{ duration: 0.5, delay: 0.3 }} > - + + <> + {contentFile ? ( + + ) : ( + + )} + ); } diff --git a/src/app/blog/[id]/MdxComponents.tsx b/src/app/blog/[id]/MdxComponents.tsx index 3b35789..5a5dd7f 100644 --- a/src/app/blog/[id]/MdxComponents.tsx +++ b/src/app/blog/[id]/MdxComponents.tsx @@ -7,6 +7,32 @@ const CopyButton = dynamic(() => import("@/components/blog/id/CopyButton"), { ssr: false, }); +const Table: React.FC> = ( + props +) => ; + +const Th: React.FC> = ( + props +) => ( + +); + export const mdxComponents: MDXComponents = { h1: (props: any) => (

, + table: Table, + th: Th, + td: Td, + tr: Tr, }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 123cbd5..8549cbc 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; import "@/style/globals.css"; import Header from "@/components/app/Header"; import Footer from "@/components/app/Footer"; @@ -11,8 +10,6 @@ import { ViewTransitions } from "next-view-transitions"; import { ThemeProvider } from "next-themes"; import AnimatedLayout from "@/components/app/AnimatedLayout"; -const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); - export const metadata: Metadata = { title: "我的博客", description: "欢迎来到我的博客", @@ -29,13 +26,7 @@ export default function RootLayout({ - +
{children} diff --git a/src/data/BlogPostMDXContent.tsx b/src/data/BlogPostMDXContent.tsx new file mode 100644 index 0000000..15bb26f --- /dev/null +++ b/src/data/BlogPostMDXContent.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { MDXProvider } from "@mdx-js/react"; +import dynamic from "next/dynamic"; +import { MDXComponents } from "@/app/blog/[id]/types"; + +interface BlogPostMDXContent { + contentFile: string; + components: MDXComponents; +} + +const BlogPostMDXContent = ({ + contentFile, + components, +}: BlogPostMDXContent) => { + const MDXContent = dynamic(() => import(`./content/${contentFile}`)); + + return ( + + {/* @ts-ignore */} + + + ); +}; + +export default BlogPostMDXContent; diff --git a/src/data/blogPosts.ts b/src/data/blogPosts.ts index 945737a..ea662e0 100644 --- a/src/data/blogPosts.ts +++ b/src/data/blogPosts.ts @@ -8,6 +8,7 @@ export interface BlogPost { tags: string[]; category?: string; coverImage?: string; + contentFile?: string; // 新增字段,指向mdx文件 } export const blogPosts: BlogPost[] = [ @@ -15,6 +16,7 @@ export const blogPosts: BlogPost[] = [ id: 1, title: "深入理解React Hooks", excerpt: "探索React Hooks的工作原理和最佳实践...", + contentFile: "1.mdx", content: ` # 深入理解React Hooks diff --git a/src/data/content/1.mdx b/src/data/content/1.mdx new file mode 100644 index 0000000..332dd97 --- /dev/null +++ b/src/data/content/1.mdx @@ -0,0 +1,224 @@ + +# 深入理解React Hooks + +React Hooks是React 16.8中引入的新特性,它彻底改变了我们编写React组件的方式。本文将深入探讨Hooks的工作原理,包括常用Hooks的实现细节和使用技巧,以及如何创建自定义Hooks。 + +## 为什么需要Hooks? + +在Hooks出现之前,React组件主要分为类组件和函数组件。类组件可以使用状态和生命周期方法,而函数组件则更简单,但功能有限。Hooks的出现使得函数组件也能够使用状态和其他React特性,从而带来以下优势: + +1. 更简洁的代码 +2. 更容易复用逻辑 +3. 更好的性能优化 +4. 更容易理解和维护的组件 + +## 常用Hooks详解 + +### useState + +`useState`是最基本的Hook,用于在函数组件中添加状态。 + +```jsx +import React, { useState } from 'react'; + +function Counter() { + const [count, setCount] = useState(0); + + return ( +
+

You clicked {count} times

+ +
+ ); +} +``` + +`useState`返回一个数组,第一个元素是当前状态值,第二个元素是更新状态的函数。 + +### useEffect + +`useEffect`用于处理副作用,如数据获取、订阅或手动修改DOM等。 + +```jsx +import React, { useState, useEffect } from 'react'; + +function DataFetcher() { + const [data, setData] = useState(null); + + useEffect(() => { + fetch('https://api.example.com/data') + .then(response => response.json()) + .then(data => setData(data)); + }, []); // 空数组表示只在组件挂载时执行一次 + + return ( +
+ {data ?
{JSON.stringify(data, null, 2)}
: 'Loading...'} +
+ ); +} +``` + +`useEffect`接受两个参数:一个函数和一个依赖数组。函数在组件渲染后执行,依赖数组决定了effect何时重新运行。 + +### useContext + +`useContext`用于访问React的Context API,使得组件可以订阅上下文变化。 + +```jsx +import React, { useContext } from 'react'; + +const ThemeContext = React.createContext('light'); + +function ThemedButton() { + const theme = useContext(ThemeContext); + return ; +} +``` + +### useReducer + +`useReducer`是`useState`的替代方案,用于管理复杂的状态逻辑。 + +```jsx +import React, { useReducer } from 'react'; + +function reducer(state, action) { + switch (action.type) { + case 'increment': + return {count: state.count + 1}; + case 'decrement': + return {count: state.count - 1}; + default: + throw new Error(); + } +} + +function Counter() { + const [state, dispatch] = useReducer(reducer, { count: 0 }); + return ( + <> + Count: {state.count} + + + + ); +} +``` + +## 自定义Hooks + +创建自定义Hook允许你将组件逻辑提取到可重用的函数中。 + +```jsx +import { useState, useEffect } from 'react'; + +function useWindowWidth() { + const [width, setWidth] = useState(window.innerWidth); + + useEffect(() => { + const handleResize = () => setWidth(window.innerWidth); + window.addEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return width; +} + +function MyResponsiveComponent() { + const width = useWindowWidth(); + return
Window width is {width}
; +} +``` + +## Hooks的使用规则 + +使用Hooks时需要遵循两个重要规则: + +1. 只在最顶层使用Hooks +2. 只在React函数中调用Hooks + +这些规则确保Hooks在每次渲染时都以相同的顺序被调用,这对于Hooks的正确工作至关重要。 + +## Hooks与类组件的对比 + +Hooks和类组件各有优势。Hooks通常能让代码更简洁,逻辑更容易复用,但类组件在某些场景下仍然有其优势,如错误边界。 + +以下是一个简单的对比: + +

+); + +const Td: React.FC> = ( + props +) => ( + +); + +const Tr: React.FC> = (props) => ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
特性Hooks类组件
代码简洁性
逻辑复用
学习曲线中等较陡
性能
+ +## 性能优化 + +Hooks提供了几种方式来优化组件性能: + +1. `useMemo`: 缓存计算结果 +2. `useCallback`: 缓存函数 +3. `React.memo`: 优化函数组件的重渲染 + +```jsx +import React, { useMemo, useCallback } from 'react'; + +function ExpensiveComponent({ data, onItemClick }) { + const sortedData = useMemo(() => { + return data.sort((a, b) => a.id - b.id); + }, [data]); + + const handleClick = useCallback((item) => { + console.log('Item clicked:', item); + onItemClick(item); + }, [onItemClick]); + + return ( +
    + {sortedData.map(item => ( +
  • handleClick(item)}> + {item.name} +
  • + ))} +
+ ); +} + +export default React.memo(ExpensiveComponent); +``` + +## 结论 + +React Hooks是一个强大的特性,它简化了状态管理和副作用处理,使得函数组件更加灵活和强大。通过深入理解Hooks的工作原理和使用技巧,我们可以编写出更简洁、可维护和高性能的React应用。 + +然而,Hooks并不是银弹。在某些场景下,类组件可能仍然是更好的选择。关键是要根据具体情况选择最合适的工具。随着React的不断发展,我们可以期待看到更多围绕Hooks的创新和最佳实践的出现。 diff --git a/src/style/font.css b/src/style/font.css new file mode 100644 index 0000000..8cc72cf --- /dev/null +++ b/src/style/font.css @@ -0,0 +1,9 @@ +@import 'lxgw-wenkai-lite-webfont/style.css'; + +body { + font-family: "LXGW WenKai Lite", sans-serif; +} + +pre,code { + font-family: "LXGW WenKai Mono Lite", sans-serif; +} \ No newline at end of file diff --git a/src/style/globals.css b/src/style/globals.css index bd7bcea..15c4473 100644 --- a/src/style/globals.css +++ b/src/style/globals.css @@ -2,6 +2,8 @@ @tailwind components; @tailwind utilities; +@import 'font.css'; + @layer base { :root { --background: 0 0% 100%; diff --git a/tailwind.config.ts b/tailwind.config.ts index b51557b..13ec42c 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,5 +1,4 @@ import type { Config } from "tailwindcss"; -import { fontFamily } from "tailwindcss/defaultTheme"; import typography from "@tailwindcss/typography"; import animate from "tailwindcss-animate"; @@ -70,9 +69,6 @@ const config = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, - fontFamily: { - sans: ["var(--font-sans)", ...fontFamily.sans], - }, backgroundColor: { "code-light": "var(--code-bg)", "code-dark": "var(--code-bg)",