React 框架深入教程 — 全球最流行的前端框架

前置要求:HTML/CSS/JavaScript基础、ES6+语法
学习时长:约3-5天(每天4小时)
React版本:18+(Hooks)
适用场景:大型单页应用、移动端(React Native)、企业级项目

一、React 核心概念

概念说明
JSXJavaScript + XML,描述UI
组件独立可复用的UI单元
Hooks函数组件的状态和生命周期
单向数据流数据自上而下流动
虚拟DOM高效的DOM更新机制

二、项目创建

# 使用Vite创建(推荐)
npm create vite@latest my-react-app -- --template react

# 进入项目
cd my-react-app
npm install
npm run dev

# 项目结构
my-react-app/
├── public/
├── src/
│   ├── components/       # 组件
│   ├── hooks/            # 自定义Hooks
│   ├── pages/            # 页面
│   ├── services/         # API服务
│   ├── stores/           # 状态管理
│   ├── utils/            # 工具函数
│   ├── App.jsx           # 根组件
│   ├── main.jsx          # 入口文件
│   └── index.css         # 全局样式
├── index.html
├── vite.config.js
└── package.json


三、JSX 语法

function App() {
  const name = '张三'
  const age = 25
  const isLoggedIn = true
  const items = ['苹果', '香蕉', '橙子']
  
  return (
    <div className="app">
      {/* 变量 */}
      <h1>你好,{name}!</h1>
      <p>年龄:{age}</p>
      
      {/* 表达式 */}
      <p>明年:{age + 1}</p>
      <p>状态:{isLoggedIn ? '已登录' : '未登录'}</p>
      
      {/* 列表 */}
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      
      {/* 条件渲染 */}
      {isLoggedIn && <p>欢迎回来!</p>}
      
      {/* 样式 */}
      <div style={{ color: 'red', fontSize: '16px' }}>
        内联样式
      </div>
    </div>
  )
}


四、Hooks 详解

4.1 useState

import { useState } from 'react'

function Counter() {
  // 基本用法
  const [count, setCount] = useState(0)
  
  // 对象状态
  const [user, setUser] = useState({
    name: '张三',
    age: 25
  })
  
  // 数组状态
  const [items, setItems] = useState([])
  
  // 更新对象(需要展开)
  const updateName = () => {
    setUser(prev => ({ ...prev, name: '李四' }))
  }
  
  // 添加数组项
  const addItem = () => {
    setItems(prev => [...prev, `item-${Date.now()}`])
  }
  
  // 删除数组项
  const removeItem = (index) => {
    setItems(prev => prev.filter((_, i) => i !== index))
  }
  
  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(prev => prev + 1)}>+1 (函数式)</button>
    </div>
  )
}

4.2 useEffect

import { useState, useEffect } from 'react'

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  
  // 组件挂载和userId变化时执行
  useEffect(() => {
    let cancelled = false
    
    const fetchUser = async () => {
      setLoading(true)
      try {
        const response = await fetch(`/api/users/${userId}`)
        const data = await response.json()
        if (!cancelled) {
          setUser(data)
        }
      } finally {
        if (!cancelled) {
          setLoading(false)
        }
      }
    }
    
    fetchUser()
    
    // 清理函数
    return () => {
      cancelled = true
    }
  }, [userId])  // 依赖数组
  
  // 仅在挂载时执行
  useEffect(() => {
    console.log('组件已挂载')
    return () => console.log('组件已卸载')
  }, [])
  
  if (loading) return <div>加载中...</div>
  if (!user) return <div>用户不存在</div>
  
  return <div>{user.name}</div>
}

4.3 自定义Hooks

// hooks/useFetch.js
import { useState, useEffect } from 'react'

