React快速入门

一、理解React、node.js和Vite的概念

(1)React

一个前端开发框架,它和vue是两个独立的、由不同团队开发的竞争性前端框架。React是meta公司的,vue是由尤雨溪开发的。

(2)node.js

前端开发(不论是vue还是react)都需要安装node.js,提供运行环境、npm包管理、构建工具。Node.js 之于 React,就像 JDK 之于 Spring Boot ------ 都是开发和构建时不可或缺的运行时环境,但 React 最终产品在浏览器运行(不像 Spring Boot 需要持续依赖 JVM)。

(3)vite

vite(也包括webpack)是 编译工具 + 打包工具 + 开发服务器 的组合,相当于 Java 的 Maven/Gradle + javac。

还有一个对比表:

二、vite+react项目

1.VS Code新建一个vite+react项目

步骤 1:打开 VS Code 终端

bash 复制代码
# 方式1:快捷键
Ctrl + `          # 打开终端

# 方式2:菜单栏
Terminal → New Terminal

步骤 2:创建项目

bash 复制代码
# npm 方式
npm create vite@latest my-react-app -- --template react

安装过程中可能会出现 ESLint安装失败的问题,有两种解决方法,一种是在项目中安装

在项目根目录 (my-react-app)执行

bash 复制代码
npm install eslint --save-dev

下载到 当前项目的 node_modules 目录,并在 package.json 中记录到 devDependencies。

另一种是全局安装

bash 复制代码
npm install -g eslint

安装完成后重启VSCode。

步骤 3:npm install 下载包

npm install 只下载 当前项目所需的包(根据 package.json 中的依赖)

步骤 4:手动启动服务

bash 复制代码
npm run dev

补充:改变npm安装包的目录

使用npm install -g 全局安装指令Windows的默认安装目录为:

bash 复制代码
C:\Users\你的用户名\AppData\Roaming\npm\node_modules

可以改变目录。

第一步:创建新文件夹

在你想要的位置(比如 D 盘)新建两个文件夹:

node_global:用来存放所有全局安装的包。

node_cache:用来存放 npm 的缓存文件。

例如,你可以在 D 盘根目录下创建这两个文件夹:

D:\software\nodejs\node_global 和 D:\software\nodejs\node_cache

第二步:修改 npm 配置

打开终端(命令提示符或 PowerShell),执行以下两条命令,告诉 npm 使用我们刚才新建的文件夹:

bash 复制代码
npm config set prefix "D:\software\nodejs\node_global"
npm config set cache "D:\software\nodejs\node_cache"

执行完后,可以运行 npm config ls 检查一下

第三步:配置环境变量

这是最关键的一步,目的是让你在电脑任何地方都能运行全局命令。

打开"系统环境变量"设置窗口(在搜索框搜"环境变量"即可找到)。

修改用户变量:

在"用户变量"区域,找到并选中 Path 变量,然后点击"编辑"。

在弹出的窗口中,点击"新建",将 D:\software\nodejs\node_global(你的新路径)加进去。

完成上述步骤后,请重启你的终端(以管理员身份运行 ),然后随意全局安装一个包(比如 npm install -g prettier)来测试。

安装完成后,去你新设的 node_global 文件夹里看看,如果能找到刚才安装的包,就说明一切配置成功了。

2. vite+react目录结构

新建的vite-react的项目目录结构如下:

完整的目录结构:

bash 复制代码
my-react-app/                    # 项目根目录
├── node_modules/                # 依赖包目录(自动生成,不手动修改)
├── public/                      # 公共静态资源目录
│   └── vite.svg                 # 网站图标(可替换)
├── src/                         # 源代码目录(主要开发区域)
│   ├── assets/                  # 资源文件目录
│   │   └── react.svg            # React 图标
│   ├── App.jsx                  # 根组件
│   ├── App.css                  # 根组件样式
│   ├── main.jsx                 # 入口文件
│   └── index.css                # 全局样式
├── .gitignore                   # Git 忽略文件配置
├── index.html                   # 主 HTML 文件
├── package.json                 # 项目配置文件,类似于springboot的pom.xml
├── package-lock.json            # 依赖版本锁定文件
├── vite.config.js               # Vite 配置文件
└── README.md                    # 项目说明文档

核心文件详解。

1.入口index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <!-- React 会挂载到这个 div 上 -->
    <div id="root"></div>
    <!-- 入口 JS 文件 -->
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

2.JavaScript 入口src/main.jsx

javascript 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'

// 找到 root 元素,将 React 应用渲染进去
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

作用:

整个应用的起点

将 App 组件渲染到 HTML 中

引入全局样式

3.根组件src/App.jsx

javascript 复制代码
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  )
}

export default App

三、React 常用语法和渲染结构

3.1 JSX核心语法

3.1.1 基本语法规则

1.定义组件

组件包括类组件和函数组件,类组件逐渐被淘汰,React 官方现在推荐只使用函数组件。

类组件定义语句(不过多展开了):

javascript 复制代码
import React, { Component } from 'react';

// 类组件
class Welcome extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  
  render() {
    return (
      <div>
        <h1>Hello, {this.props.name}</h1>
        <p>计数: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          增加
        </button>
      </div>
    );
  }
}

函数组件定义语句:

javascript 复制代码
//.jsx
import React, { useState } from 'react';

function Counter() {
  // 声明一个叫 "count" 的状态变量,初始值为 0
  //创建一个修改函数 setCount,用于更新 count 的值
  //返回一个数组,包含 [当前值, 修改函数]
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>当前计数: {count}</p>
      {/* 点击按钮时,调用 setCount 修改 count 的值 */}
      <button onClick={() => setCount(count + 1)}>
        增加
      </button>
      <button onClick={() => setCount(count - 1)}>
        减少
      </button>
      <button onClick={() => setCount(0)}>
        重置
      </button>
    </div>
  );
}

说明:组件的return语句只能返回一个根元素

javascript 复制代码
//.jsx
// ✅ 只能返回一个根元素
function Component() {
  return (
    <div>  {/* 用一个 div 包裹 */}
      <h1>标题</h1>
      <p>内容</p>
    </div>
  )
}
javascript 复制代码
//jsx
// ✅ 使用空标签(Fragment)
function Component() {
  return (
    <>
      <h1>标题</h1>
      <p>内容</p>
    </>
  )
}
javascript 复制代码
//jsx
// ❌ 错误:相邻的 JSX 元素
function Component() {
  return (
    <h1>标题</h1>
    <p>内容</p>  // 报错!
  )
}

组件可以导出给其他组件使用

javascript 复制代码
//.jsx
// components.jsx
export default function UserCard({ name, age, email }) {
  return (
    <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  );
}

/* 或者
function UserCard({ name, age, email }) {
  return (
    <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  );
}
export default UserCard;*/
javascript 复制代码
//App.jsx
//其他组件
import UserCard from './components';

// ❌ 错误:不能加花括号
// import { UserCard } from './components';

// ✅ 可以用 as 重命名
//import { UserCard as MyCard } from './components';

function App() {
  const users = [
    { name: '张三', age: 25, email: 'zhangsan@example.com' },
    { name: '李四', age: 30, email: 'lisi@example.com' }
  ];

  return (
    <div>
      {users.map(
         (user, index) => (<UserCard key={index} {...user} />)//...是对象展开运算符,{...user} 将 user 对象的所有属性作为 props 传递,// 等价于<UserCard key={index} name={user.name} age={user.age} email={user.email} />
        )
      }
    </div>
  );
}

export default App;

一个.jsx文件中可以有多个函数组件(一般是一个主组件,其他辅组件),且同一文件中的组件可以互相使用。

javascript 复制代码
//.jsx
// Components.jsx
import React from 'react';

// 子组件
function UserCard({ name, age, email }) {
  return (
    <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  );
}

function Title({ text }) {
  return <h1 style={{ color: 'blue' }}>{text}</h1>;
}

// 主组件(默认导出)
function App() {
  const users = [
    { name: '张三', age: 25, email: 'zhangsan@example.com' },
    { name: '李四', age: 30, email: 'lisi@example.com' }
  ];

  return (
    <div>
      <Title text="用户列表" />
      {users.map(
         (user, index) => (<UserCard key={index} {...user} />)//...是对象展开运算符,{...user} 将 user 对象的所有属性作为 props 传递,// 等价于<UserCard key={index} name={user.name} age={user.age} email={user.email} />
        )
      }
    </div>
  );
}

export default App;

一个jsx文件也可以导出多个组件(及常量)供其他文件使用,主组件为默认导出组件,其他组件也可以一起导出。在其他文件中导入默认导出组件不用加花括号,其他组件需要加花括号

javascript 复制代码
// components.jsx
import React from 'react';

// 定义多个组件
export function Header() {
  return <header>这是头部</header>;
}

export function Footer() {
  return <footer>这是底部</footer>;
}

export function Sidebar() {
  return <aside>侧边栏</aside>;
}

//或者
/*function Header() {
  return <header>这是头部</header>;
}
function Footer() {
  return <footer>这是底部</footer>;
}
function Sidebar() {
  return <aside>侧边栏</aside>;
}
export { Header, Footer,Sidebar};*/

// 默认导出一个主组件
export default function Layout({ children }) {
  return (
    <div>
      <Header />
      {children}
      <Footer />
    </div>
  );
}
// 也可以单独导出常量
export const API_URL = 'https://api.example.com';
export const VERSION = '1.0.0';
javascript 复制代码
// App.jsx
import Layout, { Header, Footer, Sidebar , API_URL, VERSION } from './components';

function App() {
  return (
    <Layout>
      <Sidebar />
      <div>主要内容</div>
    </Layout>
    <h2>API地址: {API_URL}</h2>
    <h3>版本: {VERSION}</h3>
  );
}
2.使用组件

先来补充一个概念Props。Props 是 Properties 的缩写,是父组件传递给子组件的数据。它是 React 组件之间通信的核心机制。

对于定义好的组件:

javascript 复制代码
import React from 'react';

export default function UserCard({ name, age, email }) {
  return (
    <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  );
}

以下是使用组件的常见几种方式。

(1)基本使用方式--直接传递
javascript 复制代码
//.jsx
// Props 本质上就是一个 JavaScript 对象
// 父组件
function App() {
  return (
    <div>
      <UserCard name="张三" age={25} email="zhangsan@example.com" />
      <UserCard name="李四" age={30} email="lisi@example.com" />
      <UserCard name="王五" age={28} email="wangwu@example.com" />
    </div>
  );
}
(2)使用变量/对象传递
javascript 复制代码
function App() {
  // 定义用户数据
  const user1 = {
    name: "张三",
    age: 25,
    email: "zhangsan@example.com"
  };
  
  const userName = "李四";
  const userAge = 30;
  const userEmail = "lisi@example.com";
  
  return (
    <div>
      {/* 使用对象展开传递 */}
      <UserCard {...user1} />
      
      {/* 使用变量传递 */}
      <UserCard name={userName} age={userAge} email={userEmail} />
      
    </div>
  );
}
(3)使用数组动态渲染多个组件
javascript 复制代码
function App() {
  // 用户数据数组
  const users = [
    { id: 1, name: "张三", age: 25, email: "zhangsan@example.com" },
    { id: 2, name: "李四", age: 30, email: "lisi@example.com" },
    { id: 3, name: "王五", age: 28, email: "wangwu@example.com" },
    { id: 4, name: "赵六", age: 35, email: "zhaoliu@example.com" }
  ];
  
  return (
    <div>
      <h1>用户列表</h1>
      {users.map(user => (
        <UserCard 
          key={user.id}
          name={user.name} 
          age={user.age} 
          email={user.email} 
        />
      ))}
    </div>
  );
}
(4)使用useState管理数据

首先来说一下useState的概念。useState 是React的一个"钩子",它的作用就是让函数组件能够拥有自己的"内部记忆"(即状态)。当状态改变时,React会自动重新渲染页面,显示最新的数据。

bash 复制代码
const [users, setUsers] = useState(初始值);

users:你定义的"状态变量",就像你给组件的一个"盒子",里面装着当前的数据。

setUsers:一个"更新函数",它是改变"盒子"里数据的唯一合法途径。

useState(...):React给你的"魔法",用来初始化这个盒子。

javascript 复制代码
import { useState } from 'react';

function App() {
  const [users, setUsers] = useState([
    { id: 1, name: "张三", age: 25, email: "zhangsan@example.com" },
    { id: 2, name: "李四", age: 30, email: "lisi@example.com" }
  ]);
  
  // 添加新用户
  const addUser = () => {
    const newUser = {
      id: users.length + 1,
      name: "新用户",
      age: 20,
      email: "newuser@example.com"
    };
    setUsers([...users, newUser]);
  };
  
  return (
    <div>
      <button onClick={addUser}>添加用户</button>
      {users.map(user => (
        <UserCard 
          key={user.id}
          name={user.name} 
          age={user.age} 
          email={user.email} 
        />
      ))}
    </div>
  );
}

上述代码中的"setUsers(...users, newUser)"是React中最经典的数组更新模式。它做了两件事:

(1)创建一个新数组:...users, newUser

(2)用新数组替换旧数组:setUsers(新数组)

为什么不能直接 setUsers(users.push(newUser))?

这是最关键的React原则:永远不要直接修改状态。

users.push(newUser):这会直接修改 users 原数组。React无法检测到这种"偷偷摸摸"的修改,因此不会更新页面。

...users, newUser:这是创建一个全新的数组,原数组纹丝不动。React能清晰地看到"这个变量指向了一个新东西",所以知道需要刷新页面。

bash 复制代码
[...users, newUser]

这是JavaScript的展开语法。假设原来的 users 数组长这样:

javascript 复制代码
// 原来的 users 数组
[
  { id: 1, name: "张三", age: 25, email: "..."},
  { id: 2, name: "李四", age: 30, email: "..."}
]

执行 ...users, newUser 的过程就像在"组装"一个新的数组:

(1)...users:把原来的"张三"和"李四"两个对象,像拆盒子一样拿出来,放进新数组。

(2), newUser:紧接着,把新创建的"新用户"对象也放进去。

最终,你得到的是一个全新的数组:

javascript 复制代码
// 全新的数组,包含3个元素
[
  { id: 1, name: "张三", age: 25, ...},
  { id: 2, name: "李四", age: 30, ...},
  { id: users.length+1, name: "新用户", age: 20, ...} // 新用户被追加在末尾
]

useState的3种常见用法:

3.1.2 在 JSX 中使用 JavaScript

javascript 复制代码
//.jsx文件
function Profile({ user }) {
  const name = "张三"
  const age = 25
  const isActive = true
  const users = ['李雷', '韩梅梅', 'Lily']
  
  return (
    <div>
      {/* 显示变量 */}
      <h1>{name}</h1>
      <p>年龄: {age}</p>
      
      {/* 三元运算符 */}
      <span>{isActive ? '在线' : '离线'}</span>
      
      {/* 函数调用 */}
      <p>{new Date().toLocaleDateString()}</p>
      
      {/* 数组渲染 */}
      <ul>
        {users.map(user => <li key={user}>{user}</li>)}
      </ul>
      
      {/* 条件渲染 */}
      {isActive && <button>发送消息</button>}
    </div>
  )
}

用一个完整案例说明

定义子组件及其样式:

javascript 复制代码
// UserCard.jsx
import React, { useState } from 'react';
import './UserCard.css';

const UserCard = ({ user }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const [likes, setLikes] = useState(0);

  const handleToggle = () => {
    setIsExpanded(!isExpanded);
  };

  const handleLike = () => {
    setLikes(likes + 1);
  };

  return (
    <div className="user-card">
      <div className="card-header">
        <img src={user.avatar} alt={user.name} className="avatar" />
        <div className="user-info">
          <h2>{user.name}</h2>
          <p className="email">{user.email}</p>
        </div>
      </div>
      
      <div className="card-content">
        <p><strong>职位:</strong>{user.position}</p>
        <p><strong>公司:</strong>{user.company}</p>
        
        {isExpanded && (
          <div className="expanded-info">
            <p><strong>地点:</strong>{user.location}</p>
            <p><strong>技能:</strong>{user.skills.join(', ')}</p>
            <p><strong>个人简介:</strong>{user.bio}</p>
          </div>
        )}
      </div>
      
      <div className="card-actions">
        <button onClick={handleToggle} className="btn-toggle">
          {isExpanded ? '收起' : '展开'}
        </button>
        <button onClick={handleLike} className="btn-like">
          👍 {likes} 点赞
        </button>
      </div>
    </div>
  );
};

export default UserCard;
css 复制代码
/* UserCard.css */
.user-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  margin: 15px;
  max-width: 400px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  transition: all 0.3s ease;
}

.user-card:hover {
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
  transform: translateY(-2px);
}

.card-header {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
}

.avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin-right: 15px;
  object-fit: cover;
}

.user-info h2 {
  margin: 0 0 5px 0;
  color: #333;
}

.email {
  color: #666;
  font-size: 14px;
  margin: 0;
}

.card-content {
  margin: 15px 0;
  line-height: 1.6;
}

.expanded-info {
  margin-top: 10px;
  padding: 10px;
  background-color: #f5f5f5;
  border-radius: 4px;
  animation: slideDown 0.3s ease;
}

@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card-actions {
  display: flex;
  gap: 10px;
  margin-top: 15px;
}

button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}

.btn-toggle {
  background-color: #4CAF50;
  color: white;
}

.btn-toggle:hover {
  background-color: #45a049;
}

.btn-like {
  background-color: #f44336;
  color: white;
}

.btn-like:hover {
  background-color: #da190b;
}

父组件使用定义好的子组件:

javascript 复制代码
// App.jsx
import React, { useState } from 'react';
import UserCard from './UserCard';

const App = () => {
  const [users, setUsers] = useState([
    {
      id: 1,
      name: '张三',
      email: 'zhangsan@example.com',
      position: '前端开发工程师',
      company: '科技公司',
      location: '北京',
      skills: ['React', 'Vue', 'JavaScript', 'CSS'],
      bio: '5年前端开发经验,热爱开源技术',
      avatar: 'https://via.placeholder.com/80/4CAF50/white?text=ZS'
    },
    {
      id: 2,
      name: '李四',
      email: 'lisi@example.com',
      position: 'UI设计师',
      company: '设计工作室',
      location: '上海',
      skills: ['Figma', 'Sketch', 'Adobe XD', 'UI/UX'],
      bio: '追求完美的设计,用户体验至上',
      avatar: 'https://via.placeholder.com/80/2196F3/white?text=LS'
    },
    {
      id: 3,
      name: '王五',
      email: 'wangwu@example.com',
      position: '全栈开发',
      company: '互联网公司',
      location: '深圳',
      skills: ['React', 'Node.js', 'Python', 'MongoDB'],
      bio: '全栈开发者,热爱学习和分享',
      avatar: 'https://via.placeholder.com/80/FF9800/white?text=WW'
    }
  ]);

  const addUser = () => {
    const newUser = {
      id: users.length + 1,
      name: `新用户${users.length + 1}`,
      email: `user${users.length + 1}@example.com`,
      position: '新入职员工',
      company: '新公司',
      location: '广州',
      skills: ['学习中'],
      bio: '新成员,请多关照',
      avatar: `https://via.placeholder.com/80/9C27B0/white?text=NU${users.length + 1}`
    };
    setUsers([...users, newUser]);
  };

  const removeUser = (id) => {
    setUsers(users.filter(user => user.id !== id));
  };

  return (
    <div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
      <h1>用户信息展示</h1>
      <button 
        onClick={addUser} 
        style={{
          marginBottom: '20px',
          padding: '10px 20px',
          backgroundColor: '#2196F3',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer'
        }}
      >
        添加新用户
      </button>
      
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(400px, 1fr))', gap: '20px' }}>
        {users.map(user => (
          <div key={user.id} style={{ position: 'relative' }}>
            <UserCard user={user} />
            <button
              onClick={() => removeUser(user.id)}
              style={{
                position: 'absolute',
                top: '10px',
                right: '10px',
                backgroundColor: '#f44336',
                color: 'white',
                borderRadius: '50%',
                width: '30px',
                height: '30px',
                padding: '0',
                fontSize: '16px'
              }}
            >
              ×
            </button>
          </div>
        ))}
      </div>
      
      {users.length === 0 && (
        <p style={{ textAlign: 'center', color: '#999' }}>暂无用户数据</p>
      )}
    </div>
  );
};

