Skip to content

代码预览功能

VitePress 支持多种代码预览和演示功能,让文档更加生动和实用。

基础代码高亮

TypeScript/JavaScript

typescript
import { useState } from 'react'
import { useToggle } from 'joy-at-meeting'

function ToggleExample() {
  const [isOn, toggle] = useToggle(false)
  
  return (
    <button onClick={toggle}>
      {isOn ? '开启' : '关闭'}
    </button>
  )
}

带行号的代码

typescript
import { useState } from 'react'
import { useToggle } from 'joy-at-meeting'

function ToggleExample() {
  const [isOn, toggle] = useToggle(false)
  
  return (
    <button onClick={toggle}>
      {isOn ? '开启' : '关闭'}
    </button>
  )
}

代码组示例

typescript
import { useState, useCallback } from 'react'

export function useToggle(initialValue: boolean = false) {
  const [value, setValue] = useState(initialValue)
  
  const toggle = useCallback(() => {
    setValue(prev => !prev)
  }, [])
  
  const setTrue = useCallback(() => {
    setValue(true)
  }, [])
  
  const setFalse = useCallback(() => {
    setValue(false)
  }, [])
  
  return [value, toggle, setTrue, setFalse] as const
}
tsx
import React from 'react'
import { useToggle } from './useToggle'

function ToggleDemo() {
  const [isVisible, toggle, show, hide] = useToggle(false)
  
  return (
    <div>
      <div className="controls">
        <button onClick={toggle}>切换</button>
        <button onClick={show}>显示</button>
        <button onClick={hide}>隐藏</button>
      </div>
      
      {isVisible && (
        <div className="content">
          <p>这是可切换的内容</p>
        </div>
      )}
    </div>
  )
}

