diff --git a/.gitignore b/.gitignore
index 4d29575..7d4754a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+
+.idea
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 8eaa236..0ff4aec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,9 +12,14 @@
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^24.2.0",
+ "@types/react": "^19.1.9",
+ "@types/react-dom": "^19.1.7",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-scripts": "5.0.1",
+ "typescript": "^5.9.2",
"web-vitals": "^2.1.4"
}
},
@@ -2506,6 +2511,14 @@
}
}
},
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmmirror.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/environment": {
"version": "27.5.1",
"resolved": "https://registry.npmmirror.com/@jest/environment/-/environment-27.5.1.tgz",
@@ -2520,6 +2533,17 @@
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
+ "node_modules/@jest/expect-utils": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz",
+ "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==",
+ "dependencies": {
+ "@jest/get-type": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/fake-timers": {
"version": "27.5.1",
"resolved": "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz",
@@ -2536,6 +2560,14 @@
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
+ "node_modules/@jest/get-type": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmmirror.com/@jest/get-type/-/get-type-30.0.1.tgz",
+ "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/globals": {
"version": "27.5.1",
"resolved": "https://registry.npmmirror.com/@jest/globals/-/globals-27.5.1.tgz",
@@ -2549,6 +2581,26 @@
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmmirror.com/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern/node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/reporters": {
"version": "27.5.1",
"resolved": "https://registry.npmmirror.com/@jest/reporters/-/reporters-27.5.1.tgz",
@@ -3483,6 +3535,202 @@
"@types/istanbul-lib-report": "*"
}
},
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmmirror.com/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@jest/types": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/@jest/types/-/types-30.0.5.tgz",
+ "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@sinclair/typebox": {
+ "version": "0.34.38",
+ "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.34.38.tgz",
+ "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA=="
+ },
+ "node_modules/@types/jest/node_modules/@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/jest/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@types/jest/node_modules/ci-info": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.3.0.tgz",
+ "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@types/jest/node_modules/expect": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/expect/-/expect-30.0.5.tgz",
+ "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==",
+ "dependencies": {
+ "@jest/expect-utils": "30.0.5",
+ "@jest/get-type": "30.0.1",
+ "jest-matcher-utils": "30.0.5",
+ "jest-message-util": "30.0.5",
+ "jest-mock": "30.0.5",
+ "jest-util": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-diff": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/jest-diff/-/jest-diff-30.0.5.tgz",
+ "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.0.1",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-matcher-utils": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz",
+ "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==",
+ "dependencies": {
+ "@jest/get-type": "30.0.1",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.0.5",
+ "pretty-format": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-message-util": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-30.0.5.tgz",
+ "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.0.5",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.0.5",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-mock": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/jest-mock/-/jest-mock-30.0.5.tgz",
+ "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==",
+ "dependencies": {
+ "@jest/types": "30.0.5",
+ "@types/node": "*",
+ "jest-util": "30.0.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-util": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-30.0.5.tgz",
+ "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==",
+ "dependencies": {
+ "@jest/types": "30.0.5",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@types/jest/node_modules/pretty-format": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-30.0.5.tgz",
+ "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3539,6 +3787,22 @@
"resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
+ "node_modules/@types/react": {
+ "version": "19.1.9",
+ "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.1.9.tgz",
+ "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.1.7",
+ "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.1.7.tgz",
+ "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmmirror.com/@types/resolve/-/resolve-1.17.1.tgz",
@@ -5888,6 +6152,11 @@
"resolved": "https://registry.npmmirror.com/cssom/-/cssom-0.3.8.tgz",
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
},
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -15096,16 +15365,15 @@
}
},
"node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
- "peer": true,
+ "version": "5.9.2",
+ "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz",
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/unbox-primitive": {
diff --git a/package.json b/package.json
index 4389229..b2ab329 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,14 @@
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^24.2.0",
+ "@types/react": "^19.1.9",
+ "@types/react-dom": "^19.1.7",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-scripts": "5.0.1",
+ "typescript": "^5.9.2",
"web-vitals": "^2.1.4"
},
"scripts": {
diff --git a/src/App.css b/src/App.css
index 74b5e05..80fd77e 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,38 +1,158 @@
.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
+ min-height: 100vh;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
-.App-link {
- color: #61dafb;
+.App-header h1 {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ font-weight: 700;
+ font-size: 2.5rem;
}
-@keyframes App-logo-spin {
+/* 滚动条样式 */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
+}
+
+/* 按钮悬停效果 */
+button {
+ transition: all 0.3s ease !important;
+}
+
+button:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+button:active {
+ transform: translateY(0);
+}
+
+/* 卡片阴影效果 */
+div[style*="border"] {
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: box-shadow 0.3s ease;
+}
+
+div[style*="border"]:hover {
+ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
+}
+
+/* 输入框样式 */
+input[type="text"] {
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
+}
+
+input[type="text"]:focus {
+ outline: none;
+ border-color: #667eea !important;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .App-header h1 {
+ font-size: 2rem;
+ }
+
+ .App-header p {
+ font-size: 14px;
+ }
+
+ nav {
+ flex-direction: column !important;
+ align-items: center !important;
+ }
+
+ nav button {
+ width: 100%;
+ max-width: 300px;
+ }
+}
+
+@media (max-width: 480px) {
+ .App-header {
+ padding: 15px !important;
+ }
+
+ main {
+ padding: 0 15px 30px 15px !important;
+ }
+
+ .App-header h1 {
+ font-size: 1.8rem;
+ }
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
from {
- transform: rotate(0deg);
+ opacity: 0;
+ transform: translateY(20px);
}
to {
- transform: rotate(360deg);
+ opacity: 1;
+ transform: translateY(0);
}
}
+
+main > div {
+ animation: fadeIn 0.5s ease-out;
+}
+
+/* 代码块样式 */
+code {
+ background-color: #f4f4f4;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: 'Courier New', monospace;
+ font-size: 0.9em;
+ color: #e83e8c;
+}
+
+/* 高亮文本 */
+strong {
+ color: #495057;
+ font-weight: 600;
+}
+
+/* 链接样式 */
+a {
+ color: #667eea;
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+a:hover {
+ color: #764ba2;
+ text-decoration: underline;
+}
diff --git a/src/App.js b/src/App.js
deleted file mode 100644
index 3784575..0000000
--- a/src/App.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import logo from './logo.svg';
-import './App.css';
-
-function App() {
- return (
-
- );
-}
-
-export default App;
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..ca80937
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,198 @@
+import React, { useState } from 'react';
+import './App.css';
+import JSXExample from './examples/JSXExample';
+import TSXExample from './examples/TSXExample';
+import ReactVsVueExamples from './examples/ReactVsVueExamples';
+import VueStyleExamples from './examples/VueStyleExamples';
+import AdvancedConcepts from './examples/AdvancedConcepts';
+import PerformanceDemo from './examples/PerformanceDemo';
+import InteractiveGames from './examples/InteractiveGames';
+
+type ViewType = 'jsx-tsx' | 'react-vue' | 'advanced' | 'performance' | 'games';
+
+const App: React.FC = () => {
+ const [currentView, setCurrentView] = useState('jsx-tsx');
+
+ const navigationItems = [
+ { key: 'jsx-tsx', label: 'JSX vs TSX', emoji: '📚' },
+ { key: 'react-vue', label: 'React vs Vue', emoji: '⚔️' },
+ { key: 'advanced', label: '高级概念', emoji: '🚀' },
+ { key: 'performance', label: '性能优化', emoji: '⚡' },
+ { key: 'games', label: '交互游戏', emoji: '🎮' }
+ ];
+
+ const renderContent = () => {
+ switch (currentView) {
+ case 'jsx-tsx':
+ return (
+
+
+ 📚 JSX 和 TSX 概念对比
+
+
+
+
+ );
+
+ case 'react-vue':
+ return (
+
+
+ ⚔️ React vs Vue 设计思想对比
+
+
+
+
+
+
+
+
🎯 核心差异总结
+
+
+
🚀 React 哲学
+
+ - 函数式编程:UI = f(state)
+ - 显式控制:开发者完全掌控
+ - 组合优于继承:通过组合构建复杂UI
+ - 单向数据流:可预测的数据流向
+ - 不可变性:状态变化通过新对象
+
+
+
+
🌟 Vue 哲学
+
+ - 渐进式框架:逐步增强现有项目
+ - 响应式系统:数据变化自动更新视图
+ - 约定优于配置:合理的默认值
+ - 开发者友好:降低学习曲线
+ - 模板语法:接近HTML的声明式语法
+
+
+
+
+
+ );
+
+ case 'advanced':
+ return ;
+
+ case 'performance':
+ return ;
+
+ case 'games':
+ return ;
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ React 全面学习演示
+
+ 深入理解JSX、TSX、设计思想、高级概念、性能优化和实战应用
+
+
+ {/* 导航按钮 */}
+
+
+ {/* 视图描述 */}
+
+ {currentView === 'jsx-tsx' && '学习JSX和TSX的语法差异、类型安全和最佳实践'}
+ {currentView === 'react-vue' && '对比React和Vue的设计理念、架构特点和适用场景'}
+ {currentView === 'advanced' && '掌握Context、自定义Hooks、HOC、Render Props等高级模式'}
+ {currentView === 'performance' && '学习React性能优化技巧:memo、虚拟滚动、懒加载等'}
+ {currentView === 'games' && '通过交互游戏实践React状态管理和事件处理'}
+
+
+
+
+ {renderContent()}
+
+
+ {/* 页脚 */}
+
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/src/concepts-explanation.md b/src/concepts-explanation.md
new file mode 100644
index 0000000..330dc98
--- /dev/null
+++ b/src/concepts-explanation.md
@@ -0,0 +1,178 @@
+# JSX vs TSX 概念详解
+
+## 📖 基本概念
+
+### JSX (JavaScript XML)
+JSX是React的语法扩展,允许你在JavaScript中编写类似HTML的代码。
+
+**特点:**
+- 语法简洁,易于理解
+- 编译时转换为普通的JavaScript函数调用
+- 支持在{}中嵌入JavaScript表达式
+- 没有类型检查
+
+### TSX (TypeScript JSX)
+TSX是JSX的TypeScript版本,在JSX基础上添加了类型安全。
+
+**特点:**
+- 所有JSX的功能
+- 编译时类型检查
+- 更好的IDE支持(自动补全、错误提示)
+- 更安全的代码
+
+## 🔄 语法对比
+
+### 1. 基本组件定义
+
+**JSX (JavaScript):**
+```jsx
+function MyComponent() {
+ return Hello World
;
+}
+```
+
+**TSX (TypeScript):**
+```tsx
+const MyComponent: React.FC = () => {
+ return Hello World
;
+};
+```
+
+### 2. Props类型定义
+
+**JSX:**
+```jsx
+function UserCard({ name, age }) {
+ return (
+
+ );
+}
+```
+
+**TSX:**
+```tsx
+interface UserCardProps {
+ name: string;
+ age: number;
+}
+
+const UserCard: React.FC = ({ name, age }) => {
+ return (
+
+ );
+};
+```
+
+### 3. 状态管理
+
+**JSX:**
+```jsx
+const [count, setCount] = useState(0);
+```
+
+**TSX:**
+```tsx
+const [count, setCount] = useState(0);
+const [user, setUser] = useState(null);
+```
+
+### 4. 事件处理
+
+**JSX:**
+```jsx
+const handleClick = (event) => {
+ console.log(event.target.value);
+};
+```
+
+**TSX:**
+```tsx
+const handleClick = (event: React.MouseEvent) => {
+ console.log(event.target.value);
+};
+```
+
+## 🎯 主要区别
+
+| 特性 | JSX | TSX |
+|------|-----|-----|
+| 类型安全 | ❌ | ✅ |
+| 编译时错误检查 | 基础 | 完整 |
+| IDE支持 | 基础 | 增强 |
+| 学习曲线 | 简单 | 中等 |
+| 运行时性能 | 相同 | 相同 |
+| 代码维护性 | 一般 | 更好 |
+
+## 🔍 实际示例中的区别
+
+### 类型安全示例
+
+**JSX可能的问题:**
+```jsx
+// 运行时才能发现错误
+function displayAge(user) {
+ return user.age; // 如果user为null会报错
+}
+```
+
+**TSX的解决方案:**
+```tsx
+interface User {
+ age: number;
+}
+
+function displayAge(user: User | null): number | null {
+ return user?.age || null; // 编译时就能发现潜在问题
+}
+```
+
+## 🚀 何时使用哪种
+
+### 选择JSX当:
+- 小型项目或原型开发
+- 团队对TypeScript不熟悉
+- 需要快速开发
+
+### 选择TSX当:
+- 中大型项目
+- 团队协作开发
+- 需要高代码质量
+- 复杂的数据结构
+
+## 💡 最佳实践
+
+### JSX最佳实践:
+1. 使用PropTypes进行运行时类型检查
+2. 编写详细的注释
+3. 使用ESLint进行代码检查
+
+### TSX最佳实践:
+1. 定义清晰的接口
+2. 使用泛型提高复用性
+3. 利用TypeScript的严格模式
+4. 合理使用联合类型和可选属性
+
+## 🔧 项目配置
+
+### 启用TSX支持需要:
+1. 安装TypeScript:`npm install typescript`
+2. 安装类型定义:`npm install @types/react @types/react-dom`
+3. 创建tsconfig.json配置文件
+4. 将.js文件重命名为.tsx
+
+### tsconfig.json关键配置:
+```json
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "strict": true,
+ "esModuleInterop": true
+ }
+}
+```
\ No newline at end of file
diff --git a/src/examples/AdvancedConcepts.tsx b/src/examples/AdvancedConcepts.tsx
new file mode 100644
index 0000000..146bd3e
--- /dev/null
+++ b/src/examples/AdvancedConcepts.tsx
@@ -0,0 +1,405 @@
+import React, { useState, useEffect, useContext, createContext, useCallback, useMemo } from 'react';
+
+// ========== 高级React概念示例 ==========
+
+// 1. React Context 示例
+interface ThemeContextType {
+ theme: 'light' | 'dark';
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext(null);
+
+const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
+
+ const toggleTheme = useCallback(() => {
+ setTheme(prev => prev === 'light' ? 'dark' : 'light');
+ }, []);
+
+ const value = useMemo(() => ({
+ theme,
+ toggleTheme
+ }), [theme, toggleTheme]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+const ThemedComponent: React.FC = () => {
+ const themeContext = useContext(ThemeContext);
+
+ if (!themeContext) {
+ throw new Error('ThemedComponent must be used within ThemeProvider');
+ }
+
+ const { theme, toggleTheme } = themeContext;
+
+ return (
+
+
🎨 Context API 示例
+
当前主题: {theme === 'light' ? '☀️ 浅色' : '🌙 深色'}
+
+
+ );
+};
+
+// 2. 自定义Hook示例
+const useCounter = (initialValue: number = 0) => {
+ const [count, setCount] = useState(initialValue);
+
+ const increment = useCallback(() => setCount(prev => prev + 1), []);
+ const decrement = useCallback(() => setCount(prev => prev - 1), []);
+ const reset = useCallback(() => setCount(initialValue), [initialValue]);
+
+ return { count, increment, decrement, reset };
+};
+
+const useLocalStorage = (key: string, initialValue: T) => {
+ const [storedValue, setStoredValue] = useState(() => {
+ try {
+ const item = window.localStorage.getItem(key);
+ return item ? JSON.parse(item) : initialValue;
+ } catch (error) {
+ console.log(error);
+ return initialValue;
+ }
+ });
+
+ const setValue = useCallback((value: T | ((val: T) => T)) => {
+ try {
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
+ setStoredValue(valueToStore);
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
+ } catch (error) {
+ console.log(error);
+ }
+ }, [key, storedValue]);
+
+ return [storedValue, setValue] as const;
+};
+
+const CustomHooksDemo: React.FC = () => {
+ const { count, increment, decrement, reset } = useCounter(0);
+ const [name, setName] = useLocalStorage('userName', '');
+
+ return (
+
+
🎣 自定义Hooks示例
+
+
+
计数器Hook:
+
计数: {count}
+
+
+
+
+
+
+
本地存储Hook:
+
setName(e.target.value)}
+ placeholder="输入你的名字(会保存到localStorage)"
+ style={{ padding: '5px', marginRight: '10px' }}
+ />
+
存储的名字: {name}
+
+
+ );
+};
+
+// 3. 高阶组件 (HOC) 示例
+interface WithLoadingProps {
+ isLoading: boolean;
+}
+
+const withLoading = (Component: React.ComponentType
) => {
+ return (props: P & WithLoadingProps) => {
+ const { isLoading, ...otherProps } = props;
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ return ;
+ };
+};
+
+const DataDisplay: React.FC<{ data: string[] }> = ({ data }) => (
+
+
数据列表:
+
+ {data.map((item, index) => (
+ - {item}
+ ))}
+
+
+);
+
+const DataDisplayWithLoading = withLoading(DataDisplay);
+
+const HOCDemo: React.FC = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [data, setData] = useState(['项目1', '项目2', '项目3']);
+
+ const loadData = () => {
+ setIsLoading(true);
+ setTimeout(() => {
+ setData(['新数据1', '新数据2', '新数据3', '新数据4']);
+ setIsLoading(false);
+ }, 2000);
+ };
+
+ return (
+
+
🔗 高阶组件 (HOC) 示例
+
+
+
+
+ );
+};
+
+// 4. Render Props 模式示例
+interface MouseTrackerProps {
+ children: (position: { x: number; y: number }) => React.ReactNode;
+}
+
+const MouseTracker: React.FC = ({ children }) => {
+ const [position, setPosition] = useState({ x: 0, y: 0 });
+
+ useEffect(() => {
+ const handleMouseMove = (event: MouseEvent) => {
+ setPosition({ x: event.clientX, y: event.clientY });
+ };
+
+ document.addEventListener('mousemove', handleMouseMove);
+ return () => document.removeEventListener('mousemove', handleMouseMove);
+ }, []);
+
+ return <>{children(position)}>;
+};
+
+const RenderPropsDemo: React.FC = () => {
+ return (
+
+
🖱️ Render Props 模式示例
+
+ {({ x, y }) => (
+
+ )}
+
+
+ );
+};
+
+// 5. 错误边界示例
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error?: Error;
+}
+
+class ErrorBoundary extends React.Component<
+ { children: React.ReactNode },
+ ErrorBoundaryState
+> {
+ constructor(props: { children: React.ReactNode }) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.log('错误边界捕获到错误:', error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
⚠️ 出错了!
+
组件渲染时发生错误
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+const BuggyComponent: React.FC<{ shouldThrow: boolean }> = ({ shouldThrow }) => {
+ if (shouldThrow) {
+ throw new Error('这是一个故意的错误!');
+ }
+ return ✅ 组件正常工作
;
+};
+
+const ErrorBoundaryDemo: React.FC = () => {
+ const [shouldThrow, setShouldThrow] = useState(false);
+
+ return (
+
+
🛡️ 错误边界示例
+
+
+
+
+
+
+ );
+};
+
+// 主组件
+const AdvancedConcepts: React.FC = () => {
+ return (
+
+
🚀 React高级概念演示
+
+
+
+
+
+
+
+
+
+
+
+
💡 高级概念总结
+
+ - Context API: 跨组件层级共享状态,避免prop drilling
+ - 自定义Hooks: 封装可复用的状态逻辑
+ - 高阶组件(HOC): 增强组件功能的设计模式
+ - Render Props: 通过函数prop共享代码
+ - 错误边界: 捕获和处理组件渲染错误
+
+
+
+ );
+};
+
+export default AdvancedConcepts;
\ No newline at end of file
diff --git a/src/examples/InteractiveGames.tsx b/src/examples/InteractiveGames.tsx
new file mode 100644
index 0000000..b005c78
--- /dev/null
+++ b/src/examples/InteractiveGames.tsx
@@ -0,0 +1,528 @@
+import React, { useState, useEffect, useCallback, useRef } from 'react';
+
+// ========== 交互式游戏演示 ==========
+
+// 1. 记忆卡片游戏
+interface Card {
+ id: number;
+ value: string;
+ isFlipped: boolean;
+ isMatched: boolean;
+}
+
+const MemoryGame: React.FC = () => {
+ const [cards, setCards] = useState([]);
+ const [flippedCards, setFlippedCards] = useState([]);
+ const [score, setScore] = useState(0);
+ const [moves, setMoves] = useState(0);
+ const [gameComplete, setGameComplete] = useState(false);
+
+ const cardValues = ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼'];
+
+ const initializeGame = useCallback(() => {
+ const shuffledCards = [...cardValues, ...cardValues]
+ .map((value, index) => ({
+ id: index,
+ value,
+ isFlipped: false,
+ isMatched: false
+ }))
+ .sort(() => Math.random() - 0.5);
+
+ setCards(shuffledCards);
+ setFlippedCards([]);
+ setScore(0);
+ setMoves(0);
+ setGameComplete(false);
+ }, []);
+
+ useEffect(() => {
+ initializeGame();
+ }, [initializeGame]);
+
+ useEffect(() => {
+ if (flippedCards.length === 2) {
+ const [first, second] = flippedCards;
+ const firstCard = cards[first];
+ const secondCard = cards[second];
+
+ if (firstCard.value === secondCard.value) {
+ setTimeout(() => {
+ setCards(prev => prev.map(card =>
+ card.id === first || card.id === second
+ ? { ...card, isMatched: true }
+ : card
+ ));
+ setFlippedCards([]);
+ setScore(prev => prev + 10);
+ }, 1000);
+ } else {
+ setTimeout(() => {
+ setCards(prev => prev.map(card =>
+ card.id === first || card.id === second
+ ? { ...card, isFlipped: false }
+ : card
+ ));
+ setFlippedCards([]);
+ }, 1000);
+ }
+ setMoves(prev => prev + 1);
+ }
+ }, [flippedCards, cards]);
+
+ useEffect(() => {
+ if (cards.length > 0 && cards.every(card => card.isMatched)) {
+ setGameComplete(true);
+ }
+ }, [cards]);
+
+ const handleCardClick = (cardId: number) => {
+ if (flippedCards.length >= 2) return;
+ if (cards[cardId].isFlipped || cards[cardId].isMatched) return;
+
+ setCards(prev => prev.map(card =>
+ card.id === cardId ? { ...card, isFlipped: true } : card
+ ));
+ setFlippedCards(prev => [...prev, cardId]);
+ };
+
+ return (
+
+
🧠 记忆卡片游戏
+
+ 得分: {score}
+ 步数: {moves}
+
+
+
+ {gameComplete && (
+
+ 🎉 恭喜!游戏完成!用了 {moves} 步,得分 {score}!
+
+ )}
+
+
+ {cards.map((card) => (
+
handleCardClick(card.id)}
+ style={{
+ width: '60px',
+ height: '60px',
+ backgroundColor: card.isFlipped || card.isMatched ? '#fff' : '#4a5568',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: '8px',
+ cursor: 'pointer',
+ fontSize: '24px',
+ border: '2px solid #e2e8f0',
+ transition: 'all 0.3s ease'
+ }}
+ >
+ {(card.isFlipped || card.isMatched) ? card.value : '?'}
+
+ ))}
+
+
+ );
+};
+
+// 2. 井字棋游戏
+type Player = 'X' | 'O' | null;
+
+const TicTacToe: React.FC = () => {
+ const [board, setBoard] = useState(Array(9).fill(null));
+ const [currentPlayer, setCurrentPlayer] = useState<'X' | 'O'>('X');
+ const [winner, setWinner] = useState(null);
+ const [gameOver, setGameOver] = useState(false);
+
+ const winningCombinations = [
+ [0, 1, 2], [3, 4, 5], [6, 7, 8], // 行
+ [0, 3, 6], [1, 4, 7], [2, 5, 8], // 列
+ [0, 4, 8], [2, 4, 6] // 对角线
+ ];
+
+ useEffect(() => {
+ const checkWinner = () => {
+ for (const combination of winningCombinations) {
+ const [a, b, c] = combination;
+ if (board[a] && board[a] === board[b] && board[a] === board[c]) {
+ setWinner(board[a]);
+ setGameOver(true);
+ return;
+ }
+ }
+
+ if (board.every(cell => cell !== null)) {
+ setGameOver(true);
+ }
+ };
+
+ checkWinner();
+ }, [board, winningCombinations]);
+
+ const handleCellClick = (index: number) => {
+ if (board[index] || gameOver) return;
+
+ const newBoard = [...board];
+ newBoard[index] = currentPlayer;
+ setBoard(newBoard);
+ setCurrentPlayer(currentPlayer === 'X' ? 'O' : 'X');
+ };
+
+ const resetGame = () => {
+ setBoard(Array(9).fill(null));
+ setCurrentPlayer('X');
+ setWinner(null);
+ setGameOver(false);
+ };
+
+ return (
+
+
⭕ 井字棋游戏
+
+
+ {gameOver ? (
+
+ {winner ? `🎉 玩家 ${winner} 获胜!` : '🤝 平局!'}
+
+ ) : (
+
当前玩家: {currentPlayer}
+ )}
+
+
+
+
+ {board.map((cell, index) => (
+
handleCellClick(index)}
+ style={{
+ backgroundColor: '#f7fafc',
+ border: '2px solid #e2e8f0',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontSize: '24px',
+ fontWeight: 'bold',
+ cursor: gameOver || cell ? 'default' : 'pointer',
+ transition: 'background-color 0.2s ease'
+ }}
+ >
+ {cell}
+
+ ))}
+
+
+ );
+};
+
+// 3. 简单贪吃蛇游戏
+interface Position {
+ x: number;
+ y: number;
+}
+
+const SnakeGame: React.FC = () => {
+ const [snake, setSnake] = useState([{ x: 10, y: 10 }]);
+ const [food, setFood] = useState({ x: 5, y: 5 });
+ const [direction, setDirection] = useState({ x: 0, y: -1 });
+ const [gameRunning, setGameRunning] = useState(false);
+ const [score, setScore] = useState(0);
+ const [gameOver, setGameOver] = useState(false);
+
+ const gameAreaSize = 20;
+ const gameLoopRef = useRef(null);
+
+ const generateFood = useCallback(() => {
+ const newFood = {
+ x: Math.floor(Math.random() * gameAreaSize),
+ y: Math.floor(Math.random() * gameAreaSize)
+ };
+ setFood(newFood);
+ }, []);
+
+ const resetGame = () => {
+ setSnake([{ x: 10, y: 10 }]);
+ setDirection({ x: 0, y: -1 });
+ setScore(0);
+ setGameOver(false);
+ setGameRunning(false);
+ generateFood();
+ };
+
+ const startGame = () => {
+ if (!gameRunning && !gameOver) {
+ setGameRunning(true);
+ }
+ };
+
+ const gameLoop = useCallback(() => {
+ setSnake(prevSnake => {
+ const newSnake = [...prevSnake];
+ const head = { ...newSnake[0] };
+
+ head.x += direction.x;
+ head.y += direction.y;
+
+ // 检查边界碰撞
+ if (head.x < 0 || head.x >= gameAreaSize || head.y < 0 || head.y >= gameAreaSize) {
+ setGameOver(true);
+ setGameRunning(false);
+ return prevSnake;
+ }
+
+ // 检查自身碰撞
+ for (const segment of newSnake) {
+ if (head.x === segment.x && head.y === segment.y) {
+ setGameOver(true);
+ setGameRunning(false);
+ return prevSnake;
+ }
+ }
+
+ newSnake.unshift(head);
+
+ // 检查是否吃到食物
+ if (head.x === food.x && head.y === food.y) {
+ setScore(prev => prev + 10);
+ generateFood();
+ } else {
+ newSnake.pop();
+ }
+
+ return newSnake;
+ });
+ }, [direction, food, generateFood]);
+
+ useEffect(() => {
+ if (gameRunning) {
+ gameLoopRef.current = window.setInterval(gameLoop, 200);
+ } else {
+ if (gameLoopRef.current) {
+ window.clearInterval(gameLoopRef.current);
+ }
+ }
+
+ return () => {
+ if (gameLoopRef.current) {
+ window.clearInterval(gameLoopRef.current);
+ }
+ };
+ }, [gameRunning, gameLoop]);
+
+ useEffect(() => {
+ const handleKeyPress = (e: KeyboardEvent) => {
+ if (!gameRunning) return;
+
+ switch (e.key) {
+ case 'ArrowUp':
+ if (direction.y === 0) setDirection({ x: 0, y: -1 });
+ break;
+ case 'ArrowDown':
+ if (direction.y === 0) setDirection({ x: 0, y: 1 });
+ break;
+ case 'ArrowLeft':
+ if (direction.x === 0) setDirection({ x: -1, y: 0 });
+ break;
+ case 'ArrowRight':
+ if (direction.x === 0) setDirection({ x: 1, y: 0 });
+ break;
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyPress);
+ return () => document.removeEventListener('keydown', handleKeyPress);
+ }, [direction, gameRunning]);
+
+ useEffect(() => {
+ generateFood();
+ }, [generateFood]);
+
+ return (
+
+
🐍 贪吃蛇游戏
+
+
+ 得分: {score}
+
+
+
+
+ {gameOver && (
+
+ 💀 游戏结束!最终得分: {score}
+
+ )}
+
+
+ 使用方向键控制蛇的移动
+
+
+
+ {Array.from({ length: gameAreaSize * gameAreaSize }).map((_, index) => {
+ const x = index % gameAreaSize;
+ const y = Math.floor(index / gameAreaSize);
+
+ const isSnake = snake.some(segment => segment.x === x && segment.y === y);
+ const isFood = food.x === x && food.y === y;
+ const isHead = snake[0] && snake[0].x === x && snake[0].y === y;
+
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+// 主组件
+const InteractiveGames: React.FC = () => {
+ return (
+
+
🎮 交互式游戏演示
+
+ 通过小游戏学习React的状态管理、事件处理和副作用处理
+
+
+
+
+
+
+
+
+
+
🎯 游戏开发中的React概念
+
+ - 状态管理: 使用useState管理游戏状态(分数、位置、游戏状态等)
+ - 副作用处理: 使用useEffect处理游戏循环、键盘事件监听
+ - 事件处理: 处理鼠标点击、键盘输入等用户交互
+ - 条件渲染: 根据游戏状态显示不同的UI元素
+ - 列表渲染: 渲染游戏网格、卡片列表等重复元素
+ - 性能优化: 使用useCallback避免不必要的重新渲染
+
+
+
+ );
+};
+
+export default InteractiveGames;
\ No newline at end of file
diff --git a/src/examples/JSXExample.js b/src/examples/JSXExample.js
new file mode 100644
index 0000000..be2ecbd
--- /dev/null
+++ b/src/examples/JSXExample.js
@@ -0,0 +1,59 @@
+import React, { useState } from 'react';
+
+// JSX示例组件
+function JSXExample() {
+ // 状态管理
+ const [count, setCount] = useState(0);
+ const [name, setName] = useState('');
+
+ // 事件处理函数
+ const handleIncrement = () => {
+ setCount(count + 1);
+ };
+
+ const handleNameChange = (event) => {
+ setName(event.target.value);
+ };
+
+ // JSX语法示例
+ return (
+
+
JSX 示例
+
+ {/* 条件渲染 */}
+ {name &&
你好, {name}!
}
+
+ {/* 表单元素 */}
+
+
+ {/* 计数器 */}
+
+
+ {/* 列表渲染 */}
+
+ {[1, 2, 3, 4, 5].map(num => (
+ - 项目 {num}
+ ))}
+
+
+ {/* 样式对象 */}
+
5 ? 'lightgreen' : 'lightblue',
+ padding: '10px',
+ borderRadius: '5px'
+ }}>
+ {count > 5 ? '计数超过5了!' : '继续点击按钮'}
+
+
+ );
+}
+
+export default JSXExample;
\ No newline at end of file
diff --git a/src/examples/PerformanceDemo.tsx b/src/examples/PerformanceDemo.tsx
new file mode 100644
index 0000000..0f64c8b
--- /dev/null
+++ b/src/examples/PerformanceDemo.tsx
@@ -0,0 +1,389 @@
+import React, { useState, useMemo, useCallback, memo, useRef, useEffect } from 'react';
+
+// ========== React性能优化演示 ==========
+
+// 1. memo优化示例
+interface ExpensiveChildProps {
+ value: number;
+ onUpdate: (value: number) => void;
+}
+
+// 未优化的组件
+const ExpensiveChildNormal: React.FC = ({ value, onUpdate }) => {
+ console.log('🔄 普通组件重新渲染:', value);
+
+ // 模拟昂贵的计算
+ const expensiveCalculation = () => {
+ let result = 0;
+ for (let i = 0; i < 1000000; i++) {
+ result += i;
+ }
+ return result;
+ };
+
+ const calculatedValue = expensiveCalculation();
+
+ return (
+
+
❌ 未优化组件
+
值: {value}
+
计算结果: {calculatedValue}
+
+
+ );
+};
+
+// memo优化的组件
+const ExpensiveChildMemo = memo(({ value, onUpdate }) => {
+ console.log('✅ memo组件重新渲染:', value);
+
+ const expensiveCalculation = useMemo(() => {
+ console.log('💚 执行昂贵计算');
+ let result = 0;
+ for (let i = 0; i < 1000000; i++) {
+ result += i;
+ }
+ return result;
+ }, [value]);
+
+ return (
+
+
✅ memo优化组件
+
值: {value}
+
计算结果: {expensiveCalculation}
+
+
+ );
+});
+
+ExpensiveChildMemo.displayName = 'ExpensiveChildMemo';
+
+const MemoDemo: React.FC = () => {
+ const [count1, setCount1] = useState(0);
+ const [count2, setCount2] = useState(0);
+ const [otherState, setOtherState] = useState(0);
+
+ // 使用useCallback优化回调函数
+ const handleUpdate1 = useCallback((value: number) => {
+ setCount1(value);
+ }, []);
+
+ const handleUpdate2 = useCallback((value: number) => {
+ setCount2(value);
+ }, []);
+
+ return (
+
+
⚡ React.memo 性能优化演示
+
+
+
+
+
+
+
+
+
+ 💡 打开控制台查看重新渲染日志。点击"更新父组件状态"按钮时,memo组件不会重新渲染!
+
+
+ );
+};
+
+// 2. 虚拟滚动演示
+interface VirtualListProps {
+ items: string[];
+ itemHeight: number;
+ containerHeight: number;
+}
+
+const VirtualList: React.FC = ({ items, itemHeight, containerHeight }) => {
+ const [scrollTop, setScrollTop] = useState(0);
+
+ const visibleStart = Math.floor(scrollTop / itemHeight);
+ const visibleEnd = Math.min(
+ visibleStart + Math.ceil(containerHeight / itemHeight) + 1,
+ items.length
+ );
+
+ const visibleItems = items.slice(visibleStart, visibleEnd);
+ const totalHeight = items.length * itemHeight;
+ const offsetY = visibleStart * itemHeight;
+
+ return (
+
+
📜 虚拟滚动演示
+
总共 {items.length} 个项目,只渲染可见的 {visibleItems.length} 个
+
+
setScrollTop(e.currentTarget.scrollTop)}
+ >
+
+
+ {visibleItems.map((item, index) => (
+
+ 第 {visibleStart + index + 1} 项: {item}
+
+ ))}
+
+
+
+
+ );
+};
+
+// 3. 防抖搜索演示
+const useDebounce = (value: T, delay: number): T => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+};
+
+const DebounceSearchDemo: React.FC = () => {
+ const [searchTerm, setSearchTerm] = useState('');
+ const [searchCount, setSearchCount] = useState(0);
+ const debouncedSearchTerm = useDebounce(searchTerm, 500);
+
+ useEffect(() => {
+ if (debouncedSearchTerm) {
+ setSearchCount(prev => prev + 1);
+ console.log('🔍 执行搜索:', debouncedSearchTerm);
+ }
+ }, [debouncedSearchTerm]);
+
+ return (
+
+
🔍 防抖搜索演示
+
setSearchTerm(e.target.value)}
+ placeholder="输入搜索内容..."
+ style={{
+ padding: '8px 12px',
+ borderRadius: '4px',
+ border: '1px solid #ccc',
+ width: '200px'
+ }}
+ />
+
搜索词: {debouncedSearchTerm}
+
搜索次数: {searchCount}
+
+ 💡 输入会延迟500ms后触发搜索,减少不必要的API调用
+
+
+ );
+};
+
+// 4. 图片懒加载演示
+interface LazyImageProps {
+ src: string;
+ alt: string;
+ width: number;
+ height: number;
+}
+
+const LazyImage: React.FC = ({ src, alt, width, height }) => {
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [isInView, setIsInView] = useState(false);
+ const imgRef = useRef(null);
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setIsInView(true);
+ observer.disconnect();
+ }
+ },
+ { threshold: 0.1 }
+ );
+
+ if (imgRef.current) {
+ observer.observe(imgRef.current);
+ }
+
+ return () => observer.disconnect();
+ }, []);
+
+ return (
+
+ {!isInView ? (
+
📷 图片占位符
+ ) : !isLoaded ? (
+
🔄 加载中...
+ ) : null}
+
+ {isInView && (
+

setIsLoaded(true)}
+ style={{
+ width: '100%',
+ height: '100%',
+ objectFit: 'cover',
+ display: isLoaded ? 'block' : 'none'
+ }}
+ />
+ )}
+
+ );
+};
+
+const LazyLoadDemo: React.FC = () => {
+ const images = [
+ 'https://picsum.photos/200/150?random=1',
+ 'https://picsum.photos/200/150?random=2',
+ 'https://picsum.photos/200/150?random=3',
+ 'https://picsum.photos/200/150?random=4',
+ 'https://picsum.photos/200/150?random=5',
+ 'https://picsum.photos/200/150?random=6'
+ ];
+
+ return (
+
+
🖼️ 图片懒加载演示
+
向下滚动查看图片懒加载效果
+
+ {images.map((src, index) => (
+
+ ))}
+
+
+ );
+};
+
+// 主组件
+const PerformanceDemo: React.FC = () => {
+ // 生成大量测试数据
+ const largeDataSet = useMemo(() => {
+ return Array.from({ length: 10000 }, (_, i) => `数据项 ${i + 1}`);
+ }, []);
+
+ return (
+
+
⚡ React性能优化演示
+
+
+
+
+
+
+
+
⚡ 性能优化技巧总结
+
+ - React.memo: 避免不必要的组件重新渲染
+ - useMemo: 缓存昂贵的计算结果
+ - useCallback: 缓存函数引用,避免子组件重新渲染
+ - 虚拟滚动: 处理大量数据时只渲染可见项
+ - 防抖(Debounce): 减少频繁操作的执行次数
+ - 懒加载: 延迟加载非关键资源
+ - 代码分割: 使用React.lazy和Suspense按需加载
+
+
+
+ );
+};
+
+export default PerformanceDemo;
\ No newline at end of file
diff --git a/src/examples/ReactVsVueExamples.tsx b/src/examples/ReactVsVueExamples.tsx
new file mode 100644
index 0000000..9679abf
--- /dev/null
+++ b/src/examples/ReactVsVueExamples.tsx
@@ -0,0 +1,250 @@
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+
+// ========== React 设计思想示例 ==========
+
+// 1. 纯函数组件 - 相同输入永远产生相同输出
+interface UserProps {
+ name: string;
+ age: number;
+ isActive: boolean;
+}
+
+const PureUserCard: React.FC = ({ name, age, isActive }) => {
+ // 纯函数:给定相同的props,总是返回相同的JSX
+ return (
+
+
{name}
+
年龄: {age}
+
{isActive ? '🟢 在线' : '⚫ 离线'}
+
+ );
+};
+
+// 2. 单向数据流示例
+interface CounterProps {
+ value: number;
+ onIncrement: () => void;
+ onDecrement: () => void;
+}
+
+const Counter: React.FC = ({ value, onIncrement, onDecrement }) => {
+ // 数据只能从父组件流入,事件通过回调函数流出
+ return (
+
+
计数器: {value}
+
+ {value}
+
+
+ );
+};
+
+// 3. 不可变性 - 状态更新通过创建新对象
+interface TodoItem {
+ id: number;
+ text: string;
+ completed: boolean;
+}
+
+const TodoList: React.FC = () => {
+ const [todos, setTodos] = useState([
+ { id: 1, text: '学习React', completed: false },
+ { id: 2, text: '理解设计思想', completed: false }
+ ]);
+
+ // 不可变更新:创建新数组而不是修改原数组
+ const toggleTodo = useCallback((id: number) => {
+ setTodos(prevTodos =>
+ prevTodos.map(todo =>
+ todo.id === id
+ ? { ...todo, completed: !todo.completed } // 创建新对象
+ : todo
+ )
+ );
+ }, []);
+
+ const addTodo = useCallback((text: string) => {
+ const newTodo: TodoItem = {
+ id: Date.now(),
+ text,
+ completed: false
+ };
+ setTodos(prevTodos => [...prevTodos, newTodo]); // 创建新数组
+ }, []);
+
+ return (
+
+
待办事项 (不可变性示例)
+ {todos.map(todo => (
+
+ toggleTodo(todo.id)}
+ />
+
+ {todo.text}
+
+
+ ))}
+
+
+ );
+};
+
+// 4. 组合优于继承 - 通过组合构建复杂UI
+interface WithLoadingProps {
+ isLoading: boolean;
+ children: React.ReactNode;
+}
+
+const WithLoading: React.FC = ({ isLoading, children }) => {
+ if (isLoading) {
+ return 🔄 加载中...
;
+ }
+ return <>{children}>;
+};
+
+const DataFetcher: React.FC = () => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ // 模拟数据获取
+ setTimeout(() => {
+ setData(['React', 'Vue', 'Angular']);
+ setLoading(false);
+ }, 2000);
+ }, []);
+
+ return (
+
+
+
框架列表
+
+ {data.map((item, index) => (
+ - {item}
+ ))}
+
+
+
+ );
+};
+
+// 5. 记忆化和性能优化
+const ExpensiveCalculation: React.FC<{ numbers: number[] }> = ({ numbers }) => {
+ // 使用useMemo避免不必要的重计算
+ const sum = useMemo(() => {
+ console.log('🔄 重新计算总和...');
+ return numbers.reduce((acc, num) => acc + num, 0);
+ }, [numbers]);
+
+ return (
+
+
性能优化示例
+
数字列表: {numbers.join(', ')}
+
总和: {sum}
+
+ );
+};
+
+// 主组件 - 展示React设计思想
+const ReactVsVueExamples: React.FC = () => {
+ const [count, setCount] = useState(0);
+ const [numbers, setNumbers] = useState([1, 2, 3]);
+
+ // 回调函数 - 单向数据流
+ const handleIncrement = useCallback(() => {
+ setCount(prev => prev + 1);
+ }, []);
+
+ const handleDecrement = useCallback(() => {
+ setCount(prev => prev - 1);
+ }, []);
+
+ const addRandomNumber = useCallback(() => {
+ const randomNum = Math.floor(Math.random() * 100);
+ setNumbers(prev => [...prev, randomNum]);
+ }, []);
+
+ const users: UserProps[] = [
+ { name: '张三', age: 25, isActive: true },
+ { name: '李四', age: 30, isActive: false },
+ { name: '王五', age: 28, isActive: true }
+ ];
+
+ return (
+
+
🚀 React 设计思想示例
+
+
+
1. 纯函数组件
+
相同的props总是产生相同的输出:
+ {users.map((user, index) => (
+
+ ))}
+
+
+
+
2. 单向数据流
+
数据从父组件流向子组件,事件通过回调函数向上传递:
+
+
+
+
+
3. 不可变性
+
+
+
+
+
4. 组合优于继承
+
+
+
+
+
5. 性能优化 (useMemo)
+
+
+
+
+
+
💡 React设计思想总结
+
+ - 函数式编程:组件是纯函数,可预测且易测试
+ - 单向数据流:数据流向清晰,便于调试
+ - 不可变性:通过创建新对象来更新状态
+ - 组合式架构:通过组合小组件构建复杂UI
+ - 显式优化:开发者需要手动进行性能优化
+
+
+
+ );
+};
+
+export default ReactVsVueExamples;
\ No newline at end of file
diff --git a/src/examples/TSXExample.tsx b/src/examples/TSXExample.tsx
new file mode 100644
index 0000000..4f46837
--- /dev/null
+++ b/src/examples/TSXExample.tsx
@@ -0,0 +1,111 @@
+import React, { useState } from 'react';
+
+// 定义接口(类型)
+interface User {
+ id: number;
+ name: string;
+ email: string;
+ age: number;
+}
+
+interface Props {
+ title: string;
+ initialCount?: number; // 可选属性
+}
+
+// TSX示例组件
+const TSXExample: React.FC = ({ title, initialCount = 0 }) => {
+ // 带类型的状态管理
+ const [count, setCount] = useState(initialCount);
+ const [user, setUser] = useState(null);
+ const [users, setUsers] = useState([]);
+
+ // 带类型的事件处理函数
+ const handleIncrement = (): void => {
+ setCount(prevCount => prevCount + 1);
+ };
+
+ const handleAddUser = (): void => {
+ const newUser: User = {
+ id: users.length + 1,
+ name: `用户${users.length + 1}`,
+ email: `user${users.length + 1}@example.com`,
+ age: Math.floor(Math.random() * 50) + 18
+ };
+ setUsers(prevUsers => [...prevUsers, newUser]);
+ };
+
+ const handleSelectUser = (selectedUser: User): void => {
+ setUser(selectedUser);
+ };
+
+ // 输入框变化处理
+ const handleInputChange = (event: React.ChangeEvent): void => {
+ console.log(event.target.value);
+ };
+
+ return (
+
+
{title}
+
+ {/* 类型安全的计数器 */}
+
+
+ {/* 用户管理 */}
+
+
+
+ {/* 条件渲染当前选中的用户 */}
+ {user && (
+
+
选中的用户:
+
姓名: {user.name}
+
邮箱: {user.email}
+
年龄: {user.age}
+
+ )}
+
+ {/* 用户列表 */}
+
+ {users.map((userItem: User) => (
+ - handleSelectUser(userItem)}
+ style={{
+ cursor: 'pointer',
+ padding: '5px',
+ marginBottom: '5px',
+ backgroundColor: user?.id === userItem.id ? '#e7f3ff' : 'transparent'
+ }}
+ >
+ {userItem.name} ({userItem.age}岁)
+
+ ))}
+
+
+
+ {/* 类型安全的输入框 */}
+
+
+ );
+};
+
+export default TSXExample;
\ No newline at end of file
diff --git a/src/examples/VueStyleExamples.tsx b/src/examples/VueStyleExamples.tsx
new file mode 100644
index 0000000..878e725
--- /dev/null
+++ b/src/examples/VueStyleExamples.tsx
@@ -0,0 +1,297 @@
+import React, { useState, useEffect } from 'react';
+
+// ========== 模拟Vue设计思想的React实现 ==========
+// 注意:这里是用React语法来展示Vue的设计理念
+
+// 1. 模拟Vue的响应式数据绑定概念
+const VueStyleReactivity: React.FC = () => {
+ // 在Vue中,这种数据变化会自动触发视图更新,无需手动优化
+ const [message, setMessage] = useState('Hello Vue!');
+ const [count, setCount] = useState(0);
+
+ // Vue风格:数据变化自动反映到视图
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setMessage(e.target.value); // Vue中通过v-model实现双向绑定
+ };
+
+ return (
+
+
📱 响应式数据绑定 (Vue风格概念)
+
+ {/* 模拟Vue模板语法 */}
+
+
+
+
计数: {count}
+
+
+
+
+
💡 Vue设计理念体现:
+
+ - 数据变化自动更新视图 (响应式)
+ - 简单的双向数据绑定
+ - 声明式的事件处理
+
+
+
+ );
+};
+
+// 2. 模拟Vue的模板指令概念
+const VueStyleDirectives: React.FC = () => {
+ const [showElement, setShowElement] = useState(true);
+ const [items, setItems] = useState(['Apple', 'Banana', 'Orange']);
+ const [inputValue, setInputValue] = useState('');
+
+ const addItem = () => {
+ if (inputValue.trim()) {
+ setItems([...items, inputValue]);
+ setInputValue('');
+ }
+ };
+
+ return (
+
+
🎯 模板指令风格 (Vue概念)
+
+ {/* 模拟 v-if */}
+
+
+ {showElement && (
+
🎉 这个元素是条件渲染的!
+ )}
+
+
+ {/* 模拟 v-for */}
+
+
列表渲染 (类似v-for):
+
+ {items.map((item, index) => (
+ - {item}
+ ))}
+
+
+
+ setInputValue(e.target.value)}
+ placeholder="添加新项目"
+ />
+
+
+
+
+
+
💡 Vue模板语法特点:
+
+ - v-if: 条件渲染
+ - v-for: 列表渲染
+ - v-model: 双向数据绑定
+ - @click: 事件监听
+
+
+
+ );
+};
+
+// 3. 模拟Vue的计算属性概念
+const VueStyleComputed: React.FC = () => {
+ const [firstName, setFirstName] = useState('张');
+ const [lastName, setLastName] = useState('三');
+ const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
+
+ // 模拟Vue的computed属性 - 在Vue中这会自动缓存和响应式更新
+ const fullName = `${firstName} ${lastName}`; // Vue: computed: { fullName() { ... } }
+ const sum = numbers.reduce((acc, num) => acc + num, 0); // Vue会自动优化这种计算
+
+ return (
+
+
⚡ 计算属性概念 (Vue风格)
+
+
+
+ setFirstName(e.target.value)}
+ />
+
+
+
+
+ setLastName(e.target.value)}
+ />
+
+
+
+
全名 (计算属性): {fullName}
+
+
+
+
数字列表: {numbers.join(', ')}
+
总和 (计算属性): {sum}
+
+
+
+
+
💡 Vue计算属性特点:
+
+ - 基于依赖自动更新
+ - 自动缓存结果
+ - 声明式定义
+ - 无需手动优化
+
+
+
+ );
+};
+
+// 4. 模拟Vue的生命周期概念
+const VueStyleLifecycle: React.FC = () => {
+ const [data, setData] = useState('');
+ const [mounted, setMounted] = useState(false);
+
+ // 模拟Vue的mounted生命周期
+ useEffect(() => {
+ console.log('🔄 组件挂载了 (类似Vue的mounted)');
+ setMounted(true);
+
+ // 模拟数据获取
+ setTimeout(() => {
+ setData('从API获取的数据');
+ }, 1000);
+
+ // 模拟Vue的beforeUnmount
+ return () => {
+ console.log('🔄 组件即将卸载 (类似Vue的beforeUnmount)');
+ };
+ }, []);
+
+ return (
+
+
🔄 生命周期概念 (Vue风格)
+
+
+
挂载状态: {mounted ? '✅ 已挂载' : '⏳ 挂载中'}
+
数据: {data || '⏳ 加载中...'}
+
+
+
+
💡 Vue生命周期特点:
+
+ - created: 实例创建后
+ - mounted: DOM挂载后
+ - updated: 数据更新后
+ - beforeUnmount: 卸载前
+
+
+
+ );
+};
+
+// 5. 模拟Vue的组件通信概念
+interface ChildProps {
+ message: string;
+ onUpdateMessage: (newMessage: string) => void;
+}
+
+const VueStyleChild: React.FC = ({ message, onUpdateMessage }) => {
+ return (
+
+
子组件
+
从父组件接收: {message}
+
+
+ );
+};
+
+const VueStyleParent: React.FC = () => {
+ const [parentMessage, setParentMessage] = useState('来自父组件的消息');
+
+ return (
+
+
👨👩👧👦 组件通信 (Vue风格)
+
+
+
父组件
+
当前消息: {parentMessage}
+
+
+
+
+
💡 Vue组件通信特点:
+
+ - props down: 父到子传递数据
+ - events up: 子到父发送事件
+ - v-model: 双向绑定语法糖
+ - provide/inject: 跨层级通信
+
+
+
+ );
+};
+
+// 主组件 - 展示Vue设计思想概念
+const VueStyleExamples: React.FC = () => {
+ return (
+
+
🌟 Vue设计思想概念展示
+
+ 注意:以下示例用React语法展示Vue的设计理念,
+ 帮助理解两个框架的不同思维方式。
+
+
+
+
+
+
+
+
+
+
🌟 Vue设计思想总结
+
+ - 响应式系统:数据变化自动更新视图
+ - 模板语法:声明式的HTML扩展语法
+ - 渐进式:可以逐步集成到现有项目
+ - 约定优于配置:提供合理的默认值
+ - 开发者友好:降低学习曲线,提高开发效率
+ - 自动优化:框架层面的性能优化
+
+
+
+ );
+};
+
+export default VueStyleExamples;
\ No newline at end of file
diff --git a/src/index.js b/src/index.tsx
similarity index 81%
rename from src/index.js
rename to src/index.tsx
index d563c0f..0124aab 100644
--- a/src/index.js
+++ b/src/index.tsx
@@ -4,7 +4,9 @@ import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
-const root = ReactDOM.createRoot(document.getElementById('root'));
+const root = ReactDOM.createRoot(
+ document.getElementById('root') as HTMLElement
+);
root.render(
@@ -14,4 +16,4 @@ root.render(
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
+reportWebVitals();
\ No newline at end of file
diff --git a/src/vue-vs-react-comparison.md b/src/vue-vs-react-comparison.md
new file mode 100644
index 0000000..f2c643d
--- /dev/null
+++ b/src/vue-vs-react-comparison.md
@@ -0,0 +1,275 @@
+# Vue vs React 设计思想对比
+
+## 🎯 核心设计理念
+
+### React 设计思想
+**"函数式编程 + 单向数据流"**
+
+React将UI视为状态的函数:`UI = f(state)`
+
+**核心理念:**
+- 组件是纯函数,给定相同的props和state,永远返回相同的结果
+- 数据单向流动,从父组件流向子组件
+- 不可变性(Immutability)- 状态变化通过创建新对象实现
+- 声明式编程 - 描述"是什么"而不是"怎么做"
+
+### Vue 设计思想
+**"渐进式框架 + 响应式系统"**
+
+Vue强调渐进式增强和开发者友好性
+
+**核心理念:**
+- 响应式数据绑定 - 数据变化自动更新视图
+- 模板语法 - 更接近HTML的声明式模板
+- 渐进式 - 可以部分采用,不需要重写整个应用
+- 约定优于配置 - 提供合理的默认值和约定
+
+## 🏗️ 架构设计对比
+
+### React 架构特点
+
+```jsx
+// React: 函数式组件 + Hooks
+function UserProfile({ userId }) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ fetchUser(userId).then(userData => {
+ setUser(userData);
+ setLoading(false);
+ });
+ }, [userId]);
+
+ if (loading) return 加载中...
;
+
+ return (
+
+
{user.name}
+
{user.email}
+
+ );
+}
+```
+
+**特点:**
+- 显式状态管理
+- 手动优化渲染
+- 函数式编程范式
+- 灵活但需要更多样板代码
+
+### Vue 架构特点
+
+```vue
+
+
+ 加载中...
+
+
{{ user.name }}
+
{{ user.email }}
+
+
+
+
+```
+
+**特点:**
+- 自动响应式更新
+- 模板语法直观
+- 约定式结构
+- 更少的样板代码
+
+## 📊 具体差异对比
+
+| 维度 | React | Vue |
+|------|-------|-----|
+| **学习曲线** | 陡峭,需要理解函数式编程 | 平缓,更接近传统前端开发 |
+| **数据绑定** | 单向数据流,手动处理 | 双向数据绑定,自动响应式 |
+| **模板语法** | JSX (JavaScript in HTML) | 模板语法 (HTML + 指令) |
+| **状态管理** | 显式,需要useState/Redux | 隐式,响应式系统 |
+| **组件通信** | Props + 回调函数 | Props + 事件 + v-model |
+| **性能优化** | 手动优化 (memo, useMemo) | 自动优化 (响应式依赖追踪) |
+| **生态系统** | 庞大但分散 | 官方维护,更集中 |
+| **灵活性** | 极高,函数式编程 | 中等,约定优于配置 |
+
+## 🔄 数据流设计
+
+### React: 单向数据流
+
+```jsx
+// 父组件
+function App() {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
+
+ );
+}
+
+// 子组件
+function Counter({ value, onChange }) {
+ return (
+
+
计数: {value}
+
+
+ );
+}
+```
+
+**特点:**
+- 数据只能从父组件流向子组件
+- 子组件通过回调函数通知父组件
+- 数据流向清晰可追踪
+
+### Vue: 响应式双向绑定
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
计数: {{ modelValue }}
+
+
+
+
+
+```
+
+**特点:**
+- 支持双向数据绑定
+- 响应式系统自动追踪依赖
+- 语法糖简化父子组件通信
+
+## 🎨 开发体验对比
+
+### React 开发特点
+
+**优势:**
+- 函数式编程,逻辑清晰
+- 组合优于继承
+- 测试友好
+- 社区生态丰富
+
+**挑战:**
+- 需要理解函数式编程概念
+- 状态管理复杂项目需要额外工具
+- 性能优化需要手动处理
+
+### Vue 开发特点
+
+**优势:**
+- 学习成本低,易于上手
+- 官方工具链完整
+- 模板语法直观
+- 自动性能优化
+
+**挑战:**
+- 大型项目可能缺乏约束
+- 模板语法限制了一些高级用法
+- TypeScript支持相对较晚
+
+## 🚀 适用场景
+
+### 选择React的场景:
+- **大型复杂应用**:需要精细控制和性能优化
+- **团队有函数式编程经验**:能充分利用React的设计理念
+- **需要丰富生态**:React社区更大,第三方库更多
+- **移动端开发**:React Native生态成熟
+
+### 选择Vue的场景:
+- **快速原型开发**:Vue的学习曲线更平缓
+- **团队技术栈偏传统**:更容易从jQuery等过渡
+- **中小型项目**:Vue的约定能提高开发效率
+- **注重开发体验**:Vue的工具链更完整统一
+
+## 🔮 设计思想演进
+
+### React的演进:
+1. **类组件时代**:面向对象编程
+2. **Hooks时代**:函数式编程回归
+3. **并发模式**:性能和用户体验优化
+4. **Server Components**:全栈React
+
+### Vue的演进:
+1. **Vue 1.x**:简单的数据绑定
+2. **Vue 2.x**:组件化 + 虚拟DOM
+3. **Vue 3.x**:Composition API + 更好的TypeScript支持
+4. **渐进式增强**:从简单到复杂的平滑过渡
+
+## 💭 设计哲学总结
+
+### React哲学:
+> "学会一次,随处编写" - 通过函数式编程原则构建可预测的UI
+
+- 显式优于隐式
+- 组合优于继承
+- 不可变性带来可预测性
+- 单一数据源
+
+### Vue哲学:
+> "渐进式框架" - 让开发者能够渐进式地采用和学习
+
+- 约定优于配置
+- 开发者体验优先
+- 性能优化自动化
+- 灵活性与易用性平衡
+
+两个框架都很优秀,选择取决于团队背景、项目需求和个人偏好。React更适合大型团队和复杂项目,Vue更适合快速开发和团队技术栈迁移。
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..42b14e5
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "es6"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "src"
+ ]
+}
\ No newline at end of file