From e287f6c9d223e59b76d631c1b982634edfd5c817 Mon Sep 17 00:00:00 2001 From: Guwan Date: Mon, 4 Aug 2025 23:58:00 +0800 Subject: [PATCH] fix: first commit --- .gitignore | 2 + package-lock.json | 278 ++++++++++++++- package.json | 5 + src/App.css | 170 +++++++-- src/App.js | 25 -- src/App.tsx | 198 +++++++++++ src/concepts-explanation.md | 178 ++++++++++ src/examples/AdvancedConcepts.tsx | 405 +++++++++++++++++++++ src/examples/InteractiveGames.tsx | 528 ++++++++++++++++++++++++++++ src/examples/JSXExample.js | 59 ++++ src/examples/PerformanceDemo.tsx | 389 ++++++++++++++++++++ src/examples/ReactVsVueExamples.tsx | 250 +++++++++++++ src/examples/TSXExample.tsx | 111 ++++++ src/examples/VueStyleExamples.tsx | 297 ++++++++++++++++ src/{index.js => index.tsx} | 6 +- src/vue-vs-react-comparison.md | 275 +++++++++++++++ tsconfig.json | 26 ++ 17 files changed, 3145 insertions(+), 57 deletions(-) delete mode 100644 src/App.js create mode 100644 src/App.tsx create mode 100644 src/concepts-explanation.md create mode 100644 src/examples/AdvancedConcepts.tsx create mode 100644 src/examples/InteractiveGames.tsx create mode 100644 src/examples/JSXExample.js create mode 100644 src/examples/PerformanceDemo.tsx create mode 100644 src/examples/ReactVsVueExamples.tsx create mode 100644 src/examples/TSXExample.tsx create mode 100644 src/examples/VueStyleExamples.tsx rename src/{index.js => index.tsx} (81%) create mode 100644 src/vue-vs-react-comparison.md create mode 100644 tsconfig.json 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 ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -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()} +
+ + {/* 页脚 */} +
+
+ 🛠️ 技术栈: React 19 + TypeScript + 📚 包含: 基础语法 + 设计模式 + 性能优化 + 🎯 目标: 全面掌握React开发 +
+
+ 💡 学习建议: 按顺序学习各个章节,动手实践每个示例,观察控制台输出 +
+
+
+ ); +}; + +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 ( +
+

{name}

+

年龄: {age}

+
+ ); +} +``` + +**TSX:** +```tsx +interface UserCardProps { + name: string; + age: number; +} + +const UserCard: React.FC = ({ name, age }) => { + return ( +
+

{name}

+

年龄: {age}

+
+ ); +}; +``` + +### 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 }) => ( +
+

鼠标位置: ({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}!

} + + {/* 表单元素 */} + + + {/* 计数器 */} +
+

计数: {count}

+ +
+ + {/* 列表渲染 */} +
    + {[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 && ( + {alt} 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}

+ + {/* 类型安全的计数器 */} +
+

计数: {count}

+ +
+ + {/* 用户管理 */} +
+ + + {/* 条件渲染当前选中的用户 */} + {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模板语法 */} +
+

消息: {message}

+ +
+ +
+

计数: {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 + + + + +``` + +**特点:** +- 自动响应式更新 +- 模板语法直观 +- 约定式结构 +- 更少的样板代码 + +## 📊 具体差异对比 + +| 维度 | 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 + + + + + + + + + +``` + +**特点:** +- 支持双向数据绑定 +- 响应式系统自动追踪依赖 +- 语法糖简化父子组件通信 + +## 🎨 开发体验对比 + +### 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