export default App;

看看效果:

运行后你将看到:

3个默认用户卡片,每个卡片显示头像、姓名、邮箱和职位

点击"展开"按钮显示更多信息(地点、技能、个人简介)

点击"点赞"按钮增加点赞数

点击"添加新用户"按钮动态添加新卡片

点击右上角"×"删除卡片

卡片有悬停动画效果

响应式网格布局

3.2 条件渲染

5种条件渲染方式。

javascript 复制代码
function ConditionalRender({ isLoggedIn, user, loading }) {
  
  // 1. if 语句(适合复杂逻辑)
  if (loading) {
    return <div>加载中...</div>
  }
  
  // 2. 三元运算符(简单条件)
  return (
    <div>
      {isLoggedIn ? (
        <div>
          <h1>欢迎回来, {user.name}!</h1>
          <button>登出</button>
        </div>
      ) : (
        <div>
          <h1>请先登录</h1>
          <button>登录</button>
        </div>
      )}
      
      {/* 3. && 运算符(只显示或什么都不显示)*/}
      {isLoggedIn && <Notification />}
      
      {/* 4. 变量存储(适合多条件)*/}
      {(() => {
        if (user.vip) return <VIPBadge />
        if (user.new) return <NewBadge />
        return <NormalBadge />
      })()}
      
      {/* 5. 立即执行函数 */}
      {(() => {
        switch(user.level) {
          case 'gold': return <GoldBadge />
          case 'silver': return <SilverBadge />
          default: return <NormalBadge />
        }
      })()}
    </div>
  )
}

