Skip to content

> 说明 > > 本文按 React 18/19 的常见面试口径整理。少量历史 API(如 getDefaultPropsreplaceStatecomponentWillReceiveProps)会保留背景说明,但会同步标注当前更推荐的写法。

1. React setState 调用的原理

image.png

在 React 中,setStateclass 组件更新状态的核心 API。更准确地说,setState 做的不是“立刻改值”,而是提交一次状态更新请求,再由 React 统一调度后续渲染。

  1. 状态更新请求(Update Request)
    当你调用 this.setState(partialState) 时,并不会立即修改组件的 this.state,而是创建一个状态更新的请求。这个请求可以是一个对象,也可以是一个返回对象的函数。React 会把这个更新请求加入到内部的更新队列中。

  2. 批量更新(Batching)
    为了提高性能,React 会对一段时间内的多个状态更新做批量处理,尽量合并为更少的渲染次数。

    • 对象形式的 setState:会把返回对象浅合并到当前 state
    • 函数形式的 setState:会按顺序消费更新队列,适合基于上一个状态计算下一个状态。
    • React 18+ 自动批处理:不只 React 事件里会批处理,setTimeout、Promise、原生事件等场景里也会自动批处理。
  3. 更新调度与 Fiber 架构
    React 18 及以后的版本基于 Fiber 架构,更新过程更加灵活和可中断。当更新请求被合并后,React 会调度一次更新任务:

    • 优先级控制:更新任务会被分配不同的优先级(比如用户交互的更新优先级较高)。调度器会根据优先级决定何时执行更新。
    • 协调过程(Reconciliation):React 会根据新的状态和现有的虚拟 DOM 生成新的虚拟 DOM 树,然后与之前的虚拟 DOM 进行比较(Diff 算法),找到最小的变化部分。
    • DOM 更新:经过比对后,React 会生成相应的 DOM 更新指令,最后把必要的更新批量应用到真实的 DOM 上。
  4. 生命周期与副作用
    在更新过程中,React 会触发组件的生命周期方法(如 shouldComponentUpdatecomponentDidUpdate 等)。这些方法可以让开发者在合适的时机进行额外的逻辑处理或者避免不必要的更新。

  5. 函数式组件与 Hooks
    在函数组件中,使用 useState Hook 管理状态。底层原理与类组件类似,调用 setState(通常命名为 setX)也会将更新请求加入队列,然后由 React 调度更新并重新渲染组件。React 同样会对这些更新进行批量处理和优先级控制。

小结

  • 不是立即改值setState 会把更新放进队列。
  • 不是纯同步 / 纯异步:更稳妥的理解是,setState 是一次“更新请求”。
  • 批处理 + 调度:React 会按优先级和批处理规则统一安排渲染。

2. React setState 调用之后发生了什么?是同步还是异步?

1. 调用之后发生了什么

调用 setState 后,React 大致会经历下面几步:

  1. 把这次状态变更加入更新队列;
  2. 根据优先级决定何时处理;
  3. 重新执行 render,生成新的 React 元素树;
  4. 和上一次结果做协调(reconciliation);
  5. 只把真正有变化的部分提交到 DOM。

2. 它到底是同步还是异步

这道题最稳妥的回答不是“同步”或“异步”二选一,而是:

> setState 更应该被理解成一次调度请求,而不是立即执行的赋值语句。

因此你会看到两个现象同时存在:

  • 在当前这段同步代码里,this.state 往往还是旧值;
  • 但 React 又不保证一定“晚很久”才更新,它会根据当前批处理和调度时机决定何时提交。

3. 旧口径里最容易答错的点

以前常见说法是:

  • React 事件里是异步批处理;
  • setTimeout、Promise、原生事件里通常是同步更新。

这个说法在 React 18 以前 更接近事实;但从 React 18 的自动批处理开始,这些场景里的更新也常常会被合并。所以今天不建议再拿“原生事件 / 定时器一定同步”当标准答案。

4. 面试里可以怎么总结

  • setState 不会立刻改掉当前执行上下文里的 this.state
  • React 会把它放进更新队列,随后统一处理;
  • React 18+ 会在更多场景里自动批处理;
  • 如果确实要强制同步刷到界面,可以用 flushSync,但这是少数场景下的手段,不应滥用。

3. React 中的 setState 批量更新过程是什么?

批量更新可以理解成:React 先收集更新,再统一计算结果,最后用更少的渲染次数提交 UI。

一个经典例子是:

js
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });

