Vue3组合式API最佳实# Docker容器化部署完整指南

引言Docker 作为现代应用部署的标准工具,彻底改变了软件开发和运维的方式。通过容器化技术,我们可以实现"一次构建,到处运行"的理想,解决环境一致性问题,提高部署效率。本文将从基础概念到实际应用,全面介绍 Docker 容器化部署的最佳实践。## Docker 基础概念### 核心概念理解#### 镜像 (Image)镜像是容器的模板,包含了运行应用所需的所有文件、依赖和配置:dockerfile# 基础镜像示例FROM node:18-alpine# 设置工作目录WORKDIR /app# 复制依赖文件COPY package*.json ./# 安装依赖RUN npm ci --only=production# 复制应用代码COPY . .# 暴露端口EXPOSE 3000# 启动命令CMD ["npm", "start"]#### 容器 (Container)容器是镜像的运行实例:bash# 创建并运行容器docker run -d \ --name my-app \ -p 3000:3000 \ -e NODE_ENV=production \ my-node-app:latest# 查看运行中的容器docker ps# 进入容器docker exec -it my-app /bin/sh# 查看容器日志docker logs my-app# 停止容器docker stop my-app# 删除容器docker rm my-app#### 仓库 (Repository)仓库用于存储和分发镜像:bash# 登录到 Docker Hubdocker login# 标记镜像docker tag my-app:latest username/my-app:v1.0.0# 推送镜像docker push username/my-app:v1.0.0# 拉取镜像docker pull username/my-app:v1.0.0## Dockerfile 最佳实践### 多阶段构建dockerfile# 多阶段构建示例 - Node.js 应用# 构建阶段FROM node:18-alpine AS builderWORKDIR /app# 复制依赖文件COPY package*.json ./# 安装所有依赖(包括开发依赖)RUN npm ci# 复制源代码COPY . .# 构建应用RUN npm run build# 生产阶段FROM node:18-alpine AS production# 创建非 root 用户RUN addgroup -g 1001 -S nodejsRUN adduser -S nextjs -u 1001WORKDIR /app# 复制依赖文件COPY package*.json ./# 只安装生产依赖RUN npm ci --only=production && npm cache clean --force# 从构建阶段复制构建产物COPY --from=builder --chown=nextjs:nodejs /app/dist ./distCOPY --from=builder --chown=nextjs:nodejs /app/public ./public# 切换到非 root 用户USER nextjsEXPOSE 3000ENV NODE_ENV=productionCMD ["npm", "start"]### 优化镜像大小dockerfile# 优化后的 Python 应用 DockerfileFROM python:3.11-slim AS base# 设置环境变量ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1# 安装系统依赖RUN apt-get update && apt-get install -y \ gcc \ && rm -rf /var/lib/apt/lists/*# 创建虚拟环境RUN python -m venv /opt/venvENV PATH="/opt/venv/bin:$PATH"# 构建阶段FROM base AS builderWORKDIR /app# 复制依赖文件COPY requirements.txt .# 安装 Python 依赖RUN pip install --no-cache-dir -r requirements.txt# 生产阶段FROM python:3.11-slim AS production# 复制虚拟环境COPY --from=builder /opt/venv /opt/venvENV PATH="/opt/venv/bin:$PATH"# 创建非 root 用户RUN groupadd -r appuser && useradd -r -g appuser appuserWORKDIR /app# 复制应用代码COPY --chown=appuser:appuser . .# 切换到非 root 用户USER appuserEXPOSE 8000CMD ["python", "app.py"]### 缓存优化策略dockerfile# 缓存优化的 DockerfileFROM node:18-alpineWORKDIR /app# 1. 首先复制依赖文件(变化频率低)COPY package*.json ./# 2. 安装依赖(利用 Docker 层缓存)RUN npm ci --only=production# 3. 复制配置文件COPY config/ ./config/# 4. 最后复制应用代码(变化频率高)COPY src/ ./src/COPY public/ ./public/# 5. 构建应用RUN npm run buildEXPOSE 3000CMD ["npm", "start"]## 容器编排与 Docker Compose### 基础 Docker Compose 配置yaml# docker-compose.ymlversion: '3.8'services: # Web 应用 web: build: context: . dockerfile: Dockerfile target: production ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgresql://user:password@db:5432/myapp - REDIS_URL=redis://redis:6379 depends_on: - db - redis volumes: - ./uploads:/app/uploads networks: - app-network restart: unless-stopped # 数据库 db: image: postgres:15-alpine environment: - POSTGRES_DB=myapp - POSTGRES_USER=user - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql networks: - app-network restart: unless-stopped # Redis 缓存 redis: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redis_data:/data networks: - app-network restart: unless-stopped # Nginx 反向代理 nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - web networks: - app-network restart: unless-stoppedvolumes: postgres_data: redis_data:networks: app-network: driver: bridge### 环境配置管理yaml# docker-compose.override.yml (开发环境)version: '3.8'services: web: build: target: development environment: - NODE_ENV=development - DEBUG=app:* volumes: - .:/app - /app/node_modules command: npm run dev db: ports: - "5432:5432" redis: ports: - "6379:6379"``````yaml# docker-compose.prod.yml (生产环境)version: '3.8'services: web: image: myapp:latest deploy: replicas: 3 resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M environment: - NODE_ENV=production logging: driver: "json-file" options: max-size: "10m" max-file: "3" db: deploy: resources: limits: cpus: '1' memory: 1G reservations: cpus: '0.5' memory: 512M### 健康检查配置yamlversion: '3.8'services: web: build: . healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s depends_on: db: condition: service_healthy db: image: postgres:15-alpine healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d myapp"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3## 生产环境部署策略### CI/CD 集成yaml# .github/workflows/deploy.ymlname: Deploy to Productionon: push: branches: [main]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run tests run: | docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit docker-compose -f docker-compose.test.yml down build: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: | myapp:latest myapp:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max deploy: needs: build runs-on: ubuntu-latest steps: - name: Deploy to server uses: appleboy/ssh-action@v0.1.5 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.SSH_KEY }} script: | cd /opt/myapp docker-compose pull docker-compose up -d docker system prune -f### 蓝绿部署bash#!/bin/bash# blue-green-deploy.shset -e# 配置IMAGE_NAME="myapp"IMAGE_TAG=${1:-latest}CURRENT_ENV=$(docker-compose ps -q web | head -1 | xargs docker inspect --format='{{.Config.Labels.environment}}' 2>/dev/null || echo "blue")# 确定目标环境if [ "$CURRENT_ENV" = "blue" ]; then TARGET_ENV="green" TARGET_PORT="3001"else TARGET_ENV="blue" TARGET_PORT="3000"fiecho "当前环境: $CURRENT_ENV"echo "目标环境: $TARGET_ENV"# 部署到目标环境echo "部署到 $TARGET_ENV 环境..."docker-compose -f docker-compose.$TARGET_ENV.yml pulldocker-compose -f docker-compose.$TARGET_ENV.yml up -d# 健康检查echo "等待服务启动..."sleep 30# 检查服务健康状态for i in {1..10}; do if curl -f http://localhost:$TARGET_PORT/health; then echo "服务健康检查通过" break fi echo "健康检查失败,重试 $i/10" sleep 10done# 切换流量echo "切换 Nginx 配置..."cp nginx.$TARGET_ENV.conf /etc/nginx/sites-available/defaultnginx -s reload# 停止旧环境echo "停止 $CURRENT_ENV 环境..."docker-compose -f docker-compose.$CURRENT_ENV.yml downecho "部署完成!"### 滚动更新yaml# docker-compose.rolling.ymlversion: '3.8'services: web: image: myapp:${IMAGE_TAG:-latest} deploy: replicas: 3 update_config: parallelism: 1 delay: 10s failure_action: rollback monitor: 60s max_failure_ratio: 0.3 rollback_config: parallelism: 1 delay: 10s failure_action: pause monitor: 60s restart_policy: condition: any delay: 5s max_attempts: 3 window: 120s networks: - app-networknetworks: app-network: external: true## 监控和日志管理### 日志收集配置yaml# docker-compose.logging.ymlversion: '3.8'services: web: build: . logging: driver: "fluentd" options: fluentd-address: localhost:24224 tag: myapp.web # Fluentd 日志收集 fluentd: image: fluent/fluentd:v1.14-debian-1 volumes: - ./fluentd/conf:/fluentd/etc - ./logs:/var/log/fluentd ports: - "24224:24224" - "24224:24224/udp" # Elasticsearch elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0 environment: - discovery.type=single-node - "ES_JAVA_OPTS=-Xms512m -Xmx512m" volumes: - elasticsearch_data:/usr/share/elasticsearch/data # Kibana kibana: image: docker.elastic.co/kibana/kibana:8.5.0 ports: - "5601:5601" depends_on: - elasticsearchvolumes: elasticsearch_data:### 监控配置yaml# docker-compose.monitoring.ymlversion: '3.8'services: # Prometheus prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' # Grafana grafana: image: grafana/grafana:latest ports: - "3001:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana_data:/var/lib/grafana - ./grafana/dashboards:/etc/grafana/provisioning/dashboards - ./grafana/datasources:/etc/grafana/provisioning/datasources # Node Exporter node-exporter: image: prom/node-exporter:latest ports: - "9100:9100" volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)' # cAdvisor cadvisor: image: gcr.io/cadvisor/cadvisor:latest ports: - "8080:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:rovolumes: prometheus_data: grafana_data:## 安全最佳实践### 镜像安全dockerfile# 安全的 Dockerfile 示例FROM node:18-alpine AS base# 更新包管理器并安装安全更新RUN apk update && apk upgrade# 创建非 root 用户RUN addgroup -g 1001 -S nodejsRUN adduser -S nextjs -u 1001# 设置工作目录WORKDIR /app# 复制依赖文件COPY package*.json ./# 安装依赖RUN npm ci --only=production && \ npm cache clean --force && \ rm -rf /tmp/*# 复制应用代码COPY --chown=nextjs:nodejs . .# 移除不必要的文件RUN rm -rf .git .gitignore README.md# 设置正确的文件权限RUN chmod -R 755 /app# 切换到非 root 用户USER nextjs# 使用非特权端口EXPOSE 3000# 设置安全的环境变量ENV NODE_ENV=productionENV NPM_CONFIG_LOGLEVEL=warn# 健康检查HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node healthcheck.jsCMD ["node", "server.js"]### 容器运行时安全yaml# 安全的 docker-compose.ymlversion: '3.8'services: web: build: . # 只读根文件系统 read_only: true # 临时文件系统 tmpfs: - /tmp - /var/cache # 安全选项 security_opt: - no-new-privileges:true # 资源限制 deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M # 网络模式 networks: - app-network # 环境变量文件 env_file: - .env.production # 重启策略 restart: unless-stoppednetworks: app-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16### 密钥管理yaml# 使用 Docker Secretsversion: '3.8'services: web: image: myapp:latest secrets: - db_password - api_key environment: - DB_PASSWORD_FILE=/run/secrets/db_password - API_KEY_FILE=/run/secrets/api_keysecrets: db_password: file: ./secrets/db_password.txt api_key: external: true``````bash# 创建外部密钥echo "my-secret-api-key" | docker secret create api_key -## 性能优化### 镜像优化dockerfile# 优化的多阶段构建FROM node:18-alpine AS depsWORKDIR /appCOPY package*.json ./RUN npm ci --only=production && npm cache clean --forceFROM node:18-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run buildFROM node:18-alpine AS runnerWORKDIR /appENV NODE_ENV productionRUN addgroup -g 1001 -S nodejsRUN adduser -S nextjs -u 1001# 只复制必要的文件COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modulesCOPY --from=builder --chown=nextjs:nodejs /app/dist ./distCOPY --from=builder --chown=nextjs:nodejs /app/public ./publicCOPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.jsonUSER nextjsEXPOSE 3000CMD ["npm", "start"]### 容器资源优化yamlversion: '3.8'services: web: image: myapp:latest deploy: resources: limits: cpus: '1' memory: 1G reservations: cpus: '0.5' memory: 512M # 使用 init 进程 init: true # 优化 ulimits ulimits: nofile: soft: 65536 hard: 65536 # 共享内存大小 shm_size: 128mb### 网络优化yamlversion: '3.8'services: web: image: myapp:latest networks: frontend: aliases: - web-service backend: db: image: postgres:15-alpine networks: backend: nginx: image: nginx:alpine networks: frontend: ports: - "80:80"networks: frontend: driver: bridge backend: driver: bridge internal: true # 内部网络,不能访问外网## 故障排查和调试### 常用调试命令bash# 查看容器状态docker ps -a# 查看容器日志docker logs -f container_name# 进入容器调试docker exec -it container_name /bin/sh# 查看容器资源使用情况docker stats# 检查容器配置docker inspect container_name# 查看镜像层信息docker history image_name# 清理未使用的资源docker system prune -a# 查看网络配置docker network lsdocker network inspect network_name# 查看卷信息docker volume lsdocker volume inspect volume_name### 性能分析bash# 容器性能监控脚本#!/bin/bashCONTAINER_NAME=$1if [ -z "$CONTAINER_NAME" ]; then echo "Usage: $0 <container_name>" exit 1fiecho "=== 容器基本信息 ==="docker inspect $CONTAINER_NAME | jq '.[0].Config'echo "=== 资源使用情况 ==="docker stats $CONTAINER_NAME --no-streamecho "=== 进程信息 ==="docker exec $CONTAINER_NAME ps auxecho "=== 网络连接 ==="docker exec $CONTAINER_NAME netstat -tulpnecho "=== 磁盘使用 ==="docker exec $CONTAINER_NAME df -hecho "=== 内存使用详情 ==="docker exec $CONTAINER_NAME cat /proc/meminfo### 日志分析bash# 日志分析脚本#!/bin/bashCONTAINER_NAME=$1LOG_LINES=${2:-100}echo "=== 最近 $LOG_LINES 行日志 ==="docker logs --tail $LOG_LINES $CONTAINER_NAMEecho "=== 错误日志统计 ==="docker logs $CONTAINER_NAME 2>&1 | grep -i error | wc -lecho "=== 警告日志统计 ==="docker logs $CONTAINER_NAME 2>&1 | grep -i warn | wc -lecho "=== 最近的错误信息 ==="docker logs $CONTAINER_NAME 2>&1 | grep -i error | tail -10## 备份和恢复### 数据备份策略bash#!/bin/bash# backup.shset -eBACKUP_DIR="/backups"DATE=$(date +%Y%m%d_%H%M%S)# 创建备份目录mkdir -p $BACKUP_DIR# 备份数据库echo "备份数据库..."docker exec postgres_container pg_dump -U user myapp > $BACKUP_DIR/db_backup_$DATE.sql# 备份卷数据echo "备份应用数据..."docker run --rm \ -v myapp_data:/data \ -v $BACKUP_DIR:/backup \ alpine tar czf /backup/data_backup_$DATE.tar.gz -C /data .# 备份配置文件echo "备份配置文件..."tar czf $BACKUP_DIR/config_backup_$DATE.tar.gz docker-compose.yml .env nginx.conf# 清理旧备份(保留7天)find $BACKUP_DIR -name "*.sql" -mtime +7 -deletefind $BACKUP_DIR -name "*.tar.gz" -mtime +7 -deleteecho "备份完成: $BACKUP_DIR"### 恢复脚本bash#!/bin/bash# restore.shset -eBACKUP_FILE=$1if [ -z "$BACKUP_FILE" ]; then echo "Usage: $0 <backup_file>" exit 1fi# 停止服务echo "停止服务..."docker-compose down# 恢复数据库if [[ $BACKUP_FILE == *.sql ]]; then echo "恢复数据库..." docker-compose up -d db sleep 10 docker exec -i postgres_container psql -U user myapp < $BACKUP_FILEfi# 恢复数据卷if [[ $BACKUP_FILE == *data_backup*.tar.gz ]]; then echo "恢复数据卷..." docker run --rm \ -v myapp_data:/data \ -v $(dirname $BACKUP_FILE):/backup \ alpine tar xzf /backup/$(basename $BACKUP_FILE) -C /datafi# 启动服务echo "启动服务..."docker-compose up -decho "恢复完成"## 总结Docker 容器化部署是现代应用开发和运维的重要技能。通过本文的介绍,我们了解了:### 核心要点1. 容器化基础:理解镜像、容器、仓库的概念和关系2. Dockerfile 优化:使用多阶段构建、缓存优化等技术3. 编排管理:使用 Docker Compose 管理复杂应用4. 生产部署:实现 CI/CD、蓝绿部署、滚动更新等策略5. 监控运维:建立完善的日志收集和监控体系6. 安全防护:遵循安全最佳实践,保护容器和应用安全### 最佳实践1. 镜像优化:保持镜像小而精,使用多阶段构建2. 安全第一:使用非 root 用户,定期更新基础镜像3. 资源管理:合理设置资源限制和健康检查4. 监控告警:建立完善的监控和日志系统5. 备份恢复:制定完整的备份和恢复策略Docker 不仅仅是一个工具,更是一种思维方式的转变。它让我们能够以更加标准化、自动化的方式管理应用的整个生命周期,从开发到生产,从部署到运维,都变得更加高效和可靠。掌握 Docker 容器化技术,将为你的开发和运维工作带来质的提升,让你能够构建更加现代化、可扩展的应用架构。践指南

