1.React核心概念与基础(1-7)

23 阅读7分钟

一、核心概念与基础(必问)

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 的最佳实践:

  1. 使用唯一且稳定的标识符(如 id)
  2. 避免使用数组索引
  3. 避免使用随机数
  4. 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 的优势:

  1. 可读性强:类似 HTML,直观易懂
  2. 类型安全:配合 TypeScript 提供类型检查
  3. 工具支持:IDE 提供语法高亮和自动补全
  4. 组件化:可以像 HTML 标签一样使用组件

注意事项:

  • JSX 必须有一个根元素(React 16+支持 Fragment)
  • 标签必须正确闭合
  • 属性名使用驼峰命名
  • 避免使用 JavaScript 关键字作为属性名