很多人会以为最后 count 增加 2,但实际上通常只会加 1。原因是这两次都读取了同一个旧 state

更稳妥的写法是使用函数式更新:

js
this.setState((prevState) => ({ count: prevState.count + 1 }));
this.setState((prevState) => ({ count: prevState.count + 1 }));

这时 React 会把两个 updater 都放进队列,再按顺序执行:

  1. 取到上一个待生效状态;
  2. 执行第一个 updater;
  3. 把结果继续传给第二个 updater;
  4. 统一完成这次渲染。

结论:

  • 对象形式适合直接赋值;
  • 函数形式适合基于旧值连续更新;
  • React 18+ 的自动批处理让“收集后统一更新”覆盖了更多场景。

4. React 中有使用过 getDefaultProps 吗?它有什么作用?

getDefaultPropsReact.createClass 时代的历史写法,作用是给 props 设置默认值。

今天更常见的写法有两种:

  1. class 组件可以用 static defaultProps
  2. 函数组件更推荐直接用参数默认值

示例:

jsx
class ShowTitle extends React.Component {
  static defaultProps = {
    title: 'React',
  };

  render() {
    return <h1>{this.props.title}</h1>;
  }
}
jsx
function ShowTitle({ title = 'React' }) {
  return <h1>{title}</h1>;
}

所以这道题现在更推荐这么答:

  • getDefaultProps 是老 API,主要出现在历史代码里;
  • 它的本质作用是“设置默认 props”;
  • 现代代码里,class 组件可以使用 defaultProps,函数组件更推荐参数默认值。

5. React 中 setState 的第二个参数作用是什么?

setState 的第二个参数是一个可选回调函数,会在这次更新提交完成后执行。

js
this.setState(
  {
    key1: newState1,
    key2: newState2,
  },
  callback
);

它适用于:

  • 你确实需要在 class 组件里拿到这次更新提交后的结果;
  • 或者要在更新完成后做一些紧跟着的逻辑。

但要补一句:

  • 这是 class 组件 API
  • 在函数组件里没有这个“第二个参数回调”,通常改用 useEffect 观察状态变化。

6. React 中的 setState 和 replaceState 的区别是什么?

这道题需要加一个前提:

> replaceState 属于历史 API,现代 React 开发里基本不再使用。

真正需要掌握的是:

  • setState:对 class 组件的 state浅合并
  • replaceState:早期用于“整对象替换 state”,但现在已经退出主流。

如果今天你需要“完全替换某段状态”,通常有两种做法:

  1. class 组件:直接在 setState 里明确写出新的完整字段;
  2. 函数组件useState 的 setter 本身就是替换,而不是合并。

例如:

jsx
const [user, setUser] = useState({ name: 'Tom', age: 18 });

setUser({ name: 'Jerry', age: 20 }); // 直接替换整个 user

所以面试里可以这么答:

  • setState 是现代 class 组件的标准更新方式;
  • replaceState 主要是历史知识点;
  • 在现代 React 里,更常见的是 class 组件使用 setState,函数组件使用 useState / useReducer

7. 在 React 中组件的 this.state 和 setState 有什么区别?

这道题说的是 class 组件

  • this.state:表示当前组件的状态快照,用来读取状态。
  • this.setState():表示提交一次状态更新请求,用来告诉 React 重新渲染。

关键点是:

  • 可以在初始化时直接给 this.state 赋值;
  • 但在后续更新中,不要直接改 this.state
  • 直接修改 this.state 不会触发重新渲染。

示例:

js
class Counter extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };
}

一句话总结:

  • this.state 负责读
  • this.setState 负责更新

8. state 是怎么注入到组件的,从 reducer 到组件经历了什么样的过程

这道题通常是在问 Redux / 外部 store 的数据是怎么流到 React 组件里的

现代项目里更常见的写法是:

  • <Provider store={store}> 把 store 放进 React 树;
  • 组件里通过 useSelector 读取状态;
  • 通过 useDispatch 派发 action。

示例:

jsx
import { useDispatch, useSelector } from 'react-redux';
import { setVisibilityFilter } from '@/reducers/Todo/actions';

function FilterLink({ filter }) {
  const dispatch = useDispatch();
  const active = useSelector((state) => state.visibilityFilter === filter);

  return (
    <button onClick={() => dispatch(setVisibilityFilter(filter))}>
      {active ? '当前筛选' : '切换筛选'}
    </button>
  );
}

reducer 到组件的大致链路