3.3 事件处理

常用事件和用法

javascript 复制代码
function EventDemo() {
  // 1. 点击事件
  const handleClick = () => {
    alert('按钮被点击了')
  }
  
  // 2. 带参数的事件
  const handleDelete = (id) => {
    console.log('删除:', id)
  }
  
  // 3. 表单事件
  const handleSubmit = (e) => {
    e.preventDefault()  // 阻止默认行为
    console.log('表单提交')
  }
  
  // 4. 键盘事件
  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      console.log('按下了回车')
    }
  }
  
  // 5. 鼠标事件
  const handleMouseEnter = () => {
    console.log('鼠标进入')
  }
  
  return (
    <div>
      {/* 基本点击 */}
      <button onClick={handleClick}>点击我</button>
      
      {/* 带参数 */}
      <button onClick={() => handleDelete(1)}>删除</button>
      
      {/* 表单提交 */}
      <form onSubmit={handleSubmit}>
        <input 
          onKeyPress={handleKeyPress}
          onMouseEnter={handleMouseEnter}
        />
        <button type="submit">提交</button>
      </form>
      
      {/* 阻止冒泡 */}
      <div onClick={() => console.log('外层')}>
        <button onClick={(e) => {
          e.stopPropagation()
          console.log('内层')
        }}>
          点击(不冒泡)
        </button>
      </div>
    </div>
  )
}

