fix: first commit

This commit is contained in:
Guwan 2025-08-04 23:58:00 +08:00
parent 7fcd6724e1
commit e287f6c9d2
17 changed files with 3145 additions and 57 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea

278
package-lock.json generated
View File

@ -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": {

View File

@ -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": {

View File

@ -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;
}

View File

@ -1,25 +0,0 @@
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

198
src/App.tsx Normal file
View File

@ -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<ViewType>('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 (
<div>
<h2 style={{ textAlign: 'center', color: '#333', marginBottom: '30px' }}>
📚 JSX TSX
</h2>
<JSXExample />
<TSXExample title="TypeScript JSX 示例" initialCount={10} />
</div>
);
case 'react-vue':
return (
<div>
<h2 style={{ textAlign: 'center', color: '#333', marginBottom: '30px' }}>
React vs Vue
</h2>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(500px, 1fr))',
gap: '20px'
}}>
<ReactVsVueExamples />
<VueStyleExamples />
</div>
<div style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#f8f9fa',
borderRadius: '10px',
border: '2px solid #dee2e6'
}}>
<h3>🎯 </h3>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '20px'
}}>
<div>
<h4 style={{ color: '#007bff' }}>🚀 React </h4>
<ul style={{ textAlign: 'left' }}>
<li><strong></strong>UI = f(state)</li>
<li><strong></strong></li>
<li><strong></strong>UI</li>
<li><strong></strong></li>
<li><strong></strong></li>
</ul>
</div>
<div>
<h4 style={{ color: '#4FC08D' }}>🌟 Vue </h4>
<ul style={{ textAlign: 'left' }}>
<li><strong></strong></li>
<li><strong></strong></li>
<li><strong></strong></li>
<li><strong></strong>线</li>
<li><strong></strong>HTML的声明式语法</li>
</ul>
</div>
</div>
</div>
</div>
);
case 'advanced':
return <AdvancedConcepts />;
case 'performance':
return <PerformanceDemo />;
case 'games':
return <InteractiveGames />;
default:
return null;
}
};
return (
<div className="App">
<header className="App-header" style={{ padding: '20px 20px 10px 20px' }}>
<h1 style={{ margin: '0 0 10px 0' }}>React </h1>
<p style={{ margin: '0 0 20px 0', color: '#666' }}>
JSXTSX
</p>
{/* 导航按钮 */}
<nav style={{
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
justifyContent: 'center',
marginBottom: '10px'
}}>
{navigationItems.map((item) => (
<button
key={item.key}
onClick={() => setCurrentView(item.key as ViewType)}
style={{
padding: '12px 20px',
backgroundColor: currentView === item.key ? '#007bff' : '#6c757d',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
transition: 'all 0.3s ease',
transform: currentView === item.key ? 'translateY(-2px)' : 'none',
boxShadow: currentView === item.key
? '0 4px 12px rgba(0, 123, 255, 0.3)'
: '0 2px 4px rgba(0, 0, 0, 0.1)'
}}
onMouseEnter={(e) => {
if (currentView !== item.key) {
e.currentTarget.style.backgroundColor = '#5a6268';
}
}}
onMouseLeave={(e) => {
if (currentView !== item.key) {
e.currentTarget.style.backgroundColor = '#6c757d';
}
}}
>
{item.emoji} {item.label}
</button>
))}
</nav>
{/* 视图描述 */}
<div style={{
padding: '10px 20px',
backgroundColor: '#e9ecef',
borderRadius: '6px',
marginBottom: '20px',
fontSize: '14px',
color: '#495057'
}}>
{currentView === 'jsx-tsx' && '学习JSX和TSX的语法差异、类型安全和最佳实践'}
{currentView === 'react-vue' && '对比React和Vue的设计理念、架构特点和适用场景'}
{currentView === 'advanced' && '掌握Context、自定义Hooks、HOC、Render Props等高级模式'}
{currentView === 'performance' && '学习React性能优化技巧memo、虚拟滚动、懒加载等'}
{currentView === 'games' && '通过交互游戏实践React状态管理和事件处理'}
</div>
</header>
<main style={{ padding: '0 20px 40px 20px', minHeight: 'calc(100vh - 200px)' }}>
{renderContent()}
</main>
{/* 页脚 */}
<footer style={{
padding: '20px',
backgroundColor: '#f8f9fa',
borderTop: '1px solid #dee2e6',
textAlign: 'center',
color: '#6c757d',
fontSize: '14px'
}}>
<div style={{ marginBottom: '10px' }}>
<span style={{ marginRight: '20px' }}>🛠 技术栈: React 19 + TypeScript</span>
<span style={{ marginRight: '20px' }}>📚 包含: 基础语法 + + </span>
<span>🎯 目标: 全面掌握React开发</span>
</div>
<div>
💡 <strong></strong>:
</div>
</footer>
</div>
);
};
export default App;

