@umacloud/knowledge 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/00-governance/governance-capabilities.md +557 -0
- package/00-governance/knowledge-map.md +39 -0
- package/00-governance/maintenance-policy.md +76 -0
- package/00-governance/review-checklist.md +81 -0
- package/README.md +13 -0
- package/ai/01-standards/agent-development-complete.md +691 -0
- package/ai/01-standards/llm-application-complete.md +488 -0
- package/ai/01-standards/mlops-complete.md +798 -0
- package/ai/01-standards/prompt-engineering-complete.md +646 -0
- package/ai/01-standards/rag-architecture-complete.md +649 -0
- package/ai/02-playbooks/llm-evaluation-playbook.md +847 -0
- package/ai/03-checklists/ai-project-checklist.md +215 -0
- package/ai/04-antipatterns/ai-antipatterns.md +661 -0
- package/ai/05-cases/case-rag-production.md +147 -0
- package/ai/06-glossary/ai-glossary.md +162 -0
- package/ai/agent-evaluation-benchmark.md +53 -0
- package/ai/ai-agent-memory-context-management.md +41 -0
- package/ai/ai-cost-capacity-optimization-playbook.md +42 -0
- package/ai/ai-data-security-and-compliance-playbook.md +37 -0
- package/ai/ai-domain-index-and-checklist.md +40 -0
- package/ai/ai-governance-maturity-model.md +50 -0
- package/ai/ai-model-selection-and-routing-strategy.md +47 -0
- package/ai/ai-observability-and-oncall-runbook.md +52 -0
- package/ai/ai-rag-engineering-playbook.md +42 -0
- package/ai/ai-red-team-and-safety-evaluation.md +42 -0
- package/ai/ai-release-readiness-and-rollback-gate.md +42 -0
- package/ai/llm-agent-engineering-deep-dive.md +57 -0
- package/ai/prompt-and-tool-guardrails.md +52 -0
- package/api/01-standards/enterprise-api-standards.md +198 -0
- package/api/01-standards/rest-api-design-guide.md +63 -0
- package/api/02-playbooks/api-pagination-playbook.md +93 -0
- package/api/02-playbooks/graphql-production-playbook.md +176 -0
- package/api/03-checklists/api-review-checklist.md +55 -0
- package/api/04-antipatterns/api-antipatterns.md +112 -0
- package/architecture/01-standards/api-gateway-patterns.md +496 -0
- package/architecture/01-standards/cloud-native-patterns.md +644 -0
- package/architecture/01-standards/distributed-systems-patterns.md +591 -0
- package/architecture/01-standards/event-driven-architecture.md +595 -0
- package/architecture/01-standards/microservices-patterns-complete.md +968 -0
- package/architecture/01-standards/microservices-patterns.md +495 -0
- package/architecture/01-standards/system-design-interview.md +664 -0
- package/architecture/02-playbooks/microservices-patterns-playbook.md +137 -0
- package/architecture/02-playbooks/migration-playbook.md +780 -0
- package/architecture/02-playbooks/system-design-playbook.md +779 -0
- package/architecture/03-checklists/architecture-decision-checklist.md +297 -0
- package/architecture/04-antipatterns/architecture-antipatterns.md +417 -0
- package/architecture/05-cases/case-netflix-microservices.md +413 -0
- package/architecture/06-glossary/architecture-glossary.md +164 -0
- package/architecture/adr-template-and-examples.md +38 -0
- package/architecture/api-gateway-deep-dive.md +1291 -0
- package/architecture/configuration-management.md +1162 -0
- package/architecture/distributed-transactions.md +1220 -0
- package/architecture/microservices-complete.md +735 -0
- package/architecture/resilience-and-disaster-patterns.md +37 -0
- package/architecture/service-governance.md +1198 -0
- package/architecture/system-architecture-deep-dive.md +37 -0
- package/backend/01-standards/analytics-and-growth.md +65 -0
- package/backend/01-standards/api-and-error-conventions.md +120 -0
- package/backend/01-standards/application-layering-and-packaging.md +160 -0
- package/backend/01-standards/auth-implementation.md +104 -0
- package/backend/01-standards/backend-framework-idioms.md +74 -0
- package/backend/01-standards/background-jobs-and-async.md +66 -0
- package/backend/01-standards/caching-strategies-complete.md +390 -0
- package/backend/01-standards/config-and-observability.md +77 -0
- package/backend/01-standards/data-modeling-and-persistence.md +94 -0
- package/backend/01-standards/django-complete.md +1765 -0
- package/backend/01-standards/email-and-notifications.md +64 -0
- package/backend/01-standards/fastapi-complete.md +925 -0
- package/backend/01-standards/file-upload-and-storage.md +66 -0
- package/backend/01-standards/graphql-api-complete.md +416 -0
- package/backend/01-standards/llm-application-standard.md +78 -0
- package/backend/01-standards/message-queue-patterns.md +379 -0
- package/backend/01-standards/microservices-and-distributed.md +78 -0
- package/backend/01-standards/nestjs-complete.md +2167 -0
- package/backend/01-standards/payment-integration.md +80 -0
- package/backend/01-standards/rate-limiting-complete.md +451 -0
- package/backend/01-standards/realtime-and-websocket.md +65 -0
- package/backend/01-standards/search-and-filtering.md +64 -0
- package/backend/01-standards/spring-boot-complete.md +445 -0
- package/backend/02-playbooks/api-design-playbook.md +718 -0
- package/backend/02-playbooks/email-send-playbook.md +130 -0
- package/backend/02-playbooks/file-upload-s3-playbook.md +153 -0
- package/backend/02-playbooks/typescript-enterprise-playbook.md +133 -0
- package/backend/02-playbooks/websocket-realtime-playbook.md +154 -0
- package/backend/03-checklists/api-launch-checklist.md +189 -0
- package/backend/04-antipatterns/backend-antipatterns.md +1051 -0
- package/blockchain/01-standards/blockchain-basics.md +557 -0
- package/blockchain/01-standards/smart-contract-development.md +1315 -0
- package/cicd/01-standards/deployment-and-delivery-standard.md +96 -0
- package/cicd/01-standards/github-actions-complete.md +473 -0
- package/cicd/01-standards/release-and-store-submission.md +75 -0
- package/cicd/02-playbooks/cicd-pipeline-playbook.md +144 -0
- package/cicd/02-playbooks/release-management-playbook.md +605 -0
- package/cicd/03-checklists/pipeline-security-checklist.md +168 -0
- package/cicd/04-antipatterns/cicd-antipatterns.md +589 -0
- package/cicd/05-cases/case-deployment-automation.md +221 -0
- package/cicd/05-cases/case-gitops-transformation.md +212 -0
- package/cicd/06-glossary/cicd-glossary.md +114 -0
- package/cicd/cicd-blueprint-deep-dive.md +38 -0
- package/cicd/release-readiness-gate.md +37 -0
- package/cloud-native/01-standards/container-security.md +741 -0
- package/cloud-native/01-standards/kubernetes-complete.md +812 -0
- package/cloud-native/02-playbooks/api-gateway-playbook.md +155 -0
- package/cloud-native/02-playbooks/gitops-with-argocd.md +760 -0
- package/cloud-native/02-playbooks/k8s-troubleshooting-playbook.md +1942 -0
- package/cloud-native/02-playbooks/message-queue-playbook.md +129 -0
- package/cloud-native/02-playbooks/multicloud-governance.md +726 -0
- package/cloud-native/02-playbooks/serverless-patterns.md +788 -0
- package/cloud-native/02-playbooks/service-mesh-playbook.md +612 -0
- package/cloud-native/02-playbooks/terraform-iac-playbook.md +143 -0
- package/cloud-native/03-checklists/container-security-checklist.md +431 -0
- package/cloud-native/03-checklists/k8s-production-readiness-checklist.md +460 -0
- package/cloud-native/04-antipatterns/container-antipatterns.md +660 -0
- package/cloud-native/04-antipatterns/k8s-antipatterns.md +743 -0
- package/cloud-native/05-cases/case-k8s-migration.md +478 -0
- package/cloud-native/05-cases/case-k8s-scaling.md +642 -0
- package/cloud-native/05-cases/case-k8s-security-incident.md +397 -0
- package/cloud-native/06-glossary/cloud-native-glossary.md +337 -0
- package/cross-platform/01-standards/cross-platform-frameworks.md +83 -0
- package/cross-platform/01-standards/platform-selection-and-architecture.md +77 -0
- package/data/01-standards/elasticsearch-complete.md +2098 -0
- package/data/01-standards/postgresql-complete.md +1613 -0
- package/data/01-standards/redis-complete.md +1527 -0
- package/data/02-playbooks/database-optimization-playbook.md +403 -0
- package/data/02-playbooks/elasticsearch-production-playbook.md +132 -0
- package/data/03-checklists/database-launch-checklist.md +187 -0
- package/data/04-antipatterns/database-antipatterns.md +873 -0
- package/data/05-cases/case-database-migration.md +310 -0
- package/data/06-glossary/database-glossary.md +440 -0
- package/data/data-governance-and-modeling-deep-dive.md +39 -0
- package/data-engineering/01-standards/airflow-complete.md +523 -0
- package/data-engineering/01-standards/kafka-complete.md +1521 -0
- package/data-engineering/02-playbooks/spark-etl-playbook.md +496 -0
- package/data-engineering/03-checklists/pipeline-launch-checklist.md +194 -0
- package/data-engineering/04-antipatterns/data-pipeline-antipatterns.md +684 -0
- package/data-engineering/05-cases/case-real-time-pipeline.md +355 -0
- package/data-engineering/06-glossary/data-engineering-glossary.md +429 -0
- package/database/01-standards/database-schema-standards.md +147 -0
- package/database/02-playbooks/postgresql-optimization-quick.md +52 -0
- package/database/02-playbooks/postgresql-performance-optimization.md +58 -0
- package/database/02-playbooks/postgresql-production-playbook.md +146 -0
- package/database/02-playbooks/redis-caching-playbook.md +117 -0
- package/database/03-checklists/database-review-checklist.md +50 -0
- package/database/04-antipatterns/database-antipatterns.md +112 -0
- package/design/01-standards/ui-design-system-complete.md +423 -0
- package/design/02-playbooks/design-handoff-playbook.md +254 -0
- package/design/02-playbooks/design-review-playbook.md +388 -0
- package/design/03-checklists/design-review-checklist.md +246 -0
- package/design/04-antipatterns/design-antipatterns.md +378 -0
- package/design/05-cases/case-design-system-adoption.md +328 -0
- package/design/06-glossary/design-glossary.md +329 -0
- package/design/ui-full-lifecycle-cross-platform-playbook.md +571 -0
- package/design/ux-system-deep-dive.md +38 -0
- package/design-systems/00-craft-rules.md +71 -0
- package/design-systems/aesthetic-families.md +43 -0
- package/design-systems/anti-ai-slop.md +162 -0
- package/design-systems/bold-geometric.md +120 -0
- package/design-systems/brutalist-bold.md +103 -0
- package/design-systems/editorial-clean.md +109 -0
- package/design-systems/glass-aurora.md +108 -0
- package/design-systems/modern-minimal.md +145 -0
- package/design-systems/premium-luxury.md +106 -0
- package/design-systems/product-type-design-map.md +48 -0
- package/design-systems/soft-warm.md +123 -0
- package/design-systems/tech-utility.md +113 -0
- package/desktop/01-standards/desktop-app-standard.md +72 -0
- package/desktop/01-standards/desktop-design.md +71 -0
- package/development/00-governance/document-template.md +41 -0
- package/development/01-standards/api-versioning-strategies.md +432 -0
- package/development/01-standards/authentication-patterns-complete.md +479 -0
- package/development/01-standards/css-architecture-complete.md +550 -0
- package/development/01-standards/database-migration-strategies.md +484 -0
- package/development/01-standards/elasticsearch-complete.md +347 -0
- package/development/01-standards/git-complete.md +371 -0
- package/development/01-standards/golang-complete.md +1565 -0
- package/development/01-standards/graphql-complete.md +298 -0
- package/development/01-standards/javascript-bundlers-complete.md +469 -0
- package/development/01-standards/javascript-typescript-complete.md +528 -0
- package/development/01-standards/jest-complete.md +275 -0
- package/development/01-standards/linux-complete.md +234 -0
- package/development/01-standards/logging-observability-complete.md +526 -0
- package/development/01-standards/microservices-communication.md +502 -0
- package/development/01-standards/mongodb-complete.md +406 -0
- package/development/01-standards/oauth2-complete.md +285 -0
- package/development/01-standards/performance-optimization-complete.md +289 -0
- package/development/01-standards/playwright-complete.md +247 -0
- package/development/01-standards/postgresql-complete.md +456 -0
- package/development/01-standards/pytest-complete.md +340 -0
- package/development/01-standards/python-async-programming.md +902 -0
- package/development/01-standards/python-complete.md +956 -0
- package/development/01-standards/python-decorators-complete.md +799 -0
- package/development/01-standards/python-design-patterns.md +2854 -0
- package/development/01-standards/python-packaging-distribution.md +420 -0
- package/development/01-standards/python-testing-strategies.md +607 -0
- package/development/01-standards/python-web-frameworks-comparison.md +471 -0
- package/development/01-standards/redis-complete.md +317 -0
- package/development/01-standards/rest-api-complete.md +316 -0
- package/development/01-standards/rust-complete.md +578 -0
- package/development/01-standards/typescript-advanced-types.md +1513 -0
- package/development/01-standards/web-security-complete.md +292 -0
- package/development/02-playbooks/api-design-playbook.md +810 -0
- package/development/02-playbooks/database-migration-playbook.md +580 -0
- package/development/02-playbooks/debugging-playbook.md +692 -0
- package/development/02-playbooks/feature-delivery-playbook.md +430 -0
- package/development/02-playbooks/incident-hotfix-playbook.md +387 -0
- package/development/02-playbooks/performance-optimization-playbook.md +531 -0
- package/development/02-playbooks/performance-tuning-playbook.md +652 -0
- package/development/02-playbooks/refactor-playbook.md +403 -0
- package/development/02-playbooks/release-playbook.md +469 -0
- package/development/03-checklists/architecture-review-checklist.md +168 -0
- package/development/03-checklists/data-migration-checklist.md +157 -0
- package/development/03-checklists/oncall-handover-checklist.md +173 -0
- package/development/03-checklists/pr-checklist.md +158 -0
- package/development/03-checklists/production-readiness-checklist.md +190 -0
- package/development/03-checklists/release-readiness-checklist.md +154 -0
- package/development/03-checklists/security-review-checklist.md +182 -0
- package/development/04-antipatterns/api-antipatterns.md +657 -0
- package/development/04-antipatterns/architecture-antipatterns.md +686 -0
- package/development/04-antipatterns/backend-antipatterns.md +648 -0
- package/development/04-antipatterns/cicd-antipatterns.md +540 -0
- package/development/04-antipatterns/code-smell-antipatterns.md +571 -0
- package/development/04-antipatterns/data-antipatterns.md +658 -0
- package/development/04-antipatterns/database-antipatterns.md +578 -0
- package/development/04-antipatterns/frontend-antipatterns.md +635 -0
- package/development/04-antipatterns/reliability-antipatterns.md +700 -0
- package/development/04-antipatterns/security-antipatterns.md +747 -0
- package/development/05-cases/case-api-version-migration.md +428 -0
- package/development/05-cases/case-authorization-hardening.md +383 -0
- package/development/05-cases/case-bluegreen-rollback.md +466 -0
- package/development/05-cases/case-cache-snowball-protection.md +485 -0
- package/development/05-cases/case-ci-cd-pipeline.md +544 -0
- package/development/05-cases/case-database-scaling.md +500 -0
- package/development/05-cases/case-db-hotspot-optimization.md +487 -0
- package/development/05-cases/case-incident-mttr-reduction.md +563 -0
- package/development/05-cases/case-microservice-migration.md +375 -0
- package/development/05-cases/case-performance-optimization.md +406 -0
- package/development/05-cases/case-security-incident-response.md +345 -0
- package/development/06-glossary/full-stack-glossary.md +166 -0
- package/development/09-maturity/quarterly-audit-template.md +35 -0
- package/development/11-ui-excellence/ui-aesthetic-system.md +41 -0
- package/development/11-ui-excellence/ui-engineering-excellence.md +435 -0
- package/development/12-scenarios/development-scenarios-guide.md +565 -0
- package/development/13-implementation-assets/implementation-toolkit.md +282 -0
- package/development/13-implementation-assets/knowledge-gates-execution.md +43 -0
- package/development/14-full-lifecycle/software-lifecycle-gates.md +511 -0
- package/development/15-lifecycle-templates/project-templates-collection.md +791 -0
- package/development/api-contract-and-versioning-guide.md +36 -0
- package/development/api-governance-complete.md +43 -0
- package/development/backend-engineering-complete.md +43 -0
- package/development/code-review-quality-complete.md +43 -0
- package/development/concurrency-reliability-complete.md +43 -0
- package/development/database-engineering-complete.md +43 -0
- package/development/engineering-effectiveness-complete.md +43 -0
- package/development/engineering-standards-deep-dive.md +38 -0
- package/development/frontend-engineering-complete.md +43 -0
- package/development/performance-capacity-complete.md +43 -0
- package/development/refactor-migration-complete.md +42 -0
- package/development/refactoring-and-techdebt-playbook.md +37 -0
- package/development/security-in-development-complete.md +43 -0
- package/devops/01-standards/cicd-pipeline-complete.md +262 -0
- package/devops/01-standards/docker-complete.md +1490 -0
- package/devops/01-standards/github-actions-complete.md +337 -0
- package/devops/01-standards/kubernetes-complete.md +638 -0
- package/devops/01-standards/terraform-complete.md +2117 -0
- package/devops/02-playbooks/docker-compose-playbook.md +233 -0
- package/devops/02-playbooks/docker-k8s-production-playbook.md +186 -0
- package/devops/02-playbooks/docker-production-playbook.md +952 -0
- package/edge-iot/01-standards/edge-iot-complete.md +473 -0
- package/experts/architect/api-design.md +178 -0
- package/experts/architect/methodology.md +124 -0
- package/experts/architect/security.md +75 -0
- package/experts/backend-lead/methodology.md +216 -0
- package/experts/devops/methodology.md +160 -0
- package/experts/frontend-lead/methodology.md +178 -0
- package/experts/product-manager/industry/ecommerce.md +43 -0
- package/experts/product-manager/industry/saas.md +40 -0
- package/experts/product-manager/methodology.md +97 -0
- package/experts/qa-lead/methodology.md +123 -0
- package/experts/qa-lead/test-strategy.md +128 -0
- package/experts/uiux-designer/methodology.md +125 -0
- package/frontend/01-standards/accessibility-complete.md +532 -0
- package/frontend/01-standards/accessibility-standard.md +74 -0
- package/frontend/01-standards/admin-dashboard-and-crud.md +72 -0
- package/frontend/01-standards/design-tokens-complete.md +444 -0
- package/frontend/01-standards/forms-and-validation.md +77 -0
- package/frontend/01-standards/frontend-architecture-and-layering.md +119 -0
- package/frontend/01-standards/i18n-and-localization.md +65 -0
- package/frontend/01-standards/nextjs-complete.md +451 -0
- package/frontend/01-standards/react-complete.md +713 -0
- package/frontend/01-standards/react-hooks-complete-guide.md +1100 -0
- package/frontend/01-standards/react-hooks-complete.md +1171 -0
- package/frontend/01-standards/seo-and-web-vitals.md +77 -0
- package/frontend/01-standards/state-management-complete.md +444 -0
- package/frontend/01-standards/vue-complete.md +499 -0
- package/frontend/01-standards/vue3-complete.md +2002 -0
- package/frontend/01-standards/web-framework-best-practices.md +64 -0
- package/frontend/01-standards/web-performance-complete.md +495 -0
- package/frontend/02-playbooks/accessibility-a11y-playbook.md +161 -0
- package/frontend/02-playbooks/frontend-performance-playbook.md +707 -0
- package/frontend/02-playbooks/i18n-internationalization-playbook.md +120 -0
- package/frontend/02-playbooks/performance-optimization-playbook.md +163 -0
- package/frontend/02-playbooks/react-nextjs-production-playbook.md +167 -0
- package/frontend/02-playbooks/react-state-management-playbook.md +173 -0
- package/frontend/03-checklists/component-quality-checklist.md +166 -0
- package/frontend/03-checklists/frontend-launch-checklist.md +299 -0
- package/frontend/04-antipatterns/frontend-antipatterns.md +886 -0
- package/frontend/05-cases/case-performance-optimization.md +274 -0
- package/harmony/01-standards/harmonyos-arkts-standard.md +75 -0
- package/harmony/01-standards/harmonyos-design.md +65 -0
- package/high-quality-engineering-playbook.md +54 -0
- package/incident/01-standards/incident-response-complete.md +303 -0
- package/incident/02-playbooks/chaos-engineering-playbook.md +883 -0
- package/incident/02-playbooks/postmortem-playbook.md +398 -0
- package/incident/03-checklists/incident-readiness-checklist.md +181 -0
- package/incident/04-antipatterns/incident-antipatterns.md +490 -0
- package/incident/05-cases/case-cascade-failure.md +176 -0
- package/incident/06-glossary/incident-glossary.md +114 -0
- package/incident/postmortem-and-response-deep-dive.md +39 -0
- package/industries/ecommerce/ecommerce-complete.md +631 -0
- package/industries/education/education-complete.md +555 -0
- package/industries/fintech/fintech-complete.md +501 -0
- package/industries/gaming/gaming-complete.md +587 -0
- package/industries/healthcare/healthcare-complete.md +452 -0
- package/low-code/01-standards/low-code-complete.md +944 -0
- package/miniprogram/01-standards/ai-common-mistakes.md +61 -0
- package/miniprogram/01-standards/miniprogram-custom-navbar-capsule.md +77 -0
- package/miniprogram/01-standards/miniprogram-design.md +61 -0
- package/miniprogram/01-standards/miniprogram-standard.md +81 -0
- package/mobile/01-standards/android-material-design.md +70 -0
- package/mobile/01-standards/flutter-complete.md +384 -0
- package/mobile/01-standards/ios-design-hig.md +78 -0
- package/mobile/01-standards/mobile-app-standard.md +85 -0
- package/mobile/01-standards/react-native-complete.md +352 -0
- package/mobile/02-playbooks/mobile-cross-platform-playbook.md +175 -0
- package/mobile/02-playbooks/mobile-performance.md +473 -0
- package/mobile/03-checklists/mobile-release-checklist.md +234 -0
- package/mobile/04-antipatterns/mobile-antipatterns.md +798 -0
- package/mobile/05-cases/case-app-performance.md +500 -0
- package/mobile/05-cases/case-app-startup-optimization.md +218 -0
- package/mobile/06-glossary/mobile-glossary.md +484 -0
- package/observability/01-standards/observability-standards.md +103 -0
- package/observability/02-playbooks/prometheus-grafana-playbook.md +135 -0
- package/observability/02-playbooks/structured-logging-playbook.md +73 -0
- package/observability/03-checklists/observability-checklist.md +54 -0
- package/observability/04-antipatterns/observability-antipatterns.md +106 -0
- package/operations/01-standards/prometheus-monitoring-complete.md +1578 -0
- package/operations/02-playbooks/capacity-planning-playbook.md +620 -0
- package/operations/03-checklists/production-launch-checklist.md +365 -0
- package/operations/04-antipatterns/operations-antipatterns.md +664 -0
- package/operations/05-cases/case-sre-practices.md +581 -0
- package/operations/06-glossary/operations-glossary.md +120 -0
- package/operations/aiops-anomaly-detection.md +758 -0
- package/operations/capacity-planning.md +1061 -0
- package/operations/chaos-engineering.md +659 -0
- package/operations/incident-command-system.md +38 -0
- package/operations/observability-complete.md +442 -0
- package/operations/slo-sli-playbook.md +517 -0
- package/operations/sre-operations-deep-dive.md +39 -0
- package/package.json +8 -0
- package/performance/01-standards/performance-and-scalability.md +80 -0
- package/performance/01-standards/performance-standards.md +156 -0
- package/performance/02-playbooks/query-optimization-playbook.md +103 -0
- package/performance/03-checklists/performance-checklist.md +56 -0
- package/performance/04-antipatterns/performance-antipatterns.md +146 -0
- package/product/01-standards/product-management-complete.md +285 -0
- package/product/02-playbooks/feature-launch-playbook.md +207 -0
- package/product/02-playbooks/user-research-playbook.md +532 -0
- package/product/03-checklists/feature-launch-checklist.md +275 -0
- package/product/04-antipatterns/product-antipatterns.md +355 -0
- package/product/05-cases/case-mvp-to-scale.md +384 -0
- package/product/06-glossary/product-glossary.md +462 -0
- package/product/feature-prioritization-framework.md +40 -0
- package/product/kpi-and-metric-tree.md +37 -0
- package/product/product-discovery-and-prd-deep-dive.md +41 -0
- package/quantum/01-standards/quantum-complete.md +1186 -0
- package/security/01-standards/api-security-complete.md +511 -0
- package/security/01-standards/container-runtime-security.md +574 -0
- package/security/01-standards/data-protection-gdpr.md +543 -0
- package/security/01-standards/owasp-top10-complete.md +1890 -0
- package/security/01-standards/secure-coding-baseline.md +90 -0
- package/security/01-standards/supply-chain-security.md +441 -0
- package/security/01-standards/web-security-checklist.md +108 -0
- package/security/01-standards/zero-trust-architecture.md +521 -0
- package/security/02-playbooks/auth-sso-playbook.md +166 -0
- package/security/02-playbooks/incident-response-security-playbook.md +588 -0
- package/security/02-playbooks/owasp-api-security-playbook.md +129 -0
- package/security/02-playbooks/payment-integration-playbook.md +119 -0
- package/security/02-playbooks/penetration-testing-playbook.md +517 -0
- package/security/03-checklists/security-audit-checklist.md +356 -0
- package/security/04-antipatterns/security-coding-antipatterns.md +580 -0
- package/security/05-cases/case-log4shell-incident.md +537 -0
- package/security/05-cases/case-major-breaches.md +468 -0
- package/security/06-glossary/security-glossary.md +212 -0
- package/security/compliance-automation.md +993 -0
- package/security/container-security.md +680 -0
- package/security/devsecops-complete.md +426 -0
- package/security/sast-dast-sca.md +775 -0
- package/security/secrets-management.md +594 -0
- package/security/security-architecture-deep-dive.md +37 -0
- package/security/threat-modeling-stride-playbook.md +40 -0
- package/seed-templates/auth-system.md +59 -0
- package/seed-templates/blog-content.md +94 -0
- package/seed-templates/dashboard.md +89 -0
- package/seed-templates/docs-site.md +73 -0
- package/seed-templates/e-commerce.md +50 -0
- package/seed-templates/saas-landing.md +92 -0
- package/seed-templates/settings-page.md +51 -0
- package/testing/01-standards/test-strategy-and-layering.md +83 -0
- package/testing/01-standards/testing-strategy-complete.md +422 -0
- package/testing/01-standards/unit-testing-best-practices.md +118 -0
- package/testing/02-playbooks/e2e-testing-playbook.md +988 -0
- package/testing/02-playbooks/testing-strategy-playbook.md +126 -0
- package/testing/03-checklists/test-strategy-checklist.md +208 -0
- package/testing/04-antipatterns/testing-antipatterns.md +718 -0
- package/testing/05-cases/case-testing-transformation.md +300 -0
- package/testing/06-glossary/testing-glossary.md +110 -0
- package/testing/risk-based-test-matrix.md +36 -0
- package/testing/testing-strategy-deep-dive.md +37 -0
|
@@ -0,0 +1,2002 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: vue3-complete
|
|
3
|
+
title: Vue 3 完整知识体系
|
|
4
|
+
domain: frontend
|
|
5
|
+
category: 01-standards
|
|
6
|
+
difficulty: intermediate
|
|
7
|
+
tags: [complete, frontend, pinia, router, vue3, 响应式系统原理, 性能优化, 概述]
|
|
8
|
+
quality_score: 70
|
|
9
|
+
last_updated: 2026-06-15
|
|
10
|
+
---
|
|
11
|
+
# Vue 3 完整知识体系
|
|
12
|
+
|
|
13
|
+
## 概述
|
|
14
|
+
|
|
15
|
+
Vue 3 是 Vue.js 的最新主版本,于 2022 年正式成为默认版本。它带来了组合式 API(Composition API)、基于 Proxy 的响应式系统、更好的 TypeScript 支持、Teleport/Suspense 等新内置组件,以及显著的性能提升。Vue 3 的设计目标是更小的包体积、更快的渲染速度和更好的可维护性。
|
|
16
|
+
|
|
17
|
+
### Vue 3 vs Vue 2 vs React
|
|
18
|
+
|
|
19
|
+
| 特性 | Vue 3 | Vue 2 | React 18 |
|
|
20
|
+
|------|-------|-------|----------|
|
|
21
|
+
| 响应式机制 | Proxy | Object.defineProperty | 不可变状态 + 调度器 |
|
|
22
|
+
| API 风格 | 组合式 API + 选项式 | 选项式 API | Hooks |
|
|
23
|
+
| 虚拟DOM | 编译时优化的 VDOM | 传统 VDOM | Fiber 架构 |
|
|
24
|
+
| TypeScript | 原生支持 | 需要额外配置 | 原生支持 |
|
|
25
|
+
| 包大小(min+gzip) | ~16KB | ~23KB | ~42KB(含ReactDOM) |
|
|
26
|
+
| 模板编译 | 编译时优化、静态提升 | 运行时编译 | JSX 转换 |
|
|
27
|
+
| 状态管理 | Pinia(官方) | Vuex | Redux/Zustand/Jotai |
|
|
28
|
+
| 路由 | Vue Router 4 | Vue Router 3 | React Router 6 |
|
|
29
|
+
| SSR/SSG | Nuxt 3 | Nuxt 2 | Next.js |
|
|
30
|
+
| 学习曲线 | 低-中 | 低 | 中 |
|
|
31
|
+
| 并发模式 | 无 | 无 | Concurrent Features |
|
|
32
|
+
| Fragment | 支持 | 不支持(需单根节点) | 支持 |
|
|
33
|
+
|
|
34
|
+
### 核心升级亮点
|
|
35
|
+
|
|
36
|
+
- **组合式 API**: 逻辑复用和代码组织的终极方案,替代 mixins
|
|
37
|
+
- **`<script setup>`**: 编译时语法糖,减少样板代码
|
|
38
|
+
- **Proxy 响应式**: 检测属性新增/删除、数组索引变化,不再需要 `Vue.set`
|
|
39
|
+
- **Tree-shaking 友好**: 按需引入,未使用的功能不会打包
|
|
40
|
+
- **多根节点组件(Fragment)**: 模板不再限制为单一根元素
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 组合式 API
|
|
45
|
+
|
|
46
|
+
### setup 与 `<script setup>`
|
|
47
|
+
|
|
48
|
+
`<script setup>` 是组合式 API 在单文件组件(SFC)中的编译时语法糖,是 Vue 3 推荐的写法:
|
|
49
|
+
|
|
50
|
+
```vue
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { ref, reactive, computed, watch, watchEffect, onMounted } from 'vue'
|
|
53
|
+
|
|
54
|
+
// 所有顶层绑定自动暴露给模板
|
|
55
|
+
const title = ref('Vue 3 知识体系')
|
|
56
|
+
const count = ref(0)
|
|
57
|
+
|
|
58
|
+
function increment() {
|
|
59
|
+
count.value++
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onMounted(() => {
|
|
63
|
+
console.log('组件已挂载')
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<h1>{{ title }}</h1>
|
|
69
|
+
<p>计数: {{ count }}</p>
|
|
70
|
+
<button @click="increment">+1</button>
|
|
71
|
+
</template>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
对比传统 `setup()` 函数写法:
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<script lang="ts">
|
|
78
|
+
import { defineComponent, ref } from 'vue'
|
|
79
|
+
|
|
80
|
+
export default defineComponent({
|
|
81
|
+
setup() {
|
|
82
|
+
const count = ref(0)
|
|
83
|
+
const increment = () => { count.value++ }
|
|
84
|
+
|
|
85
|
+
// 必须显式返回要暴露的内容
|
|
86
|
+
return { count, increment }
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
</script>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### ref 与 reactive
|
|
93
|
+
|
|
94
|
+
```vue
|
|
95
|
+
<script setup lang="ts">
|
|
96
|
+
import { ref, reactive, toRefs, toRef, isRef, unref } from 'vue'
|
|
97
|
+
|
|
98
|
+
// ref: 包装基本类型或对象,通过 .value 访问
|
|
99
|
+
const count = ref(0)
|
|
100
|
+
const message = ref('hello')
|
|
101
|
+
const user = ref({ name: 'Alice', age: 30 }) // 对象也可以用 ref
|
|
102
|
+
|
|
103
|
+
// reactive: 包装对象/数组,直接访问属性
|
|
104
|
+
const state = reactive({
|
|
105
|
+
items: [] as string[],
|
|
106
|
+
loading: false,
|
|
107
|
+
error: null as string | null
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// 修改 ref
|
|
111
|
+
count.value++
|
|
112
|
+
message.value = 'world'
|
|
113
|
+
|
|
114
|
+
// 修改 reactive(直接操作属性)
|
|
115
|
+
state.loading = true
|
|
116
|
+
state.items.push('new item')
|
|
117
|
+
|
|
118
|
+
// toRefs: 将 reactive 对象的每个属性转为 ref,保持响应式
|
|
119
|
+
const { items, loading } = toRefs(state)
|
|
120
|
+
// loading.value === state.loading
|
|
121
|
+
|
|
122
|
+
// toRef: 转换单个属性
|
|
123
|
+
const errorRef = toRef(state, 'error')
|
|
124
|
+
|
|
125
|
+
// 工具函数
|
|
126
|
+
console.log(isRef(count)) // true
|
|
127
|
+
console.log(unref(count)) // 0 (自动解包)
|
|
128
|
+
</script>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**ref vs reactive 选择指南**:
|
|
132
|
+
- 基本类型(string/number/boolean)只能用 `ref`
|
|
133
|
+
- 表单状态、复杂对象优先用 `reactive`
|
|
134
|
+
- 需要替换整个对象时用 `ref`(`reactive` 不能替换引用)
|
|
135
|
+
- composable 函数返回值推荐用 `ref`(解构不丢失响应式)
|
|
136
|
+
|
|
137
|
+
### computed
|
|
138
|
+
|
|
139
|
+
```vue
|
|
140
|
+
<script setup lang="ts">
|
|
141
|
+
import { ref, computed } from 'vue'
|
|
142
|
+
|
|
143
|
+
const firstName = ref('张')
|
|
144
|
+
const lastName = ref('三')
|
|
145
|
+
|
|
146
|
+
// 只读计算属性
|
|
147
|
+
const fullName = computed(() => `${firstName.value}${lastName.value}`)
|
|
148
|
+
|
|
149
|
+
// 可写计算属性
|
|
150
|
+
const fullNameWritable = computed({
|
|
151
|
+
get: () => `${firstName.value}${lastName.value}`,
|
|
152
|
+
set: (val: string) => {
|
|
153
|
+
firstName.value = val[0] || ''
|
|
154
|
+
lastName.value = val.slice(1) || ''
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// 带调试的计算属性
|
|
159
|
+
const expensiveComputed = computed(() => {
|
|
160
|
+
// 复杂计算
|
|
161
|
+
return someHeavyCalculation()
|
|
162
|
+
}, {
|
|
163
|
+
onTrack(e) { console.log('依赖被追踪', e) },
|
|
164
|
+
onTrigger(e) { console.log('依赖变化触发重算', e) }
|
|
165
|
+
})
|
|
166
|
+
</script>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### watch 与 watchEffect
|
|
170
|
+
|
|
171
|
+
```vue
|
|
172
|
+
<script setup lang="ts">
|
|
173
|
+
import { ref, reactive, watch, watchEffect, watchPostEffect } from 'vue'
|
|
174
|
+
|
|
175
|
+
const keyword = ref('')
|
|
176
|
+
const page = ref(1)
|
|
177
|
+
const state = reactive({ filters: { category: 'all', sort: 'date' } })
|
|
178
|
+
|
|
179
|
+
// 监听单个 ref
|
|
180
|
+
watch(keyword, (newVal, oldVal) => {
|
|
181
|
+
console.log(`关键词从 "${oldVal}" 变为 "${newVal}"`)
|
|
182
|
+
fetchResults(newVal)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// 监听多个源
|
|
186
|
+
watch([keyword, page], ([newKeyword, newPage], [oldKeyword, oldPage]) => {
|
|
187
|
+
fetchResults(newKeyword, newPage)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// 监听 reactive 对象的属性(需要用 getter)
|
|
191
|
+
watch(
|
|
192
|
+
() => state.filters.category,
|
|
193
|
+
(newCategory) => { console.log('分类变化:', newCategory) }
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
// 深度监听
|
|
197
|
+
watch(
|
|
198
|
+
() => state.filters,
|
|
199
|
+
(newFilters) => { console.log('筛选项变化:', newFilters) },
|
|
200
|
+
{ deep: true }
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
// 立即执行
|
|
204
|
+
watch(keyword, (val) => { fetchResults(val) }, { immediate: true })
|
|
205
|
+
|
|
206
|
+
// watchEffect: 自动收集依赖,立即执行
|
|
207
|
+
const stop = watchEffect((onCleanup) => {
|
|
208
|
+
const controller = new AbortController()
|
|
209
|
+
fetchData(keyword.value, { signal: controller.signal })
|
|
210
|
+
|
|
211
|
+
onCleanup(() => {
|
|
212
|
+
controller.abort() // 清理副作用
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// 停止监听
|
|
217
|
+
stop()
|
|
218
|
+
|
|
219
|
+
// watchPostEffect: DOM 更新后执行
|
|
220
|
+
watchPostEffect(() => {
|
|
221
|
+
// 此时可以安全访问更新后的 DOM
|
|
222
|
+
console.log(document.querySelector('#result')?.textContent)
|
|
223
|
+
})
|
|
224
|
+
</script>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 生命周期钩子
|
|
228
|
+
|
|
229
|
+
```vue
|
|
230
|
+
<script setup lang="ts">
|
|
231
|
+
import {
|
|
232
|
+
onBeforeMount,
|
|
233
|
+
onMounted,
|
|
234
|
+
onBeforeUpdate,
|
|
235
|
+
onUpdated,
|
|
236
|
+
onBeforeUnmount,
|
|
237
|
+
onUnmounted,
|
|
238
|
+
onActivated,
|
|
239
|
+
onDeactivated,
|
|
240
|
+
onErrorCaptured
|
|
241
|
+
} from 'vue'
|
|
242
|
+
|
|
243
|
+
onBeforeMount(() => { console.log('挂载前') })
|
|
244
|
+
onMounted(() => { console.log('挂载完成,DOM 可用') })
|
|
245
|
+
onBeforeUpdate(() => { console.log('更新前') })
|
|
246
|
+
onUpdated(() => { console.log('更新完成') })
|
|
247
|
+
onBeforeUnmount(() => { console.log('卸载前,清理定时器/事件') })
|
|
248
|
+
onUnmounted(() => { console.log('卸载完成') })
|
|
249
|
+
|
|
250
|
+
// KeepAlive 组件激活/停用
|
|
251
|
+
onActivated(() => { console.log('被 KeepAlive 激活') })
|
|
252
|
+
onDeactivated(() => { console.log('被 KeepAlive 停用') })
|
|
253
|
+
|
|
254
|
+
// 错误捕获
|
|
255
|
+
onErrorCaptured((err, instance, info) => {
|
|
256
|
+
console.error('子组件错误:', err, info)
|
|
257
|
+
return false // 阻止错误继续向上传播
|
|
258
|
+
})
|
|
259
|
+
</script>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 组合式函数(Composables)
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// composables/useFetch.ts
|
|
266
|
+
import { ref, watchEffect, type Ref } from 'vue'
|
|
267
|
+
|
|
268
|
+
interface UseFetchReturn<T> {
|
|
269
|
+
data: Ref<T | null>
|
|
270
|
+
error: Ref<string | null>
|
|
271
|
+
loading: Ref<boolean>
|
|
272
|
+
refetch: () => Promise<void>
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function useFetch<T = any>(url: Ref<string> | string): UseFetchReturn<T> {
|
|
276
|
+
const data = ref<T | null>(null) as Ref<T | null>
|
|
277
|
+
const error = ref<string | null>(null)
|
|
278
|
+
const loading = ref(false)
|
|
279
|
+
|
|
280
|
+
async function fetchData() {
|
|
281
|
+
loading.value = true
|
|
282
|
+
error.value = null
|
|
283
|
+
try {
|
|
284
|
+
const response = await fetch(typeof url === 'string' ? url : url.value)
|
|
285
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
286
|
+
data.value = await response.json()
|
|
287
|
+
} catch (e) {
|
|
288
|
+
error.value = (e as Error).message
|
|
289
|
+
} finally {
|
|
290
|
+
loading.value = false
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
watchEffect(() => {
|
|
295
|
+
fetchData()
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
return { data, error, loading, refetch: fetchData }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// composables/useDebounce.ts
|
|
302
|
+
import { ref, watch, type Ref } from 'vue'
|
|
303
|
+
|
|
304
|
+
export function useDebounce<T>(source: Ref<T>, delay = 300): Ref<T> {
|
|
305
|
+
const debounced = ref(source.value) as Ref<T>
|
|
306
|
+
let timer: ReturnType<typeof setTimeout>
|
|
307
|
+
|
|
308
|
+
watch(source, (val) => {
|
|
309
|
+
clearTimeout(timer)
|
|
310
|
+
timer = setTimeout(() => { debounced.value = val }, delay)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
return debounced
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// composables/useLocalStorage.ts
|
|
317
|
+
import { ref, watch, type Ref } from 'vue'
|
|
318
|
+
|
|
319
|
+
export function useLocalStorage<T>(key: string, defaultValue: T): Ref<T> {
|
|
320
|
+
const stored = localStorage.getItem(key)
|
|
321
|
+
const data = ref<T>(stored ? JSON.parse(stored) : defaultValue) as Ref<T>
|
|
322
|
+
|
|
323
|
+
watch(data, (val) => {
|
|
324
|
+
localStorage.setItem(key, JSON.stringify(val))
|
|
325
|
+
}, { deep: true })
|
|
326
|
+
|
|
327
|
+
return data
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
使用组合式函数:
|
|
332
|
+
|
|
333
|
+
```vue
|
|
334
|
+
<script setup lang="ts">
|
|
335
|
+
import { ref, computed } from 'vue'
|
|
336
|
+
import { useFetch } from '@/composables/useFetch'
|
|
337
|
+
import { useDebounce } from '@/composables/useDebounce'
|
|
338
|
+
|
|
339
|
+
const keyword = ref('')
|
|
340
|
+
const debouncedKeyword = useDebounce(keyword, 500)
|
|
341
|
+
const apiUrl = computed(() => `/api/search?q=${debouncedKeyword.value}`)
|
|
342
|
+
const { data, loading, error } = useFetch(apiUrl)
|
|
343
|
+
</script>
|
|
344
|
+
|
|
345
|
+
<template>
|
|
346
|
+
<input v-model="keyword" placeholder="搜索..." />
|
|
347
|
+
<div v-if="loading">加载中...</div>
|
|
348
|
+
<div v-else-if="error">错误: {{ error }}</div>
|
|
349
|
+
<ul v-else>
|
|
350
|
+
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
|
|
351
|
+
</ul>
|
|
352
|
+
</template>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## 响应式系统原理
|
|
358
|
+
|
|
359
|
+
### Proxy 代理机制
|
|
360
|
+
|
|
361
|
+
Vue 3 的响应式系统基于 ES6 Proxy,替代了 Vue 2 的 `Object.defineProperty`:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// 简化的响应式实现原理
|
|
365
|
+
function reactive<T extends object>(target: T): T {
|
|
366
|
+
const handler: ProxyHandler<T> = {
|
|
367
|
+
get(target, key, receiver) {
|
|
368
|
+
const result = Reflect.get(target, key, receiver)
|
|
369
|
+
track(target, key) // 依赖收集
|
|
370
|
+
// 深层响应式:访问嵌套对象时递归代理
|
|
371
|
+
if (typeof result === 'object' && result !== null) {
|
|
372
|
+
return reactive(result)
|
|
373
|
+
}
|
|
374
|
+
return result
|
|
375
|
+
},
|
|
376
|
+
set(target, key, value, receiver) {
|
|
377
|
+
const oldValue = Reflect.get(target, key, receiver)
|
|
378
|
+
const result = Reflect.set(target, key, value, receiver)
|
|
379
|
+
if (oldValue !== value) {
|
|
380
|
+
trigger(target, key) // 触发更新
|
|
381
|
+
}
|
|
382
|
+
return result
|
|
383
|
+
},
|
|
384
|
+
deleteProperty(target, key) {
|
|
385
|
+
const result = Reflect.deleteProperty(target, key)
|
|
386
|
+
trigger(target, key) // 删除属性也能检测
|
|
387
|
+
return result
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return new Proxy(target, handler)
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### 依赖收集与触发更新
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// 简化的依赖收集系统
|
|
398
|
+
type Dep = Set<ReactiveEffect>
|
|
399
|
+
type KeyToDepMap = Map<string | symbol, Dep>
|
|
400
|
+
const targetMap = new WeakMap<object, KeyToDepMap>()
|
|
401
|
+
|
|
402
|
+
let activeEffect: ReactiveEffect | null = null
|
|
403
|
+
|
|
404
|
+
function track(target: object, key: string | symbol) {
|
|
405
|
+
if (!activeEffect) return
|
|
406
|
+
let depsMap = targetMap.get(target)
|
|
407
|
+
if (!depsMap) {
|
|
408
|
+
targetMap.set(target, (depsMap = new Map()))
|
|
409
|
+
}
|
|
410
|
+
let dep = depsMap.get(key)
|
|
411
|
+
if (!dep) {
|
|
412
|
+
depsMap.set(key, (dep = new Set()))
|
|
413
|
+
}
|
|
414
|
+
dep.add(activeEffect) // 将当前 effect 加入依赖集合
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function trigger(target: object, key: string | symbol) {
|
|
418
|
+
const depsMap = targetMap.get(target)
|
|
419
|
+
if (!depsMap) return
|
|
420
|
+
const dep = depsMap.get(key)
|
|
421
|
+
if (dep) {
|
|
422
|
+
dep.forEach(effect => {
|
|
423
|
+
// 调度器:异步批量更新
|
|
424
|
+
if (effect.scheduler) {
|
|
425
|
+
effect.scheduler()
|
|
426
|
+
} else {
|
|
427
|
+
effect.run()
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### ref 的实现原理
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// ref 本质是带 value 属性的对象
|
|
438
|
+
class RefImpl<T> {
|
|
439
|
+
private _value: T
|
|
440
|
+
private _rawValue: T
|
|
441
|
+
public dep: Set<ReactiveEffect> = new Set()
|
|
442
|
+
public readonly __v_isRef = true
|
|
443
|
+
|
|
444
|
+
constructor(value: T) {
|
|
445
|
+
this._rawValue = value
|
|
446
|
+
this._value = isObject(value) ? reactive(value) : value
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
get value() {
|
|
450
|
+
trackRefValue(this) // 收集依赖
|
|
451
|
+
return this._value
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
set value(newVal: T) {
|
|
455
|
+
if (hasChanged(newVal, this._rawValue)) {
|
|
456
|
+
this._rawValue = newVal
|
|
457
|
+
this._value = isObject(newVal) ? reactive(newVal) : newVal
|
|
458
|
+
triggerRefValue(this) // 触发更新
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 组件设计
|
|
467
|
+
|
|
468
|
+
### Props 定义与验证
|
|
469
|
+
|
|
470
|
+
```vue
|
|
471
|
+
<script setup lang="ts">
|
|
472
|
+
// 类型声明方式(推荐)
|
|
473
|
+
interface Props {
|
|
474
|
+
title: string
|
|
475
|
+
count?: number
|
|
476
|
+
items: string[]
|
|
477
|
+
status: 'active' | 'inactive' | 'pending'
|
|
478
|
+
callback?: (id: number) => void
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
482
|
+
count: 0,
|
|
483
|
+
status: 'active'
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
// 使用 props
|
|
487
|
+
console.log(props.title, props.count)
|
|
488
|
+
</script>
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
运行时验证写法(适用于需要自定义验证器的场景):
|
|
492
|
+
|
|
493
|
+
```vue
|
|
494
|
+
<script setup>
|
|
495
|
+
const props = defineProps({
|
|
496
|
+
age: {
|
|
497
|
+
type: Number,
|
|
498
|
+
required: true,
|
|
499
|
+
validator: (value) => value >= 0 && value <= 150
|
|
500
|
+
},
|
|
501
|
+
email: {
|
|
502
|
+
type: String,
|
|
503
|
+
default: '',
|
|
504
|
+
validator: (value) => !value || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
|
505
|
+
}
|
|
506
|
+
})
|
|
507
|
+
</script>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Emits 事件声明
|
|
511
|
+
|
|
512
|
+
```vue
|
|
513
|
+
<script setup lang="ts">
|
|
514
|
+
// 类型声明方式
|
|
515
|
+
const emit = defineEmits<{
|
|
516
|
+
(e: 'update', id: number, value: string): void
|
|
517
|
+
(e: 'delete', id: number): void
|
|
518
|
+
(e: 'submit'): void
|
|
519
|
+
}>()
|
|
520
|
+
|
|
521
|
+
// Vue 3.3+ 简写语法
|
|
522
|
+
const emit = defineEmits<{
|
|
523
|
+
update: [id: number, value: string]
|
|
524
|
+
delete: [id: number]
|
|
525
|
+
submit: []
|
|
526
|
+
}>()
|
|
527
|
+
|
|
528
|
+
function handleSave(id: number) {
|
|
529
|
+
emit('update', id, 'new value')
|
|
530
|
+
}
|
|
531
|
+
</script>
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Slots 插槽
|
|
535
|
+
|
|
536
|
+
```vue
|
|
537
|
+
<!-- BaseCard.vue -->
|
|
538
|
+
<template>
|
|
539
|
+
<div class="card">
|
|
540
|
+
<!-- 默认插槽 -->
|
|
541
|
+
<div class="card-body">
|
|
542
|
+
<slot />
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<!-- 具名插槽 -->
|
|
546
|
+
<div class="card-header" v-if="$slots.header">
|
|
547
|
+
<slot name="header" />
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<!-- 作用域插槽:向父组件传递数据 -->
|
|
551
|
+
<div class="card-list">
|
|
552
|
+
<slot name="item" v-for="item in items" :key="item.id"
|
|
553
|
+
:item="item" :index="items.indexOf(item)" />
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
</template>
|
|
557
|
+
|
|
558
|
+
<script setup lang="ts">
|
|
559
|
+
interface Item { id: number; name: string }
|
|
560
|
+
defineProps<{ items: Item[] }>()
|
|
561
|
+
</script>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
使用插槽:
|
|
565
|
+
|
|
566
|
+
```vue
|
|
567
|
+
<template>
|
|
568
|
+
<BaseCard :items="products">
|
|
569
|
+
<template #header>
|
|
570
|
+
<h2>产品列表</h2>
|
|
571
|
+
</template>
|
|
572
|
+
|
|
573
|
+
<template #item="{ item, index }">
|
|
574
|
+
<div class="product-row">
|
|
575
|
+
{{ index + 1 }}. {{ item.name }}
|
|
576
|
+
</div>
|
|
577
|
+
</template>
|
|
578
|
+
|
|
579
|
+
<!-- 默认插槽 -->
|
|
580
|
+
<p>底部描述信息</p>
|
|
581
|
+
</BaseCard>
|
|
582
|
+
</template>
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Provide / Inject 依赖注入
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// types/injection-keys.ts
|
|
589
|
+
import type { InjectionKey, Ref } from 'vue'
|
|
590
|
+
|
|
591
|
+
export interface UserContext {
|
|
592
|
+
user: Ref<{ name: string; role: string } | null>
|
|
593
|
+
login: (name: string) => void
|
|
594
|
+
logout: () => void
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export const UserKey: InjectionKey<UserContext> = Symbol('user')
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
```vue
|
|
601
|
+
<!-- 祖先组件 provide -->
|
|
602
|
+
<script setup lang="ts">
|
|
603
|
+
import { ref, provide } from 'vue'
|
|
604
|
+
import { UserKey, type UserContext } from '@/types/injection-keys'
|
|
605
|
+
|
|
606
|
+
const user = ref<{ name: string; role: string } | null>(null)
|
|
607
|
+
|
|
608
|
+
const userContext: UserContext = {
|
|
609
|
+
user,
|
|
610
|
+
login: (name: string) => { user.value = { name, role: 'user' } },
|
|
611
|
+
logout: () => { user.value = null }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
provide(UserKey, userContext)
|
|
615
|
+
</script>
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
```vue
|
|
619
|
+
<!-- 后代组件 inject -->
|
|
620
|
+
<script setup lang="ts">
|
|
621
|
+
import { inject } from 'vue'
|
|
622
|
+
import { UserKey } from '@/types/injection-keys'
|
|
623
|
+
|
|
624
|
+
const userCtx = inject(UserKey)
|
|
625
|
+
if (!userCtx) throw new Error('UserContext 未提供')
|
|
626
|
+
|
|
627
|
+
const { user, login, logout } = userCtx
|
|
628
|
+
</script>
|
|
629
|
+
|
|
630
|
+
<template>
|
|
631
|
+
<div v-if="user">
|
|
632
|
+
欢迎, {{ user.name }}
|
|
633
|
+
<button @click="logout">退出</button>
|
|
634
|
+
</div>
|
|
635
|
+
<button v-else @click="login('张三')">登录</button>
|
|
636
|
+
</template>
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Teleport
|
|
640
|
+
|
|
641
|
+
```vue
|
|
642
|
+
<template>
|
|
643
|
+
<button @click="showModal = true">打开弹窗</button>
|
|
644
|
+
|
|
645
|
+
<!-- 将内容传送到 body 下,脱离组件 DOM 层级 -->
|
|
646
|
+
<Teleport to="body">
|
|
647
|
+
<div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
|
|
648
|
+
<div class="modal-content">
|
|
649
|
+
<h2>弹窗标题</h2>
|
|
650
|
+
<p>弹窗内容,渲染在 body 下但逻辑仍属于当前组件</p>
|
|
651
|
+
<button @click="showModal = false">关闭</button>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
</Teleport>
|
|
655
|
+
</template>
|
|
656
|
+
|
|
657
|
+
<script setup lang="ts">
|
|
658
|
+
import { ref } from 'vue'
|
|
659
|
+
const showModal = ref(false)
|
|
660
|
+
</script>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Suspense(实验性)
|
|
664
|
+
|
|
665
|
+
```vue
|
|
666
|
+
<template>
|
|
667
|
+
<Suspense>
|
|
668
|
+
<!-- 默认插槽:异步组件 -->
|
|
669
|
+
<template #default>
|
|
670
|
+
<AsyncDashboard />
|
|
671
|
+
</template>
|
|
672
|
+
|
|
673
|
+
<!-- 后备插槽:加载状态 -->
|
|
674
|
+
<template #fallback>
|
|
675
|
+
<div class="loading-skeleton">
|
|
676
|
+
<div class="skeleton-header" />
|
|
677
|
+
<div class="skeleton-body" />
|
|
678
|
+
</div>
|
|
679
|
+
</template>
|
|
680
|
+
</Suspense>
|
|
681
|
+
</template>
|
|
682
|
+
|
|
683
|
+
<script setup>
|
|
684
|
+
import { defineAsyncComponent } from 'vue'
|
|
685
|
+
|
|
686
|
+
const AsyncDashboard = defineAsyncComponent(() =>
|
|
687
|
+
import('./components/Dashboard.vue')
|
|
688
|
+
)
|
|
689
|
+
</script>
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
异步 setup 组件(配合 Suspense 使用):
|
|
693
|
+
|
|
694
|
+
```vue
|
|
695
|
+
<!-- Dashboard.vue -->
|
|
696
|
+
<script setup lang="ts">
|
|
697
|
+
// 顶层 await 使组件成为异步组件
|
|
698
|
+
const response = await fetch('/api/dashboard')
|
|
699
|
+
const dashboardData = await response.json()
|
|
700
|
+
</script>
|
|
701
|
+
|
|
702
|
+
<template>
|
|
703
|
+
<div>{{ dashboardData.title }}</div>
|
|
704
|
+
</template>
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## Vue Router 4
|
|
710
|
+
|
|
711
|
+
### 基础配置
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
// router/index.ts
|
|
715
|
+
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
|
716
|
+
|
|
717
|
+
const routes: RouteRecordRaw[] = [
|
|
718
|
+
{
|
|
719
|
+
path: '/',
|
|
720
|
+
name: 'Home',
|
|
721
|
+
component: () => import('@/views/Home.vue')
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
path: '/users',
|
|
725
|
+
name: 'Users',
|
|
726
|
+
component: () => import('@/views/Users.vue'),
|
|
727
|
+
meta: { requiresAuth: true, title: '用户管理' }
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
// 动态路由参数
|
|
731
|
+
path: '/users/:id',
|
|
732
|
+
name: 'UserDetail',
|
|
733
|
+
component: () => import('@/views/UserDetail.vue'),
|
|
734
|
+
props: true // 将路由参数作为 props 传入组件
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
// 嵌套路由
|
|
738
|
+
path: '/settings',
|
|
739
|
+
component: () => import('@/layouts/SettingsLayout.vue'),
|
|
740
|
+
children: [
|
|
741
|
+
{ path: '', name: 'SettingsGeneral', component: () => import('@/views/settings/General.vue') },
|
|
742
|
+
{ path: 'profile', name: 'SettingsProfile', component: () => import('@/views/settings/Profile.vue') },
|
|
743
|
+
{ path: 'security', name: 'SettingsSecurity', component: () => import('@/views/settings/Security.vue') }
|
|
744
|
+
]
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
// 捕获所有未匹配路由
|
|
748
|
+
path: '/:pathMatch(.*)*',
|
|
749
|
+
name: 'NotFound',
|
|
750
|
+
component: () => import('@/views/NotFound.vue')
|
|
751
|
+
}
|
|
752
|
+
]
|
|
753
|
+
|
|
754
|
+
const router = createRouter({
|
|
755
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
756
|
+
routes,
|
|
757
|
+
scrollBehavior(to, from, savedPosition) {
|
|
758
|
+
if (savedPosition) return savedPosition
|
|
759
|
+
if (to.hash) return { el: to.hash, behavior: 'smooth' }
|
|
760
|
+
return { top: 0 }
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
export default router
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### 路由守卫
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
// 全局前置守卫
|
|
771
|
+
router.beforeEach(async (to, from) => {
|
|
772
|
+
const authStore = useAuthStore()
|
|
773
|
+
|
|
774
|
+
// 设置页面标题
|
|
775
|
+
document.title = (to.meta.title as string) || '默认标题'
|
|
776
|
+
|
|
777
|
+
// 认证检查
|
|
778
|
+
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
|
|
779
|
+
return { name: 'Login', query: { redirect: to.fullPath } }
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// 权限检查
|
|
783
|
+
if (to.meta.requiredRole) {
|
|
784
|
+
const hasRole = authStore.user?.roles.includes(to.meta.requiredRole as string)
|
|
785
|
+
if (!hasRole) return { name: 'Forbidden' }
|
|
786
|
+
}
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
// 全局后置钩子
|
|
790
|
+
router.afterEach((to, from) => {
|
|
791
|
+
// 发送页面访问统计
|
|
792
|
+
analytics.trackPageView(to.fullPath)
|
|
793
|
+
})
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
组件内守卫:
|
|
797
|
+
|
|
798
|
+
```vue
|
|
799
|
+
<script setup lang="ts">
|
|
800
|
+
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
|
801
|
+
|
|
802
|
+
const hasUnsavedChanges = ref(false)
|
|
803
|
+
|
|
804
|
+
// 离开路由前确认
|
|
805
|
+
onBeforeRouteLeave((to, from) => {
|
|
806
|
+
if (hasUnsavedChanges.value) {
|
|
807
|
+
const answer = window.confirm('有未保存的更改,确定离开吗?')
|
|
808
|
+
if (!answer) return false
|
|
809
|
+
}
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
// 路由参数变化时(如 /users/1 -> /users/2)
|
|
813
|
+
onBeforeRouteUpdate(async (to, from) => {
|
|
814
|
+
const userId = to.params.id as string
|
|
815
|
+
await fetchUser(userId)
|
|
816
|
+
})
|
|
817
|
+
</script>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### 动态路由
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
// 运行时添加路由(权限路由场景)
|
|
824
|
+
async function initDynamicRoutes() {
|
|
825
|
+
const authStore = useAuthStore()
|
|
826
|
+
const menus = await fetchUserMenus(authStore.user!.id)
|
|
827
|
+
|
|
828
|
+
menus.forEach(menu => {
|
|
829
|
+
router.addRoute({
|
|
830
|
+
path: menu.path,
|
|
831
|
+
name: menu.name,
|
|
832
|
+
component: () => import(`@/views/${menu.component}.vue`),
|
|
833
|
+
meta: { title: menu.title, icon: menu.icon }
|
|
834
|
+
})
|
|
835
|
+
})
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// 移除路由
|
|
839
|
+
const removeRoute = router.addRoute({ path: '/temp', component: TempView })
|
|
840
|
+
removeRoute() // 调用返回值移除
|
|
841
|
+
|
|
842
|
+
// 检查路由是否存在
|
|
843
|
+
router.hasRoute('UserDetail')
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### 路由元信息与类型扩展
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
// types/router.d.ts
|
|
850
|
+
import 'vue-router'
|
|
851
|
+
|
|
852
|
+
declare module 'vue-router' {
|
|
853
|
+
interface RouteMeta {
|
|
854
|
+
requiresAuth?: boolean
|
|
855
|
+
requiredRole?: string
|
|
856
|
+
title?: string
|
|
857
|
+
icon?: string
|
|
858
|
+
keepAlive?: boolean
|
|
859
|
+
transition?: string
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
866
|
+
## Pinia 状态管理
|
|
867
|
+
|
|
868
|
+
### Store 定义
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
// stores/user.ts
|
|
872
|
+
import { defineStore } from 'pinia'
|
|
873
|
+
import { ref, computed } from 'vue'
|
|
874
|
+
|
|
875
|
+
// 组合式 API 风格(推荐)
|
|
876
|
+
export const useUserStore = defineStore('user', () => {
|
|
877
|
+
// state
|
|
878
|
+
const user = ref<{ id: number; name: string; role: string } | null>(null)
|
|
879
|
+
const token = ref<string | null>(localStorage.getItem('token'))
|
|
880
|
+
const permissions = ref<string[]>([])
|
|
881
|
+
|
|
882
|
+
// getters
|
|
883
|
+
const isAuthenticated = computed(() => !!token.value)
|
|
884
|
+
const isAdmin = computed(() => user.value?.role === 'admin')
|
|
885
|
+
const displayName = computed(() => user.value?.name || '游客')
|
|
886
|
+
|
|
887
|
+
// actions
|
|
888
|
+
async function login(username: string, password: string) {
|
|
889
|
+
const res = await fetch('/api/auth/login', {
|
|
890
|
+
method: 'POST',
|
|
891
|
+
headers: { 'Content-Type': 'application/json' },
|
|
892
|
+
body: JSON.stringify({ username, password })
|
|
893
|
+
})
|
|
894
|
+
if (!res.ok) throw new Error('登录失败')
|
|
895
|
+
const data = await res.json()
|
|
896
|
+
token.value = data.token
|
|
897
|
+
user.value = data.user
|
|
898
|
+
permissions.value = data.permissions
|
|
899
|
+
localStorage.setItem('token', data.token)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function logout() {
|
|
903
|
+
token.value = null
|
|
904
|
+
user.value = null
|
|
905
|
+
permissions.value = []
|
|
906
|
+
localStorage.removeItem('token')
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function hasPermission(perm: string): boolean {
|
|
910
|
+
return permissions.value.includes(perm)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return {
|
|
914
|
+
user, token, permissions,
|
|
915
|
+
isAuthenticated, isAdmin, displayName,
|
|
916
|
+
login, logout, hasPermission
|
|
917
|
+
}
|
|
918
|
+
})
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
选项式 API 风格:
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
// stores/counter.ts
|
|
925
|
+
export const useCounterStore = defineStore('counter', {
|
|
926
|
+
state: () => ({
|
|
927
|
+
count: 0,
|
|
928
|
+
history: [] as number[]
|
|
929
|
+
}),
|
|
930
|
+
getters: {
|
|
931
|
+
doubleCount: (state) => state.count * 2,
|
|
932
|
+
lastThreeHistory: (state) => state.history.slice(-3)
|
|
933
|
+
},
|
|
934
|
+
actions: {
|
|
935
|
+
increment() {
|
|
936
|
+
this.count++
|
|
937
|
+
this.history.push(this.count)
|
|
938
|
+
},
|
|
939
|
+
async incrementAsync() {
|
|
940
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
941
|
+
this.increment()
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
})
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### Store 间交互
|
|
948
|
+
|
|
949
|
+
```typescript
|
|
950
|
+
// stores/cart.ts
|
|
951
|
+
import { defineStore } from 'pinia'
|
|
952
|
+
import { useUserStore } from './user'
|
|
953
|
+
|
|
954
|
+
export const useCartStore = defineStore('cart', () => {
|
|
955
|
+
const items = ref<CartItem[]>([])
|
|
956
|
+
const userStore = useUserStore()
|
|
957
|
+
|
|
958
|
+
const total = computed(() =>
|
|
959
|
+
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
// VIP 用户打折
|
|
963
|
+
const finalTotal = computed(() =>
|
|
964
|
+
userStore.isAdmin ? total.value * 0.9 : total.value
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
return { items, total, finalTotal }
|
|
968
|
+
})
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
### Pinia 插件
|
|
972
|
+
|
|
973
|
+
```typescript
|
|
974
|
+
// plugins/pinia-logger.ts
|
|
975
|
+
import type { PiniaPluginContext } from 'pinia'
|
|
976
|
+
|
|
977
|
+
export function piniaLogger({ store }: PiniaPluginContext) {
|
|
978
|
+
store.$onAction(({ name, args, after, onError }) => {
|
|
979
|
+
const startTime = Date.now()
|
|
980
|
+
console.log(`[Store:${store.$id}] Action "${name}" 开始`, args)
|
|
981
|
+
|
|
982
|
+
after((result) => {
|
|
983
|
+
console.log(`[Store:${store.$id}] Action "${name}" 完成 (${Date.now() - startTime}ms)`, result)
|
|
984
|
+
})
|
|
985
|
+
|
|
986
|
+
onError((error) => {
|
|
987
|
+
console.error(`[Store:${store.$id}] Action "${name}" 失败`, error)
|
|
988
|
+
})
|
|
989
|
+
})
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// 持久化插件
|
|
993
|
+
export function piniaPersist({ store }: PiniaPluginContext) {
|
|
994
|
+
const savedState = localStorage.getItem(`pinia-${store.$id}`)
|
|
995
|
+
if (savedState) {
|
|
996
|
+
store.$patch(JSON.parse(savedState))
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
store.$subscribe((mutation, state) => {
|
|
1000
|
+
localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
|
|
1001
|
+
})
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// main.ts 注册
|
|
1005
|
+
import { createPinia } from 'pinia'
|
|
1006
|
+
const pinia = createPinia()
|
|
1007
|
+
pinia.use(piniaLogger)
|
|
1008
|
+
pinia.use(piniaPersist)
|
|
1009
|
+
app.use(pinia)
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
### 在组件中使用 Store
|
|
1013
|
+
|
|
1014
|
+
```vue
|
|
1015
|
+
<script setup lang="ts">
|
|
1016
|
+
import { storeToRefs } from 'pinia'
|
|
1017
|
+
import { useUserStore } from '@/stores/user'
|
|
1018
|
+
|
|
1019
|
+
const userStore = useUserStore()
|
|
1020
|
+
|
|
1021
|
+
// storeToRefs 保持响应式(只解构 state 和 getters)
|
|
1022
|
+
const { user, isAuthenticated, displayName } = storeToRefs(userStore)
|
|
1023
|
+
|
|
1024
|
+
// actions 直接解构(不需要 storeToRefs)
|
|
1025
|
+
const { login, logout } = userStore
|
|
1026
|
+
|
|
1027
|
+
// 订阅状态变化
|
|
1028
|
+
userStore.$subscribe((mutation, state) => {
|
|
1029
|
+
console.log('状态变化:', mutation.type, mutation.storeId)
|
|
1030
|
+
})
|
|
1031
|
+
</script>
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
---
|
|
1035
|
+
|
|
1036
|
+
## 性能优化
|
|
1037
|
+
|
|
1038
|
+
### 虚拟列表
|
|
1039
|
+
|
|
1040
|
+
```vue
|
|
1041
|
+
<!-- VirtualList.vue -->
|
|
1042
|
+
<script setup lang="ts">
|
|
1043
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
1044
|
+
|
|
1045
|
+
interface Props {
|
|
1046
|
+
items: any[]
|
|
1047
|
+
itemHeight: number
|
|
1048
|
+
containerHeight: number
|
|
1049
|
+
overscan?: number
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const props = withDefaults(defineProps<Props>(), { overscan: 5 })
|
|
1053
|
+
|
|
1054
|
+
const scrollTop = ref(0)
|
|
1055
|
+
const containerRef = ref<HTMLDivElement>()
|
|
1056
|
+
|
|
1057
|
+
const totalHeight = computed(() => props.items.length * props.itemHeight)
|
|
1058
|
+
|
|
1059
|
+
const startIndex = computed(() =>
|
|
1060
|
+
Math.max(0, Math.floor(scrollTop.value / props.itemHeight) - props.overscan)
|
|
1061
|
+
)
|
|
1062
|
+
|
|
1063
|
+
const endIndex = computed(() =>
|
|
1064
|
+
Math.min(
|
|
1065
|
+
props.items.length,
|
|
1066
|
+
Math.ceil((scrollTop.value + props.containerHeight) / props.itemHeight) + props.overscan
|
|
1067
|
+
)
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
const visibleItems = computed(() =>
|
|
1071
|
+
props.items.slice(startIndex.value, endIndex.value).map((item, i) => ({
|
|
1072
|
+
...item,
|
|
1073
|
+
_index: startIndex.value + i,
|
|
1074
|
+
_style: {
|
|
1075
|
+
position: 'absolute' as const,
|
|
1076
|
+
top: `${(startIndex.value + i) * props.itemHeight}px`,
|
|
1077
|
+
height: `${props.itemHeight}px`,
|
|
1078
|
+
width: '100%'
|
|
1079
|
+
}
|
|
1080
|
+
}))
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
function onScroll(e: Event) {
|
|
1084
|
+
scrollTop.value = (e.target as HTMLDivElement).scrollTop
|
|
1085
|
+
}
|
|
1086
|
+
</script>
|
|
1087
|
+
|
|
1088
|
+
<template>
|
|
1089
|
+
<div
|
|
1090
|
+
ref="containerRef"
|
|
1091
|
+
:style="{ height: containerHeight + 'px', overflow: 'auto', position: 'relative' }"
|
|
1092
|
+
@scroll="onScroll"
|
|
1093
|
+
>
|
|
1094
|
+
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
|
|
1095
|
+
<div v-for="item in visibleItems" :key="item._index" :style="item._style">
|
|
1096
|
+
<slot :item="item" :index="item._index" />
|
|
1097
|
+
</div>
|
|
1098
|
+
</div>
|
|
1099
|
+
</div>
|
|
1100
|
+
</template>
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
### KeepAlive 缓存
|
|
1104
|
+
|
|
1105
|
+
```vue
|
|
1106
|
+
<template>
|
|
1107
|
+
<router-view v-slot="{ Component, route }">
|
|
1108
|
+
<Transition :name="route.meta.transition || 'fade'" mode="out-in">
|
|
1109
|
+
<KeepAlive :include="cachedRoutes" :max="10">
|
|
1110
|
+
<component :is="Component" :key="route.fullPath" />
|
|
1111
|
+
</KeepAlive>
|
|
1112
|
+
</Transition>
|
|
1113
|
+
</router-view>
|
|
1114
|
+
</template>
|
|
1115
|
+
|
|
1116
|
+
<script setup lang="ts">
|
|
1117
|
+
import { computed } from 'vue'
|
|
1118
|
+
import { useRouter } from 'vue-router'
|
|
1119
|
+
|
|
1120
|
+
const router = useRouter()
|
|
1121
|
+
|
|
1122
|
+
const cachedRoutes = computed(() =>
|
|
1123
|
+
router.getRoutes()
|
|
1124
|
+
.filter(route => route.meta.keepAlive)
|
|
1125
|
+
.map(route => route.name as string)
|
|
1126
|
+
)
|
|
1127
|
+
</script>
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
### 代码分割与懒加载
|
|
1131
|
+
|
|
1132
|
+
```typescript
|
|
1133
|
+
// 路由级代码分割
|
|
1134
|
+
const routes = [
|
|
1135
|
+
{
|
|
1136
|
+
path: '/dashboard',
|
|
1137
|
+
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue')
|
|
1138
|
+
}
|
|
1139
|
+
]
|
|
1140
|
+
|
|
1141
|
+
// 组件级懒加载
|
|
1142
|
+
import { defineAsyncComponent } from 'vue'
|
|
1143
|
+
|
|
1144
|
+
const HeavyChart = defineAsyncComponent({
|
|
1145
|
+
loader: () => import('@/components/HeavyChart.vue'),
|
|
1146
|
+
loadingComponent: LoadingSpinner,
|
|
1147
|
+
errorComponent: ErrorDisplay,
|
|
1148
|
+
delay: 200, // 延迟显示 loading(避免闪烁)
|
|
1149
|
+
timeout: 10000 // 超时时间
|
|
1150
|
+
})
|
|
1151
|
+
|
|
1152
|
+
// 条件懒加载
|
|
1153
|
+
const AdminPanel = defineAsyncComponent(() =>
|
|
1154
|
+
userStore.isAdmin
|
|
1155
|
+
? import('@/components/AdminPanel.vue')
|
|
1156
|
+
: import('@/components/AccessDenied.vue')
|
|
1157
|
+
)
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
### v-memo 优化
|
|
1161
|
+
|
|
1162
|
+
```vue
|
|
1163
|
+
<template>
|
|
1164
|
+
<!-- v-memo: 仅当依赖值变化时重新渲染 -->
|
|
1165
|
+
<div v-for="item in largeList" :key="item.id" v-memo="[item.id, item.selected]">
|
|
1166
|
+
<span>{{ item.name }}</span>
|
|
1167
|
+
<span :class="{ active: item.selected }">{{ item.status }}</span>
|
|
1168
|
+
<!-- 只有 item.id 或 item.selected 变化才重新渲染此节点 -->
|
|
1169
|
+
</div>
|
|
1170
|
+
</template>
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### 其他优化技巧
|
|
1174
|
+
|
|
1175
|
+
```vue
|
|
1176
|
+
<script setup lang="ts">
|
|
1177
|
+
import { shallowRef, shallowReactive, triggerRef, markRaw } from 'vue'
|
|
1178
|
+
|
|
1179
|
+
// shallowRef: 只追踪 .value 的变化,不深层响应
|
|
1180
|
+
const hugeList = shallowRef<Item[]>([])
|
|
1181
|
+
hugeList.value = [...newData] // 触发更新
|
|
1182
|
+
// hugeList.value[0].name = 'x' // 不会触发更新
|
|
1183
|
+
triggerRef(hugeList) // 手动触发更新
|
|
1184
|
+
|
|
1185
|
+
// shallowReactive: 只追踪顶层属性
|
|
1186
|
+
const state = shallowReactive({
|
|
1187
|
+
nested: { count: 0 } // nested.count 变化不会触发更新
|
|
1188
|
+
})
|
|
1189
|
+
|
|
1190
|
+
// markRaw: 标记对象永不转为响应式(如第三方库实例)
|
|
1191
|
+
const map = markRaw(new Map())
|
|
1192
|
+
const chart = markRaw(echarts.init(el))
|
|
1193
|
+
</script>
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
**Tree-shaking 最佳实践**:
|
|
1197
|
+
- 使用 `import { ref, computed } from 'vue'` 按需导入,不要 `import Vue from 'vue'`
|
|
1198
|
+
- Vite 默认支持 Tree-shaking,确保依赖提供 ESM 格式
|
|
1199
|
+
- 使用 `rollup-plugin-visualizer` 分析打包产物
|
|
1200
|
+
|
|
1201
|
+
---
|
|
1202
|
+
|
|
1203
|
+
## 测试
|
|
1204
|
+
|
|
1205
|
+
### Vitest 单元测试
|
|
1206
|
+
|
|
1207
|
+
```typescript
|
|
1208
|
+
// __tests__/composables/useFetch.spec.ts
|
|
1209
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
1210
|
+
import { ref, nextTick } from 'vue'
|
|
1211
|
+
import { useFetch } from '@/composables/useFetch'
|
|
1212
|
+
|
|
1213
|
+
// mock fetch
|
|
1214
|
+
global.fetch = vi.fn()
|
|
1215
|
+
|
|
1216
|
+
describe('useFetch', () => {
|
|
1217
|
+
beforeEach(() => {
|
|
1218
|
+
vi.clearAllMocks()
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
it('应当成功获取数据', async () => {
|
|
1222
|
+
const mockData = [{ id: 1, name: 'Item 1' }]
|
|
1223
|
+
;(fetch as any).mockResolvedValueOnce({
|
|
1224
|
+
ok: true,
|
|
1225
|
+
json: () => Promise.resolve(mockData)
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
const { data, loading, error } = useFetch('/api/items')
|
|
1229
|
+
|
|
1230
|
+
expect(loading.value).toBe(true)
|
|
1231
|
+
await nextTick()
|
|
1232
|
+
await vi.waitFor(() => expect(loading.value).toBe(false))
|
|
1233
|
+
|
|
1234
|
+
expect(data.value).toEqual(mockData)
|
|
1235
|
+
expect(error.value).toBeNull()
|
|
1236
|
+
})
|
|
1237
|
+
|
|
1238
|
+
it('应当处理请求错误', async () => {
|
|
1239
|
+
;(fetch as any).mockResolvedValueOnce({ ok: false, status: 404 })
|
|
1240
|
+
|
|
1241
|
+
const { data, error } = useFetch('/api/not-found')
|
|
1242
|
+
await vi.waitFor(() => expect(error.value).toBeTruthy())
|
|
1243
|
+
|
|
1244
|
+
expect(data.value).toBeNull()
|
|
1245
|
+
expect(error.value).toContain('404')
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1248
|
+
it('应当在 URL 变化时重新请求', async () => {
|
|
1249
|
+
;(fetch as any).mockResolvedValue({
|
|
1250
|
+
ok: true,
|
|
1251
|
+
json: () => Promise.resolve([])
|
|
1252
|
+
})
|
|
1253
|
+
|
|
1254
|
+
const url = ref('/api/items?page=1')
|
|
1255
|
+
useFetch(url)
|
|
1256
|
+
|
|
1257
|
+
await nextTick()
|
|
1258
|
+
expect(fetch).toHaveBeenCalledTimes(1)
|
|
1259
|
+
|
|
1260
|
+
url.value = '/api/items?page=2'
|
|
1261
|
+
await nextTick()
|
|
1262
|
+
expect(fetch).toHaveBeenCalledTimes(2)
|
|
1263
|
+
})
|
|
1264
|
+
})
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
### Vue Test Utils 组件测试
|
|
1268
|
+
|
|
1269
|
+
```typescript
|
|
1270
|
+
// __tests__/components/TodoList.spec.ts
|
|
1271
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
1272
|
+
import { mount, flushPromises } from '@vue/test-utils'
|
|
1273
|
+
import { createTestingPinia } from '@pinia/testing'
|
|
1274
|
+
import { nextTick } from 'vue'
|
|
1275
|
+
import TodoList from '@/components/TodoList.vue'
|
|
1276
|
+
import { useTodoStore } from '@/stores/todo'
|
|
1277
|
+
|
|
1278
|
+
describe('TodoList', () => {
|
|
1279
|
+
function createWrapper(options = {}) {
|
|
1280
|
+
return mount(TodoList, {
|
|
1281
|
+
global: {
|
|
1282
|
+
plugins: [
|
|
1283
|
+
createTestingPinia({
|
|
1284
|
+
createSpy: vi.fn,
|
|
1285
|
+
initialState: {
|
|
1286
|
+
todo: {
|
|
1287
|
+
items: [
|
|
1288
|
+
{ id: 1, text: '学习 Vue 3', done: false },
|
|
1289
|
+
{ id: 2, text: '写测试', done: true }
|
|
1290
|
+
]
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
})
|
|
1294
|
+
]
|
|
1295
|
+
},
|
|
1296
|
+
...options
|
|
1297
|
+
})
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
it('渲染待办列表', () => {
|
|
1301
|
+
const wrapper = createWrapper()
|
|
1302
|
+
const items = wrapper.findAll('[data-testid="todo-item"]')
|
|
1303
|
+
expect(items).toHaveLength(2)
|
|
1304
|
+
expect(items[0].text()).toContain('学习 Vue 3')
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1307
|
+
it('添加新待办', async () => {
|
|
1308
|
+
const wrapper = createWrapper()
|
|
1309
|
+
const store = useTodoStore()
|
|
1310
|
+
|
|
1311
|
+
const input = wrapper.find('input[data-testid="new-todo"]')
|
|
1312
|
+
await input.setValue('新任务')
|
|
1313
|
+
await wrapper.find('form').trigger('submit')
|
|
1314
|
+
|
|
1315
|
+
expect(store.addTodo).toHaveBeenCalledWith('新任务')
|
|
1316
|
+
})
|
|
1317
|
+
|
|
1318
|
+
it('切换完成状态', async () => {
|
|
1319
|
+
const wrapper = createWrapper()
|
|
1320
|
+
const store = useTodoStore()
|
|
1321
|
+
|
|
1322
|
+
const checkbox = wrapper.find('[data-testid="todo-checkbox-1"]')
|
|
1323
|
+
await checkbox.trigger('change')
|
|
1324
|
+
|
|
1325
|
+
expect(store.toggleTodo).toHaveBeenCalledWith(1)
|
|
1326
|
+
})
|
|
1327
|
+
|
|
1328
|
+
it('显示空状态提示', () => {
|
|
1329
|
+
const wrapper = mount(TodoList, {
|
|
1330
|
+
global: {
|
|
1331
|
+
plugins: [
|
|
1332
|
+
createTestingPinia({
|
|
1333
|
+
initialState: { todo: { items: [] } }
|
|
1334
|
+
})
|
|
1335
|
+
]
|
|
1336
|
+
}
|
|
1337
|
+
})
|
|
1338
|
+
|
|
1339
|
+
expect(wrapper.find('[data-testid="empty-state"]').exists()).toBe(true)
|
|
1340
|
+
expect(wrapper.text()).toContain('暂无待办事项')
|
|
1341
|
+
})
|
|
1342
|
+
})
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
### E2E 测试(Playwright)
|
|
1346
|
+
|
|
1347
|
+
```typescript
|
|
1348
|
+
// e2e/todo.spec.ts
|
|
1349
|
+
import { test, expect } from '@playwright/test'
|
|
1350
|
+
|
|
1351
|
+
test.describe('待办应用', () => {
|
|
1352
|
+
test.beforeEach(async ({ page }) => {
|
|
1353
|
+
await page.goto('/')
|
|
1354
|
+
})
|
|
1355
|
+
|
|
1356
|
+
test('完整的待办流程', async ({ page }) => {
|
|
1357
|
+
// 添加待办
|
|
1358
|
+
await page.fill('[data-testid="new-todo"]', '学习 Playwright')
|
|
1359
|
+
await page.press('[data-testid="new-todo"]', 'Enter')
|
|
1360
|
+
await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
|
|
1361
|
+
|
|
1362
|
+
// 标记完成
|
|
1363
|
+
await page.click('[data-testid="todo-checkbox-1"]')
|
|
1364
|
+
await expect(page.locator('[data-testid="todo-item-1"]')).toHaveClass(/completed/)
|
|
1365
|
+
|
|
1366
|
+
// 筛选已完成
|
|
1367
|
+
await page.click('[data-testid="filter-completed"]')
|
|
1368
|
+
await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
|
|
1369
|
+
|
|
1370
|
+
// 删除
|
|
1371
|
+
await page.hover('[data-testid="todo-item-1"]')
|
|
1372
|
+
await page.click('[data-testid="delete-1"]')
|
|
1373
|
+
await expect(page.locator('[data-testid="empty-state"]')).toBeVisible()
|
|
1374
|
+
})
|
|
1375
|
+
})
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
---
|
|
1379
|
+
|
|
1380
|
+
## TypeScript 集成
|
|
1381
|
+
|
|
1382
|
+
### 组件类型
|
|
1383
|
+
|
|
1384
|
+
```typescript
|
|
1385
|
+
// types/components.ts
|
|
1386
|
+
import type { Component, DefineComponent } from 'vue'
|
|
1387
|
+
|
|
1388
|
+
// 全局组件类型声明
|
|
1389
|
+
declare module 'vue' {
|
|
1390
|
+
interface GlobalComponents {
|
|
1391
|
+
BaseButton: typeof import('@/components/BaseButton.vue')['default']
|
|
1392
|
+
BaseInput: typeof import('@/components/BaseInput.vue')['default']
|
|
1393
|
+
BaseModal: typeof import('@/components/BaseModal.vue')['default']
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
### defineExpose 类型
|
|
1399
|
+
|
|
1400
|
+
```vue
|
|
1401
|
+
<!-- ChildForm.vue -->
|
|
1402
|
+
<script setup lang="ts">
|
|
1403
|
+
import { ref } from 'vue'
|
|
1404
|
+
|
|
1405
|
+
const formData = ref({ name: '', email: '' })
|
|
1406
|
+
|
|
1407
|
+
function validate(): boolean {
|
|
1408
|
+
return formData.value.name.length > 0
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function reset() {
|
|
1412
|
+
formData.value = { name: '', email: '' }
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
defineExpose({ validate, reset })
|
|
1416
|
+
</script>
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
```vue
|
|
1420
|
+
<!-- ParentView.vue -->
|
|
1421
|
+
<script setup lang="ts">
|
|
1422
|
+
import { ref } from 'vue'
|
|
1423
|
+
import ChildForm from './ChildForm.vue'
|
|
1424
|
+
|
|
1425
|
+
const formRef = ref<InstanceType<typeof ChildForm>>()
|
|
1426
|
+
|
|
1427
|
+
function handleSubmit() {
|
|
1428
|
+
if (formRef.value?.validate()) {
|
|
1429
|
+
// 提交表单
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
</script>
|
|
1433
|
+
|
|
1434
|
+
<template>
|
|
1435
|
+
<ChildForm ref="formRef" />
|
|
1436
|
+
<button @click="handleSubmit">提交</button>
|
|
1437
|
+
</template>
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
### 泛型组件(Vue 3.3+)
|
|
1441
|
+
|
|
1442
|
+
```vue
|
|
1443
|
+
<!-- GenericList.vue -->
|
|
1444
|
+
<script setup lang="ts" generic="T extends { id: number }">
|
|
1445
|
+
defineProps<{
|
|
1446
|
+
items: T[]
|
|
1447
|
+
selected?: T
|
|
1448
|
+
}>()
|
|
1449
|
+
|
|
1450
|
+
defineEmits<{
|
|
1451
|
+
select: [item: T]
|
|
1452
|
+
}>()
|
|
1453
|
+
</script>
|
|
1454
|
+
|
|
1455
|
+
<template>
|
|
1456
|
+
<ul>
|
|
1457
|
+
<li
|
|
1458
|
+
v-for="item in items"
|
|
1459
|
+
:key="item.id"
|
|
1460
|
+
:class="{ active: selected?.id === item.id }"
|
|
1461
|
+
@click="$emit('select', item)"
|
|
1462
|
+
>
|
|
1463
|
+
<slot :item="item" />
|
|
1464
|
+
</li>
|
|
1465
|
+
</ul>
|
|
1466
|
+
</template>
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
使用泛型组件:
|
|
1470
|
+
|
|
1471
|
+
```vue
|
|
1472
|
+
<script setup lang="ts">
|
|
1473
|
+
interface User { id: number; name: string; email: string }
|
|
1474
|
+
const users = ref<User[]>([])
|
|
1475
|
+
const selectedUser = ref<User>()
|
|
1476
|
+
</script>
|
|
1477
|
+
|
|
1478
|
+
<template>
|
|
1479
|
+
<!-- TypeScript 会推断 item 为 User 类型 -->
|
|
1480
|
+
<GenericList :items="users" :selected="selectedUser" @select="selectedUser = $event">
|
|
1481
|
+
<template #default="{ item }">
|
|
1482
|
+
{{ item.name }} - {{ item.email }}
|
|
1483
|
+
</template>
|
|
1484
|
+
</GenericList>
|
|
1485
|
+
</template>
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
### tsconfig.json 推荐配置
|
|
1489
|
+
|
|
1490
|
+
```json
|
|
1491
|
+
{
|
|
1492
|
+
"compilerOptions": {
|
|
1493
|
+
"target": "ES2020",
|
|
1494
|
+
"module": "ESNext",
|
|
1495
|
+
"moduleResolution": "bundler",
|
|
1496
|
+
"strict": true,
|
|
1497
|
+
"jsx": "preserve",
|
|
1498
|
+
"jsxImportSource": "vue",
|
|
1499
|
+
"resolveJsonModule": true,
|
|
1500
|
+
"isolatedModules": true,
|
|
1501
|
+
"esModuleInterop": true,
|
|
1502
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
1503
|
+
"skipLibCheck": true,
|
|
1504
|
+
"noEmit": true,
|
|
1505
|
+
"paths": {
|
|
1506
|
+
"@/*": ["./src/*"]
|
|
1507
|
+
},
|
|
1508
|
+
"types": ["vite/client", "vitest/globals"]
|
|
1509
|
+
},
|
|
1510
|
+
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.d.ts"],
|
|
1511
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
1512
|
+
}
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
---
|
|
1516
|
+
|
|
1517
|
+
## SSR / SSG(Nuxt 3)
|
|
1518
|
+
|
|
1519
|
+
### 项目初始化与目录结构
|
|
1520
|
+
|
|
1521
|
+
```bash
|
|
1522
|
+
npx nuxi@latest init my-nuxt-app
|
|
1523
|
+
cd my-nuxt-app && npm install
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
```
|
|
1527
|
+
my-nuxt-app/
|
|
1528
|
+
├── app.vue # 应用入口
|
|
1529
|
+
├── nuxt.config.ts # Nuxt 配置
|
|
1530
|
+
├── pages/ # 文件系统路由
|
|
1531
|
+
│ ├── index.vue # /
|
|
1532
|
+
│ ├── about.vue # /about
|
|
1533
|
+
│ └── users/
|
|
1534
|
+
│ ├── index.vue # /users
|
|
1535
|
+
│ └── [id].vue # /users/:id
|
|
1536
|
+
├── components/ # 自动导入的组件
|
|
1537
|
+
├── composables/ # 自动导入的组合式函数
|
|
1538
|
+
├── server/ # 服务端 API
|
|
1539
|
+
│ ├── api/
|
|
1540
|
+
│ │ └── users.ts # /api/users
|
|
1541
|
+
│ └── middleware/
|
|
1542
|
+
├── layouts/ # 布局组件
|
|
1543
|
+
├── middleware/ # 路由中间件
|
|
1544
|
+
├── plugins/ # 插件
|
|
1545
|
+
└── public/ # 静态资源
|
|
1546
|
+
```
|
|
1547
|
+
|
|
1548
|
+
### 数据获取
|
|
1549
|
+
|
|
1550
|
+
```vue
|
|
1551
|
+
<!-- pages/users/[id].vue -->
|
|
1552
|
+
<script setup lang="ts">
|
|
1553
|
+
const route = useRoute()
|
|
1554
|
+
|
|
1555
|
+
// useFetch: 自动处理 SSR hydration,避免重复请求
|
|
1556
|
+
const { data: user, pending, error } = await useFetch(
|
|
1557
|
+
`/api/users/${route.params.id}`,
|
|
1558
|
+
{
|
|
1559
|
+
key: `user-${route.params.id}`,
|
|
1560
|
+
transform: (data) => ({
|
|
1561
|
+
...data,
|
|
1562
|
+
fullName: `${data.firstName} ${data.lastName}`
|
|
1563
|
+
})
|
|
1564
|
+
}
|
|
1565
|
+
)
|
|
1566
|
+
|
|
1567
|
+
// useAsyncData: 更灵活的数据获取
|
|
1568
|
+
const { data: posts } = await useAsyncData(
|
|
1569
|
+
`user-posts-${route.params.id}`,
|
|
1570
|
+
() => $fetch(`/api/users/${route.params.id}/posts`)
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
// 仅客户端获取(不参与 SSR)
|
|
1574
|
+
const { data: analytics } = await useFetch('/api/analytics', {
|
|
1575
|
+
server: false,
|
|
1576
|
+
lazy: true
|
|
1577
|
+
})
|
|
1578
|
+
</script>
|
|
1579
|
+
|
|
1580
|
+
<template>
|
|
1581
|
+
<div v-if="pending">加载中...</div>
|
|
1582
|
+
<div v-else-if="error">加载失败: {{ error.message }}</div>
|
|
1583
|
+
<div v-else>
|
|
1584
|
+
<h1>{{ user?.fullName }}</h1>
|
|
1585
|
+
<article v-for="post in posts" :key="post.id">
|
|
1586
|
+
{{ post.title }}
|
|
1587
|
+
</article>
|
|
1588
|
+
</div>
|
|
1589
|
+
</template>
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
### Server API
|
|
1593
|
+
|
|
1594
|
+
```typescript
|
|
1595
|
+
// server/api/users/[id].get.ts
|
|
1596
|
+
import { defineEventHandler, getRouterParam, createError } from 'h3'
|
|
1597
|
+
|
|
1598
|
+
export default defineEventHandler(async (event) => {
|
|
1599
|
+
const id = getRouterParam(event, 'id')
|
|
1600
|
+
|
|
1601
|
+
const user = await prisma.user.findUnique({ where: { id: Number(id) } })
|
|
1602
|
+
if (!user) {
|
|
1603
|
+
throw createError({ statusCode: 404, statusMessage: '用户不存在' })
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
return user
|
|
1607
|
+
})
|
|
1608
|
+
|
|
1609
|
+
// server/api/users/index.post.ts
|
|
1610
|
+
export default defineEventHandler(async (event) => {
|
|
1611
|
+
const body = await readBody(event)
|
|
1612
|
+
|
|
1613
|
+
// 验证
|
|
1614
|
+
if (!body.name || !body.email) {
|
|
1615
|
+
throw createError({ statusCode: 400, statusMessage: '缺少必填字段' })
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
return await prisma.user.create({ data: body })
|
|
1619
|
+
})
|
|
1620
|
+
```
|
|
1621
|
+
|
|
1622
|
+
### Nuxt 配置
|
|
1623
|
+
|
|
1624
|
+
```typescript
|
|
1625
|
+
// nuxt.config.ts
|
|
1626
|
+
export default defineNuxtConfig({
|
|
1627
|
+
devtools: { enabled: true },
|
|
1628
|
+
|
|
1629
|
+
// SSR / SSG 模式切换
|
|
1630
|
+
ssr: true,
|
|
1631
|
+
// 预渲染指定路由(SSG)
|
|
1632
|
+
routeRules: {
|
|
1633
|
+
'/': { prerender: true },
|
|
1634
|
+
'/about': { prerender: true },
|
|
1635
|
+
'/api/**': { cors: true, headers: { 'cache-control': 's-maxage=600' } },
|
|
1636
|
+
'/dashboard/**': { ssr: false } // 仅客户端渲染
|
|
1637
|
+
},
|
|
1638
|
+
|
|
1639
|
+
// 模块
|
|
1640
|
+
modules: [
|
|
1641
|
+
'@pinia/nuxt',
|
|
1642
|
+
'@nuxtjs/tailwindcss',
|
|
1643
|
+
'@vueuse/nuxt',
|
|
1644
|
+
'@nuxt/image'
|
|
1645
|
+
],
|
|
1646
|
+
|
|
1647
|
+
// 运行时配置
|
|
1648
|
+
runtimeConfig: {
|
|
1649
|
+
dbUrl: process.env.DATABASE_URL, // 仅服务端可用
|
|
1650
|
+
public: {
|
|
1651
|
+
apiBase: process.env.API_BASE || '/api' // 客户端可用
|
|
1652
|
+
}
|
|
1653
|
+
},
|
|
1654
|
+
|
|
1655
|
+
// Vite 配置
|
|
1656
|
+
vite: {
|
|
1657
|
+
css: {
|
|
1658
|
+
preprocessorOptions: {
|
|
1659
|
+
scss: { additionalData: '@use "@/assets/scss/variables" as *;' }
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
},
|
|
1663
|
+
|
|
1664
|
+
// Nitro 服务引擎
|
|
1665
|
+
nitro: {
|
|
1666
|
+
preset: 'node-server', // 或 'vercel', 'cloudflare', 'netlify'
|
|
1667
|
+
compressPublicAssets: true
|
|
1668
|
+
}
|
|
1669
|
+
})
|
|
1670
|
+
```
|
|
1671
|
+
|
|
1672
|
+
---
|
|
1673
|
+
|
|
1674
|
+
## 常见陷阱与反模式
|
|
1675
|
+
|
|
1676
|
+
### 1. 响应式丢失
|
|
1677
|
+
|
|
1678
|
+
```typescript
|
|
1679
|
+
// 错误:解构 reactive 对象会丢失响应式
|
|
1680
|
+
const state = reactive({ count: 0, name: 'Vue' })
|
|
1681
|
+
let { count } = state // count 是普通变量,失去响应式
|
|
1682
|
+
|
|
1683
|
+
// 正确:使用 toRefs
|
|
1684
|
+
const { count, name } = toRefs(state)
|
|
1685
|
+
// 或直接使用 ref
|
|
1686
|
+
const count = ref(0)
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
### 2. ref 忘记 .value
|
|
1690
|
+
|
|
1691
|
+
```typescript
|
|
1692
|
+
// 错误:在 script 中忘记 .value
|
|
1693
|
+
const count = ref(0)
|
|
1694
|
+
console.log(count) // RefImpl 对象,不是 0
|
|
1695
|
+
if (count) { /* 永远为 true,因为 ref 对象是 truthy */ }
|
|
1696
|
+
|
|
1697
|
+
// 正确
|
|
1698
|
+
console.log(count.value) // 0
|
|
1699
|
+
if (count.value) { /* 正确判断 */ }
|
|
1700
|
+
|
|
1701
|
+
// 注意:模板中自动解包,不需要 .value
|
|
1702
|
+
// <template>{{ count }}</template> 正确
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1705
|
+
### 3. reactive 整体替换
|
|
1706
|
+
|
|
1707
|
+
```typescript
|
|
1708
|
+
// 错误:替换整个 reactive 对象会断开响应式连接
|
|
1709
|
+
let state = reactive({ items: [] })
|
|
1710
|
+
state = reactive({ items: [1, 2, 3] }) // 原有引用丢失
|
|
1711
|
+
|
|
1712
|
+
// 正确:修改属性而非替换引用
|
|
1713
|
+
const state = reactive({ items: [] as number[] })
|
|
1714
|
+
state.items = [1, 2, 3] // 修改属性,响应式保持
|
|
1715
|
+
|
|
1716
|
+
// 或者使用 ref
|
|
1717
|
+
const state = ref({ items: [] as number[] })
|
|
1718
|
+
state.value = { items: [1, 2, 3] } // ref 可以替换整个值
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
### 4. watch 的陷阱
|
|
1722
|
+
|
|
1723
|
+
```typescript
|
|
1724
|
+
// 错误:直接监听 reactive 对象的属性值
|
|
1725
|
+
const state = reactive({ count: 0 })
|
|
1726
|
+
watch(state.count, (val) => { /* 不生效 */ })
|
|
1727
|
+
|
|
1728
|
+
// 正确:使用 getter 函数
|
|
1729
|
+
watch(() => state.count, (val) => { /* 生效 */ })
|
|
1730
|
+
|
|
1731
|
+
// 错误:监听 ref 时加了 .value
|
|
1732
|
+
const count = ref(0)
|
|
1733
|
+
watch(count.value, (val) => { /* 不生效,因为传入的是原始值 0 */ })
|
|
1734
|
+
|
|
1735
|
+
// 正确:直接传 ref
|
|
1736
|
+
watch(count, (val) => { /* 正确 */ })
|
|
1737
|
+
```
|
|
1738
|
+
|
|
1739
|
+
### 5. 异步操作中的响应式
|
|
1740
|
+
|
|
1741
|
+
```typescript
|
|
1742
|
+
// 错误:异步回调中 this 指向或作用域问题
|
|
1743
|
+
async function fetchData() {
|
|
1744
|
+
const data = ref<any>(null)
|
|
1745
|
+
// 如果组件在请求完成前卸载,这里可能导致内存泄漏
|
|
1746
|
+
const res = await fetch('/api/data')
|
|
1747
|
+
data.value = await res.json()
|
|
1748
|
+
return data
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// 正确:配合 watchEffect + 清理
|
|
1752
|
+
const data = ref<any>(null)
|
|
1753
|
+
watchEffect(async (onCleanup) => {
|
|
1754
|
+
const controller = new AbortController()
|
|
1755
|
+
onCleanup(() => controller.abort())
|
|
1756
|
+
|
|
1757
|
+
try {
|
|
1758
|
+
const res = await fetch('/api/data', { signal: controller.signal })
|
|
1759
|
+
data.value = await res.json()
|
|
1760
|
+
} catch (e) {
|
|
1761
|
+
if ((e as Error).name !== 'AbortError') throw e
|
|
1762
|
+
}
|
|
1763
|
+
})
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1766
|
+
### 6. v-for 与 v-if 优先级
|
|
1767
|
+
|
|
1768
|
+
```vue
|
|
1769
|
+
<!-- 错误:Vue 3 中 v-if 优先级高于 v-for,无法访问 v-for 的变量 -->
|
|
1770
|
+
<li v-for="item in items" v-if="item.active" :key="item.id">
|
|
1771
|
+
{{ item.name }}
|
|
1772
|
+
</li>
|
|
1773
|
+
|
|
1774
|
+
<!-- 正确:使用 computed 过滤或用 template 包裹 -->
|
|
1775
|
+
<li v-for="item in activeItems" :key="item.id">
|
|
1776
|
+
{{ item.name }}
|
|
1777
|
+
</li>
|
|
1778
|
+
|
|
1779
|
+
<!-- 或 -->
|
|
1780
|
+
<template v-for="item in items" :key="item.id">
|
|
1781
|
+
<li v-if="item.active">{{ item.name }}</li>
|
|
1782
|
+
</template>
|
|
1783
|
+
```
|
|
1784
|
+
|
|
1785
|
+
### 7. 组件注册陷阱
|
|
1786
|
+
|
|
1787
|
+
```vue
|
|
1788
|
+
<script setup>
|
|
1789
|
+
// 错误:在 <script setup> 中用 app.component 全局注册
|
|
1790
|
+
// 没有导入就使用组件(会在运行时报错)
|
|
1791
|
+
|
|
1792
|
+
// 正确:直接 import 即可,<script setup> 自动注册
|
|
1793
|
+
import MyComponent from './MyComponent.vue'
|
|
1794
|
+
// 模板中直接使用 <MyComponent />
|
|
1795
|
+
</script>
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
### 8. Props 修改
|
|
1799
|
+
|
|
1800
|
+
```vue
|
|
1801
|
+
<script setup lang="ts">
|
|
1802
|
+
const props = defineProps<{ modelValue: string }>()
|
|
1803
|
+
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|
1804
|
+
|
|
1805
|
+
// 错误:直接修改 prop
|
|
1806
|
+
// props.modelValue = 'new value' // Vue 会警告
|
|
1807
|
+
|
|
1808
|
+
// 正确:通过 emit 通知父组件
|
|
1809
|
+
function updateValue(val: string) {
|
|
1810
|
+
emit('update:modelValue', val)
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// 或使用 computed 代理(适用于 v-model 场景)
|
|
1814
|
+
const localValue = computed({
|
|
1815
|
+
get: () => props.modelValue,
|
|
1816
|
+
set: (val) => emit('update:modelValue', val)
|
|
1817
|
+
})
|
|
1818
|
+
</script>
|
|
1819
|
+
|
|
1820
|
+
<template>
|
|
1821
|
+
<input v-model="localValue" />
|
|
1822
|
+
</template>
|
|
1823
|
+
```
|
|
1824
|
+
|
|
1825
|
+
### 9. 内存泄漏
|
|
1826
|
+
|
|
1827
|
+
```vue
|
|
1828
|
+
<script setup lang="ts">
|
|
1829
|
+
import { onMounted, onUnmounted } from 'vue'
|
|
1830
|
+
|
|
1831
|
+
// 错误:不清理副作用
|
|
1832
|
+
onMounted(() => {
|
|
1833
|
+
window.addEventListener('resize', handleResize)
|
|
1834
|
+
setInterval(pollData, 5000)
|
|
1835
|
+
})
|
|
1836
|
+
|
|
1837
|
+
// 正确:在 onUnmounted 中清理
|
|
1838
|
+
let intervalId: ReturnType<typeof setInterval>
|
|
1839
|
+
|
|
1840
|
+
onMounted(() => {
|
|
1841
|
+
window.addEventListener('resize', handleResize)
|
|
1842
|
+
intervalId = setInterval(pollData, 5000)
|
|
1843
|
+
})
|
|
1844
|
+
|
|
1845
|
+
onUnmounted(() => {
|
|
1846
|
+
window.removeEventListener('resize', handleResize)
|
|
1847
|
+
clearInterval(intervalId)
|
|
1848
|
+
})
|
|
1849
|
+
</script>
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
### 10. 过度使用全局状态
|
|
1853
|
+
|
|
1854
|
+
```typescript
|
|
1855
|
+
// 反模式:所有数据都放 Pinia store
|
|
1856
|
+
// 只有跨组件共享的状态才需要放 store
|
|
1857
|
+
|
|
1858
|
+
// 正确分层:
|
|
1859
|
+
// - 组件局部状态 → ref/reactive
|
|
1860
|
+
// - 父子通信 → props/emits
|
|
1861
|
+
// - 跨层级共享 → provide/inject
|
|
1862
|
+
// - 全局共享 → Pinia store
|
|
1863
|
+
```
|
|
1864
|
+
|
|
1865
|
+
---
|
|
1866
|
+
|
|
1867
|
+
## 项目脚手架与工程化
|
|
1868
|
+
|
|
1869
|
+
### Vite 项目创建
|
|
1870
|
+
|
|
1871
|
+
```bash
|
|
1872
|
+
npm create vue@latest my-vue-app
|
|
1873
|
+
# 选择: TypeScript, Vue Router, Pinia, Vitest, ESLint, Prettier
|
|
1874
|
+
|
|
1875
|
+
cd my-vue-app
|
|
1876
|
+
npm install
|
|
1877
|
+
npm run dev
|
|
1878
|
+
```
|
|
1879
|
+
|
|
1880
|
+
### 推荐项目结构
|
|
1881
|
+
|
|
1882
|
+
```
|
|
1883
|
+
src/
|
|
1884
|
+
├── assets/ # 静态资源(图片、字体、全局样式)
|
|
1885
|
+
├── components/ # 通用组件
|
|
1886
|
+
│ ├── base/ # 基础 UI 组件(Button, Input, Modal)
|
|
1887
|
+
│ └── business/ # 业务组件
|
|
1888
|
+
├── composables/ # 组合式函数
|
|
1889
|
+
├── layouts/ # 布局组件
|
|
1890
|
+
├── pages/ (或 views/) # 页面组件
|
|
1891
|
+
├── router/ # 路由配置
|
|
1892
|
+
├── stores/ # Pinia stores
|
|
1893
|
+
├── types/ # TypeScript 类型定义
|
|
1894
|
+
├── utils/ # 工具函数
|
|
1895
|
+
├── api/ # API 请求封装
|
|
1896
|
+
├── plugins/ # Vue 插件
|
|
1897
|
+
├── directives/ # 自定义指令
|
|
1898
|
+
├── App.vue
|
|
1899
|
+
└── main.ts
|
|
1900
|
+
```
|
|
1901
|
+
|
|
1902
|
+
### ESLint + Prettier 配置
|
|
1903
|
+
|
|
1904
|
+
```javascript
|
|
1905
|
+
// eslint.config.js (Flat Config)
|
|
1906
|
+
import pluginVue from 'eslint-plugin-vue'
|
|
1907
|
+
import vueTsEslintConfig from '@vue/eslint-config-typescript'
|
|
1908
|
+
import pluginVitest from '@vitest/eslint-plugin'
|
|
1909
|
+
import prettierConfig from '@vue/eslint-config-prettier'
|
|
1910
|
+
|
|
1911
|
+
export default [
|
|
1912
|
+
{ name: 'app/files-to-lint', files: ['**/*.{ts,mts,tsx,vue}'] },
|
|
1913
|
+
{ name: 'app/files-to-ignore', ignores: ['**/dist/**', '**/coverage/**'] },
|
|
1914
|
+
...pluginVue.configs['flat/recommended'],
|
|
1915
|
+
...vueTsEslintConfig(),
|
|
1916
|
+
{ ...pluginVitest.configs.recommended, files: ['src/**/__tests__/*'] },
|
|
1917
|
+
prettierConfig
|
|
1918
|
+
]
|
|
1919
|
+
```
|
|
1920
|
+
|
|
1921
|
+
---
|
|
1922
|
+
|
|
1923
|
+
## 学习路径
|
|
1924
|
+
|
|
1925
|
+
### 入门 (1-2 周)
|
|
1926
|
+
1. 模板语法、指令(v-bind/v-on/v-model/v-for/v-if)
|
|
1927
|
+
2. 组件基础(props/emits/slots)
|
|
1928
|
+
3. 响应式基础(ref/reactive/computed)
|
|
1929
|
+
4. 生命周期钩子
|
|
1930
|
+
5. 事件处理与表单绑定
|
|
1931
|
+
|
|
1932
|
+
### 进阶 (2-4 周)
|
|
1933
|
+
1. 组合式 API 深入(watch/watchEffect/composables)
|
|
1934
|
+
2. Vue Router 4(动态路由/守卫/嵌套路由)
|
|
1935
|
+
3. Pinia 状态管理
|
|
1936
|
+
4. TypeScript 集成
|
|
1937
|
+
5. 组件设计模式(provide/inject/Teleport/Suspense)
|
|
1938
|
+
|
|
1939
|
+
### 高级 (1-2 月)
|
|
1940
|
+
1. 响应式系统原理(Proxy/依赖收集/调度器)
|
|
1941
|
+
2. 虚拟 DOM 与编译优化
|
|
1942
|
+
3. 性能优化(虚拟列表/KeepAlive/v-memo/shallowRef)
|
|
1943
|
+
4. SSR/SSG(Nuxt 3)
|
|
1944
|
+
5. 自定义渲染器与高级插件开发
|
|
1945
|
+
|
|
1946
|
+
### 专家 (持续)
|
|
1947
|
+
1. Vue 编译器源码
|
|
1948
|
+
2. 自定义 Vite 插件
|
|
1949
|
+
3. 大规模应用架构(微前端/Monorepo)
|
|
1950
|
+
4. 设计系统构建
|
|
1951
|
+
|
|
1952
|
+
## 参考资源
|
|
1953
|
+
|
|
1954
|
+
### 官方文档
|
|
1955
|
+
- [Vue 3 官方文档](https://cn.vuejs.org/)
|
|
1956
|
+
- [Vue Router](https://router.vuejs.org/zh/)
|
|
1957
|
+
- [Pinia](https://pinia.vuejs.org/zh/)
|
|
1958
|
+
- [Nuxt 3](https://nuxt.com/)
|
|
1959
|
+
- [Vite](https://cn.vitejs.dev/)
|
|
1960
|
+
|
|
1961
|
+
### 推荐资源
|
|
1962
|
+
- [VueUse](https://vueuse.org/) - 组合式工具集合
|
|
1963
|
+
- [Vue Macros](https://vue-macros.dev/) - 编译时宏扩展
|
|
1964
|
+
- [Vitest](https://vitest.dev/) - Vite 原生测试框架
|
|
1965
|
+
|
|
1966
|
+
---
|
|
1967
|
+
|
|
1968
|
+
## Agent Checklist
|
|
1969
|
+
|
|
1970
|
+
使用本知识文件时,Agent 应确认以下要点:
|
|
1971
|
+
|
|
1972
|
+
- [ ] 项目使用 Vue 3 + `<script setup>` + TypeScript 的推荐写法
|
|
1973
|
+
- [ ] 响应式数据选择正确(ref 用于基本类型/需要替换的对象,reactive 用于复杂对象)
|
|
1974
|
+
- [ ] computed 用于派生状态,避免在模板中写复杂表达式
|
|
1975
|
+
- [ ] watch/watchEffect 正确清理副作用(onCleanup)
|
|
1976
|
+
- [ ] composable 函数命名以 `use` 前缀开头,返回 ref 而非 reactive
|
|
1977
|
+
- [ ] Props 使用 TypeScript 接口定义,设置合理的默认值
|
|
1978
|
+
- [ ] 组件事件使用 defineEmits 类型声明
|
|
1979
|
+
- [ ] 路由使用懒加载(`() => import(...)`)
|
|
1980
|
+
- [ ] 路由守卫中正确处理认证和权限逻辑
|
|
1981
|
+
- [ ] Pinia store 使用组合式 API 风格,通过 storeToRefs 解构
|
|
1982
|
+
- [ ] 大列表使用虚拟列表或分页,避免一次渲染数千 DOM 节点
|
|
1983
|
+
- [ ] 合理使用 KeepAlive 缓存频繁切换的组件
|
|
1984
|
+
- [ ] 使用 shallowRef/shallowReactive 优化大数据结构
|
|
1985
|
+
- [ ] 第三方库实例使用 markRaw 避免不必要的响应式代理
|
|
1986
|
+
- [ ] v-for 始终提供稳定的 key
|
|
1987
|
+
- [ ] 不直接修改 props,通过 emit 或 computed 代理
|
|
1988
|
+
- [ ] 组件卸载时清理所有副作用(事件监听/定时器/AbortController)
|
|
1989
|
+
- [ ] Nuxt 3 项目使用 useFetch/useAsyncData 获取数据,利用 SSR hydration
|
|
1990
|
+
- [ ] ESLint + Prettier 配置就绪,CI 中强制检查
|
|
1991
|
+
- [ ] 单元测试覆盖核心 composables 和业务组件
|
|
1992
|
+
|
|
1993
|
+
---
|
|
1994
|
+
|
|
1995
|
+
**知识ID**: `vue3-complete`
|
|
1996
|
+
**版本**: v1.0
|
|
1997
|
+
**领域**: frontend
|
|
1998
|
+
**类型**: standards
|
|
1999
|
+
**难度**: intermediate
|
|
2000
|
+
**质量分**: 95
|
|
2001
|
+
**维护者**: frontend-team@umadev.com
|
|
2002
|
+
**最后更新**: 2026-03-28
|