export function useFetch(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  
  useEffect(() => {
    let cancelled = false
    
    const fetchData = async () => {
      setLoading(true)
      setError(null)
      
      try {
        const response = await fetch(url)
        if (!response.ok) throw new Error('请求失败')
        const result = await response.json()
        if (!cancelled) setData(result)
      } catch (err) {
        if (!cancelled) setError(err.message)
      } finally {
        if (!cancelled) setLoading(false)
      }
    }
    
    fetchData()
    return () => { cancelled = true }
  }, [url])
  
  return { data, loading, error }
}

// hooks/useDebounce.js
import { useState, useEffect } from 'react'

export function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value)
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)
    
    return () => clearTimeout(timer)
  }, [value, delay])
  
  return debouncedValue
}

// hooks/useLocalStorage.js
import { useState } from 'react'

export function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch {
      return initialValue
    }
  })
  
  const setValue = (value) => {
    const valueToStore = value instanceof Function ? value(storedValue) : value
    setStoredValue(valueToStore)
    window.localStorage.setItem(key, JSON.stringify(valueToStore))
  }
  
  return [storedValue, setValue]
}

// 使用自定义Hooks
import { useFetch } from '../hooks/useFetch'
import { useDebounce } from '../hooks/useDebounce'

function UserList() {
  const [keyword, setKeyword] = useState('')
  const debouncedKeyword = useDebounce(keyword)
  const { data: users, loading } = useFetch(`/api/users?keyword=${debouncedKeyword}`)
  
  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      {loading ? '加载中...' : users?.map(u => <div key={u.id}>{u.name}</div>)}
    </div>
  )
}


五、组件通信

5.1 Props

// 父组件
function Parent() {
  const [count, setCount] = useState(0)
  
  return (
    <Child 
      title="标题"
      count={count}
      onIncrement={() => setCount(prev => prev + 1)}
    />
  )
}

// 子组件
function Child({ title, count, onIncrement }) {
  return (
    <div>
      <h2>{title}</h2>
      <p>计数:{count}</p>
      <button onClick={onIncrement}>+1</button>
    </div>
  )
}

5.2 Context

// 创建Context
const ThemeContext = createContext('light')

// 提供者
function App() {
  const [theme, setTheme] = useState('light')
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  )
}

// 消费者
function Header() {
  const { theme, setTheme } = useContext(ThemeContext)
  
  return (
    <header className={theme}>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        切换主题
      </button>
    </header>
  )
}


六、React Router

npm install react-router-dom

// App.jsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="users" element={<UserList />} />
          <Route path="users/:id" element={<UserDetail />} />
          <Route path="admin" element={<ProtectedRoute><Admin /></ProtectedRoute>}>
            <Route index element={<Dashboard />} />
            <Route path="users" element={<AdminUsers />} />
          </Route>
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

// 布局组件
import { Outlet, NavLink } from 'react-router-dom'

function Layout() {
  return (
    <div>
      <nav>
        <NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
          首页
        </NavLink>
        <NavLink to="/users">用户</NavLink>
        <NavLink to="/admin">后台</NavLink>
      </nav>
      <Outlet />  {/* 子路由渲染位置 */}
    </div>
  )
}

// 获取路由参数
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'

function UserDetail() {
  const { id } = useParams()           // 路径参数
  const navigate = useNavigate()        // 编程式导航
  const [searchParams] = useSearchParams()  // 查询参数
  
  const page = searchParams.get('page')
  
  return (
    <div>
      <p>用户ID:{id}</p>
      <p>页码:{page}</p>
      <button onClick={() => navigate('/')}>返回首页</button>
      <button onClick={() => navigate(-1)}>返回上一页</button>
    </div>
  )
}

// 路由守卫
function ProtectedRoute({ children }) {
  const token = localStorage.getItem('token')
  
  if (!token) {
    return <Navigate to="/login" replace />
  }
  
  return children
}


七、状态管理(Zustand)

npm install zustand

// stores/useUserStore.js
import { create } from 'zustand'

