React 基础入门
大约 10 分钟约 2982 字
React 基础入门
简介
React 是当前最主流的前端 UI 库之一,核心思想是“用状态驱动界面”。相比传统 DOM 操作,React 更强调组件拆分、单向数据流、声明式渲染和可组合的 Hooks 机制,适合构建从简单后台页面到复杂中大型单页应用的各种界面系统。
特点
实现
JSX 与函数组件
// UserCard.tsx
interface UserCardProps {
name: string
email: string
avatar?: string
isVip?: boolean
onEdit?: () => void
}
export function UserCard({ name, email, avatar, isVip = false, onEdit }: UserCardProps) {
return (
<section className="user-card">
<img src={avatar ?? '/default-avatar.png'} alt={`${name} avatar`} width={56} height={56} />
<div>
<h3>
{name}
{isVip && <span className="vip-badge">VIP</span>}
</h3>
<p>{email}</p>
</div>
<button type="button" onClick={onEdit}>
编辑
</button>
</section>
)
}// App.tsx
import { UserCard } from './UserCard'
export default function App() {
return (
<main>
<UserCard
name="张三"
email="zhangsan@example.com"
isVip
onEdit={() => console.log('edit user')}
/>
</main>
)
}// JSX 注意点
export function Demo() {
const title = 'React 基础'
const isOnline = true
return (
<div>
<h1>{title}</h1>
<p>{isOnline ? '在线' : '离线'}</p>
</div>
)
}useState 与事件处理
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
const [step, setStep] = useState(1)
const increase = () => setCount(prev => prev + step)
const decrease = () => setCount(prev => prev - step)
const reset = () => {
setCount(0)
setStep(1)
}
return (
<div>
<h2>当前计数:{count}</h2>
<label>
步长:
<input
type="number"
value={step}
onChange={e => setStep(Number(e.target.value) || 1)}
/>
</label>
<div style={{ marginTop: 12 }}>
<button onClick={decrease}>-</button>
<button onClick={reset}>重置</button>
<button onClick={increase}>+</button>
</div>
</div>
)
}// 对象状态更新要保留旧值
import { useState } from 'react'
export function ProfileEditor() {
const [form, setForm] = useState({ name: '', phone: '', city: '' })
const updateField = (key: keyof typeof form, value: string) => {
setForm(prev => ({ ...prev, [key]: value }))
}
return (
<div>
<input value={form.name} onChange={e => updateField('name', e.target.value)} placeholder="姓名" />
<input value={form.phone} onChange={e => updateField('phone', e.target.value)} placeholder="手机号" />
<input value={form.city} onChange={e => updateField('city', e.target.value)} placeholder="城市" />
</div>
)
}列表渲染与条件渲染
import { useMemo, useState } from 'react'
type User = {
id: number
name: string
role: 'admin' | 'user'
active: boolean
}
const initialUsers: User[] = [
{ id: 1, name: '张三', role: 'admin', active: true },
{ id: 2, name: '李四', role: 'user', active: false },
{ id: 3, name: '王五', role: 'user', active: true },
]
export function UserList() {
const [keyword, setKeyword] = useState('')
const [users, setUsers] = useState(initialUsers)
const filteredUsers = useMemo(() => {
return users.filter(user => user.name.includes(keyword))
}, [users, keyword])
const toggleActive = (id: number) => {
setUsers(prev => prev.map(user => (user.id === id ? { ...user, active: !user.active } : user)))
}
return (
<section>
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="搜索用户"
/>
{filteredUsers.length === 0 ? (
<p>没有匹配结果</p>
) : (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>
<span>
{user.name} / {user.role} / {user.active ? '启用' : '停用'}
</span>
<button onClick={() => toggleActive(user.id)}>切换状态</button>
</li>
))}
</ul>
)}
</section>
)
}// key 必须稳定,不要优先用 index
items.map(item => <li key={item.id}>{item.name}</li>)表单处理与受控组件
import { FormEvent, useState } from 'react'
type LoginFormState = {
username: string
password: string
remember: boolean
}
export function LoginForm() {
const [form, setForm] = useState<LoginFormState>({
username: '',
password: '',
remember: false,
})
const [errors, setErrors] = useState<Record<string, string>>({})
const updateField = (field: keyof LoginFormState, value: string | boolean) => {
setForm(prev => ({ ...prev, [field]: value }))
}
const validate = () => {
const nextErrors: Record<string, string> = {}
if (!form.username.trim()) nextErrors.username = '请输入用户名'
if (!form.password.trim()) nextErrors.password = '请输入密码'
if (form.password && form.password.length < 6) nextErrors.password = '密码至少 6 位'
setErrors(nextErrors)
return Object.keys(nextErrors).length === 0
}
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
if (!validate()) return
console.log('submit', form)
}
return (
<form onSubmit={handleSubmit}>
<div>
<input
value={form.username}
onChange={e => updateField('username', e.target.value)}
placeholder="用户名"
/>
{errors.username && <small>{errors.username}</small>}
</div>
<div>
<input
type="password"
value={form.password}
onChange={e => updateField('password', e.target.value)}
placeholder="密码"
/>
{errors.password && <small>{errors.password}</small>}
</div>
<label>
<input
type="checkbox"
checked={form.remember}
onChange={e => updateField('remember', e.target.checked)}
/>
记住我
</label>
<button type="submit">登录</button>
</form>
)
}useEffect 与异步请求
import { useEffect, useState } from 'react'
type Profile = {
id: number
name: string
email: string
}
export function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<Profile | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
useEffect(() => {
const controller = new AbortController()
const load = async () => {
setLoading(true)
setError('')
try {
const resp = await fetch(`/api/users/${userId}`, { signal: controller.signal })
if (!resp.ok) throw new Error('获取用户失败')
const data: Profile = await resp.json()
setUser(data)
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
setError(err instanceof Error ? err.message : '未知错误')
} finally {
setLoading(false)
}
}
load()
return () => controller.abort()
}, [userId])
if (loading) return <p>加载中...</p>
if (error) return <p>{error}</p>
if (!user) return <p>用户不存在</p>
return <div>{user.name} / {user.email}</div>
}组件通信与状态提升
function SearchBox({ value, onChange }: { value: string; onChange: (value: string) => void }) {
return <input value={value} onChange={e => onChange(e.target.value)} placeholder="搜索关键词" />
}
function ProductTable({ keyword }: { keyword: string }) {
const products = ['键盘', '鼠标', '耳机', '显示器']
const visible = products.filter(item => item.includes(keyword))
return (
<ul>
{visible.map(item => <li key={item}>{item}</li>)}
</ul>
)
}
export function ProductPage() {
const [keyword, setKeyword] = useState('')
return (
<div>
<SearchBox value={keyword} onChange={setKeyword} />
<ProductTable keyword={keyword} />
</div>
)
}useReducer 管理复杂状态
import { useReducer } from 'react'
type CartItem = {
id: number
name: string
price: number
quantity: number
}
type CartAction =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'REMOVE_ITEM'; payload: number }
| { type: 'UPDATE_QUANTITY'; payload: { id: number; quantity: number } }
| { type: 'CLEAR' }
type CartState = {
items: CartItem[]
total: number
}
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find(i => i.id === action.payload.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === action.payload.id
? { ...i, quantity: i.quantity + 1 }
: i
),
total: state.total + action.payload.price
}
}
return {
items: [...state.items, { ...action.payload, quantity: 1 }],
total: state.total + action.payload.price
}
}
case 'REMOVE_ITEM': {
const item = state.items.find(i => i.id === action.payload)
return {
items: state.items.filter(i => i.id !== action.payload),
total: state.total - (item ? item.price * item.quantity : 0)
}
}
case 'UPDATE_QUANTITY': {
const item = state.items.find(i => i.id === action.payload.id)
const diff = action.payload.quantity - (item?.quantity ?? 0)
return {
items: state.items.map(i =>
i.id === action.payload.id
? { ...i, quantity: action.payload.quantity }
: i
),
total: state.total + (item ? item.price * diff : 0)
}
}
case 'CLEAR':
return { items: [], total: 0 }
default:
return state
}
}
export function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, { items: [], total: 0 })
const addItem = (item: Omit<CartItem, 'quantity'>) => {
dispatch({ type: 'ADD_ITEM', payload: { ...item, quantity: 1 } })
}
return (
<div>
<h2>购物车 ({cart.items.length} 件)</h2>
<p>总计: ¥{cart.total.toFixed(2)}</p>
<ul>
{cart.items.map(item => (
<li key={item.id}>
{item.name} x {item.quantity} = ¥{(item.price * item.quantity).toFixed(2)}
<button onClick={() => dispatch({ type: 'REMOVE_ITEM', payload: item.id })}>
删除
</button>
</li>
))}
</ul>
<button onClick={() => dispatch({ type: 'CLEAR' })}>清空购物车</button>
</div>
)
}useRef 与 DOM 操作
import { useRef, useEffect } from 'react'
export function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return <input ref={inputRef} placeholder="自动聚焦" />
}
// useRef 保存非渲染状态(定时器、上一次值等)
export function TimerComponent() {
const [count, setCount] = useState(0)
const timerRef = useRef<ReturnType<typeof setInterval>>()
const prevCountRef = useRef(0)
useEffect(() => {
prevCountRef.current = count
}, [count])
const start = () => {
if (timerRef.current) return
timerRef.current = setInterval(() => {
setCount(c => c + 1)
}, 1000)
}
const stop = () => {
if (timerRef.current) {
clearInterval(timerRef.current)
timerRef.current = undefined
}
}
return (
<div>
<p>当前: {count},上一次: {prevCountRef.current}</p>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
</div>
)
}useCallback 与 useMemo 性能优化
import { useState, useCallback, useMemo, memo } from 'react'
// memo:浅比较 props,避免不必要的重渲染
const ExpensiveList = memo(({ items, onItemClick }: {
items: string[]
onItemClick: (index: number) => void
}) => {
console.log('ExpensiveList rendered')
return (
<ul>
{items.map((item, i) => (
<li key={i} onClick={() => onItemClick(i)}>{item}</li>
))}
</ul>
)
})
export function OptimizedComponent() {
const [keyword, setKeyword] = useState('')
const [count, setCount] = useState(0)
// 所有数据
const allItems = useMemo(() =>
Array.from({ length: 1000 }, (_, i) => `项目 ${i + 1}`),
[]
)
// 过滤后的数据(只在 keyword 变化时重新计算)
const filteredItems = useMemo(() =>
allItems.filter(item => item.includes(keyword)),
[allItems, keyword]
)
// 稳定的回调引用(避免子组件重渲染)
const handleItemClick = useCallback((index: number) => {
console.log('clicked', index)
}, [])
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加计数</button>
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="搜索项目"
/>
<p>匹配 {filteredItems.length} 项</p>
<ExpensiveList items={filteredItems.slice(0, 50)} onItemClick={handleItemClick} />
</div>
)
}性能优化要点:
- memo:包裹子组件,避免父组件无关状态变化时子组件重渲染
- useMemo:缓存计算结果,只在依赖变化时重新计算
- useCallback:缓存回调函数,保持引用稳定
- 不是所有组件都需要优化,只在性能瓶颈处使用
- 优先保证代码正确和可读,再考虑优化自定义 Hook 封装
import { useState, useEffect, useCallback } from 'react'
// 通用数据请求 Hook
type UseFetchResult<T> = {
data: T | null
loading: boolean
error: string
refetch: () => void
}
export function useFetch<T>(url: string, options?: RequestInit): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const fetchData = useCallback(async () => {
setLoading(true)
setError('')
try {
const resp = await fetch(url, options)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const result: T = await resp.json()
setData(result)
} catch (err) {
setError(err instanceof Error ? err.message : '请求失败')
} finally {
setLoading(false)
}
}, [url])
useEffect(() => {
const controller = new AbortController()
fetchData()
return () => controller.abort()
}, [fetchData])
return { data, loading, error, refetch: fetchData }
}
// 使用示例
function ProductList() {
const { data: products, loading, error, refetch } = useFetch<Product[]>('/api/products')
if (loading) return <p>加载中...</p>
if (error) return <p>错误: {error}</p>
return (
<div>
<button onClick={refetch}>刷新</button>
<ul>
{products?.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
</div>
)
}
// 通用分页 Hook
type UsePaginationResult = {
page: number
pageSize: number
total: number
setPage: (page: number) => void
setTotal: (total: number) => void
totalPages: number
}
export function usePagination(initialPage = 1, initialSize = 20): UsePaginationResult {
const [page, setPage] = useState(initialPage)
const [pageSize] = useState(initialSize)
const [total, setTotal] = useState(0)
const totalPages = Math.ceil(total / pageSize)
return { page, pageSize, total, setPage, setTotal, totalPages }
}组件设计模式
// Render Props 模式
type MouseTrackerProps = {
render: (position: { x: number; y: number }) => React.ReactNode
}
function MouseTracker({ render }: MouseTrackerProps) {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handler = (e: MouseEvent) => setPosition({ x: e.clientX, y: e.clientY })
window.addEventListener('mousemove', handler)
return () => window.removeEventListener('mousemove', handler)
}, [])
return render(position)
}
// Compound Components 模式(组合组件)
function Tabs({ children, defaultIndex = 0 }: { children: React.ReactNode; defaultIndex?: number }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex)
return (
<div>
{React.Children.map(children, (child, i) =>
React.isValidElement(child)
? React.cloneElement(child, { activeIndex, setActiveIndex, index: i })
: child
)}
</div>
)
}
function TabList({ children, activeIndex, setActiveIndex }: any) {
return (
<div role="tablist">
{React.Children.map(children, (child, i) =>
React.isValidElement(child)
? React.cloneElement(child, { isActive: i === activeIndex, onClick: () => setActiveIndex(i) })
: child
)}
</div>
)
}
function Tab({ children, isActive, onClick }: any) {
return (
<button role="tab" aria-selected={isActive} onClick={onClick}
style={{ fontWeight: isActive ? 'bold' : 'normal' }}>
{children}
</button>
)
}
// 使用
<Tabs defaultIndex={0}>
<TabList>
<Tab>标签一</Tab>
<Tab>标签二</Tab>
</TabList>
</Tabs>组件设计原则:
- 单一职责:一个组件只做一件事
- Props 向下,事件向上:单向数据流
- 组合优于继承:通过 children 和 props 组合
- 抽取自定义 Hook:将状态逻辑从 UI 中分离
- 避免 prop drilling:超过 3 层传递时考虑 Context 或状态库优点
缺点
总结
React 基础真正要掌握的是:组件、Props、State、事件、列表渲染、表单处理和副作用。只要能把“状态变化如何驱动界面变化”这条主线理解清楚,后面的 Router、状态管理、性能优化和 SSR 都会更容易上手。
关键知识点
- React 是“状态驱动 UI”,不是“操作 DOM 驱动 UI”。
useState管状态,useEffect处理副作用。- Props 只读,子组件不能直接修改父组件状态。
- key 的稳定性会直接影响列表渲染行为。
项目落地视角
- 管理后台最常见的就是表格、表单、弹窗、筛选和详情页组件化。
- 业务页面通常先从组件拆分和状态提升做起,而不是先引入复杂状态库。
- 中大型项目建议一开始就配合 TypeScript 和 ESLint 规范使用。
- 与后端接口协作时,要同时考虑 loading、error、empty 三种状态展示。
常见误区
- 用 React 但仍然习惯手动操作 DOM。
- 在
useEffect里漏写依赖,或者把所有东西都塞进依赖数组。 - 列表使用数组 index 当 key,导致渲染和状态错位。
- 组件拆得太碎或太大,导致维护成本上升。
进阶路线
- 学习 React Router 做页面路由管理。
- 学习 Zustand、Redux Toolkit 或 React Query 做中大型项目状态治理。
- 研究
memo、useMemo、useCallback和懒加载优化。 - 进一步学习 Next.js、SSR、Server Components。
适用场景
- 后台管理系统。
- 企业 Web 应用。
- 前后端分离单页应用。
- 需要多端延伸能力的产品项目。
落地建议
- 新项目优先使用函数组件 + Hooks + TypeScript。
- 表单和数据请求组件尽量抽成可复用模块。
- 每个异步页面至少处理 loading / empty / error 三态。
- 先写清状态边界,再决定是否引入全局状态管理。
排错清单
- 页面不刷新时,先看状态有没有真正变化。
- 列表渲染异常时,先检查 key 是否稳定。
- 副作用重复执行时,先检查
useEffect依赖和 StrictMode。 - 表单输入错乱时,先检查受控组件的 value / onChange 是否成对出现。
复盘问题
- 这个页面的状态到底应该归组件本地,还是归父组件、全局状态?
- 哪些组件是真正可复用的,哪些只是当前页面特有逻辑?
- 当前 bug 是渲染问题、状态问题,还是副作用问题?
- 如果后续要做 SSR 或性能优化,当前写法是否容易演进?