3.4 常用Hooks

Hooks的作用是在不编写 class组件 的情况下使用 state 和其他 React 特性。

3.4.1 Hooks的核心规则

1. 只在顶层调用 Hooks

javascript 复制代码
// ❌ 错误:不能在条件语句中
function Component({ cond }) {
  if (cond) {
    const [state, setState] = useState(0) // 违反规则!
  }
}

// ❌ 错误:不能在循环中
function Component() {
  for (let i = 0; i < 3; i++) {
    const [state, setState] = useState(0) // 违反规则!
  }
}

// ✅ 正确:始终在顶层
function Component({ cond }) {
  const [state, setState] = useState(0)
  
  if (cond) {
    // 可以使用 state,但不能定义 Hook
  }
}

2. 只在 React 函数中调用 Hooks

javascript 复制代码
// ✅ 在 Function 组件中
function MyComponent() {
  const [state, setState] = useState(0)
}

// ✅ 在自定义 Hook 中
function useCustomHook() {
  const [state, setState] = useState(0)
}

// ❌ 普通 JavaScript 函数
function normalFunction() {
  const [state, setState] = useState(0) // 错误!
}

3.4.2 内置Hooks

1. useEffect - 副作用处理

javascript 复制代码
import { useState, useEffect } from 'react'

function DataFetching() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
  
  // 组件挂载时执行一次
  useEffect(() => {
    fetchData()
  }, [])  // 空数组 = 只执行一次
  
  // 监听某个状态变化
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title = `点击了 ${count} 次`
  }, [count])  // count 变化时执行
  
  // 清理副作用(如定时器)
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行')
    }, 1000)
    
    return () => {
      clearInterval(timer)  // 组件卸载时清理
    }
  }, [])
  
  const fetchData = async () => {
    setLoading(true)
    const res = await fetch('https://api.example.com/data')
    const json = await res.json()
    setData(json)
    setLoading(false)
  }
  
  if (loading) return <div>加载中...</div>
  return <div>{JSON.stringify(data)}</div>
}

