Skip to content

React 快速上手

概述

本章将带您快速创建第一个 React 应用,了解 React 的基本工作流程。我们将从创建项目开始,逐步了解 React 应用的核心概念,包括组件、JSX、状态管理等。

🚀 创建第一个 React 应用

项目初始化

bash
# 创建新项目
npx create-react-app my-first-react-app

# 进入项目目录
cd my-first-react-app

# 启动开发服务器
npm start

项目结构分析

my-first-react-app/
├── public/                 # 静态资源目录
│   ├── index.html         # HTML 模板
│   ├── favicon.ico        # 网站图标
│   └── manifest.json      # PWA 配置
├── src/                   # 源代码目录
│   ├── App.js            # 主应用组件
│   ├── App.css           # 应用样式
│   ├── index.js          # 应用入口点
│   ├── index.css         # 全局样式
│   └── App.test.js       # 测试文件
├── package.json          # 依赖配置
└── README.md            # 项目说明

📝 理解核心文件

index.html - HTML 模板

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="Web site created using create-react-app" />
    <title>我的第一个 React 应用</title>
  </head>
  <body>
    <noscript>您需要启用 JavaScript 才能运行此应用。</noscript>
    <!-- React 应用将挂载到这个元素 -->
    <div id="root"></div>
    <!-- 
      这个 HTML 文件是模板。
      如果打开浏览器直接查看,会看到一个空页面。
      
      可以添加字体、meta 标签或 analytics 到这个文件。
      构建步骤会将打包后的脚本放入 <body> 标签。
    -->
  </body>
</html>

index.js - 应用入口点

jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));

// 渲染应用到根元素
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// React.StrictMode 的作用:
// 1. 检测不安全的生命周期
// 2. 关于使用过时字符串 ref API 的警告
// 3. 关于使用废弃的 findDOMNode 方法的警告
// 4. 检测意外的副作用
// 5. 检测过时的 context API

App.js - 主应用组件

jsx
import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>欢迎来到 React 世界!</h1>
        <p>这是您的第一个 React 应用</p>
        <button onClick={() => alert('Hello React!')}>
          点击我
        </button>
      </header>
    </div>
  );
}

export default App;

🎯 第一个交互式组件

创建计数器组件

jsx
import React, { useState } from 'react';
import './App.css';

function Counter() {
  // 使用 useState Hook 管理状态
  const [count, setCount] = useState(0);
  
  // 事件处理函数
  const increment = () => {
    setCount(count + 1);
  };
  
  const decrement = () => {
    setCount(count - 1);
  };
  
  const reset = () => {
    setCount(0);
  };
  
  return (
    <div className="counter">
      <h2>计数器应用</h2>
      <div className="counter-display">
        当前计数: <span className="count-number">{count}</span>
      </div>
      <div className="counter-buttons">
        <button onClick={decrement} className="btn btn-red">
          -1
        </button>
        <button onClick={reset} className="btn btn-gray">
          重置
        </button>
        <button onClick={increment} className="btn btn-green">
          +1
        </button>
      </div>
    </div>
  );
}

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>我的第一个 React 应用</h1>
        <Counter />
      </header>
    </div>
  );
}

export default App;

添加样式

css
/* App.css */
.App {
  text-align: center;
}

