一、React核心概念
1. React的核心思想与特点
核心思想:
- 组件化:UI拆分为独立、可复用的组件
- 声明式:描述UI应该是什么样子,而非如何更新
- 单向数据流:数据从父组件流向子组件
主要特点:
javascript
// 1. JSX语法
const element = <h1>Hello, {name}!</h1>;
// 2. 虚拟DOM
// React维护虚拟DOM树,通过diff算法计算最小变更
// 3. 组件复用
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 4. 单向数据流
function Parent() {
const [data, setData] = useState('parent data');
return <Child data={data} />; // 数据向下传递
}
2. 虚拟DOM与Diff算法
虚拟DOM原理:
javascript
// 1. 真实DOM(慢)
const div = document.createElement('div');
div.className = 'container';
div.textContent = 'Hello';
// 2. 虚拟DOM(快)- JavaScript对象
const vdom = {
type: 'div',
props: {
className: 'container',
children: 'Hello'
}
};
// 3. 渲染过程
JSX → 虚拟DOM → Diff算法 → 最小化更新 → 真实DOM
Diff算法策略:
javascript
// 1. 同层比较
// React只比较同一层级的节点,不跨层级比较
// 2. 不同类型元素
// 旧: <div><Child /></div>
// 新: <span><Child /></span>
// 结果: 完全卸载div及其子树,重新创建span
// 3. 相同类型元素
// 旧: <div className="old" />
// 新: <div className="new" />
// 结果: 仅更新className属性
// 4. 列表Diff - key的重要性
// ❌ 没有key - 全部重新渲染
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
// ✅ 有key - 只更新变化的项
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
// 5. key使用规则
// ❌ 使用index作为key(列表会重排时)
{items.map((item, index) => <li key={index}>{item}</li>)}
// ✅ 使用唯一id
{items.map(item => <li key={item.id}>{item}</li>)}
为什么需要虚拟DOM?
javascript
// 直接操作DOM的问题
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
container.appendChild(div); // 触发1000次重排
}
// 虚拟DOM方案
const vdoms = [];
for (let i = 0; i < 1000; i++) {
vdoms.push({ type: 'div', props: { children: i } });
}
// 批量计算差异,一次性更新DOM
3. 函数组件与类组件
函数组件(推荐):
javascript
import React, { useState, useEffect } from 'react';
function FunctionComponent(props) {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('组件挂载或更新');
return () => console.log('组件卸载');
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
类组件:
javascript
import React, { Component } from 'react';
class ClassComponent extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('组件挂载');
}
componentDidUpdate(prevProps, prevState) {
console.log('组件更新');
}
componentWillUnmount() {
console.log('组件卸载');
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
对比:
| 特性 | 函数组件 | 类组件 |
|---|---|---|
| 语法 | 简洁 | 复杂 |
| this绑定 | 无需关心 | 需要手动绑定 |
| 生命周期 | Hooks | 生命周期方法 |
| 性能 | 更优 | 稍差 |
| 趋势 | 官方推荐 | 逐渐淘汰 |
二、React Hooks详解
4. useState的使用与原理
基本使用:
javascript
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 直接更新
const increment = () => setCount(count + 1);
// 函数式更新(推荐)
const increment = () => setCount(prev => prev + 1);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
useState原理简化实现:
javascript
let state = [];
let setters = [];
let cursor = 0;
function useState(initialValue) {
const currentCursor = cursor;
state[currentCursor] = state[currentCursor] || initialValue;
const setState = (newValue) => {
state[currentCursor] = newValue;
render(); // 触发重新渲染
};
setters[currentCursor] = setState;
cursor++;
return [state[currentCursor], setState];
}
function render() {
cursor = 0; // 重置游标
ReactDOM.render(<App />, root);
}
高级用法:
javascript
// 1. 惰性初始化(初始值计算成本高时使用)
const [state, setState] = useState(() => {
const initialState = computeExpensiveValue();
return initialState;
});
// 2. 对象状态更新
const [user, setUser] = useState({ name: '', age: 0 });
// ❌ 错误:直接修改不会触发更新
user.name = 'Alice';
// ✅ 正确:创建新对象
setUser({ ...user, name: 'Alice' });
// 3. 多个相关状态
// ❌ 分散管理
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
// ✅ 集中管理
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
});
5. useEffect的使用与原理
基本使用:
javascript
import { useEffect } from 'react';
function Component() {
// 1. 无依赖 - 每次渲染都执行
useEffect(() => {
console.log('每次渲染都执行');
});
// 2. 空依赖 - 仅挂载时执行一次
useEffect(() => {
console.log('组件挂载');
return () => console.log('组件卸载');
}, []);
// 3. 有依赖 - 依赖变化时执行
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count变化:', count);
}, [count]);
}
常见场景:
javascript
// 1. 数据获取
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
}
fetchData();
}, []);
// 2. 订阅事件
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// 3. 定时器
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
// 4. 监听props变化
useEffect(() => {
if (userId) {
fetchUserData(userId);
}
}, [userId]);
注意事项:
javascript
// ❌ 依赖缺失
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // count始终是初始值
}, 1000);
return () => clearInterval(timer);
}, []); // 缺少count依赖
// ✅ 正确方式1:添加依赖
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, [count]);
// ✅ 正确方式2:函数式更新
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
6. useContext、useReducer、useMemo、useCallback
useContext - 跨组件传递数据:
javascript
import { createContext, useContext } from 'react';
// 1. 创建Context
const ThemeContext = createContext('light');
// 2. Provider提供数据
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. Consumer使用数据
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}
useReducer - 复杂状态管理:
javascript
import { useReducer } from 'react';
// 1. 定义reducer
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error('Unknown action');
}
}
// 2. 使用useReducer
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
// 3. 复杂场景
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'toggle':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case 'delete':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
useMemo - 性能优化(计算缓存):
javascript
import { useMemo } from 'react';
function ExpensiveComponent({ items, filter }) {
// ❌ 每次渲染都计算
const filteredItems = items.filter(item => item.includes(filter));
// ✅ 仅在依赖变化时计算
const filteredItems = useMemo(() => {
console.log('重新计算');
return items.filter(item => item.includes(filter));
}, [items, filter]);
return <List items={filteredItems} />;
}
// 使用场景
// 1. 复杂计算
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// 2. 引用相等性
const config = useMemo(() => ({ api: '/api', timeout: 5000 }), []);
useCallback - 性能优化(函数缓存):
javascript
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// ❌ 每次渲染创建新函数,导致子组件重新渲染
const handleClick = () => {
console.log('clicked');
};
// ✅ 缓存函数引用
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
// 依赖count
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} />;
}
// 子组件使用React.memo优化
const Child = React.memo(({ onClick }) => {
console.log('Child渲染');
return <button onClick={onClick}>Click</button>;
});
useCallback vs useMemo:
javascript
// useCallback缓存函数
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// 等价于
const memoizedCallback = useMemo(() => {
return () => doSomething(a, b);
}, [a, b]);
7. 自定义Hooks
基本规则:
- 以"use"开头命名
- 只在函数组件或其他Hooks中调用
- 遵循Hooks规则(不在循环、条件、嵌套函数中调用)
javascript
// 1. useLocalStorage - 同步localStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', 'Guest');
return <input value={name} onChange={e => setName(e.target.value)} />;
}
// 2. useDebounce - 防抖
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 使用
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// 执行搜索
fetchSearchResults(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />;
}
// 3. useFetch - 数据获取
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isCancelled = false;
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
if (!isCancelled) {
setData(json);
setError(null);
}
} catch (err) {
if (!isCancelled) {
setError(err);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
isCancelled = true;
};
}, [url]);
return { data, loading, error };
}
// 使用
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// 4. useWindowSize - 监听窗口大小
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 5. usePrevious - 获取上一次的值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
三、React性能优化
8. React.memo、PureComponent、shouldComponentUpdate
React.memo - 函数组件优化:
javascript
// 1. 基本使用
const Child = React.memo(({ name }) => {
console.log('Child渲染');
return <div>{name}</div>;
});
// 父组件更新时,如果props未变化,Child不会重新渲染
// 2. 自定义比较函数
const Child = React.memo(
({ user }) => {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// 返回true表示不更新,false表示更新
return prevProps.user.id === nextProps.user.id;
}
);
PureComponent - 类组件优化:
javascript
import React, { PureComponent } from 'react';
class Child extends PureComponent {
render() {
console.log('Child渲染');
return <div>{this.props.name}</div>;
}
}
// PureComponent自动实现shouldComponentUpdate,进行浅比较
shouldComponentUpdate - 手动控制更新:
javascript
class Child extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 返回false阻止更新
if (this.props.name === nextProps.name) {
return false;
}
return true;
}
render() {
return <div>{this.props.name}</div>;
}
}
注意事项:
javascript
// ❌ 浅比较陷阱
function Parent() {
const [count, setCount] = useState(0);
// 每次都是新对象,导致Child总是重新渲染
const user = { name: 'Alice' };
return <Child user={user} />;
}
// ✅ 使用useMemo
function Parent() {
const [count, setCount] = useState(0);
const user = useMemo(() => ({ name: 'Alice' }), []);
return <Child user={user} />;
}
9. 代码分割与懒加载
javascript
import React, { lazy, Suspense } from 'react';
// 1. 路由懒加载
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
// 2. 条件懒加载
function AdminPanel() {
const [showAdmin, setShowAdmin] = useState(false);
const AdminComponent = lazy(() => import('./AdminComponent'));
return (
<div>
<button onClick={() => setShowAdmin(true)}>Show Admin</button>
{showAdmin && (
<Suspense fallback={<div>Loading admin...</div>}>
<AdminComponent />
</Suspense>
)}
</div>
);
}
// 3. 错误边界
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error('Error:', error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
10. 列表渲染优化
javascript
// 1. 虚拟列表(react-window)
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={500}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// 2. 分页加载
function PaginatedList() {
const [page, setPage] = useState(1);
const { data } = useFetch(`/api/items?page=${page}`);
return (
<div>
<List items={data} />
<button onClick={() => setPage(p => p + 1)}>Load More</button>
</div>
);
}
// 3. 无限滚动
function InfiniteScroll() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const observerRef = useRef();
useEffect(() => {
fetchItems(page).then(newItems => {
setItems(prev => [...prev, ...newItems]);
});
}, [page]);
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
setPage(p => p + 1);
}
},
{ threshold: 1.0 }
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div>
{items.map(item => <div key={item.id}>{item.name}</div>)}
<div ref={observerRef}>Loading...</div>
</div>
);
}