一、核心概念与基础(必问)
1. React 的核心思想是什么?
答案:
React 的核心思想可以概括为三个关键概念:
1. 组件化 (Component-Based)
- 将 UI 拆分为独立、可复用的组件
- 每个组件负责自己的状态和渲染逻辑
- 组件可以组合形成复杂的用户界面
- 提高代码的可维护性和复用性
2. 数据驱动 (Data-Driven)
- UI 是数据的函数:
UI = f(data) - 当数据改变时,UI 自动更新
- 开发者只需关注数据变化,无需手动操作 DOM
- 单向数据流,数据流向清晰可预测
3. 虚拟 DOM (Virtual DOM)
- 在内存中维护一个虚拟的 DOM 树
- 通过 Diff 算法比较虚拟 DOM 的变化
- 批量更新真实 DOM,减少重排重绘
- 提供跨浏览器的一致性
核心优势:
- 声明式编程:描述 UI 应该是什么样子,而不是如何操作
- 可预测性:相同的数据总是产生相同的 UI
- 高效更新:通过虚拟 DOM 优化性能
2. 虚拟 DOM (Virtual DOM) 是什么?为什么能提升性能?
答案:
虚拟 DOM 的定义: 虚拟 DOM 是真实 DOM 的 JavaScript 对象表示,是一个轻量级的 DOM 树副本。
工作原理:
// 虚拟DOM对象结构
const virtualDOM = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: { children: 'Hello World' },
},
],
},
}
性能提升的原因:
1. 批量更新 (Batching)
- 将多个 DOM 操作合并为一次更新
- 减少浏览器的重排(reflow)和重绘(repaint)
- 避免频繁的 DOM 操作造成的性能损耗
2. Diff 算法优化
- 只更新发生变化的部分
- 同层比较,避免跨层级的复杂比较
- 使用 key 优化列表渲染
3. 跨浏览器兼容
- 虚拟 DOM 提供统一的 API
- 屏蔽浏览器差异
- 确保一致的渲染结果
性能对比:
// 传统方式:直接操作DOM
document.getElementById('list').innerHTML = '' // 清空
items.forEach((item) => {
const li = document.createElement('li')
li.textContent = item
document.getElementById('list').appendChild(li)
})
// React方式:虚拟DOM
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
)
注意: 虚拟 DOM 不是银弹,在某些场景下(如大量静态内容)可能不如直接操作 DOM 高效,但在大多数动态应用中,虚拟 DOM 能显著提升性能。
3. 类组件和函数组件的区别?
答案:
语法差异:
// 类组件
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
render() {
return <div>{this.state.count}</div>
}
}
// 函数组件
function MyComponent() {
const [count, setCount] = useState(0)
return <div>{count}</div>
}
主要区别:
1. 生命周期 vs Hooks
- 类组件:使用生命周期方法(componentDidMount、componentDidUpdate 等)
- 函数组件:使用 Hooks(useEffect、useState 等)
2. 状态管理
- 类组件:this.state 和 this.setState
- 函数组件:useState Hook
3. 性能优化
- 类组件:PureComponent、shouldComponentUpdate
- 函数组件:React.memo、useMemo、useCallback
4. 代码复杂度
- 类组件:代码更冗长,this 绑定问题
- 函数组件:代码更简洁,逻辑更清晰
5. 设计哲学
- 类组件:面向对象编程思想
- 函数组件:函数式编程思想
现代趋势: React 团队推荐使用函数组件 + Hooks,因为:
- 代码更简洁
- 逻辑复用更容易
- 性能优化更灵活
- 学习成本更低
4. 受控组件 (Controlled) vs 非受控组件 (Uncontrolled)?
答案:
受控组件 (Controlled Components): 表单元素的值由 React 状态控制,通过 props 传入 value,通过 onChange 事件更新状态。
function ControlledInput() {
const [value, setValue] = useState('')
return (
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
)
}
非受控组件 (Uncontrolled Components): 表单元素的值由 DOM 自身管理,通过 ref 获取值。
function UncontrolledInput() {
const inputRef = useRef()
const handleSubmit = () => {
console.log(inputRef.current.value)
}
return <input type="text" ref={inputRef} defaultValue="初始值" />
}
对比分析:
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据流 | 单向,由 React 控制 | 双向,由 DOM 控制 |
| 验证时机 | 实时验证 | 提交时验证 |
| 默认值 | 通过 value 设置 | 通过 defaultValue 设置 |
| 性能 | 每次输入都触发重渲染 | 不触发重渲染 |
| 复杂度 | 较高 | 较低 |
使用场景:
- 受控组件:需要实时验证、格式化输入、复杂表单逻辑
- 非受控组件:简单表单、性能敏感场景、第三方组件集成
最佳实践: 大多数情况下推荐使用受控组件,因为数据流更清晰,状态管理更统一。
5. Keys 的作用是什么?为什么列表的 key 不能用 index?
答案:
Keys 的作用: Keys 帮助 React 识别哪些元素发生了变化、添加或删除,用于优化虚拟 DOM 的 Diff 算法。
为什么需要 Keys:
// 没有key的情况
const items = ['A', 'B', 'C']
// 渲染:<li>A</li><li>B</li><li>C</li>
// 在开头插入'D'
const newItems = ['D', 'A', 'B', 'C']
// 没有key:React会认为所有元素都变了,重新渲染所有li
// 有key:React知道只有第一个是新的,其他只是位置移动
为什么不能用 index 作为 key:
1. 性能问题
// 错误示例
{
items.map((item, index) => <li key={index}>{item}</li>)
}
// 当在列表开头插入新项时
// 原来:key=0(A), key=1(B), key=2(C)
// 现在:key=0(D), key=1(A), key=2(B), key=3(C)
// React认为所有元素都变了,导致不必要的重渲染
2. 状态错乱
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习React', completed: false },
{ id: 2, text: '写代码', completed: false },
])
// 错误:使用index作为key
return (
<ul>
{todos.map((todo, index) => (
<TodoItem
key={index} // ❌ 错误
todo={todo}
onToggle={() => toggleTodo(index)}
/>
))}
</ul>
)
}
// 当删除第一个todo时,第二个todo的key从1变成0
// 但它的状态(如checked)可能不会正确更新
正确的做法:
// 使用唯一且稳定的标识符
{
todos.map((todo) => (
<TodoItem
key={todo.id} // ✅ 正确
todo={todo}
onToggle={() => toggleTodo(todo.id)}
/>
))
}
Key 的最佳实践:
- 使用唯一且稳定的标识符(如 id)
- 避免使用数组索引
- 避免使用随机数
- Key 只需要在同级元素中唯一
6. React 事件系统(合成事件 SyntheticEvent)的特点?与原生事件的区别?
答案:
合成事件 (SyntheticEvent) 的特点:
1. 事件委托 (Event Delegation)
// React将所有事件监听器绑定到根节点
// 通过事件冒泡机制处理所有子元素的事件
<div onClick={handleClick}>
{' '}
// 事件委托到根节点
<button>Click me</button>
</div>
2. 事件池化 (Event Pooling)
function handleClick(e) {
console.log(e.type) // 'click'
// 异步访问事件对象会出错
setTimeout(() => {
console.log(e.type) // null (React 17之前)
}, 100)
// 正确做法:持久化事件对象
e.persist()
setTimeout(() => {
console.log(e.type) // 'click'
}, 100)
}
3. 跨浏览器兼容
// 原生事件:不同浏览器API可能不同
element.addEventListener('click', handler) // 标准
element.attachEvent('onclick', handler) // IE
// 合成事件:统一API
;<button onClick={handler}>Click</button>
与原生事件的区别:
| 特性 | 合成事件 | 原生事件 |
|---|---|---|
| 事件绑定 | 自动绑定到根节点 | 手动绑定到具体元素 |
| 事件对象 | SyntheticEvent 包装 | 原生 Event 对象 |
| 阻止冒泡 | e.stopPropagation() | e.stopPropagation() |
| 阻止默认 | e.preventDefault() | e.preventDefault() |
| 事件池化 | 有(React 17 前) | 无 |
| 跨浏览器 | 完全兼容 | 需要处理兼容性 |
事件处理示例:
function EventExample() {
const handleClick = (e) => {
e.preventDefault() // 阻止默认行为
e.stopPropagation() // 阻止事件冒泡
console.log('Clicked!')
}
return <button onClick={handleClick}>Click me</button>
}
React 17 的变化:
- 移除了事件池化
- 事件委托改为绑定到根容器
- 更接近原生事件的行为
7. 什么是 JSX?它的原理是什么?
答案:
JSX 的定义: JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的标记。
基本语法:
// JSX语法
const element = <h1>Hello, {name}!</h1>
// 编译后的JavaScript
const element = React.createElement('h1', null, 'Hello, ', name, '!')
JSX 的特点:
1. 语法糖
// JSX写法
const App = () => (
<div className="container">
<h1>Title</h1>
<p>Content</p>
</div>
)
// 编译后的JavaScript
const App = () =>
React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Content')
)
2. 表达式嵌入
const name = 'React'
const age = 5
// 在JSX中使用表达式
const element = (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
<p>Next year: {age + 1}</p>
<p>Status: {age > 3 ? 'Old' : 'Young'}</p>
</div>
)
3. 属性处理
// className 而不是 class
<div className="container">
// htmlFor 而不是 for
<label htmlFor="input">Label</label>
// 样式对象
<div style={{ color: 'red', fontSize: '16px' }}>
// 布尔属性
<input type="checkbox" checked={isChecked} />
编译过程:
// 1. JSX源码
const element = <div id="root">Hello World</div>
// 2. Babel编译
const element = React.createElement('div', { id: 'root' }, 'Hello World')
// 3. 运行时执行
const element = {
type: 'div',
props: {
id: 'root',
children: 'Hello World',
},
}
JSX 的优势:
- 可读性强:类似 HTML,直观易懂
- 类型安全:配合 TypeScript 提供类型检查
- 工具支持:IDE 提供语法高亮和自动补全
- 组件化:可以像 HTML 标签一样使用组件
注意事项:
- JSX 必须有一个根元素(React 16+支持 Fragment)
- 标签必须正确闭合
- 属性名使用驼峰命名
- 避免使用 JavaScript 关键字作为属性名