引言Vue 3 引入的组合式 API (Composition API) 为 Vue 开发带来了全新的编程范式。相比于选项式 API,组合式 API 提供了更好的逻辑复用、类型推导和代码组织方式。本文将深入探讨 Vue 3 组合式 API 的最佳实践,帮助开发者充分发挥其潜力。## 组合式 API 基础概念### setup() 函数setup() 是组合式 API 的入口点:vue<template> <div> <h1>{{ title }}</h1> <p>计数: {{ count }}</p> <button @click="increment">增加</button> </div></template><script>import { ref, reactive } from 'vue'export default { name: 'BasicExample', setup() { // 响应式数据 const title = ref('Vue 3 组合式 API') const count = ref(0) // 方法 const increment = () => { count.value++ } // 返回模板需要的数据和方法 return { title, count, increment } }}</script>### script setup 语法糖更简洁的写法:vue<template> <div> <h1>{{ title }}</h1> <p>计数: {{ count }}</p> <button @click="increment">增加</button> </div></template><script setup>import { ref } from 'vue'// 直接定义,无需返回const title = ref('Vue 3 组合式 API')const count = ref(0)const increment = () => { count.value++}</script>## 响应式系统深入理解### ref vs reactive#### ref 的使用场景vue<script setup>import { ref, computed, watch } from 'vue'// 基本类型使用 refconst count = ref(0)const message = ref('Hello')const isVisible = ref(true)// 对象也可以使用 refconst user = ref({ name: 'John', age: 30})// 访问和修改console.log(count.value) // 需要 .valuecount.value++// 在模板中自动解包,不需要 .value// <template>{{ count }}</template>// 计算属性const doubleCount = computed(() => count.value * 2)// 监听器watch(count, (newVal, oldVal) => { console.log(`count changed from ${oldVal} to ${newVal}`)})</script>#### reactive 的使用场景vue<script setup>import { reactive, toRefs, computed } from 'vue'// 对象使用 reactiveconst state = reactive({ count: 0, message: 'Hello', user: { name: 'John', age: 30 }, todos: []})// 直接访问,无需 .valueconsole.log(state.count)state.count++// 添加新属性state.newProperty = 'new value'// 解构时保持响应性const { count, message } = toRefs(state)// 计算属性const userInfo = computed(() => `${state.user.name} (${state.user.age})`)// 方法const addTodo = (text) => { state.todos.push({ id: Date.now(), text, completed: false })}</script>### 响应式工具函数#### toRef 和 toRefsvue<script setup>import { reactive, toRef, toRefs } from 'vue'const state = reactive({ count: 0, message: 'Hello', user: { name: 'John', age: 30 }})// toRef: 为单个属性创建 refconst count = toRef(state, 'count')// toRefs: 为所有属性创建 refconst { message, user } = toRefs(state)// 现在可以解构使用,且保持响应性console.log(count.value) // 0console.log(message.value) // 'Hello'// 修改会影响原对象count.value++console.log(state.count) // 1</script>#### readonly 和 shallowRefvue<script setup>import { ref, reactive, readonly, shallowRef } from 'vue'// readonly: 创建只读代理const state = reactive({ count: 0, message: 'Hello'})const readonlyState = readonly(state)// 尝试修改会在开发环境发出警告// readonlyState.count++ // 警告// shallowRef: 浅层响应式const shallowState = shallowRef({ count: 0, nested: { value: 1 }})// 只有根级别的替换会触发响应shallowState.value = { count: 1, nested: { value: 2 } } // 响应式shallowState.value.count++ // 非响应式shallowState.value.nested.value++ // 非响应式</script>## 生命周期钩子### 组合式 API 中的生命周期vue<script setup>import { ref, onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount} from 'vue'const count = ref(0)const message = ref('')// 组件挂载前onBeforeMount(() => { console.log('组件即将挂载')})// 组件挂载后onMounted(() => { console.log('组件已挂载') // DOM 操作 document.title = '组合式 API 示例'})// 组件更新前onBeforeUpdate(() => { console.log('组件即将更新')})// 组件更新后onUpdated(() => { console.log('组件已更新')})// 组件卸载前onBeforeUnmount(() => { console.log('组件即将卸载') // 清理工作})// 组件卸载后onUnmounted(() => { console.log('组件已卸载') // 清理定时器、事件监听器等})</script>### 生命周期最佳实践vue<script setup>import { ref, onMounted, onUnmounted } from 'vue'const count = ref(0)let timer = null// 封装定时器逻辑const useTimer = (callback, interval) => { let timer = null const start = () => { if (timer) return timer = setInterval(callback, interval) } const stop = () => { if (timer) { clearInterval(timer) timer = null } } // 组件卸载时自动清理 onUnmounted(() => { stop() }) return { start, stop }}// 使用定时器const { start: startTimer, stop: stopTimer } = useTimer(() => { count.value++}, 1000)onMounted(() => { startTimer()})</script>## 计算属性和监听器### 计算属性最佳实践vue<script setup>import { ref, computed, reactive } from 'vue'const firstName = ref('John')const lastName = ref('Doe')// 简单计算属性const fullName = computed(() => { return `${firstName.value} ${lastName.value}`})// 可写计算属性const writableFullName = computed({ get() { return `${firstName.value} ${lastName.value}` }, set(value) { const names = value.split(' ') firstName.value = names[0] lastName.value = names[names.length - 1] }})// 复杂计算属性const todos = reactive([ { id: 1, text: '学习 Vue', completed: false }, { id: 2, text: '写代码', completed: true }, { id: 3, text: '休息', completed: false }])const completedTodos = computed(() => { return todos.filter(todo => todo.completed)})const todoStats = computed(() => { const total = todos.length const completed = completedTodos.value.length const remaining = total - completed return { total, completed, remaining, completionRate: total > 0 ? (completed / total * 100).toFixed(1) : 0 }})// 使用示例console.log(fullName.value) // 'John Doe'writableFullName.value = 'Jane Smith'console.log(firstName.value) // 'Jane'console.log(lastName.value) // 'Smith'</script>### 监听器最佳实践vue<script setup>import { ref, reactive, watch, watchEffect, nextTick } from 'vue'const count = ref(0)const message = ref('')const state = reactive({ user: { name: 'John', age: 30 }})// 基本监听watch(count, (newVal, oldVal) => { console.log(`count changed from ${oldVal} to ${newVal}`)})// 监听多个源watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => { console.log('Multiple values changed')})// 深度监听watch( () => state.user, (newUser, oldUser) => { console.log('User changed:', newUser) }, { deep: true })// 立即执行watch( count, (newVal) => { console.log('Count is:', newVal) }, { immediate: true })// watchEffect: 自动追踪依赖watchEffect(() => { console.log(`Count is ${count.value}, message is ${message.value}`)})// 停止监听const stopWatcher = watch(count, (newVal) => { if (newVal > 10) { console.log('Count exceeded 10, stopping watcher') stopWatcher() }})// 异步监听watch( count, async (newVal) => { if (newVal > 0) { await nextTick() console.log('DOM updated') } })// 监听器清理watchEffect((onInvalidate) => { const timer = setTimeout(() => { console.log('Timer executed') }, 1000) onInvalidate(() => { clearTimeout(timer) console.log('Timer cleared') })})</script>## 逻辑复用和组合### 自定义组合函数 (Composables)#### 基础组合函数javascript// composables/useCounter.jsimport { ref, computed } from 'vue'export function useCounter(initialValue = 0) { const count = ref(initialValue) const increment = () => count.value++ const decrement = () => count.value-- const reset = () => count.value = initialValue const isEven = computed(() => count.value % 2 === 0) const isPositive = computed(() => count.value > 0) return { count, increment, decrement, reset, isEven, isPositive }}``````vue<!-- 使用组合函数 --><template> <div> <p>计数: {{ count }}</p> <p>是偶数: {{ isEven }}</p> <p>是正数: {{ isPositive }}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="reset">重置</button> </div></template><script setup>import { useCounter } from '@/composables/useCounter'const { count, increment, decrement, reset, isEven, isPositive } = useCounter(10)</script>#### 高级组合函数javascript// composables/useFetch.jsimport { ref, reactive, watchEffect } from 'vue'export function useFetch(url, options = {}) { const data = ref(null) const error = ref(null) const loading = ref(false) const state = reactive({ data, error, loading }) const execute = async () => { loading.value = true error.value = null try { const response = await fetch(url.value || url, { ...options }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result = await response.json() data.value = result } catch (err) { error.value = err.message } finally { loading.value = false } } // 如果 url 是响应式的,自动重新请求 if (typeof url === 'object' && url.value !== undefined) { watchEffect(() => { if (url.value) { execute() } }) } else { execute() } return { ...state, execute, refresh: execute }}``````vue<!-- 使用 useFetch --><template> <div> <div v-if="loading">加载中...</div> <div v-else-if="error">错误: {{ error }}</div> <div v-else-if="data"> <h3>用户信息</h3> <p>姓名: {{ data.name }}</p> <p>邮箱: {{ data.email }}</p> </div> <button @click="refresh">刷新</button> </div></template><script setup>import { ref } from 'vue'import { useFetch } from '@/composables/useFetch'const userId = ref(1)const url = computed(() => `https://jsonplaceholder.typicode.com/users/${userId.value}`)const { data, loading, error, refresh } = useFetch(url)</script>### 复杂状态管理javascript// composables/useStore.jsimport { reactive, readonly, computed } from 'vue'function createStore(initialState, actions) { const state = reactive(initialState) const getters = {} const mutations = {} // 创建 getters Object.keys(actions.getters || {}).forEach(key => { getters[key] = computed(() => actions.getters[key](state)) }) // 创建 mutations Object.keys(actions.mutations || {}).forEach(key => { mutations[key] = (...args) => actions.mutations[key](state, ...args) }) return { state: readonly(state), getters, ...mutations }}// 使用示例export const useUserStore = () => { return createStore( { users: [], currentUser: null, loading: false }, { getters: { userCount: (state) => state.users.length, isLoggedIn: (state) => !!state.currentUser, activeUsers: (state) => state.users.filter(user => user.active) }, mutations: { setUsers(state, users) { state.users = users }, setCurrentUser(state, user) { state.currentUser = user }, setLoading(state, loading) { state.loading = loading }, addUser(state, user) { state.users.push(user) }, removeUser(state, userId) { const index = state.users.findIndex(user => user.id === userId) if (index > -1) { state.users.splice(index, 1) } } } } )}## 组件通信### Props 和 Emitsvue<!-- 子组件 --><template> <div class="user-card"> <h3>{{ user.name }}</h3> <p>{{ user.email }}</p> <button @click="handleEdit">编辑</button> <button @click="handleDelete">删除</button> </div></template><script setup>import { computed } from 'vue'// 定义 propsconst props = defineProps({ user: { type: Object, required: true, validator: (user) => { return user && typeof user.name === 'string' && typeof user.email === 'string' } }, editable: { type: Boolean, default: true }})// 定义 emitsconst emit = defineEmits(['edit', 'delete', 'update'])// 计算属性const isEditable = computed(() => props.editable && props.user.id)// 方法const handleEdit = () => { if (isEditable.value) { emit('edit', props.user) }}const handleDelete = () => { emit('delete', props.user.id)}// 暴露给父组件的方法defineExpose({ focus: () => { // 聚焦逻辑 }})</script>``````vue<!-- 父组件 --><template> <div> <UserCard v-for="user in users" :key="user.id" :user="user" :editable="true" @edit="handleEditUser" @delete="handleDeleteUser" ref="userCards" /> </div></template><script setup>import { ref } from 'vue'import UserCard from './UserCard.vue'const users = ref([ { id: 1, name: 'John', email: 'john@example.com' }, { id: 2, name: 'Jane', email: 'jane@example.com' }])const userCards = ref([])const handleEditUser = (user) => { console.log('编辑用户:', user)}const handleDeleteUser = (userId) => { const index = users.value.findIndex(user => user.id === userId) if (index > -1) { users.value.splice(index, 1) }}// 调用子组件方法const focusFirstCard = () => { if (userCards.value[0]) { userCards.value[0].focus() }}</script>### Provide/Injectvue<!-- 祖先组件 --><template> <div> <ThemeProvider> <UserList /> </ThemeProvider> </div></template><script setup>import { provide, reactive } from 'vue'import ThemeProvider from './ThemeProvider.vue'import UserList from './UserList.vue'// 提供主题配置const theme = reactive({ primaryColor: '#007bff', fontSize: '14px', darkMode: false})const toggleDarkMode = () => { theme.darkMode = !theme.darkMode theme.primaryColor = theme.darkMode ? '#6c757d' : '#007bff'}provide('theme', theme)provide('toggleDarkMode', toggleDarkMode)</script>``````vue<!-- 后代组件 --><template> <div :style="themeStyles"> <h2>用户列表</h2> <button @click="toggleDarkMode"> 切换到{{ theme.darkMode ? '浅色' : '深色' }}模式 </button> </div></template><script setup>import { inject, computed } from 'vue'// 注入主题const theme = inject('theme')const toggleDarkMode = inject('toggleDarkMode')// 计算样式const themeStyles = computed(() => ({ color: theme.darkMode ? '#fff' : '#000', backgroundColor: theme.darkMode ? '#333' : '#fff', fontSize: theme.fontSize}))</script>## 性能优化### 响应式优化vue<script setup>import { ref, reactive, shallowRef, shallowReactive, markRaw } from 'vue'// 对于大型数据结构,使用 shallowRefconst largeData = shallowRef({ items: new Array(10000).fill(0).map((_, i) => ({ id: i, value: i }))})// 更新整个对象才会触发响应const updateLargeData = () => { largeData.value = { items: largeData.value.items.map(item => ({ ...item, value: item.value + 1 })) }}// 对于不需要响应式的数据,使用 markRawconst nonReactiveData = markRaw({ heavyObject: new SomeHeavyClass(), config: { /* 大量配置 */ }})// 使用 shallowReactive 优化嵌套对象const shallowState = shallowReactive({ count: 0, nested: { value: 1 // 这个不会是响应式的 }})</script>### 计算属性缓存优化vue<script setup>import { ref, computed, watchEffect } from 'vue'const items = ref([])const filter = ref('')const sortBy = ref('name')// 分步计算,利用缓存const filteredItems = computed(() => { if (!filter.value) return items.value return items.value.filter(item => item.name.toLowerCase().includes(filter.value.toLowerCase()) )})const sortedItems = computed(() => { return [...filteredItems.value].sort((a, b) => { const aVal = a[sortBy.value] const bVal = b[sortBy.value] return aVal < bVal ? -1 : aVal > bVal ? 1 : 0 })})// 避免在模板中进行复杂计算const itemsWithIndex = computed(() => { return sortedItems.value.map((item, index) => ({ ...item, displayIndex: index + 1 }))})</script>### 组件懒加载vue<script setup>import { ref, defineAsyncComponent } from 'vue'// 异步组件const HeavyComponent = defineAsyncComponent({ loader: () => import('./HeavyComponent.vue'), loadingComponent: () => import('./LoadingComponent.vue'), errorComponent: () => import('./ErrorComponent.vue'), delay: 200, timeout: 3000})const showHeavyComponent = ref(false)// 条件加载const ConditionalComponent = defineAsyncComponent(() => { return showHeavyComponent.value ? import('./HeavyComponent.vue') : Promise.resolve({ template: '<div>占位符</div>' })})</script>## 类型安全 (TypeScript)### 基础类型定义vue<script setup lang="ts">import { ref, reactive, computed } from 'vue'// 接口定义interface User { id: number name: string email: string avatar?: string}interface UserState { users: User[] currentUser: User | null loading: boolean}// 类型化的响应式数据const users = ref<User[]>([])const currentUser = ref<User | null>(null)const state = reactive<UserState>({ users: [], currentUser: null, loading: false})// 类型化的计算属性const userCount = computed<number>(() => users.value.length)const hasUsers = computed<boolean>(() => userCount.value > 0)// 类型化的方法const addUser = (user: Omit<User, 'id'>): void => { const newUser: User = { id: Date.now(), ...user } users.value.push(newUser)}const findUser = (id: number): User | undefined => { return users.value.find(user => user.id === id)}</script>### 组合函数类型定义typescript// composables/useApi.tsimport { ref, Ref } from 'vue'interface ApiResponse<T> { data: T | null loading: boolean error: string | null}interface UseApiReturn<T> extends ApiResponse<T> { execute: () => Promise<void> reset: () => void}export function useApi<T>( url: string | Ref<string>, options?: RequestInit): UseApiReturn<T> { const data = ref<T | null>(null) const loading = ref<boolean>(false) const error = ref<string | null>(null) const execute = async (): Promise<void> => { loading.value = true error.value = null try { const response = await fetch( typeof url === 'string' ? url : url.value, options ) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } data.value = await response.json() } catch (err) { error.value = err instanceof Error ? err.message : 'Unknown error' } finally { loading.value = false } } const reset = (): void => { data.value = null error.value = null loading.value = false } return { data: data as Ref<T | null>, loading, error, execute, reset }}### Props 和 Emits 类型定义vue<script setup lang="ts">interface User { id: number name: string email: string}interface Props { user: User editable?: boolean maxLength?: number}interface Emits { (e: 'update', user: User): void (e: 'delete', id: number): void (e: 'error', message: string): void}// 定义 props 和 emitsconst props = withDefaults(defineProps<Props>(), { editable: true, maxLength: 100})const emit = defineEmits<Emits>()// 类型安全的方法const handleUpdate = (updatedUser: User): void => { emit('update', updatedUser)}const handleDelete = (): void => { emit('delete', props.user.id)}const handleError = (message: string): void => { emit('error', message)}</script>## 测试策略### 组合函数测试javascript// tests/composables/useCounter.test.jsimport { describe, it, expect } from 'vitest'import { useCounter } from '@/composables/useCounter'describe('useCounter', () => { it('should initialize with default value', () => { const { count } = useCounter() expect(count.value).toBe(0) }) it('should initialize with custom value', () => { const { count } = useCounter(10) expect(count.value).toBe(10) }) it('should increment count', () => { const { count, increment } = useCounter(5) increment() expect(count.value).toBe(6) }) it('should decrement count', () => { const { count, decrement } = useCounter(5) decrement() expect(count.value).toBe(4) }) it('should reset count', () => { const { count, increment, reset } = useCounter(5) increment() increment() expect(count.value).toBe(7) reset() expect(count.value).toBe(5) }) it('should compute isEven correctly', () => { const { count, increment, isEven } = useCounter(2) expect(isEven.value).toBe(true) increment() expect(isEven.value).toBe(false) })})### 组件测试javascript// tests/components/UserCard.test.jsimport { describe, it, expect, vi } from 'vitest'import { mount } from '@vue/test-utils'import UserCard from '@/components/UserCard.vue'describe('UserCard', () => { const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com' } it('should render user information', () => { const wrapper = mount(UserCard, { props: { user: mockUser } }) expect(wrapper.text()).toContain('John Doe') expect(wrapper.text()).toContain('john@example.com') }) it('should emit edit event when edit button is clicked', async () => { const wrapper = mount(UserCard, { props: { user: mockUser, editable: true } }) await wrapper.find('[data-test="edit-button"]').trigger('click') expect(wrapper.emitted('edit')).toBeTruthy() expect(wrapper.emitted('edit')[0]).toEqual([mockUser]) }) it('should emit delete event when delete button is clicked', async () => { const wrapper = mount(UserCard, { props: { user: mockUser } }) await wrapper.find('[data-test="delete-button"]').trigger('click') expect(wrapper.emitted('delete')).toBeTruthy() expect(wrapper.emitted('delete')[0]).toEqual([mockUser.id]) }) it('should not show edit button when not editable', () => { const wrapper = mount(UserCard, { props: { user: mockUser, editable: false } }) expect(wrapper.find('[data-test="edit-button"]').exists()).toBe(false) })})## 最佳实践总结### 代码组织1. 按功能分组:将相关的响应式数据、计算属性和方法放在一起2. 提取组合函数:将可复用的逻辑提取为组合函数3. 保持单一职责:每个组合函数只负责一个特定的功能vue<script setup>// 1. 导入import { ref, computed, onMounted } from 'vue'import { useRouter } from 'vue-router'import { useUserStore } from '@/stores/user'// 2. 组合函数const router = useRouter()const userStore = useUserStore()// 3. 响应式数据const loading = ref(false)const error = ref(null)// 4. 计算属性const isLoggedIn = computed(() => userStore.currentUser !== null)// 5. 方法const handleLogin = async () => { // 登录逻辑}// 6. 生命周期onMounted(() => { // 初始化逻辑})</script>### 性能优化原则1. 合理使用响应式:不是所有数据都需要响应式2. 避免不必要的计算:使用计算属性缓存复杂计算3. 组件懒加载:按需加载大型组件4. 优化渲染:使用 v-memo 和 v-once 指令### 类型安全1. 使用 TypeScript:获得更好的开发体验和代码质量2. 定义清晰的接口:为数据结构定义明确的类型3. 类型化组合函数:确保组合函数的类型安全### 测试策略1. 单元测试组合函数:测试业务逻辑的核心部分2. 组件测试:测试组件的行为和交互3. 集成测试:测试组件间的协作## 总结Vue 3 的组合式 API 为我们提供了更灵活、更强大的开发方式。通过合理使用响应式系统、生命周期钩子、计算属性和监听器,我们可以构建出更加健壮和可维护的应用。关键要点:1. 理解响应式原理:掌握 ref 和 reactive 的使用场景2. 善用组合函数:提高代码复用性和可测试性3. 注重性能优化:合理使用响应式,避免不必要的计算4. 保证类型安全:使用 TypeScript 提高代码质量5. 完善测试覆盖:确保代码的可靠性组合式 API 不仅仅是一种新的语法,更是一种新的思维方式。它鼓励我们以更加函数式的方式思考和组织代码,最终构建出更加优雅和高效的 Vue 应用。大数据

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