.App-header {
  background-color: #282c34;
  padding: 20px;
  color: white;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.counter {
  background-color: white;
  color: #282c34;
  padding: 30px;
  border-radius: 10px;
  margin: 20px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.counter-display {
  font-size: 24px;
  margin: 20px 0;
}

.count-number {
  font-weight: bold;
  color: #61dafb;
  font-size: 32px;
}

.counter-buttons {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.btn {
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
  font-weight: bold;
  transition: transform 0.2s, box-shadow 0.2s;
}

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

.btn-red {
  background-color: #ff6b6b;
  color: white;
}

.btn-gray {
  background-color: #6c757d;
  color: white;
}

.btn-green {
  background-color: #51cf66;
  color: white;
}

🔧 添加更多功能

待办事项组件

jsx
import React, { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');
  
  // 添加待办事项
  const addTodo = () => {
    if (inputValue.trim()) {
      const newTodo = {
        id: Date.now(),
        text: inputValue,
        completed: false
      };
      setTodos([...todos, newTodo]);
      setInputValue('');
    }
  };
  
  // 切换完成状态
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  // 删除待办事项
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  // 处理键盘事件
  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      addTodo();
    }
  };
  
  return (
    <div className="todo-app">
      <h2>待办事项</h2>
      
      {/* 输入区域 */}
      <div className="todo-input">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={handleKeyPress}
          placeholder="输入待办事项..."
          className="todo-input-field"
        />
        <button onClick={addTodo} className="btn btn-blue">
          添加
        </button>
      </div>
      
      {/* 待办事项列表 */}
      <div className="todo-list">
        {todos.length === 0 ? (
          <p className="no-todos">暂无待办事项</p>
        ) : (
          todos.map(todo => (
            <div
              key={todo.id}
              className={`todo-item ${todo.completed ? 'completed' : ''}`}
            >
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              <span className="todo-text">{todo.text}</span>
              <button
                onClick={() => deleteTodo(todo.id)}
                className="btn btn-small btn-red"
              >
                删除
              </button>
            </div>
          ))
        )}
      </div>
      
      {/* 统计信息 */}
      <div className="todo-stats">
        <p>总计: {todos.length} | 已完成: {todos.filter(t => t.completed).length}</p>
      </div>
    </div>
  );
}

// 更新 App 组件
function App() {
  const [currentView, setCurrentView] = useState('counter');
  
  return (
    <div className="App">
      <header className="App-header">
        <h1>我的 React 应用集合</h1>
        
        {/* 导航菜单 */}
        <nav className="app-nav">
          <button 
            onClick={() => setCurrentView('counter')}
            className={`nav-btn ${currentView === 'counter' ? 'active' : ''}`}
          >
            计数器
          </button>
          <button 
            onClick={() => setCurrentView('todo')}
            className={`nav-btn ${currentView === 'todo' ? 'active' : ''}`}
          >
            待办事项
          </button>
        </nav>
        
        {/* 根据当前视图显示组件 */}
        {currentView === 'counter' && <Counter />}
        {currentView === 'todo' && <TodoApp />}
      </header>
    </div>
  );
}

export default App;

完整样式

css
/* 添加到 App.css */

/* 导航样式 */
.app-nav {
  margin-bottom: 30px;
}

.nav-btn {
  padding: 10px 20px;
  margin: 0 10px;
  border: 2px solid #61dafb;
  background-color: transparent;
  color: #61dafb;
  border-radius: 5px;
  cursor: pointer;
  transition: all 0.3s;
}

.nav-btn:hover,
.nav-btn.active {
  background-color: #61dafb;
  color: #282c34;
}

/* 待办事项样式 */
.todo-app {
  background-color: white;
  color: #282c34;
  padding: 30px;
  border-radius: 10px;
  margin: 20px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  max-width: 500px;
}

.todo-input {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.todo-input-field {
  flex: 1;
  padding: 10px;
  border: 2px solid #ddd;
  border-radius: 5px;
  font-size: 16px;
}

.todo-input-field:focus {
  outline: none;
  border-color: #61dafb;
}

.btn-blue {
  background-color: #61dafb;
  color: #282c34;
}

.todo-list {
  min-height: 200px;
}

.todo-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-bottom: 1px solid #eee;
  transition: background-color 0.2s;
}

.todo-item:hover {
  background-color: #f8f9fa;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #6c757d;
}

.todo-text {
  flex: 1;
  text-align: left;
}

.btn-small {
  padding: 5px 10px;
  font-size: 12px;
}

.no-todos {
  text-align: center;
  color: #6c757d;
  font-style: italic;
  padding: 40px;
}

.todo-stats {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #eee;
  font-size: 14px;
  color: #6c757d;
}

🎨 理解 React 核心概念

1. 组件(Components)

jsx
// 函数组件 - 推荐方式
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// 类组件 - 传统方式
class GreetingClass extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

2. JSX 语法

jsx
function ExampleComponent() {
  const user = { name: '张三', age: 25 };
  const isLoggedIn = true;
  
  return (
    <div>
      {/* JavaScript 表达式 */}
      <h1>用户名: {user.name}</h1>
      <p>年龄: {user.age}</p>
      
      {/* 条件渲染 */}
      {isLoggedIn ? (
        <p>欢迎回来!</p>
      ) : (
        <p>请先登录</p>
      )}
      
      {/* 列表渲染 */}
      <ul>
        {['苹果', '香蕉', '橙子'].map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

3. 状态管理(State)

jsx
import { useState } from 'react';

function StateExample() {
  // 简单状态
  const [count, setCount] = useState(0);
  
  // 对象状态
  const [user, setUser] = useState({
    name: '',
    email: ''
  });
  
  // 数组状态
  const [items, setItems] = useState([]);
  
  const updateUserName = (newName) => {
    setUser(prevUser => ({
      ...prevUser,
      name: newName
    }));
  };
  
  const addItem = (item) => {
    setItems(prevItems => [...prevItems, item]);
  };
  
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加
      </button>
    </div>
  );
}

4. 事件处理

jsx
function EventExample() {
  const [inputValue, setInputValue] = useState('');
  
  // 处理输入事件
  const handleInputChange = (event) => {
    setInputValue(event.target.value);
  };
  
  // 处理表单提交
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('提交的值:', inputValue);
  };
  
  // 处理键盘事件
  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      console.log('按下了回车键');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
        onKeyDown={handleKeyDown}
        placeholder="输入文本..."
      />
      <button type="submit">提交</button>
    </form>
  );
}

🔍 开发者工具使用

浏览器控制台调试

jsx
function DebugExample() {
  const [data, setData] = useState(null);
  
  const fetchData = async () => {
    try {
      console.log('开始获取数据...');
      const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
      const result = await response.json();
      console.log('获取到的数据:', result);
      setData(result);
    } catch (error) {
      console.error('获取数据失败:', error);
    }
  };
  
  return (
    <div>
      <button onClick={fetchData}>获取数据</button>
      {data && (
        <div>
          <h3>{data.title}</h3>
          <p>{data.body}</p>
        </div>
      )}
    </div>
  );
}

React Developer Tools

jsx
// 为组件添加 displayName 便于调试
function UserCard({ user }) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}
UserCard.displayName = 'UserCard';

