Appearance
React 基础面试题
1. React 基础概念
问题:React 的核心特性是什么?
答案:
核心特性:
- 组件化:
jsx
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 使用
<Welcome name="John" />- JSX:
jsx
// JSX 语法
const element = <h1>Hello, world!</h1>;
// 编译为
const element = React.createElement(
'h1',
null,
'Hello, world!'
);
// 表达式
const name = 'John';
const element = <h1>Hello, {name}</h1>;
// 属性
const element = <div className="greeting">Hello</div>;
// 子元素
const element = (
<div>
<h1>Hello</h1>
<p>Good to see you</p>
</div>
);- 虚拟 DOM:
- React 使用虚拟 DOM 来提高渲染性能
- 通过 diff 算法比较新旧虚拟 DOM
- 只更新需要变化的部分
- 单向数据流:
jsx
// 父组件传递数据给子组件
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} />;
}
function Child({ count }) {
return <p>Count: {count}</p>;
}2. State 和 Props
问题:React 中的 State 和 Props 有什么区别?
答案:
Props:
jsx
// Props 是从父组件传递给子组件的数据
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用
<Greeting name="John" />
// 解构 props
function Greeting({ name, age }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
</div>
);
}
// Props 类型检查
import PropTypes from 'prop-types';
Greeting.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
};
// 默认 props
Greeting.defaultProps = {
name: 'Guest',
age: 0
};State:
jsx
import { useState } from 'react';
function Counter() {
// useState 返回 [当前值, 更新函数]
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
// 类组件中的 state
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}State 和 Props 的区别:
| 特性 | Props | State |
|---|---|---|
| 来源 | 父组件传递 | 组件内部 |
| 可变性 | 只读 | 可变 |
| 作用域 | 组件间传递 | 组件内部 |
| 更新方式 | 父组件更新 | setState/setState |
3. 生命周期
问题:React 组件的生命周期有哪些?
答案:
函数组件生命周期(Hooks):
jsx
import { useState, useEffect, useRef } from 'react';
function LifecycleComponent() {
const [count, setCount] = useState(0);
const mounted = useRef(false);
// componentDidMount + componentDidUpdate
useEffect(() => {
console.log('组件已挂载或更新');
});
// componentDidMount
useEffect(() => {
console.log('组件已挂载');
mounted.current = true;
// componentWillUnmount
return () => {
console.log('组件将卸载');
mounted.current = false;
};
}, []);
// 监听特定依赖
useEffect(() => {
console.log('count 变化了:', count);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}类组件生命周期:
jsx
class LifecycleComponent extends React.Component {
// 挂载阶段
constructor(props) {
super(props);
console.log('constructor');
}
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps');
return null;
}
componentDidMount() {
console.log('componentDidMount');
}
// 更新阶段
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate');
return true;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate');
return null;
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate');
}
// 卸载阶段
componentWillUnmount() {
console.log('componentWillUnmount');
}
render() {
return <div>Component</div>;
}
}4. React Hooks
问题:React Hooks 如何使用?
答案:
useState:
jsx
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(prev => prev + 1)}>
Increment with callback
</button>
</div>
);
}
// 对象 state
function UserForm() {
const [user, setUser] = useState({
name: '',
email: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setUser(prev => ({
...prev,
[name]: value
}));
};
return (
<form>
<input
name="name"
value={user.name}
onChange={handleChange}
/>
<input
name="email"
value={user.email}
onChange={handleChange}
/>
</form>
);
}useEffect:
jsx
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, []); // 空依赖数组,只在挂载时执行
useEffect(() => {
document.title = `Data: ${data?.title}`;
}, [data]); // data 变化时执行
if (loading) return <p>Loading...</p>;
return <div>{data?.title}</div>;
}useContext:
jsx
// 创建 Context
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}useReducer:
jsx
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>
Increment
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
Decrement
</button>
</div>
);
}useMemo 和 useCallback:
jsx
function ExpensiveComponent({ a, b }) {
// 缓存计算结果
const expensiveValue = useMemo(() => {
console.log('Computing expensive value');
return a * b;
}, [a, b]);
// 缓存函数
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return (
<div>
<p>Value: {expensiveValue}</p>
<button onClick={handleClick}>Click</button>
</div>
);
}useRef:
jsx
function FocusInput() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
// 保存之前的值
function PreviousValue({ value }) {
const prevValue = useRef();
useEffect(() => {
prevValue.current = value;
}, [value]);
return (
<div>
<p>Current: {value}</p>
<p>Previous: {prevValue.current}</p>
</div>
);
}5. 事件处理
问题:React 中如何处理事件?
答案:
基本事件处理:
jsx
function Button() {
const handleClick = (e) => {
e.preventDefault();
console.log('Button clicked');
};
return (
<button onClick={handleClick}>
Click me
</button>
);
}
// 传递参数
function ButtonList() {
const buttons = ['A', 'B', 'C'];
return (
<div>
{buttons.map((button, index) => (
<button
key={index}
onClick={() => console.log(button)}
>
{button}
</button>
))}
</div>
);
}事件对象:
jsx
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted');
};
const handleChange = (e) => {
const { name, value } = e.target;
console.log(`${name}: ${value}`);
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
console.log('Enter key pressed');
}
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<button type="submit">Submit</button>
</form>
);
}合成事件:
jsx
function EventDemo() {
const handleClick = (e) => {
// 合成事件对象
console.log('Event type:', e.type);
console.log('Event target:', e.target);
console.log('Event currentTarget:', e.currentTarget);
// 原生事件
console.log('Native event:', e.nativeEvent);
};
return <button onClick={handleClick}>Click me</button>;
}6. 条件渲染和列表渲染
问题:React 中如何进行条件渲染和列表渲染?
答案:
条件渲染:
jsx
function Greeting({ isLoggedIn }) {
// 三元运算符
return (
<div>
{isLoggedIn ? <Welcome /> : <Login />}
</div>
);
}
// 逻辑与运算符
function Notification({ message }) {
return (
<div>
{message && <p>{message}</p>}
</div>
);
}
// 立即调用函数
function Greeting({ user }) {
return (
<div>
{(() => {
if (!user) return <Login />;
if (user.role === 'admin') return <AdminPanel />;
return <UserPanel />;
})()}
</div>
);
}列表渲染:
jsx
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={todo.id}>
{index + 1}. {todo.text}
</li>
))}
</ul>
);
}
// 嵌套列表
function NestedList({ data }) {
return (
<ul>
{data.map(item => (
<li key={item.id}>
{item.name}
{item.children && (
<NestedList data={item.children} />
)}
</li>
))}
</ul>
);
}7. 表单处理
问题:React 中如何处理表单?
答案:
受控组件:
jsx
function Form() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form data:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}非受控组件:
jsx
function UncontrolledForm() {
const usernameRef = useRef();
const emailRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const formData = {
username: usernameRef.current.value,
email: emailRef.current.value
};
console.log('Form data:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
name="username"
ref={usernameRef}
defaultValue=""
/>
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
ref={emailRef}
defaultValue=""
/>
</div>
<button type="submit">Submit</button>
</form>
);
}表单验证:
jsx
function ValidatedForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.username) {
newErrors.username = 'Username is required';
}
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Email is invalid';
}
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('Form submitted:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
/>
{errors.username && <span className="error">{errors.username}</span>}
</div>
{/* 其他字段... */}
<button type="submit">Submit</button>
</form>
);
}8. 组件通信
问题:React 组件之间如何通信?
答案:
父子通信:
jsx
// 父组件
function Parent() {
const [message, setMessage] = useState('Hello from parent');
const handleMessage = (msg) => {
setMessage(msg);
};
return (
<div>
<p>Message: {message}</p>
<Child message={message} onMessage={handleMessage} />
</div>
);
}
// 子组件
function Child({ message, onMessage }) {
const handleClick = () => {
onMessage('Hello from child');
};
return (
<div>
<p>Received: {message}</p>
<button onClick={handleClick}>Send Message</button>
</div>
);
}兄弟组件通信:
jsx
// 使用 Context
const MessageContext = React.createContext();
function Parent() {
const [message, setMessage] = useState('');
return (
<MessageContext.Provider value={{ message, setMessage }}>
<SiblingA />
<SiblingB />
</MessageContext.Provider>
);
}
function SiblingA() {
const { setMessage } = useContext(MessageContext);
return (
<button onClick={() => setMessage('Hello from A')}>
Send Message
</button>
);
}
function SiblingB() {
const { message } = useContext(MessageContext);
return <p>Message: {message}</p>;
}跨层级通信:
jsx
// 使用 Context 跨层级传递数据
const ThemeContext = React.createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<Header />
<Main />
<Footer />
</ThemeContext.Provider>
);
}
function Header() {
const theme = useContext(ThemeContext);
return <header className={theme}>Header</header>;
}
function Main() {
const theme = useContext(ThemeContext);
return <main className={theme}>Main</main>;
}9. React Router
问题:React Router 如何使用?
答案:
基本路由:
jsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users">Users</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
</Routes>
</BrowserRouter>
);
}动态路由:
jsx
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</BrowserRouter>
);
}
function UserDetail() {
const { id } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => setUser(data));
}, [id]);
if (!user) return <p>Loading...</p>;
return <div>User: {user.name}</div>;
}嵌套路由:
jsx
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/admin" element={<AdminLayout />}>
<Route path="dashboard" element={<Dashboard />} />
<Route path="users" element={<UserManagement />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function AdminLayout() {
return (
<div>
<nav>
<Link to="/admin/dashboard">Dashboard</Link>
<Link to="/admin/users">Users</Link>
<Link to="/admin/settings">Settings</Link>
</nav>
<Outlet />
</div>
);
}路由守卫:
jsx
function ProtectedRoute({ children }) {
const isAuthenticated = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return children;
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}10. React 性能优化
问题:如何优化 React 组件性能?
答案:
React.memo:
jsx
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered');
return <div>{data}</div>;
});
// 使用
function Parent() {
const [count, setCount] = useState(0);
const data = useMemo(() => 'expensive data', []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<ExpensiveComponent data={data} />
</div>
);
}useMemo 和 useCallback:
jsx
function OptimizedComponent({ a, b, onClick }) {
// 缓存计算结果
const result = useMemo(() => {
console.log('Computing...');
return a * b;
}, [a, b]);
// 缓存函数
const handleClick = useCallback(() => {
console.log('Clicked');
onClick();
}, [onClick]);
return (
<div>
<p>Result: {result}</p>
<button onClick={handleClick}>Click</button>
</div>
);
}虚拟列表:
jsx
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>Item {items[index]}</div>
);
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={35}
width={300}
>
{Row}
</FixedSizeList>
);
}代码分割:
jsx
import { lazy, Suspense } from 'react';
// 懒加载组件
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}避免不必要的渲染:
jsx
// 使用 shouldComponentUpdate(类组件)
class OptimizedComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
render() {
return <div>{this.props.data}</div>;
}
}
// 使用 React.memo(函数组件)
const MemoizedComponent = React.memo(function MemoizedComponent({ data }) {
return <div>{data}</div>;
}, (prevProps, nextProps) => {
return prevProps.data === nextProps.data;
});