178
src/concepts-explanation.md Normal file
View File

@ -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 <div>Hello World</div>;
}
```
**TSX (TypeScript):**
```tsx
const MyComponent: React.FC = () => {
return <div>Hello World</div>;
};
```
### 2. Props类型定义
**JSX:**
```jsx
function UserCard({ name, age }) {
return (
<div>
<h3>{name}</h3>
<p>年龄: {age}</p>
</div>
);
}
```
**TSX:**
```tsx
interface UserCardProps {
name: string;
age: number;
}
const UserCard: React.FC<UserCardProps> = ({ name, age }) => {
return (
<div>
<h3>{name}</h3>
<p>年龄: {age}</p>
</div>
);
};
```
### 3. 状态管理
**JSX:**
```jsx
const [count, setCount] = useState(0);
```
**TSX:**
```tsx
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
```
### 4. 事件处理
**JSX:**
```jsx
const handleClick = (event) => {
console.log(event.target.value);
};
```
**TSX:**
```tsx
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
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
}
}
```

View File

@ -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<ThemeContextType | null>(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 (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};
const ThemedComponent: React.FC = () => {
const themeContext = useContext(ThemeContext);
if (!themeContext) {
throw new Error('ThemedComponent must be used within ThemeProvider');
}
const { theme, toggleTheme } = themeContext;
return (
<div style={{
padding: '15px',
backgroundColor: theme === 'light' ? '#ffffff' : '#2d3748',
color: theme === 'light' ? '#000000' : '#ffffff',
border: `2px solid ${theme === 'light' ? '#e2e8f0' : '#4a5568'}`,
borderRadius: '8px',
margin: '10px'
}}>
<h4>🎨 Context API </h4>
<p>: {theme === 'light' ? '☀️ 浅色' : '🌙 深色'}</p>
<button
onClick={toggleTheme}
style={{
padding: '8px 16px',
backgroundColor: theme === 'light' ? '#4299e1' : '#63b3ed',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
</button>
</div>
);
};
// 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 = <T,>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T>(() => {
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 (
<div style={{
padding: '15px',
border: '2px solid #38a169',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🎣 Hooks示例</h4>
<div style={{ marginBottom: '15px' }}>
<h5>Hook:</h5>
<p>: {count}</p>
<button onClick={increment} style={{ margin: '2px', padding: '5px 10px' }}>+</button>
<button onClick={decrement} style={{ margin: '2px', padding: '5px 10px' }}>-</button>
<button onClick={reset} style={{ margin: '2px', padding: '5px 10px' }}></button>
</div>
<div>
<h5>Hook:</h5>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="输入你的名字会保存到localStorage"
style={{ padding: '5px', marginRight: '10px' }}
/>
<p>: {name}</p>
</div>
</div>
);
};
// 3. 高阶组件 (HOC) 示例
interface WithLoadingProps {
isLoading: boolean;
}
const withLoading = <P extends object>(Component: React.ComponentType<P>) => {
return (props: P & WithLoadingProps) => {
const { isLoading, ...otherProps } = props;
if (isLoading) {
return (
<div style={{
padding: '20px',
textAlign: 'center',
backgroundColor: '#f7fafc',
border: '1px dashed #cbd5e0',
borderRadius: '4px'
}}>
<div>🔄 ...</div>
</div>
);
}
return <Component {...(otherProps as P)} />;
};
};
const DataDisplay: React.FC<{ data: string[] }> = ({ data }) => (
<div style={{ padding: '10px' }}>
<h5>:</h5>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
const DataDisplayWithLoading = withLoading(DataDisplay);
const HOCDemo: React.FC = () => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<string[]>(['项目1', '项目2', '项目3']);
const loadData = () => {
setIsLoading(true);
setTimeout(() => {
setData(['新数据1', '新数据2', '新数据3', '新数据4']);
setIsLoading(false);
}, 2000);
};
return (
<div style={{
padding: '15px',
border: '2px solid #805ad5',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🔗 (HOC) </h4>
<button
onClick={loadData}
style={{
padding: '8px 16px',
backgroundColor: '#805ad5',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginBottom: '10px'
}}
>
</button>
<DataDisplayWithLoading isLoading={isLoading} data={data} />
</div>
);
};
// 4. Render Props 模式示例
interface MouseTrackerProps {
children: (position: { x: number; y: number }) => React.ReactNode;
}
const MouseTracker: React.FC<MouseTrackerProps> = ({ 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 (
<div style={{
padding: '15px',
border: '2px solid #ed8936',
borderRadius: '8px',
margin: '10px',
minHeight: '100px'
}}>
<h4>🖱 Render Props </h4>
<MouseTracker>
{({ x, y }) => (
<div>
<p>: ({x}, {y})</p>
<div
style={{
position: 'absolute',
left: x - 10,
top: y - 10,
width: '20px',
height: '20px',
backgroundColor: '#ed8936',
borderRadius: '50%',
pointerEvents: 'none',
transition: 'all 0.1s ease'
}}
/>
</div>
)}
</MouseTracker>
</div>
);
};
// 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 (
<div style={{
padding: '20px',
backgroundColor: '#fed7d7',
border: '2px solid #e53e3e',
borderRadius: '8px',
color: '#742a2a'
}}>
<h4> </h4>
<p></p>
<button
onClick={() => this.setState({ hasError: false })}
style={{
padding: '5px 10px',
backgroundColor: '#e53e3e',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
</button>
</div>
);
}
return this.props.children;
}
}
const BuggyComponent: React.FC<{ shouldThrow: boolean }> = ({ shouldThrow }) => {
if (shouldThrow) {
throw new Error('这是一个故意的错误!');
}
return <div> </div>;
};
const ErrorBoundaryDemo: React.FC = () => {
const [shouldThrow, setShouldThrow] = useState(false);
return (
<div style={{
padding: '15px',
border: '2px solid #e53e3e',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🛡 </h4>
<button
onClick={() => setShouldThrow(!shouldThrow)}
style={{
padding: '8px 16px',
backgroundColor: shouldThrow ? '#38a169' : '#e53e3e',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginBottom: '10px'
}}
>
{shouldThrow ? '修复组件' : '触发错误'}
</button>
<ErrorBoundary>
<BuggyComponent shouldThrow={shouldThrow} />
</ErrorBoundary>
</div>
);
};
// 主组件
const AdvancedConcepts: React.FC = () => {
return (
<div style={{ padding: '20px' }}>
<h2>🚀 React高级概念演示</h2>
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
<CustomHooksDemo />
<HOCDemo />
<RenderPropsDemo />
<ErrorBoundaryDemo />
<div style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#ebf8ff',
borderRadius: '10px',
border: '2px solid #3182ce'
}}>
<h3>💡 </h3>
<ul style={{ textAlign: 'left', lineHeight: '1.6' }}>
<li><strong>Context API</strong>: prop drilling</li>
<li><strong>Hooks</strong>: </li>
<li><strong>(HOC)</strong>: </li>
<li><strong>Render Props</strong>: prop共享代码</li>
<li><strong></strong>: </li>
</ul>
</div>
</div>
);
};
export default AdvancedConcepts;

View File

@ -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<Card[]>([]);
const [flippedCards, setFlippedCards] = useState<number[]>([]);
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 (
<div style={{
padding: '15px',
border: '2px solid #9f7aea',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🧠 </h4>
<div style={{ marginBottom: '15px' }}>
<span style={{ marginRight: '20px' }}>: {score}</span>
<span style={{ marginRight: '20px' }}>: {moves}</span>
<button
onClick={initializeGame}
style={{
padding: '5px 10px',
backgroundColor: '#9f7aea',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
</button>
</div>
{gameComplete && (
<div style={{
padding: '10px',
backgroundColor: '#c6f6d5',
borderRadius: '4px',
marginBottom: '15px',
textAlign: 'center'
}}>
🎉 {moves} {score}
</div>
)}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '8px',
maxWidth: '320px'
}}>
{cards.map((card) => (
<div
key={card.id}
onClick={() => 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 : '?'}
</div>
))}
</div>
</div>
);
};
// 2. 井字棋游戏
type Player = 'X' | 'O' | null;
const TicTacToe: React.FC = () => {
const [board, setBoard] = useState<Player[]>(Array(9).fill(null));
const [currentPlayer, setCurrentPlayer] = useState<'X' | 'O'>('X');
const [winner, setWinner] = useState<Player>(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 (
<div style={{
padding: '15px',
border: '2px solid #4299e1',
borderRadius: '8px',
margin: '10px'
}}>
<h4> </h4>
<div style={{ marginBottom: '15px' }}>
{gameOver ? (
<div>
{winner ? `🎉 玩家 ${winner} 获胜!` : '🤝 平局!'}
</div>
) : (
<div>: {currentPlayer}</div>
)}
<button
onClick={resetGame}
style={{
padding: '5px 10px',
backgroundColor: '#4299e1',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginLeft: '10px'
}}
>
</button>
</div>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '4px',
width: '180px',
height: '180px'
}}>
{board.map((cell, index) => (
<div
key={index}
onClick={() => 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}
</div>
))}
</div>
</div>
);
};
// 3. 简单贪吃蛇游戏
interface Position {
x: number;
y: number;
}
const SnakeGame: React.FC = () => {
const [snake, setSnake] = useState<Position[]>([{ x: 10, y: 10 }]);
const [food, setFood] = useState<Position>({ x: 5, y: 5 });
const [direction, setDirection] = useState<Position>({ 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<number | null>(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 (
<div style={{
padding: '15px',
border: '2px solid #38a169',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🐍 </h4>
<div style={{ marginBottom: '15px' }}>
<span style={{ marginRight: '20px' }}>: {score}</span>
<button
onClick={startGame}
disabled={gameRunning || gameOver}
style={{
padding: '5px 10px',
backgroundColor: gameRunning ? '#cbd5e0' : '#38a169',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: gameRunning ? 'not-allowed' : 'pointer',
marginRight: '10px'
}}
>
</button>
<button
onClick={resetGame}
style={{
padding: '5px 10px',
backgroundColor: '#e53e3e',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
</button>
</div>
{gameOver && (
<div style={{
padding: '10px',
backgroundColor: '#fed7d7',
borderRadius: '4px',
marginBottom: '15px',
textAlign: 'center'
}}>
💀 : {score}
</div>
)}
<p style={{ fontSize: '14px', color: '#666' }}>
使
</p>
<div style={{
display: 'grid',
gridTemplateColumns: `repeat(${gameAreaSize}, 1fr)`,
gap: '1px',
width: '300px',
height: '300px',
backgroundColor: '#2d3748',
padding: '2px'
}}>
{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 (
<div
key={index}
style={{
backgroundColor: isSnake
? (isHead ? '#e53e3e' : '#38a169')
: isFood
? '#ed8936'
: '#f7fafc',
transition: 'background-color 0.1s ease'
}}
/>
);
})}
</div>
</div>
);
};
// 主组件
const InteractiveGames: React.FC = () => {
return (
<div style={{ padding: '20px' }}>
<h2>🎮 </h2>
<p style={{ color: '#666', marginBottom: '20px' }}>
React的状态管理
</p>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
gap: '20px'
}}>
<MemoryGame />
<TicTacToe />
<SnakeGame />
</div>
<div style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#fef5e7',
borderRadius: '10px',
border: '2px solid #ed8936'
}}>
<h3>🎯 React概念</h3>
<ul style={{ textAlign: 'left', lineHeight: '1.6' }}>
<li><strong></strong>: 使useState管理游戏状态</li>
<li><strong></strong>: 使useEffect处理游戏循环</li>
<li><strong></strong>: </li>
<li><strong></strong>: UI元素</li>
<li><strong></strong>: </li>
<li><strong></strong>: 使useCallback避免不必要的重新渲染</li>
</ul>
</div>
</div>
);
};
export default InteractiveGames;

View File

@ -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 (
<div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
<h2>JSX 示例</h2>
{/* 条件渲染 */}
{name && <p>你好, {name}!</p>}
{/* 表单元素 */}
<input
type="text"
placeholder="输入你的名字"
value={name}
onChange={handleNameChange}
/>
{/* 计数器 */}
<div>
<p>计数: {count}</p>
<button onClick={handleIncrement}>增加</button>
</div>
{/* 列表渲染 */}
<ul>
{[1, 2, 3, 4, 5].map(num => (
<li key={num}>项目 {num}</li>
))}
</ul>
{/* 样式对象 */}
<div style={{
backgroundColor: count > 5 ? 'lightgreen' : 'lightblue',
padding: '10px',
borderRadius: '5px'
}}>
{count > 5 ? '计数超过5了' : '继续点击按钮'}
</div>
</div>
);
}
export default JSXExample;

View File

@ -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<ExpensiveChildProps> = ({ 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 (
<div style={{
padding: '10px',
border: '2px solid #e53e3e',
borderRadius: '8px',
margin: '5px',
backgroundColor: '#fed7d7'
}}>
<h5> </h5>
<p>: {value}</p>
<p>: {calculatedValue}</p>
<button onClick={() => onUpdate(value + 1)}></button>
</div>
);
};
// memo优化的组件
const ExpensiveChildMemo = memo<ExpensiveChildProps>(({ 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 (
<div style={{
padding: '10px',
border: '2px solid #38a169',
borderRadius: '8px',
margin: '5px',
backgroundColor: '#c6f6d5'
}}>
<h5> memo优化组件</h5>
<p>: {value}</p>
<p>: {expensiveCalculation}</p>
<button onClick={() => onUpdate(value + 1)}></button>
</div>
);
});
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 (
<div style={{
padding: '15px',
border: '2px solid #3182ce',
borderRadius: '8px',
margin: '10px'
}}>
<h4> React.memo </h4>
<button
onClick={() => setOtherState(otherState + 1)}
style={{
padding: '8px 16px',
backgroundColor: '#3182ce',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginBottom: '10px'
}}
>
() - {otherState}
</button>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
<ExpensiveChildNormal value={count1} onUpdate={handleUpdate1} />
<ExpensiveChildMemo value={count2} onUpdate={handleUpdate2} />
</div>
<p style={{ fontSize: '14px', color: '#666', marginTop: '10px' }}>
💡 "更新父组件状态"memo组件不会重新渲染
</p>
</div>
);
};
// 2. 虚拟滚动演示
interface VirtualListProps {
items: string[];
itemHeight: number;
containerHeight: number;
}
const VirtualList: React.FC<VirtualListProps> = ({ 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 (
<div style={{
padding: '15px',
border: '2px solid #805ad5',
borderRadius: '8px',
margin: '10px'
}}>
<h4>📜 </h4>
<p> {items.length} {visibleItems.length} </p>
<div
style={{
height: containerHeight,
overflow: 'auto',
border: '1px solid #ccc',
position: 'relative'
}}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div
key={visibleStart + index}
style={{
height: itemHeight,
padding: '10px',
borderBottom: '1px solid #eee',
display: 'flex',
alignItems: 'center',
backgroundColor: (visibleStart + index) % 2 === 0 ? '#f9f9f9' : 'white'
}}
>
{visibleStart + index + 1} : {item}
</div>
))}
</div>
</div>
</div>
</div>
);
};
// 3. 防抖搜索演示
const useDebounce = <T,>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(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 (
<div style={{
padding: '15px',
border: '2px solid #ed8936',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🔍 </h4>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="输入搜索内容..."
style={{
padding: '8px 12px',
borderRadius: '4px',
border: '1px solid #ccc',
width: '200px'
}}
/>
<p>: {debouncedSearchTerm}</p>
<p>: {searchCount}</p>
<p style={{ fontSize: '14px', color: '#666' }}>
💡 500ms后触发搜索API调用
</p>
</div>
);
};
// 4. 图片懒加载演示
interface LazyImageProps {
src: string;
alt: string;
width: number;
height: number;
}
const LazyImage: React.FC<LazyImageProps> = ({ src, alt, width, height }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef<HTMLDivElement>(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 (
<div
ref={imgRef}
style={{
width,
height,
backgroundColor: '#f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '10px',
border: '1px solid #ddd',
position: 'relative'
}}
>
{!isInView ? (
<div>📷 </div>
) : !isLoaded ? (
<div>🔄 ...</div>
) : null}
{isInView && (
<img
src={src}
alt={alt}
onLoad={() => setIsLoaded(true)}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
display: isLoaded ? 'block' : 'none'
}}
/>
)}
</div>
);
};
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 (
<div style={{
padding: '15px',
border: '2px solid #38a169',
borderRadius: '8px',
margin: '10px'
}}>
<h4>🖼 </h4>
<p></p>
<div style={{ height: '300px', overflow: 'auto' }}>
{images.map((src, index) => (
<LazyImage
key={index}
src={src}
alt={`懒加载图片 ${index + 1}`}
width={200}
height={150}
/>
))}
</div>
</div>
);
};
// 主组件
const PerformanceDemo: React.FC = () => {
// 生成大量测试数据
const largeDataSet = useMemo(() => {
return Array.from({ length: 10000 }, (_, i) => `数据项 ${i + 1}`);
}, []);
return (
<div style={{ padding: '20px' }}>
<h2> React性能优化演示</h2>
<MemoDemo />
<VirtualList
items={largeDataSet}
itemHeight={60}
containerHeight={300}
/>
<DebounceSearchDemo />
<LazyLoadDemo />
<div style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#f0fff4',
borderRadius: '10px',
border: '2px solid #38a169'
}}>
<h3> </h3>
<ul style={{ textAlign: 'left', lineHeight: '1.6' }}>
<li><strong>React.memo</strong>: </li>
<li><strong>useMemo</strong>: </li>
<li><strong>useCallback</strong>: </li>
<li><strong></strong>: </li>
<li><strong>(Debounce)</strong>: </li>
<li><strong></strong>: </li>
<li><strong></strong>: 使React.lazy和Suspense按需加载</li>
</ul>
</div>
</div>
);
};
export default PerformanceDemo;

View File

@ -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<UserProps> = ({ name, age, isActive }) => {
// 纯函数给定相同的props总是返回相同的JSX
return (
<div style={{
padding: '10px',
border: isActive ? '2px solid green' : '2px solid gray',
margin: '5px',
borderRadius: '5px'
}}>
<h4>{name}</h4>
<p>: {age}</p>
<span>{isActive ? '🟢 在线' : '⚫ 离线'}</span>
</div>
);
};
// 2. 单向数据流示例
interface CounterProps {
value: number;
onIncrement: () => void;
onDecrement: () => void;
}
const Counter: React.FC<CounterProps> = ({ value, onIncrement, onDecrement }) => {
// 数据只能从父组件流入,事件通过回调函数流出
return (
<div style={{ padding: '10px', border: '1px solid #ccc', margin: '10px' }}>
<h3>: {value}</h3>
<button onClick={onDecrement}>-</button>
<span style={{ margin: '0 10px' }}>{value}</span>
<button onClick={onIncrement}>+</button>
</div>
);
};
// 3. 不可变性 - 状态更新通过创建新对象
interface TodoItem {
id: number;
text: string;
completed: boolean;
}
const TodoList: React.FC = () => {
const [todos, setTodos] = useState<TodoItem[]>([
{ 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 (
<div style={{ padding: '15px', border: '2px solid #007bff', margin: '10px' }}>
<h3> ()</h3>
{todos.map(todo => (
<div key={todo.id} style={{ margin: '5px 0' }}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
marginLeft: '8px'
}}
>
{todo.text}
</span>
</div>
))}
<button
onClick={() => addTodo(`新任务 ${todos.length + 1}`)}
style={{ marginTop: '10px' }}
>
</button>
</div>
);
};
// 4. 组合优于继承 - 通过组合构建复杂UI
interface WithLoadingProps {
isLoading: boolean;
children: React.ReactNode;
}
const WithLoading: React.FC<WithLoadingProps> = ({ isLoading, children }) => {
if (isLoading) {
return <div>🔄 ...</div>;
}
return <>{children}</>;
};
const DataFetcher: React.FC = () => {
const [data, setData] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 模拟数据获取
setTimeout(() => {
setData(['React', 'Vue', 'Angular']);
setLoading(false);
}, 2000);
}, []);
return (
<WithLoading isLoading={loading}>
<div style={{ padding: '15px', border: '2px solid #28a745', margin: '10px' }}>
<h3></h3>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
</WithLoading>
);
};
// 5. 记忆化和性能优化
const ExpensiveCalculation: React.FC<{ numbers: number[] }> = ({ numbers }) => {
// 使用useMemo避免不必要的重计算
const sum = useMemo(() => {
console.log('🔄 重新计算总和...');
return numbers.reduce((acc, num) => acc + num, 0);
}, [numbers]);
return (
<div style={{ padding: '10px', backgroundColor: '#f8f9fa', margin: '10px' }}>
<h4></h4>
<p>: {numbers.join(', ')}</p>
<p>: {sum}</p>
</div>
);
};
// 主组件 - 展示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 (
<div style={{ padding: '20px' }}>
<h2>🚀 React </h2>
<div style={{ marginBottom: '20px' }}>
<h3>1. </h3>
<p>props总是产生相同的输出</p>
{users.map((user, index) => (
<PureUserCard key={index} {...user} />
))}
</div>
<div style={{ marginBottom: '20px' }}>
<h3>2. </h3>
<p></p>
<Counter
value={count}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<h3>3. </h3>
<TodoList />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>4. </h3>
<DataFetcher />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>5. (useMemo)</h3>
<ExpensiveCalculation numbers={numbers} />
<button onClick={addRandomNumber}>
</button>
</div>
<div style={{
marginTop: '30px',
padding: '15px',
backgroundColor: '#e3f2fd',
borderRadius: '8px'
}}>
<h3>💡 React设计思想总结</h3>
<ul>
<li><strong></strong></li>
<li><strong></strong>便</li>
<li><strong></strong></li>
<li><strong></strong>UI</li>
<li><strong></strong></li>
</ul>
</div>
</div>
);
};
export default ReactVsVueExamples;

111
src/examples/TSXExample.tsx Normal file
View File

@ -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<Props> = ({ title, initialCount = 0 }) => {
// 带类型的状态管理
const [count, setCount] = useState<number>(initialCount);
const [user, setUser] = useState<User | null>(null);
const [users, setUsers] = useState<User[]>([]);
// 带类型的事件处理函数
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<HTMLInputElement>): void => {
console.log(event.target.value);
};
return (
<div style={{
padding: '20px',
border: '2px solid #007bff',
margin: '10px',
borderRadius: '8px'
}}>
<h2>{title}</h2>
{/* 类型安全的计数器 */}
<div>
<p>: {count}</p>
<button onClick={handleIncrement}></button>
</div>
{/* 用户管理 */}
<div style={{ marginTop: '20px' }}>
<button onClick={handleAddUser}></button>
{/* 条件渲染当前选中的用户 */}
{user && (
<div style={{
backgroundColor: '#f8f9fa',
padding: '10px',
margin: '10px 0',
borderRadius: '4px'
}}>
<h4>:</h4>
<p>: {user.name}</p>
<p>: {user.email}</p>
<p>: {user.age}</p>
</div>
)}
{/* 用户列表 */}
<ul>
{users.map((userItem: User) => (
<li
key={userItem.id}
onClick={() => handleSelectUser(userItem)}
style={{
cursor: 'pointer',
padding: '5px',
marginBottom: '5px',
backgroundColor: user?.id === userItem.id ? '#e7f3ff' : 'transparent'
}}
>
{userItem.name} ({userItem.age})
</li>
))}
</ul>
</div>
{/* 类型安全的输入框 */}
<input
type="text"
placeholder="带类型的输入框"
onChange={handleInputChange}
style={{ marginTop: '10px', padding: '5px' }}
/>
</div>
);
};
export default TSXExample;

View File

@ -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<HTMLInputElement>) => {
setMessage(e.target.value); // Vue中通过v-model实现双向绑定
};
return (
<div style={{ padding: '15px', border: '2px solid #4FC08D', margin: '10px' }}>
<h3>📱 (Vue风格概念)</h3>
{/* 模拟Vue模板语法 */}
<div>
<p>: {message}</p>
<input
type="text"
value={message}
onChange={handleInputChange}
placeholder="修改消息 (类似v-model)"
/>
</div>
<div style={{ marginTop: '15px' }}>
<p>: {count}</p>
<button onClick={() => setCount(count + 1)}>
(@click)
</button>
</div>
<div style={{ marginTop: '15px', backgroundColor: '#f0f8f0', padding: '10px' }}>
<h4>💡 Vue设计理念体现</h4>
<ul>
<li> ()</li>
<li></li>
<li></li>
</ul>
</div>
</div>
);
};
// 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 (
<div style={{ padding: '15px', border: '2px solid #4FC08D', margin: '10px' }}>
<h3>🎯 (Vue概念)</h3>
{/* 模拟 v-if */}
<div>
<button onClick={() => setShowElement(!showElement)}>
{showElement ? '隐藏' : '显示'} (v-if)
</button>
{showElement && (
<p style={{ color: 'green' }}>🎉 </p>
)}
</div>
{/* 模拟 v-for */}
<div style={{ marginTop: '15px' }}>
<h4> (v-for):</h4>
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="添加新项目"
/>
<button onClick={addItem}></button>
</div>
</div>
<div style={{ marginTop: '15px', backgroundColor: '#f0f8f0', padding: '10px' }}>
<h4>💡 Vue模板语法特点</h4>
<ul>
<li>v-if: </li>
<li>v-for: </li>
<li>v-model: 双向数据绑定</li>
<li>@click: </li>
</ul>
</div>
</div>
);
};
// 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 (
<div style={{ padding: '15px', border: '2px solid #4FC08D', margin: '10px' }}>
<h3> (Vue风格)</h3>
<div>
<label>: </label>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
<div style={{ marginTop: '10px' }}>
<label>: </label>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
<div style={{ marginTop: '15px' }}>
<p><strong> (): {fullName}</strong></p>
</div>
<div style={{ marginTop: '15px' }}>
<p>: {numbers.join(', ')}</p>
<p><strong> (): {sum}</strong></p>
<button onClick={() => setNumbers([...numbers, Math.floor(Math.random() * 10)])}>
</button>
</div>
<div style={{ marginTop: '15px', backgroundColor: '#f0f8f0', padding: '10px' }}>
<h4>💡 Vue计算属性特点</h4>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
);
};
// 4. 模拟Vue的生命周期概念
const VueStyleLifecycle: React.FC = () => {
const [data, setData] = useState<string>('');
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 (
<div style={{ padding: '15px', border: '2px solid #4FC08D', margin: '10px' }}>
<h3>🔄 (Vue风格)</h3>
<div>
<p>: {mounted ? '✅ 已挂载' : '⏳ 挂载中'}</p>
<p>: {data || '⏳ 加载中...'}</p>
</div>
<div style={{ marginTop: '15px', backgroundColor: '#f0f8f0', padding: '10px' }}>
<h4>💡 Vue生命周期特点</h4>
<ul>
<li>created: 实例创建后</li>
<li>mounted: DOM挂载后</li>
<li>updated: 数据更新后</li>
<li>beforeUnmount: 卸载前</li>
</ul>
</div>
</div>
);
};
// 5. 模拟Vue的组件通信概念
interface ChildProps {
message: string;
onUpdateMessage: (newMessage: string) => void;
}
const VueStyleChild: React.FC<ChildProps> = ({ message, onUpdateMessage }) => {
return (
<div style={{ padding: '10px', backgroundColor: '#e8f5e8', border: '1px solid #4FC08D' }}>
<h4></h4>
<p>: {message}</p>
<button onClick={() => onUpdateMessage('来自子组件的消息')}>
($emit)
</button>
</div>
);
};
const VueStyleParent: React.FC = () => {
const [parentMessage, setParentMessage] = useState('来自父组件的消息');
return (
<div style={{ padding: '15px', border: '2px solid #4FC08D', margin: '10px' }}>
<h3>👨👩👧👦 (Vue风格)</h3>
<div>
<h4></h4>
<p>: {parentMessage}</p>
<VueStyleChild
message={parentMessage}
onUpdateMessage={setParentMessage}
/>
</div>
<div style={{ marginTop: '15px', backgroundColor: '#f0f8f0', padding: '10px' }}>
<h4>💡 Vue组件通信特点</h4>
<ul>
<li>props down: 父到子传递数据</li>
<li>events up: 子到父发送事件</li>
<li>v-model: 双向绑定语法糖</li>
<li>provide/inject: 跨层级通信</li>
</ul>
</div>
</div>
);
};
// 主组件 - 展示Vue设计思想概念
const VueStyleExamples: React.FC = () => {
return (
<div style={{ padding: '20px' }}>
<h2>🌟 Vue设计思想概念展示</h2>
<p style={{
backgroundColor: '#fffbe6',
padding: '10px',
borderLeft: '4px solid #4FC08D',
marginBottom: '20px'
}}>
<strong></strong>React语法展示Vue的设计理念
</p>
<VueStyleReactivity />
<VueStyleDirectives />
<VueStyleComputed />
<VueStyleLifecycle />
<VueStyleParent />
<div style={{
marginTop: '30px',
padding: '15px',
backgroundColor: '#f0f8f0',
borderRadius: '8px'
}}>
<h3>🌟 Vue设计思想总结</h3>
<ul>
<li><strong></strong></li>
<li><strong></strong>HTML扩展语法</li>
<li><strong></strong></li>
<li><strong></strong></li>
<li><strong></strong>线</li>
<li><strong></strong></li>
</ul>
</div>
</div>
);
};
export default VueStyleExamples;

View File

@ -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(
<React.StrictMode>
<App />

View File

@ -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 <div>加载中...</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
```
**特点:**
- 显式状态管理
- 手动优化渲染
- 函数式编程范式
- 灵活但需要更多样板代码
### Vue 架构特点
```vue
<!-- Vue: 单文件组件 + 选项式API -->
<template>
<div v-if="loading">加载中...</div>
<div v-else>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
</template>
<script>
export default {
props: ['userId'],
data() {
return {
user: null,
loading: true
}
},
watch: {
userId: {
immediate: true,
handler(newId) {
this.fetchUserData(newId);
}
}
},
methods: {
async fetchUserData(id) {
this.loading = true;
this.user = await fetchUser(id);
this.loading = false;
}
}
}
</script>
```
**特点:**
- 自动响应式更新
- 模板语法直观
- 约定式结构
- 更少的样板代码
## 📊 具体差异对比
| 维度 | 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 (
<div>
<Counter
value={count}
onChange={setCount}
/>
</div>
);
}
// 子组件
function Counter({ value, onChange }) {
return (
<div>
<p>计数: {value}</p>
<button onClick={() => onChange(value + 1)}>
增加
</button>
</div>
);
}
```
**特点:**
- 数据只能从父组件流向子组件
- 子组件通过回调函数通知父组件
- 数据流向清晰可追踪
### Vue: 响应式双向绑定
```vue
<!-- 父组件 -->
<template>
<div>
<Counter v-model="count" />
</div>
</template>
<script>
export default {
data() {
return { count: 0 }
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<p>计数: {{ modelValue }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
increment() {
this.$emit('update:modelValue', this.modelValue + 1);
}
}
}
</script>
```
**特点:**
- 支持双向数据绑定
- 响应式系统自动追踪依赖
- 语法糖简化父子组件通信
## 🎨 开发体验对比
### 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更适合快速开发和团队技术栈迁移。

26
tsconfig.json Normal file
View File

@ -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"
]
}