Skip to content

React 核心概念与基础语法

本页关键词:JSX/TSX、useState、useEffect、组件即函数、UI = f(state)、依赖数组、StrictMode


一、React 核心思想

React 的核心公式:

UI = f(state)

界面是状态的函数。与 Vue 依赖模板语法和响应式追踪不同,React 更直接——一切都是 JS

对比项VueReact
模板.vue 单文件模板语法JSX(本质是 JS)
响应式自动追踪依赖state 变化 → 函数重新执行 → diff
更新粒度组件级精确更新父组件 re-render → 子组件全部 re-render

面试要点:React 没有"模板响应式追踪",它的更新机制是:重新执行组件函数 → 生成新的虚拟 DOM → diff 比较 → 最小化更新真实 DOM。


二、JSX 与 TSX

JSX 本质

JSX 不是模板,它是 React.createElement 的语法糖:

tsx
// JSX 写法
<h1>Hello React</h1>

// 编译后等价于
React.createElement("h1", null, "Hello React")

JSX 与 TSX 的区别

  • .jsx = JavaScript + JSX 语法
  • .tsx = TypeScript + JSX 语法

TSX 只是多了类型能力,其他完全一样:

tsx
// jsx
export default function App() {
  return <h1>Hello React</h1>
}

// tsx(可以标注返回类型)
export default function App(): JSX.Element {
  return <h1>Hello React</h1>
}

JSX 不是 React 专属,它只是语法扩展。但当前前端岗位默认使用 React + TypeScript,所以统一用 .tsx

JSX 表达式插槽 {}

JSX 中的 {} 类似 Vue 模板的 ,可以放任何 JS 表达式

表达式结果渲染行为
字符串 / 数字渲染为文本
JSX 元素渲染组件/标签
JSX 元素数组依次渲染每个元素
null / undefined / false不渲染

条件渲染与列表渲染

React 没有 v-ifv-for,全部用原生 JS 代替:

tsx
// 条件渲染:三元表达式(对应 Vue 的 v-if)
{todos.length === 0 ? <p>暂无数据</p> : <ul>...</ul>}

// 列表渲染:map()(对应 Vue 的 v-for)
{todos.map((todo, index) => (
  <li key={index}>{todo}</li>
))}

map 是 JavaScript 原生数组方法,不是 React 的 API。它遍历数组,对每个元素执行回调,返回一个新数组。在 JSX 中,map 返回的 JSX 元素数组会被 React 依次渲染。

面试要点:JSX 的 {} 里放的是 JS 表达式,不是模板语法。React 用三元表达式替代 v-if,用 map 替代 v-for,用 && 替代 v-show


三、useState —— 状态管理基础

useState 是 React 最核心的 Hook,用于在函数组件中声明响应式状态。

tsx
import { useState } from "react"

export default function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}

核心机制

  1. state 改变 → 组件函数重新执行(不是局部更新,是整个函数重新跑一遍)
  2. 组件就是函数,每次 render 都是函数重新执行
  3. React 通过 diff 算法只更新真正变化的 DOM 部分

验证方式:在组件函数体内(return 之前)加 console.log("render"),每次 state 变化都会打印。

与 Vue 的对比

javascript
// Vue
const input = ref("")
input.value = "你好"    // 直接通过 .value 修改

// React
const [input, setInput] = useState("")
setInput("你好")        // 必须通过 setter 函数修改,不能直接赋值

不可变更新原则

React 要求不能直接修改 state,必须创建新值:

tsx
// ✗ 错误:直接 push
todos.push("新任务")

// ✓ 正确:创建新数组
setTodos([...todos, "新任务"])

面试要点:React 的 state 更新是不可变的(immutable),必须通过 setter 函数传入新值。直接修改原对象/数组不会触发 re-render。


四、useEffect —— 副作用处理

useEffect 用于处理副作用(数据请求、DOM 操作、订阅等),在 render 之后执行。

tsx
import { useState, useEffect } from "react"

export default function App() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log("count changed:", count)
  }, [count])

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}

依赖数组的三种形态

写法执行时机等价含义
useEffect(fn)每次 render 后都执行无限制
useEffect(fn, [])仅首次 render 后执行一次组件挂载(mounted)
useEffect(fn, [count])首次 + count 变化时执行监听特定依赖

依赖数组写错会导致无限循环,这是 React 开发中最常见的坑之一。

面试要点useEffect 在 render 之后执行,依赖数组控制触发时机。空数组 [] 等价于 onMounted,不传数组等价于每次 render 后都执行。


五、受控组件

React 中表单元素的值由 state 控制,通过 onChange 更新,称为受控组件

tsx
const [input, setInput] = useState("")

<input
  value={input}
  onChange={(e) => setInput(e.target.value)}
  placeholder="输入内容"
/>

这和 Vue 的 v-model 是同一个概念,但 React 需要手动绑定 valueonChange

面试要点:受控组件的值完全由 React state 驱动,每次输入都会触发 onChangesetState → re-render。与之对应的是非受控组件(通过 ref 直接访问 DOM)。


六、key 的作用

列表渲染时,每个元素必须有 key,帮助 React 在 diff 时精确识别元素:

没有 key:React 按顺序对比,删除中间元素时会错误地更新后续元素的内容。

有 key:React 通过 key 精确匹配,直接删除目标 DOM 节点,其他不动。

为什么不建议用 index 做 key

当列表发生增删时,index 会重新分配,导致 key 与元素的对应关系变化,React 无法正确复用 DOM。

正确做法是使用唯一且稳定的 id

tsx
const [todos, setTodos] = useState<{ id: number; text: string }[]>([])

{todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))}

面试要点:key 帮助 React 在 diff 时识别哪些元素变了。用 index 做 key 在列表增删时会导致错误复用,应使用稳定唯一的 id。


七、StrictMode 双重渲染

React 18 的 <StrictMode>开发模式下会故意对组件进行双重渲染,用于检测:

  1. 不纯的渲染逻辑 —— 组件函数应该是纯函数
  2. 遗漏的副作用清理 —— 如 useEffect 中忘记 return 清理函数
tsx
// main.tsx
createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

生产构建不会双重渲染,不影响性能。 开发时看到 console.log 打印两次是正常现象。


八、闭包与 React

在 React 中,组件函数内部的所有函数都是闭包

javascript
function createGreeter(name) {
  return function() {
    console.log("你好," + name)
  }
}

const greetZhang = createGreeter("张三")
greetZhang()  // "你好,张三"
// createGreeter 已执行完毕,但返回的函数仍能访问 name

闭包的定义:函数会"记住"它被创建时所在作用域的变量,即使那个作用域已经执行完毕。

map 循环中,每次迭代创建的箭头函数都通过闭包捕获了当时的 index 值:

tsx
{todos.map((todo, index) => (
  <li key={index} onClick={() => removeTodo(index)}>
    {/* 每个函数捕获了不同的 index */}
    {todo}
  </li>
))}

面试要点:闭包在 React 中的实际影响——函数捕获的是创建时的值,不是最新的值。这就是为什么有时需要 useCallback 和函数式更新(setState(prev => ...))。