从入门到核心概念的全面掌握
React 是由 Facebook 开发的用于构建用户界面的 JavaScript 库。它通过组件化和声明式编程的设计思想,彻底改变了前端开发的方式。
React 解决的核心问题:
核心设计思想:
React 组件是构建应用的基本单元。JSX 是 JavaScript 的语法扩展,允许在 JS 中编写类似 HTML 的代码。
JSX 核心特性:
React.createElement() 调用<img />、<input />function WelcomeMessage({ name }) {
return <h1>欢迎, {name}!</h1>;
}
// 使用组件
<WelcomeMessage name="开发者" />
State (状态) 是组件内部的数据,当状态改变时,组件会重新渲染。useState 是最常用的 Hook。
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}
React 采用单向数据流:数据从父组件通过 Props 传递给子组件,保证数据流向清晰可控。
// 父组件
function UserList() {
const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
];
return users.map(user =>
<UserItem key={user.id} user={user} />
);
}
// 子组件
function UserItem({ user }) {
return <div>{user.name}</div>;
}
Hooks 让函数组件拥有状态和生命周期能力。useEffect 用于处理副作用(如数据获取、订阅等)。
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// 组件挂载时获取数据
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
// 清理函数(组件卸载时执行)
return () => {
console.log('清理资源');
};
}, [userId]); // 依赖项: userId 变化时重新执行
return <div>{user?.name}</div>;
}
useEffect 依赖项最佳实践:
React 通过虚拟 DOM (Virtual DOM) 优化性能。当状态改变时,React 先在内存中对比新旧虚拟 DOM 树,计算出最小差异,然后只更新必要的真实 DOM 节点。
协调过程的关键优化:
Context API 提供了一种在组件树中传递数据的方法,无需手动逐层传递 props。适用于主题、用户信息、语言设置等全局数据。
// 创建 Context
const ThemeContext = React.createContext('light');
// 提供者组件
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 消费者组件
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}
⚠️ 使用建议:
Redux 是一个用于 JavaScript 应用的可预测状态容器,特别适合大型应用的复杂状态管理。它基于三大核心原则:单一数据源、状态只读、使用纯函数修改状态。
// 1. 定义 Action Types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 2. 创建 Action Creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });
// 3. 创建 Reducer
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
// 4. 创建 Store
import { createStore } from 'redux';
const store = createStore(counterReducer);
// 5. 在组件中使用 (React-Redux)
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<p>计数: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
Redux Toolkit 是 Redux 官方推荐的工具集,简化了 Redux 开发流程。
// 使用 createSlice 简化代码
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => {
state.value += 1; // 可以直接修改(内部使用 Immer)
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// 组件中使用
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>增加</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}
Redux vs Context API - 何时使用?
// 使用 Redux Thunk 处理异步逻辑
import { createAsyncThunk } from '@reduxjs/toolkit';
// 创建异步 thunk
const fetchUser = createAsyncThunk(
'users/fetchById',
async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
// 使用
function UserProfile({ userId }) {
const dispatch = useDispatch();
const { data, loading } = useSelector(state => state.user);
useEffect(() => {
dispatch(fetchUser(userId));
}, [userId]);
if (loading) return <div>加载中...</div>;
return <div>{data?.name}</div>;
}
React 应用的性能优化是构建高质量应用的关键。以下是几种常用的优化技术。
// 子组件只在 props 变化时重新渲染
const MemoizedChild = React.memo(function Child({ name, age }) {
console.log('Child 渲染');
return <div>{name} - {age}岁</div>;
});
// 自定义比较函数
const MemoizedChild = React.memo(Child, (prevProps, nextProps) => {
return prevProps.id === nextProps.id; // 返回 true 则跳过渲染
});
function ProductList({ products, filter }) {
// 只在 products 或 filter 变化时重新计算
const filteredProducts = useMemo(() => {
console.log('执行过滤逻辑');
return products.filter(p => p.category === filter);
}, [products, filter]);
return <div>{filteredProducts.map(p => ...)}</div>;
}
function Parent() {
const [count, setCount] = useState(0);
// 函数引用保持不变,避免子组件重渲染
const handleClick = useCallback(() => {
console.log('点击了按钮');
}, []); // 依赖项为空,函数永不改变
return <MemoizedChild onClick={handleClick} />;
}
// 使用 React.lazy 和 Suspense 实现懒加载
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
);
}
自定义 Hooks 允许你提取组件逻辑,实现代码复用。自定义 Hook 是一个以 "use" 开头的函数,内部可以调用其他 Hooks。
function useLocalStorage(key, initialValue) {
// 从 localStorage 读取初始值
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
// 更新 state 和 localStorage
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用自定义 Hook
function App() {
const [name, setName] = useLocalStorage('name', 'Anonymous');
return <input value={name} onChange={e => setName(e.target.value)} />;
}
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
fetch(url)
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => { isMounted = false; };
}, [url]);
return { data, loading, error };
}
// 使用
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <div>{data?.name}</div>;
}
自定义 Hooks 最佳实践:
在渲染列表时,为每个元素提供唯一且稳定的 key,帮助 React 识别哪些元素发生了变化。
// ✓ 正确:使用唯一ID
{users.map(user =>
<UserItem key={user.id} user={user} />
)}
// ✗ 错误:使用索引(数据顺序变化时会出问题)
{users.map((user, index) =>
<UserItem key={index} user={user} />
)}
每个组件应该只负责一个功能,保持组件小而专注,便于复用和测试。
// ✓ 好的设计:职责分离
function UserProfile({ user }) {
return (
<>
<UserAvatar url={user.avatar} />
<UserInfo name={user.name} email={user.email} />
</>
);
}
使用 React.memo、useMemo、useCallback 优化性能,但不要过度优化。
// 使用 React.memo 避免不必要的重渲染
const ExpensiveComponent = React.memo(({ data }) => {
return <div>{/* 复杂渲染逻辑 */}</div>;
});
// 使用 useMemo 缓存计算结果
const sortedData = useMemo(() => {
return data.sort((a, b) => a.value - b.value);
}, [data]);
在 useEffect 中处理异步操作时,注意清理和竞态条件。
useEffect(() => {
let cancelled = false;
async function fetchData() {
const result = await api.getData();
if (!cancelled) {
setData(result);
}
}
fetchData();
// 清理函数防止内存泄漏
return () => { cancelled = true; };
}, []);
React 推崇组合而非继承。当多个组件需要共享状态时,将状态提升到最近的公共父组件。
// ✓ 使用组合
function Dialog({ title, children }) {
return (
<div className="dialog">
<h2>{title}</h2>
{children}
</div>
);
}
// 使用
<Dialog title="欢迎">
<p>感谢访问!</p>
<button>确定</button>
</Dialog>
每次渲染创建新引用会导致子组件不必要的重新渲染。
// ✗ 错误:每次渲染都创建新对象
function Parent() {
return <Child config={{ theme: 'dark' }} />;
}
// ✓ 正确:使用 useMemo 缓存
function Parent() {
const config = useMemo(() => ({ theme: 'dark' }), []);
return <Child config={config} />;
}