2. useContext - 跨组件传值

javascript 复制代码
import { createContext, useContext, useState } from 'react'

// 创建 Context
const ThemeContext = createContext()

// 父组件提供数据
function App() {
  const [theme, setTheme] = useState('light')
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  )
}

// 中间组件不需要传 props
function Toolbar() {
  return <ThemedButton />
}

// 深层组件直接使用
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext)
  
  return (
    <button 
      style={{ 
        background: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff'
      }}
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      切换主题
    </button>
  )
}

3. useRef - 引用 DOM 或存储值

javascript 复制代码
import { useRef, useEffect } from 'react'

function RefDemo() {
  // 引用 DOM 元素
  const inputRef = useRef(null)
  
  // 存储不触发重新渲染的值
  const countRef = useRef(0)
  
  const focusInput = () => {
    inputRef.current.focus()
  }
  
  const incrementRef = () => {
    countRef.current++
    console.log('Ref count:', countRef.current)
    // 不会触发组件重新渲染
  }
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>聚焦输入框</button>
      <button onClick={incrementRef}>增加 Ref</button>
    </div>
  )
}
相关推荐
喵了几个咪1 小时前
AI重构软件开发范式:框架与脚手架为何仍是生产级开发的刚需?
vue.js·人工智能·react.js·重构·golang·ai编程
ImTryCatchException1 小时前
React Native 嵌入现有 Android 项目:踩坑记录与解决方案
android·react native·react.js
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_40:(Flexbox实战技能测试)
前端·css·ui·html·tensorflow
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_36:(float、clear与BFC深度解析)
前端·javascript·css·ui·交互
ConardLi2 小时前
啊?我刚开源的 Skills 已经 7K Star 了?!
前端·人工智能·后端
糯米团子7492 小时前
javascript高频知识点
开发语言·前端·javascript
道友可好2 小时前
Git Worktree:一个仓库,多个分身
前端·后端·程序员
道友可好2 小时前
AI 写代码太快了,快到你对齐不了它
前端·人工智能
无风听海2 小时前
Bearer Token 权威指南:从原理到生产级安全实践
前端·javascript·安全