React 常见问题
jsx // 传统方式(命令式) const btn = document.getElementById('btn'); btn.addEventListener('click', () => { document.getElementById('text').innerText = '已点击'; });
// React 方式(声明式) function App() { const [clicked, setClicked] = useState(false); return (
{clicked ? '已点击' : '未点击'}
#### 虚拟 DOM 是什么?它是如何提升性能的?
虚拟 DOM(Virtual DOM)是真实 DOM 的轻量级 JavaScript 对象表示。React 会在内存中维护一棵虚拟 DOM 树,当状态改变时,先计算出新的虚拟 DOM 树,然后与旧的树进行对比(Diff 算法),最终只将真正变化的部分更新到真实 DOM 中。这避免了直接操作 DOM 的高昂开销,使得 UI 更新更高效。
#### 为什么 JSX 里写 HTML 不会报错?JSX 到底是什么?
JSX 是一种 JavaScript 的语法扩展,看起来像在 JS 里写 HTML。实际上,JSX 会被 Babel 等编译器转换成 `React.createElement()` 函数调用,最终生成 JavaScript 对象(虚拟 DOM 节点)。所以浏览器并不会直接执行 JSX,它只是开发阶段的语法糖。
```jsx
const element = <h1 className="greeting">Hello!</h1>;
// 编译后:
const element = React.createElement('h1', { className: 'greeting' }, 'Hello!');
组件与数据篇
函数组件和类组件有什么区别?现在该用哪种?
函数组件是使用 JavaScript 函数定义的组件,类组件则是使用 ES6 的 class 定义并继承 React.Component。在 React 16.8 引入 Hooks 之后,函数组件也能拥有状态和生命周期逻辑,而类组件逐渐成为历史遗留方式。官方推荐使用 函数组件 + Hooks 编写新代码,因为它更简洁、更易测试和拆分逻辑。
// 函数组件(推荐)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
state 和 props 有什么区别?什么时候用哪个?
- props(属性):由父组件传递给子组件,是只读的,子组件不能修改接收到的 props。
- state(状态):组件内部私有的数据,可以修改,通过
useStateHook 或类组件的this.setState更新。
记住一个原则:数据属于哪个组件,就应该放在哪个组件的 state 里,然后通过 props 向下传递。如果多个组件需要共享数据,应该将状态提升到最近的公共祖先。
如何正确更新 state?为什么直接修改 state 不生效?
在函数组件中,必须使用 useState 返回的更新函数,例如 setCount(count + 1)。类组件中需使用 this.setState。直接给 state 赋值是无效的,因为 React 需要知道数据变化才能触发重新渲染。同时更新函数可能是异步的,如果新值依赖于旧值,应该使用函数式更新:
// 错误
count = 5;
// 正确
setCount(5);
// 依赖旧值时
setCount(prevCount => prevCount + 1);
Hooks 高频问题
useState 的初始值如何惰性计算?
如果 useState 的初始值需要复杂计算,可以传入一个函数,该函数只会在初次渲染时执行一次。
const [data, setData] = useState(() => {
// 只在初始化时执行
return expensiveComputation();
});
useEffect 何时执行?为什么会出现无限循环?
useEffect 默认在每次渲染后执行。如果你在 effect 内部更新了 effect 所依赖的状态,就会导致“渲染 → 执行 effect → 更新状态 → 再渲染”的死循环。解决方法是正确设置依赖数组,只让 effect 在指定值变化时执行。
useEffect(() => {
// 错误:没有依赖数组,每次渲染都执行,且内部更新了 count → 无限循环
setCount(count + 1);
});
// 正确:空数组表示只在挂载时执行一次
useEffect(() => {
// 获取初始数据
}, []);
useMemo 和 useCallback 到底解决什么问题?
它们都用于性能优化,避免不必要的重新计算或函数重新创建。
- useMemo:缓存计算结果,当依赖项未变时直接返回上次的计算值。
- useCallback:缓存函数引用,避免因函数引用变化导致子组件无意义重渲染。
通常只在计算开销大、且依赖稳定的场景下使用,不要过早地滥用它们。
自定义 Hook 如何抽取复用逻辑?
自定义 Hook 就是一个以 use 开头的函数,内部可以调用其他 Hook。它可以把组件逻辑抽取成可复用的函数,例如处理表单输入、监听窗口尺寸等。
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return width;
}