// 使用 React.memo 优化性能
const OptimizedUserCard = React.memo(UserCard);

🏃‍♂️ 实践练习

练习1:个人资料卡片

jsx
function ProfileCard() {
  const [profile, setProfile] = useState({
    name: '张三',
    title: '前端开发工程师',
    bio: '热爱编程,专注于React开发',
    avatar: 'https://via.placeholder.com/150'
  });
  
  const [isEditing, setIsEditing] = useState(false);
  
  return (
    <div className="profile-card">
      {isEditing ? (
        <div>
          <input 
            value={profile.name}
            onChange={(e) => setProfile({...profile, name: e.target.value})}
          />
          <input 
            value={profile.title}
            onChange={(e) => setProfile({...profile, title: e.target.value})}
          />
          <textarea 
            value={profile.bio}
            onChange={(e) => setProfile({...profile, bio: e.target.value})}
          />
          <button onClick={() => setIsEditing(false)}>保存</button>
        </div>
      ) : (
        <div>
          <img src={profile.avatar} alt="Avatar" />
          <h2>{profile.name}</h2>
          <h3>{profile.title}</h3>
          <p>{profile.bio}</p>
          <button onClick={() => setIsEditing(true)}>编辑</button>
        </div>
      )}
    </div>
  );
}

练习2:简单计算器

jsx
function Calculator() {
  const [display, setDisplay] = useState('0');
  const [previousValue, setPreviousValue] = useState(null);
  const [operation, setOperation] = useState(null);
  const [waitingForNewValue, setWaitingForNewValue] = useState(false);
  
  const inputNumber = (num) => {
    if (waitingForNewValue) {
      setDisplay(String(num));
      setWaitingForNewValue(false);
    } else {
      setDisplay(display === '0' ? String(num) : display + num);
    }
  };
  
  const inputOperation = (nextOperation) => {
    const inputValue = parseFloat(display);
    
    if (previousValue === null) {
      setPreviousValue(inputValue);
    } else if (operation) {
      const currentValue = previousValue || 0;
      const newValue = calculate(currentValue, inputValue, operation);
      
      setDisplay(String(newValue));
      setPreviousValue(newValue);
    }
    
    setWaitingForNewValue(true);
    setOperation(nextOperation);
  };
  
  const calculate = (firstValue, secondValue, operation) => {
    switch (operation) {
      case '+': return firstValue + secondValue;
      case '-': return firstValue - secondValue;
      case '×': return firstValue * secondValue;
      case '÷': return firstValue / secondValue;
      case '=': return secondValue;
      default: return secondValue;
    }
  };
  
  const performCalculation = () => {
    inputOperation('=');
  };
  
  const clear = () => {
    setDisplay('0');
    setPreviousValue(null);
    setOperation(null);
    setWaitingForNewValue(false);
  };
  
  return (
    <div className="calculator">
      <div className="display">{display}</div>
      <div className="buttons">
        <button onClick={clear}>AC</button>
        <button onClick={() => inputOperation('÷')}>÷</button>
        <button onClick={() => inputOperation('×')}>×</button>
        <button onClick={() => inputOperation('-')}>-</button>
        <button onClick={() => inputNumber(7)}>7</button>
        <button onClick={() => inputNumber(8)}>8</button>
        <button onClick={() => inputNumber(9)}>9</button>
        <button onClick={() => inputOperation('+')}>+</button>
        <button onClick={() => inputNumber(4)}>4</button>
        <button onClick={() => inputNumber(5)}>5</button>
        <button onClick={() => inputNumber(6)}>6</button>
        <button onClick={() => inputNumber(1)}>1</button>
        <button onClick={() => inputNumber(2)}>2</button>
        <button onClick={() => inputNumber(3)}>3</button>
        <button onClick={() => inputNumber(0)}>0</button>
        <button onClick={performCalculation}>=</button>
      </div>
    </div>
  );
}

📝 本章小结

通过本章的快速上手学习,你应该掌握了:

关键概念

  • ✅ React 项目的基本结构
  • ✅ 组件的创建和使用
  • ✅ JSX 语法基础
  • ✅ 状态管理基础
  • ✅ 事件处理机制
  • ✅ 基本的调试技巧

实践技能

  • ✅ 创建交互式组件
  • ✅ 处理用户输入
  • ✅ 管理应用状态
  • ✅ 样式和布局
  • ✅ 条件渲染和列表渲染

开发流程

  1. 创建项目:使用 Create React App
  2. 编写组件:函数组件 + Hooks
  3. 添加样式:CSS 样式
  4. 测试功能:浏览器调试
  5. 迭代开发:持续改进

下一步学习

  • 深入了解 React 项目结构
  • 学习更多 Hooks 的使用
  • 掌握组件间通信
  • 了解路由和状态管理

继续学习下一章 - React 项目说明

本站内容仅供学习和研究使用。