export default ToggleDemo
css
.controls {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

.controls button {
  padding: 8px 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: white;
  cursor: pointer;
  transition: all 0.2s;
}

.controls button:hover {
  background: #f5f5f5;
  border-color: #999;
}

.content {
  padding: 16px;
  background: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 4px;
  animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}

交互式代码演示

使用自定义演示组件

我们创建了一个自定义的 HookDemo 组件来展示 hooks 的实际效果:

useToggle Hook 演示

状态: ❌ 关闭

实时编辑器集成

你也可以使用 VitePress 的插件来集成在线代码编辑器:

vue
<template>
  <div class="demo-container">
    <!-- 代码演示区域 -->
    <div class="demo-preview">
      <ToggleDemo />
    </div>
    
    <!-- 代码编辑器 -->
    <div class="demo-code">
      <CodeEditor 
        :code="demoCode" 
        language="tsx"
        @change="updateCode"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ToggleDemo from './components/ToggleDemo.vue'
import CodeEditor from './components/CodeEditor.vue'

const demoCode = ref(`
import React from 'react'
import { useToggle } from 'joy-at-meeting'

function ToggleDemo() {
  const [isVisible, toggle] = useToggle(false)
  
  return (
    <div>
      <button onClick={toggle}>
        {isVisible ? '隐藏' : '显示'}
      </button>
      {isVisible && <p>Hello World!</p>}
    </div>
  )
}

export default ToggleDemo
`)

function updateCode(newCode) {
  demoCode.value = newCode
}
</script>

代码块功能增强

1. 代码复制功能

VitePress 自动为所有代码块添加复制按钮:

typescript
// 这个代码块右上角会有复制按钮
import { useLocalStorage } from 'joy-at-meeting'

function App() {
  const [name, setName] = useLocalStorage('username', '')
  
  return (
    <input 
      value={name}
      onChange={(e) => setName(e.target.value)}
      placeholder="输入用户名"
    />
  )
}

2. 代码折叠

typescript
// 长代码可以设置折叠
import React, { useState, useEffect, useCallback, useMemo } from 'react'
import { 
  useLocalStorage, 
  useToggle, 
  useDebounce,
  useThrottle,
  useAsync,
  useFetch
} from 'joy-at-meeting'

interface User {
  id: number
  name: string
  email: string
}

function ComplexComponent() {
  // 状态管理
  const [users, setUsers] = useState<User[]>([])
  const [searchTerm, setSearchTerm] = useLocalStorage('search', '')
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  
  // 防抖搜索
  const debouncedSearchTerm = useDebounce(searchTerm, 300)
  
  // 异步数据获取
  const fetchUsers = useCallback(async (search: string) => {
    setIsLoading(true)
    setError(null)
    
    try {
      const response = await fetch(`/api/users?search=${search}`)
      if (!response.ok) throw new Error('Failed to fetch')
      const data = await response.json()
      setUsers(data)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error')
    } finally {
      setIsLoading(false)
    }
  }, [])
  
  // 监听搜索词变化
  useEffect(() => {
    if (debouncedSearchTerm) {
      fetchUsers(debouncedSearchTerm)
    } else {
      setUsers([])
    }
  }, [debouncedSearchTerm, fetchUsers])
  
  // 过滤用户
  const filteredUsers = useMemo(() => {
    return users.filter(user => 
      user.name.toLowerCase().includes(debouncedSearchTerm.toLowerCase())
    )
  }, [users, debouncedSearchTerm])
  
  return (
    <div className="user-search">
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="搜索用户..."
        className="search-input"
      />
      
      {isLoading && <div className="loading">加载中...</div>}
      {error && <div className="error">错误: {error}</div>}
      
      <div className="user-list">
        {filteredUsers.map(user => (
          <div key={user.id} className="user-item">
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </div>
        ))}
      </div>
    </div>
  )
}

export default ComplexComponent

3. 代码差异对比

typescript
// 旧版本
function useToggle(initialValue: boolean) {
  const [value, setValue] = useState(initialValue)
  
  const toggle = () => {
    setValue(!value) 
  }
  
  return [value, toggle]
}

// 新版本
function useToggle(initialValue: boolean = false) { 
  const [value, setValue] = useState(initialValue)
  
  const toggle = useCallback(() => { 
    setValue(prev => !prev) 
  }, []) 
  
  return [value, toggle]
}

集成第三方预览工具

CodeSandbox 集成

你可以在文档中嵌入 CodeSandbox:

StackBlitz 集成

html
<iframe 
  src="https://stackblitz.com/edit/react-joy-at-meeting?embed=1&file=src/App.tsx"
  style="width:100%; height:500px; border:0; border-radius: 4px;"
  title="Joy At Meeting on StackBlitz"
></iframe>

自定义代码预览组件

你可以创建自定义的 Vue 组件来增强代码预览功能:

vue
<!-- .vitepress/components/CodePreview.vue -->
<template>
  <div class="code-preview">
    <div class="preview-tabs">
      <button 
        v-for="tab in tabs" 
        :key="tab.name"
        :class="{ active: activeTab === tab.name }"
        @click="activeTab = tab.name"
      >
        {{ tab.label }}
      </button>
    </div>
    
    <div class="preview-content">
      <div v-if="activeTab === 'preview'" class="demo-area">
        <slot name="demo" />
      </div>
      
      <div v-else class="code-area">
        <slot name="code" />
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const activeTab = ref('preview')
const tabs = [
  { name: 'preview', label: '预览' },
  { name: 'code', label: '代码' }
]
</script>

<style scoped>
.code-preview {
  border: 1px solid var(--vp-c-border);
  border-radius: 8px;
  overflow: hidden;
}

.preview-tabs {
  display: flex;
  background: var(--vp-c-bg-soft);
  border-bottom: 1px solid var(--vp-c-border);
}

.preview-tabs button {
  padding: 8px 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  transition: all 0.2s;
}

.preview-tabs button.active {
  background: var(--vp-c-bg);
  color: var(--vp-c-brand);
}

.preview-content {
  padding: 16px;
}

.demo-area {
  min-height: 200px;
}
</style>

配置代码预览功能

在 VitePress 配置中启用代码预览功能:

typescript
// .vitepress/config.ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  // ... 其他配置
  
  markdown: {
    // 启用代码行号
    lineNumbers: true,
    
    // 配置代码高亮
    theme: {
      light: 'github-light',
      dark: 'github-dark'
    },
    
    // 自定义代码块配置
    config: (md) => {
      // 添加自定义代码块处理
      md.use(require('markdown-it-container'), 'demo', {
        render: (tokens, idx) => {
          const token = tokens[idx]
          if (token.nesting === 1) {
            return `<div class="demo-container">`
          } else {
            return `</div>`
          }
        }
      })
    }
  },
  
  // 配置 Vue 组件
  vue: {
    template: {
      compilerOptions: {
        isCustomElement: (tag) => tag.includes('-')
      }
    }
  }
})

最佳实践

1. 代码示例组织

  • 将复杂示例拆分为多个文件
  • 使用代码组展示相关文件
  • 提供完整的可运行示例

2. 交互性增强

  • 添加实时预览功能
  • 提供可编辑的代码示例
  • 集成在线编辑器

3. 用户体验

  • 确保代码可复制
  • 提供清晰的注释
  • 使用语法高亮
  • 添加错误处理示例

通过这些功能,VitePress 可以提供丰富的代码预览和演示体验,让文档更加生动和实用。

基于 MIT 许可证发布