可以概括成 6 步:

  1. 组件触发事件,调用 dispatch(action)
  2. store 把 action 交给 reducer;
  3. reducer 根据旧 state 和 action 计算出新 state;
  4. store 保存新 state,并通知订阅者;
  5. React-Redux 读取新 state,判断组件依赖的数据是否变化;
  6. 依赖变化的组件重新渲染,拿到新的 props / selector 结果。

connect 还要不要会

要会,但知道它是旧一点但仍受支持的写法即可:

  • connect(mapStateToProps, mapDispatchToProps)(Component) 本质上也是把 store 里的状态和 dispatch 注入组件;
  • 只是现在很多函数组件项目会优先使用 useSelectoruseDispatch

这题里需要避免的老口径

下面这些实现细节现在不建议继续背:

  • 手写 contextTypes 读取 store;
  • componentWillMount 里订阅;
  • 自己实现一版 connect 当标准答案。

知道原理即可,面试回答时优先讲清楚 Provider -> store -> reducer -> 订阅通知 -> 组件重渲染 这条链路。

9. React 组件的 state 和 props 有什么区别?

(1)props

props 是从外部传进组件的参数,主要作用是从父组件向子组件传递数据。它具有只读性和不可变性,组件不能直接修改 props,只能通过父组件传入新的 props 来驱动重新渲染。

(2)state

state 用来保存组件内部的可变数据。它可以来自 class 组件的 this.state,也可以来自函数组件的 useState / useReducer

(3)区别

  • props 是组件的输入,通常来自父组件。
  • state 是组件内部维护的可变数据。
  • props 应保持只读;state 则通过 setStateuseStateuseReducer 等方式更新。
  • props 变化和 state 变化,都会触发组件重新渲染。

10. React 中的 props 为什么是只读的?

props 是组件之间沟通的接口,原则上只能从父组件流向子组件。React 强调单向数据流和“输入决定输出”的思想,所以 props 应该保持只读。

提到函数式编程就要提一个概念:纯函数。它有几个特点:

  • 给定相同的输入,总是返回相同的输出。
  • 过程没有副作用。
  • 不依赖外部状态。

正因为 props 只读,组件才更接近纯函数:相同输入得到相同输出,也更容易定位数据来源和排查问题。

11. 在 React 中组件的 props 改变时,常见的处理方式有哪些?

这道题最容易写成旧生命周期答案。现在更推荐按“场景”来回答,而不是直接背 componentWillReceiveProps

1. 最常见:直接重渲染

大多数情况下,父组件传入新 props 后,子组件会自然重新渲染,你什么都不用做

jsx
function Price({ amount, rate }) {
  return <span>{amount * rate}</span>;
}

2. props 变化后要做副作用

比如重新请求数据、上报埋点、重置订阅等:

  • 函数组件:用 useEffect
  • class 组件:用 componentDidUpdate
jsx
import { useEffect } from 'react';

function UserProfile({ userId }) {
  useEffect(() => {
    fetchUser(userId);
  }, [userId]);

  return null;
}

3. 真的需要“派生 state”

如果你确实要把某个 prop 同步成内部 state,可以考虑 getDerivedStateFromProps,但这是相对少见的场景,必须谨慎使用。

js
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.type !== prevState.type) {
    return { type: nextProps.type };
  }
  return null;
}

4. 不再推荐的旧方案

componentWillReceiveProps 已经属于历史 API,新代码不建议继续使用。面试里知道它是旧生命周期、后来被 UNSAFE_componentWillReceiveProps 替代即可。

5. 一句话总结

  • 能直接根据 props 计算,就直接算
  • 要做副作用,就用 useEffect / componentDidUpdate
  • 确实需要派生 state,再考虑 getDerivedStateFromProps

12. React 中怎么检验 props?验证 props 的目的是什么?

React 常见的 props 校验方式有两类:

  1. 运行时校验PropTypes
  2. 编译期校验:TypeScript

PropTypes 的作用是:当传入的 props 类型不符合预期时,在开发环境给出警告,帮助提升可读性和可维护性。

js
import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string,
};

如果项目使用 TypeScript,那么现在更常见的做法是直接用类型系统描述 props:

tsx
type GreetingProps = {
  name: string;
};

function Greeting({ name }: GreetingProps) {
  return <h1>Hello, {name}</h1>;
}

所以这题可以总结为:

  • PropTypes 适合做运行时提示;
  • TypeScript 适合做更完整的静态类型校验;
  • 在现代 React 项目里,TypeScript 往往更常见。

评论区

欢迎留言、补充或勘误。

xiaoba.blog