const useUserStore = create((set, get) => ({
  // State
  userInfo: null,
  token: localStorage.getItem('token') || '',
  
  // Getters(通过函数实现)
  isLoggedIn: () => !!get().token,
  displayName: () => get().userInfo?.nickname || get().userInfo?.username || '未登录',
  
  // Actions
  login: async (credentials) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(credentials)
    })
    const data = await response.json()
    
    set({ token: data.token, userInfo: data.user })
    localStorage.setItem('token', data.token)
  },
  
  fetchUser: async () => {
    const response = await fetch('/api/user', {
      headers: { Authorization: `Bearer ${get().token}` }
    })
    const user = await response.json()
    set({ userInfo: user })
  },
  
  logout: () => {
    set({ token: '', userInfo: null })
    localStorage.removeItem('token')
  }
}))

export default useUserStore

// 使用Store
import useUserStore from '../stores/useUserStore'

function Header() {
  const { userInfo, isLoggedIn, logout } = useUserStore()
  
  return (
    <header>
      {isLoggedIn() ? (
        <>
          <span>{userInfo?.username}</span>
          <button onClick={logout}>退出</button>
        </>
      ) : (
        <a href="/login">登录</a>
      )}
    </header>
  )
}


八、表单处理

import { useState } from 'react'

function UserForm({ onSubmit }) {
  const [form, setForm] = useState({
    username: '',
    email: '',
    role: 'user'
  })
  const [errors, setErrors] = useState({})
  const [loading, setLoading] = useState(false)
  
  const handleChange = (e) => {
    const { name, value } = e.target
    setForm(prev => ({ ...prev, [name]: value }))
    // 清除错误
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }))
    }
  }
  
  const validate = () => {
    const newErrors = {}
    
    if (!form.username) {
      newErrors.username = '用户名不能为空'
    } else if (form.username.length < 3) {
      newErrors.username = '用户名至少3个字符'
    }
    
    if (!form.email) {
      newErrors.email = '邮箱不能为空'
    } else if (!/\S+@\S+\.\S+/.test(form.email)) {
      newErrors.email = '邮箱格式不正确'
    }
    
    setErrors(newErrors)
    return Object.keys(newErrors).length === 0
  }
  
  const handleSubmit = async (e) => {
    e.preventDefault()
    if (!validate()) return
    
    setLoading(true)
    try {
      await onSubmit(form)
    } finally {
      setLoading(false)
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>用户名:</label>
        <input
          name="username"
          value={form.username}
          onChange={handleChange}
        />
        {errors.username && <span className="error">{errors.username}</span>}
      </div>
      
      <div>
        <label>邮箱:</label>
        <input
          name="email"
          type="email"
          value={form.email}
          onChange={handleChange}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <label>角色:</label>
        <select name="role" value={form.role} onChange={handleChange}>
          <option value="user">普通用户</option>
          <option value="admin">管理员</option>
        </select>
      </div>
      
      <button type="submit" disabled={loading}>
        {loading ? '提交中...' : '提交'}
      </button>
    </form>
  )
}


九、常用UI库

组件库适用场景特点
Ant Design企业级后台组件最全、设计规范
Material UIMaterial风格Google设计语言
Chakra UI现代应用简洁灵活
Shadcn/ui自定义风格可复制组件代码
Headless UI无样式组件完全自定义样式

十、Vue vs React 对比

特性Vue 3React
模板语法<template>JSX
状态管理ref() / reactive()useState()
副作用watchEffect()useEffect()
全局状态PiniaZustand/Redux
路由Vue RouterReact Router
学习曲线★★☆☆☆★★★☆☆
中文生态★★★★★★★★☆☆
全球市场★★★★☆★★★★★

学习建议

  1. 先掌握JSX语法,理解React的思维方式
  2. 深入理解Hooks,特别是useState和useEffect
  3. 学会自定义Hooks,逻辑复用的关键
  4. 掌握React Router,单页应用必备
  5. 学习状态管理,Zustand是目前最推荐的方案

下一步学习

返回首页